init commit

This commit is contained in:
rainbownapkin 2021-12-06 19:56:40 -05:00
parent ae639426d0
commit 7a491681cc
257 changed files with 95524 additions and 80 deletions

589
www/js/acp.js Normal file
View file

@ -0,0 +1,589 @@
(function () {
var chosenServer = IO_SERVERS[0]; // Is the array even necessary for the ACP?
var opts = {
secure: chosenServer.secure,
withCredentials: true // needed for sio cookie to work
};
window.socket = io.connect(chosenServer.url, opts);
window.socket.on("connect", function () {
window.socket.emit("initACP");
window.socket.emit("acp-list-activechannels");
readEventlog();
});
window.socket.on("errMessage", function (data) {
alert(data.msg);
});
})();
function addMenuItem(target, text) {
var ul = $("#nav-acp-section ul");
var li = $("<li/>").appendTo(ul);
var a = $("<a/>").attr("href", "javascript:void(0)")
.text(text)
.appendTo(li)
.click(function () {
$(".acp-panel").hide();
$(target).show();
});
};
addMenuItem("#acp-logview", "Log Viewer");
addMenuItem("#acp-announcements", "Announcements");
addMenuItem("#acp-global-bans", "Global Bans");
addMenuItem("#acp-user-lookup", "Users");
addMenuItem("#acp-channel-lookup", "Channels");
addMenuItem("#acp-loaded-channels", "Active Channels");
addMenuItem("#acp-eventlog", "Event Log");
/* Log Viewer */
function readSyslog() {
$.ajax(location.protocol + "//" + location.host + "/acp/syslog").done(function (data) {
$("#acp-log").text(data);
$("#acp-log").scrollTop($("#acp-log").prop("scrollHeight"));
});
}
function readErrlog() {
$.ajax(location.protocol + "//" + location.host + "/acp/errlog").done(function (data) {
$("#acp-log").text(data);
$("#acp-log").scrollTop($("#acp-log").prop("scrollHeight"));
});
}
function readHttplog() {
$.ajax(location.protocol + "//" + location.host + "/acp/httplog").done(function (data) {
$("#acp-log").text(data);
$("#acp-log").scrollTop($("#acp-log").prop("scrollHeight"));
});
}
function readEventlog() {
$.ajax(location.protocol + "//" + location.host + "/acp/eventlog").done(function (data) {
handleEventLog(data);
});
}
function readChanlog(name) {
$.ajax(location.protocol + "//" + location.host + "/acp/chanlog/" + name).done(function (data) {
$("#acp-log").text(data);
$("#acp-log").scrollTop($("#acp-log").prop("scrollHeight"));
});
}
$("#acp-syslog-btn").click(readSyslog);
$("#acp-errlog-btn").click(readErrlog);
$("#acp-httplog-btn").click(readHttplog);
$("#acp-chanlog-name").keyup(function (ev) {
if (ev.keyCode === 13) {
readChanlog($("#acp-chanlog-name").val());
}
});
/* Announcements */
$("#acp-announce-submit").click(function () {
socket.emit("acp-announce", {
title: $("#acp-announce-title").val(),
content: $("#acp-announce-content").val()
});
});
socket.on("announcement", function (data) {
$("#acp-announcements").find(".announcement").remove();
var signature = "<br>\u2014" + data.from;
var al = makeAlert(data.title, data.text + signature)
.removeClass("col-md-12")
.addClass("announcement")
.insertAfter($("#acp-announcements h3")[0]);
al.find(".close").click(function () {
socket.emit("acp-announce-clear");
});
$("#acp-announce-title").val(data.title);
$("#acp-announce-content").val(data.text);
});
/* Global bans */
$("#acp-gban-submit").click(function () {
socket.emit("acp-gban", {
ip: $("#acp-gban-ip").val(),
note: $("#acp-gban-note").val()
});
});
socket.on("acp-gbanlist", function (bans) {
var tbl = $("#acp-global-bans table");
tbl.find("tbody").remove();
bans.forEach(function (b) {
var tr = $("<tr/>").appendTo(tbl);
var td = $("<td/>").appendTo(tr);
var del = $("<button/>").addClass("btn btn-xs btn-danger")
.html("<span class='glyphicon glyphicon-trash'></span>")
.click(function () {
socket.emit("acp-gban-delete", b);
})
.appendTo(td);
td = $("<td/>").appendTo(tr).html("<code>" + b.ip + "</code>");
td = $("<td/>").appendTo(tr).text(b.note);
});
});
/* User listing */
(function () {
var doSearch = function () {
if ($("#acp-ulookup-query").val().trim() === "") {
if (!confirm("You are about to list the entire users table. " +
"This table might be very large and take a long " +
"time to query. Continue?")) {
return;
}
}
socket.emit("acp-list-users", {
value: $("#acp-ulookup-query").val(),
field: $(this).data()["field"]
});
};
$("#acp-ulookup-btn-name").click(doSearch);
$("#acp-ulookup-btn-email").click(doSearch);
$("#acp-ulookup-query").keyup(function (ev) {
if (ev.keyCode === 13) {
$("#acp-ulookup-btn-name").click();
}
});
})();
socket.on("acp-list-users", function (users) {
var tbl = $("#acp-user-lookup table");
tbl.data("entries", users);
var p = tbl.data("paginator");
if (p) {
p.paginator.remove();
}
var opts = {
preLoadPage: function () {
tbl.find("tbody").remove();
},
generator: function (u, page, index) {
var tr = $("<tr/>").appendTo(tbl);
tr.attr("title", u.name + " joined on " + new Date(u.time) + " from IP " + u.ip);
$("<td/>").text(u.id).appendTo(tr);
$("<td/>").text(u.name).appendTo(tr);
var rank = $("<td/>").text(u.global_rank).appendTo(tr);
$("<td/>").text(u.email).appendTo(tr);
var reset = $("<td/>").appendTo(tr);
// Rank editor
rank.click(function () {
if (rank.find(".rank-edit").length > 0) {
return;
}
var old = rank.text();
rank.text("");
var editor = $("<input/>").addClass("rank-edit form-control")
.attr("type", "text")
.attr("placeholder", old)
.appendTo(rank)
.focus();
var save = function () {
var newrank = editor.val();
if (newrank.trim() === "") {
newrank = old;
}
rank.text(old);
if (newrank === old) {
return;
}
socket.emit("acp-set-rank", {
name: u.name,
rank: parseInt(newrank)
});
};
editor.blur(save);
editor.keydown(function (ev) {
if (ev.keyCode === 13) {
save();
}
});
});
// Password reset
$("<button/>").addClass("btn btn-xs btn-danger")
.text("Reset password")
.click(function () {
if (!confirm("Really reset password for " + u.name + "?")) {
return;
}
socket.emit("acp-reset-password", {
name: u.name,
email: u.email
}, function (result) {
if (result.error) {
modalAlert({
title: "Error",
textContent: result.error
});
} else {
var link = new URL("/account/passwordrecover/" + result.hash,
new URL(location));
modalAlert({
title: "Reset Link",
textContent: link
});
}
});
}).appendTo(reset);
}
};
p = Paginate(users, opts);
p.paginator.css("margin-top", "20px");
p.paginator.insertBefore(tbl);
tbl.data("paginator", p);
});
socket.on("acp-set-rank", function (data) {
var table = $("#acp-user-lookup table");
var p = table.data("paginator");
var e = table.data("entries");
if (e) {
for (var i = 0; i < e.length; i++) {
if (e[i].name === data.name) {
e[i].rank = data.rank;
break;
}
}
if (p) {
p.items = e;
}
}
table.find("td:contains('" + data.name + "')")
.parent()
.children()[2]
.innerHTML = data.rank;
});
/* Channel listing */
(function () {
var doSearch = function () {
if ($("#acp-clookup-value").val().trim() === "") {
if (!confirm("You are about to list the entire channels table. " +
"This table might be very large and take a long " +
"time to query. Continue?")) {
return;
}
}
socket.emit("acp-list-channels", {
field: $("#acp-clookup-field").val(),
value: $("#acp-clookup-value").val()
});
};
$("#acp-clookup-submit").click(doSearch);
$("#acp-clookup-value").keyup(function (ev) {
if (ev.keyCode === 13) {
doSearch();
}
});
})();
socket.on("acp-list-channels", function (channels) {
var tbl = $("#acp-channel-lookup table");
tbl.data("entries", channels);
var p = tbl.data("paginator");
if (p) {
p.paginator.remove();
}
var opts = {
preLoadPage: function () {
tbl.find("tbody").remove();
},
generator: function (c, page, index) {
var tr = $("<tr/>").appendTo(tbl);
tr.attr("title", c.name + " was registered on " + new Date(c.time));
$("<td/>").text(c.id).appendTo(tr);
$("<td/>").text(c.name).appendTo(tr);
$("<td/>").text(c.owner).appendTo(tr);
$("<td/>").text(c.last_loaded).appendTo(tr);
$("<td/>").text(c.owner_last_seen).appendTo(tr);
var remove = $("<td/>").appendTo(tr);
// Drop channel
$("<button/>").addClass("btn btn-xs btn-danger")
.text("Delete channel")
.click(function () {
if (!confirm("Really delete " + c.owner + "/" + c.name + "?")) {
return;
}
socket.emit("acp-delete-channel", {
name: c.name,
});
}).appendTo(remove);
}
};
p = Paginate(channels, opts);
p.paginator.css("margin-top", "20px");
p.paginator.insertBefore(tbl);
tbl.data("paginator", p);
});
socket.on("acp-delete-channel", function (data) {
var table = $("#acp-channel-lookup table");
var p = table.data("paginator");
var e = table.data("entries");
var found = -1;
if (e) {
for (var i = 0; i < e.length; i++) {
if (e[i].name === data.name) {
found = i;
break;
}
}
if (found > 0) {
e.splice(found, 1);
}
if (p) {
p.items = e;
}
}
table.find("td:contains('" + data.name + "')")
.parent()
.remove();
});
/* Active channels */
function showChannelDetailModal(c) {
var wrap = $("<div/>").addClass("modal fade").appendTo($("body"));
var dialog = $("<div/>").addClass("modal-dialog").appendTo(wrap);
var content = $("<div/>").addClass("modal-content").appendTo(dialog);
var head = $("<div/>").addClass("modal-header").appendTo(content);
$("<button/>").addClass("close")
.attr("data-dismiss", "modal")
.attr("data-hidden", "true")
.html("&times;")
.appendTo(head);
$("<h4/>").addClass("modal-title").text(c.name).appendTo(head);
var body = $("<div/>").addClass("modal-body").appendTo(content);
var table = $("<table/>").addClass("table table-striped table-compact")
.appendTo(body);
var tr;
tr = $("<tr/>").appendTo(table);
$("<td/>").text("Page Title").appendTo(tr);
$("<td/>").text(c.pagetitle).appendTo(tr);
tr = $("<tr/>").appendTo(table);
$("<td/>").text("Current Media").appendTo(tr);
$("<a/>").attr("href", c.mediaLink).text(c.mediatitle).appendTo(
$("<td/>").appendTo(tr)
);
tr = $("<tr/>").appendTo(table);
$("<td/>").text("User Count").appendTo(tr);
$("<td/>").text(c.usercount).appendTo(tr);
tr = $("<tr/>").appendTo(table);
$("<td/>").text("User List").appendTo(tr);
$("<td/>").text(c.users.join(" ")).appendTo(tr);
tr = $("<tr/>").appendTo(table);
$("<td/>").text("Registered").appendTo(tr);
$("<td/>").text(c.registered).appendTo(tr);
tr = $("<tr/>").appendTo(table);
$("<td/>").text("Public").appendTo(tr);
$("<td/>").text(c.public).appendTo(tr);
tr = $("<tr/>").appendTo(table);
$("<td/>").text("ActiveLock Count").appendTo(tr);
$("<td/>").text(c.activeLockCount).appendTo(tr);
tr = $("<tr/>").appendTo(table);
$("<td/>").text("Chat Filter Count").appendTo(tr);
$("<td/>").text(c.chatFilterCount).appendTo(tr);
tr = $("<tr/>").appendTo(table);
$("<td/>").text("Emote Count").appendTo(tr);
$("<td/>").text(c.emoteCount).appendTo(tr);
$("<h3/>").text("Recent Chat").appendTo(body);
$("<pre/>").text(c.chat.map(function (data) {
var msg = "<" + data.username;
if (data.addClass) {
msg += "." + data.addClass;
}
msg += "> " + data.msg;
msg = "[" + new Date(data.time).toTimeString().split(" ")[0] + "] " + msg;
return msg;
}).join("\n")).appendTo(body);
wrap.on("hidden.bs.modal", function () {
wrap.remove();
});
wrap.modal();
}
socket.on("acp-list-activechannels", function (channels) {
console.log(channels[0]);
var tbl = $("#acp-loaded-channels table");
tbl.find("tbody").remove();
channels.sort(function (a, b) {
if (a.usercount === b.usercount) {
var x = a.name.toLowerCase();
var y = b.name.toLowerCase();
return x === y ? 0 : (x > y ? 1 : -1);
}
return a.usercount > b.usercount ? -1 : 1;
});
var count = 0;
channels.forEach(function (c) {
var tr = $("<tr/>").appendTo(tbl);
var name = $("<td/>").appendTo(tr);
$("<a/>").attr("href", `/${CHANNELPATH}/${c.name}`)
.text(c.pagetitle + ` (/${CHANNELPATH}/${c.name})`)
.appendTo(name);
var usercount = $("<td/>").text(c.usercount).appendTo(tr);
count += c.usercount;
var nowplaying = $("<td/>").text(c.mediatitle).appendTo(tr);
var registered = $("<td/>").text(c.registered).appendTo(tr);
var public = $("<td/>").text(c.public).appendTo(tr);
var controlOuter = $("<td/>").appendTo(tr);
var controlInner = $("<div/>").addClass("btn-group").appendTo(controlOuter);
$("<button/>").addClass("btn btn-default btn-xs")
.html("<span class='glyphicon glyphicon-list-alt'></span>")//.text("Details")
.attr("title", "Details")
.appendTo(controlInner)
.click(function () {
showChannelDetailModal(c);
});
$("<button/>").addClass("btn btn-danger btn-xs")
.html("<span class='glyphicon glyphicon-remove'></span>")//.text("Force Unload")
.attr("title", "Unload")
.appendTo(controlInner)
.click(function () {
if (confirm(`Are you sure you want to unload /${CHANNELPATH}/${c.name}?`)) {
socket.emit("acp-force-unload", {
name: c.name
});
}
});
});
var total = $("<tr/>").appendTo(tbl);
$("<td/>").html("<strong>Total</strong>").appendTo(total);
$("<td/>").html("<strong>" + count + "</strong>").appendTo(total);
$("<td/>").appendTo(total);
$("<td/>").appendTo(total);
$("<td/>").appendTo(total);
$("<td/>").appendTo(total);
});
$("#acp-lchannels-refresh").click(function () {
socket.emit("acp-list-activechannels");
});
/* Event log */
function getEventKey(line) {
var left = line.indexOf("[", 1);
var right = line.indexOf("]", left);
return line.substring(left+1, right);
}
function handleEventLog(data) {
data = data.split("\n").filter(function (ln) { return ln.indexOf("[") === 0; });
var keys = {};
data.forEach(function (ln) {
keys[getEventKey(ln)] = true;
});
$("#acp-eventlog-text").data("lines", data);
$("#acp-eventlog-filter").html("");
for (var k in keys) {
$("<option/>").attr("value", k)
.text(k)
.appendTo($("#acp-eventlog-filter"));
}
filterEventLog();
}
function filterEventLog() {
var selected = $("#acp-eventlog-filter").val();
var all = selected == null || selected.length === 0;
var lines = $("#acp-eventlog-text").data("lines");
var show = [];
lines.forEach(function (ln) {
if (all || selected.indexOf(getEventKey(ln)) !== -1) {
show.push(ln);
}
});
$("#acp-eventlog-text").text(show.join("\n"));
$("#acp-eventlog-text").scrollTop($("#acp-eventlog-text").prop("scrollHeight"));
}
$("#acp-eventlog-filter").change(filterEventLog);
$("#acp-eventlog-refresh").click(readEventlog);
/* Initialize keyed table sorts */
$("table").each(function () {
var table = $(this);
var sortable = table.find("th.sort");
sortable.each(function () {
var th = $(this);
th.click(function () {
var p = table.data("paginator");
if (!p) {
return;
}
var key = th.attr("data-key");
if (!key) {
return;
}
var asc = -th.attr("data-sort-direction") || -1;
th.attr("data-sort-direction", asc);
var entries = table.data("entries") || [];
entries.sort(function (a, b) {
return a[key] === b[key] ? 0 : asc*(a[key] > b[key] ? 1 : -1);
});
table.data("entries", entries);
p.items = entries;
p.loadPage(0);
});
});
});

