diff options
| -rw-r--r-- | build/server.js | 9 | ||||
| -rw-r--r-- | default-config.json | 1 | ||||
| -rw-r--r-- | res/client.js | 111 | ||||
| -rw-r--r-- | res/css/des.css | 2 | ||||
| -rw-r--r-- | src/Types.hx | 1 | ||||
| -rw-r--r-- | src/client/Main.hx | 62 | ||||
| -rw-r--r-- | src/client/Player.hx | 60 | ||||
| -rw-r--r-- | src/client/Utils.hx | 5 | ||||
| -rw-r--r-- | src/client/players/Raw.hx | 2 | ||||
| -rw-r--r-- | src/client/players/Vk.hx | 2 | ||||
| -rw-r--r-- | src/client/players/Youtube.hx | 6 |
11 files changed, 202 insertions, 59 deletions
diff --git a/build/server.js b/build/server.js index 8b24397..e2f25cc 100644 --- a/build/server.js +++ b/build/server.js @@ -1624,7 +1624,7 @@ JsonParser_$62.__name__ = true; JsonParser_$62.__super__ = json2object_reader_BaseParser; JsonParser_$62.prototype = $extend(json2object_reader_BaseParser.prototype,{ onIncorrectType: function(pos,variable) { - this.errors.push(json2object_Error.IncorrectType(variable,"{ youtubePlaylistLimit : Int, youtubeApiKey : String, userVideoLimit : Int, totalVideoLimit : Int, templateUrl : String, serverChatHistory : Int, ?salt : Null<String>, requestLeaderOnPause : Bool, port : Int, permissions : Permissions, maxMessageLength : Int, maxLoginLength : Int, localNetworkOnly : Bool, localAdmins : Bool, ?isVerbose : Null<Bool>, filters : Array<Filter>, emotes : Array<Emote>, channelName : String, cacheStorageLimitGiB : Float, allowProxyIps : Bool }",pos)); + this.errors.push(json2object_Error.IncorrectType(variable,"{ youtubePlaylistLimit : Int, youtubeApiKey : String, userVideoLimit : Int, unpauseWithoutLeader : Bool, totalVideoLimit : Int, templateUrl : String, serverChatHistory : Int, ?salt : Null<String>, requestLeaderOnPause : Bool, port : Int, permissions : Permissions, maxMessageLength : Int, maxLoginLength : Int, localNetworkOnly : Bool, localAdmins : Bool, ?isVerbose : Null<Bool>, filters : Array<Filter>, emotes : Array<Emote>, channelName : String, cacheStorageLimitGiB : Float, allowProxyIps : Bool }",pos)); json2object_reader_BaseParser.prototype.onIncorrectType.call(this,pos,variable); } ,loadJsonNull: function(pos,variable) { @@ -1632,7 +1632,7 @@ JsonParser_$62.prototype = $extend(json2object_reader_BaseParser.prototype,{ } ,loadJsonObject: function(o,pos,variable) { var assigned = new haxe_ds_StringMap(); - this.objectSetupAssign(assigned,["allowProxyIps","cacheStorageLimitGiB","channelName","emotes","filters","isVerbose","localAdmins","localNetworkOnly","maxLoginLength","maxMessageLength","permissions","port","requestLeaderOnPause","salt","serverChatHistory","templateUrl","totalVideoLimit","userVideoLimit","youtubeApiKey","youtubePlaylistLimit"],[false,false,false,false,false,true,false,false,false,false,false,false,false,true,false,false,false,false,false,false]); + this.objectSetupAssign(assigned,["allowProxyIps","cacheStorageLimitGiB","channelName","emotes","filters","isVerbose","localAdmins","localNetworkOnly","maxLoginLength","maxMessageLength","permissions","port","requestLeaderOnPause","salt","serverChatHistory","templateUrl","totalVideoLimit","unpauseWithoutLeader","userVideoLimit","youtubeApiKey","youtubePlaylistLimit"],[false,false,false,false,false,true,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false]); this.value = this.getAuto(); var _g = 0; while(_g < o.length) { @@ -1690,6 +1690,9 @@ JsonParser_$62.prototype = $extend(json2object_reader_BaseParser.prototype,{ case "totalVideoLimit": this.value.totalVideoLimit = this.loadObjectField(($_=new JsonParser_$54(this.errors,this.putils,1),$bind($_,$_.loadJson)),field,"totalVideoLimit",assigned,this.value.totalVideoLimit,pos); break; + case "unpauseWithoutLeader": + this.value.unpauseWithoutLeader = this.loadObjectField(($_=new JsonParser_$46(this.errors,this.putils,1),$bind($_,$_.loadJson)),field,"unpauseWithoutLeader",assigned,this.value.unpauseWithoutLeader,pos); + break; case "userVideoLimit": this.value.userVideoLimit = this.loadObjectField(($_=new JsonParser_$54(this.errors,this.putils,1),$bind($_,$_.loadJson)),field,"userVideoLimit",assigned,this.value.userVideoLimit,pos); break; @@ -1706,7 +1709,7 @@ JsonParser_$62.prototype = $extend(json2object_reader_BaseParser.prototype,{ this.objectErrors(assigned,pos); } ,getAuto: function() { - return { allowProxyIps : new JsonParser_$46([],this.putils,0).loadJson(new hxjsonast_Json(hxjsonast_JsonValue.JNull,new hxjsonast_Position("",0,1))), cacheStorageLimitGiB : new JsonParser_$47([],this.putils,0).loadJson(new hxjsonast_Json(hxjsonast_JsonValue.JNull,new hxjsonast_Position("",0,1))), channelName : new JsonParser_$45([],this.putils,0).loadJson(new hxjsonast_Json(hxjsonast_JsonValue.JNull,new hxjsonast_Position("",0,1))), emotes : new JsonParser_$67([],this.putils,0).loadJson(new hxjsonast_Json(hxjsonast_JsonValue.JNull,new hxjsonast_Position("",0,1))), filters : new JsonParser_$68([],this.putils,0).loadJson(new hxjsonast_Json(hxjsonast_JsonValue.JNull,new hxjsonast_Position("",0,1))), isVerbose : new JsonParser_$58([],this.putils,0).loadJson(new hxjsonast_Json(hxjsonast_JsonValue.JNull,new hxjsonast_Position("",0,1))), localAdmins : new JsonParser_$46([],this.putils,0).loadJson(new hxjsonast_Json(hxjsonast_JsonValue.JNull,new hxjsonast_Position("",0,1))), localNetworkOnly : new JsonParser_$46([],this.putils,0).loadJson(new hxjsonast_Json(hxjsonast_JsonValue.JNull,new hxjsonast_Position("",0,1))), maxLoginLength : new JsonParser_$54([],this.putils,0).loadJson(new hxjsonast_Json(hxjsonast_JsonValue.JNull,new hxjsonast_Position("",0,1))), maxMessageLength : new JsonParser_$54([],this.putils,0).loadJson(new hxjsonast_Json(hxjsonast_JsonValue.JNull,new hxjsonast_Position("",0,1))), permissions : new JsonParser_$70([],this.putils,0).loadJson(new hxjsonast_Json(hxjsonast_JsonValue.JNull,new hxjsonast_Position("",0,1))), port : new JsonParser_$54([],this.putils,0).loadJson(new hxjsonast_Json(hxjsonast_JsonValue.JNull,new hxjsonast_Position("",0,1))), requestLeaderOnPause : new JsonParser_$46([],this.putils,0).loadJson(new hxjsonast_Json(hxjsonast_JsonValue.JNull,new hxjsonast_Position("",0,1))), salt : new JsonParser_$50([],this.putils,0).loadJson(new hxjsonast_Json(hxjsonast_JsonValue.JNull,new hxjsonast_Position("",0,1))), serverChatHistory : new JsonParser_$54([],this.putils,0).loadJson(new hxjsonast_Json(hxjsonast_JsonValue.JNull,new hxjsonast_Position("",0,1))), templateUrl : new JsonParser_$45([],this.putils,0).loadJson(new hxjsonast_Json(hxjsonast_JsonValue.JNull,new hxjsonast_Position("",0,1))), totalVideoLimit : new JsonParser_$54([],this.putils,0).loadJson(new hxjsonast_Json(hxjsonast_JsonValue.JNull,new hxjsonast_Position("",0,1))), userVideoLimit : new JsonParser_$54([],this.putils,0).loadJson(new hxjsonast_Json(hxjsonast_JsonValue.JNull,new hxjsonast_Position("",0,1))), youtubeApiKey : new JsonParser_$45([],this.putils,0).loadJson(new hxjsonast_Json(hxjsonast_JsonValue.JNull,new hxjsonast_Position("",0,1))), youtubePlaylistLimit : new JsonParser_$54([],this.putils,0).loadJson(new hxjsonast_Json(hxjsonast_JsonValue.JNull,new hxjsonast_Position("",0,1)))}; + return { allowProxyIps : new JsonParser_$46([],this.putils,0).loadJson(new hxjsonast_Json(hxjsonast_JsonValue.JNull,new hxjsonast_Position("",0,1))), cacheStorageLimitGiB : new JsonParser_$47([],this.putils,0).loadJson(new hxjsonast_Json(hxjsonast_JsonValue.JNull,new hxjsonast_Position("",0,1))), channelName : new JsonParser_$45([],this.putils,0).loadJson(new hxjsonast_Json(hxjsonast_JsonValue.JNull,new hxjsonast_Position("",0,1))), emotes : new JsonParser_$67([],this.putils,0).loadJson(new hxjsonast_Json(hxjsonast_JsonValue.JNull,new hxjsonast_Position("",0,1))), filters : new JsonParser_$68([],this.putils,0).loadJson(new hxjsonast_Json(hxjsonast_JsonValue.JNull,new hxjsonast_Position("",0,1))), isVerbose : new JsonParser_$58([],this.putils,0).loadJson(new hxjsonast_Json(hxjsonast_JsonValue.JNull,new hxjsonast_Position("",0,1))), localAdmins : new JsonParser_$46([],this.putils,0).loadJson(new hxjsonast_Json(hxjsonast_JsonValue.JNull,new hxjsonast_Position("",0,1))), localNetworkOnly : new JsonParser_$46([],this.putils,0).loadJson(new hxjsonast_Json(hxjsonast_JsonValue.JNull,new hxjsonast_Position("",0,1))), maxLoginLength : new JsonParser_$54([],this.putils,0).loadJson(new hxjsonast_Json(hxjsonast_JsonValue.JNull,new hxjsonast_Position("",0,1))), maxMessageLength : new JsonParser_$54([],this.putils,0).loadJson(new hxjsonast_Json(hxjsonast_JsonValue.JNull,new hxjsonast_Position("",0,1))), permissions : new JsonParser_$70([],this.putils,0).loadJson(new hxjsonast_Json(hxjsonast_JsonValue.JNull,new hxjsonast_Position("",0,1))), port : new JsonParser_$54([],this.putils,0).loadJson(new hxjsonast_Json(hxjsonast_JsonValue.JNull,new hxjsonast_Position("",0,1))), requestLeaderOnPause : new JsonParser_$46([],this.putils,0).loadJson(new hxjsonast_Json(hxjsonast_JsonValue.JNull,new hxjsonast_Position("",0,1))), salt : new JsonParser_$50([],this.putils,0).loadJson(new hxjsonast_Json(hxjsonast_JsonValue.JNull,new hxjsonast_Position("",0,1))), serverChatHistory : new JsonParser_$54([],this.putils,0).loadJson(new hxjsonast_Json(hxjsonast_JsonValue.JNull,new hxjsonast_Position("",0,1))), templateUrl : new JsonParser_$45([],this.putils,0).loadJson(new hxjsonast_Json(hxjsonast_JsonValue.JNull,new hxjsonast_Position("",0,1))), totalVideoLimit : new JsonParser_$54([],this.putils,0).loadJson(new hxjsonast_Json(hxjsonast_JsonValue.JNull,new hxjsonast_Position("",0,1))), unpauseWithoutLeader : new JsonParser_$46([],this.putils,0).loadJson(new hxjsonast_Json(hxjsonast_JsonValue.JNull,new hxjsonast_Position("",0,1))), userVideoLimit : new JsonParser_$54([],this.putils,0).loadJson(new hxjsonast_Json(hxjsonast_JsonValue.JNull,new hxjsonast_Position("",0,1))), youtubeApiKey : new JsonParser_$45([],this.putils,0).loadJson(new hxjsonast_Json(hxjsonast_JsonValue.JNull,new hxjsonast_Position("",0,1))), youtubePlaylistLimit : new JsonParser_$54([],this.putils,0).loadJson(new hxjsonast_Json(hxjsonast_JsonValue.JNull,new hxjsonast_Position("",0,1)))}; } ,__class__: JsonParser_$62 }); diff --git a/default-config.json b/default-config.json index 15dfc6c..38fc271 100644 --- a/default-config.json +++ b/default-config.json @@ -7,6 +7,7 @@ "totalVideoLimit": 0, "userVideoLimit": 0, "requestLeaderOnPause": false, + "unpauseWithoutLeader": false, "localAdmins": true, "allowProxyIps": true, "localNetworkOnly": false, diff --git a/res/client.js b/res/client.js index febaff7..b009967 100644 --- a/res/client.js +++ b/res/client.js @@ -1290,7 +1290,9 @@ var client_Main = function() { this.filters = []; this.pageTitle = window.document.title; this.clients = []; + this.lastStateTimeStamp = 0.0; this.lastState = { time : 0, rate : 1.0, paused : false, pausedByServer : false}; + this.timeFromLastState = 0.0; this.showingServerPause = false; this.playersCacheSupport = []; this.isPlaylistOpen = true; @@ -1635,7 +1637,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 : 454, 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 : 458, className : "client.Main", methodName : "onMessage", customParams : [Reflect.field(data,t.charAt(0).toLowerCase() + HxOverrides.substr(t,1,null))]}); } client_JsApi.fireOnceEvent(data); switch(data.type) { @@ -1676,6 +1678,7 @@ client_Main.prototype = { this.lastState.paused = data.getTime.paused; this.lastState.pausedByServer = data.getTime.pausedByServer; this.lastState.rate = data.getTime.rate; + this.updateLastStateTime(); if(isPauseChanged) { this.updateUserList(); } @@ -1751,7 +1754,9 @@ client_Main.prototype = { this.showGuestPasswordPanel(); break; case "Pause": + this.lastState.time = data.pause.time; this.lastState.paused = true; + this.updateLastStateTime(); this.player.setPauseIndicator(this.lastState.paused); this.updateUserList(); if((this.personal.group & 4) != 0) { @@ -1761,7 +1766,9 @@ client_Main.prototype = { this.player.setTime(data.pause.time); break; case "Play": + this.lastState.time = data.play.time; this.lastState.paused = false; + this.updateLastStateTime(); this.player.setPauseIndicator(this.lastState.paused); this.updateUserList(); if((this.personal.group & 4) != 0) { @@ -1784,6 +1791,8 @@ client_Main.prototype = { } break; case "Rewind": + this.lastState.time = data.rewind.time; + this.updateLastStateTime(); this.player.setTime(data.rewind.time + 0.5); break; case "ServerMessage": @@ -1808,6 +1817,8 @@ client_Main.prototype = { this.player.setPlaybackRate(data.setRate.rate); break; case "SetTime": + this.lastState.time = data.setTime.time; + this.updateLastStateTime(); var synchThreshold = this.settings.synchThreshold; var newTime = data.setTime.time; if(Math.abs(this.player.getTime() - newTime) < synchThreshold) { @@ -1841,6 +1852,10 @@ client_Main.prototype = { this.player.setItems(data.updatePlaylist.videoList); break; case "VideoLoaded": + this.lastState.paused = false; + this.lastState.pausedByServer = false; + this.lastState.time = 0; + this.updateLastStateTime(); this.player.setTime(0); this.player.play(); if((this.personal.group & 4) != 0 && !this.player.isVideoLoaded()) { @@ -1849,6 +1864,13 @@ client_Main.prototype = { break; } } + ,updateLastStateTime: function() { + if(this.lastStateTimeStamp == 0) { + this.lastStateTimeStamp = HxOverrides.now() / 1000; + } + this.timeFromLastState = HxOverrides.now() / 1000 - this.lastStateTimeStamp; + this.lastStateTimeStamp = HxOverrides.now() / 1000; + } ,onConnected: function(data) { var connected = data.connected; this.settings.uuid = connected.uuid; @@ -1924,10 +1946,7 @@ client_Main.prototype = { window.document.querySelector("#requestLeaderHintButton").onclick = function(e) { window.scrollTo(0,0); if(client_Utils.isTouch()) { - window.document.querySelector("#leader_btn").classList.add("hint"); - haxe_Timer.delay(function() { - window.document.querySelector("#leader_btn").classList.remove("hint"); - },1000); + _gthis.blinkLeaderButton(); } }; window.document.querySelector("#requestLeaderHintButton").onpointerenter = function(e) { @@ -1962,6 +1981,12 @@ client_Main.prototype = { client_Settings.write(_gthis.settings); }; } + ,blinkLeaderButton: function() { + window.document.querySelector("#leader_btn").classList.add("hint"); + haxe_Timer.delay(function() { + window.document.querySelector("#leader_btn").classList.remove("hint"); + },500); + } ,onUserGroupChanged: function() { var button = window.document.querySelector("#queue_next"); if(this.personal.hasPermission("changeOrder",this.config.permissions)) { @@ -2006,6 +2031,7 @@ client_Main.prototype = { this.config = config; if(client_Utils.isTouch()) { config.requestLeaderOnPause = false; + config.unpauseWithoutLeader = false; } this.pageTitle = config.channelName; window.document.querySelector("#guestname").maxLength = config.maxLoginLength; @@ -2271,7 +2297,7 @@ client_Main.prototype = { _gthis.hideDynamicChin(); _gthis.send({ type : "SetLeader", setLeader : { clientName : _gthis.personal.name}}); client_JsApi.once("SetLeader",function(event) { - _gthis.send({ type : "SetLeader", setLeader : { clientName : ""}}); + _gthis.removeLeader(); }); }; chin.style.display = ""; @@ -2499,6 +2525,9 @@ client_Main.prototype = { this.setLeaderButton((this.personal.group & 4) == 0); this.send({ type : "SetLeader", setLeader : { clientName : (this.personal.group & 4) != 0 ? "" : this.personal.name}}); } + ,removeLeader: function() { + this.send({ type : "SetLeader", setLeader : { clientName : ""}}); + } ,toggleLeaderAndPause: function() { var _gthis = this; if((this.personal.group & 4) == 0) { @@ -2516,6 +2545,9 @@ client_Main.prototype = { ,hasLeaderOnPauseRequest: function() { return this.config.requestLeaderOnPause; } + ,hasUnpauseWithoutLeader: function() { + return this.config.unpauseWithoutLeader; + } ,getTemplateUrl: function() { return this.config.templateUrl; } @@ -2541,6 +2573,7 @@ client_Main.prototype = { } }; var client_Player = function(main) { + this.inUserInteraction = false; this.voiceOverVolume = 0.3; this.needsVolumeReset = false; this.isAudioTrackLoaded = false; @@ -2575,6 +2608,12 @@ var client_Player = function(main) { client_Buttons.onViewportResize(); }; } + this.playerEl.addEventListener("click",function(e) { + _gthis.inUserInteraction = true; + return haxe_Timer.delay(function() { + _gthis.inUserInteraction = false; + },350); + },{ }); }; client_Player.__name__ = true; client_Player.prototype = { @@ -2705,7 +2744,7 @@ client_Player.prototype = { return _gthis.isAudioTrackLoaded = true; }; this.audioTrack.onerror = function(e) { - haxe_Log.trace(e,{ fileName : "src/client/Player.hx", lineNumber : 205, className : "client.Player", methodName : "setExternalAudioTrack"}); + haxe_Log.trace(e,{ fileName : "src/client/Player.hx", lineNumber : 215, className : "client.Player", methodName : "setExternalAudioTrack"}); _gthis.audioTrack.oncanplay = null; _gthis.audioTrack.onerror = null; _gthis.isAudioTrackLoaded = false; @@ -2762,9 +2801,10 @@ client_Player.prototype = { if(!this.main.isSyncActive) { return; } - window.document.querySelector("#pause-indicator").setAttribute("name",isPause ? "pause" : "play"); + var state = isPause ? "pause" : "play"; + window.document.querySelector("#pause-indicator").setAttribute("name",state); var el2 = window.document.querySelector("#pause-indicator-portrait"); - el2.setAttribute("name","pause"); + el2.setAttribute("name",state); var tmp = isPause || this.main.hasLeader() ? "" : "none"; el2.style.display = tmp; } @@ -2780,14 +2820,24 @@ client_Player.prototype = { if(tmp != null) { tmp.play(); } + if(!this.isLoaded) { + return; + } + if(this.videoList.items.length == 0) { + return; + } + var hasAutoPause = this.main.hasLeaderOnPauseRequest(); if((this.main.personal.group & 4) == 0) { - if(this.main.lastState.paused) { + if(hasAutoPause && this.inUserInteraction || this.main.hasUnpauseWithoutLeader()) { + this.main.removeLeader(); + } else if(this.main.lastState.paused) { this.pause(); + this.main.blinkLeaderButton(); } return; } this.main.send({ type : "Play", play : { time : this.getTime()}}); - if(this.main.hasLeaderOnPauseRequest() && this.videoList.items.length > 0) { + if(hasAutoPause) { if(this.main.hasPermission("requestLeader")) { this.main.toggleLeader(); } @@ -2799,6 +2849,9 @@ client_Player.prototype = { if(tmp != null) { tmp.pause(); } + if(!this.isLoaded) { + return; + } var _this = this.videoList; var tmp = _this.items[_this.pos]; if(tmp == null) { @@ -2825,6 +2878,7 @@ client_Player.prototype = { if((this.main.personal.group & 4) == 0) { if(!this.main.lastState.paused) { this.play(); + this.main.blinkLeaderButton(); } return; } @@ -2838,7 +2892,17 @@ client_Player.prototype = { this.skipSetTime = false; return; } + if(this.videoList.items.length == 0) { + return; + } if((this.main.personal.group & 4) == 0) { + if(this.main.hasLeader() || this.main.lastState.pausedByServer) { + this.isPaused(); + var time = this.main.lastState.time; + this.getTime(); + this.setTime(time); + this.main.blinkLeaderButton(); + } return; } this.main.send({ type : "SetTime", setTime : { time : this.getTime()}}); @@ -2851,7 +2915,11 @@ client_Player.prototype = { this.skipSetRate = false; return; } + if(this.videoList.items.length == 0) { + return; + } if((this.main.personal.group & 4) == 0) { + this.main.blinkLeaderButton(); return; } this.main.send({ type : "SetRate", setRate : { rate : this.getPlaybackRate()}}); @@ -3168,7 +3236,7 @@ client_Player.prototype = { } }; http.onError = function(msg) { - haxe_Log.trace(msg,{ fileName : "src/client/Player.hx", lineNumber : 608, className : "client.Player", methodName : "skipAd"}); + haxe_Log.trace(msg,{ fileName : "src/client/Player.hx", lineNumber : 652, className : "client.Player", methodName : "skipAd"}); }; http.request(); } @@ -3310,13 +3378,6 @@ client_Utils.outerHeight = function(el) { var style = window.getComputedStyle(el); return el.getBoundingClientRect().height + parseFloat(style.marginTop) + parseFloat(style.marginBottom); }; -client_Utils.prepend = function(parent,child) { - if(parent.firstChild == null) { - parent.appendChild(child); - } else { - parent.insertBefore(child,parent.firstChild); - } -}; client_Utils.insertAtIndex = function(parent,child,i) { if(i >= parent.children.length) { parent.appendChild(child); @@ -3561,7 +3622,7 @@ client_players_Raw.prototype = { } callback({ duration : video.duration, title : title, subs : subs}); }; - client_Utils.prepend(this.playerEl,video); + this.playerEl.prepend(video); if(isHls) { this.initHlsSource(video,url); } @@ -4055,7 +4116,7 @@ client_players_Vk.prototype = { return; } var tempVideo = client_Utils.nodeFromString(StringTools.trim("<iframe id=\"temp-videoplayer\" src=\"https://vk.com/video_ext.php?oid=" + ids.oid + "&id=" + ids.id + "&hd=1&js_api=1\"\n\t\t\t\tallow=\"autoplay; encrypted-media; fullscreen; picture-in-picture;\"\n\t\t\t\tframeborder=\"0\" allowfullscreen>\n\t\t\t</iframe>")); - client_Utils.prepend(this.playerEl,tempVideo); + this.playerEl.prepend(tempVideo); var tempVkPlayer = this.createVkPlayer(tempVideo); tempVkPlayer.on("inited",function() { callback({ duration : tempVkPlayer.getDuration(), title : "VK media", url : url}); @@ -4320,7 +4381,7 @@ client_players_Youtube.prototype = { } var video = window.document.createElement("div"); video.id = "temp-videoplayer"; - client_Utils.prepend(this.playerEl,video); + this.playerEl.prepend(video); var tempYoutube = null; tempYoutube = new YT.Player(video.id,{ videoId : this.extractVideoId(url), playerVars : { modestbranding : 1, rel : 0, showinfo : 0}, events : { onReady : function(e) { if(_gthis.playerEl.contains(video)) { @@ -4358,11 +4419,13 @@ client_players_Youtube.prototype = { e.target.mute(); } _gthis.isLoaded = true; - _gthis.youtube.pauseVideo(); + if(_gthis.main.lastState.paused) { + _gthis.youtube.pauseVideo(); + } + _gthis.player.onCanBePlayed(); }, onStateChange : function(e) { switch(e.data) { case -1: - _gthis.player.onCanBePlayed(); break; case 0: break; diff --git a/res/css/des.css b/res/css/des.css index 10c246c..8c7f0cc 100644 --- a/res/css/des.css +++ b/res/css/des.css @@ -203,7 +203,7 @@ button span { #leader_btn { border: .125rem solid; border-color: transparent; - transition: border-color ease-in-out 800ms; + transition: border-color ease-in-out 500ms; } #leader_btn.hint { diff --git a/src/Types.hx b/src/Types.hx index fdb3ba9..ec09b5c 100644 --- a/src/Types.hx +++ b/src/Types.hx @@ -32,6 +32,7 @@ typedef Config = { totalVideoLimit:Int, userVideoLimit:Int, requestLeaderOnPause:Bool, + unpauseWithoutLeader:Bool, localAdmins:Bool, allowProxyIps:Bool, localNetworkOnly:Bool, diff --git a/src/client/Main.hx b/src/client/Main.hx index 62fa7de..6ec8727 100644 --- a/src/client/Main.hx +++ b/src/client/Main.hx @@ -40,6 +40,8 @@ class Main { public var isPlaylistOpen(default, null) = true; public var playersCacheSupport(default, null):Array<PlayerType> = []; public var showingServerPause(default, null) = false; + /** How much time passed from last GetTime **/ + public var timeFromLastState(default, null) = 0.0; public final lastState:GetTimeEvent = { time: 0, rate: 1.0, @@ -47,6 +49,8 @@ class Main { pausedByServer: false }; + var lastStateTimeStamp = 0.0; + final clients:Array<Client> = []; var pageTitle = document.title; var config:Null<Config>; @@ -510,6 +514,10 @@ class Main { if (player.itemsLength() == 1) player.setVideo(0); case VideoLoaded: + lastState.paused = false; + lastState.pausedByServer = false; + lastState.time = 0; + updateLastStateTime(); player.setTime(0); player.play(); // try to sync leader after with GetTime events @@ -524,7 +532,9 @@ class Main { if (player.isListEmpty()) player.pause(); case Pause: + lastState.time = data.pause.time; lastState.paused = true; + updateLastStateTime(); player.setPauseIndicator(lastState.paused); updateUserList(); if (isLeader()) return; @@ -532,7 +542,9 @@ class Main { player.setTime(data.pause.time); case Play: + lastState.time = data.play.time; lastState.paused = false; + updateLastStateTime(); player.setPauseIndicator(lastState.paused); updateUserList(); if (isLeader()) return; @@ -554,6 +566,7 @@ class Main { lastState.paused = data.getTime.paused; lastState.pausedByServer = data.getTime.pausedByServer; lastState.rate = data.getTime.rate; + updateLastStateTime(); if (isPauseChanged) updateUserList(); @@ -592,6 +605,8 @@ class Main { else player.setTime(newTime); case SetTime: + lastState.time = data.setTime.time; + updateLastStateTime(); final synchThreshold = settings.synchThreshold; final newTime = data.setTime.time; final time = player.getTime(); @@ -603,6 +618,8 @@ class Main { player.setPlaybackRate(data.setRate.rate); case Rewind: + lastState.time = data.rewind.time; + updateLastStateTime(); player.setTime(data.rewind.time + 0.5); case Flashback: // server-only @@ -640,6 +657,14 @@ class Main { } } + function updateLastStateTime():Void { + if (lastStateTimeStamp == 0) { + lastStateTimeStamp = Timer.stamp(); + } + timeFromLastState = Timer.stamp() - lastStateTimeStamp; + lastStateTimeStamp = Timer.stamp(); + } + function onConnected(data:WsEvent):Void { final connected = data.connected; @@ -713,10 +738,7 @@ class Main { } ge("#requestLeaderHintButton").onclick = (e:MouseEvent) -> { window.scrollTo(0, 0); - if (Utils.isTouch()) { - ge("#leader_btn").classList.add("hint"); - Timer.delay(() -> ge("#leader_btn").classList.remove("hint"), 1000); - } + if (Utils.isTouch()) blinkLeaderButton(); } ge("#requestLeaderHintButton").onpointerenter = e -> { if (Utils.isTouch()) return; @@ -744,6 +766,11 @@ class Main { } } + public function blinkLeaderButton():Void { + ge("#leader_btn").classList.add("hint"); + Timer.delay(() -> ge("#leader_btn").classList.remove("hint"), 500); + } + function onUserGroupChanged():Void { final button:ButtonElement = cast ge("#queue_next"); if (personal.hasPermission(ChangeOrderPerm, config.permissions)) { @@ -790,7 +817,10 @@ class Main { function setConfig(config:Config):Void { this.config = config; - if (Utils.isTouch()) config.requestLeaderOnPause = false; + if (Utils.isTouch()) { + config.requestLeaderOnPause = false; + config.unpauseWithoutLeader = false; + } pageTitle = config.channelName; final login:InputElement = cast ge("#guestname"); login.maxLength = config.maxLoginLength; @@ -1063,14 +1093,7 @@ class Main { clientName: personal.name } }); - JsApi.once(SetLeader, event -> { - send({ - type: SetLeader, - setLeader: { - clientName: "" - } - }); - }); + JsApi.once(SetLeader, event -> removeLeader()); } chin.style.display = ""; @@ -1318,6 +1341,15 @@ class Main { }); } + public function removeLeader():Void { + send({ + type: SetLeader, + setLeader: { + clientName: "" + } + }); + } + public function toggleLeaderAndPause():Void { if (!isLeader()) { JsApi.once(SetLeader, event -> { @@ -1336,6 +1368,10 @@ class Main { return config.requestLeaderOnPause; } + public function hasUnpauseWithoutLeader():Bool { + return config.unpauseWithoutLeader; + } + public function getTemplateUrl():String { return config.templateUrl; } diff --git a/src/client/Player.hx b/src/client/Player.hx index 911092e..92f34e9 100644 --- a/src/client/Player.hx +++ b/src/client/Player.hx @@ -12,6 +12,7 @@ import client.players.Vk; import client.players.Youtube; import haxe.Http; import haxe.Json; +import haxe.Timer; import js.html.Audio; import js.html.Element; import js.html.InputElement; @@ -37,6 +38,9 @@ class Player { var needsVolumeReset = false; final voiceOverVolume = 0.3; + /** If player was clicked and pause/play event was not generated by browser events. **/ + public var inUserInteraction = false; + public function new(main:Main):Void { this.main = main; youtube = new Youtube(main, this); @@ -57,12 +61,18 @@ class Player { if (resizeObserver != null) { resizeObserver.observe(playerEl); } else { - final timer = new haxe.Timer(50); + final timer = new Timer(50); timer.run = () -> { if (isLoaded) return; Buttons.onViewportResize(); } } + + playerEl.addEventListener("click", e -> { + inUserInteraction = true; + // for some reason Chrome has ~300ms event delay + Timer.delay(() -> inUserInteraction = false, 350); + }, {}); } function initItemButtons():Void { @@ -249,7 +259,7 @@ class Player { el.setAttribute("name", state); final el2 = ge("#pause-indicator-portrait"); - el2.setAttribute("name", "pause"); + el2.setAttribute("name", state); var isVisible = isPause || main.hasLeader(); el2.style.display = isVisible ? "" : "none"; } @@ -262,10 +272,23 @@ class Player { public function onPlay():Void { audioTrack?.play(); + if (!isLoaded) return; + if (videoList.length == 0) return; + final hasAutoPause = main.hasLeaderOnPauseRequest(); if (!main.isLeader()) { - // paused and no leader - instant pause - if (main.lastState.paused) pause(); + // user click, so we can unpause by removing leader + // (doesn't work in Firefox because of no video click propagation) + final allowUnpause = (hasAutoPause && inUserInteraction); + if (allowUnpause || main.hasUnpauseWithoutLeader()) { + main.removeLeader(); + } else { + // paused and no leader - instant pause + if (main.lastState.paused) { + pause(); + main.blinkLeaderButton(); + } + } return; } main.send({ @@ -274,7 +297,6 @@ class Player { time: getTime() } }); - final hasAutoPause = main.hasLeaderOnPauseRequest() && videoList.length > 0; if (hasAutoPause) { // do not remove leader if user cannot request it back if (main.hasPermission(RequestLeaderPerm)) main.toggleLeader(); @@ -284,6 +306,7 @@ class Player { public function onPause():Void { audioTrack?.pause(); + if (!isLoaded) return; final item = videoList.currentItem ?? return; // do not send pause if video is ended if (getTime() >= item.duration - 0.01) return; @@ -311,7 +334,10 @@ class Player { } if (!main.isLeader()) { // no pause and no permission - instant play - if (!main.lastState.paused) play(); + if (!main.lastState.paused) { + play(); + main.blinkLeaderButton(); + } return; } // we are leader, so just send pause @@ -332,7 +358,21 @@ class Player { skipSetTime = false; return; } - if (!main.isLeader()) return; + if (videoList.length == 0) return; + if (!main.isLeader()) { + if (main.hasLeader() || main.lastState.pausedByServer) { + final off = isPaused() ? 0 : main.timeFromLastState; + final time = main.lastState.time; + final delta = Math.abs(time - getTime()); + setTime(time); + main.blinkLeaderButton(); + } else { + // we dont want to seek back here because + // after seek can happen pause that will give auto-leader, + // so seeking will work + } + return; + } main.send({ type: SetTime, setTime: { @@ -349,7 +389,11 @@ class Player { skipSetRate = false; return; } - if (!main.isLeader()) return; + if (videoList.length == 0) return; + if (!main.isLeader()) { + main.blinkLeaderButton(); + return; + } main.send({ type: SetRate, setRate: { diff --git a/src/client/Utils.hx b/src/client/Utils.hx index 717d64f..4d85697 100644 --- a/src/client/Utils.hx +++ b/src/client/Utils.hx @@ -54,11 +54,6 @@ class Utils { + Std.parseFloat(style.marginBottom)); } - public static function prepend(parent:Element, child:Element):Void { - if (parent.firstChild == null) parent.appendChild(child); - else parent.insertBefore(child, parent.firstChild); - } - public static function insertAtIndex(parent:Element, child:Element, i:Int) { if (i >= parent.children.length) parent.appendChild(child); else parent.insertBefore(child, parent.children[i]); diff --git a/src/client/players/Raw.hx b/src/client/players/Raw.hx index 01752e7..f054f14 100644 --- a/src/client/players/Raw.hx +++ b/src/client/players/Raw.hx @@ -76,7 +76,7 @@ class Raw implements IPlayer { subs: subs, }); } - Utils.prepend(playerEl, video); + playerEl.prepend(video); if (isHls) initHlsSource(video, url); } diff --git a/src/client/players/Vk.hx b/src/client/players/Vk.hx index fbcf60c..599b5eb 100644 --- a/src/client/players/Vk.hx +++ b/src/client/players/Vk.hx @@ -107,7 +107,7 @@ class Vk implements IPlayer { frameborder="0" allowfullscreen> </iframe>'.trim() ); - Utils.prepend(playerEl, tempVideo); + playerEl.prepend(tempVideo); final tempVkPlayer = createVkPlayer(tempVideo); tempVkPlayer.on("inited", () -> { callback({ diff --git a/src/client/players/Youtube.hx b/src/client/players/Youtube.hx index b30dfb3..f5fb88c 100644 --- a/src/client/players/Youtube.hx +++ b/src/client/players/Youtube.hx @@ -175,7 +175,7 @@ class Youtube implements IPlayer { } final video = document.createDivElement(); video.id = "temp-videoplayer"; - Utils.prepend(playerEl, video); + playerEl.prepend(video); var tempYoutube:YoutubePlayer = null; tempYoutube = new YoutubePlayer(video.id, { videoId: extractVideoId(url), @@ -232,12 +232,12 @@ class Youtube implements IPlayer { onReady: e -> { if (!main.isAutoplayAllowed()) e.target.mute(); isLoaded = true; - youtube.pauseVideo(); + if (main.lastState.paused) youtube.pauseVideo(); + player.onCanBePlayed(); }, onStateChange: e -> { switch (e.data) { case UNSTARTED: - player.onCanBePlayed(); case ENDED: case PLAYING: player.onPlay(); |
