diff options
Diffstat (limited to 'res')
| -rw-r--r-- | res/client.js | 1510 | ||||
| -rw-r--r-- | res/css/des.css | 385 | ||||
| -rw-r--r-- | res/index.html | 178 | ||||
| -rw-r--r-- | res/langs/en.json | 25 | ||||
| -rw-r--r-- | res/langs/ru.json | 23 |
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("<[^&]+>","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}. ${acceptableEmbedCodesAre} <iframe> ${or} <object>. ${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": "Вы уверены что хотите заблокировать плейлист?", |