1348
www/js/callbacks.js Normal file

File diff suppressed because it is too large Load diff

30
www/js/dash.all.min.js vendored Normal file

File diff suppressed because one or more lines are too long

244
www/js/data.js Normal file
View file

@ -0,0 +1,244 @@
var CL_VERSION = 3.0;
var GS_VERSION = 1.7; // Google Drive Userscript
var CLIENT = {
rank: -1,
leader: false,
name: "",
logged_in: false,
profile: {
image: "",
text: ""
}
};
var SUPERADMIN = false;
var CHANNEL = {
opts: {},
openqueue: false,
perms: {},
css: "",
js: "",
motd: "",
name: CHANNELNAME,
usercount: 0,
emotes: []
};
var PLAYER = false;
var LIVESTREAM_CHROMELESS = false;
var FLUIDLAYOUT = false;
var VWIDTH;
var VHEIGHT;
if($("#videowidth").length > 0) {
VWIDTH = $("#videowidth").css("width").replace("px", "");
VHEIGHT = ""+parseInt(parseInt(VWIDTH) * 9 / 16);
}
var REBUILDING = false;
var socket = {
emit: function() {
console.log("socket not initialized");
console.log(arguments);
}
};
var CHATHIST = [];
var CHATHISTIDX = 0;
var CHATTHROTTLE = false;
var CHATMAXSIZE = 100;
var SCROLLCHAT = true;
var IGNORE_SCROLL_EVENT = false;
var LASTCHAT = {
name: ""
};
var FOCUSED = true;
var PAGETITLE = "CyTube";
var TITLE_BLINK;
var CHATSOUND = new Audio("/boop.wav");
var KICKED = false;
var NAME = readCookie("cytube_uname");
var SESSION = readCookie("cytube_session");
var LEADTMR = false;
var PL_FROM = "";
var PL_AFTER = "";
var PL_CURRENT = -1;
var PL_WAIT_SCROLL = false;
var FILTER_FROM = 0;
var FILTER_TO = 0;
var NO_STORAGE = typeof localStorage == "undefined" || localStorage === null;
var SOCKETIO_CONNECT_ERROR_COUNT = 0;
var HAS_CONNECTED_BEFORE = false;
var IMAGE_MATCH = /<img\s[^>]*?src\s*=\s*['\"]([^'\"]*?)['\"][^>]*?>/gi;
var CyTube = {};
CyTube.ui = {
suppressedAnnouncementId: getOpt("suppressed_announcement_id")
};
CyTube.featureFlag = {
efficientEmotes: true
};
CyTube.channelCustomizations = {
cssHash: null,
jsHash: null
};
CyTube._internal_do_not_use_or_you_will_be_banned = {};
function getOpt(k) {
var v = NO_STORAGE ? readCookie(k) : localStorage.getItem(k);
try {
v = JSON.parse(v);
} catch (e) { }
return v;
}
function setOpt(k, v) {
v = JSON.stringify(v);
NO_STORAGE ? createCookie(k, v, 1000) : localStorage.setItem(k, v);
}
function getOrDefault(k, def) {
var v = getOpt(k);
if(v === null || v === "null")
return def;
if(v === "true")
return true;
if(v === "false")
return false;
if(v.match && v.match(/^[0-9]+$/))
return parseInt(v);
if(v.match && v.match(/^[0-9\.]+$/))
return parseFloat(v);
return v;
}
var IGNORED = getOrDefault("ignorelist", []);
var USEROPTS = {
theme : getOrDefault("theme", DEFAULT_THEME), // Set in head template
layout : getOrDefault("layout", "fluid"),
synch : getOrDefault("synch", true),
hidevid : getOrDefault("hidevid", false),
show_timestamps : getOrDefault("show_timestamps", true),
modhat : getOrDefault("modhat", false),
blink_title : getOrDefault("blink_title", "onlyping"),
sync_accuracy : getOrDefault("sync_accuracy", 2),
wmode_transparent : getOrDefault("wmode_transparent", true),
chatbtn : getOrDefault("chatbtn", false),
altsocket : getOrDefault("altsocket", false),
qbtn_hide : getOrDefault("qbtn_hide", false),
qbtn_idontlikechange : getOrDefault("qbtn_idontlikechange", false),
first_visit : getOrDefault("first_visit", true),
ignore_channelcss : getOrDefault("ignore_channelcss", false),
ignore_channeljs : getOrDefault("ignore_channeljs", false),
sort_rank : getOrDefault("sort_rank", true),
sort_afk : getOrDefault("sort_afk", false),
default_quality : getOrDefault("default_quality", "auto"),
boop : getOrDefault("boop", "never"),
show_shadowchat : getOrDefault("show_shadowchat", false),
emotelist_sort : getOrDefault("emotelist_sort", true),
no_emotes : getOrDefault("no_emotes", false),
strip_image : getOrDefault("strip_image", false),
chat_tab_method : getOrDefault("chat_tab_method", "Cycle options"),
notifications : getOrDefault("notifications", "never"),
show_ip_in_tooltip : getOrDefault("show_ip_in_tooltip", true)
};
/* Backwards compatibility check */
if (USEROPTS.blink_title === true) {
USEROPTS.blink_title = "always";
} else if (USEROPTS.blink_title === false) {
USEROPTS.blink_title = "onlyping";
}
/* Last ditch */
if (["never", "onlyping", "always"].indexOf(USEROPTS.blink_title) === -1) {
USEROPTS.blink_title = "onlyping";
}
if (USEROPTS.boop === true) {
USEROPTS.boop = "onlyping";
} else if (USEROPTS.boop === false) {
USEROPTS.boop = "never";
}
if (["never", "onlyping", "always"].indexOf(USEROPTS.boop) === -1) {
USEROPTS.boop = "onlyping";
}
// As of 3.8, preferred quality names are different
(function () {
var fix = {
small: "240",
medium: "360",
large: "480",
hd720: "720",
hd1080: "1080",
highres: "best"
};
if (fix.hasOwnProperty(USEROPTS.default_quality)) {
USEROPTS.default_quality = fix[USEROPTS.default_quality];
}
})();
var VOLUME = parseFloat(getOrDefault("volume", 1));
var NO_WEBSOCKETS = USEROPTS.altsocket;
var NO_VIMEO = Boolean(location.host.match("cytu.be"));
var JSPREF = getOpt("channel_js_pref") || {};
// Dunno why this happens
if (typeof JSPREF !== "object" || JSPREF === null) {
try {
JSPREF = JSON.parse(JSPREF);
} catch (e) {
console.error("JSPREF is bugged: " + e + " (" + JSPREF + ")");
JSPREF = {};
setOpt("channel_js_pref", JSPREF);
}
}
var Rank = {
Guest: 0,
Member: 1,
Leader: 1.5,
Moderator: 2,
Admin: 3,
Owner: 10,
Siteadmin: 255
};
function createCookie(name,value,days) {
if (days) {
var date = new Date();
date.setTime(date.getTime()+(days*24*60*60*1000));
var expires = "; expires="+date.toGMTString();
}
else var expires = "";
document.cookie = name+"="+value+expires+"; path=/";
}
function readCookie(name) {
var nameEQ = name + "=";
var ca = document.cookie.split(";");
for(var i=0;i < ca.length;i++) {
var c = ca[i];
while (c.charAt(0)==" ") c = c.substring(1,c.length);
if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
}
return null;
}
function eraseCookie(name) {
createCookie(name,"",-1);
}
(function () {
var localVersion = parseFloat(getOpt("version"));
if (isNaN(localVersion)) {
USEROPTS.theme = DEFAULT_THEME;
USEROPTS.layout = "fluid";
setOpt("theme", DEFAULT_THEME);
setOpt("layout", "fluid");
setOpt("version", CL_VERSION);
}
})();
/* to be implemented in callbacks.js */
function setupCallbacks() { }

4
www/js/jquery-1.11.0.min.js vendored Normal file

File diff suppressed because one or more lines are too long

15003
www/js/jquery-ui.js vendored Normal file

File diff suppressed because it is too large Load diff

210
www/js/paginator.js Normal file
View file

@ -0,0 +1,210 @@
(function () {
var defaults = {
preLoadPage: function () { },
postLoadPage: function () { },
generator: function () { },
itemsPerPage: 20,
maxPages: 5
};
function P(items, opts) {
this.items = items;
this.opts = opts || {};
for(var k in defaults)
if(!this.opts[k])
this.opts[k] = defaults[k];
this.paginator = $("<ul/>").addClass("pagination");
this.loadPage(0);
}
P.prototype.loadButtons = function (p) {
var pages = parseInt(this.items.length / this.opts.itemsPerPage) + 1;
var endcaps = pages > this.opts.maxPages;
this.paginator.html("");
if (this.items.length < this.opts.itemsPerPage) {
this.paginator.css("margin-top", "0");
return;
}
var ul = this.paginator;
var s = p - parseInt(this.opts.maxPages / 2);
s = s + this.opts.maxPages < pages ? s : pages - this.opts.maxPages;
s = s < 0 ? 0 : s;
if(endcaps) {
var li = $("<li/>").appendTo(ul);
$("<a/>").attr("href", "javascript:void(0)")
.html("&laquo;")
.click(function () {
this.loadPage(0);
}.bind(this))
.appendTo(li);
if(p == 0)
li.addClass("disabled");
if(s > 0) {
var sep = $("<li/>").addClass("disabled")
.appendTo(ul);
$("<a/>").attr("href", "javascript:void(0)")
.html("&hellip;")
.appendTo(sep);
}
}
for(var i = s; i < s + this.opts.maxPages && i < s + pages; i++) {
(function (i) {
var li = $("<li/>").appendTo(ul);
if(i == p)
li.addClass("active");
$("<a/>").attr("href", "javascript:void(0)")
.text(i + 1)
.click(function () {
this.loadPage(i);
}.bind(this))
.appendTo(li);
}.bind(this))(i);
}
if(endcaps) {
if(s + this.opts.maxPages < pages) {
var sep = $("<li/>").addClass("disabled")
.appendTo(ul);
$("<a/>").attr("href", "javascript:void(0)")
.html("&hellip;")
.appendTo(sep);
}
var li = $("<li/>").appendTo(ul);
$("<a/>").attr("href", "javascript:void(0)")
.html("&raquo;")
.click(function () {
this.loadPage(pages - 1);
}.bind(this))
.appendTo(li);
if(p == pages - 1)
li.addClass("disabled");
}
}
P.prototype.loadPage = function (page) {
this.opts.preLoadPage(page);
this.loadButtons(page);
var s = page * this.opts.itemsPerPage;
var e = s + this.opts.itemsPerPage;
if(e > this.items.length)
e = this.items.length;
for(var i = s; i < e; i++) {
this.opts.generator(this.items[i], page, i);
}
this.opts.postLoadPage();
}
window.Paginate = function (items, opts) {
var p = new P(items, opts);
return p;
};
})();
function NewPaginator(numItems, itemsPerPage, pageLoader) {
this.numItems = numItems;
this.itemsPerPage = itemsPerPage;
this.elem = document.createElement("ul");
this.elem.className = "pagination";
this.btnBefore = 3;
this.btnAfter = 3;
this.pageLoader = pageLoader;
}
NewPaginator.prototype.makeButton = function (target, text) {
var li = document.createElement("li");
var btn = document.createElement("a");
btn.href = "javascript:void(0)";
btn.innerHTML = text;
var _this = this;
if (target !== null) {
btn.onclick = function (event) {
if (this.parentNode.className === "disabled") {
event.preventDefault();
return false;
}
_this.loadPage(target);
};
}
li.appendChild(btn);
return li;
};
NewPaginator.prototype.makeBreak = function () {
var btn = this.makeButton(null, "&hellip;");
btn.className = "disabled";
return btn;
};
NewPaginator.prototype.loadButtons = function (page) {
this.elem.innerHTML = "";
var first = this.makeButton(0, "First");
this.elem.appendChild(first);
if (page === 0) {
first.className = "disabled";
}
var prev = this.makeButton(page - 1, "&laquo;");
this.elem.appendChild(prev);
if (page === 0) {
prev.className = "disabled";
}
if (page > this.btnBefore) {
var sep = this.makeBreak();
this.elem.appendChild(sep);
}
var numPages = Math.ceil(this.numItems / this.itemsPerPage);
numPages = Math.max(numPages, 1);
var numBtns = Math.min(this.btnBefore + this.btnAfter + 1, numPages);
var start;
if (page < this.btnBefore) {
start = 0;
} else if (page > numPages - this.btnAfter - 1) {
start = numPages - numBtns;
} else {
start = page - this.btnBefore;
}
var end = start + numBtns;
var _this = this;
for (var i = start; i < end; i++) {
(function (i) {
var btn = _this.makeButton(i, String(i + 1));
_this.elem.appendChild(btn);
if (i === page) {
btn.className = "disabled";
}
})(i);
}
if (page < numPages - this.btnAfter - 1) {
var sep = this.makeBreak();
this.elem.appendChild(sep);
}
var next = this.makeButton(page + 1, "&raquo;");
this.elem.appendChild(next);
if (page === numPages - 1) {
next.className = "disabled";
}
var last = this.makeButton(numPages - 1, "Last");
this.elem.appendChild(last);
if (page === numPages - 1) {
last.className = "disabled";
}
};
NewPaginator.prototype.loadPage = function (page) {
this.loadButtons(page);
if (this.pageLoader) {
this.pageLoader(page);
}
};

1690
www/js/player.js Normal file

File diff suppressed because it is too large Load diff

1187
www/js/playerjs-0.0.12.js Normal file

File diff suppressed because it is too large Load diff

1
www/js/sc.js Normal file

File diff suppressed because one or more lines are too long

148
www/js/tabcomplete.js Normal file
View file

@ -0,0 +1,148 @@
CyTube.tabCompleteMethods = {};
// Bash-style completion
// Only completes as far as it is possible to maintain uniqueness of the completion.
CyTube.tabCompleteMethods['Longest unique match'] = function (input, position, options, context) {
var lower = input.toLowerCase();
// First, backtrack to the nearest whitespace to find the
// incomplete string that should be completed.
var start;
var incomplete = '';
for (start = position - 1; start >= 0; start--) {
if (/\s/.test(lower[start])) {
break;
}
incomplete = lower[start] + incomplete;
}
start++;
// Nothing to complete
if (!incomplete.length) {
return {
text: input,
newPosition: position
};
}
var matches = options.filter(function (option) {
return option.toLowerCase().indexOf(incomplete) === 0;
});
var completed;
var isFullMatch = false;
if (matches.length === 0) {
return {
text: input,
newPosition: position
};
} else if (matches.length === 1) {
// Unique match
completed = matches[0];
isFullMatch = true;
} else {
// There is not a unique match, find the longest possible prefix
// that results in a unique completion
// Do this by comparing each match to the next and trimming to the
// first index where they differ.
var currentPrefix = null;
for (var i = 0; i < matches.length - 1; i++) {
var first = matches[i];
var second = matches[i+1];
var nextPrefix = '';
for (var j = 0; (currentPrefix === null || j < currentPrefix.length)
&& j < first.length
&& j < second.length; j++) {
if (first[j].toLowerCase() === second[j].toLowerCase()) {
nextPrefix += first[j];
} else {
break;
}
}
if (currentPrefix === null || nextPrefix.length < currentPrefix.length) {
currentPrefix = nextPrefix;
}
}
completed = currentPrefix;
}
var space = isFullMatch ? ' ' : '';
return {
text: input.substring(0, start) + completed + space + input.substring(position),
newPosition: start + completed.length + space.length
};
};
// Zsh-style completion.
// Always complete a full option, and cycle through available options on successive tabs
CyTube.tabCompleteMethods['Cycle options'] = function (input, position, options, context) {
if (typeof context.start !== 'undefined') {
var currentCompletion = input.substring(context.start, position - 1);
if (currentCompletion === context.matches[context.tabIndex]) {
context.tabIndex = (context.tabIndex + 1) % context.matches.length;
var completed = context.matches[context.tabIndex];
return {
text: input.substring(0, context.start) + completed + ' ' + input.substring(position),
newPosition: context.start + completed.length + 1
};
} else {
delete context.matches;
delete context.tabIndex;
delete context.start;
}
}
var lower = input.toLowerCase();
// First, backtrack to the nearest whitespace to find the
// incomplete string that should be completed.
var start;
var incomplete = '';
for (start = position - 1; start >= 0; start--) {
if (/\s/.test(lower[start])) {
break;
}
incomplete = lower[start] + incomplete;
}
start++;
// Nothing to complete
if (!incomplete.length) {
return {
text: input,
newPosition: position
};
}
var matches = options.filter(function (option) {
return option.toLowerCase().indexOf(incomplete) === 0;
}).sort(function (a, b) {
var aLower = a.toLowerCase();
var bLower = b.toLowerCase();
if (aLower > bLower) {
return 1;
} else if (aLower < bLower) {
return -1;
} else {
return 0;
}
});
if (matches.length === 0) {
return {
text: input,
newPosition: position
};
}
context.start = start;
context.matches = matches;
context.tabIndex = 0;
return {
text: input.substring(0, start) + matches[0] + ' ' + input.substring(position),
newPosition: start + matches[0].length + 1
};
};

30
www/js/theme.js Normal file
View file

@ -0,0 +1,30 @@
(function () {
var c = document.cookie.split(";").map(function (s) {
return s.trim();
});
// Set in the head template.
var theme = DEFAULT_THEME;
for (var i = 0; i < c.length; i++) {
if (c[i].indexOf("cytube-theme=") === 0) {
theme = c[i].split("=")[1];
break;
}
}
if (theme == null || !theme.match(/^\/css\/themes\/.+?.css$/)) {
return;
}
if (theme !== DEFAULT_THEME) {
console.info("THEME COOKIE:", theme);
var cur = document.getElementById("usertheme");
cur.parentNode.removeChild(cur);
var css = document.createElement("link");
css.setAttribute("rel", "stylesheet");
css.setAttribute("type", "text/css");
css.setAttribute("href", theme);
css.setAttribute("id", "usertheme");
document.head.appendChild(css);
}
})();

940
www/js/ui.js Normal file
View file

@ -0,0 +1,940 @@
/* window focus/blur */
CyTube.ui.onPageFocus = function () {
FOCUSED = true;
clearInterval(TITLE_BLINK);
TITLE_BLINK = false;
document.title = PAGETITLE;
};
CyTube.ui.onPageBlur = function (event) {
FOCUSED = false;
};
$(window).focus(CyTube.ui.onPageFocus).blur(CyTube.ui.onPageBlur);
// See #783
$(".modal").focus(CyTube.ui.onPageFocus);
$("#togglemotd").click(function () {
var hidden = $("#motd")[0].style.display === "none";
$("#motd").toggle();
if (hidden) {
$("#togglemotd").find(".glyphicon-plus")
.removeClass("glyphicon-plus")
.addClass("glyphicon-minus");
} else {
$("#togglemotd").find(".glyphicon-minus")
.removeClass("glyphicon-minus")
.addClass("glyphicon-plus");
}
});
/* chatbox */
$("#modflair").click(function () {
var m = $("#modflair");
if (m.hasClass("label-success")) {
USEROPTS.modhat = false;
m.removeClass("label-success");
if (SUPERADMIN) {
USEROPTS.adminhat = true;
m.addClass("label-danger");
} else {
m.addClass("label-default");
}
} else if (m.hasClass("label-danger")) {
USEROPTS.adminhat = false;
m.removeClass("label-danger")
.addClass("label-default");
} else {
USEROPTS.modhat = true;
m.removeClass("label-default")
.addClass("label-success");
}
$("#us-modflair").prop("checked", USEROPTS.modhat);
setOpt('modhat', USEROPTS.modhat);
});
$("#usercount").mouseenter(function (ev) {
var breakdown = calcUserBreakdown();
// re-using profile-box class for convenience
var popup = $("<div/>")
.addClass("profile-box")
.css("top", (ev.clientY + 5) + "px")
.css("left", (ev.clientX) + "px")
.appendTo($("#usercount"));
var contents = "";
for(var key in breakdown) {
contents += "<strong>" + key + ":&nbsp;</strong>" + breakdown[key];
contents += "<br>"
}
popup.html(contents);
});
$("#usercount").mousemove(function (ev) {
var popup = $("#usercount").find(".profile-box");
if(popup.length == 0)
return;
popup.css("top", (ev.clientY + 5) + "px");
popup.css("left", (ev.clientX) + "px");
});
$("#usercount").mouseleave(function () {
$("#usercount").find(".profile-box").remove();
});
$("#messagebuffer").scroll(function (ev) {
if (IGNORE_SCROLL_EVENT) {
// Skip event, this was triggered by scrollChat() and not by a user action.
// Reset for next event.
IGNORE_SCROLL_EVENT = false;
return;
}
var m = $("#messagebuffer");
var lastChildHeight = 0;
var messages = m.children();
if (messages.length > 0) {
lastChildHeight = messages[messages.length - 1].clientHeight || 0;
}
var isCaughtUp = m.height() + m.scrollTop() >= m.prop("scrollHeight") - lastChildHeight;
if (isCaughtUp) {
SCROLLCHAT = true;
$("#newmessages-indicator").remove();
} else {
SCROLLCHAT = false;
}
});
$("#guestname").keydown(function (ev) {
if (ev.keyCode === 13) {
socket.emit("login", {
name: $("#guestname").val()
});
}
});
CyTube.chatTabCompleteData = {
context: {}
};
function chatTabComplete(chatline) {
if (!CyTube.tabCompleteMethods) {
console.error('Missing CyTube.tabCompleteMethods!');
return;
}
var currentText = chatline.value;
var currentPosition = chatline.selectionEnd;
if (typeof currentPosition !== 'number' || !chatline.setSelectionRange) {
// Bail, we're on IE8 or something similarly dysfunctional
return;
}
var firstWord = !/\s/.test(currentText.trim());
var options = [];
var userlistElems = document.getElementById("userlist").children;
for (var i = 0; i < userlistElems.length; i++) {
var username = userlistElems[i].children[1].textContent;
if (firstWord) {
username += ':';
}
options.push(username);
}
CHANNEL.emotes.forEach(function (emote) {
options.push(emote.name);
});
var method = USEROPTS.chat_tab_method;
if (!CyTube.tabCompleteMethods[method]) {
console.error("Unknown chat tab completion method '" + method + "', using default");
method = "Cycle options";
}
var result = CyTube.tabCompleteMethods[method](
currentText,
currentPosition,
options,
CyTube.chatTabCompleteData.context
);
chatline.value = result.text;
chatline.setSelectionRange(result.newPosition, result.newPosition);
}
$("#chatline").keydown(function(ev) {
// Enter/return
if(ev.keyCode == 13) {
if (CHATTHROTTLE) {
return;
}
var msg = $("#chatline").val();
if(msg.trim()) {
var meta = {};
if (USEROPTS.adminhat && CLIENT.rank >= 255) {
msg = "/a " + msg;
} else if (USEROPTS.modhat && CLIENT.rank >= Rank.Moderator) {
meta.modflair = CLIENT.rank;
}
// The /m command no longer exists, so emulate it clientside
if (CLIENT.rank >= 2 && msg.indexOf("/m ") === 0) {
meta.modflair = CLIENT.rank;
msg = msg.substring(3);
}
socket.emit("chatMsg", {
msg: msg,
meta: meta
});
CHATHIST.push($("#chatline").val());
CHATHISTIDX = CHATHIST.length;
$("#chatline").val("");
}
return;
}
else if(ev.keyCode == 9) { // Tab completion
try {
chatTabComplete(ev.target);
} catch (error) {
console.error(error);
}
ev.preventDefault();
return false;
}
else if(ev.keyCode == 38) { // Up arrow (input history)
if(CHATHISTIDX == CHATHIST.length) {
CHATHIST.push($("#chatline").val());
}
if(CHATHISTIDX > 0) {
CHATHISTIDX--;
$("#chatline").val(CHATHIST[CHATHISTIDX]);
}
ev.preventDefault();
return false;
}
else if(ev.keyCode == 40) { // Down arrow (input history)
if(CHATHISTIDX < CHATHIST.length - 1) {
CHATHISTIDX++;
$("#chatline").val(CHATHIST[CHATHISTIDX]);
}
ev.preventDefault();
return false;
}
});
/* poll controls */
$("#newpollbtn").click(showPollMenu);
/* search controls */
$("#library_search").click(function() {
if (!hasPermission("seeplaylist")) {
$("#searchcontrol .alert").remove();
var al = makeAlert("Permission Denied",
"This channel does not allow you to search its library",
"alert-danger");
al.find(".alert").insertAfter($("#library_query").parent());
return;
}
socket.emit("searchMedia", {
source: "library",
query: $("#library_query").val().toLowerCase()
});
});
$("#library_query").keydown(function(ev) {
if(ev.keyCode == 13) {
if (!hasPermission("seeplaylist")) {
$("#searchcontrol .alert").remove();
var al = makeAlert("Permission Denied",
"This channel does not allow you to search its library",
"alert-danger");
al.find(".alert").insertAfter($("#library_query").parent());
return;
}
socket.emit("searchMedia", {
source: "library",
query: $("#library_query").val().toLowerCase()
});
}
});
$("#youtube_search").click(function () {
var query = $("#library_query").val().toLowerCase();
try {
parseMediaLink(query);
makeAlert("Media Link", "If you already have the link, paste it " +
"in the 'Media URL' box under Playlist Controls. This "+
"searchbar works like YouTube's search function.",
"alert-danger")
.insertBefore($("#library"));
} catch (e) {}
socket.emit("searchMedia", {
source: "yt",
query: query
});
});
/* user playlists */
$("#userpl_save").click(function() {
if($("#userpl_name").val().trim() == "") {
makeAlert("Invalid Name", "Playlist name cannot be empty", "alert-danger")
.insertAfter($("#userpl_save").parent());
return;
}
socket.emit("clonePlaylist", {
name: $("#userpl_name").val()
});
});
/* video controls */
$("#mediarefresh").click(function() {
PLAYER.mediaType = "";
PLAYER.mediaId = "";
// playerReady triggers the server to send a changeMedia.
// the changeMedia handler then reloads the player
socket.emit("playerReady");
});
/* playlist controls */
$("#queue").sortable({
start: function(ev, ui) {
PL_FROM = ui.item.data("uid");
},
update: function(ev, ui) {
var prev = ui.item.prevAll();
if(prev.length == 0)
PL_AFTER = "prepend";
else
PL_AFTER = $(prev[0]).data("uid");
socket.emit("moveMedia", {
from: PL_FROM,
after: PL_AFTER
});
$("#queue").sortable("cancel");
}
});
$("#queue").disableSelection();
function queue(pos, src) {
if (!src) {
src = "url";
}
if (src === "customembed") {
var title = $("#customembed-title").val();
if (!title) {
title = false;
}
var content = $("#customembed-content").val();
socket.emit("queue", {
id: content,
title: title,
pos: pos,
type: "cu",
temp: $(".add-temp").prop("checked")
});
} else {
var linkList = $("#mediaurl").val();
var links = linkList.split(",http").map(function (link, i) {
if (i > 0) {
return "http" + link;
} else {
return link;
}
});
if (pos === "next") links = links.reverse();
if (pos === "next" && $("#queue li").length === 0) links.unshift(links.pop());
var emitQueue = [];
var addTemp = $(".add-temp").prop("checked");
var notification = document.getElementById("addfromurl-queue");
if (!notification) {
notification = document.createElement("div");
notification.id = "addfromurl-queue";
document.getElementById("addfromurl").appendChild(notification);
}
links.forEach(function (link) {
var data;
try {
data = parseMediaLink(link);
} catch (error) {
Callbacks.queueFail({
link: link,
msg: error.message
});
return;
}
var duration = undefined;
var title = undefined;
if (data.type === "fi") {
if (data.id.match(/^http:/)) {
Callbacks.queueFail({
link: data.id,
msg: "Raw files must begin with 'https'. Plain http is not supported."
});
return;
}
// Explicit checks for kissanime and mega.nz since everyone
// asks about them
if (data.id.match(/kissanime|kimcartoon|kisscartoon/i)) {
Callbacks.queueFail({
link: data.id,
msg: "Kisscartoon and Kissanime are not supported. See https://git.io/vxS9n" +
" for more information about why these cannot be supported."
});
return;
} else if (data.id.match(/mega\.nz/)) {
Callbacks.queueFail({
link: data.id,
msg: "Mega.nz is not supported. See https://git.io/fx6fz" +
" for more information about why mega.nz cannot be supported."
});
return;
}
// Raw files allow title overrides since the ffprobe tag data
// is not always correct.
title = $("#addfromurl-title-val").val();
}
if (data.id == null || data.type == null) {
makeAlert("Error", "Failed to parse link " + link +
". Please check that it is correct",
"alert-danger", true)
.insertAfter($("#addfromurl"));
} else {
emitQueue.push({
id: data.id,
type: data.type,
pos: pos,
duration: duration,
title: title,
temp: addTemp,
link: link
});
}
});
var nextQueueDelay = 1020;
function next() {
var data = emitQueue.shift();
if (!data) {
$("#mediaurl").val("");
$("#addfromurl-title").remove();
return;
}
var link = data.link;
delete data.link;
socket.emit("queue", data);
startQueueSpinner(data);
if (emitQueue.length > 0) {
notification.textContent = "Waiting to queue " + emitQueue[0].link;
} else {
notification.textContent = "";
}
setTimeout(next, nextQueueDelay);
}
next();
}
}
$("#queue_next").click(queue.bind(this, "next", "url"));
$("#queue_end").click(queue.bind(this, "end", "url"));
$("#ce_queue_next").click(queue.bind(this, "next", "customembed"));
$("#ce_queue_end").click(queue.bind(this, "end", "customembed"));
$("#mediaurl").keyup(function(ev) {
if (ev.keyCode === 13) {
queue("end", "url");
} else {
var editTitle = false;
try {
if (parseMediaLink($("#mediaurl").val()).type === "fi") {
editTitle = true;
}
} catch (error) {
}
if (editTitle) {
var title = $("#addfromurl-title");
if (title.length === 0) {
title = $("<div/>")
.attr("id", "addfromurl-title")
.appendTo($("#addfromurl"));
$("<span/>").text("Title (optional; for raw files only)")
.appendTo(title);
$("<input/>").addClass("form-control")
.attr("type", "text")
.attr("id", "addfromurl-title-val")
.keydown(function (ev) {
if (ev.keyCode === 13) {
queue("end", "url");
}
})
.appendTo($("#addfromurl-title"));
}
} else {
$("#addfromurl-title").remove();
}
}
});
$("#customembed-content").keydown(function(ev) {
if (ev.keyCode === 13) {
queue("end", "customembed");
}
});
$("#qlockbtn").click(function() {
socket.emit("togglePlaylistLock");
});
$("#voteskip").click(function() {
socket.emit("voteskip");
$("#voteskip").attr("disabled", true);
});
$("#getplaylist").click(function() {
var callback = function(data) {
var idx = socket.listeners("errorMsg").indexOf(errCallback);
if (idx >= 0) {
socket.listeners("errorMsg").splice(idx);
}
idx = socket.listeners("playlist").indexOf(callback);
if (idx >= 0) {
socket.listeners("playlist").splice(idx);
}
var list = [];
for(var i = 0; i < data.length; i++) {
var entry = formatURL(data[i].media);
list.push(entry);
}
var urls = list.join(",");
var outer = $("<div/>").addClass("modal fade")
.appendTo($("body"));
modal = $("<div/>").addClass("modal-dialog").appendTo(outer);
modal = $("<div/>").addClass("modal-content").appendTo(modal);
var head = $("<div/>").addClass("modal-header")
.appendTo(modal);
$("<button/>").addClass("close")
.attr("data-dismiss", "modal")
.attr("aria-hidden", "true")
.html("&times;")
.appendTo(head);
$("<h3/>").text("Playlist URLs").appendTo(head);
var body = $("<div/>").addClass("modal-body").appendTo(modal);
$("<input/>").addClass("form-control").attr("type", "text")
.val(urls)
.appendTo(body);
$("<div/>").addClass("modal-footer").appendTo(modal);
outer.on("hidden.bs.modal", function() {
outer.remove();
});
outer.modal();
};
socket.on("playlist", callback);
var errCallback = function(data) {
if (data.code !== "REQ_PLAYLIST_LIMIT_REACHED") {
return;
}
var idx = socket.listeners("errorMsg").indexOf(errCallback);
if (idx >= 0) {
socket.listeners("errorMsg").splice(idx);
}
idx = socket.listeners("playlist").indexOf(callback);
if (idx >= 0) {
socket.listeners("playlist").splice(idx);
}
};
socket.on("errorMsg", errCallback);
socket.emit("requestPlaylist");
});
$("#clearplaylist").click(function() {
var clear = confirm("Are you sure you want to clear the playlist?");
if(clear) {
socket.emit("clearPlaylist");
}
});
$("#shuffleplaylist").click(function() {
var shuffle = confirm("Are you sure you want to shuffle the playlist?");
if(shuffle) {
socket.emit("shufflePlaylist");
}
});
/* channel ranks stuff */
function chanrankSubmit(rank) {
var name = $("#cs-chanranks-name").val();
socket.emit("setChannelRank", {
name: name,
rank: rank
});
}
$("#cs-chanranks-mod").click(chanrankSubmit.bind(this, 2));
$("#cs-chanranks-adm").click(chanrankSubmit.bind(this, 3));
$("#cs-chanranks-owner").click(chanrankSubmit.bind(this, 4));
["#showmediaurl", "#showsearch", "#showcustomembed", "#showplaylistmanager"]
.forEach(function (id) {
$(id).click(function () {
var wasActive = $(id).hasClass("active");
$(".plcontrol-collapse").collapse("hide");
$("#plcontrol button.active").button("toggle");
if (!wasActive) {
$(id).button("toggle");
}
});
});
$("#plcontrol button").button();
$("#plcontrol button").button("hide");
$(".plcontrol-collapse").collapse();
$(".plcontrol-collapse").collapse("hide");
$(".cs-checkbox").change(function () {
var box = $(this);
var key = box.attr("id").replace("cs-", "");
var value = box.prop("checked");
var data = {};
data[key] = value;
socket.emit("setOptions", data);
});
$(".cs-textbox").keyup(function () {
var box = $(this);
var key = box.attr("id").replace("cs-", "");
var value = box.val();
var lastkey = Date.now();
box.data("lastkey", lastkey);
setTimeout(function () {
if (box.data("lastkey") !== lastkey || box.val() !== value) {
return;
}
var data = {};
if (key.match(/chat_antiflood_(burst|sustained)/)) {
data = {
chat_antiflood_params: {
burst: $("#cs-chat_antiflood_burst").val(),
sustained: $("#cs-chat_antiflood_sustained").val()
}
};
} else {
data[key] = value;
}
socket.emit("setOptions", data);
}, 1000);
});
$(".cs-textbox-timeinput").keyup(function (event) {
var box = $(this);
var key = box.attr("id").replace("cs-", "");
var value = box.val();
var lastkey = Date.now();
box.data("lastkey", lastkey);
setTimeout(function () {
if (box.data("lastkey") !== lastkey || box.val() !== value) {
return;
}
$("#cs-textbox-timeinput-validation-error-" + key).remove();
$(event.target).parent().removeClass("has-error");
var data = {};
try {
data[key] = parseTimeout(value);
} catch (error) {
var msg = "Invalid timespan value '" + value + "'. Please use the format " +
"HH:MM:SS or enter a single number for the number of seconds.";
var validationError = $("<p/>").addClass("text-danger").text(msg)
.attr("id", "cs-textbox-timeinput-validation-error-" + key);
validationError.insertAfter(event.target);
$(event.target).parent().addClass("has-error");
return;
}
socket.emit("setOptions", data);
}, 1000);
});
$("#cs-chanlog-refresh").click(function () {
socket.emit("readChanLog");
});
$("#cs-chanlog-filter").change(filterChannelLog);
$("#cs-motdsubmit").click(function () {
socket.emit("setMotd", {
motd: $("#cs-motdtext").val()
});
});
$("#cs-csssubmit").click(function () {
socket.emit("setChannelCSS", {
css: $("#cs-csstext").val()
});
});
$("#cs-jssubmit").click(function () {
socket.emit("setChannelJS", {
js: $("#cs-jstext").val()
});
});
$("#cs-chatfilters-newsubmit").click(function () {
var name = $("#cs-chatfilters-newname").val();
var regex = $("#cs-chatfilters-newregex").val();
var flags = $("#cs-chatfilters-newflags").val();
var replace = $("#cs-chatfilters-newreplace").val();
var entcheck = checkEntitiesInStr(regex);
if (entcheck) {
alert("Warning: " + entcheck.src + " will be replaced by " +
entcheck.replace + " in the message preprocessor. This " +
"regular expression may not match what you intended it to " +
"match.");
}
socket.emit("addFilter", {
name: name,
source: regex,
flags: flags,
replace: replace,
active: true
});
socket.once("addFilterSuccess", function () {
$("#cs-chatfilters-newname").val("");
$("#cs-chatfilters-newregex").val("");
$("#cs-chatfilters-newflags").val("");
$("#cs-chatfilters-newreplace").val("");
});
});
$("#cs-emotes-newsubmit").click(function () {
var name = $("#cs-emotes-newname").val();
var image = $("#cs-emotes-newimage").val();
socket.emit("updateEmote", {
name: name,
image: image,
});
$("#cs-emotes-newname").val("");
$("#cs-emotes-newimage").val("");
});
$("#cs-chatfilters-export").click(function () {
var callback = function (data) {
socket.listeners("chatFilters").splice(
socket.listeners("chatFilters").indexOf(callback)
);
$("#cs-chatfilters-exporttext").val(JSON.stringify(data));
};
socket.on("chatFilters", callback);
socket.emit("requestChatFilters");
});
$("#cs-chatfilters-import").click(function () {
var text = $("#cs-chatfilters-exporttext").val();
var choose = confirm("You are about to import filters from the contents of the textbox below the import button. If this is empty, it will clear all of your filters. Are you sure you want to continue?");
if (!choose) {
return;
}
if (text.trim() === "") {
text = "[]";
}
var data;
try {
data = JSON.parse(text);
} catch (e) {
alert("Invalid import data: " + e);
return;
}
socket.emit("importFilters", data);
});
$("#cs-emotes-export").click(function () {
var em = CHANNEL.emotes.map(function (f) {
return {
name: f.name,
image: f.image
};
});
$("#cs-emotes-exporttext").val(JSON.stringify(em));
});
$("#cs-emotes-import").click(function () {
var text = $("#cs-emotes-exporttext").val();
var choose = confirm("You are about to import emotes from the contents of the textbox below the import button. If this is empty, it will clear all of your emotes. Are you sure you want to continue?");
if (!choose) {
return;
}
if (text.trim() === "") {
text = "[]";
}
var data;
try {
data = JSON.parse(text);
} catch (e) {
alert("Invalid import data: " + e);
return;
}
socket.emit("importEmotes", data);
});
var toggleUserlist = function () {
var direction = !USEROPTS.layout.match(/synchtube/) ? "glyphicon-chevron-right" : "glyphicon-chevron-left"
if ($("#userlist")[0].style.display === "none") {
$("#userlist").show();
$("#userlisttoggle").removeClass(direction).addClass("glyphicon-chevron-down");
} else {
$("#userlist").hide();
$("#userlisttoggle").removeClass("glyphicon-chevron-down").addClass(direction);
}
scrollChat();
};
$("#usercount").click(toggleUserlist);
$("#userlisttoggle").click(toggleUserlist);
$(".add-temp").change(function () {
$(".add-temp").prop("checked", $(this).prop("checked"));
});
/*
* Fixes #417 which is caused by changes in Bootstrap 3.3.0
* (see twbs/bootstrap#15136)
*
* Whenever the active tab in channel options is changed,
* the modal must be updated so that the backdrop is resized
* appropriately.
*/
$("#channeloptions li > a[data-toggle='tab']").on("shown.bs.tab", function () {
$("#channeloptions").data("bs.modal").handleUpdate();
});
applyOpts();
(function () {
var embed = document.querySelector("#videowrap .embed-responsive");
if (!embed) {
return;
}
if (typeof window.MutationObserver === "function") {
var mr = new MutationObserver(function (records) {
records.forEach(function (record) {
if (record.type !== "childList") return;
if (!record.addedNodes || record.addedNodes.length === 0) return;
var elem = record.addedNodes[0];
if (elem.id === "ytapiplayer") handleVideoResize();
});
});
mr.observe(embed, { childList: true });
} else {
/*
* DOMNodeInserted is deprecated. This code is here only as a fallback
* for browsers that do not support MutationObserver
*/
embed.addEventListener("DOMNodeInserted", function (ev) {
if (ev.target.id === "ytapiplayer") handleVideoResize();
});
}
})();
var EMOTELISTMODAL = $("#emotelist");
$("#emotelistbtn").click(function () {
EMOTELISTMODAL.modal();
});
EMOTELISTMODAL.find(".emotelist-alphabetical").change(function () {
USEROPTS.emotelist_sort = this.checked;
setOpt("emotelist_sort", USEROPTS.emotelist_sort);
});
EMOTELISTMODAL.find(".emotelist-alphabetical").prop("checked", USEROPTS.emotelist_sort);
$("#fullscreenbtn").click(function () {
var elem = document.querySelector("#videowrap .embed-responsive");
// this shit is why frontend web development sucks
var fn = elem.requestFullscreen ||
elem.mozRequestFullScreen || // Mozilla has to be different and use a capital 'S'
elem.webkitRequestFullscreen ||
elem.msRequestFullscreen;
if (fn) {
fn.call(elem);
}
});
function handleCSSJSTooLarge(selector) {
if (this.value.length > 20000) {
var warning = $(selector);
if (warning.length > 0) {
return;
}
warning = makeAlert("Maximum Size Exceeded", "Inline CSS and JavaScript are " +
"limited to 20,000 characters or less. If you need more room, you " +
"need to use the external CSS or JavaScript option.", "alert-danger")
.attr("id", selector.replace(/#/, ""));
warning.insertBefore(this);
} else {
$(selector).remove();
}
}
$("#cs-csstext").bind("input", handleCSSJSTooLarge.bind($("#cs-csstext")[0],
"#cs-csstext-too-big"));
$("#cs-jstext").bind("input", handleCSSJSTooLarge.bind($("#cs-jstext")[0],
"#cs-jstext-too-big"));
$("#resize-video-larger").click(function () {
try {
CyTube.ui.changeVideoWidth(1);
} catch (error) {
console.error(error);
}
});
$("#resize-video-smaller").click(function () {
try {
CyTube.ui.changeVideoWidth(-1);
} catch (error) {
console.error(error);
}
});

3452
www/js/util.js Normal file

File diff suppressed because it is too large Load diff

24310
www/js/video.js Normal file

File diff suppressed because one or more lines are too long

8
www/js/videojs-contrib-hls.min.js vendored Normal file

File diff suppressed because one or more lines are too long

636
www/js/videojs-dash.js Normal file
View file

@ -0,0 +1,636 @@
/**
* videojs-contrib-dash
* @version 2.9.1
* @copyright 2017 Brightcove, Inc
* @license Apache-2.0
*/
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.videojsDash = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
(function (global){
'use strict';
exports.__esModule = true;
exports['default'] = setupAudioTracks;
var _dashjs = (typeof window !== "undefined" ? window['dashjs'] : typeof global !== "undefined" ? global['dashjs'] : null);
var _dashjs2 = _interopRequireDefault(_dashjs);
var _video = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null);
var _video2 = _interopRequireDefault(_video);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
/**
* Setup audio tracks. Take the tracks from dash and add the tracks to videojs. Listen for when
* videojs changes tracks and apply that to the dash player because videojs doesn't do this
* natively.
*
* @private
* @param {videojs} player the videojs player instance
* @param {videojs.tech} tech the videojs tech being used
*/
function handlePlaybackMetadataLoaded(player, tech) {
var mediaPlayer = player.dash.mediaPlayer;
var dashAudioTracks = mediaPlayer.getTracksFor('audio');
var videojsAudioTracks = player.audioTracks();
function generateIdFromTrackIndex(index) {
return 'dash-audio-' + index;
}
function findDashAudioTrack(dashAudioTracks, videojsAudioTrack) {
return dashAudioTracks.find(function (_ref) {
var index = _ref.index;
return generateIdFromTrackIndex(index) === videojsAudioTrack.id;
});
}
// Safari creates a single native `AudioTrack` (not `videojs.AudioTrack`) when loading. Clear all
// automatically generated audio tracks so we can create them all ourself.
if (videojsAudioTracks.length) {
tech.clearTracks(['audio']);
}
var currentAudioTrack = mediaPlayer.getCurrentTrackFor('audio');
dashAudioTracks.forEach(function (dashTrack) {
var label = dashTrack.lang;
if (dashTrack.roles && dashTrack.roles.length) {
label += ' (' + dashTrack.roles.join(', ') + ')';
}
// Add the track to the player's audio track list.
videojsAudioTracks.addTrack(new _video2['default'].AudioTrack({
enabled: dashTrack === currentAudioTrack,
id: generateIdFromTrackIndex(dashTrack.index),
kind: dashTrack.kind || 'main',
label: label,
language: dashTrack.lang
}));
});
videojsAudioTracks.addEventListener('change', function () {
for (var i = 0; i < videojsAudioTracks.length; i++) {
var track = videojsAudioTracks[i];
if (track.enabled) {
// Find the audio track we just selected by the id
var dashAudioTrack = findDashAudioTrack(dashAudioTracks, track);
// Set is as the current track
mediaPlayer.setCurrentTrack(dashAudioTrack);
// Stop looping
continue;
}
}
});
}
/*
* Call `handlePlaybackMetadataLoaded` when `mediaPlayer` emits
* `dashjs.MediaPlayer.events.PLAYBACK_METADATA_LOADED`.
*/
function setupAudioTracks(player, tech) {
// When `dashjs` finishes loading metadata, create audio tracks for `video.js`.
player.dash.mediaPlayer.on(_dashjs2['default'].MediaPlayer.events.PLAYBACK_METADATA_LOADED, handlePlaybackMetadataLoaded.bind(null, player, tech));
}
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{}],2:[function(require,module,exports){
(function (global){
'use strict';
exports.__esModule = true;
exports['default'] = setupTextTracks;
var _dashjs = (typeof window !== "undefined" ? window['dashjs'] : typeof global !== "undefined" ? global['dashjs'] : null);
var _dashjs2 = _interopRequireDefault(_dashjs);
var _video = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null);
var _video2 = _interopRequireDefault(_video);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
function find(l, f) {
for (var i = 0; i < l.length; i++) {
if (f(l[i])) {
return l[i];
}
}
}
/*
* Attach text tracks from dash.js to videojs
*
* @param {videojs} player the videojs player instance
* @param {array} tracks the tracks loaded by dash.js to attach to videojs
*
* @private
*/
function attachDashTextTracksToVideojs(player, tech, tracks) {
var trackDictionary = [];
// Add remote tracks
var tracksAttached = tracks
// Map input data to match HTMLTrackElement spec
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLTrackElement
.map(function (track) {
return {
dashTrack: track,
trackConfig: {
label: track.lang,
language: track.lang,
srclang: track.lang
}
};
}
// Add track to videojs track list
).map(function (_ref) {
var trackConfig = _ref.trackConfig,
dashTrack = _ref.dashTrack;
var remoteTextTrack = player.addRemoteTextTrack(trackConfig, true);
trackDictionary.push({ textTrack: remoteTextTrack.track, dashTrack: dashTrack });
// Don't add the cues becuase we're going to let dash handle it natively. This will ensure
// that dash handle external time text files and fragmented text tracks.
//
// Example file with external time text files:
// https://storage.googleapis.com/shaka-demo-assets/sintel-mp4-wvtt/dash.mpd
return remoteTextTrack;
});
/*
* Scan `videojs.textTracks()` to find one that is showing. Set the dash text track.
*/
function updateActiveDashTextTrack() {
var dashMediaPlayer = player.dash.mediaPlayer;
var textTracks = player.textTracks();
var activeTextTrackIndex = -1;
// Iterate through the tracks and find the one marked as showing. If none are showing,
// `activeTextTrackIndex` will be set to `-1`, disabling text tracks.
var _loop = function _loop(i) {
var textTrack = textTracks[i];
if (textTrack.mode === 'showing') {
// Find the dash track we want to use
/* jshint loopfunc: true */
var dictionaryLookupResult = find(trackDictionary, function (track) {
return track.textTrack === textTrack;
});
/* jshint loopfunc: false */
var dashTrackToActivate = dictionaryLookupResult ? dictionaryLookupResult.dashTrack : null;
// If we found a track, get it's index.
if (dashTrackToActivate) {
activeTextTrackIndex = tracks.indexOf(dashTrackToActivate);
}
}
};
for (var i = 0; i < textTracks.length; i += 1) {
_loop(i);
}
// If the text track has changed, then set it in dash
if (activeTextTrackIndex !== dashMediaPlayer.getCurrentTextTrackIndex()) {
dashMediaPlayer.setTextTrack(activeTextTrackIndex);
}
}
// Update dash when videojs's selected text track changes.
player.textTracks().on('change', updateActiveDashTextTrack);
// Cleanup event listeners whenever we start loading a new source
player.one('loadstart', function () {
player.textTracks().off('change', updateActiveDashTextTrack);
});
// Initialize the text track on our first run-through
updateActiveDashTextTrack();
return tracksAttached;
}
/*
* Wait for dash to emit `TEXT_TRACKS_ADDED` and then attach the text tracks loaded by dash if
* we're not using native text tracks.
*
* @param {videojs} player the videojs player instance
* @private
*/
function setupTextTracks(player, tech, options) {
// Clear VTTCue if it was shimmed by vttjs and let dash.js use TextTrackCue.
// This is necessary because dash.js creates text tracks
// using addTextTrack which is incompatible with vttjs.VTTCue in IE11
if (window.VTTCue && !/\[native code\]/.test(window.VTTCue.toString())) {
window.VTTCue = false;
}
// Store the tracks that we've added so we can remove them later.
var dashTracksAttachedToVideoJs = [];
// We're relying on the user to disable native captions. Show an error if they didn't do so.
if (tech.featuresNativeTextTracks) {
_video2['default'].log.error('You must pass {html: {nativeCaptions: false}} in the videojs constructor ' + 'to use text tracks in videojs-contrib-dash');
return;
}
var mediaPlayer = player.dash.mediaPlayer;
// Clear the tracks that we added. We don't clear them all because someone else can add tracks.
function clearDashTracks() {
dashTracksAttachedToVideoJs.forEach(player.removeRemoteTextTrack.bind(player));
dashTracksAttachedToVideoJs = [];
}
function handleTextTracksAdded(_ref2) {
var index = _ref2.index,
tracks = _ref2.tracks;
// Stop listening for this event. We only want to hear it once.
mediaPlayer.off(_dashjs2['default'].MediaPlayer.events.TEXT_TRACKS_ADDED, handleTextTracksAdded);
// Cleanup old tracks
clearDashTracks();
if (!tracks.length) {
// Don't try to add text tracks if there aren't any
return;
}
// Save the tracks so we can remove them later
dashTracksAttachedToVideoJs = attachDashTextTracksToVideojs(player, tech, tracks, options);
}
// Attach dash text tracks whenever we dash emits `TEXT_TRACKS_ADDED`.
mediaPlayer.on(_dashjs2['default'].MediaPlayer.events.TEXT_TRACKS_ADDED, handleTextTracksAdded);
function cleanup() {
mediaPlayer.off(_dashjs2['default'].MediaPlayer.events.TEXT_TRACKS_ADDED, handleTextTracksAdded);
player.one('loadstart', clearDashTracks);
}
// When the player can play, remove the initialization events. We might not have received
// TEXT_TRACKS_ADDED` so we have to stop listening for it or we'll get errors when we load new
// videos and are listening for the same event in multiple places, including cleaned up
// mediaPlayers.
mediaPlayer.on(_dashjs2['default'].MediaPlayer.events.CAN_PLAY, cleanup);
}
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{}],3:[function(require,module,exports){
(function (global){
var win;
if (typeof window !== "undefined") {
win = window;
} else if (typeof global !== "undefined") {
win = global;
} else if (typeof self !== "undefined"){
win = self;
} else {
win = {};
}
module.exports = win;
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{}],4:[function(require,module,exports){
(function (global){
'use strict';
exports.__esModule = true;
var _window = require('global/window');
var _window2 = _interopRequireDefault(_window);
var _video = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null);
var _video2 = _interopRequireDefault(_video);
var _dashjs = (typeof window !== "undefined" ? window['dashjs'] : typeof global !== "undefined" ? global['dashjs'] : null);
var _dashjs2 = _interopRequireDefault(_dashjs);
var _setupAudioTracks = require('./setup-audio-tracks');
var _setupAudioTracks2 = _interopRequireDefault(_setupAudioTracks);
var _setupTextTracks = require('./setup-text-tracks');
var _setupTextTracks2 = _interopRequireDefault(_setupTextTracks);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
/**
* videojs-contrib-dash
*
* Use Dash.js to playback DASH content inside of Video.js via a SourceHandler
*/
var Html5DashJS = function () {
function Html5DashJS(source, tech, options) {
var _this = this;
_classCallCheck(this, Html5DashJS);
// Get options from tech if not provided for backwards compatibility
options = options || tech.options_;
this.player = (0, _video2['default'])(options.playerId);
this.player.dash = this.player.dash || {};
this.tech_ = tech;
this.el_ = tech.el();
this.elParent_ = this.el_.parentNode;
// Do nothing if the src is falsey
if (!source.src) {
return;
}
// While the manifest is loading and Dash.js has not finished initializing
// we must defer events and functions calls with isReady_ and then `triggerReady`
// again later once everything is setup
tech.isReady_ = false;
if (Html5DashJS.updateSourceData) {
_video2['default'].log.warn('updateSourceData has been deprecated.' + ' Please switch to using hook("updatesource", callback).');
source = Html5DashJS.updateSourceData(source);
}
// call updatesource hooks
Html5DashJS.hooks('updatesource').forEach(function (hook) {
source = hook(source);
});
var manifestSource = source.src;
this.keySystemOptions_ = Html5DashJS.buildDashJSProtData(source.keySystemOptions);
this.player.dash.mediaPlayer = _dashjs2['default'].MediaPlayer().create();
this.mediaPlayer_ = this.player.dash.mediaPlayer;
// Log MedaPlayer messages through video.js
if (Html5DashJS.useVideoJSDebug) {
_video2['default'].log.warn('useVideoJSDebug has been deprecated.' + ' Please switch to using hook("beforeinitialize", callback).');
Html5DashJS.useVideoJSDebug(this.mediaPlayer_);
}
if (Html5DashJS.beforeInitialize) {
_video2['default'].log.warn('beforeInitialize has been deprecated.' + ' Please switch to using hook("beforeinitialize", callback).');
Html5DashJS.beforeInitialize(this.player, this.mediaPlayer_);
}
Html5DashJS.hooks('beforeinitialize').forEach(function (hook) {
hook(_this.player, _this.mediaPlayer_);
});
// Must run controller before these two lines or else there is no
// element to bind to.
this.mediaPlayer_.initialize();
// Apply all dash options that are set
if (options.dash) {
Object.keys(options.dash).forEach(function (key) {
var _mediaPlayer_;
var dashOptionsKey = 'set' + key.charAt(0).toUpperCase() + key.slice(1);
var value = options.dash[key];
if (_this.mediaPlayer_.hasOwnProperty(dashOptionsKey)) {
// Providing a key without `set` prefix is now deprecated.
_video2['default'].log.warn('Using dash options in videojs-contrib-dash without the set prefix ' + ('has been deprecated. Change \'' + key + '\' to \'' + dashOptionsKey + '\''));
// Set key so it will still work
key = dashOptionsKey;
}
if (!_this.mediaPlayer_.hasOwnProperty(key)) {
_video2['default'].log.warn('Warning: dash configuration option unrecognized: ' + key);
return;
}
// Guarantee `value` is an array
if (!Array.isArray(value)) {
value = [value];
}
(_mediaPlayer_ = _this.mediaPlayer_)[key].apply(_mediaPlayer_, value);
});
}
this.mediaPlayer_.attachView(this.el_);
// Dash.js autoplays by default, video.js will handle autoplay
this.mediaPlayer_.setAutoPlay(false);
// Setup audio tracks
_setupAudioTracks2['default'].call(null, this.player, tech);
// Setup text tracks
_setupTextTracks2['default'].call(null, this.player, tech, options);
// Attach the source with any protection data
this.mediaPlayer_.setProtectionData(this.keySystemOptions_);
this.mediaPlayer_.attachSource(manifestSource);
this.tech_.triggerReady();
}
/*
* Iterate over the `keySystemOptions` array and convert each object into
* the type of object Dash.js expects in the `protData` argument.
*
* Also rename 'licenseUrl' property in the options to an 'serverURL' property
*/
Html5DashJS.buildDashJSProtData = function buildDashJSProtData(keySystemOptions) {
var output = {};
if (!keySystemOptions || !Array.isArray(keySystemOptions)) {
return null;
}
for (var i = 0; i < keySystemOptions.length; i++) {
var keySystem = keySystemOptions[i];
var options = _video2['default'].mergeOptions({}, keySystem.options);
if (options.licenseUrl) {
options.serverURL = options.licenseUrl;
delete options.licenseUrl;
}
output[keySystem.name] = options;
}
return output;
};
Html5DashJS.prototype.dispose = function dispose() {
if (this.mediaPlayer_) {
this.mediaPlayer_.reset();
}
if (this.player.dash) {
delete this.player.dash;
}
};
Html5DashJS.prototype.duration = function duration() {
var duration = this.el_.duration;
if (duration === Number.MAX_VALUE) {
return Infinity;
}
return duration;
};
/**
* Get a list of hooks for a specific lifecycle
*
* @param {string} type the lifecycle to get hooks from
* @param {Function=|Function[]=} hook Optionally add a hook tothe lifecycle
* @return {Array} an array of hooks or epty if none
* @method hooks
*/
Html5DashJS.hooks = function hooks(type, hook) {
Html5DashJS.hooks_[type] = Html5DashJS.hooks_[type] || [];
if (hook) {
Html5DashJS.hooks_[type] = Html5DashJS.hooks_[type].concat(hook);
}
return Html5DashJS.hooks_[type];
};
/**
* Add a function hook to a specific dash lifecycle
*
* @param {string} type the lifecycle to hook the function to
* @param {Function|Function[]} hook the function or array of functions to attach
* @method hook
*/
Html5DashJS.hook = function hook(type, _hook) {
Html5DashJS.hooks(type, _hook);
};
/**
* Remove a hook from a specific dash lifecycle.
*
* @param {string} type the lifecycle that the function hooked to
* @param {Function} hook The hooked function to remove
* @return {boolean} True if the function was removed, false if not found
* @method removeHook
*/
Html5DashJS.removeHook = function removeHook(type, hook) {
var index = Html5DashJS.hooks(type).indexOf(hook);
if (index === -1) {
return false;
}
Html5DashJS.hooks_[type] = Html5DashJS.hooks_[type].slice();
Html5DashJS.hooks_[type].splice(index, 1);
return true;
};
return Html5DashJS;
}();
Html5DashJS.hooks_ = {};
var canHandleKeySystems = function canHandleKeySystems(source) {
// copy the source
source = JSON.parse(JSON.stringify(source));
if (Html5DashJS.updateSourceData) {
_video2['default'].log.warn('updateSourceData has been deprecated.' + ' Please switch to using hook("updatesource", callback).');
source = Html5DashJS.updateSourceData(source);
}
// call updatesource hooks
Html5DashJS.hooks('updatesource').forEach(function (hook) {
source = hook(source);
});
var videoEl = document.createElement('video');
if (source.keySystemOptions && !(navigator.requestMediaKeySystemAccess ||
// IE11 Win 8.1
videoEl.msSetMediaKeys)) {
return false;
}
return true;
};
_video2['default'].DashSourceHandler = function () {
return {
canHandleSource: function canHandleSource(source) {
var dashExtRE = /\.mpd/i;
if (!canHandleKeySystems(source)) {
return '';
}
if (_video2['default'].DashSourceHandler.canPlayType(source.type)) {
return 'probably';
} else if (dashExtRE.test(source.src)) {
return 'maybe';
} else {
return '';
}
},
handleSource: function handleSource(source, tech, options) {
return new Html5DashJS(source, tech, options);
},
canPlayType: function canPlayType(type) {
return _video2['default'].DashSourceHandler.canPlayType(type);
}
};
};
_video2['default'].DashSourceHandler.canPlayType = function (type) {
var dashTypeRE = /^application\/dash\+xml/i;
if (dashTypeRE.test(type)) {
return 'probably';
}
return '';
};
// Only add the SourceHandler if the browser supports MediaSourceExtensions
if (!!_window2['default'].MediaSource) {
_video2['default'].getTech('Html5').registerSourceHandler(_video2['default'].DashSourceHandler(), 0);
}
_video2['default'].Html5DashJS = Html5DashJS;
exports['default'] = Html5DashJS;
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{"./setup-audio-tracks":1,"./setup-text-tracks":2,"global/window":3}]},{},[4])(4)
});

View file

@ -0,0 +1,367 @@
/*! videojs-resolution-switcher - 2015-7-26
* Copyright (c) 2016 Kasper Moskwiak
* Modified by Pierre Kraft and Derk-Jan Hartman
* Licensed under the Apache-2.0 license. */
(function() {
/* jshint eqnull: true*/
/* global require */
'use strict';
var videojs = null;
if(typeof window.videojs === 'undefined' && typeof require === 'function') {
videojs = require('video.js');
} else {
videojs = window.videojs;
}
(function(window, videojs) {
var videoJsResolutionSwitcher,
defaults = {
ui: true
};
/*
* Resolution menu item
*/
var MenuItem = videojs.getComponent('MenuItem');
var ResolutionMenuItem = videojs.extend(MenuItem, {
constructor: function(player, options){
options.selectable = true;
// Sets this.player_, this.options_ and initializes the component
MenuItem.call(this, player, options);
this.src = options.src;
player.on('resolutionchange', videojs.bind(this, this.update));
}
} );
ResolutionMenuItem.prototype.handleClick = function(event){
MenuItem.prototype.handleClick.call(this,event);
this.player_.currentResolution(this.options_.label);
};
ResolutionMenuItem.prototype.update = function(){
var selection = this.player_.currentResolution();
this.selected(this.options_.label === selection.label);
};
MenuItem.registerComponent('ResolutionMenuItem', ResolutionMenuItem);
/*
* Resolution menu button
*/
var MenuButton = videojs.getComponent('MenuButton');
var ResolutionMenuButton = videojs.extend(MenuButton, {
constructor: function(player, options){
this.label = document.createElement('span');
options.label = 'Quality';
// Sets this.player_, this.options_ and initializes the component
MenuButton.call(this, player, options);
this.el().setAttribute('aria-label','Quality');
this.controlText('Quality');
if(options.dynamicLabel){
videojs.addClass(this.label, 'vjs-resolution-button-label');
this.el().appendChild(this.label);
}else{
var staticLabel = document.createElement('span');
videojs.addClass(staticLabel, 'vjs-menu-icon');
this.el().appendChild(staticLabel);
}
player.on('updateSources', videojs.bind( this, this.update ) );
}
} );
ResolutionMenuButton.prototype.createItems = function(){
var menuItems = [];
var labels = (this.sources && this.sources.label) || {};
// FIXME order is not guaranteed here.
for (var key in labels) {
if (labels.hasOwnProperty(key)) {
menuItems.push(new ResolutionMenuItem(
this.player_,
{
label: key,
src: labels[key],
selected: key === (this.currentSelection ? this.currentSelection.label : false)
})
);
}
}
return menuItems;
};
ResolutionMenuButton.prototype.update = function(){
this.sources = this.player_.getGroupedSrc();
this.currentSelection = this.player_.currentResolution();
this.label.innerHTML = this.currentSelection ? this.currentSelection.label : '';
return MenuButton.prototype.update.call(this);
};
ResolutionMenuButton.prototype.buildCSSClass = function(){
return MenuButton.prototype.buildCSSClass.call( this ) + ' vjs-resolution-button';
};
MenuButton.registerComponent('ResolutionMenuButton', ResolutionMenuButton);
/**
* Initialize the plugin.
* @param {object} [options] configuration for the plugin
*/
videoJsResolutionSwitcher = function(options) {
var settings = videojs.mergeOptions(defaults, options),
player = this,
groupedSrc = {},
currentSources = {},
currentResolutionState = {};
/**
* Updates player sources or returns current source URL
* @param {Array} [src] array of sources [{src: '', type: '', label: '', res: ''}]
* @returns {Object|String|Array} videojs player object if used as setter or current source URL, object, or array of sources
*/
player.updateSrc = function(src){
//Return current src if src is not given
if(!src){ return player.src(); }
// Only add those sources which we can (maybe) play
src = src.filter( function(source) {
try {
return ( player.canPlayType( source.type ) !== '' );
} catch (e) {
// If a Tech doesn't yet have canPlayType just add it
return true;
}
});
//Sort sources
this.currentSources = src.sort(compareResolutions);
this.groupedSrc = bucketSources(this.currentSources);
// Pick one by default
var chosen = chooseSrc(this.groupedSrc, this.currentSources);
this.currentResolutionState = {
label: chosen.label,
sources: chosen.sources
};
player.trigger('updateSources');
player.setSourcesSanitized(chosen.sources, chosen.label);
player.trigger('resolutionchange');
return player;
};
/**
* Returns current resolution or sets one when label is specified
* @param {String} [label] label name
* @param {Function} [customSourcePicker] custom function to choose source. Takes 2 arguments: sources, label. Must return player object.
* @returns {Object} current resolution object {label: '', sources: []} if used as getter or player object if used as setter
*/
player.currentResolution = function(label, customSourcePicker){
if(label == null) { return this.currentResolutionState; }
// Lookup sources for label
if(!this.groupedSrc || !this.groupedSrc.label || !this.groupedSrc.label[label]){
return;
}
var sources = this.groupedSrc.label[label];
// Remember player state
var currentTime = player.currentTime();
var isPaused = player.paused();
// Hide bigPlayButton
if(!isPaused && this.player_.options_.bigPlayButton){
this.player_.bigPlayButton.hide();
}
// Change player source and wait for loadeddata event, then play video
// loadedmetadata doesn't work right now for flash.
// Probably because of https://github.com/videojs/video-js-swf/issues/124
// If player preload is 'none' and then loadeddata not fired. So, we need timeupdate event for seek handle (timeupdate doesn't work properly with flash)
var handleSeekEvent = 'loadeddata';
if(this.player_.techName_ !== 'Youtube' && this.player_.preload() === 'none' && this.player_.techName_ !== 'Flash') {
handleSeekEvent = 'timeupdate';
}
player
.setSourcesSanitized(sources, label, customSourcePicker || settings.customSourcePicker)
.one(handleSeekEvent, function() {
player.currentTime(currentTime);
player.handleTechSeeked_();
if(!isPaused){
// Start playing and hide loadingSpinner (flash issue ?)
player.play().handleTechSeeked_();
}
player.trigger('resolutionchange');
});
return player;
};
/**
* Returns grouped sources by label, resolution and type
* @returns {Object} grouped sources: { label: { key: [] }, res: { key: [] }, type: { key: [] } }
*/
player.getGroupedSrc = function(){
return this.groupedSrc;
};
player.setSourcesSanitized = function(sources, label, customSourcePicker) {
this.currentResolutionState = {
label: label,
sources: sources
};
if(typeof customSourcePicker === 'function'){
return customSourcePicker(player, sources, label);
}
player.src(sources.map(function(src) {
return {src: src.src, type: src.type, res: src.res};
}));
return player;
};
/**
* Method used for sorting list of sources
* @param {Object} a - source object with res property
* @param {Object} b - source object with res property
* @returns {Number} result of comparation
*/
function compareResolutions(a, b){
if(!a.res || !b.res){ return 0; }
return (+b.res)-(+a.res);
}
/**
* Group sources by label, resolution and type
* @param {Array} src Array of sources
* @returns {Object} grouped sources: { label: { key: [] }, res: { key: [] }, type: { key: [] } }
*/
function bucketSources(src){
var resolutions = {
label: {},
res: {},
type: {}
};
src.map(function(source) {
initResolutionKey(resolutions, 'label', source);
initResolutionKey(resolutions, 'res', source);
initResolutionKey(resolutions, 'type', source);
appendSourceToKey(resolutions, 'label', source);
appendSourceToKey(resolutions, 'res', source);
appendSourceToKey(resolutions, 'type', source);
});
return resolutions;
}
function initResolutionKey(resolutions, key, source) {
if(resolutions[key][source[key]] == null) {
resolutions[key][source[key]] = [];
}
}
function appendSourceToKey(resolutions, key, source) {
resolutions[key][source[key]].push(source);
}
/**
* Choose src if option.default is specified
* @param {Object} groupedSrc {res: { key: [] }}
* @param {Array} src Array of sources sorted by resolution used to find high and low res
* @returns {Object} {res: string, sources: []}
*/
function chooseSrc(groupedSrc, src){
var selectedRes = settings['default']; // use array access as default is a reserved keyword
var selectedLabel = '';
if (selectedRes === 'high') {
selectedRes = src[0].res;
selectedLabel = src[0].label;
} else if (selectedRes === 'low' || selectedRes == null || !groupedSrc.res[selectedRes]) {
// Select low-res if default is low or not set
selectedRes = src[src.length - 1].res;
selectedLabel = src[src.length -1].label;
} else if (groupedSrc.res[selectedRes]) {
selectedLabel = groupedSrc.res[selectedRes][0].label;
}
return {res: selectedRes, label: selectedLabel, sources: groupedSrc.res[selectedRes]};
}
function initResolutionForYt(player){
// Map youtube qualities names
var _yts = {
highres: {res: 1080, label: '1080', yt: 'highres'},
hd1080: {res: 1080, label: '1080', yt: 'hd1080'},
hd720: {res: 720, label: '720', yt: 'hd720'},
large: {res: 480, label: '480', yt: 'large'},
medium: {res: 360, label: '360', yt: 'medium'},
small: {res: 240, label: '240', yt: 'small'},
tiny: {res: 144, label: '144', yt: 'tiny'},
auto: {res: 0, label: 'auto', yt: 'auto'}
};
// Overwrite default sourcePicker function
var _customSourcePicker = function(_player, _sources, _label){
// Note that setPlayebackQuality is a suggestion. YT does not always obey it.
player.tech_.ytPlayer.setPlaybackQuality(_sources[0]._yt);
player.trigger('updateSources');
return player;
};
settings.customSourcePicker = _customSourcePicker;
// Init resolution
player.tech_.ytPlayer.setPlaybackQuality('auto');
// This is triggered when the resolution actually changes
player.tech_.ytPlayer.addEventListener('onPlaybackQualityChange', function(event){
for(var res in _yts) {
if(res.yt === event.data) {
player.currentResolution(res.label, _customSourcePicker);
return;
}
}
});
// We must wait for play event
player.one('play', function(){
var qualities = player.tech_.ytPlayer.getAvailableQualityLevels();
var _sources = [];
qualities.map(function(q){
_sources.push({
src: player.src().src,
type: player.src().type,
label: _yts[q].label,
res: _yts[q].res,
_yt: _yts[q].yt
});
});
player.groupedSrc = bucketSources(_sources);
var chosen = {label: 'auto', res: 0, sources: player.groupedSrc.label.auto};
this.currentResolutionState = {
label: chosen.label,
sources: chosen.sources
};
player.trigger('updateSources');
player.setSourcesSanitized(chosen.sources, chosen.label, _customSourcePicker);
});
}
player.ready(function(){
if( settings.ui ) {
var menuButton = new ResolutionMenuButton(player, settings);
player.controlBar.resolutionSwitcher = player.controlBar.el_.insertBefore(menuButton.el_, player.controlBar.getChild('fullscreenToggle').el_);
player.controlBar.resolutionSwitcher.dispose = function(){
this.parentNode.removeChild(this);
};
}
if(player.options_.sources.length > 1){
// tech: Html5 and Flash
// Create resolution switcher for videos form <source> tag inside <video>
player.updateSrc(player.options_.sources);
}
if(player.techName_ === 'Youtube'){
// tech: YouTube
initResolutionForYt(player);
}
});
};
// register the plugin
videojs.plugin('videoJsResolutionSwitcher', videoJsResolutionSwitcher);
})(window, videojs);
})();