aboutsummaryrefslogtreecommitdiffstats
path: root/res
diff options
context:
space:
mode:
Diffstat (limited to 'res')
-rw-r--r--res/client.js1510
-rw-r--r--res/css/des.css385
-rw-r--r--res/index.html178
-rw-r--r--res/langs/en.json25
-rw-r--r--res/langs/ru.json23
5 files changed, 1672 insertions, 449 deletions
diff --git a/res/client.js b/res/client.js
index 89c5829..23ac4fe 100644
--- a/res/client.js
+++ b/res/client.js
@@ -1,4 +1,4 @@
-// Generated by Haxe 4.3.7
+// Generated by Haxe 4.3.3
(function ($hx_exports, $global) { "use strict";
$hx_exports["client"] = $hx_exports["client"] || {};
$hx_exports["client"]["JsApi"] = $hx_exports["client"]["JsApi"] || {};
@@ -68,11 +68,18 @@ ClientTools.setLeader = function(clients,name) {
}
};
ClientTools.hasLeader = function(clients) {
+ return ClientTools.getLeader(clients) != null;
+};
+ClientTools.getLeader = function(clients) {
var _g = 0;
- while(_g < clients.length) if((clients[_g++].group & 4) != 0) {
- return true;
+ while(_g < clients.length) {
+ var client = clients[_g];
+ ++_g;
+ if((client.group & 4) != 0) {
+ return client;
+ }
}
- return false;
+ return null;
};
ClientTools.getByName = function(clients,name,def) {
var _g = 0;
@@ -338,13 +345,6 @@ Reflect.fields = function(o) {
}
return a;
};
-Reflect.deleteField = function(o,field) {
- if(!Object.prototype.hasOwnProperty.call(o,field)) {
- return false;
- }
- delete(o[field]);
- return true;
-};
Reflect.copy = function(o) {
if(o == null) {
return null;
@@ -501,8 +501,14 @@ StringTools.hex = function(n,digits) {
};
var _$Types_VideoItemTools = function() { };
_$Types_VideoItemTools.__name__ = true;
+_$Types_VideoItemTools.isPlayable = function(item) {
+ if(item.isIncomplete == true && item.doCache) {
+ return false;
+ }
+ return true;
+};
_$Types_VideoItemTools.withUrl = function(item,url) {
- return { url : url, title : item.title, author : item.author, duration : item.duration, subs : item.subs, voiceOverTrack : item.voiceOverTrack, isTemp : item.isTemp, doCache : item.doCache, playerType : item.playerType};
+ return { url : url, title : item.title, author : item.author, duration : item.duration, subs : item.subs, voiceOverTrack : item.voiceOverTrack, isTemp : item.isTemp, doCache : item.doCache, isIncomplete : item.isIncomplete, playerType : item.playerType};
};
var VideoList = function() {
this.items = [];
@@ -571,7 +577,7 @@ client_Buttons.init = function(main) {
}
client_Buttons.split = new client_Split(client_Buttons.settings);
client_Buttons.split.setSize(client_Buttons.settings.chatSize);
- client_Buttons.initChatInputs(main);
+ client_Buttons.initInputs(main);
var _g = 0;
var _g1 = client_Buttons.settings.checkboxes;
while(_g < _g1.length) {
@@ -697,8 +703,12 @@ client_Buttons.init = function(main) {
haxe_Timer.delay(function() {
window.document.querySelector("#userlist-wrap").style.transition = "200ms";
},0);
+ var playlistMenuBtn = window.document.querySelector("#playlist-menu-btn");
+ var playlistMenuDropdown = window.document.querySelector("#playlist-menu-dropdown");
var toggleSynch = window.document.querySelector("#togglesynch");
toggleSynch.onclick = function(e) {
+ playlistMenuDropdown.style.display = "none";
+ playlistMenuBtn.classList.remove("active");
var icon = toggleSynch.firstElementChild;
if(main.isSyncActive) {
if(!window.confirm(Lang.get("toggleSynchConfirm"))) {
@@ -706,23 +716,29 @@ client_Buttons.init = function(main) {
}
main.isSyncActive = false;
icon.style.color = "rgba(238, 72, 67, 0.75)";
- icon.setAttribute("name","pause");
+ playlistMenuBtn.classList.add("danger");
} else {
main.isSyncActive = true;
icon.style.color = "";
- icon.setAttribute("name","play");
+ playlistMenuBtn.classList.remove("danger");
main.send({ type : "UpdatePlaylist"});
}
+ client_Buttons.updateToggleSynchBtn(main);
};
window.document.querySelector("#mediarefresh").onclick = function(e) {
main.refreshPlayer();
};
window.document.querySelector("#fullscreenbtn").onclick = function(e) {
- if((client_Utils.isTouch() || main.isVerbose()) && !client_Utils.hasFullscreen()) {
- window.scrollTo(0,0);
- return client_Utils.requestFullscreen(window.document.documentElement);
+ return client_Utils.toggleFullscreen(window.document.querySelector("#ytapiplayer"));
+ };
+ window.document.querySelector("#fullscreen-chat-btn").onclick = function(e) {
+ e.stopPropagation();
+ e.preventDefault();
+ client_Buttons.clearUnreadChatMessages();
+ if(client_Buttons.settings.pageFullscreen) {
+ client_Utils.switchToPageFullscreen();
} else {
- return client_Utils.requestFullscreen(window.document.querySelector("#ytapiplayer"));
+ client_Utils.cancelFullscreen();
}
};
client_Buttons.initPageFullscreen();
@@ -735,13 +751,41 @@ client_Buttons.init = function(main) {
icon.setAttribute("name","link");
},2000);
};
+ playlistMenuBtn.onclick = function(e) {
+ e.stopPropagation();
+ var isOpen = playlistMenuDropdown.style.display != "none";
+ if(isOpen) {
+ playlistMenuDropdown.style.display = "none";
+ playlistMenuBtn.classList.remove("active");
+ } else {
+ main.hasSeenSkipVote = true;
+ playlistMenuDropdown.style.display = "";
+ playlistMenuBtn.classList.add("active");
+ window.document.querySelector("#playlist-menu-indicator").style.display = "none";
+ }
+ };
+ window.document.addEventListener("click",function(e) {
+ var target = e.target;
+ if(!playlistMenuBtn.contains(target) && !playlistMenuDropdown.contains(target)) {
+ playlistMenuDropdown.style.display = "none";
+ playlistMenuBtn.classList.remove("active");
+ }
+ });
+ window.document.querySelector("#voteskip").addEventListener("click",function() {
+ playlistMenuDropdown.style.display = "none";
+ playlistMenuBtn.classList.remove("active");
+ });
window.document.querySelector("#clearplaylist").onclick = function(e) {
+ playlistMenuDropdown.style.display = "none";
+ playlistMenuBtn.classList.remove("active");
if(!window.confirm(Lang.get("clearPlaylistConfirm"))) {
return;
}
main.send({ type : "ClearPlaylist"});
};
window.document.querySelector("#shuffleplaylist").onclick = function(e) {
+ playlistMenuDropdown.style.display = "none";
+ playlistMenuBtn.classList.remove("active");
if(!window.confirm(Lang.get("shufflePlaylistConfirm"))) {
return;
}
@@ -760,6 +804,8 @@ client_Buttons.init = function(main) {
};
var showMediaUrl = window.document.querySelector("#showmediaurl");
showMediaUrl.onclick = function(e) {
+ playlistMenuDropdown.style.display = "none";
+ playlistMenuBtn.classList.remove("active");
if(client_Buttons.showPlayerGroup(showMediaUrl)) {
haxe_Timer.delay(function() {
window.document.querySelector("#addfromurl").scrollIntoView();
@@ -769,6 +815,8 @@ client_Buttons.init = function(main) {
};
var showCustomEmbed = window.document.querySelector("#showcustomembed");
showCustomEmbed.onclick = function(e) {
+ playlistMenuDropdown.style.display = "none";
+ playlistMenuBtn.classList.remove("active");
if(client_Buttons.showPlayerGroup(showCustomEmbed)) {
haxe_Timer.delay(function() {
window.document.querySelector("#customembed").scrollIntoView();
@@ -905,6 +953,39 @@ client_Buttons.initTextButtons = function(main) {
client_Buttons.updateHotkeysBtn();
};
client_Buttons.updateHotkeysBtn();
+ window.document.querySelector("#pageFullscreenBtn").onclick = function(e) {
+ client_Buttons.settings.pageFullscreen = !client_Buttons.settings.pageFullscreen;
+ client_Settings.write(client_Buttons.settings);
+ client_Buttons.updatePageFullscreenBtn();
+ if(client_Buttons.settings.pageFullscreen) {
+ client_Utils.switchToPageFullscreen();
+ } else {
+ client_Utils.cancelFullscreen();
+ }
+ };
+ client_Buttons.updatePageFullscreenBtn();
+ var fullscreenActionBtn = window.document.querySelector("#fullscreenActionBtn");
+ if(fullscreenActionBtn != null) {
+ fullscreenActionBtn.onclick = function(e) {
+ client_Buttons.settings.fullscreenAction = !client_Buttons.settings.fullscreenAction;
+ client_Settings.write(client_Buttons.settings);
+ client_Buttons.updateFullscreenActionBtn();
+ };
+ client_Buttons.updateFullscreenActionBtn();
+ }
+ window.document.body.addEventListener("click",function(e) {
+ if(!client_Buttons.settings.pageFullscreen) {
+ return;
+ }
+ if(client_Utils.hasFullscreen()) {
+ return;
+ }
+ var target = e.target;
+ if(target != null && target.closest("button, input, a, textarea, select, #optionsPanel, #chatline, #userlist, .queue_sortable, .nav") != null) {
+ return;
+ }
+ client_Utils.requestFullscreen(window.document.documentElement);
+ });
var removeBtn = window.document.querySelector("#removePlayerBtn");
removeBtn.onclick = function(e) {
if(main.toggleVideoElement()) {
@@ -926,6 +1007,7 @@ client_Buttons.initTextButtons = function(main) {
client_JsApi.setVideoSrc(url);
});
};
+ client_Buttons.updateToggleSynchBtn(main);
};
client_Buttons.initHotkeys = function(main,player) {
window.document.querySelector("#mediarefresh").title += " (Alt-R)";
@@ -967,6 +1049,9 @@ client_Buttons.initHotkeys = function(main,player) {
case 83:
window.document.querySelector("#voteskip").onclick();
break;
+ case 85:
+ window.document.querySelector("#togglesynch").onclick();
+ break;
default:
return;
}
@@ -991,11 +1076,43 @@ client_Buttons.updateSynchThresholdBtn = function() {
window.document.querySelector("#synchThresholdBtn").innerText = tmp + "s";
};
client_Buttons.updateHotkeysBtn = function() {
- var text = Lang.get("hotkeys");
- var state = client_Buttons.settings.hotkeysEnabled ? Lang.get("on") : Lang.get("off");
- window.document.querySelector("#hotkeysBtn").innerText = "" + text + ": " + state;
+ var btn = window.document.querySelector("#hotkeysBtn");
+ var switchEl = btn.querySelector(".toggle-switch");
+ if(switchEl == null) {
+ btn.innerHTML = "<span>" + Lang.get("hotkeys") + "</span><div class=\"toggle-switch\"></div>";
+ switchEl = btn.querySelector(".toggle-switch");
+ }
+ switchEl.classList.toggle("on",client_Buttons.settings.hotkeysEnabled);
+ window.document.body.classList.toggle("hotkeys-disabled",!client_Buttons.settings.hotkeysEnabled);
+};
+client_Buttons.updatePageFullscreenBtn = function() {
+ var btn = window.document.querySelector("#pageFullscreenBtn");
+ var switchEl = btn.querySelector(".toggle-switch");
+ if(switchEl == null) {
+ btn.innerHTML = "<span>" + Lang.get("pageFullscreen") + "</span><div class=\"toggle-switch\"></div>";
+ switchEl = btn.querySelector(".toggle-switch");
+ }
+ switchEl.classList.toggle("on",client_Buttons.settings.pageFullscreen);
};
-client_Buttons.initChatInputs = function(main) {
+client_Buttons.updateFullscreenActionBtn = function() {
+ var btn = window.document.querySelector("#fullscreenActionBtn");
+ var switchEl = btn.querySelector(".toggle-switch");
+ if(switchEl == null) {
+ btn.innerHTML = "<span>" + Lang.get(client_Utils.isTouch() ? "fullscreenActionTouch" : "fullscreenActionDesktop") + "</span><div class=\"toggle-switch\"></div>";
+ switchEl = btn.querySelector(".toggle-switch");
+ }
+ switchEl.classList.toggle("on",client_Buttons.settings.fullscreenAction);
+};
+client_Buttons.updateToggleSynchBtn = function(main) {
+ var toggleSynch = window.document.querySelector("#togglesynch");
+ var span = toggleSynch.querySelector("span");
+ var text = Lang.get(main.isSyncActive ? "disableVideoSync" : "enableVideoSync");
+ toggleSynch.title = "" + text + " (Alt-U)";
+ if(span != null) {
+ span.innerHTML = "" + text + " <span class=\"hotkey-hint\">(Alt-U)</span>";
+ }
+};
+client_Buttons.initInputs = function(main) {
var guestName = window.document.querySelector("#guestname");
guestName.onkeydown = function(e) {
if(e.keyCode == 13) {
@@ -1015,19 +1132,6 @@ client_Buttons.initChatInputs = function(main) {
}
}
};
- var chatline = window.document.querySelector("#chatline");
- chatline.onfocus = function(e) {
- if(client_Utils.isIOS()) {
- var startY = 0;
- haxe_Timer.delay(function() {
- window.scrollBy(0,-(window.scrollY - startY));
- window.document.querySelector("#video").scrollTop = 0;
- main.scrollChatToEnd();
- },100);
- } else if(client_Utils.isTouch()) {
- main.scrollChatToEnd();
- }
- };
var viewport = window.visualViewport;
if(viewport != null) {
viewport.addEventListener("resize",function(e) {
@@ -1035,16 +1139,6 @@ client_Buttons.initChatInputs = function(main) {
});
client_Buttons.onViewportResize();
}
- new client_InputWithHistory(chatline,null,50,function(value) {
- if(main.handleCommands(value)) {
- return true;
- }
- main.send({ type : "Message", message : { clientName : "", text : value}});
- if(client_Utils.isTouch()) {
- chatline.blur();
- }
- return true;
- });
var checkboxes = [window.document.querySelector("#add-temp")];
var _g = 0;
while(_g < checkboxes.length) {
@@ -1078,17 +1172,302 @@ client_Buttons.onViewportResize = function() {
}
window.document.querySelector("#chat").style.height = "" + h + "px";
};
+client_Buttons.onNewChatMessage = function() {
+ client_Buttons.hasUnreadChatMessages = true;
+ client_Buttons.updateFullscreenChatBtn();
+};
+client_Buttons.clearUnreadChatMessages = function() {
+ client_Buttons.hasUnreadChatMessages = false;
+ client_Buttons.updateFullscreenChatBtn();
+};
+client_Buttons.updateFullscreenChatBtn = function() {
+ var chatBtn = window.document.querySelector("#fullscreen-chat-btn");
+ if(client_Utils.hasFullscreen() && client_Buttons.hasUnreadChatMessages) {
+ chatBtn.classList.add("has-unread");
+ } else {
+ chatBtn.classList.remove("has-unread");
+ }
+};
client_Buttons.initPageFullscreen = function() {
window.document.onfullscreenchange = function(e) {
- var el = window.document.documentElement;
- if(client_Utils.hasFullscreen()) {
- if(e.target == el) {
- el.classList.add("mobile-view");
+ client_Buttons.clearUnreadChatMessages();
+ client_Buttons.updateFullscreenChatBtn();
+ };
+};
+var client_ChatInput = function(main) {
+ this.selectedCommandIndex = -1;
+ this.filteredCommands = [];
+ this.main = main;
+ this.chatline = window.document.querySelector("#chatline");
+ this.commandsWrap = window.document.querySelector("#chat-commands-wrap");
+ this.commandsList = window.document.querySelector("#chat-commands-list");
+ this.setupFocusHandler();
+ this.setupAutocomplete();
+ this.inputWithHistory = new client_InputWithHistory(this.chatline,null,50,$bind(this,this.onEnter),$bind(this,this.onInterceptKeyDown));
+};
+client_ChatInput.__name__ = true;
+client_ChatInput.prototype = {
+ setupFocusHandler: function() {
+ var _gthis = this;
+ this.chatline.onfocus = function(e) {
+ if(client_Utils.isIOS()) {
+ var startY = 0;
+ haxe_Timer.delay(function() {
+ window.scrollBy(0,-(window.scrollY - startY));
+ window.document.querySelector("#video").scrollTop = 0;
+ _gthis.main.scrollChatToEnd();
+ },100);
+ } else if(client_Utils.isTouch()) {
+ _gthis.main.scrollChatToEnd();
}
+ };
+ }
+ ,setupAutocomplete: function() {
+ var _gthis = this;
+ this.commandsList.addEventListener("mousemove",function(e) {
+ _gthis.commandsList.classList.remove("keyboard-nav");
+ });
+ this.chatline.addEventListener("input",function(e) {
+ var val = _gthis.chatline.value;
+ if(StringTools.startsWith(val,"/")) {
+ _gthis.filteredCommands = client_Commands.filterForAutocomplete(val);
+ _gthis.selectedCommandIndex = _gthis.filteredCommands.length > 0 ? 0 : -1;
+ _gthis.renderCommands();
+ } else {
+ _gthis.commandsWrap.style.display = "none";
+ _gthis.selectedCommandIndex = -1;
+ }
+ });
+ this.chatline.addEventListener("blur",function(e) {
+ return _gthis.commandsWrap.style.display = "none";
+ });
+ }
+ ,renderCommands: function() {
+ var _gthis = this;
+ this.commandsList.innerHTML = "";
+ if(this.filteredCommands.length == 0) {
+ this.commandsWrap.style.display = "none";
+ return;
+ }
+ var parsed = client_Commands.parse(this.chatline.value);
+ var currentArgIndex = parsed.currentArgIndex;
+ var _g = 0;
+ var _g1 = this.filteredCommands.length;
+ while(_g < _g1) {
+ var i = [_g++];
+ var cmd = [this.filteredCommands[i[0]]];
+ var item = window.document.createElement("div");
+ item.className = "chat-command-item" + (i[0] == this.selectedCommandIndex ? " active" : "");
+ item.onclick = (function(cmd) {
+ return function(e) {
+ _gthis.chatline.value = (cmd[0].name == null ? "null" : cmd[0].name) + (cmd[0].args > 0 ? " " : "");
+ _gthis.chatline.focus();
+ if(cmd[0].args == 0) {
+ _gthis.commandsWrap.style.display = "none";
+ _gthis.selectedCommandIndex = -1;
+ } else {
+ _gthis.chatline.dispatchEvent(new Event("input"));
+ }
+ };
+ })(cmd);
+ item.onmouseenter = (function(i) {
+ return function(e) {
+ if(!_gthis.commandsList.classList.contains("keyboard-nav")) {
+ _gthis.selectedCommandIndex = i[0];
+ var _g = 0;
+ var _g1 = _gthis.commandsList.children.length;
+ while(_g < _g1) {
+ var j = _g++;
+ var child = _gthis.commandsList.children[j];
+ if(j == i[0]) {
+ child.classList.add("active");
+ } else {
+ child.classList.remove("active");
+ }
+ }
+ }
+ };
+ })(i);
+ var nameDiv = window.document.createElement("div");
+ nameDiv.className = "chat-command-item-name";
+ nameDiv.innerText = cmd[0].name;
+ var descDiv = window.document.createElement("div");
+ descDiv.className = "chat-command-item-desc";
+ descDiv.innerHTML = client_Commands.formatDescWithHighlight(Lang.get(cmd[0].lang),currentArgIndex);
+ item.appendChild(nameDiv);
+ item.appendChild(descDiv);
+ this.commandsList.appendChild(item);
+ }
+ this.commandsWrap.style.display = "";
+ if(this.selectedCommandIndex >= 0 && this.commandsList.children.length > this.selectedCommandIndex) {
+ var selectedEl = this.commandsList.children[this.selectedCommandIndex];
+ selectedEl.scrollIntoView({ block : "center"});
+ }
+ }
+ ,onEnter: function(value) {
+ if(this.main.handleCommands(value)) {
+ return true;
+ }
+ this.main.send({ type : "Message", message : { clientName : "", text : value}});
+ if(client_Utils.isTouch()) {
+ this.chatline.blur();
+ }
+ this.commandsWrap.style.display = "none";
+ return true;
+ }
+ ,onInterceptKeyDown: function(e) {
+ if(this.commandsWrap.style.display == "none") {
+ return false;
+ }
+ var key = e.keyCode;
+ switch(key) {
+ case 9:case 13:
+ if(this.selectedCommandIndex < 0 || this.selectedCommandIndex >= this.filteredCommands.length) {
+ return false;
+ }
+ var selectedCmd = this.filteredCommands[this.selectedCommandIndex];
+ var val = this.chatline.value;
+ var hasSpace = val.indexOf(" ") >= 0;
+ if(key == 13) {
+ if(hasSpace || selectedCmd.args == 0 && StringTools.trim(val).toLowerCase() == selectedCmd.name) {
+ return false;
+ }
+ }
+ if(!hasSpace) {
+ this.chatline.value = (selectedCmd.name == null ? "null" : selectedCmd.name) + (selectedCmd.args > 0 ? " " : "");
+ }
+ e.preventDefault();
+ this.chatline.dispatchEvent(new Event("input"));
+ return true;
+ case 27:
+ this.commandsWrap.style.display = "none";
+ return false;
+ case 38:
+ this.commandsList.classList.add("keyboard-nav");
+ if(this.selectedCommandIndex > 0) {
+ this.selectedCommandIndex--;
+ } else {
+ this.selectedCommandIndex = this.filteredCommands.length - 1;
+ }
+ this.renderCommands();
+ e.preventDefault();
+ return true;
+ case 40:
+ this.commandsList.classList.add("keyboard-nav");
+ if(this.selectedCommandIndex < this.filteredCommands.length - 1) {
+ this.selectedCommandIndex++;
+ } else {
+ this.selectedCommandIndex = 0;
+ }
+ this.renderCommands();
+ e.preventDefault();
+ return true;
+ default:
+ return false;
+ }
+ }
+};
+var client_CommandInfo = function(name,args,aliases) {
+ if(args == null) {
+ args = 0;
+ }
+ this.name = name;
+ this.args = args;
+ this.aliases = aliases != null ? aliases : [];
+ this.lang = this.getLangId();
+};
+client_CommandInfo.__name__ = true;
+client_CommandInfo.prototype = {
+ getLangId: function() {
+ var withoutSlash = HxOverrides.substr(this.name,1,null);
+ return "cmd" + withoutSlash.charAt(0).toUpperCase() + HxOverrides.substr(withoutSlash,1,null);
+ }
+};
+var client_Commands = function() { };
+client_Commands.__name__ = true;
+client_Commands.find = function(name) {
+ var lower = name.toLowerCase();
+ var _g = 0;
+ var _g1 = client_Commands.list;
+ while(_g < _g1.length) {
+ var c = _g1[_g];
+ ++_g;
+ if(c.name == lower) {
+ return c;
+ }
+ var _g2 = 0;
+ var _g3 = c.aliases;
+ while(_g2 < _g3.length) if(_g3[_g2++].toLowerCase() == lower) {
+ return c;
+ }
+ }
+ return null;
+};
+client_Commands.parse = function(input) {
+ if(!StringTools.startsWith(input,"/")) {
+ return { cmd : null, rawCmd : "", args : [], currentArgIndex : -1};
+ }
+ var parts = input.split(" ");
+ var rawCmd = parts[0];
+ var args = parts.slice(1);
+ var currentArgIndex = args.length > 0 ? args.length - 1 : -1;
+ return { cmd : client_Commands.find(rawCmd), rawCmd : rawCmd, args : args, currentArgIndex : currentArgIndex};
+};
+client_Commands.filterForAutocomplete = function(val) {
+ if(!StringTools.startsWith(val,"/")) {
+ return [];
+ }
+ var parts = val.toLowerCase().split(" ");
+ var searchCmd = parts[0];
+ var spaceCount = parts.length - 1;
+ var result = [];
+ var _g = 0;
+ var _g1 = client_Commands.list;
+ while(_g < _g1.length) {
+ var c = _g1[_g];
+ ++_g;
+ if(spaceCount == 0) {
+ if(StringTools.startsWith(c.name,searchCmd)) {
+ result.push(c);
+ } else {
+ var _g2 = 0;
+ var _g3 = c.aliases;
+ while(_g2 < _g3.length) if(StringTools.startsWith(_g3[_g2++].toLowerCase(),searchCmd)) {
+ result.push(c);
+ break;
+ }
+ }
+ } else if(spaceCount <= c.args) {
+ if(c.name == searchCmd) {
+ result.push(c);
+ } else {
+ var _g4 = 0;
+ var _g5 = c.aliases;
+ while(_g4 < _g5.length) if(_g5[_g4++].toLowerCase() == searchCmd) {
+ result.push(c);
+ break;
+ }
+ }
+ }
+ }
+ return result;
+};
+client_Commands.formatDescWithHighlight = function(desc,activeArgIndex) {
+ desc = StringTools.htmlEscape(desc);
+ var argCounter = 0;
+ desc = new EReg("&lt;[^&]+&gt;","g").map(desc,function(reg) {
+ var token = reg.matched(0);
+ var isCurrent = argCounter == activeArgIndex;
+ argCounter += 1;
+ if(isCurrent) {
+ return "<span class=\"chat-command-arg-active\">" + token + "</span>";
} else {
- el.classList.remove("mobile-view");
+ return token;
}
- };
+ });
+ return new EReg("`([^`]+)`","g").map(desc,function(reg) {
+ return "<span class=\"chat-command-code\">" + reg.matched(1) + "</span>";
+ });
};
var client_FileUploader = function(main) {
this.main = main;
@@ -1102,52 +1481,104 @@ client_FileUploader.prototype = {
return;
}
var _this_r = new RegExp("[?#%/\\\\]","g".split("u").join(""));
- var name = StringTools.trim(file.name.replace(_this_r,""));
- if(name.length == 0) {
- name = "video";
+ var title = StringTools.trim(file.name.replace(_this_r,""));
+ if(title.length == 0) {
+ title = "video";
}
- name = window.encodeURIComponent(name);
+ var name = window.encodeURIComponent(title);
+ var isTemp = client_Main.isTempChecked(window.document.querySelector("#addfromurl .add-temp"));
this.uploadLastChunk(file,name,function(data) {
if(data.errorId != null) {
_gthis.main.serverMessage(data.info,true,false);
return;
}
- window.document.querySelector("#mediaurl").value = data.url;
- _gthis.uploadFullFile(name,file);
+ var url = data.url;
+ _gthis.getFileDuration(file,function(duration) {
+ if(duration == 0 || isNaN(duration) || !isFinite(duration)) {
+ _gthis.main.serverMessage(Lang.get("addVideoError"),true,false);
+ return;
+ }
+ _gthis.uploadFullFile(name,file,url,function() {
+ _gthis.main.addUploadedVideo(url,title,duration,true,isTemp);
+ });
+ });
});
}
- ,uploadFullFile: function(name,file) {
+ ,getFileDuration: function(file,callback) {
+ var objUrl = URL.createObjectURL(file);
+ var video = window.document.createElement("video");
+ video.preload = "metadata";
+ video.muted = true;
+ video.onloadedmetadata = function() {
+ var duration = video.duration;
+ URL.revokeObjectURL(objUrl);
+ callback(duration);
+ };
+ video.onerror = function(e) {
+ URL.revokeObjectURL(objUrl);
+ callback(0);
+ };
+ video.src = objUrl;
+ }
+ ,uploadFullFile: function(name,file,url,onStarted) {
var _gthis = this;
var request = new XMLHttpRequest();
request.open("POST","/upload",true);
request.setRequestHeader("content-name",name);
var tmp = this.main.settings.uuid;
request.setRequestHeader("x-client-uuid",tmp != null ? tmp : "");
+ var added = false;
+ var ensureAdded = function() {
+ if(added) {
+ return;
+ }
+ added = true;
+ onStarted();
+ _gthis.main.registerUpload(url,function() {
+ request.abort();
+ });
+ };
+ var sendProgress = function(type,ratio) {
+ _gthis.main.send({ type : "Progress", progress : { type : type, ratio : ratio, url : url}});
+ };
+ var lastSentRatio = 0.0;
request.upload.onprogress = function(event) {
+ ensureAdded();
var ratio = 0.0;
if(event.lengthComputable) {
var v = event.loaded / event.total;
ratio = v < 0 ? 0 : v > 1 ? 1 : v;
}
- _gthis.main.onProgressEvent({ type : "Progress", progress : { type : "Uploading", ratio : ratio}});
+ if(ratio - lastSentRatio < 0.01 && ratio < 1) {
+ return;
+ }
+ lastSentRatio = ratio;
+ sendProgress("Uploading",ratio);
};
request.onload = function(e) {
+ _gthis.main.unregisterUpload(url);
var data;
try {
data = JSON.parse(request.responseText);
} catch( _g ) {
- haxe_Log.trace(haxe_Exception.caught(_g),{ fileName : "src/client/FileUploader.hx", lineNumber : 66, className : "client.FileUploader", methodName : "uploadFullFile"});
+ haxe_Log.trace(haxe_Exception.caught(_g),{ fileName : "src/client/FileUploader.hx", lineNumber : 112, className : "client.FileUploader", methodName : "uploadFullFile"});
+ sendProgress("Canceled",0);
return;
}
- if(data.errorId == null) {
+ if(data.errorId != null) {
+ _gthis.main.serverMessage(data.info,true,false);
+ sendProgress("Canceled",0);
return;
}
- _gthis.main.serverMessage(data.info,true,false);
+ ensureAdded();
+ sendProgress("Completed",1);
};
- request.onloadend = function() {
- return haxe_Timer.delay(function() {
- _gthis.main.hideDynamicChin();
- },500);
+ request.onerror = function(e) {
+ _gthis.main.unregisterUpload(url);
+ sendProgress("Canceled",0);
+ };
+ request.onabort = function(e) {
+ _gthis.main.unregisterUpload(url);
};
request.send(file);
}
@@ -1163,7 +1594,7 @@ client_FileUploader.prototype = {
});
}
};
-var client_InputWithHistory = function(element,history,maxItems,onEnter) {
+var client_InputWithHistory = function(element,history,maxItems,onEnter,onInterceptKeyDown) {
this.historyId = -1;
this.element = element;
if(history != null) {
@@ -1173,6 +1604,7 @@ var client_InputWithHistory = function(element,history,maxItems,onEnter) {
}
this.maxItems = maxItems;
this.onEnter = onEnter;
+ this.onInterceptKeyDown = onInterceptKeyDown;
element.onkeydown = $bind(this,this.onKeyDown);
};
client_InputWithHistory.__name__ = true;
@@ -1184,6 +1616,9 @@ client_InputWithHistory.pushIfNotLast = function(arr,item) {
};
client_InputWithHistory.prototype = {
onKeyDown: function(e) {
+ if(this.onInterceptKeyDown != null && this.onInterceptKeyDown(e)) {
+ return;
+ }
var key = e.keyCode;
switch(key) {
case 13:
@@ -1201,6 +1636,9 @@ client_InputWithHistory.prototype = {
this.element.value = "";
this.onInput();
break;
+ case 27:
+ e.preventDefault();
+ break;
case 38:
this.historyId--;
if(this.historyId == -2) {
@@ -1231,9 +1669,7 @@ client_InputWithHistory.prototype = {
}
}
,onInput: function() {
- if(this.element.oninput != null) {
- this.element.oninput();
- }
+ this.element.dispatchEvent(new Event("input"));
}
};
var client_JsApi = function() { };
@@ -1268,9 +1704,10 @@ client_JsApi.addScriptToHead = $hx_exports["client"]["JsApi"]["addScriptToHead"]
window.document.head.appendChild(script);
};
client_JsApi.hasScriptInHead = $hx_exports["client"]["JsApi"]["hasScriptInHead"] = function(url) {
+ var abs = new URL(url,window.document.baseURI).href;
var _g = 0;
var _g1 = window.document.getElementsByTagName("head")[0].children;
- while(_g < _g1.length) if(_g1[_g++].src == url) {
+ while(_g < _g1.length) if(_g1[_g++].src == abs) {
return true;
}
return false;
@@ -1282,11 +1719,11 @@ client_JsApi.getVideoItems = $hx_exports["client"]["JsApi"]["getVideoItems"] = f
while(_g1 < items.length) _g.push(Reflect.copy(items[_g1++]));
return _g;
};
-client_JsApi.addVideoItem = $hx_exports["client"]["JsApi"]["addVideoItem"] = function(url,atEnd,isTemp,callback,doCache) {
+client_JsApi.addVideoItem = $hx_exports["client"]["JsApi"]["addVideoItem"] = function(url,atEnd,isTemp,afterSend,doCache) {
if(doCache == null) {
doCache = false;
}
- client_JsApi.main.addVideo(url,atEnd,isTemp,doCache,callback);
+ client_JsApi.main.addVideo(url,atEnd,isTemp,doCache,afterSend);
};
client_JsApi.removeVideoItem = $hx_exports["client"]["JsApi"]["removeVideoItem"] = function(url) {
client_JsApi.main.removeVideoItem(url);
@@ -1329,24 +1766,24 @@ client_JsApi.once = $hx_exports["client"]["JsApi"]["once"] = function(type,callb
client_JsApi.onceListeners.unshift({ type : type, callback : callback});
};
client_JsApi.fireEvents = function(event) {
- var _g_arr = client_JsApi.onListeners;
- var _g_i = _g_arr.length - 1;
+ var listeners = client_JsApi.onListeners.slice();
+ var _g_i = listeners.length - 1;
while(_g_i > -1) {
- var listener = _g_arr[_g_i--];
+ var listener = listeners[_g_i--];
if(listener.type != event.type) {
continue;
}
listener.callback(event);
}
- var _g_arr = client_JsApi.onceListeners;
- var _g_i = _g_arr.length - 1;
+ var onces = client_JsApi.onceListeners.slice();
+ var _g_i = onces.length - 1;
while(_g_i > -1) {
- var listener = _g_arr[_g_i--];
+ var listener = onces[_g_i--];
if(listener.type != event.type) {
continue;
}
- listener.callback(event);
HxOverrides.remove(client_JsApi.onceListeners,listener);
+ listener.callback(event);
}
};
client_JsApi.notifyOnVideoChange = $hx_exports["client"]["JsApi"]["notifyOnVideoChange"] = function(callback) {
@@ -1373,6 +1810,7 @@ client_JsApi.fireVideoRemoveEvents = function(item) {
};
var client_Main = function() {
this.matchSimpleDate = new EReg("^-?([0-9]+d)?([0-9]+h)?([0-9]+m)?([0-9]+s?)?$","");
+ this.activeUploads = new haxe_ds_StringMap();
this.urlMask = new EReg("\\${([0-9]+)-([0-9]+)}","g");
this.isPageVisible = true;
this.isPageUnloading = false;
@@ -1388,6 +1826,9 @@ var client_Main = function() {
this.lastStateTimeStamp = 0.0;
this.lastState = { time : 0, rate : 1.0, paused : false, pausedByServer : false};
this.timeFromLastState = 0.0;
+ this.skipVoteRequired = 0;
+ this.skipVoteCount = 0;
+ this.hasSeenSkipVote = false;
this.showingServerPause = false;
this.playersCacheSupport = [];
this.isPlaylistOpen = true;
@@ -1403,7 +1844,7 @@ var client_Main = function() {
if(this.host == "") {
this.host = "localhost";
}
- client_Settings.init({ version : 6, uuid : null, name : "", hash : "", chatSize : 300, synchThreshold : 1, isSwapped : false, isUserListHidden : true, latestLinks : [], latestSubs : [], hotkeysEnabled : true, showHintList : true, checkboxes : [], checkedCache : []},$bind(this,this.settingsPatcher));
+ client_Settings.init({ version : 7, uuid : null, name : "", hash : "", chatSize : 300, synchThreshold : 1, isSwapped : false, isUserListHidden : true, latestLinks : [], latestSubs : [], hotkeysEnabled : true, pageFullscreen : false, fullscreenAction : client_Utils.isTouch(), showHintList : true, checkboxes : [], checkedCache : []},$bind(this,this.settingsPatcher));
this.settings = client_Settings.read();
this.initListeners();
this.onTimeGet = new haxe_Timer(this.settings.synchThreshold * 1000);
@@ -1443,6 +1884,17 @@ client_Main.__name__ = true;
client_Main.main = function() {
new client_Main();
};
+client_Main.isTempChecked = function(checkbox) {
+ if(checkbox == null) {
+ return true;
+ }
+ var isHidden = checkbox.style.display == "none";
+ if(isHidden) {
+ return true;
+ } else {
+ return checkbox.checked;
+ }
+};
client_Main.prototype = {
onFirstInteraction: function() {
if(this.gotFirstPageInteraction) {
@@ -1460,32 +1912,18 @@ client_Main.prototype = {
}
,settingsPatcher: function(data,version) {
switch(version) {
- case 1:
- data.hotkeysEnabled = true;
- break;
- case 2:
- data.latestSubs = [];
- break;
- case 3:
- data.showHintList = true;
- break;
- case 4:
- data.checkboxes = [];
+ case 1:case 2:case 3:case 4:
break;
case 5:
- var data1 = data;
- data1.checkedCache = [];
- Reflect.deleteField(data1,"playerSize");
- Reflect.deleteField(data1,"isExtendedPlayer");
- var oldCheck = Lambda.find(data1.checkboxes,function(item) {
- return item.id == "cache-on-server";
- });
- if(oldCheck != null) {
- HxOverrides.remove(data1.checkboxes,oldCheck);
- data1.checkedCache.push("YoutubeType");
- }
+ client_Settings.reset();
+ data = client_Settings.read();
break;
case 6:
+ var data1 = data;
+ data1.pageFullscreen = false;
+ data1.fullscreenAction = client_Utils.isTouch();
+ break;
+ case 7:
throw haxe_Exception.thrown("skipped version " + version);
default:
throw haxe_Exception.thrown("skipped version " + version);
@@ -1499,6 +1937,9 @@ client_Main.prototype = {
if(this.player.isListEmpty()) {
return;
}
+ if(!_$Types_VideoItemTools.isPlayable(this.player.getCurrentItem())) {
+ return;
+ }
this.send({ type : "GetTime"});
}
,openWebSocket: function() {
@@ -1545,6 +1986,7 @@ client_Main.prototype = {
}
,initListeners: function() {
var _gthis = this;
+ this.chatInput = new client_ChatInput(this);
client_Buttons.init(this);
var openBtn = window.document.getElementById("openChatBtn");
var openBtnFadeTimer = null;
@@ -1625,9 +2067,7 @@ client_Main.prototype = {
e.preventDefault();
};
window.document.querySelector("#voteskip").onclick = function(e) {
- if(client_Utils.isTouch() && !window.confirm(Lang.get("skipItemConfirm"))) {
- return;
- }
+ _gthis.hasSeenSkipVote = true;
if(_gthis.player.isListEmpty()) {
return;
}
@@ -1697,7 +2137,7 @@ client_Main.prototype = {
,addVideoUrl: function(atEnd) {
var mediaUrl = window.document.querySelector("#mediaurl");
var subsUrl = window.document.querySelector("#subsurl");
- var isTemp = window.document.querySelector("#addfromurl .add-temp").checked;
+ var isTemp = client_Main.isTempChecked(window.document.querySelector("#addfromurl .add-temp"));
var checkboxCache = window.document.querySelector("#cache-on-server");
var doCache = (this.personal.group & 8) != 0 && checkboxCache.checked && checkboxCache.parentElement.style.display != "none";
var url = mediaUrl.value;
@@ -1758,7 +2198,7 @@ client_Main.prototype = {
_gthis.addVideoArray(links,atEnd,isTemp,doCache);
});
}
- ,addVideo: function(url,atEnd,isTemp,doCache,callback) {
+ ,addVideo: function(url,atEnd,isTemp,doCache,afterSend) {
var _gthis = this;
var protocol = $global.location.protocol;
if(StringTools.startsWith(url,"/")) {
@@ -1771,26 +2211,60 @@ client_Main.prototype = {
url = "" + protocol + "//" + url;
}
}
- var obj = { url : url, atEnd : atEnd};
- if(doCache) {
- this.showProgressInfo(Lang.get("caching") + "...");
+ var sendItem = function(data) {
+ var tmp = data.url;
+ var tmp1 = data.title;
+ _gthis.send({ type : "AddVideo", addVideo : { item : { url : tmp != null ? tmp : url, title : tmp1 != null ? tmp1 : "", author : _gthis.personal.name, duration : data.duration, isTemp : isTemp, doCache : doCache, subs : data.subs, voiceOverTrack : data.voiceOverTrack, playerType : data.playerType}, atEnd : atEnd}});
+ if(afterSend != null) {
+ afterSend();
+ }
+ };
+ var isServerResolvesMeta = doCache && this.isServerCacheServiceEnabled();
+ var linkPlayerType = this.getLinkPlayerType(url);
+ if(isServerResolvesMeta && (linkPlayerType == "VkType" || linkPlayerType == "VimeoType")) {
+ sendItem({ duration : 0, title : "", url : url, playerType : linkPlayerType});
+ return;
}
- this.player.getVideoData(obj,function(data) {
+ this.player.getVideoData({ url : url, atEnd : atEnd},function(data) {
if(data.duration == 0) {
- if(doCache) {
- _gthis.hideDynamicChin();
+ if(!isServerResolvesMeta) {
+ _gthis.serverMessage(Lang.get("addVideoError"));
+ return;
}
- _gthis.serverMessage(Lang.get("addVideoError"));
- return;
- }
- data.title = data.title != null ? data.title : Lang.get("rawVideo");
- data.url = data.url != null ? data.url : url;
- _gthis.send({ type : "AddVideo", addVideo : { item : { url : data.url, title : data.title, author : _gthis.personal.name, duration : data.duration, isTemp : isTemp, doCache : doCache, subs : data.subs, voiceOverTrack : data.voiceOverTrack, playerType : data.playerType}, atEnd : atEnd}});
- if(callback != null) {
- callback();
+ } else {
+ data.title = data.title != null ? data.title : Lang.get("rawVideo");
}
+ sendItem(data);
+ });
+ }
+ ,isServerCacheServiceEnabled: function() {
+ return Lambda.exists(this.playersCacheSupport,function(type) {
+ return type != "RawType";
});
}
+ ,registerUpload: function(url,cancel) {
+ this.activeUploads.h[url] = cancel;
+ }
+ ,unregisterUpload: function(url) {
+ var _this = this.activeUploads;
+ if(Object.prototype.hasOwnProperty.call(_this.h,url)) {
+ delete(_this.h[url]);
+ }
+ }
+ ,cancelUpload: function(url) {
+ var tmp = this.activeUploads.h[url];
+ if(tmp == null) {
+ return;
+ }
+ var _this = this.activeUploads;
+ if(Object.prototype.hasOwnProperty.call(_this.h,url)) {
+ delete(_this.h[url]);
+ }
+ tmp();
+ }
+ ,addUploadedVideo: function(url,title,duration,atEnd,isTemp) {
+ this.send({ type : "AddVideo", addVideo : { item : { url : url, title : title, author : this.personal.name, duration : duration, isTemp : isTemp, doCache : false, isIncomplete : true, playerType : "RawType"}, atEnd : atEnd}});
+ }
,addIframe: function(atEnd) {
var _gthis = this;
var iframeCode = window.document.querySelector("#customembed-content");
@@ -1802,7 +2276,6 @@ client_Main.prototype = {
var mediaTitle = window.document.querySelector("#customembed-title");
var title = mediaTitle.value;
mediaTitle.value = "";
- var isTemp = window.document.querySelector("#customembed .add-temp").checked;
this.player.getIframeData({ url : iframe, atEnd : atEnd},function(data) {
if(data.duration == 0) {
_gthis.serverMessage(Lang.get("addVideoError"));
@@ -1813,7 +2286,7 @@ client_Main.prototype = {
}
data.title = data.title != null ? data.title : "Custom Media";
data.url = data.url != null ? data.url : iframe;
- _gthis.send({ type : "AddVideo", addVideo : { item : { url : data.url, title : data.title, author : _gthis.personal.name, duration : data.duration, isTemp : isTemp, doCache : false, playerType : "IframeType"}, atEnd : atEnd}});
+ _gthis.send({ type : "AddVideo", addVideo : { item : { url : data.url, title : data.title, author : _gthis.personal.name, duration : data.duration, isTemp : true, doCache : false, playerType : "IframeType"}, atEnd : atEnd}});
});
}
,removeVideoItem: function(url) {
@@ -1854,7 +2327,7 @@ client_Main.prototype = {
var data = JSON.parse(e.data);
if(this.config != null && this.config.isVerbose) {
var t = data.type;
- haxe_Log.trace("Event: " + data.type,{ fileName : "src/client/Main.hx", lineNumber : 547, className : "client.Main", methodName : "onMessage", customParams : [Reflect.field(data,t.charAt(0).toLowerCase() + HxOverrides.substr(t,1,null))]});
+ haxe_Log.trace("Event: " + data.type,{ fileName : "src/client/Main.hx", lineNumber : 599, className : "client.Main", methodName : "onMessage", customParams : [Reflect.field(data,t.charAt(0).toLowerCase() + HxOverrides.substr(t,1,null))]});
}
client_JsApi.fireEvents(data);
switch(data.type) {
@@ -1870,7 +2343,9 @@ client_Main.prototype = {
this.clearChat();
break;
case "ClearPlaylist":
+ this.resetSkipVoteUi();
this.player.clearItems();
+ this.updateServerPauseUi();
if(this.player.isListEmpty()) {
this.player.pause();
}
@@ -1892,20 +2367,10 @@ client_Main.prototype = {
data.getTime.paused = data.getTime.paused != null && data.getTime.paused;
data.getTime.pausedByServer = data.getTime.pausedByServer != null && data.getTime.pausedByServer;
data.getTime.rate = data.getTime.rate != null ? data.getTime.rate : 1;
- var isPauseChanged = this.lastState.paused != data.getTime.paused;
this.lastState.time = data.getTime.time;
- this.lastState.paused = data.getTime.paused;
- this.lastState.pausedByServer = data.getTime.pausedByServer;
this.lastState.rate = data.getTime.rate;
this.updateLastStateTime();
- if(isPauseChanged) {
- this.updateUserList();
- }
- if(data.getTime.pausedByServer) {
- this.showServerUnpause();
- } else if(this.showingServerPause) {
- this.hideDynamicChin();
- }
+ this.updatePauseState(data.getTime.paused,data.getTime.pausedByServer);
if(this.player.getPlaybackRate() != data.getTime.rate) {
this.player.setPlaybackRate(data.getTime.rate);
}
@@ -1974,10 +2439,8 @@ client_Main.prototype = {
break;
case "Pause":
this.lastState.time = data.pause.time;
- this.lastState.paused = true;
this.updateLastStateTime();
- this.player.setPauseIndicator(this.lastState.paused);
- this.updateUserList();
+ this.updatePauseState(true,this.lastState.pausedByServer);
if((this.personal.group & 4) != 0) {
return;
}
@@ -1986,10 +2449,8 @@ client_Main.prototype = {
break;
case "Play":
this.lastState.time = data.play.time;
- this.lastState.paused = false;
this.updateLastStateTime();
- this.player.setPauseIndicator(this.lastState.paused);
- this.updateUserList();
+ this.updatePauseState(false);
if((this.personal.group & 4) != 0) {
return;
}
@@ -2001,13 +2462,21 @@ client_Main.prototype = {
this.player.play();
break;
case "PlayItem":
+ this.resetSkipVoteUi();
this.player.setVideo(data.playItem.pos);
break;
case "Progress":
this.onProgressEvent(data);
break;
case "RemoveVideo":
+ var tmp = this.player.getCurrentItem();
+ var isCurrent = (tmp != null ? tmp.url : null) == data.removeVideo.url;
+ this.cancelUpload(data.removeVideo.url);
this.player.removeItem(data.removeVideo.url);
+ if(isCurrent) {
+ this.resetSkipVoteUi();
+ }
+ this.updateServerPauseUi();
if(this.player.isListEmpty()) {
this.player.pause();
}
@@ -2038,6 +2507,9 @@ client_Main.prototype = {
if((this.personal.group & 4) != 0) {
this.player.onSetTime();
}
+ if(!this.hasLeader()) {
+ this.updatePauseState(this.lastState.paused);
+ }
break;
case "SetNextItem":
this.player.setNextItem(data.setNextItem.pos);
@@ -2061,11 +2533,28 @@ client_Main.prototype = {
case "ShufflePlaylist":
break;
case "SkipVideo":
+ this.resetSkipVoteUi();
this.player.skipItem(data.skipVideo.url);
+ this.updateServerPauseUi();
if(this.player.isListEmpty()) {
this.player.pause();
}
break;
+ case "SkipVideoStats":
+ if(data.skipVideoStats.voted == 1 && this.skipVoteCount == 0) {
+ this.serverMessage(Lang.get("skipVoteInitiated"));
+ }
+ this.skipVoteCount = data.skipVideoStats.voted;
+ this.skipVoteRequired = data.skipVideoStats.required;
+ var tmp = "" + ("(" + this.skipVoteCount + "/" + this.skipVoteRequired + ")") + " ";
+ var tmp1 = Lang.get("voteForSkip");
+ window.document.querySelector("#voteskip-text").textContent = tmp + tmp1;
+ var indicator = window.document.querySelector("#playlist-menu-indicator");
+ var dropdown = window.document.querySelector("#playlist-menu-dropdown");
+ if(!this.hasSeenSkipVote && dropdown.style.display == "none") {
+ indicator.style.display = "";
+ }
+ break;
case "ToggleItemType":
this.player.toggleItemType(data.toggleItemType.pos);
break;
@@ -2084,10 +2573,10 @@ client_Main.prototype = {
this.player.setItems(data.updatePlaylist.videoList);
break;
case "VideoLoaded":
- this.lastState.paused = false;
- this.lastState.pausedByServer = false;
+ this.resetSkipVoteUi();
this.lastState.time = 0;
this.updateLastStateTime();
+ this.updatePauseState(false);
this.player.setTime(0);
this.player.play();
if((this.personal.group & 4) != 0 && !this.player.isVideoLoaded()) {
@@ -2097,33 +2586,21 @@ client_Main.prototype = {
}
}
,onProgressEvent: function(data) {
- var _gthis = this;
var data1 = data.progress;
- var text;
+ var tmp = data1.url;
+ if(tmp == null) {
+ return;
+ }
switch(data1.type) {
- case "Caching":
- text = "" + Lang.get("caching") + " " + data1.data;
- break;
case "Canceled":
- this.hideDynamicChin();
- return;
- case "Downloading":
- text = Lang.get("downloading");
+ this.player.removeItem(tmp);
break;
- case "Uploading":
- text = Lang.get("uploading");
+ case "Completed":
+ this.player.completeItem(tmp);
+ break;
+ case "Caching":case "Downloading":case "Uploading":
+ this.player.setItemProgress(tmp,data1.ratio);
break;
- }
- var percent = tools_MathTools.toFixed(data1.ratio * 100,1);
- var text1 = "" + text + "...";
- if(percent > 0) {
- text1 += " " + percent + "%";
- }
- this.showProgressInfo(text1);
- if(data1.ratio == 1) {
- haxe_Timer.delay(function() {
- _gthis.hideDynamicChin();
- },500);
}
}
,updateLastStateTime: function() {
@@ -2451,9 +2928,12 @@ client_Main.prototype = {
if((client.group & 8) != 0) {
klass += " userlist_owner";
}
- list_b += Std.string("<span class=\"" + klass + "\">" + client.name + "</span></div>");
+ list_b += Std.string("<span class=\"" + klass + "\">" + StringTools.htmlEscape(client.name) + "</span></div>");
}
window.document.querySelector("#userlist").innerHTML = list_b;
+ if(this.player != null) {
+ this.player.updateTitle();
+ }
}
,getPageTitle: function() {
return "" + this.pageTitle + " (" + this.clients.length + ")";
@@ -2519,6 +2999,9 @@ client_Main.prototype = {
if(this.onBlinkTab == null) {
this.blinkTabWithTitle("*" + Lang.get("chat") + "*");
}
+ if(name != this.personal.name) {
+ client_Buttons.onNewChatMessage();
+ }
}
,getFirstMessageDiv: function() {
if(this.isMessageBufferReversed()) {
@@ -2558,43 +3041,66 @@ client_Main.prototype = {
return btn.style.display = "none";
},{ once : true});
}
- ,showProgressInfo: function(text) {
- var chin = window.document.querySelector("#dynamic-chin");
- var div = chin.querySelector("#progress-info");
- if(div == null) {
- div = window.document.createElement("div");
- div.id = "progress-info";
- chin.prepend(div);
+ ,resetSkipVoteUi: function() {
+ this.hasSeenSkipVote = false;
+ this.skipVoteCount = 0;
+ this.skipVoteRequired = 0;
+ window.document.querySelector("#voteskip-text").textContent = Lang.get("voteForSkip");
+ window.document.querySelector("#playlist-menu-indicator").style.display = "none";
+ }
+ ,updatePauseState: function(paused,pausedByServer) {
+ if(pausedByServer == null) {
+ pausedByServer = false;
+ }
+ var isPauseChanged = this.lastState.paused != paused;
+ this.lastState.paused = paused;
+ this.lastState.pausedByServer = pausedByServer;
+ this.player.setPauseIndicator(this.lastState.paused);
+ if(isPauseChanged) {
+ this.updateUserList();
}
- div.textContent = text;
- this.showDynamicChin();
+ this.updateServerPauseUi();
}
- ,showServerUnpause: function() {
- var _gthis = this;
- if(this.showingServerPause) {
+ ,updateServerPauseUi: function() {
+ var show = this.lastState.pausedByServer && !this.player.isListEmpty();
+ if(this.showingServerPause == show) {
return;
}
- this.showingServerPause = true;
+ this.showingServerPause = show;
+ this.renderDynamicChin();
+ }
+ ,renderDynamicChin: function() {
+ var _gthis = this;
var chin = window.document.querySelector("#dynamic-chin");
+ if(!this.showingServerPause) {
+ this.hideDynamicChinAnim();
+ return;
+ }
chin.innerHTML = "";
+ var wrapper = window.document.createElement("div");
+ wrapper.style.display = "flex";
+ wrapper.style.flexDirection = "column";
+ wrapper.style.gap = "0.5rem";
var div = window.document.createElement("div");
div.className = "server-whisper";
+ div.style.textAlign = "center";
div.textContent = Lang.get("leaderDisconnectedServerOnPause");
- chin.appendChild(div);
+ wrapper.appendChild(div);
var btn = window.document.createElement("button");
btn.id = "unpause-server";
btn.textContent = Lang.get("unpause");
- chin.appendChild(btn);
+ wrapper.appendChild(btn);
btn.onclick = function() {
- _gthis.hideDynamicChin();
+ _gthis.updatePauseState(_gthis.lastState.paused,false);
_gthis.send({ type : "SetLeader", setLeader : { clientName : _gthis.personal.name}});
client_JsApi.once("SetLeader",function(event) {
_gthis.removeLeader();
});
};
- this.showDynamicChin();
+ chin.appendChild(wrapper);
+ this.showDynamicChinAnim();
}
- ,showDynamicChin: function() {
+ ,showDynamicChinAnim: function() {
var chin = window.document.querySelector("#dynamic-chin");
if(chin.style.display == "") {
return;
@@ -2619,9 +3125,11 @@ client_Main.prototype = {
};
chin.addEventListener("transitionend",onTransitionEnd);
}
- ,hideDynamicChin: function() {
- this.showingServerPause = false;
+ ,hideDynamicChinAnim: function() {
var chin = window.document.querySelector("#dynamic-chin");
+ if(chin.style.display == "none") {
+ return;
+ }
var h = chin.clientHeight;
chin.style.height = "" + h + "px";
haxe_Timer.delay(function() {
@@ -2686,79 +3194,81 @@ client_Main.prototype = {
if(!StringTools.startsWith(command,"/")) {
return false;
}
- var args = StringTools.trim(command).split(" ");
- command = HxOverrides.substr(args.shift(),1,null);
- if(command.length == 0) {
- return false;
- }
- switch(command) {
- case "ad":
- this.player.skipAd();
- return false;
- case "ban":
- this.mergeRedundantArgs(args,0,2);
- var name = args[0];
- var time = this.parseSimpleDate(args[1]);
- if(time < 0) {
- return true;
+ var parsed = client_Commands.parse(command);
+ var args = parsed.args;
+ var tmp = parsed.cmd;
+ var _g = tmp != null ? tmp.name : null;
+ if(_g == null) {
+ var cmdNameWithoutSlash = HxOverrides.substr(parsed.rawCmd,1,null);
+ if(this.matchSimpleDate.match(cmdNameWithoutSlash)) {
+ this.send({ type : "Rewind", rewind : { time : this.parseSimpleDate(cmdNameWithoutSlash)}});
}
- this.send({ type : "BanClient", banClient : { name : name, time : time}});
- return true;
- case "clear":
- this.send({ type : "ClearChat"});
- return true;
- case "crash":
- this.send({ type : "CrashTest"});
- return true;
- case "dump":
- this.send({ type : "Dump"});
- return true;
- case "fb":case "flashback":
- this.send({ type : "Flashback"});
return false;
- case "giveup":
- this.mergeRedundantArgs(args,0,1);
- var tmp = args[0];
- this.send({ type : "SetLeader", setLeader : { clientName : tmp != null ? tmp : ""}});
- return true;
- case "help":
- this.showChatHintList();
- return true;
- case "kick":
- this.mergeRedundantArgs(args,0,1);
- this.send({ type : "KickClient", kickClient : { name : args[0]}});
- return true;
- case "removeBan":case "unban":
- this.mergeRedundantArgs(args,0,1);
- this.send({ type : "BanClient", banClient : { name : args[0], time : 0}});
- return true;
- case "volume":
- var v = parseFloat(args[0]);
- if(isNaN(v)) {
- v = 1;
- }
- if(v < 0) {
- v = 0;
- } else if(v > 3) {
- v = 3;
- }
- var wasNotFull = this.player.getVolume() < 1;
- this.player.setVolume(v < 0 ? 0 : v > 1 ? 1 : v);
- if(this.player.getPlayerType() != "RawType") {
+ } else {
+ switch(_g) {
+ case "/ad":
+ this.player.skipAd();
+ return false;
+ case "/ban":
+ this.mergeRedundantArgs(args,0,2);
+ var name = args[0];
+ var time = this.parseSimpleDate(args[1]);
+ if(time < 0) {
+ return true;
+ }
+ this.send({ type : "BanClient", banClient : { name : name, time : time}});
return true;
- }
- if(wasNotFull && v > 1) {
- this.serverMessage("Volume was not maxed yet to be boosted, you can send command again.");
+ case "/clear":
+ this.send({ type : "ClearChat"});
+ return true;
+ case "/crash":
+ this.send({ type : "CrashTest"});
+ return true;
+ case "/dump":
+ this.send({ type : "Dump"});
+ return true;
+ case "/fb":
+ this.send({ type : "Flashback"});
+ return false;
+ case "/giveup":
+ this.mergeRedundantArgs(args,0,1);
+ var tmp = args[0];
+ this.send({ type : "SetLeader", setLeader : { clientName : tmp != null ? tmp : ""}});
+ return true;
+ case "/help":
+ this.showChatHintList();
+ return true;
+ case "/kick":
+ this.mergeRedundantArgs(args,0,1);
+ this.send({ type : "KickClient", kickClient : { name : args[0]}});
+ return true;
+ case "/unban":
+ this.mergeRedundantArgs(args,0,1);
+ this.send({ type : "BanClient", banClient : { name : args[0], time : 0}});
+ return true;
+ case "/volume":
+ var v = parseFloat(args[0]);
+ if(isNaN(v)) {
+ v = 1;
+ }
+ if(v < 0) {
+ v = 0;
+ } else if(v > 3) {
+ v = 3;
+ }
+ var wasNotFull = this.player.getVolume() < 1;
+ this.player.setVolume(v < 0 ? 0 : v > 1 ? 1 : v);
+ if(this.player.getPlayerType() != "RawType") {
+ return true;
+ }
+ if(wasNotFull && v > 1) {
+ this.serverMessage("Volume was not maxed yet to be boosted, you can send command again.");
+ return true;
+ }
+ this.player.rawPlayer.boostVolume(v);
return true;
}
- this.player.rawPlayer.boostVolume(v);
- return true;
}
- if(this.matchSimpleDate.match(command)) {
- this.send({ type : "Rewind", rewind : { time : this.parseSimpleDate(command)}});
- return false;
- }
- return false;
}
,parseSimpleDate: function(text) {
if(text == null) {
@@ -2870,6 +3380,9 @@ client_Main.prototype = {
,hasLeader: function() {
return ClientTools.hasLeader(this.clients);
}
+ ,getLeader: function() {
+ return ClientTools.getLeader(this.clients);
+ }
,hasLeaderOnPauseRequest: function() {
if(this.config.requestLeaderOnPause) {
return this.isPageVisible && !this.isPageUnloading;
@@ -2900,9 +3413,6 @@ client_Main.prototype = {
}
return this.gotFirstPageInteraction;
}
- ,isVerbose: function() {
- return this.config.isVerbose;
- }
,escapeRegExp: function(regex) {
var _this_r = new RegExp("([.*+?^${}()|[\\]\\\\])","g".split("u").join(""));
return regex.replace(_this_r,"\\$1");
@@ -2923,9 +3433,9 @@ var client_Player = function(main) {
var _gthis = this;
this.main = main;
this.youtube = new client_players_Youtube(main,this);
- this.players = [this.youtube,new client_players_Vk(main,this),new client_players_Streamable(main,this),new client_players_Peertube(main,this)];
this.iframePlayer = new client_players_Iframe(main,this);
this.rawPlayer = new client_players_Raw(main,this);
+ this.players = [this.youtube,new client_players_Vimeo(main,this),new client_players_Vk(main,this),new client_players_Streamable(main,this),new client_players_Peertube(main,this),this.iframePlayer];
this.initItemButtons();
var resizeObserver = client_Utils.createResizeObserver(function(entries) {
if(_gthis.isVideoLoaded() || _gthis.videoList.items.length == 0) {
@@ -2949,6 +3459,22 @@ var client_Player = function(main) {
_gthis.inUserInteraction = false;
},350);
},{ });
+ this.playerEl.oncontextmenu = function(e) {
+ if(!main.settings.fullscreenAction) {
+ return;
+ }
+ e.preventDefault();
+ var isPlayerFs = window.document.fullscreenElement == _gthis.playerEl;
+ if(isPlayerFs) {
+ if(main.settings.pageFullscreen) {
+ client_Utils.switchToPageFullscreen();
+ } else {
+ client_Utils.cancelFullscreen();
+ }
+ } else {
+ client_Utils.requestFullscreen(_gthis.playerEl);
+ }
+ };
};
client_Player.__name__ = true;
client_Player.prototype = {
@@ -2965,13 +3491,20 @@ client_Player.prototype = {
_gthis.main.send({ type : "SetNextItem", setNextItem : { pos : i}});
}
if(btn.classList.contains("qbtn-tmp")) {
- _gthis.main.send({ type : "ToggleItemType", toggleItemType : { pos : i}});
+ if(e.altKey || e.shiftKey) {
+ _gthis.toggleAllItemTypes(i);
+ } else {
+ _gthis.main.send({ type : "ToggleItemType", toggleItemType : { pos : i}});
+ }
}
if(btn.classList.contains("qbtn-delete")) {
_gthis.main.removeVideoItem(item.querySelector(".qe_title").getAttribute("href"));
}
};
}
+ ,toggleAllItemTypes: function(pos) {
+ this.main.send({ type : "ToggleItemType", toggleItemType : { pos : pos, all : true}});
+ }
,setNextItem: function(pos) {
this.videoList.setNextItem(pos);
var next = this.videoItemsEl.children[pos];
@@ -3055,10 +3588,12 @@ client_Player.prototype = {
return;
}
var item = this.videoList.items[i];
+ if(!_$Types_VideoItemTools.isPlayable(item)) {
+ return;
+ }
this.setSupportedPlayer(item.url,item.playerType);
- this.removeActiveLabel(this.videoList.pos);
this.videoList.setPos(i);
- this.addActiveLabel(this.videoList.pos);
+ this.addActiveItemLabel();
this.canBePlayedSent = false;
if(this.main.isVideoEnabled) {
this.player.loadVideo(item);
@@ -3067,7 +3602,7 @@ client_Player.prototype = {
this.onCanBePlayed();
}
client_JsApi.fireVideoChangeEvents(item);
- window.document.querySelector("#currenttitle").textContent = item.title;
+ this.updateTitle();
}
,setExternalAudioTrack: function(item) {
var _gthis = this;
@@ -3089,7 +3624,7 @@ client_Player.prototype = {
return _gthis.isAudioTrackLoaded = true;
};
this.audioTrack.onerror = function(e) {
- haxe_Log.trace(e,{ fileName : "src/client/Player.hx", lineNumber : 220, className : "client.Player", methodName : "setExternalAudioTrack"});
+ haxe_Log.trace(e,{ fileName : "src/client/Player.hx", lineNumber : 255, className : "client.Player", methodName : "setExternalAudioTrack"});
_gthis.audioTrack.oncanplay = null;
_gthis.audioTrack.onerror = null;
_gthis.isAudioTrackLoaded = false;
@@ -3139,19 +3674,41 @@ client_Player.prototype = {
var _this = this.videoList;
client_JsApi.fireVideoRemoveEvents(_this.items[_this.pos]);
this.player.removeVideo();
- window.document.querySelector("#currenttitle").textContent = Lang.get("nothingPlaying");
+ this.updateTitle();
this.setPauseIndicator(false);
}
+ ,updateTitle: function() {
+ var baseTitle;
+ if(this.isListEmpty()) {
+ baseTitle = Lang.get("nothingPlaying");
+ } else {
+ var _this = this.videoList;
+ baseTitle = _this.items[_this.pos].title;
+ }
+ if(!this.main.lastState.paused) {
+ window.document.querySelector("#currenttitle").textContent = baseTitle;
+ return;
+ }
+ var pauserName = Lang.get("server");
+ if(!this.main.lastState.pausedByServer) {
+ var leader = this.main.getLeader();
+ if(leader != null) {
+ pauserName = leader.name;
+ }
+ }
+ var tmp = "" + ("<span class=\"paused-by\">" + StringTools.htmlEscape(StringTools.replace(Lang.get("pausedBy"),"$NAME",pauserName)) + "</span>") + " ";
+ var tmp1 = StringTools.htmlEscape(baseTitle);
+ window.document.querySelector("#currenttitle").innerHTML = tmp + tmp1;
+ }
,setPauseIndicator: function(isPause) {
if(!this.main.isSyncActive) {
return;
}
- var state = isPause ? "pause" : "play";
- window.document.querySelector("#pause-indicator").setAttribute("name",state);
- var el2 = window.document.querySelector("#pause-indicator-portrait");
- el2.setAttribute("name",state);
+ var el = window.document.querySelector("#pause-indicator-chat");
+ el.setAttribute("name",isPause ? "pause" : "play");
var tmp = isPause || this.main.hasLeader() ? "" : "none";
- el2.style.display = tmp;
+ el.style.display = tmp;
+ this.updateTitle();
}
,onCanBePlayed: function() {
if(!this.canBePlayedSent) {
@@ -3285,8 +3842,9 @@ client_Player.prototype = {
,addVideoItem: function(item,atEnd) {
var url = StringTools.htmlEscape(item.url,true);
var duration = item.playerType == "IframeType" ? "" : this.duration(item.duration);
- var author = StringTools.htmlEscape(item.author);
- var itemEl = client_Utils.nodeFromString("<li class=\"queue_entry info\" title=\"" + Lang.get("addedBy") + ": " + author + "\">\n\t\t\t\t<header>\n\t\t\t\t\t<span class=\"qe_time\">" + duration + "</span>\n\t\t\t\t\t<h4><a class=\"qe_title\" href=\"" + url + "\" target=\"_blank\">" + StringTools.htmlEscape(item.title) + "</a></h4>\n\t\t\t\t\t<small class=\"qe_author\">" + Lang.get("addedBy") + ": " + author + "</small>\n\t\t\t\t</header>\n\t\t\t\t<span class=\"controls\">\n\t\t\t\t\t<button class=\"qbtn-play\" title=\"" + Lang.get("play") + "\"><ion-icon name=\"play\"></ion-icon></button>\n\t\t\t\t\t<button class=\"qbtn-next\" title=\"" + Lang.get("setNext") + "\"><ion-icon name=\"arrow-up\"></ion-icon></button>\n\t\t\t\t\t<button class=\"qbtn-tmp\"><ion-icon></ion-icon></button>\n\t\t\t\t\t<button class=\"qbtn-delete\" title=\"" + Lang.get("delete") + "\"><ion-icon name=\"close\"></ion-icon></button>\n\t\t\t\t</span>\n\t\t\t</li>");
+ var author = StringTools.htmlEscape(item.author,true);
+ var isIncomplete = item.isIncomplete == true;
+ var itemEl = client_Utils.nodeFromString("<li class=\"queue_entry info" + (isIncomplete ? " queue_incomplete" : "") + "\" title=\"" + Lang.get("addedBy") + ": " + author + "\">\n\t\t\t\t" + (isIncomplete ? "<div class=\"qe_progress\"></div>" : "") + "<header>\n\t\t\t\t\t<span class=\"qe_time\">" + duration + "</span>\n\t\t\t\t\t<h4><a class=\"qe_title\" href=\"" + url + "\" target=\"_blank\">" + StringTools.htmlEscape(item.title) + "</a></h4>\n\t\t\t\t\t<small class=\"qe_author\">" + Lang.get("addedBy") + ": " + author + "</small>\n\t\t\t\t</header>\n\t\t\t\t<span class=\"controls\">\n\t\t\t\t\t<button class=\"qbtn-play\" title=\"" + Lang.get("play") + "\"><ion-icon name=\"play\"></ion-icon></button>\n\t\t\t\t\t<button class=\"qbtn-next\" title=\"" + Lang.get("setNext") + "\"><ion-icon name=\"arrow-up\"></ion-icon></button>\n\t\t\t\t\t<button class=\"qbtn-tmp\"><ion-icon></ion-icon></button>\n\t\t\t\t\t<button class=\"qbtn-delete\" title=\"" + Lang.get("delete") + "\"><ion-icon name=\"close\"></ion-icon></button>\n\t\t\t\t</span>\n\t\t\t</li>");
this.videoList.addItem(item,atEnd);
this.setItemElementType(itemEl,item.isTemp);
if(atEnd) {
@@ -3318,17 +3876,61 @@ client_Player.prototype = {
this.setVideo(this.videoList.pos);
}
}
- ,removeElementItem: function(url) {
+ ,findItemElement: function(url) {
var _g = 0;
var _g1 = this.videoItemsEl.children;
while(_g < _g1.length) {
var child = _g1[_g];
++_g;
if(child.querySelector(".qe_title").getAttribute("href") == url) {
- this.videoItemsEl.removeChild(child);
- break;
+ return child;
+ }
+ }
+ return null;
+ }
+ ,removeElementItem: function(url) {
+ var tmp = this.findItemElement(url);
+ if(tmp == null) {
+ return;
+ }
+ this.videoItemsEl.removeChild(tmp);
+ }
+ ,setItemProgress: function(url,ratio) {
+ var tmp = this.findItemElement(url);
+ if(tmp == null) {
+ return;
+ }
+ var tmp1 = tmp.querySelector(".qe_progress");
+ if(tmp1 == null) {
+ return;
+ }
+ tmp1.style.width = "" + ratio * 100 + "%";
+ }
+ ,completeItem: function(url) {
+ var index = this.videoList.findIndex(function(item) {
+ return item.url == url;
+ });
+ if(index == -1) {
+ return;
+ }
+ var item = this.videoList.items[index];
+ if(item.isIncomplete != true) {
+ return;
+ }
+ var wasPlayable = _$Types_VideoItemTools.isPlayable(item);
+ item.isIncomplete = false;
+ var el = this.findItemElement(url);
+ if(el != null) {
+ el.classList.remove("queue_incomplete");
+ el.classList.add("queue_cached");
+ var bar = el.querySelector(".qe_progress");
+ if(bar != null) {
+ el.removeChild(bar);
}
}
+ if(index == this.videoList.pos && !wasPlayable) {
+ this.setVideo(index);
+ }
}
,skipItem: function(url) {
var pos = this.videoList.findIndex(function(item) {
@@ -3337,7 +3939,6 @@ client_Player.prototype = {
if(pos == -1) {
return;
}
- this.removeActiveLabel(this.videoList.pos);
this.videoList.setPos(pos);
var _this = this.videoList;
if(_this.items[_this.pos].isTemp) {
@@ -3350,16 +3951,19 @@ client_Player.prototype = {
}
this.setVideo(this.videoList.pos);
}
- ,addActiveLabel: function(pos) {
+ ,addActiveItemLabel: function() {
var childs = this.videoItemsEl.children;
- if(childs[this.videoList.pos] != null) {
- childs[this.videoList.pos].classList.add("queue_active");
+ var _g = 0;
+ while(_g < childs.length) {
+ var element = childs[_g];
+ ++_g;
+ if(element.classList.contains("queue_active")) {
+ element.classList.remove("queue_active");
+ break;
+ }
}
- }
- ,removeActiveLabel: function(pos) {
- var childs = this.videoItemsEl.children;
if(childs[this.videoList.pos] != null) {
- childs[this.videoList.pos].classList.remove("queue_active");
+ childs[this.videoList.pos].classList.add("queue_active");
}
}
,updateCounters: function() {
@@ -3392,7 +3996,7 @@ client_Player.prototype = {
if(currentUrl != _this.items[_this.pos].url) {
this.setVideo(this.videoList.pos);
} else {
- this.addActiveLabel(this.videoList.pos);
+ this.addActiveItemLabel();
}
}
,clearItems: function() {
@@ -3455,7 +4059,7 @@ client_Player.prototype = {
return this.videoList.pos;
}
,hasVideo: function() {
- return this.playerEl.children.length != 0;
+ return this.playerEl.querySelector("video, iframe, embed, object") != null;
}
,getDuration: function() {
if(this.videoList.pos >= this.videoList.items.length) {
@@ -3612,7 +4216,7 @@ client_Player.prototype = {
}
};
http.onError = function(msg) {
- haxe_Log.trace(msg,{ fileName : "src/client/Player.hx", lineNumber : 677, className : "client.Player", methodName : "skipAd"});
+ haxe_Log.trace(msg,{ fileName : "src/client/Player.hx", lineNumber : 756, className : "client.Player", methodName : "skipAd"});
};
http.request();
}
@@ -3710,6 +4314,12 @@ client_Settings.write = function(data) {
}
client_Settings.storage.setItem("data",JSON.stringify(data));
};
+client_Settings.reset = function() {
+ if(client_Settings.defaults == null) {
+ throw haxe_Exception.thrown("reset: default data is null");
+ }
+ client_Settings.write(client_Settings.defaults);
+};
var client_Split = function(settings) {
this.settings = settings;
this.split = new Split({ columnGutters : [{ element : window.document.querySelector(".gutter"), track : 1}], minSize : 200, snapOffset : 0, onDragEnd : $bind(this,this.saveSize)});
@@ -3801,23 +4411,48 @@ client_Utils.getIndex = function(parent,child) {
return i;
};
client_Utils.hasFullscreen = function() {
- var doc = window.document;
- if(!(window.document.fullscreenElement != null || doc.mozFullScreenElement != null)) {
- return doc.webkitFullscreenElement != null;
- } else {
- return true;
- }
+ return window.document.fullscreenElement != null;
};
client_Utils.requestFullscreen = function(el) {
- var el2 = el;
if(el.requestFullscreen != null) {
el.requestFullscreen();
- } else if(el2.webkitRequestFullscreen != null) {
- el2.webkitRequestFullscreen(HTMLElement.ALLOW_KEYBOARD_INPUT);
+ return true;
+ }
+ return false;
+};
+client_Utils.cancelFullscreen = function(el) {
+ if(window.document.exitFullscreen != null) {
+ window.document.exitFullscreen();
+ }
+};
+client_Utils.switchToPageFullscreen = function() {
+ var docEl = window.document.documentElement;
+ if(window.document.fullscreenElement == docEl) {
+ return;
+ }
+ if(window.document.fullscreenElement == null) {
+ client_Utils.requestFullscreen(docEl);
+ return;
+ }
+ var promise = window.document.exitFullscreen();
+ if(promise != null) {
+ promise.then(function(_) {
+ client_Utils.switchToPageFullscreen();
+ }).catch(function(_) {
+ return;
+ });
} else {
+ haxe_Timer.delay(function() {
+ client_Utils.switchToPageFullscreen();
+ },1);
+ }
+};
+client_Utils.toggleFullscreen = function(el) {
+ if(client_Utils.hasFullscreen()) {
+ client_Utils.cancelFullscreen(el);
return false;
}
- return true;
+ return client_Utils.requestFullscreen(el);
};
client_Utils.copyToClipboard = function(text) {
var clipboardData = window.clipboardData;
@@ -3941,18 +4576,38 @@ var client_players_Iframe = function(main,player) {
this.player = player;
};
client_players_Iframe.__name__ = true;
+client_players_Iframe.extractTwitchChannel = function(url) {
+ url = StringTools.trim(url);
+ if(!client_players_Iframe.matchTwitchChannel.match(url)) {
+ return "";
+ }
+ return client_players_Iframe.matchTwitchChannel.matched(1);
+};
client_players_Iframe.prototype = {
getPlayerType: function() {
return "IframeType";
}
,isSupportedLink: function(url) {
- return true;
+ var trimmed = StringTools.trim(url);
+ if(client_players_Iframe.extractTwitchChannel(trimmed).length > 0) {
+ return true;
+ }
+ if(StringTools.startsWith(trimmed,"<iframe") || StringTools.startsWith(trimmed,"<object")) {
+ return true;
+ }
+ return false;
}
,getVideoData: function(data,callback) {
+ var rawUrl = StringTools.trim(data.url);
+ var channel = client_players_Iframe.extractTwitchChannel(rawUrl);
+ if(channel.length > 0) {
+ callback({ duration : 356400, title : "Twitch: " + channel, url : "<iframe src=\"https://player.twitch.tv/?channel=" + channel + "&parent=www.example.com\" frameborder=\"0\" allowfullscreen=\"true\" scrolling=\"no\" height=\"378\" width=\"620\"></iframe>"});
+ return;
+ }
var iframe = window.document.createElement("div");
- iframe.innerHTML = StringTools.trim(data.url);
+ iframe.innerHTML = rawUrl;
if(this.isValidIframe(iframe)) {
- callback({ duration : 356400, title : "Iframe media"});
+ callback({ duration : 356400, title : "Iframe media", url : rawUrl});
} else {
callback({ duration : 0});
}
@@ -4799,6 +5454,245 @@ client_players_Streamable.prototype = $extend(client_players_Raw.prototype,{
http.request();
}
});
+var client_players_Vimeo = function(main,player) {
+ this.matchVimeoId = new EReg("vimeo\\.com/(?:channels/(?:\\w+/)?|groups/[^/]+/videos/|video/|ondemand/[^/]+/)?([0-9]+)","i");
+ this.matchVimeo = new EReg("(?:vimeo\\.com|player\\.vimeo\\.com)","i");
+ this.prevTime = 0;
+ this.volume = 1;
+ this.playbackRate = 1;
+ this.currentTime = 0;
+ this.isPausedState = true;
+ this.isLoaded = false;
+ this.playerEl = window.document.querySelector("#ytapiplayer");
+ this.main = main;
+ this.player = player;
+};
+client_players_Vimeo.__name__ = true;
+client_players_Vimeo.prototype = {
+ getPlayerType: function() {
+ return "VimeoType";
+ }
+ ,isSupportedLink: function(url) {
+ if(this.matchVimeo.match(url)) {
+ return this.extractVideoId(url).length > 0;
+ } else {
+ return false;
+ }
+ }
+ ,extractVideoId: function(url) {
+ if(!this.matchVimeoId.match(url)) {
+ return "";
+ }
+ return this.matchVimeoId.matched(1);
+ }
+ ,loadApi: function(callback) {
+ if(client_players_Vimeo.isApiLoaded) {
+ callback();
+ return;
+ }
+ client_players_Vimeo.apiCallbacks.push(callback);
+ if(client_players_Vimeo.isApiLoading) {
+ return;
+ }
+ client_players_Vimeo.isApiLoading = true;
+ client_JsApi.addScriptToHead("https://player.vimeo.com/api/player.js",function() {
+ client_players_Vimeo.isApiLoaded = true;
+ client_players_Vimeo.isApiLoading = false;
+ var _g = 0;
+ var _g1 = client_players_Vimeo.apiCallbacks;
+ while(_g < _g1.length) _g1[_g++]();
+ client_players_Vimeo.apiCallbacks = [];
+ });
+ }
+ ,getVideoData: function(data,callback) {
+ var _gthis = this;
+ var url = data.url;
+ if(!this.isSupportedLink(url)) {
+ callback({ duration : 0});
+ return;
+ }
+ var http = new haxe_http_HttpJs("https://vimeo.com/api/oembed.json?url=" + encodeURIComponent(url));
+ http.onData = function(text) {
+ try {
+ var json = JSON.parse(text);
+ var tmp = json.title;
+ var tmp1 = json.duration;
+ callback({ duration : tmp1 != null ? tmp1 : 0, title : tmp != null ? tmp : "Vimeo video", url : url});
+ } catch( _g ) {
+ _gthis.getRemoteDataFallback(url,callback);
+ }
+ };
+ http.onError = function(msg) {
+ _gthis.getRemoteDataFallback(url,callback);
+ };
+ http.request();
+ }
+ ,getRemoteDataFallback: function(url,callback) {
+ var _gthis = this;
+ if(!client_players_Vimeo.isApiLoaded) {
+ this.loadApi(function() {
+ _gthis.getRemoteDataFallback(url,callback);
+ });
+ return;
+ }
+ var tempVideo = window.document.createElement("div");
+ tempVideo.className = "temp-videoplayer";
+ tempVideo.style.display = "none";
+ this.playerEl.appendChild(tempVideo);
+ var tempPlayer = null;
+ tempPlayer = new window.Vimeo.Player(tempVideo, { url : url});
+ tempPlayer.on("loaded",function() {
+ return tempPlayer.getDuration().then(function(dur) {
+ try {
+ tempPlayer.destroy();
+ } catch( _g ) {
+ }
+ if(_gthis.playerEl.contains(tempVideo)) {
+ _gthis.playerEl.removeChild(tempVideo);
+ }
+ callback({ title : "Vimeo video", duration : dur, url : url});
+ });
+ });
+ tempPlayer.on("error",function(err) {
+ try {
+ tempPlayer.destroy();
+ } catch( _g ) {
+ }
+ if(_gthis.playerEl.contains(tempVideo)) {
+ _gthis.playerEl.removeChild(tempVideo);
+ }
+ callback({ duration : 0});
+ });
+ }
+ ,loadVideo: function(item) {
+ var _gthis = this;
+ if(!client_players_Vimeo.isApiLoaded) {
+ this.loadApi(function() {
+ _gthis.loadVideo(item);
+ });
+ return;
+ }
+ this.removeVideo();
+ this.video = window.document.createElement("div");
+ this.video.id = "videoplayer";
+ this.playerEl.appendChild(this.video);
+ this.vimeoPlayer = new window.Vimeo.Player(this.video, { url : item.url, autoplay : true, playsinline : true, responsive : true});
+ this.vimeoPlayer.on("loaded",function() {
+ if(!_gthis.main.isAutoplayAllowed()) {
+ _gthis.vimeoPlayer.setVolume(0);
+ }
+ _gthis.isLoaded = true;
+ if(_gthis.main.lastState.paused) {
+ _gthis.vimeoPlayer.pause();
+ }
+ _gthis.player.onCanBePlayed();
+ });
+ this.vimeoPlayer.on("play",function() {
+ _gthis.isPausedState = false;
+ if(!_gthis.isLoaded) {
+ _gthis.isLoaded = true;
+ _gthis.player.onCanBePlayed();
+ }
+ _gthis.player.onPlay();
+ });
+ this.vimeoPlayer.on("pause",function() {
+ _gthis.isPausedState = true;
+ if(!_gthis.isLoaded) {
+ _gthis.isLoaded = true;
+ _gthis.player.onCanBePlayed();
+ }
+ _gthis.player.onPause();
+ });
+ this.vimeoPlayer.on("timeupdate",function(data) {
+ var sec = data.seconds;
+ _gthis.currentTime = sec;
+ var diff = Math.abs(_gthis.prevTime - sec);
+ _gthis.prevTime = sec;
+ if(diff > 1) {
+ _gthis.player.onSetTime();
+ }
+ });
+ this.vimeoPlayer.on("ratechange",function(data) {
+ _gthis.playbackRate = data.playbackRate;
+ _gthis.player.onRateChange();
+ });
+ this.vimeoPlayer.on("volumechange",function(data) {
+ return _gthis.volume = data.volume;
+ });
+ this.vimeoPlayer.on("error",function(data) {
+ haxe_Log.trace("Vimeo error: " + data,{ fileName : "src/client/players/Vimeo.hx", lineNumber : 234, className : "client.players.Vimeo", methodName : "loadVideo"});
+ });
+ }
+ ,removeVideo: function() {
+ if(this.video == null) {
+ return;
+ }
+ this.isLoaded = false;
+ this.isPausedState = true;
+ if(this.vimeoPlayer != null) {
+ try {
+ this.vimeoPlayer.destroy();
+ } catch( _g ) {
+ haxe_Log.trace(haxe_Exception.caught(_g),{ fileName : "src/client/players/Vimeo.hx", lineNumber : 246, className : "client.players.Vimeo", methodName : "removeVideo"});
+ }
+ this.vimeoPlayer = null;
+ }
+ if(this.playerEl.contains(this.video)) {
+ this.playerEl.removeChild(this.video);
+ }
+ this.video = null;
+ }
+ ,isVideoLoaded: function() {
+ return this.isLoaded;
+ }
+ ,play: function() {
+ if(this.vimeoPlayer != null) {
+ this.vimeoPlayer.play();
+ }
+ }
+ ,pause: function() {
+ if(this.vimeoPlayer != null) {
+ this.vimeoPlayer.pause();
+ }
+ }
+ ,isPaused: function() {
+ return this.isPausedState;
+ }
+ ,getTime: function() {
+ return this.currentTime;
+ }
+ ,setTime: function(time) {
+ this.currentTime = time;
+ if(this.vimeoPlayer != null) {
+ this.vimeoPlayer.setCurrentTime(time);
+ }
+ }
+ ,getPlaybackRate: function() {
+ return this.playbackRate;
+ }
+ ,setPlaybackRate: function(rate) {
+ this.playbackRate = rate;
+ if(this.vimeoPlayer != null) {
+ this.vimeoPlayer.setPlaybackRate(rate);
+ }
+ }
+ ,getVolume: function() {
+ return this.volume;
+ }
+ ,setVolume: function(vol) {
+ this.volume = vol;
+ if(this.vimeoPlayer != null) {
+ this.vimeoPlayer.setVolume(vol);
+ }
+ }
+ ,unmute: function() {
+ if(this.vimeoPlayer == null) {
+ return;
+ }
+ this.vimeoPlayer.setMuted(false);
+ this.vimeoPlayer.setVolume(1);
+ }
+};
var client_players_Vk = function(main,player) {
this.matchIds = new EReg("video(-?[0-9]+)_([0-9]+)","g");
this.matchVk = new EReg("(vk.com/video|vkvideo)","g");
@@ -6025,18 +6919,6 @@ js_youtube_Youtube.init = function(onAPIReady) {
}
};
};
-var tools_MathTools = function() { };
-tools_MathTools.__name__ = true;
-tools_MathTools.toFixed = function(v,digits) {
- if(digits == null) {
- digits = 2;
- }
- if(digits > 8) {
- throw haxe_Exception.thrown("digits is " + digits + ", but cannot be bigger than 8 (for value " + v + ")");
- }
- var ratio = Math.pow(10,digits);
- return (v * ratio | 0) / ratio;
-};
var utils_YoutubeUtils = function() { };
utils_YoutubeUtils.__name__ = true;
utils_YoutubeUtils.extractVideoId = function(url) {
@@ -6074,6 +6956,8 @@ js_Boot.__toStr = ({ }).toString;
Lang.langs = new haxe_ds_StringMap();
Lang.ids = ["en","ru"];
Lang.lang = HxOverrides.substr($global.navigator.language,0,2).toLowerCase();
+client_Buttons.hasUnreadChatMessages = false;
+client_Commands.list = [new client_CommandInfo("/help",0,null),new client_CommandInfo("/clear",0,null),new client_CommandInfo("/ban",2,null),new client_CommandInfo("/unban",1,null),new client_CommandInfo("/kick",1,null),new client_CommandInfo("/ad",0,null),new client_CommandInfo("/volume",1,null),new client_CommandInfo("/dump",0,null),new client_CommandInfo("/crash",0,null),new client_CommandInfo("/giveup",1,null),new client_CommandInfo("/fb",0,["/flashback"])];
client_JsApi.subtitleFormats = [];
client_JsApi.videoChange = [];
client_JsApi.videoRemove = [];
@@ -6081,7 +6965,11 @@ client_JsApi.onListeners = [];
client_JsApi.onceListeners = [];
client_Settings.isSupported = false;
client_Utils.isMacSafari = client_Utils._isMacSafari();
+client_players_Iframe.matchTwitchChannel = new EReg("^(?:https?://)?(?:www\\.|m\\.)?twitch\\.tv/([A-Za-z0-9_]+)/?$","i");
client_players_RawSubs.assTimeStamp = new EReg("([0-9]+):([0-9][0-9]):([0-9][0-9]).([0-9][0-9])","");
+client_players_Vimeo.isApiLoaded = false;
+client_players_Vimeo.isApiLoading = false;
+client_players_Vimeo.apiCallbacks = [];
haxe_crypto_Base64.CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
haxe_crypto_Base64.BYTES = haxe_io_Bytes.ofString(haxe_crypto_Base64.CHARS);
js_youtube_Youtube.isLoadedAPI = false;
diff --git a/res/css/des.css b/res/css/des.css
index 99a2c58..da003e7 100644
--- a/res/css/des.css
+++ b/res/css/des.css
@@ -39,6 +39,7 @@
--warning: var(--cl-yellow);
--error: var(--cl-red-6);
--border: var(--cl-gray-3);
+ --paused-by: var(--cl-white);
--box-shadow: 0rem .25rem 1rem rgba(0, 0, 0, .25);
}
@@ -392,6 +393,10 @@ button.danger-bg:focus {
overflow: hidden;
}
+.paused-by {
+ color: var(--paused-by);
+}
+
.info header {
display: flex;
align-items: center;
@@ -431,6 +436,83 @@ header h4 {
align-items: center;
}
+.playlist-menu-wrap {
+ position: relative;
+ display: inline-flex;
+ margin-left: .5rem;
+}
+
+#playlist-menu-btn {
+ position: relative;
+}
+
+#playlist-menu-indicator {
+ position: absolute;
+ top: 4px;
+ right: 4px;
+ width: 8px;
+ height: 8px;
+ background-color: var(--accent);
+ border-radius: 50%;
+}
+
+.playlist-menu-dropdown {
+ position: absolute;
+ top: calc(100% + .5rem);
+ right: 0;
+ background: var(--background-chat);
+ border: .063rem solid var(--border);
+ border-radius: 0;
+ box-shadow: var(--box-shadow);
+ z-index: 1000;
+ min-width: 14rem;
+ padding: .35rem 0;
+ display: flex;
+ flex-direction: column;
+}
+
+.playlist-menu-dropdown button.playlist-menu-item {
+ display: flex;
+ align-items: center;
+ width: 100%;
+ padding: .6rem 1rem;
+ margin-left: 0;
+ background: transparent;
+ border-radius: 0;
+ text-align: left;
+ white-space: nowrap;
+ font-size: .9rem;
+ color: var(--midground);
+ transition: background-color ease .15s, color ease .15s;
+}
+
+.playlist-menu-dropdown button.playlist-menu-item:hover,
+.playlist-menu-dropdown button.playlist-menu-item.active {
+ background-color: rgba(255, 255, 255, .08);
+ color: var(--foreground);
+}
+
+.playlist-menu-dropdown button.playlist-menu-item ion-icon {
+ margin-right: .6rem;
+ font-size: 1.1rem;
+}
+
+.playlist-menu-dropdown button.playlist-menu-item .hotkey-hint {
+ color: var(--midground);
+ opacity: .75;
+ font-size: .85em;
+ margin-left: .25rem;
+}
+
+.hotkeys-disabled .hotkey-hint {
+ display: none;
+}
+
+#playlist-menu-btn.danger,
+#playlist-menu-btn.danger ion-icon {
+ color: var(--error);
+}
+
/*
* Start main
*/
@@ -453,9 +535,7 @@ header h4 {
grid-area: video;
width: 100%;
height: 100%;
- /* Breaks subs after viewport overflow on FF+Linux (SyncTube#28) */
- /* overflow: auto; */
- overflow: scroll;
+ overflow: auto;
background: var(--background-video);
}
}
@@ -488,14 +568,54 @@ header h4 {
/* Embed responsive */
-.embed-responsive:empty {
- min-height: 60vh;
+.embed-responsive:empty,
+.embed-responsive:not(:has(iframe, video, object, embed)) {
+ aspect-ratio: 16 / 9;
+ min-height: unset;
}
.embed-responsive {
background-color: var(--background);
width: 100%;
max-height: 80vh;
+ position: relative;
+}
+
+.fullscreen-chat-btn {
+ display: none;
+ position: absolute;
+ top: 15px;
+ right: 15px;
+ z-index: 1;
+ background: rgba(0, 0, 0, 0.5);
+ opacity: 0.5;
+ border-radius: 50%;
+ width: 42px;
+ height: 42px;
+ justify-content: center;
+ outline: none;
+ transition: opacity 0.2s ease, background-color 0.2s ease, transform 0.15s ease;
+}
+
+.fullscreen-chat-btn:hover {
+ opacity: 1;
+ background: rgba(0, 0, 0, 0.75);
+ color: #ffffff;
+ transform: scale(1.08);
+}
+
+.fullscreen-chat-btn:active {
+ transform: scale(0.95);
+}
+
+.fullscreen-chat-btn ion-icon {
+ color: white;
+ font-size: 22px;
+}
+
+/* Fullscreen chat button display only when unread messages exist */
+#ytapiplayer:fullscreen .fullscreen-chat-btn.has-unread {
+ display: flex;
}
.temp-videoplayer {
@@ -507,6 +627,10 @@ header h4 {
max-height: 80vh;
}
+#ytapiplayer:fullscreen #videoplayer {
+ max-height: unset;
+}
+
.videoplayerIframeParent {
width: 100%;
height: 100%;
@@ -520,10 +644,6 @@ iframe#videoplayer {
}
@media only screen and (orientation: portrait) {
- .embed-responsive:empty {
- min-height: 20vh;
- }
-
.embed-responsive {
max-height: 30vh;
}
@@ -623,6 +743,8 @@ iframe#videoplayer {
}
.queue_entry {
+ position: relative;
+ overflow: hidden;
padding: 1rem;
border-bottom: .063rem solid;
border-color: var(--border);
@@ -632,9 +754,56 @@ iframe#videoplayer {
transition: background-color ease-in-out .15s;
}
+.queue_entry>header,
+.queue_entry>.controls {
+ position: relative;
+ z-index: 1;
+}
+
+.qe_progress {
+ position: absolute;
+ left: 0;
+ top: 0;
+ z-index: 0;
+ height: 100%;
+ width: 0;
+ background-color: var(--background-chat);
+ pointer-events: none;
+ transition: width ease-out .2s;
+}
+
+.queue_cached {
+ background-color: var(--background-chat);
+}
+
+.queue_entry.queue_incomplete {
+ background-color: transparent;
+}
+
+.queue_incomplete>header,
+.queue_incomplete .qe_title {
+ color: color-mix(in srgb, var(--foreground) 50%, transparent);
+}
+
+.queue_incomplete .controls button {
+ background-color: color-mix(in srgb, var(--background-chat) 50%, transparent);
+}
+
+.queue_entry.queue_incomplete {
+ border-left: .25rem solid;
+ border-left-color: var(--background-chat);
+ animation: queue_incomplete_blinker 3s ease-in-out infinite;
+}
+
+@keyframes queue_incomplete_blinker {
+ 50% {
+ border-left-color: var(--border);
+ }
+}
+
.queue_entry:first-child {
border-top: .063rem solid;
- border-color: var(--border);
+ border-top-color: var(--border);
}
.queue_entry a {
@@ -654,10 +823,6 @@ footer#footer {
padding: 1rem;
}
-/*
- * End main
- */
-
.gutter {
display: none;
}
@@ -687,10 +852,26 @@ footer#footer {
order: 0;
flex-direction: column;
flex-wrap: nowrap;
- padding: 1rem;
+ container-type: inline-size;
+ --chat-px: clamp(0.25rem, 10cqw - 1rem, 1rem);
+ --chat-py: clamp(0.25rem, 2vh, 1rem);
+ padding: 0;
height: 40vh;
}
+#chat>* {
+ padding-left: var(--chat-px);
+ padding-right: var(--chat-px);
+}
+
+#chat>*:first-child {
+ padding-top: var(--chat-py);
+}
+
+#chat>#chat-body {
+ padding-bottom: var(--chat-py);
+}
+
@media only screen and (orientation: landscape) {
#chat {
grid-area: chat;
@@ -707,7 +888,15 @@ footer#footer {
#chat .controls {
display: flex;
justify-content: space-between;
- padding-bottom: 1rem;
+ padding-bottom: clamp(0.25rem, 1.5vh, 1rem);
+}
+
+#chat-body {
+ position: relative;
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ min-height: 0;
}
/* Users online */
@@ -763,7 +952,7 @@ footer#footer {
text-align: left;
}
-#pause-indicator-portrait {
+#pause-indicator-chat {
padding-left: 0.2rem;
}
@@ -780,38 +969,91 @@ footer#footer {
/* Options */
+.toggle-switch {
+ position: relative;
+ display: inline-block;
+ width: 44px;
+ height: 24px;
+ background-color: var(--midground);
+ border-radius: 24px;
+ transition: background-color 0.3s;
+ flex-shrink: 0;
+ margin-left: 1rem;
+ cursor: pointer;
+}
+
+.toggle-switch.on {
+ background-color: var(--accent);
+}
+
+.toggle-switch::after {
+ content: '';
+ position: absolute;
+ top: 2px;
+ left: 2px;
+ width: 20px;
+ height: 20px;
+ background-color: #fff;
+ border-radius: 50%;
+ transition: transform 0.2s ease;
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
+}
+
+.toggle-switch.on::after {
+ transform: translateX(20px);
+}
+
#optionsPanel {
+ display: flex;
+ flex-direction: column;
z-index: 1;
background-color: var(--background-chat);
- overflow-y: scroll;
position: absolute;
- top: 4rem;
- left: 1rem;
- bottom: 1rem;
- right: 1rem;
+ /* to prevent fraction gap between chat controls and this panel */
+ top: -1px;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ padding-left: var(--chat-px);
+ padding-right: var(--chat-px);
+ padding-bottom: var(--chat-py);
+ padding-top: 1rem;
transition: opacity 200ms;
opacity: 0;
}
-#optionsList div {
+#optionsList {
+ overflow-y: auto;
+}
+
+.options-category {
display: flex;
flex-direction: column;
- margin-bottom: .5rem;
+ margin-bottom: 0.5rem;
}
-#optionsList div:not(:first-child) {
+.options-category:not(:first-child) {
border-top: .063rem solid;
border-color: var(--border);
- padding-top: 1rem;
+ padding-top: 1.25rem;
+}
+
+#optionsList h4 {
+ padding: 0 1.25rem 0.75rem 0;
+ font-size: 1.15rem;
+ font-weight: 600;
+ color: #fff;
}
#optionsList li button {
- padding: 1rem 0;
+ padding: 0.8rem 0.8rem 0.8rem 0;
display: flex;
- align-items: stretch;
+ align-items: center;
+ justify-content: space-between;
width: 100%;
border-radius: 0;
text-align: left;
+ font-size: 1rem;
}
#optionsList li button:hover {
@@ -915,7 +1157,7 @@ footer#footer {
}
#chatbox {
- padding-top: 1rem;
+ padding-top: clamp(0.25rem, 1.5vh, 1rem);
border-top: .063rem solid;
border-color: var(--border);
display: flex;
@@ -931,6 +1173,66 @@ footer#footer {
width: 2rem;
}
+#chat-commands-wrap {
+ display: flex;
+ width: 100%;
+ background: var(--cl-gray-2);
+ border-radius: 0;
+ border-bottom-left-radius: 0;
+ border-bottom-right-radius: 0;
+ overflow: hidden;
+ flex-direction: column;
+ position: absolute;
+ bottom: 100%;
+ z-index: 1;
+}
+
+#chat-commands-list {
+ display: flex;
+ max-height: 50vh;
+ overflow-y: auto;
+ flex-direction: column;
+ width: 100%;
+ padding: 0.5rem;
+}
+
+.chat-command-item {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 0.25rem;
+ padding: 0.5rem 1rem;
+ cursor: pointer;
+ border-radius: 0;
+}
+
+.chat-command-item.active {
+ background: var(--cl-gray-3);
+}
+
+.chat-command-item-name {
+ font-weight: bold;
+}
+
+.chat-command-item-desc {
+ font-size: 0.8rem;
+ opacity: 0.8;
+}
+
+.chat-command-arg-active {
+ color: var(--accent);
+ font-weight: bold;
+ opacity: 1;
+}
+
+.chat-command-code {
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
+ background-color: var(--cl-gray-1);
+ color: var(--foreground);
+ padding: 0.1rem 0.3rem;
+ border-radius: 0;
+}
+
/* Emotes */
#smiles-wrap {
@@ -1006,10 +1308,6 @@ footer#footer {
}
/*
- * End chat
- */
-
-/*
* Scrollbar
*/
@@ -1028,28 +1326,9 @@ html {
::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.1);
+ border-radius: .5rem;
}
::-webkit-scrollbar-thumb:hover {
background: rgba(255, 255, 255, 0.2);
}
-
-/* Mobile page fullscreen */
-
-.mobile-view #chatbox {
- padding-top: 0;
- border-top: 0;
-}
-
-.mobile-view #chat {
- padding: 0 1rem;
-}
-
-.mobile-view #chat .controls {
- padding-bottom: 0;
-}
-
-.mobile-view #optionsPanel {
- top: 2.2rem;
- bottom: 0;
-}
diff --git a/res/index.html b/res/index.html
index 02ef525..3e2673a 100644
--- a/res/index.html
+++ b/res/index.html
@@ -25,7 +25,11 @@
<main id="video">
<!-- Player -->
<section id="player">
- <div id="ytapiplayer" class="embed-responsive"></div>
+ <div id="ytapiplayer" class="embed-responsive">
+ <button id="fullscreen-chat-btn" class="fullscreen-chat-btn">
+ <ion-icon name="chatbubble"></ion-icon>
+ </button>
+ </div>
<!-- Video info -->
<div class="info video-info">
<header>
@@ -33,18 +37,12 @@
</header>
<!-- Video controls -->
<span class="controls">
- <button id="togglesynch" title="${toggleVideoSync}">
- <ion-icon id="pause-indicator" name="play"></ion-icon>
- </button>
<button id="mediarefresh" title="${refreshPlayer}">
<ion-icon name="refresh"></ion-icon>
</button>
<button id="fullscreenbtn" title="${fullscreenPlayer}">
<ion-icon name="expand"></ion-icon>
</button>
- <button id="voteskip" title="${voteForSkip}">
- <ion-icon name="play-skip-forward"></ion-icon>
- </button>
</span>
</div>
</section>
@@ -67,16 +65,36 @@
data-target="#addfromurl" aria-expanded="false">
<ion-icon name="add"></ion-icon>
</button>
- <button class="collapsed" id="showcustomembed" title="${embedCustomFrame}" data-toggle="collapse"
- data-target="#customembed" aria-expanded="false">
- <ion-icon name="code"></ion-icon>
- </button>
- <button id="shuffleplaylist" title="${shufflePlaylist}">
- <ion-icon name="shuffle"></ion-icon>
- </button>
- <button id="clearplaylist" title="${clearPlaylist}">
- <ion-icon name="close"></ion-icon>
- </button>
+ <div class="playlist-menu-wrap">
+ <button id="playlist-menu-btn">
+ <ion-icon name="menu"></ion-icon>
+ <div id="playlist-menu-indicator" style="display: none;">
+ </div>
+ </button>
+ <div id="playlist-menu-dropdown" class="playlist-menu-dropdown" style="display: none;">
+ <button class="playlist-menu-item" id="voteskip" title="${voteForSkip}">
+ <ion-icon name="play-skip-forward"></ion-icon>
+ <span><span id="voteskip-text">${voteForSkip}</span> <span class="hotkey-hint">(Alt-S)</span></span>
+ </button>
+ <button class="playlist-menu-item" id="togglesynch" title="${disableVideoSync}">
+ <ion-icon name="unlink"></ion-icon>
+ <span>${disableVideoSync} <span class="hotkey-hint">(Alt-U)</span></span>
+ </button>
+ <button class="collapsed playlist-menu-item" id="showcustomembed" data-toggle="collapse"
+ data-target="#customembed" aria-expanded="false">
+ <ion-icon name="code"></ion-icon>
+ <span>${embedCustomFrame}</span>
+ </button>
+ <button class="playlist-menu-item" id="shuffleplaylist">
+ <ion-icon name="shuffle"></ion-icon>
+ <span>${shufflePlaylist}</span>
+ </button>
+ <button class="playlist-menu-item" id="clearplaylist">
+ <ion-icon name="close"></ion-icon>
+ <span>${clearPlaylist}</span>
+ </button>
+ </div>
+ </div>
</span>
</div>
<!-- Playlist metadata -->
@@ -112,7 +130,7 @@
<input id="voiceoverurl" type="text" placeholder="${voiceOverAudioTrackUrlOptional}">
</div>
<div class="checkbox-row">
- <label class="checkbox-row-item">
+ <label class="checkbox-row-item" style="display: none;">
<input id="add-temp" class="add-temp" type="checkbox" checked>${addAsTemporary}
</label>
<label class="checkbox-row-item">
@@ -131,9 +149,6 @@
<input id="customembed-title" type="text" placeholder="${optionalTitle}">
<textarea id="customembed-content" rows="5"
placeholder="${pasteEmbedCodeAndClick}. &#10;${acceptableEmbedCodesAre} &lt;iframe&gt; ${or} &lt;object&gt;. &#10;${customEmbedsCannotBeSynchronized}."></textarea>
- <label class="checkbox-row-item">
- <input id="add-temp-embed" class="add-temp" type="checkbox" checked>${addAsTemporary}
- </label>
<div class="queue-buttons">
<button id="ce_queue_next">${queueNext}</button>
<button id="ce_queue_end">${queueLast}</button>
@@ -163,7 +178,7 @@
<button id="userlisttoggle" title="${toggleUserList}">
<ion-icon name="chevron-down"></ion-icon>
<span id="usercount">${connection}...</span>
- <ion-icon id="pause-indicator-portrait" name="play" style="display: none;"></ion-icon>
+ <ion-icon id="pause-indicator-chat" name="play" style="display: none;"></ion-icon>
</button>
<span>
<button id="togglechat" title="Collapse Chat">
@@ -176,68 +191,75 @@
</button>
</span>
</div>
- <!-- User list -->
- <div id="userlist-wrap" class="collapsible">
- <div id="userlist"></div>
- </div>
- <!-- Settings list -->
- <ul id="optionsPanel" class="collapse" aria-expanded="false">
- <div id="optionsList" class="collapse">
- <div>
- <div>
- <h4>${account}</h4>
- <li><button id="exitBtn">${login}</button></li>
- </div>
- <div>
- <h4>${general}</h4>
- <li><button id="hotkeysBtn"><span>${hotkeys}</span></button></li>
- <li><button id="swapLayoutBtn" title="${swapLayout}"><span>${swapLayout}</span></button></li>
- </div>
+ <div id="chat-body">
+ <!-- User list -->
+ <div id="userlist-wrap" class="collapsible">
+ <div id="userlist"></div>
+ </div>
+ <!-- Settings list -->
+ <ul id="optionsPanel" class="collapse" aria-expanded="false">
+ <div id="optionsList" class="collapse">
<div>
- <h4>${video}</h4>
- <li><button id="synchThresholdBtn"><span>${synchThreshold}</span></button></li>
- <li><button id="setVideoUrlBtn"><span>${setVideoUrl}</span></button></li>
- <li><button id="selectLocalVideoBtn"><span>${selectLocalVideo}</span></button></li>
- <li><button id="removePlayerBtn"><span>${removePlayer}</span></button></li>
- <li><a href="/admin-register"><button><span>${registerAdmin}</span></button></a></li>
- </div>
- <div id="adminMenu" style="display: none;">
- <h4>${chat}</h4>
- <li><button id="clearchatbtn"><span>${clearChat}</span></button></li>
+ <div class="options-category">
+ <h4>${account}</h4>
+ <li><button id="exitBtn">${login}</button></li>
+ <li><a href="/admin-register"><button><span>${registerAdmin}</span></button></a></li>
+ </div>
+ <div class="options-category">
+ <h4>${general}</h4>
+ <li><button id="swapLayoutBtn" title="${swapLayout}"><span>${swapLayout}</span></button></li>
+ <li><button id="pageFullscreenBtn" title="${pageFullscreen}"><span>${pageFullscreen}</span></button></li>
+ <li><button id="synchThresholdBtn"><span>${synchThreshold}</span></button></li>
+ <li><button id="hotkeysBtn"><span>${hotkeys}</span></button></li>
+ </div>
+ <div class="options-category">
+ <h4>${player}</h4>
+ <li><button id="fullscreenActionBtn"></button></li>
+ <li><button id="setVideoUrlBtn"><span>${setVideoUrl}</span></button></li>
+ <li><button id="selectLocalVideoBtn"><span>${selectLocalVideo}</span></button></li>
+ <li><button id="removePlayerBtn"><span>${removePlayer}</span></button></li>
+ </div>
+ <div id="adminMenu" class="options-category" style="display: none;">
+ <h4>${chat}</h4>
+ <li><button id="clearchatbtn"><span>${clearChat}</span></button></li>
+ </div>
</div>
</div>
- </div>
- </ul>
- <!-- Messages -->
- <div id="messagebuffer" style="flex-direction: column-reverse;"></div>
- <div id="chat-inputs-wrapper">
- <div id="dynamic-chin" class="collapsed" style="display: none;"></div>
- <button id="scroll-to-chat-end" style="display: none; opacity: 0;" class="active">
- <ion-icon name="arrow-down" role="img" class="md hydrated"></ion-icon>
- </button>
- <!-- Message input -->
- <div id="chatbox">
- <input id="chatline" type="text" placeholder="${chatlinePlaceholder}">
- <button id="smilesbtn" title="${emotes}">
- <ion-icon name="happy"></ion-icon>
+ </ul>
+ <!-- Messages -->
+ <div id="messagebuffer" style="flex-direction: column-reverse;"></div>
+ <div id="chat-inputs-wrapper">
+ <div id="dynamic-chin" class="collapsed" style="display: none;"></div>
+ <button id="scroll-to-chat-end" style="display: none; opacity: 0;" class="active">
+ <ion-icon name="arrow-down" role="img" class="md hydrated"></ion-icon>
</button>
- </div>
- <div id="smiles-wrap" class="collapsible" style="display: none;">
- <div id="smiles-list"></div>
- </div>
- <!-- Guest login -->
- <div id="guestlogin" style="display: none;">
- <label>${enterAsGuest}</label>
- <input id="guestname" type="text" placeholder="${yourName}">
- </div>
- <div id="guestpassword" style="display: none;">
- <label>${enterUserPassword}</label>
- <div id="passwordbox">
- <input id="guestpass" type="text" placeholder="${yourPassword}">
- <button id="guestpass_icon">
- <ion-icon name="eye"></ion-icon>
+ <div id="chat-commands-wrap" class="collapsible" style="display: none;">
+ <div id="chat-commands-list"></div>
+ </div>
+ <!-- Message input -->
+ <div id="chatbox">
+ <input id="chatline" type="text" placeholder="${chatlinePlaceholder}">
+ <button id="smilesbtn" title="${emotes}">
+ <ion-icon name="happy"></ion-icon>
</button>
</div>
+ <div id="smiles-wrap" class="collapsible" style="display: none;">
+ <div id="smiles-list"></div>
+ </div>
+ <!-- Guest login -->
+ <div id="guestlogin" style="display: none;">
+ <label>${enterAsGuest}</label>
+ <input id="guestname" type="text" placeholder="${yourName}">
+ </div>
+ <div id="guestpassword" style="display: none;">
+ <label>${enterUserPassword}</label>
+ <div id="passwordbox">
+ <input id="guestpass" type="text" placeholder="${yourPassword}">
+ <button id="guestpass_icon">
+ <ion-icon name="eye"></ion-icon>
+ </button>
+ </div>
+ </div>
</div>
</div>
</aside>
diff --git a/res/langs/en.json b/res/langs/en.json
index e0190c6..1bc7ecb 100644
--- a/res/langs/en.json
+++ b/res/langs/en.json
@@ -106,15 +106,32 @@
"clearPlaylistConfirm": "Are you sure you want to clear the playlist?",
"shufflePlaylistConfirm": "Are you sure you want to shuffle the playlist?",
"lockPlaylistConfirm": "Are you sure you want to lock the playlist?",
-
"yes": "Yes",
"no": "No",
"on": "On",
"off": "Off",
-
"areYouSure": "Are you sure?",
"dataWillBeLost": "The data will be lost.",
-
"setupTitle": "Welcome to Dohee Cinema!",
- "registerAdmin": "Register as Admin"
+ "registerAdmin": "Register as Admin",
+ "cmdHelp": "Show introduction message",
+ "cmdClear": "Clear the chat history",
+ "cmdBan": "`/ban <name> <time>` - Ban a user",
+ "cmdUnban": "`/unban <name>` - Unban a user",
+ "cmdKick": "`/kick <name>` - Kick a user",
+ "cmdAd": "Skip a sponsored segment in the video",
+ "cmdVolume": "`/volume <0-3>` - Set the player volume",
+ "cmdDump": "Dump the current server state to a file",
+ "cmdCrash": "Simulate a server crash",
+ "cmdFb": "Rewind to the last remembered time (before a seek or skip)",
+ "player": "Player",
+ "pageFullscreen": "Page Fullscreen",
+ "fullscreenActionTouch": "Long-tap Fullscreen",
+ "fullscreenActionDesktop": "Right-click Fullscreen",
+ "pausedBy": "(Paused by $NAME)",
+ "server": "Server",
+ "disableVideoSync": "Disable video synchronization",
+ "enableVideoSync": "Enable video synchronization",
+ "skipVoteInitiated": "Skip vote started",
+ "cmdGiveup": "`/giveup <name>` - Transfer leadership to another user"
}
diff --git a/res/langs/ru.json b/res/langs/ru.json
index 7ae9138..2b9bde9 100644
--- a/res/langs/ru.json
+++ b/res/langs/ru.json
@@ -23,6 +23,17 @@
"accessError": "Ошибка доступа",
"noPermission": "Нет '$PERMISSION' разрешения.",
"totalVideoLimitError": "Был достингут лимит видео в плейлисте.",
+ "cmdGiveup": "`/giveup <name>` - Передать управление другому пользователю",
+ "cmdHelp": "Показать вступительное сообщение",
+ "cmdClear": "Очистить историю чата",
+ "cmdBan": "`/ban <имя> <время>` - Заблокировать пользователя",
+ "cmdUnban": "`/unban <имя>` - Разблокировать пользователя",
+ "cmdKick": "`/kick <имя>` - Выгнать пользователя",
+ "cmdAd": "Пропустить спонсорскую вставку в видео",
+ "cmdVolume": "`/volume <0-3>` - Установить громкость плеера",
+ "cmdDump": "Сохранить текущее состояние сервера в файл",
+ "cmdCrash": "Симулировать сбой сервера",
+ "cmdFb": "Вернуться к предыдущему времени (до перемотки)",
"userVideoLimitError": "Был достингут лимит видео на пользователя.",
"videoAlreadyExistsError": "Видео уже имеется в плейлисте.",
"addVideoError": "Не удалось добавить видео.",
@@ -47,10 +58,13 @@
"synchThreshold": "Частота синхронизации",
"general": "Основное",
"hotkeys": "Горячие клавиши",
- "video": "Видео",
+ "player": "Плеер",
"channel": "Канал",
"layout": "Разметка",
"swapLayout": "Сменить разметку",
+ "pageFullscreen": "Полноэкранная страница",
+ "fullscreenActionTouch": "Полный экран по долгому нажатию",
+ "fullscreenActionDesktop": "Полный экран по правому клику",
"chatOnly": "Только чат",
"setVideoUrl": "Задать ссылку на видео",
"setVideoUrlPrompt": "Новая ссылка на видео:\n(Не влияет на других пользователей)",
@@ -71,6 +85,8 @@
"clearChat": "Очистить чат",
"chatlinePlaceholder": "Введите сообщение...",
"leaderDisconnectedServerOnPause": "Лидер вышел, сервер на паузе.",
+ "pausedBy": "(Пауза: $NAME)",
+ "server": "Сервер",
"unpause": "Возобновить",
"addVideoFromUrl": "Добавить видео по ссылке",
"embedCustomFrame": "Добавить iframe",
@@ -80,12 +96,14 @@
"playlistOpen": "Плейлист открыт",
"playlistLocked": "Плейлист заблокирован",
"expandPlayer": "Расширить плеер",
- "toggleVideoSync": "Переключить синхронизацию видео",
+ "disableVideoSync": "Отключить синхронизацию видео",
+ "enableVideoSync": "Включить синхронизацию видео",
"toggleSynchConfirm": "Вы уверены что хотите отключить синхронизацию видео?",
"refreshPlayer": "Обновить плеер",
"fullscreenPlayer": "Полноэкранный режим видео",
"retrievePlaylistLinks": "Получить ссылки на видео из плейлиста",
"voteForSkip": "Голосовать за пропуск",
+ "skipVoteInitiated": "Голосование за пропуск начато",
"addAsTemporary": "Добавить как временное",
"cacheOnServer": "Закэшировать на сервере",
"mediaUrl": "Ссылка на видео",
@@ -102,7 +120,6 @@
"acceptableEmbedCodesAre": "Можно добавить видео с тегами",
"customEmbedsCannotBeSynchronized": "СИНХРОНИЗАЦИЯ БУДЕТ НЕДОСТУПНА",
"save": "Сохранить",
- "skipItemConfirm": "Вы уверены что хотите пропустить текущее видео?",
"clearPlaylistConfirm": "Вы уверены что хотите очистить плейлист?",
"shufflePlaylistConfirm": "Вы уверены что хотите перемешать плейлист?",
"lockPlaylistConfirm": "Вы уверены что хотите заблокировать плейлист?",
send patches to the email below
yukais@pinapelz.com
include the subject [PATCH repo_name]
pinapelz.com
homepage