diff options
| author | RblSb <msrblsb@gmail.com> | 2020-02-27 20:24:51 +0300 |
|---|---|---|
| committer | RblSb <msrblsb@gmail.com> | 2020-02-27 20:24:51 +0300 |
| commit | 8c739fa43946ba8cc5bc6c6226032154b9481a40 (patch) | |
| tree | 8e112fdf4fe4d9d2fbe851e4bbdce56ec8298d16 | |
| parent | 2c7811e0c56d5c33ab0138b6dcd281f516bff809 (diff) | |
Permanent items and player abstraction
| -rw-r--r-- | build/server.js | 103 | ||||
| -rw-r--r-- | res/client.js | 360 | ||||
| -rw-r--r-- | res/index.html | 2 | ||||
| -rw-r--r-- | src/Types.hx | 8 | ||||
| -rw-r--r-- | src/client/IPlayer.hx | 12 | ||||
| -rw-r--r-- | src/client/Main.hx | 45 | ||||
| -rw-r--r-- | src/client/Player.hx | 218 | ||||
| -rw-r--r-- | src/client/players/Raw.hx | 72 | ||||
| -rw-r--r-- | src/server/Main.hx | 64 | ||||
| -rw-r--r-- | src/server/ServerState.hx | 1 |
10 files changed, 625 insertions, 260 deletions
diff --git a/build/server.js b/build/server.js index a6edc55..4cd1089 100644 --- a/build/server.js +++ b/build/server.js @@ -176,6 +176,13 @@ HxOverrides.iter = function(a) { }; var Lambda = function() { }; Lambda.__name__ = true; +Lambda.exists = function(it,f) { + var x = $getIterator(it); + while(x.hasNext()) if(f(x.next())) { + return true; + } + return false; +}; Lambda.find = function(it,f) { var v = $getIterator(it); while(v.hasNext()) { @@ -638,6 +645,7 @@ var server_Main = function(port,wsPort) { } this.loadedClientsCount = 0; this.htmlChars = new EReg("[&^<>'\"]",""); + this.itemPos = 0; this.messages = []; this.videoTimer = new server_VideoTimer(); this.videoList = []; @@ -676,8 +684,8 @@ var server_Main = function(port,wsPort) { this.port = port; server_Utils.getGlobalIp(function(ip) { _gthis.globalIp = ip; - haxe_Log.trace("Local: http://" + _gthis.localIp + ":" + port,{ fileName : "src/server/Main.hx", lineNumber : 70, className : "server.Main", methodName : "new"}); - haxe_Log.trace("Global: http://" + _gthis.globalIp + ":" + port,{ fileName : "src/server/Main.hx", lineNumber : 71, className : "server.Main", methodName : "new"}); + haxe_Log.trace("Local: http://" + _gthis.localIp + ":" + port,{ fileName : "src/server/Main.hx", lineNumber : 71, className : "server.Main", methodName : "new"}); + haxe_Log.trace("Global: http://" + _gthis.globalIp + ":" + port,{ fileName : "src/server/Main.hx", lineNumber : 72, className : "server.Main", methodName : "new"}); return; }); var dir = "" + this.rootDir + "/res"; @@ -709,28 +717,29 @@ server_Main.prototype = { var field = _g1[_g]; ++_g; if(Reflect.field(config,field) == null) { - haxe_Log.trace("Warning: config field \"" + field + "\" is unknown",{ fileName : "src/server/Main.hx", lineNumber : 92, className : "server.Main", methodName : "getUserConfig"}); + haxe_Log.trace("Warning: config field \"" + field + "\" is unknown",{ fileName : "src/server/Main.hx", lineNumber : 93, className : "server.Main", methodName : "getUserConfig"}); } config[field] = Reflect.field(customConfig,field); } return config; } ,saveState: function() { - haxe_Log.trace("Saving state...",{ fileName : "src/server/Main.hx", lineNumber : 99, className : "server.Main", methodName : "saveState"}); - var json = JSON.stringify({ videoList : this.videoList, messages : this.messages, timer : { time : this.videoTimer.getTime(), paused : this.videoTimer.isPaused()}},null,"\t"); + haxe_Log.trace("Saving state...",{ fileName : "src/server/Main.hx", lineNumber : 100, className : "server.Main", methodName : "saveState"}); + var json = JSON.stringify({ videoList : this.videoList, itemPos : this.itemPos, messages : this.messages, timer : { time : this.videoTimer.getTime(), paused : this.videoTimer.isPaused()}},null,"\t"); js_node_Fs.writeFileSync(this.statePath,json); } ,loadState: function() { if(!sys_FileSystem.exists(this.statePath)) { return; } - haxe_Log.trace("Loading state...",{ fileName : "src/server/Main.hx", lineNumber : 114, className : "server.Main", methodName : "loadState"}); + haxe_Log.trace("Loading state...",{ fileName : "src/server/Main.hx", lineNumber : 116, className : "server.Main", methodName : "loadState"}); var data = JSON.parse(js_node_Fs.readFileSync(this.statePath,{ encoding : "utf8"})); this.videoList.length = 0; this.messages.length = 0; var _g = 0; var _g1 = data.videoList; while(_g < _g1.length) this.videoList.push(_g1[_g++]); + this.itemPos = data.itemPos; var _g2 = 0; var _g3 = data.messages; while(_g2 < _g3.length) this.messages.push(_g3[_g2++]); @@ -739,7 +748,7 @@ server_Main.prototype = { this.videoTimer.pause(); } ,logError: function(type,data) { - haxe_Log.trace(type,{ fileName : "src/server/Main.hx", lineNumber : 126, className : "server.Main", methodName : "logError", customParams : [data]}); + haxe_Log.trace(type,{ fileName : "src/server/Main.hx", lineNumber : 129, className : "server.Main", methodName : "logError", customParams : [data]}); var crashesFolder = "" + this.rootDir + "/user/crashes"; var name = new Date().toISOString() + "-" + type; if(!sys_FileSystem.exists(crashesFolder)) { @@ -755,7 +764,7 @@ server_Main.prototype = { return; } var url = "http://" + process.env["APP_URL"]; - haxe_Log.trace("Ping " + url,{ fileName : "src/server/Main.hx", lineNumber : 140, className : "server.Main", methodName : "initIntergationHandlers"}); + haxe_Log.trace("Ping " + url,{ fileName : "src/server/Main.hx", lineNumber : 143, className : "server.Main", methodName : "initIntergationHandlers"}); js_node_Http.get(url,function(r) { return; }); @@ -767,7 +776,7 @@ server_Main.prototype = { var ip = req.connection.remoteAddress; var id = this.freeIds.length > 0 ? this.freeIds.shift() : this.clients.length; var name = "Guest " + (id + 1); - haxe_Log.trace("" + name + " connected (" + ip + ")",{ fileName : "src/server/Main.hx", lineNumber : 150, className : "server.Main", methodName : "onConnect"}); + haxe_Log.trace("" + name + " connected (" + ip + ")",{ fileName : "src/server/Main.hx", lineNumber : 153, className : "server.Main", methodName : "onConnect"}); var client = new Client(ws,req,id,name,0); if(req.connection.localAddress == ip) { client.group |= 4; @@ -785,7 +794,7 @@ server_Main.prototype = { var _g1 = 0; var _g2 = this.clients; while(_g1 < _g2.length) _g.push(_g2[_g1++].getData()); - this.send(client,{ type : "Connected", connected : { config : tmp, history : tmp1, isUnknownClient : true, clientName : client1, clients : _g, videoList : this.videoList, globalIp : this.globalIp}}); + this.send(client,{ type : "Connected", connected : { config : tmp, history : tmp1, isUnknownClient : true, clientName : client1, clients : _g, videoList : this.videoList, itemPos : this.itemPos, globalIp : this.globalIp}}); this.sendClientList(); ws.on("message",function(data) { var tmp2 = JSON.parse(data); @@ -793,7 +802,7 @@ server_Main.prototype = { return; }); ws.on("close",function(err) { - haxe_Log.trace("Client " + client.name + " disconnected",{ fileName : "src/server/Main.hx", lineNumber : 178, className : "server.Main", methodName : "onConnect"}); + haxe_Log.trace("Client " + client.name + " disconnected",{ fileName : "src/server/Main.hx", lineNumber : 182, className : "server.Main", methodName : "onConnect"}); server_Utils.sortedPush(_gthis.freeIds,client.id); HxOverrides.remove(_gthis.clients,client); _gthis.sendClientList(); @@ -815,15 +824,20 @@ server_Main.prototype = { switch(data.type) { case "AddVideo": var item = data.addVideo.item; + var local = "" + this.localIp + ":" + this.port; + if(item.url.indexOf(local) != -1) { + item.url = StringTools.replace(item.url,local,"" + this.globalIp + ":" + this.port); + } item.author = client.name; - var localOrigin = "" + this.localIp + ":" + this.port; - if(item.url.indexOf(localOrigin) != -1) { - item.url = StringTools.replace(item.url,localOrigin,"" + this.globalIp + ":" + this.port); + if(Lambda.exists(this.videoList,function(i) { + return i.url == item.url; + })) { + return; } if(data.addVideo.atEnd) { this.videoList.push(item); } else { - this.videoList.splice(1,0,item); + this.videoList.splice(this.itemPos + 1,0,item); } this.broadcast(data); if(this.videoList.length == 1) { @@ -839,6 +853,7 @@ server_Main.prototype = { case "ClearPlaylist": this.videoTimer.stop(); this.videoList.length = 0; + this.itemPos = 0; this.broadcast(data); break; case "Connected": @@ -847,9 +862,9 @@ server_Main.prototype = { if(this.videoList.length == 0) { return; } - if(this.videoTimer.getTime() > this.videoList[0].duration) { + if(this.videoTimer.getTime() > this.videoList[this.itemPos].duration) { this.videoTimer.stop(); - this.onMessage(client,{ type : "RemoveVideo", removeVideo : { url : this.videoList[0].url}}); + this.onMessage(client,{ type : "SkipVideo", skipVideo : { url : this.videoList[this.itemPos].url}}); return; } this.send(client,{ type : "GetTime", getTime : { time : this.videoTimer.getTime(), paused : this.videoTimer.isPaused()}}); @@ -916,15 +931,26 @@ server_Main.prototype = { return; } var url = data.removeVideo.url; - var isFirst = this.videoList[0].url == url; - if(isFirst) { - this.videoTimer.stop(); + var item1 = Lambda.find(this.videoList,function(item2) { + return item2.url == url; + }); + if(item1 == null) { + return; } - HxOverrides.remove(this.videoList,Lambda.find(this.videoList,function(item1) { - return item1.url == url; - })); - if(isFirst && this.videoList.length > 0) { - this.restartWaitTimer(); + var index = this.videoList.indexOf(item1); + var isCurrent = this.videoList[this.itemPos].url == url; + if(index < this.itemPos) { + this.itemPos--; + } + HxOverrides.remove(this.videoList,item1); + if(isCurrent) { + if(this.itemPos >= this.videoList.length) { + this.itemPos = 0; + } + this.videoTimer.stop(); + if(this.videoList.length > 0) { + this.restartWaitTimer(); + } } this.broadcast(data); break; @@ -966,11 +992,34 @@ server_Main.prototype = { if(this.videoList.length == 0) { return; } - var first = this.videoList.shift(); + var current = this.videoList[this.itemPos]; + HxOverrides.remove(this.videoList,current); server_Utils.shuffle(this.videoList); - this.videoList.unshift(first); + this.videoList.splice(this.itemPos,0,current); this.broadcast({ type : "UpdatePlaylist", updatePlaylist : { videoList : this.videoList}}); break; + case "SkipVideo": + if(this.videoList.length == 0) { + return; + } + var item3 = this.videoList[this.itemPos]; + if(item3.url != data.skipVideo.url) { + return; + } + if(!item3.isTemp) { + this.itemPos++; + } else { + HxOverrides.remove(this.videoList,item3); + } + if(this.itemPos >= this.videoList.length) { + this.itemPos = 0; + } + this.videoTimer.stop(); + if(this.videoList.length > 0) { + this.restartWaitTimer(); + } + this.broadcast(data); + break; case "UpdateClients": this.sendClientList(); break; diff --git a/res/client.js b/res/client.js index 0f003eb..bcbe9cd 100644 --- a/res/client.js +++ b/res/client.js @@ -674,23 +674,30 @@ client_Main.prototype = { _gthis.send({ type : "SetLeader", setLeader : { clientName : (_gthis.personal.group & 2) != 0 ? "" : _gthis.personal.name}}); return; }; - window.document.querySelector("#queue_next").onclick = function(e1) { + window.document.querySelector("#voteskip").onclick = function(e1) { + if(_gthis.player.isListEmpty()) { + return; + } + _gthis.send({ type : "SkipVideo", skipVideo : { url : _gthis.player.getItems()[_gthis.player.getItemPos()].url}}); + return; + }; + window.document.querySelector("#queue_next").onclick = function(e2) { _gthis.addVideoUrl(false); return; }; - window.document.querySelector("#queue_end").onclick = function(e2) { + window.document.querySelector("#queue_end").onclick = function(e3) { _gthis.addVideoUrl(true); return; }; - window.document.querySelector("#mediaurl").onkeydown = function(e3) { - if(e3.keyCode == 13) { + window.document.querySelector("#mediaurl").onkeydown = function(e4) { + if(e4.keyCode == 13) { _gthis.addVideoUrl(true); } }; } ,addVideoUrl: function(atEnd) { - var _gthis = this; var mediaUrl = window.document.querySelector("#mediaurl"); + var isTemp = window.document.querySelector("#addfromurl").querySelector(".add-temp").checked; var url = mediaUrl.value; if(url.length == 0) { return; @@ -698,22 +705,29 @@ client_Main.prototype = { mediaUrl.value = ""; var _this_r = new RegExp(",(https?)","g".split("u").join("")); var links = url.replace(_this_r,"|$1").split("|"); - this.addVideo(atEnd || this.player.isListEmpty() ? links.shift() : links.pop(),atEnd,function() { - _gthis.addVideoArray(links,atEnd); - return; - }); + if(!atEnd) { + var first = null; + if(this.player.isListEmpty()) { + first = links.shift(); + } + links.reverse(); + if(this.player.isListEmpty()) { + links.unshift(first); + } + } + this.addVideoArray(links,atEnd,isTemp); } - ,addVideoArray: function(links,atEnd) { + ,addVideoArray: function(links,atEnd,isTemp) { var _gthis = this; if(links.length == 0) { return; } - this.addVideo(atEnd ? links.shift() : links.pop(),atEnd,function() { - _gthis.addVideoArray(links,atEnd); + this.addVideo(links.shift(),atEnd,isTemp,function() { + _gthis.addVideoArray(links,atEnd,isTemp); return; }); } - ,addVideo: function(url,atEnd,callback) { + ,addVideo: function(url,atEnd,isTemp,callback) { var _gthis = this; var protocol = window.location.protocol; if(StringTools.startsWith(url,"/")) { @@ -722,7 +736,8 @@ client_Main.prototype = { if(!StringTools.startsWith(url,"http")) { url = "" + protocol + "//" + url; } - var name = HxOverrides.substr(url,url.lastIndexOf("/") + 1,null); + var pos = url.lastIndexOf("/") + 1; + var name = HxOverrides.substr(url,pos,null); var matchName = new EReg("^(.+)\\.",""); if(matchName.match(name)) { name = matchName.matched(1); @@ -735,7 +750,7 @@ client_Main.prototype = { _gthis.serverMessage(4,tmp); return; } - _gthis.send({ type : "AddVideo", addVideo : { item : { url : url, title : name, author : _gthis.personal.name, duration : duration}, atEnd : atEnd}}); + _gthis.send({ type : "AddVideo", addVideo : { item : { url : url, title : name, author : _gthis.personal.name, duration : duration, isTemp : isTemp}, atEnd : atEnd}}); callback(); return; }); @@ -744,7 +759,7 @@ client_Main.prototype = { if(this.player.hasVideo()) { this.player.removeVideo(); } else if(!this.player.isListEmpty()) { - this.player.setVideo(this.player.getItems()[0]); + this.player.setVideo(this.player.getItemPos()); } return this.player.hasVideo(); } @@ -788,13 +803,13 @@ client_Main.prototype = { var data = JSON.parse(e.data); var t = data.type; var t1 = t.charAt(0).toLowerCase() + HxOverrides.substr(t,1,null); - haxe_Log.trace("Event: " + data.type,{ fileName : "src/client/Main.hx", lineNumber : 207, className : "client.Main", methodName : "onMessage", customParams : [data[t1]]}); + haxe_Log.trace("Event: " + data.type,{ fileName : "src/client/Main.hx", lineNumber : 228, className : "client.Main", methodName : "onMessage", customParams : [data[t1]]}); switch(data.type) { case "AddVideo": - if(this.player.isListEmpty()) { - this.player.setVideo(data.addVideo.item); - } this.player.addVideoItem(data.addVideo.item,data.addVideo.atEnd); + if(this.player.itemsLength() == 1) { + this.player.setVideo(0); + } break; case "ClearChat": this.clearChat(); @@ -885,6 +900,12 @@ client_Main.prototype = { break; case "ShufflePlaylist": break; + case "SkipVideo": + this.player.skipItem(data.skipVideo.url); + if(this.player.isListEmpty()) { + this.player.pause(); + } + break; case "UpdateClients": this.updateClients(data.updateClients.clients); this.personal = ClientTools.getByName(this.clients,this.personal.name,this.personal); @@ -922,7 +943,7 @@ client_Main.prototype = { ++_g; this.addMessage(message.name,message.text,message.time); } - this.player.setItems(connected.videoList); + this.player.setItems(connected.videoList,connected.itemPos); } ,setConfig: function(config) { this.config = config; @@ -946,8 +967,9 @@ client_Main.prototype = { } var smilesWrap = window.document.querySelector("#smileswrap"); smilesWrap.onclick = function(e) { + var el = e.target; var form = window.document.querySelector("#chatline"); - form.value += " " + e.target.title; + form.value += " " + el.title; form.focus(); return; }; @@ -1064,6 +1086,7 @@ client_Main.prototype = { nameDiv.className = "username"; nameDiv.textContent = name + ": "; var textDiv = window.document.createElement("span"); + text = StringTools.htmlEscape(text); if(StringTools.startsWith(text,"/")) { if(name == this.personal.name) { this.handleCommands(HxOverrides.substr(text,1,null)); @@ -1077,7 +1100,6 @@ client_Main.prototype = { text = text.replace(filter.regex.r,filter.replace); } } - text = StringTools.htmlEscape(text); textDiv.innerHTML = text; var isInChatEnd = msgBuf.scrollHeight - msgBuf.scrollTop == msgBuf.clientHeight; userDiv.appendChild(tstamp); @@ -1150,93 +1172,105 @@ client_MobileView.init = function() { }; }; var client_Player = function(main) { + this.matchYoutube = new EReg("v=([A-z0-9_-]+)",""); this.skipSetTime = false; this.isLoaded = false; - this.player = window.document.querySelector("#ytapiplayer"); + this.itemPos = 0; + this.currentSrc = ""; + this.playerEl = window.document.querySelector("#ytapiplayer"); this.videoItemsEl = window.document.querySelector("#queue"); this.items = []; this.main = main; }; client_Player.__name__ = true; client_Player.prototype = { - setVideo: function(item) { - var _gthis = this; + setPlayer: function(player) { + this.player = player; + } + ,isYoutube: function(url) { + if(url.indexOf("youtube.com/") == -1) { + return false; + } + if(url.indexOf("youtu.be/") == -1) { + return false; + } + if(!this.matchYoutube.match(url)) { + return false; + } + return true; + } + ,setVideo: function(i) { + var item = this.items[i]; + if(!this.isYoutube(item.url)) { + this.setPlayer(new client_players_Raw(this.main,this)); + } + var childs = this.videoItemsEl.children; + if(childs[this.itemPos] != null) { + childs[this.itemPos].classList.remove("queue_active"); + } + this.itemPos = i; + childs[this.itemPos].classList.add("queue_active"); + this.currentSrc = item.url; + this.playerEl.textContent = ""; this.isLoaded = false; - this.video = window.document.createElement("video"); - this.video.id = "videoplayer"; - item.url = this.main.tryLocalIp(item.url); - this.video.src = item.url; - this.video.controls = true; - var isTouch = 'ontouchstart' in window; - if(!isTouch) { - haxe_Timer.delay(function() { - _gthis.video.controls = false; - _gthis.video.onmouseover = function(e) { - _gthis.video.controls = true; - _gthis.video.onmouseover = null; - return _gthis.video.onmousemove = null; - }; - return _gthis.video.onmousemove = _gthis.video.onmouseover; - },3000); + this.player.loadVideo(item); + window.document.querySelector("#currenttitle").textContent = item.title; + } + ,removeVideo: function() { + this.currentSrc = ""; + this.player.removeVideo(); + window.document.querySelector("#currenttitle").textContent = Lang.get("nothingPlaying"); + } + ,onCanBePlayed: function() { + if(!this.isLoaded) { + this.main.send({ type : "VideoLoaded"}); } - this.video.oncanplaythrough = function(e1) { - if(!_gthis.isLoaded) { - _gthis.main.send({ type : "VideoLoaded"}); - } - return _gthis.isLoaded = true; - }; - this.video.onseeking = function(e2) { - if(_gthis.skipSetTime) { - _gthis.skipSetTime = false; - return; - } - if((_gthis.main.personal.group & 1 << ClientGroup.Leader._hx_index) == 0) { - return; - } - _gthis.main.send({ type : "SetTime", setTime : { time : _gthis.video.currentTime}}); + this.isLoaded = true; + } + ,onPlay: function() { + if((this.main.personal.group & 2) == 0) { return; - }; - this.video.onpause = function(e3) { - if((_gthis.main.personal.group & 1 << ClientGroup.Leader._hx_index) == 0) { - return; - } - _gthis.main.send({ type : "Pause", pause : { time : _gthis.video.currentTime}}); + } + this.main.send({ type : "Play", play : { time : this.getTime()}}); + } + ,onPause: function() { + if((this.main.personal.group & 2) == 0) { return; - }; - this.video.onplay = function(e4) { - if((_gthis.main.personal.group & 1 << ClientGroup.Leader._hx_index) == 0) { - return; - } - _gthis.main.send({ type : "Play", play : { time : _gthis.video.currentTime}}); + } + this.main.send({ type : "Pause", pause : { time : this.getTime()}}); + } + ,onSetTime: function() { + if(this.skipSetTime) { + this.skipSetTime = false; return; - }; - this.player.textContent = ""; - this.player.appendChild(this.video); - window.document.querySelector("#currenttitle").textContent = item.title; + } + if((this.main.personal.group & 2) == 0) { + return; + } + this.main.send({ type : "SetTime", setTime : { time : this.getTime()}}); } ,addVideoItem: function(item,atEnd) { var _gthis = this; - this.items.push(item); - var itemEl = this.nodeFromString("<li class=\"queue_entry pluid-0 queue_temp queue_active\" title=\"" + Lang.get("addedBy") + ": " + item.author + "\">\n\t\t\t\t<a class=\"qe_title\" href=\"" + item.url + "\" target=\"_blank\">" + StringTools.htmlEscape(item.title) + "</a>\n\t\t\t\t<span class=\"qe_time\">" + this.duration(item.duration) + "</span>\n\t\t\t\t<div class=\"qe_clear\"></div>\n\t\t\t\t<div class=\"btn-group\" style=\"display: inline-block;\">\n\t\t\t\t\t<button class=\"btn btn-xs btn-default qbtn-play\">\n\t\t\t\t\t\t<span class=\"glyphicon glyphicon-play\"></span>" + Lang.get("play") + "\n\t\t\t\t\t</button>\n\t\t\t\t\t<button class=\"btn btn-xs btn-default qbtn-next\">\n\t\t\t\t\t\t<span class=\"glyphicon glyphicon-share-alt\"></span>" + Lang.get("skip") + "\n\t\t\t\t\t</button>\n\t\t\t\t\t<button class=\"btn btn-xs btn-default qbtn-tmp\">\n\t\t\t\t\t\t<span class=\"glyphicon glyphicon-flag\"></span>" + Lang.get("makePermanent") + "\n\t\t\t\t\t</button>\n\t\t\t\t\t<button class=\"btn btn-xs btn-default qbtn-delete\" id=\"btn-delete\">\n\t\t\t\t\t\t<span class=\"glyphicon glyphicon-trash\"></span>" + Lang.get("delete") + "\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t</li>"); + var itemEl = this.nodeFromString("<li class=\"queue_entry pluid-0\" title=\"" + Lang.get("addedBy") + ": " + item.author + "\">\n\t\t\t\t<a class=\"qe_title\" href=\"" + item.url + "\" target=\"_blank\">" + StringTools.htmlEscape(item.title) + "</a>\n\t\t\t\t<span class=\"qe_time\">" + this.duration(item.duration) + "</span>\n\t\t\t\t<div class=\"qe_clear\"></div>\n\t\t\t\t<div class=\"btn-group\" style=\"display: inline-block;\">\n\t\t\t\t\t<button class=\"btn btn-xs btn-default qbtn-play\">\n\t\t\t\t\t\t<span class=\"glyphicon glyphicon-play\"></span>" + Lang.get("play") + "\n\t\t\t\t\t</button>\n\t\t\t\t\t<button class=\"btn btn-xs btn-default qbtn-next\">\n\t\t\t\t\t\t<span class=\"glyphicon glyphicon-share-alt\"></span>" + Lang.get("skip") + "\n\t\t\t\t\t</button>\n\t\t\t\t\t<button class=\"btn btn-xs btn-default qbtn-tmp\">\n\t\t\t\t\t\t<span class=\"glyphicon glyphicon-flag\"></span>" + Lang.get("makePermanent") + "\n\t\t\t\t\t</button>\n\t\t\t\t\t<button class=\"btn btn-xs btn-default qbtn-delete\" id=\"btn-delete\">\n\t\t\t\t\t\t<span class=\"glyphicon glyphicon-trash\"></span>" + Lang.get("delete") + "\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t</li>"); + if(item.isTemp) { + itemEl.classList.add("queue_temp"); + } itemEl.querySelector("#btn-delete").onclick = function(e) { _gthis.main.send({ type : "RemoveVideo", removeVideo : { url : itemEl.querySelector(".qe_title").getAttribute("href")}}); return; }; if(atEnd) { + this.items.push(item); + } else { + this.items.splice(this.itemPos + 1,0,item); + } + if(atEnd) { this.videoItemsEl.appendChild(itemEl); } else { - client_Utils.insertAtIndex(this.videoItemsEl,itemEl,1); + client_Utils.insertAtIndex(this.videoItemsEl,itemEl,this.itemPos + 1); } this.updateCounters(); } - ,removeVideo: function() { - if(this.video == null) { - return; - } - this.player.removeChild(this.video); - this.video = null; - window.document.querySelector("#currenttitle").textContent = Lang.get("nothingPlaying"); - } ,removeItem: function(url) { var _g = 0; var _g1 = this.videoItemsEl.children; @@ -1248,18 +1282,46 @@ client_Player.prototype = { break; } } - HxOverrides.remove(this.items,Lambda.find(this.items,function(item) { - return item.url == url; - })); + var item = Lambda.find(this.items,function(item1) { + return item1.url == url; + }); + if(item == null) { + return; + } + var index = this.items.indexOf(item); + HxOverrides.remove(this.items,item); this.updateCounters(); - if(this.video == null) { + if(index < this.itemPos) { + this.itemPos--; return; } - if(this.video.src == url) { - if(this.items.length > 0) { - this.setVideo(this.items[0]); - } + if(index != this.itemPos) { + return; + } + if(this.items.length == 0) { + return; + } + if(this.items[index] == null) { + index = 0; + } + this.setVideo(index); + } + ,skipItem: function(url) { + var item = Lambda.find(this.items,function(item1) { + return item1.url == url; + }); + if(item == null) { + return; + } + if(item.isTemp) { + this.removeItem(url); + return; } + var index = this.items.indexOf(item) + 1; + if(index >= this.items.length) { + index = 0; + } + this.setVideo(index); } ,updateCounters: function() { var tmp = "" + this.items.length + " "; @@ -1270,16 +1332,19 @@ client_Player.prototype = { ,getItems: function() { return this.items; } - ,setItems: function(list) { + ,setItems: function(list,pos) { this.clearItems(); + if(pos != null) { + this.itemPos = pos; + } if(list.length == 0) { return; } - if(this.video == null || this.video.src != list[0].url) { - this.setVideo(list[0]); - } var _g = 0; while(_g < list.length) this.addVideoItem(list[_g++],true); + if(this.currentSrc != this.items[this.itemPos].url) { + this.setVideo(this.itemPos); + } } ,clearItems: function() { this.items.length = 0; @@ -1291,7 +1356,7 @@ client_Player.prototype = { return; } this.removeVideo(); - this.setVideo(this.items[0]); + this.setVideo(this.itemPos); } ,duration: function(time) { var h = time / 60 / 60 | 0; @@ -1325,36 +1390,42 @@ client_Player.prototype = { ,isListEmpty: function() { return this.items.length == 0; } + ,itemsLength: function() { + return this.items.length; + } + ,getItemPos: function() { + return this.itemPos; + } ,hasVideo: function() { - return this.video != null; + return this.player != null; } - ,pause: function() { - if(this.video == null) { + ,play: function() { + if(this.player == null) { return; } - this.video.pause(); + this.player.play(); } - ,play: function() { - if(this.video == null) { + ,pause: function() { + if(this.player == null) { return; } - this.video.play(); + this.player.pause(); + } + ,getTime: function() { + if(this.player == null) { + return 0; + } + return this.player.getTime(); } ,setTime: function(time,isLocal) { if(isLocal == null) { isLocal = true; } - if(this.video == null) { + if(this.player == null) { return; } this.skipSetTime = isLocal; - this.video.currentTime = time; - } - ,getTime: function() { - if(this.video == null) { - return 0; - } - return this.video.currentTime; + this.player.setTime(time); } }; var client_Utils = function() { }; @@ -1414,6 +1485,71 @@ client_Utils.copyToClipboard = function(text) { window.document.body.removeChild(textarea); } }; +var client_players_Raw = function(main,player) { + this.playerEl = window.document.querySelector("#ytapiplayer"); + this.main = main; + this.player = player; +}; +client_players_Raw.__name__ = true; +client_players_Raw.prototype = { + loadVideo: function(item) { + var _gthis = this; + this.video = window.document.createElement("video"); + this.video.id = "videoplayer"; + var url = this.main.tryLocalIp(item.url); + this.video.src = url; + this.video.controls = true; + var isTouch = 'ontouchstart' in window; + if(!isTouch) { + haxe_Timer.delay(function() { + _gthis.video.controls = false; + _gthis.video.onmouseover = function(e) { + _gthis.video.controls = true; + _gthis.video.onmouseover = null; + return _gthis.video.onmousemove = null; + }; + return _gthis.video.onmousemove = _gthis.video.onmouseover; + },3000); + } + this.video.oncanplaythrough = ($_=this.player,$bind($_,$_.onCanBePlayed)); + this.video.onseeking = ($_=this.player,$bind($_,$_.onSetTime)); + this.video.onplay = ($_=this.player,$bind($_,$_.onPlay)); + this.video.onpause = ($_=this.player,$bind($_,$_.onPause)); + this.playerEl.appendChild(this.video); + this.video.pause(); + } + ,removeVideo: function() { + if(this.video == null) { + return; + } + this.playerEl.removeChild(this.video); + this.video = null; + } + ,play: function() { + if(this.video == null) { + return; + } + this.video.play(); + } + ,pause: function() { + if(this.video == null) { + return; + } + this.video.pause(); + } + ,getTime: function() { + if(this.video == null) { + return 0; + } + return this.video.currentTime; + } + ,setTime: function(time) { + if(this.video == null) { + return; + } + this.video.currentTime = time; + } +}; var haxe_Log = function() { }; haxe_Log.__name__ = true; haxe_Log.formatOutput = function(v,infos) { diff --git a/res/index.html b/res/index.html index 7b51c56..bbc2751 100644 --- a/res/index.html +++ b/res/index.html @@ -99,7 +99,7 @@ <button class="btn btn-sm btn-default" id="mediarefresh" title="${refreshPlayer}"><span class="glyphicon glyphicon-retweet"></span></button> <button class="btn btn-sm btn-default" id="fullscreenbtn" title="${fullscreenPlayer}"><span class="glyphicon glyphicon-fullscreen"></span></button> <button class="btn btn-sm btn-default" id="getplaylist" title="${retrievePlaylistLinks}"><span class="glyphicon glyphicon-link"></span></button> - <button class="btn btn-sm btn-default" id="voteskip" title="${voteForSkip}" disabled="disabled"><span class="glyphicon glyphicon-step-forward"></span></button> + <button class="btn btn-sm btn-default" id="voteskip" title="${voteForSkip}"><span class="glyphicon glyphicon-step-forward"></span></button> </div> </div> </div> diff --git a/src/Types.hx b/src/Types.hx index 5706111..b98a6c2 100644 --- a/src/Types.hx +++ b/src/Types.hx @@ -35,7 +35,8 @@ typedef VideoItem = { url:String, title:String, author:String, - duration:Float + duration:Float, + isTemp:Bool } typedef WsEvent = { @@ -47,6 +48,7 @@ typedef WsEvent = { isUnknownClient:Bool, clientName:String, videoList:Array<VideoItem>, + itemPos:Int, globalIp:String }, ?login:{ @@ -73,6 +75,9 @@ typedef WsEvent = { ?removeVideo:{ url:String }, + ?skipVideo:{ + url:String + }, ?pause:{ time:Float }, @@ -108,6 +113,7 @@ enum abstract WsEventType(String) { // var RemoveClient; var AddVideo; var RemoveVideo; + var SkipVideo; var VideoLoaded; var Pause; var Play; diff --git a/src/client/IPlayer.hx b/src/client/IPlayer.hx new file mode 100644 index 0000000..9f224a8 --- /dev/null +++ b/src/client/IPlayer.hx @@ -0,0 +1,12 @@ +package client; + +import Types.VideoItem; + +interface IPlayer { + function loadVideo(item:VideoItem):Void; + function removeVideo():Void; + function play():Void; + function pause():Void; + function getTime():Float; + function setTime(time:Float):Void; +} diff --git a/src/client/Main.hx b/src/client/Main.hx index b2e224d..19ca44b 100644 --- a/src/client/Main.hx +++ b/src/client/Main.hx @@ -90,6 +90,18 @@ class Main { } }); } + final voteSkip = ge("#voteskip"); + voteSkip.onclick = e -> { + if (player.isListEmpty()) return; + final items = player.getItems(); + final pos = player.getItemPos(); + send({ + type: SkipVideo, + skipVideo: { + url: items[pos].url + } + }); + } ge("#queue_next").onclick = e -> addVideoUrl(false); ge("#queue_end").onclick = e -> addVideoUrl(true); @@ -112,23 +124,31 @@ class Main { function addVideoUrl(atEnd:Bool):Void { final mediaUrl:InputElement = cast ge("#mediaurl"); + final checkbox:InputElement = cast ge("#addfromurl").querySelector(".add-temp"); + final isTemp = checkbox.checked; final url = mediaUrl.value; if (url.length == 0) return; mediaUrl.value = ""; final url = ~/,(https?)/g.replace(url, "|$1"); final links = url.split("|"); - // if videos added as next, we need to load it in reverse order - final link = (atEnd || player.isListEmpty()) ? links.shift() : links.pop(); - addVideo(link, atEnd, () -> addVideoArray(links, atEnd)); + // if videos added as next, we need to load them in reverse order + if (!atEnd) { + // except first item when list empty + var first:Null<String> = null; + if (player.isListEmpty()) first = links.shift(); + links.reverse(); + if (player.isListEmpty()) links.unshift(first); + } + addVideoArray(links, atEnd, isTemp); } - function addVideoArray(links:Array<String>, atEnd:Bool):Void { + function addVideoArray(links:Array<String>, atEnd:Bool, isTemp:Bool):Void { if (links.length == 0) return; - final link = atEnd ? links.shift() : links.pop(); - addVideo(link, atEnd, () -> addVideoArray(links, atEnd)); + final link = links.shift(); + addVideo(link, atEnd, isTemp, () -> addVideoArray(links, atEnd, isTemp)); } - function addVideo(url:String, atEnd:Bool, callback:()->Void):Void { + function addVideo(url:String, atEnd:Bool, isTemp:Bool, callback:()->Void):Void { final protocol = Browser.location.protocol; if (url.startsWith("/")) { final host = Browser.location.hostname; @@ -153,6 +173,7 @@ class Main { title: name, author: personal.name, duration: duration, + isTemp: isTemp }, atEnd: atEnd }}); @@ -163,7 +184,7 @@ class Main { public function toggleVideoElement():Bool { if (player.hasVideo()) player.removeVideo(); else if (!player.isListEmpty()) { - player.setVideo(player.getItems()[0]); + player.setVideo(player.getItemPos()); } return player.hasVideo(); } @@ -231,8 +252,8 @@ class Main { addMessage(data.message.clientName, data.message.text); case AddVideo: - if (player.isListEmpty()) player.setVideo(data.addVideo.item); player.addVideoItem(data.addVideo.item, data.addVideo.atEnd); + if (player.itemsLength() == 1) player.setVideo(0); case VideoLoaded: player.setTime(0); @@ -242,6 +263,10 @@ class Main { player.removeItem(data.removeVideo.url); if (player.isListEmpty()) player.pause(); + case SkipVideo: + player.skipItem(data.skipVideo.url); + if (player.isListEmpty()) player.pause(); + case Pause: if (isLeader()) return; player.pause(); @@ -318,7 +343,7 @@ class Main { for (message in connected.history) { addMessage(message.name, message.text, message.time); } - player.setItems(connected.videoList); + player.setItems(connected.videoList, connected.itemPos); } function setConfig(config:Config):Void { diff --git a/src/client/Player.hx b/src/client/Player.hx index d8e4cb4..2c8f62e 100644 --- a/src/client/Player.hx +++ b/src/client/Player.hx @@ -1,10 +1,9 @@ package client; -import haxe.Timer; import js.html.Element; -import js.html.VideoElement; import js.Browser.document; import client.Main.ge; +import client.players.Raw; import Types.VideoItem; using StringTools; using Lambda; @@ -14,76 +13,93 @@ class Player { final main:Main; final items:Array<VideoItem> = []; final videoItemsEl = ge("#queue"); - final player:Element = ge("#ytapiplayer"); + final playerEl:Element = ge("#ytapiplayer"); + var player:Null<IPlayer>; + var currentSrc = ""; + var itemPos = 0; var isLoaded = false; var skipSetTime = false; - var video:VideoElement; + final matchYoutube = ~/v=([A-z0-9_-]+)/; public function new(main:Main):Void { this.main = main; } - public function setVideo(item:VideoItem):Void { + function setPlayer(player:IPlayer):Void { + this.player = player; + } + + function isYoutube(url:String):Bool { + if (!url.contains("youtube.com/")) return false; + if (!url.contains("youtu.be/")) return false; + if (!matchYoutube.match(url)) return false; + return true; + } + + public function setVideo(i:Int):Void { + final item = items[i]; + if (isYoutube(item.url)) {} // setPlayer(new Youtube(main, this)); + else setPlayer(new Raw(main, this)); + + final childs = videoItemsEl.children; + if (childs[itemPos] != null) { + childs[itemPos].classList.remove("queue_active"); + } + itemPos = i; + childs[itemPos].classList.add("queue_active"); + + currentSrc = item.url; + playerEl.textContent = ""; isLoaded = false; - video = document.createVideoElement(); - video.id = "videoplayer"; - item.url = main.tryLocalIp(item.url); - video.src = item.url; - video.controls = true; - final isTouch = untyped __js__("'ontouchstart' in window"); - if (!isTouch) Timer.delay(() -> { - video.controls = false; - video.onmouseover = e -> { - video.controls = true; - video.onmouseover = null; - video.onmousemove = null; + player.loadVideo(item); + ge("#currenttitle").textContent = item.title; + } + + public function removeVideo():Void { + currentSrc = ""; + player.removeVideo(); + ge("#currenttitle").textContent = Lang.get("nothingPlaying"); + } + + public function onCanBePlayed():Void { + if (!isLoaded) main.send({type: VideoLoaded}); + isLoaded = true; + } + + public function onPlay():Void { + if (!main.isLeader()) return; + main.send({ + type: Play, play: { + time: getTime() } - video.onmousemove = video.onmouseover; - }, 3000); - video.oncanplaythrough = e -> { - if (!isLoaded) main.send({type: VideoLoaded}); - isLoaded = true; - } - video.onseeking = e -> { - if (skipSetTime) { - skipSetTime = false; - return; + }); + } + + public function onPause():Void { + if (!main.isLeader()) return; + main.send({ + type: Pause, pause: { + time: getTime() } - if (!main.isLeader()) return; - main.send({ - type: SetTime, - setTime: { - time: video.currentTime - } - }); - } - video.onpause = e -> { - if (!main.isLeader()) return; - main.send({ - type: Pause, - pause: { - time: video.currentTime - } - }); - } - video.onplay = e -> { - if (!main.isLeader()) return; - main.send({ - type: Play, - play: { - time: video.currentTime - } - }); + }); + } + + public function onSetTime():Void { + if (skipSetTime) { + skipSetTime = false; + return; } - player.textContent = ""; - player.appendChild(video); - ge("#currenttitle").textContent = item.title; + if (!main.isLeader()) return; + main.send({ + type: SetTime, setTime: { + time: getTime() + } + }); } public function addVideoItem(item:VideoItem, atEnd:Bool):Void { - items.push(item); final itemEl = nodeFromString( - '<li class="queue_entry pluid-0 queue_temp queue_active" title="${Lang.get("addedBy")}: ${item.author}"> + '<li class="queue_entry pluid-0" title="${Lang.get("addedBy")}: ${item.author}"> <a class="qe_title" href="${item.url}" target="_blank">${item.title.htmlEscape()}</a> <span class="qe_time">${duration(item.duration)}</span> <div class="qe_clear"></div> @@ -103,6 +119,7 @@ class Player { </div> </li>' ); + if (item.isTemp) itemEl.classList.add("queue_temp"); final deleteBtn = itemEl.querySelector("#btn-delete"); deleteBtn.onclick = e -> { main.send({ @@ -112,18 +129,13 @@ class Player { } }); } + if (atEnd) items.push(item); + else items.insert(itemPos + 1, item); if (atEnd) videoItemsEl.appendChild(itemEl); - else Utils.insertAtIndex(videoItemsEl, itemEl, 1); + else Utils.insertAtIndex(videoItemsEl, itemEl, itemPos + 1); updateCounters(); } - public function removeVideo():Void { - if (video == null) return; - player.removeChild(video); - video = null; - ge("#currenttitle").textContent = Lang.get("nothingPlaying"); - } - public function removeItem(url:String):Void { for (child in videoItemsEl.children) { if (child.querySelector(".qe_title").getAttribute("href") == url) { @@ -132,15 +144,32 @@ class Player { } } - items.remove( - items.find(item -> item.url == url) - ); + final item = items.find(item -> item.url == url); + if (item == null) return; + var index = items.indexOf(item); + items.remove(item); updateCounters(); - if (video == null) return; - if (video.src == url) { - if (items.length > 0) setVideo(items[0]); + if (index < itemPos) { + itemPos--; + return; + } + if (index != itemPos) return; + if (items.length == 0) return; + if (items[index] == null) index = 0; + setVideo(index); + } + + public function skipItem(url:String):Void { + final item = items.find(item -> item.url == url); + if (item == null) return; + if (item.isTemp) { + removeItem(url); + return; } + var index = items.indexOf(item) + 1; + if (index >= items.length) index = 0; + setVideo(index); } function updateCounters():Void { @@ -152,15 +181,12 @@ class Player { return items; } - public function setItems(list:Array<VideoItem>):Void { + public function setItems(list:Array<VideoItem>, ?pos:Int):Void { clearItems(); + if (pos != null) itemPos = pos; if (list.length == 0) return; - if (video == null || video.src != list[0].url) { - setVideo(list[0]); - } - for (video in list) { - addVideoItem(video, true); - } + for (video in list) addVideoItem(video, true); + if (currentSrc != items[itemPos].url) setVideo(itemPos); } public function clearItems():Void { @@ -172,7 +198,7 @@ class Player { public function refresh():Void { if (items.length == 0) return; removeVideo(); - setVideo(items[0]); + setVideo(itemPos); } function duration(time:Float):String { @@ -203,29 +229,37 @@ class Player { return items.length == 0; } - public function hasVideo():Bool { - return video != null; + public function itemsLength():Int { + return items.length; } - public function pause():Void { - if (video == null) return; - video.pause(); + public function getItemPos():Int { + return itemPos; + } + + public function hasVideo():Bool { + return player != null; } public function play():Void { - if (video == null) return; - video.play(); + if (player == null) return; + player.play(); } - public function setTime(time:Float, isLocal = true):Void { - if (video == null) return; - skipSetTime = isLocal; - video.currentTime = time; + public function pause():Void { + if (player == null) return; + player.pause(); } public function getTime():Float { - if (video == null) return 0; - return video.currentTime; + if (player == null) return 0; + return player.getTime(); + } + + public function setTime(time:Float, isLocal = true):Void { + if (player == null) return; + skipSetTime = isLocal; + player.setTime(time); } } diff --git a/src/client/players/Raw.hx b/src/client/players/Raw.hx new file mode 100644 index 0000000..41b421c --- /dev/null +++ b/src/client/players/Raw.hx @@ -0,0 +1,72 @@ +package client.players; + +import haxe.Timer; +import js.html.Element; +import js.html.VideoElement; +import js.Browser.document; +import client.Main.ge; +import Types.VideoItem; + +class Raw implements IPlayer { + + final main:Main; + final player:Player; + var video:VideoElement; + final playerEl:Element = ge("#ytapiplayer"); + + public function new(main:Main, player:Player) { + this.main = main; + this.player = player; + } + + public function loadVideo(item:VideoItem):Void { + video = document.createVideoElement(); + video.id = "videoplayer"; + final url = main.tryLocalIp(item.url); + video.src = url; + video.controls = true; + final isTouch = untyped __js__("'ontouchstart' in window"); + if (!isTouch) Timer.delay(() -> { + video.controls = false; + video.onmouseover = e -> { + video.controls = true; + video.onmouseover = null; + video.onmousemove = null; + } + video.onmousemove = video.onmouseover; + }, 3000); + video.oncanplaythrough = player.onCanBePlayed; + video.onseeking = player.onSetTime; + video.onplay = player.onPlay; + video.onpause = player.onPause; + playerEl.appendChild(video); + video.pause(); + } + + public function removeVideo():Void { + if (video == null) return; + playerEl.removeChild(video); + video = null; + } + + public function play():Void { + if (video == null) return; + video.play(); + } + + public function pause():Void { + if (video == null) return; + video.pause(); + } + + public function getTime():Float { + if (video == null) return 0; + return video.currentTime; + } + + public function setTime(time:Float):Void { + if (video == null) return; + video.currentTime = time; + } + +} diff --git a/src/server/Main.hx b/src/server/Main.hx index 5d82ef1..e7895ca 100644 --- a/src/server/Main.hx +++ b/src/server/Main.hx @@ -31,6 +31,7 @@ class Main { final videoList:Array<VideoItem> = []; final videoTimer = new VideoTimer(); final messages:Array<Message> = []; + var itemPos = 0; static function main():Void new Main(); @@ -99,6 +100,7 @@ class Main { trace("Saving state..."); final data:ServerState = { videoList: videoList, + itemPos: itemPos, messages: messages, timer: { time: videoTimer.getTime(), @@ -116,6 +118,7 @@ class Main { videoList.resize(0); messages.resize(0); for (item in data.videoList) videoList.push(item); + itemPos = data.itemPos; for (message in data.messages) messages.push(message); videoTimer.start(); videoTimer.setTime(data.timer.time); @@ -166,6 +169,7 @@ class Main { for (client in clients) client.getData() ], videoList: videoList, + itemPos: itemPos, globalIp: globalIp } }); @@ -244,13 +248,17 @@ class Main { case AddVideo: final item = data.addVideo.item; + final local = '$localIp:$port'; + if (item.url.contains(local)) { + item.url = item.url.replace(local, '$globalIp:$port'); + } item.author = client.name; - final localOrigin = '$localIp:$port'; - if (item.url.indexOf(localOrigin) != -1) { - item.url = item.url.replace(localOrigin, '$globalIp:$port'); + if (videoList.exists(i -> i.url == item.url)) { + // TODO send server message + return; } if (data.addVideo.atEnd) videoList.push(item); - else videoList.insert(1, item); + else videoList.insert(itemPos + 1, item); broadcast(data); // Initial timer start if VideoLoaded is not happen if (videoList.length == 1) restartWaitTimer(); @@ -262,12 +270,30 @@ class Main { case RemoveVideo: if (videoList.length == 0) return; final url = data.removeVideo.url; - final isFirst = videoList[0].url == url; - if (isFirst) videoTimer.stop(); - videoList.remove( - videoList.find(item -> item.url == url) - ); - if (isFirst && videoList.length > 0) restartWaitTimer(); + final item = videoList.find(item -> item.url == url); + if (item == null) return; + final index = videoList.indexOf(item); + final isCurrent = videoList[itemPos].url == url; + if (index < itemPos) itemPos--; + videoList.remove(item); + if (isCurrent) { + if (itemPos >= videoList.length) itemPos = 0; + videoTimer.stop(); + if (videoList.length > 0) restartWaitTimer(); + } + broadcast(data); + + case SkipVideo: + if (videoList.length == 0) return; + final item = videoList[itemPos]; + if (item.url != data.skipVideo.url) return; + + if (!item.isTemp) itemPos++; + else videoList.remove(item); + if (itemPos >= videoList.length) itemPos = 0; + + videoTimer.stop(); + if (videoList.length > 0) restartWaitTimer(); broadcast(data); case Pause: @@ -284,12 +310,12 @@ class Main { case GetTime: if (videoList.length == 0) return; - if (videoTimer.getTime() > videoList[0].duration) { + if (videoTimer.getTime() > videoList[itemPos].duration) { videoTimer.stop(); onMessage(client, { - type: RemoveVideo, - removeVideo: { - url: videoList[0].url + type: SkipVideo, + skipVideo: { + url: videoList[itemPos].url } }); return; @@ -338,14 +364,18 @@ class Main { case ClearPlaylist: videoTimer.stop(); videoList.resize(0); + itemPos = 0; broadcast(data); case ShufflePlaylist: if (videoList.length == 0) return; - final first = videoList.shift(); + final current = videoList[itemPos]; + videoList.remove(current); Utils.shuffle(videoList); - videoList.unshift(first); - broadcast({type: UpdatePlaylist, updatePlaylist: { + videoList.insert(itemPos, current); + broadcast({ + type: UpdatePlaylist, + updatePlaylist: { videoList: videoList }}); case UpdatePlaylist: // client-only diff --git a/src/server/ServerState.hx b/src/server/ServerState.hx index 06240e5..02cdc40 100644 --- a/src/server/ServerState.hx +++ b/src/server/ServerState.hx @@ -5,6 +5,7 @@ import Types.VideoItem; typedef ServerState = { videoList:Array<VideoItem>, + itemPos:Int, messages:Array<Message>, timer:{ time:Float, |
