aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--build/server.js9
-rw-r--r--default-config.json1
-rw-r--r--res/client.js111
-rw-r--r--res/css/des.css2
-rw-r--r--src/Types.hx1
-rw-r--r--src/client/Main.hx62
-rw-r--r--src/client/Player.hx60
-rw-r--r--src/client/Utils.hx5
-rw-r--r--src/client/players/Raw.hx2
-rw-r--r--src/client/players/Vk.hx2
-rw-r--r--src/client/players/Youtube.hx6
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();
send patches to the email below
yukais@pinapelz.com
include the subject [PATCH repo_name]
pinapelz.com
homepage