aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorRblSb <msrblsb@gmail.com>2020-02-24 01:19:30 +0300
committerRblSb <msrblsb@gmail.com>2020-02-24 01:19:30 +0300
commitc561fb9e2e42e4968f2b48cd535f208e90f8c12c (patch)
tree0c7e1ffc99744aabbb240864b371b9555a611d92 /src
parent113b06e895f5dc752e8393c2a4f3f1669a7d0aab (diff)
More playlist and video control
Diffstat (limited to 'src')
-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
8 files changed, 248 insertions, 81 deletions
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