Continue rewriting things

This commit is contained in:
calzoneman 2013-06-11 11:29:21 -04:00
parent 161a116e59
commit 42fc2e45c8
9 changed files with 1159 additions and 458 deletions

View File

@ -150,9 +150,7 @@ Channel.prototype.loadDump = function() {
}
this.queue.push(m);
}
this.sendAll("playlist", {
pl: this.queue
});
this.sendAll("playlist", this.queue);
this.broadcastPlaylistMeta();
// Backwards compatibility
if(data.currentPosition != undefined) {
@ -559,7 +557,7 @@ Channel.prototype.userJoin = function(user) {
// Set the new guy up
this.sendPlaylist(user);
this.sendMediaUpdate(user);
user.socket.emit("queueLock", {locked: !this.openqueue});
user.socket.emit("setPlaylistLocked", {locked: !this.openqueue});
this.sendUserlist(user);
this.sendRecentChat(user);
user.socket.emit("channelCSSJS", {css: this.css, js: this.js});
@ -569,7 +567,7 @@ Channel.prototype.userJoin = function(user) {
user.socket.emit("channelOpts", this.opts);
user.socket.emit("setPermissions", this.permissions);
user.socket.emit("updateMotd", this.motd);
user.socket.emit("drinkCount", {count: this.drinks});
user.socket.emit("drinkCount", this.drinks);
// Send things that require special permission
this.sendRankStuff(user);
@ -714,13 +712,9 @@ Channel.prototype.sendACL = function(user) {
}
Channel.prototype.sendPlaylist = function(user) {
user.socket.emit("playlist", {
pl: this.queue
});
user.socket.emit("updatePlaylistIdx", {
idx: this.position
});
user.socket.emit("updatePlaylistMeta", this.plmeta);
user.socket.emit("playlist", this.queue);
user.socket.emit("setPosition", this.position);
user.socket.emit("setPlaylistMeta", this.plmeta);
}
Channel.prototype.sendMediaUpdate = function(user) {
@ -779,13 +773,11 @@ Channel.prototype.broadcastPlaylistMeta = function() {
time: timestr
};
this.plmeta = packet;
this.sendAll("updatePlaylistMeta", packet);
this.sendAll("setPlaylistMeta", packet);
}
Channel.prototype.broadcastUsercount = function() {
this.sendAll("usercount", {
count: this.users.length
});
this.sendAll("usercount", this.users.length);
}
Channel.prototype.broadcastNewUser = function(user) {
@ -928,7 +920,7 @@ Channel.prototype.broadcastMotd = function() {
}
Channel.prototype.broadcastDrinks = function() {
this.sendAll("drinkCount", {count: this.drinks});
this.sendAll("drinkCount", this.drinks);
}
/* REGION Playlist Stuff */
@ -1134,7 +1126,7 @@ Channel.prototype.setTemp = function(idx, temp) {
var med = this.queue[idx];
med.temp = temp;
this.sendAll("setTemp", {
idx: idx,
position: idx,
temp: temp
});
@ -1147,36 +1139,37 @@ Channel.prototype.trySetTemp = function(user, data) {
if(!this.hasPermission(user, "settemp")) {
return;
}
if(typeof data.idx != "number" || typeof data.temp != "boolean") {
if(typeof data.position != "number" || typeof data.temp != "boolean") {
return;
}
if(data.idx < 0 || data.idx >= this.queue.length) {
if(data.position < 0 || data.position >= this.queue.length) {
return;
}
this.setTemp(data.idx, data.temp);
this.setTemp(data.position, data.temp);
}
Channel.prototype.dequeue = function(data) {
if(data.pos < 0 || data.pos >= this.queue.length) {
Channel.prototype.dequeue = function(position) {
if(position < 0 || position >= this.queue.length) {
return;
}
this.queue.splice(data.pos, 1);
this.sendAll("unqueue", {
pos: data.pos
this.queue.splice(position, 1);
this.sendAll("delete", {
position: position
});
this.broadcastPlaylistMeta();
// If you remove the currently playing video, play the next one
if(data.pos == this.position && !data.removeonly) {
if(position == this.position) {
this.position--;
this.playNext();
return;
}
// If you remove a video whose position is before the one currently
// playing, you have to reduce the position of the one playing
if(data.pos < this.position) {
if(position < this.position) {
this.position--;
}
}
@ -1186,9 +1179,8 @@ Channel.prototype.tryDequeue = function(user, data) {
return;
}
if(data.pos === undefined) {
if(typeof data !== "number")
return;
}
this.dequeue(data);
}
@ -1250,10 +1242,7 @@ Channel.prototype.jumpTo = function(pos) {
this.media.paused = false;
this.sendAll("changeMedia", this.media.fullupdate());
this.sendAll("updatePlaylistIdx", {
old: old,
idx: this.position
});
this.sendAll("setPosition", this.position);
// If it's not a livestream, enable autolead
if(this.leader == null && !isLive(this.media.type)) {
@ -1269,11 +1258,11 @@ Channel.prototype.tryJumpTo = function(user, data) {
return;
}
if(data.pos === undefined) {
if(typeof data !== "number") {
return;
}
this.jumpTo(data.pos);
this.jumpTo(data);
}
Channel.prototype.clearqueue = function() {
@ -1343,34 +1332,35 @@ Channel.prototype.tryUpdate = function(user, data) {
this.sendAll("mediaUpdate", this.media.timeupdate());
}
Channel.prototype.move = function(data) {
if(data.src < 0 || data.src >= this.queue.length) {
Channel.prototype.move = function(data, user) {
if(data.from < 0 || data.from >= this.queue.length) {
return;
}
if(data.dest < 0 || data.dest > this.queue.length) {
if(data.to < 0 || data.to > this.queue.length) {
return;
}
var media = this.queue[data.src];
var dest = data.dest > data.src ? data.dest + 1 : data.dest;
var src = data.dest > data.src ? data.src : data.src + 1;
var media = this.queue[data.from];
var to = data.to > data.from ? data.to + 1 : data.to;
var from = data.to > data.from ? data.from : data.from + 1;
this.queue.splice(dest, 0, media);
this.queue.splice(src, 1);
this.queue.splice(to, 0, media);
this.queue.splice(from, 1);
this.sendAll("moveVideo", {
src: data.src,
dest: data.dest
from: data.from,
to: data.to,
moveby: user ? user.name : ""
});
// Account for moving things around the active video
if(data.src < this.position && data.dest >= this.position) {
if(data.from < this.position && data.to >= this.position) {
this.position--;
}
else if(data.src > this.position && data.dest < this.position) {
else if(data.from > this.position && data.to < this.position) {
this.position++
}
else if(data.src == this.position) {
this.position = data.dest;
else if(data.from == this.position) {
this.position = data.to;
}
}
@ -1448,7 +1438,7 @@ Channel.prototype.tryVoteskip = function(user) {
Channel.prototype.setLock = function(locked) {
this.openqueue = !locked;
this.sendAll("queueLock", {locked: locked});
this.sendAll("setPlaylistLocked", {locked: locked});
}
Channel.prototype.trySetLock = function(user, data) {
@ -1463,6 +1453,14 @@ Channel.prototype.trySetLock = function(user, data) {
this.setLock(data.locked);
}
Channel.prototype.tryToggleLock = function(user) {
if(!Rank.hasPermission(user, "qlock")) {
return;
}
this.setLock(this.openqueue);
}
Channel.prototype.updateFilter = function(filter) {
var found = false;
for(var i = 0; i < this.filters.length; i++) {

53
user.js
View File

@ -206,7 +206,7 @@ User.prototype.initCallbacks = function() {
}
}.bind(this));
this.socket.on("unqueue", function(data) {
this.socket.on("delete", function(data) {
if(this.channel != null) {
this.channel.tryDequeue(this, data);
}
@ -248,9 +248,9 @@ User.prototype.initCallbacks = function() {
}
}.bind(this));
this.socket.on("queueLock", function(data) {
this.socket.on("togglePlaylistLock", function() {
if(this.channel != null) {
this.channel.trySetLock(this, data);
this.channel.tryToggleLock(this);
}
}.bind(this));
@ -260,18 +260,18 @@ User.prototype.initCallbacks = function() {
}
}.bind(this));
this.socket.on("searchLibrary", function(data) {
this.socket.on("searchMedia", function(data) {
if(this.channel != null) {
if(data.yt) {
var callback = function(vids) {
this.socket.emit("librarySearchResults", {
this.socket.emit("searchResults", {
results: vids
});
}.bind(this);
this.channel.search(data.query, callback);
}
else {
this.socket.emit("librarySearchResults", {
this.socket.emit("searchResults", {
results: this.channel.search(data.query)
});
}
@ -331,12 +331,6 @@ User.prototype.initCallbacks = function() {
}
}.bind(this));
this.socket.on("adm", function(data) {
if(Rank.hasPermission(this, "acp")) {
this.handleAdm(data);
}
}.bind(this));
this.socket.on("announce", function(data) {
if(Rank.hasPermission(this, "announce")) {
if(data.clear) {
@ -500,27 +494,6 @@ User.prototype.initCallbacks = function() {
}.bind(this));
}
// Handle administration
User.prototype.handleAdm = function(data) {
if(data.cmd == "listchannels") {
var chans = [];
for(var chan in Server.channels) {
var nowplaying = "-";
if(Server.channels[chan].media != null)
nowplaying = Server.channels[chan].media.title;
chans.push({
name: chan,
usercount: Server.channels[chan].users.length,
nowplaying: nowplaying
});
}
this.socket.emit("adm", {
cmd: "listchannels",
chans: chans
});
}
};
var lastguestlogin = {};
// Attempt to login
User.prototype.login = function(name, pw, session) {
@ -568,11 +541,10 @@ User.prototype.login = function(name, pw, session) {
this.name = name;
this.loggedIn = false;
this.socket.emit("login", {
success: true
});
this.socket.emit("rank", {
rank: this.rank
success: true,
name: name
});
this.socket.emit("rank", this.rank);
if(this.channel != null) {
this.channel.logger.log(this.ip + " signed in as " + name);
this.channel.broadcastNewUser(this);
@ -585,7 +557,8 @@ User.prototype.login = function(name, pw, session) {
this.loggedIn = true;
this.socket.emit("login", {
success: true,
session: row.session_hash
session: row.session_hash,
name: name
});
Logger.syslog.log(this.ip + " logged in as " + name);
this.profile = {
@ -597,9 +570,7 @@ User.prototype.login = function(name, pw, session) {
var rank = (chanrank > row.global_rank) ? chanrank
: row.global_rank;
this.rank = (this.rank > rank) ? this.rank : rank;
this.socket.emit("rank", {
rank: this.rank
});
this.socket.emit("rank", this.rank);
this.name = name;
if(this.channel != null) {
this.channel.logger.log(this.ip + " logged in as " + name);

View File

@ -116,6 +116,7 @@ html, body {
#plmeta {
border: 1px solid #aaaaaa;
border-top: 0;
background-color: #ffffff;
padding: 3px;
margin: 0;

View File

@ -16,7 +16,7 @@ Callbacks = {
socket.emit("joinChannel", {
name: CHANNEL.name
});
if(uname && session) {
if(NAME && SESSION) {
socket.emit("login", {
name: NAME,
session: SESSION
@ -231,7 +231,8 @@ Callbacks = {
setPermissions: function(perms) {
CHANNEL.perms = perms;
if(CLIENT.rank >= Rank.Admin)
genPermissionsEditor();
1;
//genPermissionsEditor();
handlePermissionChange();
},
@ -464,9 +465,9 @@ Callbacks = {
}
},
drinkCount: function(data) {
if(data.count != 0) {
var text = data.count + " drink";
drinkCount: function(count) {
if(count != 0) {
var text = count + " drink";
if(data.count != 1) {
text += "s";
}
@ -484,7 +485,6 @@ Callbacks = {
var q = $("#queue");
q.html("");
for(var i = 0; i < data.length; i++) {
Callbacks.queue({
media: data[i],
@ -550,6 +550,7 @@ Callbacks = {
},
moveVideo: function(data) {
if(data.moveby != CLIENT.name)
playlistMove(data.src, data.dest);
},
@ -598,22 +599,7 @@ Callbacks = {
setPlaylistLocked: function(data) {
CHANNEL.openqueue = !data.locked;
// TODO handlePermissionsChange?
if(CHANNEL.openqueue) {
$("#playlist_controls").css("display", "");
if(RANK < Rank.Moderator) {
$("#qlockbtn").css("display", "none");
rebuildPlaylist();
if(!CHANNELOPTS.qopen_allow_qnext)
$("#queue_next").attr("disabled", true);
if(!CHANNELOPTS.qopen_allow_playnext)
$("#play_next").attr("disabled", true);
}
}
else if(RANK < Rank.Moderator && !LEADER) {
$("#playlist_controls").css("display", "none");
rebuildPlaylist();
}
handlePermissionChange();
if(CHANNEL.openqueue) {
$("#qlockbtn").removeClass("btn-danger")
.addClass("btn-success")
@ -802,25 +788,39 @@ Callbacks = {
}
/*
pl = [];
for(var i = 0; i < 10; i++) {
var m = {
title: "Test " + i,
type: "yt",
id: "test" + i,
seconds: 0,
duration: "00:00"
};
pl.push(m);
}
setTimeout(function() {
Callbacks.playlist(pl);
}, 1000);
*/
$.getScript(IO_URL+"/socket.io/socket.io.js", function() {
try {
socket = io.connect(IO_URL);
for(var key in Callbacks) {
socket.on(key, Callbacks[key]);
}
setupCallbacks();
}
catch(e) {
Callbacks.disconnect();
}
});
*/
setupCallbacks = function() {
for(var key in Callbacks) {
(function(key) {
socket.on(key, function() {
Callbacks[key]();
socket.on(key, function(data) {
Callbacks[key](data);
});
})(key);
}
}

View File

@ -17,6 +17,7 @@ var CHANNEL = {
};
var PLAYER = false;
var FLUIDLAYOUT = false;
var VWIDTH = $("#ytapiplayer").parent().css("width").replace("px", "");
var VHEIGHT = ""+parseInt(parseInt(VWIDTH) * 9 / 16);
var POSITION = -1;
@ -39,6 +40,8 @@ var KICKED = false;
var NAME = readCookie("cytube_uname");
var SESSION = readCookie("cytube_session");
var LEADTMR = false;
var PL_FROM = 0;
var PL_TO = 0;
function getOrDefault(k, def) {
var v = localStorage.getItem(k);

556
www/assets/js/player.js Normal file
View File

@ -0,0 +1,556 @@
/*
The MIT License (MIT)
Copyright (c) 2013 Calvin Montgomery
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
var Player = function(data) {
if(!data) {
data = {
id: "",
type: "null"
};
}
this.id = data.id;
this.type = data.type;
this.diff = 0;
switch(this.type) {
case "yt":
this.initYouTube();
break;
case "vi":
this.initVimeo();
break;
case "dm":
this.initDailymotion();
break;
case "sc":
this.initSoundcloud();
break;
case "li":
this.initLivestream();
break;
case "tw":
this.initTwitch();
break;
case "jt":
this.initJustinTV();
break;
case "rt":
this.initRTMP();
break;
case "jw":
this.initJWPlayer();
break;
case "us":
this.initUstream();
break;
case "im":
this.initImgur();
break;
default:
this.nullPlayer();
break;
}
}
Player.prototype.nullPlayer = function() {
this.player = null;
this.load = function(data) { }
this.play = function() { }
this.pause = function() { }
this.getTime = function(callback) { }
this.seek = function(time) { }
}
Player.prototype.initYouTube = function() {
this.removeOld();
this.player = new YT.Player("ytapiplayer", {
height: VHEIGHT,
width: VWIDTH,
videoId: this.id,
playerVars: {
"autoplay": 1,
"controls": 1,
},
events: {
onReady: function() {
socket.emit("playerReady");
},
onStateChange: function(ev) {
if(PLAYER.paused && ev.data != YT.PlayerState.PAUSED
|| !PLAYER.paused && ev.data == YT.PlayerState.PAUSED) {
PLAYER.paused = (ev.data == YT.PlayerState.PAUSED);
sendVideoUpdate();
}
else {
PLAYER.paused = (ev.data == YT.PlayerState.PAUSED);
}
if(CLIENT.leader && ev.data == YT.PlayerState.ENDED) {
socket.emit("playNext");
}
}
}
});
$("#ytapiplayer").css("border", "none");
this.load = function(data) {
if(this.player.loadVideoById) {
this.player.loadVideoById(data.id, data.currentTime);
this.id = data.id;
}
}
this.pause = function() {
this.player.pauseVideo();
}
this.play = function() {
this.player.playVideo();
}
this.getTime = function(callback) {
callback(this.player.getCurrentTime());
}
this.seek = function(time) {
this.player.seekTo(time, true);
}
}
Player.prototype.initVimeo = function() {
var iframe = $("<iframe/>").insertBefore($("#ytapiplayer"));
$("#ytapiplayer").remove();
iframe.attr("id", "ytapiplayer");
iframe.attr("width", VWIDTH);
iframe.attr("height", VHEIGHT);
iframe.attr("src", "http://player.vimeo.com/video/"+this.id+"?api=1&player_id=ytapiplayer");
iframe.attr("webkitAllowFullScreen", "");
iframe.attr("mozallowfullscreen", "");
iframe.attr("allowFullScreen", "");
iframe.css("border", "none");
this.player = $f(iframe[0]);
$f(iframe[0]).addEvent("ready", function() {
socket.emit("playerReady");
this.player = $f(iframe[0]);
this.player.api("play");
this.player.addEvent("finish", function() {
if(CLIENT.leader) {
socket.emit("playNext");
}
});
this.player.addEvent("pause", function() {
PLAYER.paused = true;
sendVideoUpdate();
});
this.player.addEvent("play", function() {
PLAYER.paused = false;
sendVideoUpdate();
});
}.bind(this));
this.load = function(data) {
this.id = data.id;
this.initVimeo();
}
this.pause = function() {
this.player.api("pause");
}
this.play = function() {
this.player.api("play");
}
this.getTime = function(callback) {
// Vimeo api returns time as a string because fuck logic
this.player.api("getCurrentTime", function(time) {
callback(parseFloat(time));
});
}
this.seek = function(time) {
this.player.api("seekTo", time);
}
}
Player.prototype.initDailymotion = function() {
this.removeOld();
this.player = DM.player("ytapiplayer", {
video: this.id,
width: parseInt(VWIDTH),
height: parseInt(VHEIGHT),
params: {autoplay: 1}
});
this.player.addEventListener("apiready", function(e) {
socket.emit("playerReady");
this.player.addEventListener("ended", function(e) {
if(CLIENT.leader) {
socket.emit("playNext");
}
});
this.player.addEventListener("pause", function(e) {
PLAYER.paused = true;
sendVideoUpdate();
});
this.player.addEventListener("playing", function(e) {
PLAYER.paused = false;
sendVideoUpdate();
});
}.bind(this));
this.load = function(data) {
this.id = data.id;
this.player.api("load", data.id);
}
this.pause = function() {
this.player.api("pause");
}
this.play = function() {
this.player.api("play");
}
this.getTime = function(callback) {
callback(this.player.currentTime);
}
this.seek = function(seconds) {
this.player.api("seek", seconds);
}
}
Player.prototype.initSoundcloud = function() {
unfixSoundcloudShit();
var iframe = $("<iframe/>").insertBefore($("#ytapiplayer"));
$("#ytapiplayer").remove();
iframe.attr("id", "ytapiplayer");
iframe.attr("src", "https://w.soundcloud.com/player/?url=" + this.id);
iframe.css("width", "100%").attr("height", "166");
iframe.css("border", "none");
this.player = SC.Widget("ytapiplayer");
this.player.bind(SC.Widget.Events.READY, function() {
socket.emit("playerReady");
this.player.load(this.id, {auto_play: true});
this.player.bind(SC.Widget.Events.PAUSE, function() {
PLAYER.paused = true;
sendVideoUpdate();
});
this.player.bind(SC.Widget.Events.PLAY, function() {
PLAYER.paused = false;
sendVideoUpdate();
});
this.player.bind(SC.Widget.Events.FINISH, function() {
if(CLIENT.leader) {
socket.emit("playNext");
}
});
}.bind(this));
this.load = function(data) {
this.id = data.id;
this.player.load(data.id, {auto_play: true});
}
this.pause = function() {
this.player.pause();
}
this.play = function() {
this.player.play();
}
this.getTime = function(callback) {
this.player.getPosition(function(pos) {
callback(pos / 1000);
});
}
this.seek = function(seconds) {
this.player.seekTo(seconds * 1000);
}
}
Player.prototype.initLivestream = function() {
this.removeOld();
var flashvars = {channel: this.id};
var params = {AllowScriptAccess: "always"};
swfobject.embedSWF("http://cdn.livestream.com/chromelessPlayer/v20/playerapi.swf", "ytapiplayer", VWIDTH, VHEIGHT, "9.0.0", "expressInstall.swf", flashvars, params);
this.load = function(data) {
this.id = data.id;
this.initLivestream();
}
this.pause = function() { }
this.play = function() { }
this.getTime = function() { }
this.seek = function() { }
}
Player.prototype.initTwitch = function() {
this.removeOld();
var url = "http://www.twitch.tv/widgets/live_embed_player.swf?channel="+this.id;
var params = {
allowFullScreen:"true",
allowScriptAccess:"always",
allowNetworking:"all",
movie:"http://www.twitch.tv/widgets/live_embed_player.swf",
id: "live_embed_player_flash",
flashvars:"hostname=www.twitch.tv&channel="+this.id+"&auto_play=true&start_volume=100"
};
swfobject.embedSWF( url, "ytapiplayer", VWIDTH, VHEIGHT, "8", null, null, params, {} );
this.load = function(data) {
this.id = data.id;
this.initTwitch();
}
this.pause = function() { }
this.play = function() { }
this.getTime = function() { }
this.seek = function() { }
}
Player.prototype.initJustinTV = function() {
this.removeOld();
var url = "http://www.justin.tv/widgets/live_embed_player.swf?channel="+this.id;
var params = {
allowFullScreen:"true",
allowScriptAccess:"always",
allowNetworking:"all",
movie:"http://www.justin.tv/widgets/live_embed_player.swf",
id: "live_embed_player_flash",
flashvars:"hostname=www.justin.tv&channel="+this.id+"&auto_play=true&start_volume=100"
};
swfobject.embedSWF( url, "ytapiplayer", VWIDTH, VHEIGHT, "8", null, null, params, {} );
this.load = function(data) {
this.id = data.id;
this.initTwitch();
}
this.pause = function() { }
this.play = function() { }
this.getTime = function() { }
this.seek = function() { }
}
Player.prototype.initRTMP = function() {
this.removeOld();
var url = "http://fpdownload.adobe.com/strobe/FlashPlayerPlayback_101.swf";
var src = encodeURIComponent(this.id);
var params = {
allowFullScreen:"true",
allowScriptAccess:"always",
allowNetworking:"all",
wMode:"direct",
movie:"http://fpdownload.adobe.com/strobe/FlashPlayerPlayback_101.swf",
flashvars:"src="+src+"&streamType=live&autoPlay=true"
};
swfobject.embedSWF(url, "ytapiplayer", VWIDTH, VHEIGHT, "8", null, null, params, {} );
this.load = function(data) {
this.id = data.id;
this.initTwitch();
}
this.pause = function() { }
this.play = function() { }
this.getTime = function() { }
this.seek = function() { }
}
Player.prototype.initJWPlayer = function() {
if(typeof jwplayer == "undefined") {
setTimeout(function() {
this.initJWPlayer();
}.bind(this), 100);
return;
}
this.removeOld();
jwplayer("ytapiplayer").setup({
file: this.id,
width: VWIDTH,
height: VHEIGHT,
autostart: true
});
jwplayer().onPlay(function() {
this.paused = false;
}.bind(this));
jwplayer().onPause(function() {
this.paused = true;
}.bind(this));
jwplayer().onComplete(function() {
socket.emit("playNext");
});
this.load = function(data) {
this.id = data.id;
this.initJWPlayer();
}
this.pause = function() {
jwplayer().pause(true);
}
this.play = function() {
jwplayer().play(true);
}
this.getTime = function(callback) {
// Only return time for non-live media
if(jwplayer().getDuration() != -1) {
callback(jwplayer().getPosition());
}
}
this.seek = function(time) {
jwplayer().seek(time);
}
}
Player.prototype.initUstream = function() {
var iframe = $("<iframe/>").insertBefore($("#ytapiplayer"));
$("#ytapiplayer").remove();
iframe.attr("id", "ytapiplayer");
iframe.attr("width", VWIDTH);
iframe.attr("height", VHEIGHT);
iframe.attr("src", "http://www.ustream.tv/embed/"+this.id+"?v=3&wmode=direct");
iframe.attr("frameborder", "0");
iframe.attr("scrolling", "no");
iframe.css("border", "none");
this.load = function(data) {
this.id = data.id;
this.initUstream();
}
this.pause = function() { }
this.play = function() { }
this.getTime = function() { }
this.seek = function() { }
}
Player.prototype.initImgur = function() {
var iframe = $("<iframe/>").insertBefore($("#ytapiplayer"));
$("#ytapiplayer").remove();
iframe.attr("id", "ytapiplayer");
iframe.attr("width", VWIDTH);
iframe.attr("height", VHEIGHT);
iframe.attr("src", "http://imgur.com/a/"+this.id+"/embed");
iframe.attr("frameborder", "0");
iframe.attr("scrolling", "no");
iframe.css("border", "none");
this.load = function(data) {
this.id = data.id;
this.initImgur()
}
this.pause = function() { }
this.play = function() { }
this.getTime = function() { }
this.seek = function() { }
}
Player.prototype.update = function(data) {
if(data.id && data.id != this.id) {
if(data.currentTime < 0) {
data.currentTime = 0;
}
this.load(data);
}
if(!USEROPTS.synch) {
return;
}
if(data.paused) {
this.seek(data.currentTime);
this.pause();
}
else {
this.play();
}
if(CLIENT.leader) {
return;
}
this.getTime(function(seconds) {
var time = data.currentTime;
var diff = time - seconds || time;
if(diff > USEROPTS.sync_accuracy) {
this.seek(time);
}
else if(diff < -USEROPTS.sync_accuracy) {
this.seek(time + 1);
}
}.bind(this));
}
Player.prototype.removeOld = function() {
var old = $("#ytapiplayer");
var placeholder = $("<div/>").insertBefore(old);
old.remove();
placeholder.attr("id", "ytapiplayer");
}
Player.prototype.hide = function() {
if(!/chrome/ig.test(navigator.userAgent)) {
return;
}
this.size = {
width: $("#ytapiplayer").attr("width"),
height: $("#ytapiplayer").attr("height")
};
$("#ytapiplayer").attr("width", 1)
.attr("height", 1);
}
Player.prototype.unhide = function() {
if(!/chrome/ig.test(navigator.userAgent)) {
return;
}
$("#ytapiplayer").attr("width", this.size.width)
.attr("height", this.size.height);
}

View File

@ -158,7 +158,20 @@ $("#userpl_save").click(function() {
/* playlist controls */
$(function() {
$("#queue").sortable();
$("#queue").sortable({
start: function(ev, ui) {
PL_FROM = ui.item.prevAll().length;
},
update: function(ev, ui) {
PL_TO = ui.item.prevAll().length;
if(PL_TO != PL_FROM) {
socket.emit("moveMedia", {
from: PL_FROM,
to: PL_TO
});
}
}
});
$("#queue").disableSelection();
});
@ -245,3 +258,59 @@ $("#shuffleplaylist").click(function() {
socket.emit("shufflePlaylist");
}
});
/* layout stuff */
$(window).resize(function() {
VWIDTH = $("#ytapiplayer").parent().css("width").replace("px", "");
var VHEIGHT = ""+parseInt(parseInt(VWIDTH) * 9 / 16);
$("#messagebuffer").css("height", (VHEIGHT - 31) + "px");
$("#userlist").css("height", (VHEIGHT - 31) + "px");
$("#ytapiplayer").attr("width", VWIDTH);
$("#ytapiplayer").attr("height", VHEIGHT);
});
/* initial YouTube api */
if(!USEROPTS.hidevid) {
var tag = document.createElement("script");
tag.src = "http://www.youtube.com/iframe_api";
var firstScriptTag = document.getElementsByTagName("script")[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
}
function onYouTubeIframeAPIReady() {
if(!PLAYER)
PLAYER = new Player({id:"", type: "yt"});
if(FLUIDLAYOUT)
fluid();
}
/* load channel */
var loc = document.location+"";
var m = loc.match(/\/r\/([a-zA-Z0-9-_]+)$/);
if(m) {
CHANNEL.name = m[1];
}
else {
var main = $("#main");
var container = $("<div/>").addClass("container").insertBefore(main);
var row = $("<div/>").addClass("row").appendTo(container);
var div = $("<div/>").addClass("span6").appendTo(row);
main.css("display", "none");
var label = $("<label/>").text("Enter Channel:").appendTo(div);
var entry = $("<input/>").attr("type", "text").appendTo(div);
entry.keydown(function(ev) {
var host = ""+document.location;
host = host.replace("http://", "");
host = host.substring(0, host.indexOf("/"));
if(ev.keyCode == 13) {
document.location = "http://" + host + "/r/" + entry.val();
socket.emit("joinChannel", {
name: entry.val()
});
container.remove();
main.css("display", "");
}
});
}

View File

@ -18,8 +18,37 @@ function makeAlert(title, text, klass) {
return al;
}
function formatURL(data) {
switch(data.type) {
case "yt":
return "http://youtube.com/watch?v=" + data.id;
case "vi":
return "http://vimeo.com/" + data.id;
case "dm":
return "http://dailymotion.com/video/" + data.id;
case "sc":
return data.id;
case "li":
return "http://livestream.com/" + data.id;
case "tw":
return "http://twitch.tv/" + data.id;
case "jt":
return "http://justin.tv/" + data.id;
case "rt":
return data.id;
case "jw":
return data.id;
case "im":
return "http://imgur.com/a/" + data.id;
case "us":
return "http://ustream.tv/" + data.id;
default:
return "#";
}
}
function formatUserlistItem(div, data) {
var name = $(div.children[1]);
var name = $(div.children()[1]);
name.removeClass();
name.css("font-style", "");
name.addClass(getNameColor(data.rank));
@ -49,7 +78,7 @@ function formatUserlistItem(div, data) {
profile.remove();
});
var flair = div.children[0];
var flair = div.children()[0];
flair.innerHTML = "";
// denote current leader with a star
if(data.leader) {
@ -205,7 +234,7 @@ function makeQueueEntry(video) {
}
var title = $("<a/>").addClass("qe_title").appendTo(li)
.text(video.title)
.attr("href", "#")//formatURL(video))
.attr("href", formatURL(video))
.attr("target", "_blank");
var time = $("<span/>").addClass("qe_time").appendTo(li);
time.text(video.duration);
@ -214,9 +243,16 @@ function makeQueueEntry(video) {
li.addClass("queue_temp");
}
// TODO Permissions
addQueueButtons(li);
return li;
}
function addQueueButtons(li) {
li.find(".btn-group").remove();
var menu = $("<div/>").addClass("btn-group").appendTo(li);
// Play
if(hasPermission("playlistjump")) {
$("<button/>").addClass("btn btn-mini qbtn-play")
.html("<i class='icon-play'></i>Play")
.click(function() {
@ -224,7 +260,9 @@ function makeQueueEntry(video) {
socket.emit("jumpTo", i);
})
.appendTo(menu);
}
// Queue next
if(hasPermission("playlistnext")) {
$("<button/>").addClass("btn btn-mini qbtn-next")
.html("<i class='icon-share-alt'></i>Queue Next")
.click(function() {
@ -235,7 +273,9 @@ function makeQueueEntry(video) {
});
})
.appendTo(menu);
}
// Temp/Untemp
if(hasPermission("settemp")) {
$("<button/>").addClass("btn btn-mini qbtn-tmp")
.html("<i class='icon-flag'></i>Make Temporary")
.click(function() {
@ -247,7 +287,9 @@ function makeQueueEntry(video) {
});
})
.appendTo(menu);
}
// Delete
if(hasPermission("playlistdelete")) {
$("<button/>").addClass("btn btn-mini qbtn-delete")
.html("<i class='icon-trash'></i>Delete")
.click(function() {
@ -255,6 +297,7 @@ function makeQueueEntry(video) {
socket.emit("delete", i);
})
.appendTo(menu);
}
menu.hide();
@ -266,21 +309,12 @@ function makeQueueEntry(video) {
menu.hide("blind");
return false;
});
menu.blur(function() {
menu.hide();
});
return li;
}
function addQueueButtons(li) {
}
function rebuildPlaylist() {
$("#queue li").each(function() {
$(this).find(".btn-group").remove();
addQueueButtons(this);
addQueueButtons($(this));
});
}
@ -733,7 +767,7 @@ function loadSearchPage(page) {
var li = makeQueueEntry(results[i]);
if(hasPermission("playlistadd")) {
if(results[i].thumb) {
addLibraryButtons(li, results[i].id, true);
addLibraryButtons(li, results[i].id, "yt");
}
else {
addLibraryButtons(li, results[i].id);
@ -748,3 +782,206 @@ function loadSearchPage(page) {
$($("#search_pagination").find("li")[page]).addClass("active");
}
}
function addLibraryButtons(li, id, type) {
var btns = $("<div/>").addClass("btn-group")
.prependTo(li);
if(hasPermission("playlistadd")) {
if(hasPermission("playlistnext")) {
$("<button/>").addClass("btn btn-mini")
.text("Next")
.click(function() {
socket.emit("queue", {
id: id,
pos: "next",
type: type
});
})
.appendTo(btns);
}
$("<button/>").addClass("btn btn-mini")
.text("End")
.click(function() {
socket.emit("queue", {
id: id,
pos: "end",
type: type
});
})
.appendTo(btns);
}
}
/* queue stuff */
function playlistMove(from, to) {
if(from < 0 || to < 0)
return false;
var q = $("#queue");
if(from >= q.children().length)
return false;
var old = $(q.children()[from]);
old.hide("blind", function() {
old.remove();
if(to >= q.children().length)
old.appendTo(q);
else
old.insertBefore(q.children()[to]);
old.show("blind");
});
}
function parseMediaLink(url) {
if(typeof url != "string") {
return {
id: null,
type: null
};
}
url = url.trim();
if(url.indexOf("jw:") == 0) {
return {
id: url.substring(3),
type: "jw"
};
}
if(url.indexOf("rtmp://") == 0) {
return {
id: url,
type: "rt"
};
}
var m;
if((m = url.match(/youtube\.com\/watch\?v=([^&#]+)/))) {
return {
id: m[1],
type: "yt"
};
}
if((m = url.match(/youtu\.be\/([^&#]+)/))) {
return {
id: m[1],
type: "yt"
};
}
if((m = url.match(/youtube\.com\/playlist\?list=([^&#]+)/))) {
return {
id: m[1],
type: "yp"
};
}
if((m = url.match(/twitch\.tv\/([^&#]+)/))) {
return {
id: m[1],
type: "tw"
};
}
if((m = url.match(/justin\.tv\/([^&#]+)/))) {
return {
id: m[1],
type: "jt"
};
}
if((m = url.match(/livestream\.com\/([^&#]+)/))) {
return {
id: m[1],
type: "li"
};
}
if((m = url.match(/ustream\.tv\/([^&#]+)/))) {
return {
id: m[1],
type: "us"
};
}
if((m = url.match(/vimeo\.com\/([^&#]+)/))) {
return {
id: m[1],
type: "vi"
};
}
if((m = url.match(/dailymotion\.com\/video\/([^&#]+)/))) {
return {
id: m[1],
type: "dm"
};
}
if((m = url.match(/imgur\.com\/a\/([^&#]+)/))) {
return {
id: m[1],
type: "im"
};
}
if((m = url.match(/soundcloud\.com\/([^&#]+)/))) {
return {
id: url,
type: "sc"
};
}
}
/* chat */
function addChatMessage(data) {
if(IGNORED.indexOf(data.username) != -1) {
return;
}
var div = formatChatMessage(data);
div.data("sender", data.username);
div.appendTo($("#messagebuffer"));
div.mouseover(function() {
$("#messagebuffer").children().each(function() {
var name = $(this).data("sender");
if(name == data.username) {
$(this).addClass("nick-hover");
}
});
});
div.mouseleave(function() {
$("#messagebuffer").children().each(function() {
$(this).removeClass("nick-hover");
});
});
// Cap chatbox at most recent 100 messages
if($("#messagebuffer").children().length > 100) {
$($("#messagebuffer").children()[0]).remove();
}
if(SCROLLCHAT)
scrollChat();
if(USEROPTS.blink_title && !FOCUSED && !TITLE_BLINK) {
TITLE_BLINK = setInterval(function() {
if(document.title == "*Chat*")
document.title = PAGETITLE;
else
document.title = "*Chat*";
}, 1000);
}
if(CLIENT.name && data.username != CLIENT.name) {
if(data.msg.toUpperCase().indexOf(CLIENT.name.toUpperCase()) != -1) {
div.addClass("nick-highlight");
if(!FOCUSED && !TITLE_BLINK) {
TITLE_BLINK = setInterval(function() {
if(document.title == "*Chat*")
document.title = PAGETITLE;
else
document.title = "*Chat*";
}, 1000);
}
}
}
}

View File

@ -4,27 +4,26 @@
<meta charset="utf-8">
<title>CyTube</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="">
<meta name="description" content="A free, open source synchtube replacement">
<meta name="author" content="Calvin 'calzoneman' Montgomery">
<link href="./assets/css/bootstrap.css" rel="stylesheet">
<link href="./assets/css/bootstrap-responsive.css" rel="stylesheet">
<link href="./assets/css/ytsync.css" rel="stylesheet" id="defaultcss">
<link href="http://code.jquery.com/ui/1.10.3/themes/smoothness/jquery-ui.css" rel="stylesheet">
<style>
body {
padding-top: 60px; /* 60px to make the container go all the way to the bottom of the topbar */
padding-top: 60px;
}
</style>
<link href="./assets/css/bootstrap-responsive.css" rel="stylesheet">
</head>
<body>
<div class="wrapper">
<!-- begin navbar -->
<div class="navbar navbar-inverse navbar-fixed-top">
<div class="navbar-inner">
<div class="container">
<a class="brand" href="index.html">CyTube</a>
<div class="">
<ul class="nav">
<li class="active"><a href="index.html">Home</a></li>
<li><a href="help.html">Help</a></li>
@ -43,79 +42,99 @@
</div>
</div>
</div>
</div>
<div class="container">
<div class="row-fluid" id="motdrow">
<div class="well span12">
<p id="motd"></p>
<!-- end navbar -->
<!-- begin main page -->
<div class="container" id="mainpage">
<!-- top row (MOTD, drink count) -->
<div class="row-fluid" id="toprow">
<div class="well span12" id="motd">
</div>
<div class="span12" id="drinkbar">
<h1 id="drinkcount"></h1>
</div>
</div>
<div class="row-fluid" id="announcerow" style="display: none">
<!-- announcement area -->
<div class="row-fluid" id="announcements">
</div>
<div class="row" id="main" style="margin-top: 20px;">
<div class="span5" id="chatdiv">
<div id="usercountcontainer">
<i class="icon-chevron-up" id="ulistchevron" title="Show/Hide Userlist"></i>
<p id="usercount"></p>
<!-- main row -->
<div class="row" id="main">
<!-- chat container -->
<div class="span5" id="chatwrap">
<!-- user count -->
<div id="usercountwrap" class="pointer">
<i class="icon-chevron-up pull-left" id="userlisttoggle" title="Show/Hide Userlist"></i>
<p id="usercount">Not connected</p>
</div>
<!-- userlist -->
<div id="userlist">
</div>
<!-- message buffer -->
<div id="messagebuffer">
</div>
<!-- chat input -->
<input type="text" id="chatline" maxlength="240" class="span5">
</div>
<div class="span7" id="videodiv">
<p id="currenttitle">Currently Playing: </p>
<!-- video container -->
<div class="span7" id="videowrap">
<!-- current video display -->
<p id="currenttitle">Nothing playing</p>
<!-- video frame -->
<div id="ytapiplayer">
</div>
</div>
</div>
<div class="row" id="queuerow">
<div class="span5">
<div class="row-fluid">
<div class="span12" id="pollcontainer">
<!-- playlist row -->
<div class="row" id="playlistrow">
<!-- left pane - Library + user playlists -->
<div class="span5" id="leftpane-outer">
<div class="row-fluid" id="leftpane-inner">
<!-- poll container -->
<div class="span12" id="pollwrap">
<!-- new poll controls -->
<button class="btn btn-primary btn-small" id="newpollbtn">New Poll</button>
</div>
<div class="span12" id="leftpanetabs" style="margin-left: 0">
<ul class="nav nav-pills">
<li class="active"><a href="javascript:void(0)" id="show_library">Library/YouTube Search</a></li>
<li><a href="javascript:void(0)" id="show_userpl">User Playlists</a></li>
<!-- library search -->
<div class="span12 pointer" id="librarytoggle">
<i class="icon-chevron-down pull-left"></i>
<p>Show Library</p>
</div>
<div id="userplcontainer" style="display: none">
<div class="span7" style="margin-left: 0;">
<div id="librarywrap">
<div class="span7" id="querywrap">
<input type="text" id="library_query" class="input-block-level" placeholder="Search Query">
</div>
<div class="span5 btn-group" id="searchbtns">
<button class="btn" id="library_search">Library</button>
<button class="btn" id="youtube_search">YouTube</button>
</div>
</div>
<!-- user playlists -->
<div class="span12 pointer" id="userpltoggle">
<i class="icon-chevron-down pull-left"></i>
<p>Show Playlist Manager</p>
</div>
<div id="userplaylistwrap">
<div class="span7">
<input type="text" id="userpl_name" class="input-block-level" placeholder="Playlist Name">
</div>
<div class="span5">
<button class="btn btn-block" id="userpl_save">Save Playlist</button>
</div>
<ul class="span12" id="userpl_list" style="margin-left: 0">
<ul class="span12" id="userpl_list">
</ul>
</div>
<div id="libcontainer">
<div class="span7" style="margin-left: 0;">
<input type="text" id="library_query" class="input-block-level" placeholder="Search Query">
</div>
<div class="span5 btn-group">
<button class="btn" id="library_search">Library</button>
<button class="btn" id="youtube_search">YouTube</button>
</div>
<div class="span12" style="margin-left: 0;">
<ul id="library" class="videolist">
</ul>
<button class="btn btn-block" id="search_clear">Clear Results</button>
</div>
</div>
<!-- right pane - channel playlist -->
<div class="span7" id="rightpane-outer">
<div class="row-fluid" id="rightpane-inner">
<!-- account for left pane having the poll buffer -->
<div class="span12" id="queue_align"></div>
<!-- playlist controls -->
<div class="span12 pointer" id="playlisttoggle">
<i class="icon-chevron-down pull-left"></i>
<p>Show Playlist Controls</p>
</div>
</div>
<div class="span7" id="queuediv">
<div class="row-fluid" id="qclear">
<div class="span12"></div>
</div>
<div id="playlist_controls"style="display: none;">
<div class="row-fluid">
<div id="playlist_controls">
<div class="span8">
<input type="text" id="mediaurl" class="input-block-level" placeholder="Media URL">
</div>
@ -123,185 +142,24 @@
<button class="btn" id="queue_next">Next</button>
<button class="btn" id="queue_end">End</button>
</div>
<div id="extended_controls" class="span12">
<button class="btn btn-danger btn-block" id="qlockbtn">Unlock Queue</button>
<button class="btn btn-block" id="getplaylist">Get Playlist URLs</button>
<button class="btn btn-block" id="clearplaylist">Clear Playlist</button>
<button class="btn btn-block" id="shuffleplaylist">Shuffle Playlist</button>
</div>
</div>
<button class="btn btn-block" id="voteskip">Voteskip</button>
<div class="row-fluid">
<ul id="queue" class="span12 videolist">
<!-- video queue -->
<ul class="span12 videolist" id="queue">
</ul>
<div class="span12 well well-small" id="plmeta">
<span id="plcount"></span>
<span id="pllength"></span>
<div class="clear: both;"></div>
</div>
<button class="btn btn-danger btn-block" id="qlockbtn" style="display:none;">Unlock Queue</button>
<button class="btn btn-block" id="getplaylist" style="display: none;">Get Playlist URLs</button>
<button class="btn btn-block" id="clearplaylist" style="display: none;">Clear Playlist</button>
<button class="btn btn-block" id="shuffleplaylist" style="display: none;">Shuffle Playlist</button>
<div class="span12" id="plmeta">
<span id="plcount">0 items</span>
<span id="pllength">00:00:00</span>
</div>
</div>
</div>
<div class="row" style="display: none;" id="modnav">
<div class="span12" id="modtabs">
<ul class="nav nav-tabs">
<li class="active" id="chancontrols_tab">
<a href="javascript:void(0)" id="show_chancontrols">Channel Controls</a>
</li>
<li id="chanperms_tab">
<a href="javascript:void(0)" id="show_chanperms">Channel Permissions</a>
</li>
<li id="banlist_tab">
<a href="javascript:void(0)" id="show_banlist">Ban List</a>
</li>
<li id="loginlog_tab">
<a href="javascript:void(0)" id="show_loginlog">Connection Log</a>
</li>
<li id="motdeditor_tab">
<a href="javascript:void(0)" id="show_motdeditor">MOTD</a>
</li>
<li style="display: none" id="csseditor_tab">
<a href="javascript:void(0)" id="show_csseditor">CSS Editor</a>
</li>
<li style="display: none" id="jseditor_tab">
<a href="javascript:void(0)" id="show_jseditor">JS Editor</a>
</li>
<li id="filtereditor_tab">
<a href="javascript:void(0)" id="show_filtereditor">Chat Filters</a>
</li>
<li id="acl_tab">
<a href="javascript:void(0)" id="show_acl">Channel Ranks</a>
</li>
<li style="display: none" id="dropchannel_tab">
<a href="javascript:void(0)" id="drop_channel">Unregister Channel</a>
</li>
</ul>
</div>
</div>
<div class="row modonly" style="display: none" id="chancontrols">
<div class="span12">
<form action="javascript:void(0)">
<fieldset>
<div class="span5">
<label>Page Title
<input type="text" id="opt_pagetitle" placeholder="CyTube" class="pull-right">
</label>
<br>
<label>External CSS
<input type="text" id="opt_customcss" class="pull-right" placeholder="CSS URL">
</label>
<br>
<label>External JS
<input type="text" id="opt_customjs" class="pull-right" placeholder="JS URL">
</label>
<br>
<label class="checkbox">
<input type="checkbox" id="opt_allow_voteskip">
Allow voteskip
</label>
<br>
<label>Voteskip Ratio
<input type="text" id="opt_voteskip_ratio" class="pull-right">
</label>
<br>
<label class="checkbox">
<input type="checkbox" id="opt_chat_antiflood">
Prevent chat flood
</label>
<br>
<label class="checkbox">
<input type="checkbox" id="opt_show_public">
Show channel publicly
</label>
<br>
<label class="checkbox">
<input type="checkbox" id="opt_enable_link_regex">
Convert URLs to links in chat messages
</label>
</div>
<div class="span10">
<button class="btn btn-primary" id="opt_submit">Save</button>
</div>
</fieldset>
</form>
</div>
</div>
<div class="row modonly" id="chanperms" style="display: none;">
</div>
<div class="row modonly" id="banlist" style="display: none;">
<div class="span12">
<table class="table table-striped">
<thead>
<th></th>
<th>IP</th>
<th>Name</th>
<th>Banned By</th>
</thead>
</table>
</div>
</div>
<div class="row modonly" id="loginlog" style="display: none;">
<div class="span12">
<table class="table table-striped">
<thead>
<th></th>
<th>IP</th>
<th>Names</th>
</thead>
</table>
</div>
</div>
<div class="row modonly" id="motdeditor" style="display: none;">
<div class="span12">
<textarea rows="10" id="motdtext"></textarea>
<button class="btn btn-primary" id="updatemotd">Update</button>
</div>
</div>
<div class="row modonly" id="csseditor" style="display: none;">
<div class="span12">
<p>Max 20KB. If you need more CSS, host the file somewhere and use the External CSS channel option</p>
<textarea rows="10" id="csstext"></textarea>
<button class="btn btn-primary" id="updatecss">Update</button>
</div>
</div>
<div class="row modonly" id="jseditor" style="display: none;">
<div class="span12">
<p>Max 20KB. If you need more JS, host the file somewhere and use the External JS channel option</p>
<textarea rows="10" id="jstext"></textarea>
<button class="btn btn-primary" id="updatejs">Update</button>
</div>
</div>
<div class="row modonly" id="filtereditor" style="display: none;">
<div class="span12">
<p><strong>Note:</strong> if you just want simple word replacement, like emoticons, put the word in the Regex field, use <code>ig</code> for the flags, and put the replacement in the Replacement field.</p>
<table class="table table-striped">
<thead>
<th></th>
<th>Name</th>
<th>Regex</th>
<th>Flags</th>
<th>Replacement</th>
<th>Active</th>
</thead>
</table>
</div>
<div class="span12">
<p>Multiple filters can be added at once below. They should contain 3-4 fields separated by whitespace: (name) regex flags replacement.<br>If any field contains whitespace, it must be escaped by a backslash, for example "what\ a\ story\ mark"</p>
<textarea rows="10" class="input-block-level" id="multifiltereditor"></textarea>
<button class="btn btn-primary" id="multifilter">Update Multiple</button>
</div>
</div>
<div class="row modonly" id="channelranks" style="display: none;">
<div class="span12">
<table class="table table-striped">
<thead>
<th>Name</th>
<th>Rank (Click to edit)</th>
</thead>
</table>
</div>
</div>
</div> <!-- /container -->
<div class="push"></div>
<div id="sitefooter">
</div>
@ -315,13 +173,21 @@
</div>
<script src="./assets/js/jquery.js"></script>
<script src="http://code.jquery.com/ui/1.10.3/jquery-ui.js"></script>
<!-- My Javascript -->
<script src="./assets/js/data.js"></script>
<script src="./assets/js/iourl.js"></script>
<script src="./assets/js/player.js"></script>
<script src="./assets/js/util.js"></script>
<script src="./assets/js/ui.js"></script>
<script src="./assets/js/callbacks.js"></script>
<script src="./assets/js/notwebsocket.js"></script>
<script src="./assets/js/media.js"></script>
<!--
<script src="./assets/js/iourl.js"></script>
<script src="./assets/js/functions.js"></script>
<script src="./assets/js/client.js"></script>
<script src="./assets/js/callbacks.js"></script>
-->
<!-- APIs -->
<script src="http://api.dmcdn.net/all.js"></script>
<script src="http://jwpsrv.com/library/QouFCLBMEeKC+CIACpYGxA.js"></script>