aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--build/client.js277
-rw-r--r--build/server.js58
-rw-r--r--res/css/des.css3
-rw-r--r--res/css/mobile-view.css3
-rw-r--r--res/langs/en.json2
-rw-r--r--res/langs/ru.json2
-rw-r--r--src/Types.hx9
-rw-r--r--src/client/Buttons.hx42
-rw-r--r--src/client/Main.hx80
-rw-r--r--src/client/MobileView.hx23
-rw-r--r--src/client/Player.hx59
-rw-r--r--src/client/Utils.hx63
-rw-r--r--src/server/Main.hx42
-rw-r--r--src/server/Utils.hx11
14 files changed, 519 insertions, 155 deletions
diff --git a/build/client.js b/build/client.js
index 8d796a3..4b9954b 100644
--- a/build/client.js
+++ b/build/client.js
@@ -359,7 +359,40 @@ client_Buttons.init = function(main) {
extendPlayer.classList.toggle("active");
return window.dispatchEvent(new Event("resize"));
};
- window.document.querySelector("#showmediaurl").onclick = function(e5) {
+ window.document.querySelector("#mediarefresh").onclick = function(e5) {
+ main.refreshPlayer();
+ return;
+ };
+ window.document.querySelector("#fullscreenbtn").onclick = function(e6) {
+ return client_Utils.toggleFullScreen(window.document.querySelector("#ytapiplayer"));
+ };
+ var getPlaylist = window.document.querySelector("#getplaylist");
+ getPlaylist.onclick = function(e7) {
+ client_Utils.copyToClipboard(main.getPlaylistLinks().join(","));
+ var icon = getPlaylist.firstElementChild;
+ icon.classList.remove("glyphicon-link");
+ icon.classList.add("glyphicon-ok");
+ return haxe_Timer.delay(function() {
+ icon.classList.add("glyphicon-link");
+ icon.classList.remove("glyphicon-ok");
+ return;
+ },2000);
+ };
+ window.document.querySelector("#clearplaylist").onclick = function(e8) {
+ if(!window.confirm(Lang.get("clearPlaylistConfirm"))) {
+ return;
+ }
+ main.send({ type : "ClearPlaylist"});
+ return;
+ };
+ window.document.querySelector("#shuffleplaylist").onclick = function(e9) {
+ if(!window.confirm(Lang.get("shufflePlaylistConfirm"))) {
+ return;
+ }
+ main.send({ type : "ShufflePlaylist"});
+ return;
+ };
+ window.document.querySelector("#showmediaurl").onclick = function(e10) {
window.document.querySelector("#showmediaurl").classList.toggle("collapsed");
window.document.querySelector("#showmediaurl").classList.toggle("active");
return window.document.querySelector("#addfromurl").classList.toggle("collapse");
@@ -497,23 +530,49 @@ client_Main.prototype = {
return;
};
window.document.querySelector("#queue_next").onclick = function(e1) {
- _gthis.addVideoUrl();
+ _gthis.addVideoUrl(false);
return;
};
window.document.querySelector("#queue_end").onclick = function(e2) {
- _gthis.addVideoUrl();
+ _gthis.addVideoUrl(true);
return;
};
window.document.querySelector("#mediaurl").onkeydown = function(e3) {
if(e3.keyCode == 13) {
- _gthis.addVideoUrl();
+ _gthis.addVideoUrl(true);
}
};
}
- ,addVideoUrl: function() {
+ ,addVideoUrl: function(atEnd) {
var _gthis = this;
var mediaUrl = window.document.querySelector("#mediaurl");
var url = mediaUrl.value;
+ if(url.length == 0) {
+ return;
+ }
+ 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;
+ });
+ }
+ ,addVideoArray: function(links,atEnd) {
+ var _gthis = this;
+ if(links.length == 0) {
+ return;
+ }
+ this.addVideo(atEnd ? links.shift() : links.pop(),atEnd,function() {
+ _gthis.addVideoArray(links,atEnd);
+ return;
+ });
+ }
+ ,addVideo: function(url,atEnd,callback) {
+ var _gthis = this;
+ if(!StringTools.startsWith(url,"http")) {
+ url = "" + window.location.protocol + "//" + url;
+ }
var pos = url.lastIndexOf("/") + 1;
var name = HxOverrides.substr(url,pos,null);
var matchName = new EReg("^(.+)\\.","");
@@ -522,11 +581,21 @@ client_Main.prototype = {
} else {
name = Lang.get("rawVideo");
}
- this.getRemoteVideoDuration(mediaUrl.value,function(duration) {
- _gthis.send({ type : "AddVideo", addVideo : { item : { url : url, title : name, author : _gthis.personal.name, duration : duration}}});
+ this.getRemoteVideoDuration(url,function(duration) {
+ _gthis.send({ type : "AddVideo", addVideo : { item : { url : url, title : name, author : _gthis.personal.name, duration : duration}, atEnd : atEnd}});
+ callback();
return;
});
- mediaUrl.value = "";
+ }
+ ,refreshPlayer: function() {
+ this.player.refresh();
+ }
+ ,getPlaylistLinks: function() {
+ var items = this.player.getItems();
+ var _g = [];
+ var _g1 = 0;
+ while(_g1 < items.length) _g.push(items[_g1++].url);
+ return _g;
}
,getRemoteVideoDuration: function(src,callback) {
var player = window.document.querySelector("#ytapiplayer");
@@ -537,36 +606,38 @@ client_Main.prototype = {
return;
};
video.onloadedmetadata = function() {
- player.removeChild(video);
+ if(player.contains(video)) {
+ player.removeChild(video);
+ }
callback(video.duration);
return;
};
- this.prepend(player,video);
- }
- ,prepend: function(parent,child) {
- if(parent.firstChild == null) {
- parent.appendChild(child);
- } else {
- parent.insertBefore(child,parent.firstChild);
- }
+ client_Utils.prepend(player,video);
}
,onMessage: function(e) {
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 : 149, className : "client.Main", methodName : "onMessage", customParams : [data[t1]]});
+ haxe_Log.trace("Event: " + data.type,{ fileName : "src/client/Main.hx", lineNumber : 171, 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);
+ this.player.addVideoItem(data.addVideo.item,data.addVideo.atEnd);
break;
case "ClearChat":
window.document.querySelector("#messagebuffer").innerHTML = "";
break;
+ case "ClearPlaylist":
+ this.player.clearItems();
+ if(this.player.isListEmpty()) {
+ this.player.pause();
+ }
+ break;
case "Connected":
this.onConnected(data);
+ this.onTimeGet.run();
break;
case "GetTime":
var newTime = data.getTime.time;
@@ -578,15 +649,15 @@ client_Main.prototype = {
this.player.setTime(time,false);
return;
}
- if(Math.abs(time - newTime) < 2) {
- return;
- }
- this.player.setTime(newTime);
if(!data.getTime.paused) {
this.player.play();
} else {
this.player.pause();
}
+ if(Math.abs(time - newTime) < 2) {
+ return;
+ }
+ this.player.setTime(newTime);
break;
case "Login":
this.onLogin(data.login.clients,data.login.clientName);
@@ -642,10 +713,15 @@ client_Main.prototype = {
}
this.player.setTime(newTime1);
break;
+ case "ShufflePlaylist":
+ break;
case "UpdateClients":
this.updateClients(data.updateClients.clients);
this.personal = ClientTools.getByName(this.clients,this.personal.name,this.personal);
break;
+ case "UpdatePlaylist":
+ this.player.setItems(data.updatePlaylist.videoList);
+ break;
case "VideoLoaded":
this.player.setTime(0);
this.player.play();
@@ -673,14 +749,7 @@ client_Main.prototype = {
++_g;
this.addMessage(message.name,message.text,message.time);
}
- var list = connected.videoList;
- if(list.length == 0) {
- return;
- }
- this.player.setVideo(list[0]);
- var _g2 = 0;
- var _g3 = connected.videoList;
- while(_g2 < _g3.length) this.player.addVideoItem(_g3[_g2++]);
+ this.player.setItems(connected.videoList);
}
,setConfig: function(config) {
this.config = config;
@@ -852,6 +921,11 @@ client_Main.prototype = {
}
}
,handleCommands: function(text) {
+ if(text == "clear") {
+ if((this.personal.group & 4) != 0) {
+ this.send({ type : "ClearChat"});
+ }
+ }
if(this.matchNumbers.match(text)) {
this.send({ type : "Rewind", rewind : { time : Std.parseInt(text)}});
}
@@ -874,7 +948,7 @@ client_MobileView.__name__ = true;
client_MobileView.init = function() {
var mvbtn = window.document.querySelector("#mv_btn");
mvbtn.onclick = function(e) {
- if(client_MobileView.toggleFullScreen()) {
+ if(client_Utils.toggleFullScreen(window.document.documentElement)) {
window.document.body.classList.add("mobile-view");
mvbtn.classList.add("active");
var vwrap = window.document.querySelector("#videowrap");
@@ -892,31 +966,6 @@ client_MobileView.init = function() {
return;
};
};
-client_MobileView.toggleFullScreen = function() {
- var state = true;
- var doc = window.document;
- if(window.document.fullscreenElement == null && doc.mozFullScreenElement == null && doc.webkitFullscreenElement == null) {
- if(window.document.documentElement.requestFullscreen != null) {
- window.document.documentElement.requestFullscreen();
- } else if(doc.documentElement.mozRequestFullScreen != null) {
- doc.documentElement.mozRequestFullScreen();
- } else if(doc.documentElement.webkitRequestFullscreen != null) {
- doc.documentElement.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
- } else {
- state = false;
- }
- } else {
- if(doc.cancelFullScreen != null) {
- doc.cancelFullScreen();
- } else if(doc.mozCancelFullScreen != null) {
- doc.mozCancelFullScreen();
- } else if(doc.webkitCancelFullScreen != null) {
- doc.webkitCancelFullScreen();
- }
- state = false;
- }
- return state;
-};
var client_Player = function(main) {
this.skipSetTime = false;
this.isLoaded = false;
@@ -969,7 +1018,7 @@ client_Player.prototype = {
this.player.appendChild(this.video);
window.document.querySelector("#currenttitle").innerHTML = item.title;
}
- ,addVideoItem: function(item) {
+ ,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\">" + 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>");
@@ -977,21 +1026,29 @@ client_Player.prototype = {
_gthis.main.send({ type : "RemoveVideo", removeVideo : { url : itemEl.querySelector(".qe_title").getAttribute("href")}});
return;
};
- this.videoItemsEl.appendChild(itemEl);
- var tmp = "" + this.items.length + " ";
- var tmp1 = Lang.get("videos");
- window.document.querySelector("#plcount").innerHTML = tmp + tmp1;
- window.document.querySelector("#pllength").innerHTML = this.totalDuration();
+ if(atEnd) {
+ this.videoItemsEl.appendChild(itemEl);
+ } else {
+ client_Utils.insertAtIndex(this.videoItemsEl,itemEl,1);
+ }
+ this.updateCounters();
+ }
+ ,removeVideo: function() {
+ if(this.video == null) {
+ return;
+ }
+ this.player.removeChild(this.video);
+ this.video = null;
+ window.document.querySelector("#currenttitle").innerHTML = Lang.get("nothingPlaying");
}
,removeItem: function(url) {
- var list = window.document.querySelector("#queue");
var _g = 0;
- var _g1 = list.children;
+ var _g1 = this.videoItemsEl.children;
while(_g < _g1.length) {
var child = _g1[_g];
++_g;
if(child.querySelector(".qe_title").getAttribute("href") == url) {
- list.removeChild(child);
+ this.videoItemsEl.removeChild(child);
break;
}
}
@@ -1003,14 +1060,43 @@ client_Player.prototype = {
this.setVideo(this.items[0]);
}
}
+ this.updateCounters();
+ }
+ ,updateCounters: function() {
var tmp = "" + this.items.length + " ";
var tmp1 = Lang.get("videos");
window.document.querySelector("#plcount").innerHTML = tmp + tmp1;
window.document.querySelector("#pllength").innerHTML = this.totalDuration();
}
+ ,getItems: function() {
+ return this.items;
+ }
+ ,setItems: function(list) {
+ this.clearItems();
+ 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);
+ }
+ ,clearItems: function() {
+ this.items.length = 0;
+ this.videoItemsEl.innerHTML = "";
+ this.updateCounters();
+ }
+ ,refresh: function() {
+ if(this.items.length == 0) {
+ return;
+ }
+ this.removeVideo();
+ this.setVideo(this.items[0]);
+ }
,duration: function(time) {
var h = time / 60 / 60 | 0;
- var m = time / 60 | 0;
+ var m = (time / 60 | 0) - h * 60;
var s = time % 60 | 0;
var time1 = "" + m + ":";
if(m < 10) {
@@ -1069,6 +1155,63 @@ client_Player.prototype = {
return this.video.currentTime;
}
};
+var client_Utils = function() { };
+client_Utils.__name__ = true;
+client_Utils.prepend = function(parent,child) {
+ if(parent.firstChild == null) {
+ parent.appendChild(child);
+ } else {
+ parent.insertBefore(child,parent.firstChild);
+ }
+};
+client_Utils.insertAtIndex = function(parent,child,i) {
+ if(i >= parent.children.length) {
+ parent.appendChild(child);
+ } else {
+ parent.insertBefore(child,parent.children[i]);
+ }
+};
+client_Utils.toggleFullScreen = function(el) {
+ var state = true;
+ var doc = window.document;
+ var el2 = el;
+ if(window.document.fullscreenElement == null && doc.mozFullScreenElement == null && doc.webkitFullscreenElement == null) {
+ if(el.requestFullscreen != null) {
+ el.requestFullscreen();
+ } else if(el2.mozRequestFullScreen != null) {
+ el2.mozRequestFullScreen();
+ } else if(el2.webkitRequestFullscreen != null) {
+ el2.webkitRequestFullscreen(HTMLElement.ALLOW_KEYBOARD_INPUT);
+ } else {
+ state = false;
+ }
+ } else {
+ if(doc.cancelFullScreen != null) {
+ doc.cancelFullScreen();
+ } else if(doc.mozCancelFullScreen != null) {
+ doc.mozCancelFullScreen();
+ } else if(doc.webkitCancelFullScreen != null) {
+ doc.webkitCancelFullScreen();
+ }
+ state = false;
+ }
+ return state;
+};
+client_Utils.copyToClipboard = function(text) {
+ var clipboardData = window.clipboardData;
+ if(clipboardData != null && clipboardData.setData != null) {
+ clipboardData.setData("Text",text);
+ return;
+ } else if(window.document.queryCommandSupported != null) {
+ var textarea = window.document.createElement("textarea");
+ textarea.textContent = text;
+ textarea.style.position = "fixed";
+ window.document.body.appendChild(textarea);
+ textarea.select();
+ window.document.execCommand("copy");
+ window.document.body.removeChild(textarea);
+ }
+};
var haxe_Log = function() { };
haxe_Log.__name__ = true;
haxe_Log.formatOutput = function(v,infos) {
diff --git a/build/server.js b/build/server.js
index 611ca9c..68ad69d 100644
--- a/build/server.js
+++ b/build/server.js
@@ -292,6 +292,13 @@ Std.__name__ = true;
Std.string = function(s) {
return js_Boot.__string_rec(s,"");
};
+Std.random = function(x) {
+ if(x <= 0) {
+ return 0;
+ } else {
+ return Math.floor(Math.random() * x);
+ }
+};
var StringTools = function() { };
StringTools.__name__ = true;
StringTools.startsWith = function(s,start) {
@@ -652,7 +659,7 @@ server_Main.prototype = {
client.group |= 4;
}
this.clients.push(client);
- if(this.clients.length == 1) {
+ if(this.clients.length == 1 && this.videoList.length > 0) {
if(this.videoTimer.isPaused()) {
this.videoTimer.play();
}
@@ -682,6 +689,9 @@ server_Main.prototype = {
}
}
if(_gthis.clients.length == 0) {
+ if(_gthis.waitVideoStart != null) {
+ _gthis.waitVideoStart.stop();
+ }
_gthis.videoTimer.pause();
}
return;
@@ -702,10 +712,14 @@ server_Main.prototype = {
,onMessage: function(client,data) {
switch(data.type) {
case "AddVideo":
- this.videoList.push(data.addVideo.item);
+ if(data.addVideo.atEnd) {
+ this.videoList.push(data.addVideo.item);
+ } else {
+ this.videoList.splice(1,0,data.addVideo.item);
+ }
this.broadcast(data);
if(this.videoList.length == 1) {
- this.waitVideoStart = haxe_Timer.delay($bind(this,this.startVideoPlayback),3000);
+ this.restartWaitTimer();
}
break;
case "ClearChat":
@@ -713,6 +727,11 @@ server_Main.prototype = {
this.broadcast(data);
}
break;
+ case "ClearPlaylist":
+ this.videoTimer.stop();
+ this.videoList.length = 0;
+ this.broadcast(data);
+ break;
case "Connected":
break;
case "GetTime":
@@ -793,6 +812,9 @@ server_Main.prototype = {
return item.url == url;
}));
this.broadcast(data);
+ if(this.videoList.length > 0) {
+ this.restartWaitTimer();
+ }
break;
case "Rewind":
if(this.videoList.length == 0) {
@@ -828,9 +850,20 @@ server_Main.prototype = {
this.videoTimer.setTime(data.setTime.time);
this.broadcastExcept(client,data);
break;
+ case "ShufflePlaylist":
+ if(this.videoList.length == 0) {
+ return;
+ }
+ var first = this.videoList.shift();
+ server_Utils.shuffle(this.videoList);
+ this.videoList.unshift(first);
+ this.broadcast({ type : "UpdatePlaylist", updatePlaylist : { videoList : this.videoList}});
+ break;
case "UpdateClients":
this.sendClientList();
break;
+ case "UpdatePlaylist":
+ break;
case "VideoLoaded":
this.prepareVideoPlayback();
break;
@@ -868,13 +901,19 @@ server_Main.prototype = {
client.ws.send(json,null);
}
}
+ ,restartWaitTimer: function() {
+ if(this.waitVideoStart != null) {
+ this.waitVideoStart.stop();
+ }
+ this.waitVideoStart = haxe_Timer.delay($bind(this,this.startVideoPlayback),3000);
+ }
,prepareVideoPlayback: function() {
if(this.videoTimer.isStarted) {
return;
}
this.loadedClientsCount++;
if(this.loadedClientsCount == 1) {
- this.waitVideoStart = haxe_Timer.delay($bind(this,this.startVideoPlayback),3000);
+ this.restartWaitTimer();
}
if(this.loadedClientsCount >= this.clients.length) {
this.startVideoPlayback();
@@ -915,6 +954,17 @@ server_Utils.getLocalIp = function() {
}
return "127.0.0.1";
};
+server_Utils.shuffle = function(arr) {
+ var _g = 0;
+ var _g1 = arr.length;
+ while(_g < _g1) {
+ var i = _g++;
+ var n = Std.random(arr.length);
+ var a = arr[i];
+ arr[i] = arr[n];
+ arr[n] = a;
+ }
+};
var server_VideoTimer = function() {
this.pauseStartTime = 0.0;
this.startTime = 0.0;
diff --git a/res/css/des.css b/res/css/des.css
index e1f960b..e3f81bd 100644
--- a/res/css/des.css
+++ b/res/css/des.css
@@ -185,6 +185,9 @@ src:url('https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.2/fonts/gl
.glyphicon-link:before {
content:"\e144"
}
+.glyphicon-ok:before {
+ content:"\e013"
+}
.glyphicon-sort:before {
content:"\e150"
}
diff --git a/res/css/mobile-view.css b/res/css/mobile-view.css
index a23b963..596dcde 100644
--- a/res/css/mobile-view.css
+++ b/res/css/mobile-view.css
@@ -54,9 +54,6 @@
padding-bottom: 0;
margin-bottom: 0;
}
-.mobile-view #footer {
- display: none;
-}
@media (max-width:799px) {
.navbar {
diff --git a/res/langs/en.json b/res/langs/en.json
index 87f7d84..5fa5d53 100644
--- a/res/langs/en.json
+++ b/res/langs/en.json
@@ -54,6 +54,8 @@
"acceptableEmbedCodesAre": "Acceptable embed codes are",
"customEmbedsCannotBeSynchronized": "CUSTOM EMBEDS CANNOT BE SYNCHRONIZED",
"save": "Save",
+ "clearPlaylistConfirm": "Are you sure you want to clear the playlist?",
+ "shufflePlaylistConfirm": "Are you sure you want to shuffle the playlist?",
"yes": "Yes",
"no": "No",
diff --git a/res/langs/ru.json b/res/langs/ru.json
index e13a6d5..f9944d1 100644
--- a/res/langs/ru.json
+++ b/res/langs/ru.json
@@ -54,6 +54,8 @@
"acceptableEmbedCodesAre": "Можно добавить видео с тегами",
"customEmbedsCannotBeSynchronized": "СИНХРОНИЗАЦИЯ БУДЕТ НЕДОСТУПНА",
"save": "Сохранить",
+ "clearPlaylistConfirm": "Вы уверены что хотите очистить плейлист?",
+ "shufflePlaylistConfirm": "Вы уверены что хотите перемешать плейлист?",
"yes": "Да",
"no": "Нет",
diff --git a/src/Types.hx b/src/Types.hx
index 643fd4f..f2b506f 100644
--- a/src/Types.hx
+++ b/src/Types.hx
@@ -66,7 +66,8 @@ typedef WsEvent = {
clients:Array<ClientData>,
},
?addVideo:{
- item:VideoItem
+ item:VideoItem,
+ atEnd:Bool
},
?removeVideo:{
url:String
@@ -89,6 +90,9 @@ typedef WsEvent = {
},
?setLeader:{
clientName:String
+ },
+ ?updatePlaylist:{
+ videoList:Array<VideoItem>
}
}
@@ -111,4 +115,7 @@ enum abstract WsEventType(String) {
var Rewind;
var SetLeader;
var ClearChat;
+ var ClearPlaylist;
+ var ShufflePlaylist;
+ var UpdatePlaylist;
}
diff --git a/src/client/Buttons.hx b/src/client/Buttons.hx
index a5fbfa4..f72fbb3 100644
--- a/src/client/Buttons.hx
+++ b/src/client/Buttons.hx
@@ -1,8 +1,8 @@
package client;
+import haxe.Timer;
import js.html.KeyboardEvent;
import js.html.InputElement;
-import js.html.ButtonElement;
import js.html.Element;
import client.Main.ge;
import js.Browser.window;
@@ -77,16 +77,48 @@ class Buttons {
extendPlayer.onclick = e -> {
if (extendPlayer.classList.contains("active")) {
split.setSizes([40, 60]);
- ge('#userlist').style.width = "90px";
+ ge("#userlist").style.width = "90px";
} else {
split.setSizes([20, 80]);
- ge('#userlist').style.width = "80px";
+ ge("#userlist").style.width = "80px";
}
extendPlayer.classList.toggle("active");
- window.dispatchEvent(new Event('resize'));
+ window.dispatchEvent(new Event("resize"));
}
- final showMediaUrl:ButtonElement = cast ge("#showmediaurl");
+ final mediaRefresh = ge("#mediarefresh");
+ mediaRefresh.onclick = e -> {
+ main.refreshPlayer();
+ }
+ final fullscreenBtn = ge("#fullscreenbtn");
+ fullscreenBtn.onclick = e -> {
+ final el = ge("#ytapiplayer");
+ Utils.toggleFullScreen(el);
+ }
+ final getPlaylist = ge("#getplaylist");
+ getPlaylist.onclick = e -> {
+ final text = main.getPlaylistLinks().join(",");
+ Utils.copyToClipboard(text);
+ final icon = getPlaylist.firstElementChild;
+ icon.classList.remove("glyphicon-link");
+ icon.classList.add("glyphicon-ok");
+ Timer.delay(() -> {
+ icon.classList.add("glyphicon-link");
+ icon.classList.remove("glyphicon-ok");
+ }, 2000);
+ }
+ final clearPlaylist = ge("#clearplaylist");
+ clearPlaylist.onclick = e -> {
+ if (!window.confirm(Lang.get("clearPlaylistConfirm"))) return;
+ main.send({type: ClearPlaylist});
+ }
+ final shufflePlaylist = ge("#shuffleplaylist");
+ shufflePlaylist.onclick = e -> {
+ if (!window.confirm(Lang.get("shufflePlaylistConfirm"))) return;
+ main.send({type: ShufflePlaylist});
+ }
+
+ final showMediaUrl = ge("#showmediaurl");
showMediaUrl.onclick = e -> {
ge("#showmediaurl").classList.toggle("collapsed");
ge("#showmediaurl").classList.toggle("active");
diff --git a/src/client/Main.hx b/src/client/Main.hx
index dc5a803..96353f8 100644
--- a/src/client/Main.hx
+++ b/src/client/Main.hx
@@ -71,8 +71,7 @@ class Main {
Buttons.init(this);
MobileView.init();
- final leaderBtn = ge("#leader_btn");
- leaderBtn.onclick = (e) -> {
+ ge("#leader_btn").onclick = e -> {
// change button style before answer
setLeaderButton(!personal.isLeader);
final name = personal.isLeader ? "" : personal.name;
@@ -84,11 +83,10 @@ class Main {
});
}
- // TODO next/end
- ge("#queue_next").onclick = (e:MouseEvent) -> addVideoUrl();
- ge("#queue_end").onclick = (e:MouseEvent) -> addVideoUrl();
+ ge("#queue_next").onclick = (e:MouseEvent) -> addVideoUrl(false);
+ ge("#queue_end").onclick = (e:MouseEvent) -> addVideoUrl(true);
ge("#mediaurl").onkeydown = function(e:KeyboardEvent) {
- if (e.keyCode == 13) addVideoUrl();
+ if (e.keyCode == 13) addVideoUrl(true);
}
}
@@ -100,26 +98,55 @@ class Main {
return personal.isAdmin;
}
- function addVideoUrl():Void {
+ function addVideoUrl(atEnd:Bool):Void {
final mediaUrl:InputElement = cast ge("#mediaurl");
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));
+ }
+
+ function addVideoArray(links:Array<String>, atEnd:Bool):Void {
+ if (links.length == 0) return;
+ final link = atEnd ? links.shift() : links.pop();
+ addVideo(link, atEnd, () -> addVideoArray(links, atEnd));
+ }
+
+ function addVideo(url:String, atEnd:Bool, callback:()->Void):Void {
+ if (!url.startsWith("http")) url = '${Browser.location.protocol}//$url';
var name = url.substr(url.lastIndexOf('/') + 1);
final matchName = ~/^(.+)\./;
if (matchName.match(name)) name = matchName.matched(1);
else name = Lang.get("rawVideo");
- getRemoteVideoDuration(mediaUrl.value, (duration:Float) -> {
+ getRemoteVideoDuration(url, (duration:Float) -> {
send({
type: AddVideo, addVideo: {
item: {
url: url,
title: name,
author: personal.name,
- duration: duration
- }
- }});
+ duration: duration,
+ },
+ atEnd: atEnd
+ }});
+ callback();
});
- mediaUrl.value = "";
+ }
+
+ public function refreshPlayer():Void {
+ player.refresh();
+ }
+
+ public function getPlaylistLinks():Array<String> {
+ final items = player.getItems();
+ return [
+ for (item in items) item.url
+ ];
}
function getRemoteVideoDuration(src:String, callback:(duration:Float)->Void):Void {
@@ -131,15 +158,10 @@ class Main {
callback(0);
}
video.onloadedmetadata = () -> {
- player.removeChild(video);
+ if (player.contains(video)) player.removeChild(video);
callback(video.duration);
}
- prepend(player, video);
- }
-
- function prepend(parent:Element, child:Element):Void {
- if (parent.firstChild == null) parent.appendChild(child);
- else parent.insertBefore(child, parent.firstChild);
+ Utils.prepend(player, video);
}
function onMessage(e):Void {
@@ -150,6 +172,7 @@ class Main {
switch (data.type) {
case Connected:
onConnected(data);
+ onTimeGet.run();
case Login:
onLogin(data.login.clients, data.login.clientName);
case LoginError:
@@ -167,7 +190,7 @@ class Main {
addMessage(data.message.clientName, data.message.text);
case AddVideo:
if (player.isListEmpty()) player.setVideo(data.addVideo.item);
- player.addVideoItem(data.addVideo.item);
+ player.addVideoItem(data.addVideo.item, data.addVideo.atEnd);
case VideoLoaded:
player.setTime(0);
player.play();
@@ -192,10 +215,10 @@ class Main {
player.setTime(time, false);
return;
}
- if (Math.abs(time - newTime) < 2) return;
- player.setTime(newTime);
if (!data.getTime.paused) player.play();
else player.pause();
+ if (Math.abs(time - newTime) < 2) return;
+ player.setTime(newTime);
case SetTime:
final newTime = data.setTime.time;
final time = player.getTime();
@@ -210,6 +233,12 @@ class Main {
if (isLeader()) player.setTime(player.getTime(), false);
case ClearChat:
ge("#messagebuffer").innerHTML = "";
+ case ClearPlaylist:
+ player.clearItems();
+ if (player.isListEmpty()) player.pause();
+ case ShufflePlaylist: // server-only
+ case UpdatePlaylist:
+ player.setItems(data.updatePlaylist.videoList);
}
}
@@ -233,12 +262,7 @@ class Main {
for (message in connected.history) {
addMessage(message.name, message.text, message.time);
}
- final list = connected.videoList;
- if (list.length == 0) return;
- player.setVideo(list[0]);
- for (video in connected.videoList) {
- player.addVideoItem(video);
- }
+ player.setItems(connected.videoList);
}
function setConfig(config:Config):Void {
diff --git a/src/client/MobileView.hx b/src/client/MobileView.hx
index 976958b..ad788df 100644
--- a/src/client/MobileView.hx
+++ b/src/client/MobileView.hx
@@ -8,7 +8,7 @@ class MobileView {
public static function init():Void {
final mvbtn = ge("#mv_btn");
mvbtn.onclick = e -> {
- final mobileView = toggleFullScreen();
+ final mobileView = Utils.toggleFullScreen(document.documentElement);
if (mobileView) {
document.body.classList.add('mobile-view');
mvbtn.classList.add('active');
@@ -27,25 +27,4 @@ class MobileView {
}
}
- static function toggleFullScreen():Bool {
- var state = true;
- final doc:Dynamic = document;
- if (document.fullscreenElement == null &&
- doc.mozFullScreenElement == null &&
- doc.webkitFullscreenElement == null) {
- if (document.documentElement.requestFullscreen != null) {
- document.documentElement.requestFullscreen();
- } else if (doc.documentElement.mozRequestFullScreen != null) {
- doc.documentElement.mozRequestFullScreen();
- } else if (doc.documentElement.webkitRequestFullscreen != null) {
- doc.documentElement.webkitRequestFullscreen(untyped Element.ALLOW_KEYBOARD_INPUT);
- } else state = false;
- } else {
- if (doc.cancelFullScreen != null) doc.cancelFullScreen();
- else if (doc.mozCancelFullScreen != null) doc.mozCancelFullScreen();
- else if (doc.webkitCancelFullScreen != null) doc.webkitCancelFullScreen();
- state = false;
- }
- return state;
- }
}
diff --git a/src/client/Player.hx b/src/client/Player.hx
index 19e0c84..34ef716 100644
--- a/src/client/Player.hx
+++ b/src/client/Player.hx
@@ -1,7 +1,5 @@
package client;
-import js.html.LIElement;
-import js.html.UListElement;
import js.html.Element;
import js.html.VideoElement;
import js.Browser.document;
@@ -29,7 +27,7 @@ class Player {
video.id = "videoplayer";
video.src = item.url;
video.controls = true;
- video.oncanplaythrough = (e) -> {
+ video.oncanplaythrough = e -> {
if (!isLoaded) main.send({type: VideoLoaded});
isLoaded = true;
}
@@ -46,7 +44,7 @@ class Player {
}
});
}
- video.onpause = (e) -> {
+ video.onpause = e -> {
if (!main.isLeader()) return;
main.send({
type: Pause,
@@ -55,7 +53,7 @@ class Player {
}
});
}
- video.onplay = (e) -> {
+ video.onplay = e -> {
if (!main.isLeader()) return;
main.send({
type: Play,
@@ -69,9 +67,9 @@ class Player {
ge("#currenttitle").innerHTML = item.title;
}
- public function addVideoItem(item:VideoItem):Void {
+ public function addVideoItem(item:VideoItem, atEnd:Bool):Void {
items.push(item);
- final itemEl:LIElement = cast nodeFromString(
+ final itemEl = nodeFromString(
'<li class="queue_entry pluid-0 queue_temp queue_active" title="${Lang.get("addedBy")}: ${item.author}">
<a class="qe_title" href="${item.url}" target="_blank">${item.title}</a>
<span class="qe_time">${duration(item.duration)}</span>
@@ -93,7 +91,7 @@ class Player {
</li>'
);
final deleteBtn = itemEl.querySelector("#btn-delete");
- deleteBtn.onclick = (e) -> {
+ deleteBtn.onclick = e -> {
main.send({
type: RemoveVideo,
removeVideo: {
@@ -101,22 +99,22 @@ class Player {
}
});
}
- videoItemsEl.appendChild(itemEl);
- ge("#plcount").innerHTML = '${items.length} ${Lang.get("videos")}';
- ge("#pllength").innerHTML = totalDuration();
+ if (atEnd) videoItemsEl.appendChild(itemEl);
+ else Utils.insertAtIndex(videoItemsEl, itemEl, 1);
+ updateCounters();
}
public function removeVideo():Void {
+ if (video == null) return;
player.removeChild(video);
video = null;
ge("#currenttitle").innerHTML = Lang.get("nothingPlaying");
}
public function removeItem(url:String):Void {
- final list = ge("#queue");
- for (child in list.children) {
+ for (child in videoItemsEl.children) {
if (child.querySelector(".qe_title").getAttribute("href") == url) {
- list.removeChild(child);
+ videoItemsEl.removeChild(child);
break;
}
}
@@ -128,13 +126,44 @@ class Player {
if (video.src == url) {
if (items.length > 0) setVideo(items[0]);
}
+ updateCounters();
+ }
+
+ function updateCounters():Void {
ge("#plcount").innerHTML = '${items.length} ${Lang.get("videos")}';
ge("#pllength").innerHTML = totalDuration();
}
+ public function getItems():Array<VideoItem> {
+ return items;
+ }
+
+ public function setItems(list:Array<VideoItem>):Void {
+ clearItems();
+ if (list.length == 0) return;
+ if (video == null || video.src != list[0].url) {
+ setVideo(list[0]);
+ }
+ for (video in list) {
+ addVideoItem(video, true);
+ }
+ }
+
+ public function clearItems():Void {
+ items.resize(0);
+ videoItemsEl.innerHTML = "";
+ updateCounters();
+ }
+
+ public function refresh():Void {
+ if (items.length == 0) return;
+ removeVideo();
+ setVideo(items[0]);
+ }
+
function duration(time:Float):String {
final h = Std.int(time / 60 / 60);
- final m = Std.int(time / 60);
+ final m = Std.int(time / 60) - h * 60;
final s = Std.int(time % 60);
var time = '$m:';
if (m < 10) time = '0$time';
diff --git a/src/client/Utils.hx b/src/client/Utils.hx
new file mode 100644
index 0000000..37de672
--- /dev/null
+++ b/src/client/Utils.hx
@@ -0,0 +1,63 @@
+package client;
+
+import js.html.Element;
+import js.Browser.document;
+import js.Browser.window;
+
+class Utils {
+
+ public static function prepend(parent:Element, child:Element):Void {
+ if (parent.firstChild == null) parent.appendChild(child);
+ else parent.insertBefore(child, parent.firstChild);
+ }
+
+ public static function insertAtIndex(parent:Element, child:Element, i:Int) {
+ if (i >= parent.children.length) parent.appendChild(child);
+ else parent.insertBefore(child, parent.children[i]);
+ }
+
+ public static function toggleFullScreen(el:Element):Bool {
+ var state = true;
+ final doc:Dynamic = document;
+ final el2:Dynamic = el;
+ if (document.fullscreenElement == null &&
+ doc.mozFullScreenElement == null &&
+ doc.webkitFullscreenElement == null) {
+ if (el.requestFullscreen != null) {
+ el.requestFullscreen();
+ } else if (el2.mozRequestFullScreen != null) {
+ el2.mozRequestFullScreen();
+ } else if (el2.webkitRequestFullscreen != null) {
+ el2.webkitRequestFullscreen(untyped Element.ALLOW_KEYBOARD_INPUT);
+ } else state = false;
+ } else {
+ if (doc.cancelFullScreen != null) doc.cancelFullScreen();
+ else if (doc.mozCancelFullScreen != null) doc.mozCancelFullScreen();
+ else if (doc.webkitCancelFullScreen != null) doc.webkitCancelFullScreen();
+ state = false;
+ }
+ return state;
+ }
+
+ public static function copyToClipboard(text:String):Void {
+ final clipboardData = (window : Dynamic).clipboardData;
+ if (clipboardData != null && clipboardData.setData != null) {
+ // IE-specific code path to prevent textarea being shown while dialog is visible.
+ clipboardData.setData("Text", text);
+ return;
+ } else if ((document : Dynamic).queryCommandSupported != null) {
+ final textarea = document.createTextAreaElement();
+ textarea.textContent = text;
+ // Prevent scrolling to bottom of page in Microsoft Edge.
+ textarea.style.position = "fixed";
+ document.body.appendChild(textarea);
+ textarea.select();
+ try {
+ // Security exception may be thrown by some browsers.
+ document.execCommand("copy");
+ }
+ document.body.removeChild(textarea);
+ }
+ }
+
+}
diff --git a/src/server/Main.hx b/src/server/Main.hx
index cdf6924..e2b9b18 100644
--- a/src/server/Main.hx
+++ b/src/server/Main.hx
@@ -80,7 +80,7 @@ class Main {
final client = new Client(ws, id, name, 0);
if (isAdmin) client.group.set(Admin);
clients.push(client);
- if (clients.length == 1)
+ if (clients.length == 1 && videoList.length > 0)
if (videoTimer.isPaused()) videoTimer.play();
send(client, {
@@ -109,7 +109,10 @@ class Main {
if (client.isLeader) {
if (videoTimer.isPaused()) videoTimer.play();
}
- if (clients.length == 0) videoTimer.pause();
+ if (clients.length == 0) {
+ if (waitVideoStart != null) waitVideoStart.stop();
+ videoTimer.pause();
+ }
});
}
@@ -131,7 +134,8 @@ class Main {
sendClientList();
case Login:
final name = data.login.clientName;
- if (name.length == 0 || name.length > config.maxLoginLength || clients.getByName(name) != null) {
+ if (name.length == 0 || name.length > config.maxLoginLength
+ || clients.getByName(name) != null) {
send(client, {type: LoginError});
return;
}
@@ -172,12 +176,13 @@ class Main {
if (messages.length > config.serverChatHistory) messages.shift();
broadcast(data);
case AddVideo:
- videoList.push(data.addVideo.item);
+ if (data.addVideo.atEnd) videoList.push(data.addVideo.item);
+ else videoList.insert(1, data.addVideo.item);
broadcast(data);
- if (videoList.length == 1) {
- waitVideoStart = Timer.delay(startVideoPlayback, 3000);
- }
+ // Initial timer start if VideoLoaded is not happen
+ if (videoList.length == 1) restartWaitTimer();
case VideoLoaded:
+ // Called if client loads next video and can play it
prepareVideoPlayback();
case RemoveVideo:
if (videoList.length == 0) return;
@@ -187,6 +192,7 @@ class Main {
videoList.find(item -> item.url == url)
);
broadcast(data);
+ if (videoList.length > 0) restartWaitTimer();
case Pause:
if (videoList.length == 0) return;
if (!client.isLeader) return;
@@ -244,6 +250,19 @@ class Main {
}
case ClearChat:
if (client.isAdmin) broadcast(data);
+ case ClearPlaylist:
+ videoTimer.stop();
+ videoList.resize(0);
+ broadcast(data);
+ case ShufflePlaylist:
+ if (videoList.length == 0) return;
+ final first = videoList.shift();
+ Utils.shuffle(videoList);
+ videoList.unshift(first);
+ broadcast({type: UpdatePlaylist, updatePlaylist: {
+ videoList: videoList
+ }});
+ case UpdatePlaylist:
}
}
@@ -282,12 +301,15 @@ class Main {
var waitVideoStart:Timer;
var loadedClientsCount = 0;
+ function restartWaitTimer():Void {
+ if (waitVideoStart != null) waitVideoStart.stop();
+ waitVideoStart = Timer.delay(startVideoPlayback, 3000);
+ }
+
function prepareVideoPlayback():Void {
if (videoTimer.isStarted) return;
loadedClientsCount++;
- if (loadedClientsCount == 1) {
- waitVideoStart = Timer.delay(startVideoPlayback, 3000);
- }
+ if (loadedClientsCount == 1) restartWaitTimer();
if (loadedClientsCount >= clients.length) startVideoPlayback();
}
diff --git a/src/server/Utils.hx b/src/server/Utils.hx
index c3510c9..2ecbd42 100644
--- a/src/server/Utils.hx
+++ b/src/server/Utils.hx
@@ -27,4 +27,15 @@ class Utils {
}
return "127.0.0.1";
}
+
+ public static function shuffle<T>(arr:Array<T>):Void {
+ for (i in 0...arr.length) {
+ final n = Std.random(arr.length);
+ final a = arr[i];
+ final b = arr[n];
+ arr[i] = b;
+ arr[n] = a;
+ }
+ }
+
}
send patches to the email below
yukais@pinapelz.com
include the subject [PATCH repo_name]
pinapelz.com
homepage