diff --git a/channel.js b/channel.js index e504d907..d42f0aa9 100644 --- a/channel.js +++ b/channel.js @@ -167,7 +167,7 @@ Channel.prototype.loadDump = function() { } this.playlist.append(p); } - this.sendAll("playlist", this.playlist.toArray()); + this.sendAll("playlist", this.playlist.items.toArray()); if(this.playlist.current) this.sendAll("setCurrent", this.playlist.current.uid); this.broadcastPlaylistMeta(); @@ -175,7 +175,7 @@ Channel.prototype.loadDump = function() { else if(data.playlist) { var chan = this; this.playlist.load(data.playlist, function() { - chan.sendAll("playlist", chan.playlist.toArray()); + chan.sendAll("playlist", chan.playlist.items.toArray()); if(chan.playlist.current) chan.sendAll("setCurrent", chan.playlist.current.uid); chan.broadcastPlaylistMeta(); @@ -727,7 +727,7 @@ Channel.prototype.sendChannelRanks = function(user) { } Channel.prototype.sendPlaylist = function(user) { - user.socket.emit("playlist", this.playlist.toArray()); + user.socket.emit("playlist", this.playlist.items.toArray()); if(this.playlist.current) user.socket.emit("setCurrent", this.playlist.current.uid); user.socket.emit("setPlaylistMeta", this.plmeta); @@ -780,14 +780,14 @@ Channel.prototype.sendAllWithPermission = function(perm, msg, data) { Channel.prototype.broadcastPlaylistMeta = function() { var total = 0; - var iter = this.playlist.first; - while(iter != null) { + var iter = this.playlist.items.first; + while(iter !== null) { total += iter.media.seconds; iter = iter.next; } var timestr = formatTime(total); var packet = { - count: this.playlist.length, + count: this.playlist.items.length, time: timestr }; this.plmeta = packet; @@ -1182,7 +1182,44 @@ Channel.prototype.tryQueue = function(user, data) { if(data.list) this.enqueueList(data, user); else - this.enqueue(data, user); + this.addMedia(data, user); +} + +Channel.prototype.addMedia = function(data, user, callback) { + // TODO fix caching + if(data.id in this.library) { + data.type = this.library[data.id].type; + } + if(isLive(data.type) && !this.hasPermission(user, "playlistaddlive")) { + user.socket.emit("queueFail", "You don't have permission to queue livestreams"); + return; + } + + data.temp = isLive(data.type) || !this.hasPermission(user, "addnontemp"); + data.queueby = user ? user.name : ""; + + var chan = this; + this.playlist.addMedia(data, function(err, item) { + if(err) { + if(callback) + callback(false); + if(err === true) + err = false; + if(user) + user.socket.emit("queueFail", err); + return; + } + else { + chan.sendAll("queue", { + item: item.pack(), + after: item.prev ? item.prev.uid : "prepend" + }); + chan.broadcastPlaylistMeta(); + chan.cacheMedia(item.media); + if(callback) + callback(true); + } + }); } Channel.prototype.enqueueList = function(data, user) { @@ -1233,7 +1270,7 @@ Channel.prototype.tryQueuePlaylist = function(user, data) { } Channel.prototype.setTemp = function(uid, temp) { - var item = this.playlist.find(uid); + var item = this.playlist.items.find(uid); if(!item) return; item.temp = temp; @@ -1262,9 +1299,12 @@ Channel.prototype.trySetTemp = function(user, data) { Channel.prototype.dequeue = function(uid) { var chan = this; function afterDelete() { + chan.sendAll("delete", { + uid: uid + }); chan.broadcastPlaylistMeta(); } - if(!this.playlist.remove(uid, true, afterDelete)) + if(!this.playlist.remove(uid, afterDelete)) return; } @@ -1321,7 +1361,7 @@ Channel.prototype.tryJumpTo = function(user, data) { Channel.prototype.clearqueue = function() { this.playlist.clear(); - this.sendAll("playlist", this.playlist.toArray()); + this.sendAll("playlist", this.playlist.items.toArray()); this.broadcastPlaylistMeta(); } @@ -1334,7 +1374,7 @@ Channel.prototype.tryClearqueue = function(user) { Channel.prototype.shufflequeue = function() { var n = []; - var pl = this.playlist.toArray(); + var pl = this.playlist.items.toArray(); var current = pl.current; while(pl.length > 0) { var i = parseInt(Math.random() * pl.length); @@ -1344,7 +1384,7 @@ Channel.prototype.shufflequeue = function() { // TODO fix this.playlist.current = this.playlist.last; this.playNext(); - this.sendAll("playlist", this.playlist.toArray()); + this.sendAll("playlist", this.playlist.items.toArray()); this.sendAll("setPlaylistMeta", this.plmeta); } diff --git a/get-info.js b/get-info.js index bdbdb908..06a1b27b 100644 --- a/get-info.js +++ b/get-info.js @@ -17,7 +17,7 @@ var Media = require("./media.js").Media; // Helper function for making an HTTP request and getting the result // as JSON function getJSON(options, callback) { - var req = http.request(options, function(res){ + var req = http.request(options, function(res) { var buffer = ""; res.setEncoding("utf8"); res.on("data", function (chunk) { @@ -47,7 +47,7 @@ function getJSON(options, callback) { // Dailymotion uses HTTPS for anonymous requests... [](/picard) function getJSONHTTPS(options, callback) { - var req = https.request(options, function(res){ + var req = https.request(options, function(res) { var buffer = ""; res.setEncoding("utf8"); res.on("data", function (chunk) { @@ -87,6 +87,7 @@ exports.getYTInfo = function(id, callback) { timeout: 1000}, callback); } +// Look up a YouTube playlist exports.getYTPlaylist = function(id, callback, url) { var path = "/feeds/api/playlists/" + id + "?v=2&alt=json"; if(url) { @@ -101,6 +102,7 @@ exports.getYTPlaylist = function(id, callback, url) { timeout: 1000}, callback); } +// Search YouTube exports.searchYT = function(terms, callback) { // I really miss Python's list comprehensions for(var i = 0; i < terms.length; i++) { @@ -157,13 +159,8 @@ exports.getYTSearchResults = function(query, callback) { } // Look up Soundcloud metadata -// Whoever designed this should rethink it. I'll submit a feedback -// form on their website. exports.getSCInfo = function(url, callback) { const SC_CLIENT = "2e0c82ab5a020f3a7509318146128abd"; - // SoundCloud is dumb - // I have to request the API URL for the given input URL - // Because the sound ID isn"t in the URL getJSON({ host: "api.soundcloud.com", port: 80, @@ -360,6 +357,30 @@ exports.getMedia = function(id, type, callback) { } } exports.getYTPlaylist(id, cback); + case "li": + case "tw": + case "jt": + case "us": + case "jw": + const prefix = { + "li": "Livestream.com - ", + "tw": "Twitch.tv - ", + "jt": "Justin.tv - ", + "us": "Ustream.tv - ", + "jw": "JWPlayer Stream - " + }; + var media = new Media(data.id, prefix[data.type] + data.id, "--:--", data.type); + callback(false, media); + break; + case "rt": + case "im": + const names = { + "rt": "Livestream", + "im": "Imgur Album" + }; + var media = new Media(data.id, names[data.type], "--:--", data.type); + callback(false, media); + break; default: break; } diff --git a/playlist.js b/playlist.js index 64aad4dd..d13d8f01 100644 --- a/playlist.js +++ b/playlist.js @@ -1,4 +1,6 @@ +var ULList = require("./ullist").ULList; var Media = require("./media").Media; +var InfoGetter = require("./get-info"); function PlaylistItem(media, uid) { this.media = media; @@ -19,11 +21,8 @@ PlaylistItem.prototype.pack = function() { } function Playlist(chan) { + this.items = new ULList(); this.next_uid = 0; - this.first = null; - this.last = null; - this.current = null; - this.length = 0; this._leadInterval = false; this._lastUpdate = 0; this._counter = 0; @@ -33,6 +32,9 @@ function Playlist(chan) { "mediaUpdate": [], "remove": [], }; + this.lock = false; + this.alter_queue = []; + this._qaInterval = false; if(chan) { var pl = this; @@ -51,18 +53,27 @@ function Playlist(chan) { } } +Playlist.prototype.queueAction = function(data) { + this.alter_queue.push(data); + if(this._qaInterval) + return; + var pl = this; + this._qaInterval = setInterval(function() { + if(!pl.lock) { + var data = pl.alter_queue.shift(); + pl[data.fn].apply(pl, data.args); + if(pl.alter_queue.length == 0) { + clearInterval(pl._qaInterval); + } + } + }, 100); +} + Playlist.prototype.dump = function() { - var arr = []; - var item = this.first; - var i = 0; - var pos = 0; - while(item != null) { - arr.push(item.pack()); - if(item == this.current) - pos = i; - i++; - item = item.next; - } + var arr = this.items.toArray(); + var pos = arr.indexOf(this.current); + if(pos < 0) + pos = 0; var time = 0; if(this.current) @@ -83,7 +94,7 @@ Playlist.prototype.load = function(data, callback) { var it = this.makeItem(m); it.temp = data.pl[i].temp; it.queueby = data.pl[i].queueby; - this._append(it); + this.items.append(it); if(i == parseInt(data.pos)) { this.current = it; this.startPlayback(data.time); @@ -112,136 +123,81 @@ Playlist.prototype.makeItem = function(media) { return new PlaylistItem(media, this.next_uid++); } -Playlist.prototype.find = function(uid) { - if(this.first === null) - return false; - var item = this.first; - var iter = this.first; - while(iter != null && item.uid != uid) { - item = iter; - iter = iter.next; - } - - if(item && item.uid == uid) - return item; +Playlist.prototype.add = function(item, pos) { + if(pos == "append") + return this.items.append(item); + else if(pos == "prepend") + return this.items.prepend(item); else - return false; + return this.items.insertAfter(item, pos); } -Playlist.prototype.prepend = function(plitem, callback) { - this._prepend(plitem, callback); - if(this.length == 1) - this.startPlayback(); -} - -Playlist.prototype._prepend = function(plitem, callback) { - if(this.first !== null) { - plitem.next = this.first; - this.first.prev = plitem; +Playlist.prototype.addMedia = function(data, callback) { + var pos = "append"; + if(data.pos == "next") { + if(!this.current) + pos = "prepend"; + else + pos = this.current.uid; } - // prepending to empty list - else { - this.current = plitem; - this.last = plitem; - } - this.first = plitem; - this.first.prev = null; - this.length++; - if(callback) - callback(); - return true; -} -Playlist.prototype.append = function(plitem, callback) { - this._append(plitem, callback); - if(this.length == 1) - this.startPlayback(); -} - -Playlist.prototype._append = function(plitem, callback) { - if(this.last != null) { - plitem.prev = this.last; - this.last.next = plitem; - } - // appending to empty list - else { - this.first = plitem; - this.current = plitem; - } - this.last = plitem; - this.last.next = null; - this.length++; - if(callback) - callback(); - return true; -} - -Playlist.prototype.insertAfter = function(plitem, uid, callback) { - this._insertAfter(plitem, uid, callback); -} - -Playlist.prototype._insertAfter = function(plitem, uid, callback) { - var item = this.find(uid); - - if(item) { - plitem.next = item.next; - plitem.prev = item; - item.next = plitem; - if(item == this.last) { - this.last = plitem; + var pl = this; + InfoGetter.getMedia(data.id, data.type, function(err, media) { + if(err) { + callback(err, null); + return; } - this.length++; + + var it = pl.makeItem(media); + it.temp = data.temp; + it.queueby = data.queueby; + if(!pl.add(it, pos)) + callback(true, null); + else + callback(false, it); + }); +} + +Playlist.prototype.remove = function(uid, callback) { + var item = this.items.find(uid); + if(this.items.remove(uid)) { + if(item == this.current) + this._next(); if(callback) callback(); - return true; } - - return false; -} - -Playlist.prototype.remove = function(uid, next) { - this._remove(uid, next); - this.on("remove")(item); -} - -Playlist.prototype._remove = function(uid, next) { - var item = this.find(uid); - if(!item) - return false; - - if(item == this.first) - this.first = item.next; - if(item == this.last) - this.last = item.prev; - - if(item.prev) - item.prev.next = item.next; - if(item.next) - item.next.prev = item.prev; - - if(this.current == item && next) - this._next(); - - this.length--; - return true; } Playlist.prototype.move = function(from, after, callback) { - var it = this.find(from); - if(!this._remove(from)) + if(this.lock) { + this.queueAction({ + fn: "move", + args: arguments + }); + return; + } + this.lock = true; + this._move(from, after, callback); + this.lock = false; + this.lock = false; +} + +Playlist.prototype._move = function(from, after, callback) { + var it = this.items.find(from); + if(!this.items.remove(from)) return; if(after === "prepend") { - if(!this._prepend(it)) + if(!this.items.prepend(it)) return; } else if(after === "append") { - if(!this._append(it)) + if(!this.items.append(it)) return; } - else if(!this._insertAfter(it, after)) + else if(!this.items.insertAfter(it, after)) return; callback(); @@ -255,7 +211,10 @@ Playlist.prototype.next = function() { this._next(); if(it.temp) { - this.remove(it.uid, true); + var pl = this; + this.remove(it.uid, function() { + pl.on("remove")(it); + }); } return this.current; @@ -265,8 +224,8 @@ Playlist.prototype._next = function() { if(!this.current) return; this.current = this.current.next; - if(this.current === null && this.first !== null) - this.current = this.first; + if(this.current === null && this.items.first !== null) + this.current = this.items.first; if(this.current) { this.startPlayback(); @@ -277,7 +236,7 @@ Playlist.prototype.jump = function(uid) { if(!this.current) return false; - var jmp = this.find(uid); + var jmp = this.items.find(uid); if(!jmp) return false; @@ -296,21 +255,8 @@ Playlist.prototype.jump = function(uid) { return this.current; } -Playlist.prototype.toArray = function() { - var arr = []; - var item = this.first; - while(item != null) { - arr.push(item.pack()); - item = item.next; - } - return arr; -} - Playlist.prototype.clear = function() { - this.first = null; - this.last = null; - this.current = null; - this.length = 0; + this.items.clear(); this.next_uid = 0; clearInterval(this._leadInterval); } @@ -330,15 +276,15 @@ Playlist.prototype.lead = function(lead) { } Playlist.prototype.startPlayback = function(time) { - this.current.media.paused = true; - this.current.media.currentTime = time || -2; + if(this.current.media === "loading") { + setTimeout(function() { + this.startPlayback(time); + }.bind(this), 100); + return; + } + this.current.media.paused = false; + this.current.media.currentTime = time || -1; var pl = this; - setTimeout(function() { - if(!pl.current) - return; - pl.current.media.paused = false; - pl.on("mediaUpdate")(pl.current.media); - }, 2000); if(this.leading && !this._leadInterval && !isLive(this.current.media.type)) { this._lastUpdate = Date.now(); this._leadInterval = setInterval(function() {