mirror of https://github.com/calzoneman/sync.git
Continue rewriting things
This commit is contained in:
parent
161a116e59
commit
42fc2e45c8
100
channel.js
100
channel.js
|
@ -150,9 +150,7 @@ Channel.prototype.loadDump = function() {
|
||||||
}
|
}
|
||||||
this.queue.push(m);
|
this.queue.push(m);
|
||||||
}
|
}
|
||||||
this.sendAll("playlist", {
|
this.sendAll("playlist", this.queue);
|
||||||
pl: this.queue
|
|
||||||
});
|
|
||||||
this.broadcastPlaylistMeta();
|
this.broadcastPlaylistMeta();
|
||||||
// Backwards compatibility
|
// Backwards compatibility
|
||||||
if(data.currentPosition != undefined) {
|
if(data.currentPosition != undefined) {
|
||||||
|
@ -559,7 +557,7 @@ Channel.prototype.userJoin = function(user) {
|
||||||
// Set the new guy up
|
// Set the new guy up
|
||||||
this.sendPlaylist(user);
|
this.sendPlaylist(user);
|
||||||
this.sendMediaUpdate(user);
|
this.sendMediaUpdate(user);
|
||||||
user.socket.emit("queueLock", {locked: !this.openqueue});
|
user.socket.emit("setPlaylistLocked", {locked: !this.openqueue});
|
||||||
this.sendUserlist(user);
|
this.sendUserlist(user);
|
||||||
this.sendRecentChat(user);
|
this.sendRecentChat(user);
|
||||||
user.socket.emit("channelCSSJS", {css: this.css, js: this.js});
|
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("channelOpts", this.opts);
|
||||||
user.socket.emit("setPermissions", this.permissions);
|
user.socket.emit("setPermissions", this.permissions);
|
||||||
user.socket.emit("updateMotd", this.motd);
|
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
|
// Send things that require special permission
|
||||||
this.sendRankStuff(user);
|
this.sendRankStuff(user);
|
||||||
|
@ -714,13 +712,9 @@ Channel.prototype.sendACL = function(user) {
|
||||||
}
|
}
|
||||||
|
|
||||||
Channel.prototype.sendPlaylist = function(user) {
|
Channel.prototype.sendPlaylist = function(user) {
|
||||||
user.socket.emit("playlist", {
|
user.socket.emit("playlist", this.queue);
|
||||||
pl: this.queue
|
user.socket.emit("setPosition", this.position);
|
||||||
});
|
user.socket.emit("setPlaylistMeta", this.plmeta);
|
||||||
user.socket.emit("updatePlaylistIdx", {
|
|
||||||
idx: this.position
|
|
||||||
});
|
|
||||||
user.socket.emit("updatePlaylistMeta", this.plmeta);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Channel.prototype.sendMediaUpdate = function(user) {
|
Channel.prototype.sendMediaUpdate = function(user) {
|
||||||
|
@ -779,13 +773,11 @@ Channel.prototype.broadcastPlaylistMeta = function() {
|
||||||
time: timestr
|
time: timestr
|
||||||
};
|
};
|
||||||
this.plmeta = packet;
|
this.plmeta = packet;
|
||||||
this.sendAll("updatePlaylistMeta", packet);
|
this.sendAll("setPlaylistMeta", packet);
|
||||||
}
|
}
|
||||||
|
|
||||||
Channel.prototype.broadcastUsercount = function() {
|
Channel.prototype.broadcastUsercount = function() {
|
||||||
this.sendAll("usercount", {
|
this.sendAll("usercount", this.users.length);
|
||||||
count: this.users.length
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Channel.prototype.broadcastNewUser = function(user) {
|
Channel.prototype.broadcastNewUser = function(user) {
|
||||||
|
@ -928,7 +920,7 @@ Channel.prototype.broadcastMotd = function() {
|
||||||
}
|
}
|
||||||
|
|
||||||
Channel.prototype.broadcastDrinks = function() {
|
Channel.prototype.broadcastDrinks = function() {
|
||||||
this.sendAll("drinkCount", {count: this.drinks});
|
this.sendAll("drinkCount", this.drinks);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* REGION Playlist Stuff */
|
/* REGION Playlist Stuff */
|
||||||
|
@ -1134,7 +1126,7 @@ Channel.prototype.setTemp = function(idx, temp) {
|
||||||
var med = this.queue[idx];
|
var med = this.queue[idx];
|
||||||
med.temp = temp;
|
med.temp = temp;
|
||||||
this.sendAll("setTemp", {
|
this.sendAll("setTemp", {
|
||||||
idx: idx,
|
position: idx,
|
||||||
temp: temp
|
temp: temp
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1147,36 +1139,37 @@ Channel.prototype.trySetTemp = function(user, data) {
|
||||||
if(!this.hasPermission(user, "settemp")) {
|
if(!this.hasPermission(user, "settemp")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if(typeof data.idx != "number" || typeof data.temp != "boolean") {
|
if(typeof data.position != "number" || typeof data.temp != "boolean") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if(data.idx < 0 || data.idx >= this.queue.length) {
|
if(data.position < 0 || data.position >= this.queue.length) {
|
||||||
return;
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.queue.splice(data.pos, 1);
|
this.queue.splice(position, 1);
|
||||||
this.sendAll("unqueue", {
|
this.sendAll("delete", {
|
||||||
pos: data.pos
|
position: position
|
||||||
});
|
});
|
||||||
this.broadcastPlaylistMeta();
|
this.broadcastPlaylistMeta();
|
||||||
|
|
||||||
// If you remove the currently playing video, play the next one
|
// 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.position--;
|
||||||
this.playNext();
|
this.playNext();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// If you remove a video whose position is before the one currently
|
// If you remove a video whose position is before the one currently
|
||||||
// playing, you have to reduce the position of the one playing
|
// playing, you have to reduce the position of the one playing
|
||||||
if(data.pos < this.position) {
|
if(position < this.position) {
|
||||||
this.position--;
|
this.position--;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1186,9 +1179,8 @@ Channel.prototype.tryDequeue = function(user, data) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(data.pos === undefined) {
|
if(typeof data !== "number")
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
this.dequeue(data);
|
this.dequeue(data);
|
||||||
}
|
}
|
||||||
|
@ -1250,10 +1242,7 @@ Channel.prototype.jumpTo = function(pos) {
|
||||||
this.media.paused = false;
|
this.media.paused = false;
|
||||||
|
|
||||||
this.sendAll("changeMedia", this.media.fullupdate());
|
this.sendAll("changeMedia", this.media.fullupdate());
|
||||||
this.sendAll("updatePlaylistIdx", {
|
this.sendAll("setPosition", this.position);
|
||||||
old: old,
|
|
||||||
idx: this.position
|
|
||||||
});
|
|
||||||
|
|
||||||
// If it's not a livestream, enable autolead
|
// If it's not a livestream, enable autolead
|
||||||
if(this.leader == null && !isLive(this.media.type)) {
|
if(this.leader == null && !isLive(this.media.type)) {
|
||||||
|
@ -1269,11 +1258,11 @@ Channel.prototype.tryJumpTo = function(user, data) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(data.pos === undefined) {
|
if(typeof data !== "number") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.jumpTo(data.pos);
|
this.jumpTo(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
Channel.prototype.clearqueue = function() {
|
Channel.prototype.clearqueue = function() {
|
||||||
|
@ -1343,34 +1332,35 @@ Channel.prototype.tryUpdate = function(user, data) {
|
||||||
this.sendAll("mediaUpdate", this.media.timeupdate());
|
this.sendAll("mediaUpdate", this.media.timeupdate());
|
||||||
}
|
}
|
||||||
|
|
||||||
Channel.prototype.move = function(data) {
|
Channel.prototype.move = function(data, user) {
|
||||||
if(data.src < 0 || data.src >= this.queue.length) {
|
if(data.from < 0 || data.from >= this.queue.length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if(data.dest < 0 || data.dest > this.queue.length) {
|
if(data.to < 0 || data.to > this.queue.length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var media = this.queue[data.src];
|
var media = this.queue[data.from];
|
||||||
var dest = data.dest > data.src ? data.dest + 1 : data.dest;
|
var to = data.to > data.from ? data.to + 1 : data.to;
|
||||||
var src = data.dest > data.src ? data.src : data.src + 1;
|
var from = data.to > data.from ? data.from : data.from + 1;
|
||||||
|
|
||||||
this.queue.splice(dest, 0, media);
|
this.queue.splice(to, 0, media);
|
||||||
this.queue.splice(src, 1);
|
this.queue.splice(from, 1);
|
||||||
this.sendAll("moveVideo", {
|
this.sendAll("moveVideo", {
|
||||||
src: data.src,
|
from: data.from,
|
||||||
dest: data.dest
|
to: data.to,
|
||||||
|
moveby: user ? user.name : ""
|
||||||
});
|
});
|
||||||
|
|
||||||
// Account for moving things around the active video
|
// 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--;
|
this.position--;
|
||||||
}
|
}
|
||||||
else if(data.src > this.position && data.dest < this.position) {
|
else if(data.from > this.position && data.to < this.position) {
|
||||||
this.position++
|
this.position++
|
||||||
}
|
}
|
||||||
else if(data.src == this.position) {
|
else if(data.from == this.position) {
|
||||||
this.position = data.dest;
|
this.position = data.to;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1448,7 +1438,7 @@ Channel.prototype.tryVoteskip = function(user) {
|
||||||
|
|
||||||
Channel.prototype.setLock = function(locked) {
|
Channel.prototype.setLock = function(locked) {
|
||||||
this.openqueue = !locked;
|
this.openqueue = !locked;
|
||||||
this.sendAll("queueLock", {locked: locked});
|
this.sendAll("setPlaylistLocked", {locked: locked});
|
||||||
}
|
}
|
||||||
|
|
||||||
Channel.prototype.trySetLock = function(user, data) {
|
Channel.prototype.trySetLock = function(user, data) {
|
||||||
|
@ -1463,6 +1453,14 @@ Channel.prototype.trySetLock = function(user, data) {
|
||||||
this.setLock(data.locked);
|
this.setLock(data.locked);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Channel.prototype.tryToggleLock = function(user) {
|
||||||
|
if(!Rank.hasPermission(user, "qlock")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setLock(this.openqueue);
|
||||||
|
}
|
||||||
|
|
||||||
Channel.prototype.updateFilter = function(filter) {
|
Channel.prototype.updateFilter = function(filter) {
|
||||||
var found = false;
|
var found = false;
|
||||||
for(var i = 0; i < this.filters.length; i++) {
|
for(var i = 0; i < this.filters.length; i++) {
|
||||||
|
|
53
user.js
53
user.js
|
@ -206,7 +206,7 @@ User.prototype.initCallbacks = function() {
|
||||||
}
|
}
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
|
|
||||||
this.socket.on("unqueue", function(data) {
|
this.socket.on("delete", function(data) {
|
||||||
if(this.channel != null) {
|
if(this.channel != null) {
|
||||||
this.channel.tryDequeue(this, data);
|
this.channel.tryDequeue(this, data);
|
||||||
}
|
}
|
||||||
|
@ -248,9 +248,9 @@ User.prototype.initCallbacks = function() {
|
||||||
}
|
}
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
|
|
||||||
this.socket.on("queueLock", function(data) {
|
this.socket.on("togglePlaylistLock", function() {
|
||||||
if(this.channel != null) {
|
if(this.channel != null) {
|
||||||
this.channel.trySetLock(this, data);
|
this.channel.tryToggleLock(this);
|
||||||
}
|
}
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
|
|
||||||
|
@ -260,18 +260,18 @@ User.prototype.initCallbacks = function() {
|
||||||
}
|
}
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
|
|
||||||
this.socket.on("searchLibrary", function(data) {
|
this.socket.on("searchMedia", function(data) {
|
||||||
if(this.channel != null) {
|
if(this.channel != null) {
|
||||||
if(data.yt) {
|
if(data.yt) {
|
||||||
var callback = function(vids) {
|
var callback = function(vids) {
|
||||||
this.socket.emit("librarySearchResults", {
|
this.socket.emit("searchResults", {
|
||||||
results: vids
|
results: vids
|
||||||
});
|
});
|
||||||
}.bind(this);
|
}.bind(this);
|
||||||
this.channel.search(data.query, callback);
|
this.channel.search(data.query, callback);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this.socket.emit("librarySearchResults", {
|
this.socket.emit("searchResults", {
|
||||||
results: this.channel.search(data.query)
|
results: this.channel.search(data.query)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -331,12 +331,6 @@ User.prototype.initCallbacks = function() {
|
||||||
}
|
}
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
|
|
||||||
this.socket.on("adm", function(data) {
|
|
||||||
if(Rank.hasPermission(this, "acp")) {
|
|
||||||
this.handleAdm(data);
|
|
||||||
}
|
|
||||||
}.bind(this));
|
|
||||||
|
|
||||||
this.socket.on("announce", function(data) {
|
this.socket.on("announce", function(data) {
|
||||||
if(Rank.hasPermission(this, "announce")) {
|
if(Rank.hasPermission(this, "announce")) {
|
||||||
if(data.clear) {
|
if(data.clear) {
|
||||||
|
@ -500,27 +494,6 @@ User.prototype.initCallbacks = function() {
|
||||||
}.bind(this));
|
}.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 = {};
|
var lastguestlogin = {};
|
||||||
// Attempt to login
|
// Attempt to login
|
||||||
User.prototype.login = function(name, pw, session) {
|
User.prototype.login = function(name, pw, session) {
|
||||||
|
@ -568,11 +541,10 @@ User.prototype.login = function(name, pw, session) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.loggedIn = false;
|
this.loggedIn = false;
|
||||||
this.socket.emit("login", {
|
this.socket.emit("login", {
|
||||||
success: true
|
success: true,
|
||||||
});
|
name: name
|
||||||
this.socket.emit("rank", {
|
|
||||||
rank: this.rank
|
|
||||||
});
|
});
|
||||||
|
this.socket.emit("rank", this.rank);
|
||||||
if(this.channel != null) {
|
if(this.channel != null) {
|
||||||
this.channel.logger.log(this.ip + " signed in as " + name);
|
this.channel.logger.log(this.ip + " signed in as " + name);
|
||||||
this.channel.broadcastNewUser(this);
|
this.channel.broadcastNewUser(this);
|
||||||
|
@ -585,7 +557,8 @@ User.prototype.login = function(name, pw, session) {
|
||||||
this.loggedIn = true;
|
this.loggedIn = true;
|
||||||
this.socket.emit("login", {
|
this.socket.emit("login", {
|
||||||
success: true,
|
success: true,
|
||||||
session: row.session_hash
|
session: row.session_hash,
|
||||||
|
name: name
|
||||||
});
|
});
|
||||||
Logger.syslog.log(this.ip + " logged in as " + name);
|
Logger.syslog.log(this.ip + " logged in as " + name);
|
||||||
this.profile = {
|
this.profile = {
|
||||||
|
@ -597,9 +570,7 @@ User.prototype.login = function(name, pw, session) {
|
||||||
var rank = (chanrank > row.global_rank) ? chanrank
|
var rank = (chanrank > row.global_rank) ? chanrank
|
||||||
: row.global_rank;
|
: row.global_rank;
|
||||||
this.rank = (this.rank > rank) ? this.rank : rank;
|
this.rank = (this.rank > rank) ? this.rank : rank;
|
||||||
this.socket.emit("rank", {
|
this.socket.emit("rank", this.rank);
|
||||||
rank: this.rank
|
|
||||||
});
|
|
||||||
this.name = name;
|
this.name = name;
|
||||||
if(this.channel != null) {
|
if(this.channel != null) {
|
||||||
this.channel.logger.log(this.ip + " logged in as " + name);
|
this.channel.logger.log(this.ip + " logged in as " + name);
|
||||||
|
|
|
@ -116,6 +116,7 @@ html, body {
|
||||||
|
|
||||||
#plmeta {
|
#plmeta {
|
||||||
border: 1px solid #aaaaaa;
|
border: 1px solid #aaaaaa;
|
||||||
|
border-top: 0;
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
padding: 3px;
|
padding: 3px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|
|
@ -16,7 +16,7 @@ Callbacks = {
|
||||||
socket.emit("joinChannel", {
|
socket.emit("joinChannel", {
|
||||||
name: CHANNEL.name
|
name: CHANNEL.name
|
||||||
});
|
});
|
||||||
if(uname && session) {
|
if(NAME && SESSION) {
|
||||||
socket.emit("login", {
|
socket.emit("login", {
|
||||||
name: NAME,
|
name: NAME,
|
||||||
session: SESSION
|
session: SESSION
|
||||||
|
@ -231,7 +231,8 @@ Callbacks = {
|
||||||
setPermissions: function(perms) {
|
setPermissions: function(perms) {
|
||||||
CHANNEL.perms = perms;
|
CHANNEL.perms = perms;
|
||||||
if(CLIENT.rank >= Rank.Admin)
|
if(CLIENT.rank >= Rank.Admin)
|
||||||
genPermissionsEditor();
|
1;
|
||||||
|
//genPermissionsEditor();
|
||||||
handlePermissionChange();
|
handlePermissionChange();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -464,9 +465,9 @@ Callbacks = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
drinkCount: function(data) {
|
drinkCount: function(count) {
|
||||||
if(data.count != 0) {
|
if(count != 0) {
|
||||||
var text = data.count + " drink";
|
var text = count + " drink";
|
||||||
if(data.count != 1) {
|
if(data.count != 1) {
|
||||||
text += "s";
|
text += "s";
|
||||||
}
|
}
|
||||||
|
@ -484,7 +485,6 @@ Callbacks = {
|
||||||
var q = $("#queue");
|
var q = $("#queue");
|
||||||
q.html("");
|
q.html("");
|
||||||
|
|
||||||
|
|
||||||
for(var i = 0; i < data.length; i++) {
|
for(var i = 0; i < data.length; i++) {
|
||||||
Callbacks.queue({
|
Callbacks.queue({
|
||||||
media: data[i],
|
media: data[i],
|
||||||
|
@ -550,7 +550,8 @@ Callbacks = {
|
||||||
},
|
},
|
||||||
|
|
||||||
moveVideo: function(data) {
|
moveVideo: function(data) {
|
||||||
playlistMove(data.src, data.dest);
|
if(data.moveby != CLIENT.name)
|
||||||
|
playlistMove(data.src, data.dest);
|
||||||
},
|
},
|
||||||
|
|
||||||
setPosition: function(data) {
|
setPosition: function(data) {
|
||||||
|
@ -598,22 +599,7 @@ Callbacks = {
|
||||||
|
|
||||||
setPlaylistLocked: function(data) {
|
setPlaylistLocked: function(data) {
|
||||||
CHANNEL.openqueue = !data.locked;
|
CHANNEL.openqueue = !data.locked;
|
||||||
// TODO handlePermissionsChange?
|
handlePermissionChange();
|
||||||
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();
|
|
||||||
}
|
|
||||||
if(CHANNEL.openqueue) {
|
if(CHANNEL.openqueue) {
|
||||||
$("#qlockbtn").removeClass("btn-danger")
|
$("#qlockbtn").removeClass("btn-danger")
|
||||||
.addClass("btn-success")
|
.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() {
|
$.getScript(IO_URL+"/socket.io/socket.io.js", function() {
|
||||||
try {
|
try {
|
||||||
socket = io.connect(IO_URL);
|
socket = io.connect(IO_URL);
|
||||||
for(var key in Callbacks) {
|
setupCallbacks();
|
||||||
socket.on(key, Callbacks[key]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch(e) {
|
catch(e) {
|
||||||
Callbacks.disconnect();
|
Callbacks.disconnect();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
*/
|
|
||||||
|
|
||||||
setupCallbacks = function() {
|
setupCallbacks = function() {
|
||||||
for(var key in Callbacks) {
|
for(var key in Callbacks) {
|
||||||
(function(key) {
|
(function(key) {
|
||||||
socket.on(key, function() {
|
socket.on(key, function(data) {
|
||||||
Callbacks[key]();
|
Callbacks[key](data);
|
||||||
});
|
});
|
||||||
})(key);
|
})(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@ var CHANNEL = {
|
||||||
};
|
};
|
||||||
|
|
||||||
var PLAYER = false;
|
var PLAYER = false;
|
||||||
|
var FLUIDLAYOUT = false;
|
||||||
var VWIDTH = $("#ytapiplayer").parent().css("width").replace("px", "");
|
var VWIDTH = $("#ytapiplayer").parent().css("width").replace("px", "");
|
||||||
var VHEIGHT = ""+parseInt(parseInt(VWIDTH) * 9 / 16);
|
var VHEIGHT = ""+parseInt(parseInt(VWIDTH) * 9 / 16);
|
||||||
var POSITION = -1;
|
var POSITION = -1;
|
||||||
|
@ -39,6 +40,8 @@ var KICKED = false;
|
||||||
var NAME = readCookie("cytube_uname");
|
var NAME = readCookie("cytube_uname");
|
||||||
var SESSION = readCookie("cytube_session");
|
var SESSION = readCookie("cytube_session");
|
||||||
var LEADTMR = false;
|
var LEADTMR = false;
|
||||||
|
var PL_FROM = 0;
|
||||||
|
var PL_TO = 0;
|
||||||
|
|
||||||
function getOrDefault(k, def) {
|
function getOrDefault(k, def) {
|
||||||
var v = localStorage.getItem(k);
|
var v = localStorage.getItem(k);
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
|
@ -158,7 +158,20 @@ $("#userpl_save").click(function() {
|
||||||
/* playlist controls */
|
/* playlist controls */
|
||||||
|
|
||||||
$(function() {
|
$(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();
|
$("#queue").disableSelection();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -245,3 +258,59 @@ $("#shuffleplaylist").click(function() {
|
||||||
socket.emit("shufflePlaylist");
|
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", "");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
@ -18,8 +18,37 @@ function makeAlert(title, text, klass) {
|
||||||
return al;
|
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) {
|
function formatUserlistItem(div, data) {
|
||||||
var name = $(div.children[1]);
|
var name = $(div.children()[1]);
|
||||||
name.removeClass();
|
name.removeClass();
|
||||||
name.css("font-style", "");
|
name.css("font-style", "");
|
||||||
name.addClass(getNameColor(data.rank));
|
name.addClass(getNameColor(data.rank));
|
||||||
|
@ -49,7 +78,7 @@ function formatUserlistItem(div, data) {
|
||||||
profile.remove();
|
profile.remove();
|
||||||
});
|
});
|
||||||
|
|
||||||
var flair = div.children[0];
|
var flair = div.children()[0];
|
||||||
flair.innerHTML = "";
|
flair.innerHTML = "";
|
||||||
// denote current leader with a star
|
// denote current leader with a star
|
||||||
if(data.leader) {
|
if(data.leader) {
|
||||||
|
@ -205,7 +234,7 @@ function makeQueueEntry(video) {
|
||||||
}
|
}
|
||||||
var title = $("<a/>").addClass("qe_title").appendTo(li)
|
var title = $("<a/>").addClass("qe_title").appendTo(li)
|
||||||
.text(video.title)
|
.text(video.title)
|
||||||
.attr("href", "#")//formatURL(video))
|
.attr("href", formatURL(video))
|
||||||
.attr("target", "_blank");
|
.attr("target", "_blank");
|
||||||
var time = $("<span/>").addClass("qe_time").appendTo(li);
|
var time = $("<span/>").addClass("qe_time").appendTo(li);
|
||||||
time.text(video.duration);
|
time.text(video.duration);
|
||||||
|
@ -214,47 +243,61 @@ function makeQueueEntry(video) {
|
||||||
li.addClass("queue_temp");
|
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);
|
var menu = $("<div/>").addClass("btn-group").appendTo(li);
|
||||||
// Play
|
// Play
|
||||||
$("<button/>").addClass("btn btn-mini qbtn-play")
|
if(hasPermission("playlistjump")) {
|
||||||
.html("<i class='icon-play'></i>Play")
|
$("<button/>").addClass("btn btn-mini qbtn-play")
|
||||||
.click(function() {
|
.html("<i class='icon-play'></i>Play")
|
||||||
var i = $("#queue").children().index(li);
|
.click(function() {
|
||||||
socket.emit("jumpTo", i);
|
var i = $("#queue").children().index(li);
|
||||||
})
|
socket.emit("jumpTo", i);
|
||||||
.appendTo(menu);
|
})
|
||||||
|
.appendTo(menu);
|
||||||
|
}
|
||||||
// Queue next
|
// Queue next
|
||||||
$("<button/>").addClass("btn btn-mini qbtn-next")
|
if(hasPermission("playlistnext")) {
|
||||||
.html("<i class='icon-share-alt'></i>Queue Next")
|
$("<button/>").addClass("btn btn-mini qbtn-next")
|
||||||
.click(function() {
|
.html("<i class='icon-share-alt'></i>Queue Next")
|
||||||
var i = $("#queue").children().index(li);
|
.click(function() {
|
||||||
socket.emit("moveMedia", {
|
var i = $("#queue").children().index(li);
|
||||||
src: i,
|
socket.emit("moveMedia", {
|
||||||
dest: i < POSITION ? POSITION : POSITION + 1
|
src: i,
|
||||||
});
|
dest: i < POSITION ? POSITION : POSITION + 1
|
||||||
})
|
});
|
||||||
.appendTo(menu);
|
})
|
||||||
|
.appendTo(menu);
|
||||||
|
}
|
||||||
// Temp/Untemp
|
// Temp/Untemp
|
||||||
$("<button/>").addClass("btn btn-mini qbtn-tmp")
|
if(hasPermission("settemp")) {
|
||||||
.html("<i class='icon-flag'></i>Make Temporary")
|
$("<button/>").addClass("btn btn-mini qbtn-tmp")
|
||||||
.click(function() {
|
.html("<i class='icon-flag'></i>Make Temporary")
|
||||||
var i = $("#queue").children().index(li);
|
.click(function() {
|
||||||
var temp = li.find(".qbtn-tmp").data("temp");
|
var i = $("#queue").children().index(li);
|
||||||
socket.emit("setTemp", {
|
var temp = li.find(".qbtn-tmp").data("temp");
|
||||||
position: i,
|
socket.emit("setTemp", {
|
||||||
temp: !temp
|
position: i,
|
||||||
});
|
temp: !temp
|
||||||
})
|
});
|
||||||
.appendTo(menu);
|
})
|
||||||
|
.appendTo(menu);
|
||||||
|
}
|
||||||
// Delete
|
// Delete
|
||||||
$("<button/>").addClass("btn btn-mini qbtn-delete")
|
if(hasPermission("playlistdelete")) {
|
||||||
.html("<i class='icon-trash'></i>Delete")
|
$("<button/>").addClass("btn btn-mini qbtn-delete")
|
||||||
.click(function() {
|
.html("<i class='icon-trash'></i>Delete")
|
||||||
var i = $("#queue").children().index(li);
|
.click(function() {
|
||||||
socket.emit("delete", i);
|
var i = $("#queue").children().index(li);
|
||||||
})
|
socket.emit("delete", i);
|
||||||
.appendTo(menu);
|
})
|
||||||
|
.appendTo(menu);
|
||||||
|
}
|
||||||
|
|
||||||
menu.hide();
|
menu.hide();
|
||||||
|
|
||||||
|
@ -266,21 +309,12 @@ function makeQueueEntry(video) {
|
||||||
menu.hide("blind");
|
menu.hide("blind");
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
menu.blur(function() {
|
|
||||||
menu.hide();
|
|
||||||
});
|
|
||||||
return li;
|
|
||||||
}
|
|
||||||
|
|
||||||
function addQueueButtons(li) {
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function rebuildPlaylist() {
|
function rebuildPlaylist() {
|
||||||
$("#queue li").each(function() {
|
$("#queue li").each(function() {
|
||||||
$(this).find(".btn-group").remove();
|
$(this).find(".btn-group").remove();
|
||||||
addQueueButtons(this);
|
addQueueButtons($(this));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -733,7 +767,7 @@ function loadSearchPage(page) {
|
||||||
var li = makeQueueEntry(results[i]);
|
var li = makeQueueEntry(results[i]);
|
||||||
if(hasPermission("playlistadd")) {
|
if(hasPermission("playlistadd")) {
|
||||||
if(results[i].thumb) {
|
if(results[i].thumb) {
|
||||||
addLibraryButtons(li, results[i].id, true);
|
addLibraryButtons(li, results[i].id, "yt");
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
addLibraryButtons(li, results[i].id);
|
addLibraryButtons(li, results[i].id);
|
||||||
|
@ -748,3 +782,206 @@ function loadSearchPage(page) {
|
||||||
$($("#search_pagination").find("li")[page]).addClass("active");
|
$($("#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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
438
www/channel.html
438
www/channel.html
|
@ -4,27 +4,26 @@
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>CyTube</title>
|
<title>CyTube</title>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<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">
|
<meta name="author" content="Calvin 'calzoneman' Montgomery">
|
||||||
|
|
||||||
<link href="./assets/css/bootstrap.css" rel="stylesheet">
|
<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="./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>
|
<style>
|
||||||
body {
|
body {
|
||||||
padding-top: 60px; /* 60px to make the container go all the way to the bottom of the topbar */
|
padding-top: 60px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<link href="./assets/css/bootstrap-responsive.css" rel="stylesheet">
|
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
<div class="navbar navbar-inverse navbar-fixed-top">
|
<!-- begin navbar -->
|
||||||
<div class="navbar-inner">
|
<div class="navbar navbar-inverse navbar-fixed-top">
|
||||||
<div class="container">
|
<div class="navbar-inner">
|
||||||
<a class="brand" href="index.html">CyTube</a>
|
<div class="container">
|
||||||
<div class="">
|
<a class="brand" href="index.html">CyTube</a>
|
||||||
<ul class="nav">
|
<ul class="nav">
|
||||||
<li class="active"><a href="index.html">Home</a></li>
|
<li class="active"><a href="index.html">Home</a></li>
|
||||||
<li><a href="help.html">Help</a></li>
|
<li><a href="help.html">Help</a></li>
|
||||||
|
@ -32,306 +31,173 @@
|
||||||
<li><a href="javascript:void(0)" id="optlink">Options</a></li>
|
<li><a href="javascript:void(0)" id="optlink">Options</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
<div class="navbar-form pull-right" id="loginform">
|
<div class="navbar-form pull-right" id="loginform">
|
||||||
<input type="text" id="guestname" placeholder="Name">
|
<input type="text" id="guestname" placeholder="Name">
|
||||||
<button class="btn" id="guestlogin">Guest Login</button>
|
<button class="btn" id="guestlogin">Guest Login</button>
|
||||||
<button class="btn" id="login">Login/Register</button>
|
<button class="btn" id="login">Login/Register</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="navbar-form pull-right" id="logoutform" style="display: none;">
|
<div class="navbar-form pull-right" id="logoutform" style="display: none;">
|
||||||
<span id="welcome"></span>
|
<span id="welcome"></span>
|
||||||
<button class="btn" id="logout">Logout</button>
|
<button class="btn" id="logout">Logout</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<!-- end navbar -->
|
||||||
|
<!-- begin main page -->
|
||||||
<div class="container">
|
<div class="container" id="mainpage">
|
||||||
<div class="row-fluid" id="motdrow">
|
<!-- top row (MOTD, drink count) -->
|
||||||
<div class="well span12">
|
<div class="row-fluid" id="toprow">
|
||||||
<p id="motd"></p>
|
<div class="well span12" id="motd">
|
||||||
</div>
|
</div>
|
||||||
<div class="span12" id="drinkbar">
|
<div class="span12" id="drinkbar">
|
||||||
<h1 id="drinkcount"></h1>
|
<h1 id="drinkcount"></h1>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row-fluid" id="announcerow" style="display: none">
|
<!-- announcement area -->
|
||||||
|
<div class="row-fluid" id="announcements">
|
||||||
</div>
|
</div>
|
||||||
<div class="row" id="main" style="margin-top: 20px;">
|
<!-- main row -->
|
||||||
<div class="span5" id="chatdiv">
|
<div class="row" id="main">
|
||||||
<div id="usercountcontainer">
|
<!-- chat container -->
|
||||||
<i class="icon-chevron-up" id="ulistchevron" title="Show/Hide Userlist"></i>
|
<div class="span5" id="chatwrap">
|
||||||
<p id="usercount"></p>
|
<!-- user count -->
|
||||||
</div>
|
<div id="usercountwrap" class="pointer">
|
||||||
<div id="userlist">
|
<i class="icon-chevron-up pull-left" id="userlisttoggle" title="Show/Hide Userlist"></i>
|
||||||
</div>
|
<p id="usercount">Not connected</p>
|
||||||
<div id="messagebuffer">
|
|
||||||
</div>
|
|
||||||
<input type="text" id="chatline" maxlength="240" class="span5">
|
|
||||||
</div>
|
</div>
|
||||||
<div class="span7" id="videodiv">
|
<!-- userlist -->
|
||||||
<p id="currenttitle">Currently Playing: </p>
|
<div id="userlist">
|
||||||
<div id="ytapiplayer">
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<!-- message buffer -->
|
||||||
|
<div id="messagebuffer">
|
||||||
|
</div>
|
||||||
|
<!-- chat input -->
|
||||||
|
<input type="text" id="chatline" maxlength="240" class="span5">
|
||||||
|
</div>
|
||||||
|
<!-- 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>
|
||||||
<div class="row" id="queuerow">
|
<!-- playlist row -->
|
||||||
<div class="span5">
|
<div class="row" id="playlistrow">
|
||||||
<div class="row-fluid">
|
<!-- left pane - Library + user playlists -->
|
||||||
<div class="span12" id="pollcontainer">
|
<div class="span5" id="leftpane-outer">
|
||||||
</div>
|
<div class="row-fluid" id="leftpane-inner">
|
||||||
<div class="span12" id="leftpanetabs" style="margin-left: 0">
|
<!-- poll container -->
|
||||||
<ul class="nav nav-pills">
|
<div class="span12" id="pollwrap">
|
||||||
<li class="active"><a href="javascript:void(0)" id="show_library">Library/YouTube Search</a></li>
|
<!-- new poll controls -->
|
||||||
<li><a href="javascript:void(0)" id="show_userpl">User Playlists</a></li>
|
<button class="btn btn-primary btn-small" id="newpollbtn">New Poll</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="userplcontainer" style="display: none">
|
<!-- library search -->
|
||||||
<div class="span7" style="margin-left: 0;">
|
<div class="span12 pointer" id="librarytoggle">
|
||||||
<input type="text" id="userpl_name" class="input-block-level" placeholder="Playlist Name">
|
<i class="icon-chevron-down pull-left"></i>
|
||||||
</div>
|
<p>Show Library</p>
|
||||||
<div class="span5">
|
</div>
|
||||||
<button class="btn btn-block" id="userpl_save">Save Playlist</button>
|
<div id="librarywrap">
|
||||||
</div>
|
<div class="span7" id="querywrap">
|
||||||
<ul class="span12" id="userpl_list" style="margin-left: 0">
|
<input type="text" id="library_query" class="input-block-level" placeholder="Search Query">
|
||||||
</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>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="span5 btn-group" id="searchbtns">
|
||||||
<div class="span7" id="queuediv">
|
<button class="btn" id="library_search">Library</button>
|
||||||
<div class="row-fluid" id="qclear">
|
<button class="btn" id="youtube_search">YouTube</button>
|
||||||
<div class="span12"></div>
|
|
||||||
</div>
|
</div>
|
||||||
<div id="playlist_controls"style="display: none;">
|
</div>
|
||||||
<div class="row-fluid">
|
<!-- user playlists -->
|
||||||
<div class="span8">
|
<div class="span12 pointer" id="userpltoggle">
|
||||||
<input type="text" id="mediaurl" class="input-block-level" placeholder="Media URL">
|
<i class="icon-chevron-down pull-left"></i>
|
||||||
</div>
|
<p>Show Playlist Manager</p>
|
||||||
<div class="span4 btn-group">
|
</div>
|
||||||
<button class="btn" id="queue_next">Next</button>
|
<div id="userplaylistwrap">
|
||||||
<button class="btn" id="queue_end">End</button>
|
<div class="span7">
|
||||||
</div>
|
<input type="text" id="userpl_name" class="input-block-level" placeholder="Playlist Name">
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<button class="btn btn-block" id="voteskip">Voteskip</button>
|
<div class="span5">
|
||||||
<div class="row-fluid">
|
<button class="btn btn-block" id="userpl_save">Save Playlist</button>
|
||||||
<ul id="queue" class="span12 videolist">
|
|
||||||
</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>
|
</div>
|
||||||
</div>
|
<ul class="span12" id="userpl_list">
|
||||||
</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>
|
</ul>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row modonly" style="display: none" id="chancontrols">
|
<!-- right pane - channel playlist -->
|
||||||
<div class="span12">
|
<div class="span7" id="rightpane-outer">
|
||||||
<form action="javascript:void(0)">
|
<div class="row-fluid" id="rightpane-inner">
|
||||||
<fieldset>
|
<!-- account for left pane having the poll buffer -->
|
||||||
<div class="span5">
|
<div class="span12" id="queue_align"></div>
|
||||||
<label>Page Title
|
<!-- playlist controls -->
|
||||||
<input type="text" id="opt_pagetitle" placeholder="CyTube" class="pull-right">
|
<div class="span12 pointer" id="playlisttoggle">
|
||||||
</label>
|
<i class="icon-chevron-down pull-left"></i>
|
||||||
<br>
|
<p>Show Playlist Controls</p>
|
||||||
<label>External CSS
|
</div>
|
||||||
<input type="text" id="opt_customcss" class="pull-right" placeholder="CSS URL">
|
<div id="playlist_controls">
|
||||||
</label>
|
<div class="span8">
|
||||||
<br>
|
<input type="text" id="mediaurl" class="input-block-level" placeholder="Media URL">
|
||||||
<label>External JS
|
</div>
|
||||||
<input type="text" id="opt_customjs" class="pull-right" placeholder="JS URL">
|
<div class="span4 btn-group">
|
||||||
</label>
|
<button class="btn" id="queue_next">Next</button>
|
||||||
<br>
|
<button class="btn" id="queue_end">End</button>
|
||||||
<label class="checkbox">
|
</div>
|
||||||
<input type="checkbox" id="opt_allow_voteskip">
|
<div id="extended_controls" class="span12">
|
||||||
Allow voteskip
|
<button class="btn btn-danger btn-block" id="qlockbtn">Unlock Queue</button>
|
||||||
</label>
|
<button class="btn btn-block" id="getplaylist">Get Playlist URLs</button>
|
||||||
<br>
|
<button class="btn btn-block" id="clearplaylist">Clear Playlist</button>
|
||||||
<label>Voteskip Ratio
|
<button class="btn btn-block" id="shuffleplaylist">Shuffle Playlist</button>
|
||||||
<input type="text" id="opt_voteskip_ratio" class="pull-right">
|
</div>
|
||||||
</label>
|
</div>
|
||||||
<br>
|
<!-- video queue -->
|
||||||
<label class="checkbox">
|
<ul class="span12 videolist" id="queue">
|
||||||
<input type="checkbox" id="opt_chat_antiflood">
|
</ul>
|
||||||
Prevent chat flood
|
<div class="span12" id="plmeta">
|
||||||
</label>
|
<span id="plcount">0 items</span>
|
||||||
<br>
|
<span id="pllength">00:00:00</span>
|
||||||
<label class="checkbox">
|
</div>
|
||||||
<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>
|
||||||
</div>
|
</div>
|
||||||
<div class="row modonly" id="chanperms" style="display: none;">
|
</div>
|
||||||
</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 class="push"></div>
|
||||||
<div id="sitefooter">
|
<div id="sitefooter">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="footer">
|
<div id="footer">
|
||||||
<p class="muted">
|
<p class="muted">
|
||||||
CyTube Software Copyright © 2013 Calvin Montgomery · Available for free on <a href="http://github.com/calzoneman/sync">GitHub</a> ·
|
CyTube Software Copyright © 2013 Calvin Montgomery · Available for free on <a href="http://github.com/calzoneman/sync">GitHub</a> ·
|
||||||
<a href="https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=5Y7PUVVGVSEWG&lc=US&item_name=CyTube¤cy_code=USD&bn=PP%2dDonationsBF%3abtn_donate_SM%2egif%3aNonHosted">Donate</a>
|
<a href="https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=5Y7PUVVGVSEWG&lc=US&item_name=CyTube¤cy_code=USD&bn=PP%2dDonationsBF%3abtn_donate_SM%2egif%3aNonHosted">Donate</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="./assets/js/jquery.js"></script>
|
<script src="./assets/js/jquery.js"></script>
|
||||||
<!-- My Javascript -->
|
<script src="http://code.jquery.com/ui/1.10.3/jquery-ui.js"></script>
|
||||||
<script src="./assets/js/iourl.js"></script>
|
<!-- My Javascript -->
|
||||||
<script src="./assets/js/notwebsocket.js"></script>
|
<script src="./assets/js/data.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/player.js"></script>
|
||||||
<script src="./assets/js/client.js"></script>
|
<script src="./assets/js/util.js"></script>
|
||||||
<script src="./assets/js/callbacks.js"></script>
|
<script src="./assets/js/ui.js"></script>
|
||||||
<!-- APIs -->
|
<script src="./assets/js/callbacks.js"></script>
|
||||||
<script src="http://api.dmcdn.net/all.js"></script>
|
<script src="./assets/js/notwebsocket.js"></script>
|
||||||
<script src="http://jwpsrv.com/library/QouFCLBMEeKC+CIACpYGxA.js"></script>
|
<!--
|
||||||
<script src="./assets/js/sc.js"></script>
|
<script src="./assets/js/iourl.js"></script>
|
||||||
<script src="./assets/js/froogaloop.min.js"></script>
|
<script src="./assets/js/functions.js"></script>
|
||||||
<script src="./assets/js/swf.js"></script>
|
<script src="./assets/js/client.js"></script>
|
||||||
<!-- Third party -->
|
<script src="./assets/js/callbacks.js"></script>
|
||||||
<script src="./assets/js/bootstrap.js"></script>
|
-->
|
||||||
<script src="./assets/js/bootstrap-transition.js"></script>
|
<!-- APIs -->
|
||||||
<script src="./assets/js/bootstrap-modal.js"></script>
|
<script src="http://api.dmcdn.net/all.js"></script>
|
||||||
|
<script src="http://jwpsrv.com/library/QouFCLBMEeKC+CIACpYGxA.js"></script>
|
||||||
|
<script src="./assets/js/sc.js"></script>
|
||||||
|
<script src="./assets/js/froogaloop.min.js"></script>
|
||||||
|
<script src="./assets/js/swf.js"></script>
|
||||||
|
<!-- Third party -->
|
||||||
|
<script src="./assets/js/bootstrap.js"></script>
|
||||||
|
<script src="./assets/js/bootstrap-transition.js"></script>
|
||||||
|
<script src="./assets/js/bootstrap-modal.js"></script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
Loading…
Reference in New Issue