diff options
| -rw-r--r-- | .vscode/extensions.json | 6 | ||||
| -rw-r--r-- | build/server.js | 46 | ||||
| -rw-r--r-- | res/client.js | 252 | ||||
| -rw-r--r-- | res/css/des.css | 6 | ||||
| -rw-r--r-- | res/index.html | 3 | ||||
| -rw-r--r-- | res/langs/en.json | 1 | ||||
| -rw-r--r-- | res/langs/ru.json | 5 | ||||
| -rw-r--r-- | src/Types.hx | 3 | ||||
| -rw-r--r-- | src/client/Buttons.hx | 1 | ||||
| -rw-r--r-- | src/client/IPlayer.hx | 4 | ||||
| -rw-r--r-- | src/client/Main.hx | 29 | ||||
| -rw-r--r-- | src/client/Player.hx | 124 | ||||
| -rw-r--r-- | src/client/players/Iframe.hx | 15 | ||||
| -rw-r--r-- | src/client/players/Raw.hx | 19 | ||||
| -rw-r--r-- | src/client/players/Youtube.hx | 26 | ||||
| -rw-r--r-- | src/server/Main.hx | 2 |
16 files changed, 481 insertions, 61 deletions
diff --git a/.vscode/extensions.json b/.vscode/extensions.json deleted file mode 100644 index fc94805..0000000 --- a/.vscode/extensions.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "recommendations": [ - "nadako.vshaxe", - "hookyqr.beautify" - ] -} diff --git a/build/server.js b/build/server.js index cc6ce37..2646c9f 100644 --- a/build/server.js +++ b/build/server.js @@ -1314,7 +1314,7 @@ JsonParser_$43.__name__ = true; JsonParser_$43.__super__ = json2object_reader_BaseParser; JsonParser_$43.prototype = $extend(json2object_reader_BaseParser.prototype,{ onIncorrectType: function(pos,variable) { - this.errors.push(json2object_Error.IncorrectType(variable,"{ url : String, title : String, ?subs : Null<String>, isTemp : Bool, isIframe : Bool, duration : Float, author : String }",pos)); + this.errors.push(json2object_Error.IncorrectType(variable,"{ ?voiceOverTrack : Null<String>, url : String, title : String, ?subs : Null<String>, isTemp : Bool, isIframe : Bool, duration : Float, author : String }",pos)); json2object_reader_BaseParser.prototype.onIncorrectType.call(this,pos,variable); } ,loadJsonNull: function(pos,variable) { @@ -1322,7 +1322,7 @@ JsonParser_$43.prototype = $extend(json2object_reader_BaseParser.prototype,{ } ,loadJsonObject: function(o,pos,variable) { var assigned = new haxe_ds_StringMap(); - this.objectSetupAssign(assigned,["author","duration","isIframe","isTemp","subs","title","url"],[false,false,false,false,true,false,false]); + this.objectSetupAssign(assigned,["author","duration","isIframe","isTemp","subs","title","url","voiceOverTrack"],[false,false,false,false,true,false,false,true]); this.value = this.getAuto(); var _g = 0; while(_g < o.length) { @@ -1350,6 +1350,9 @@ JsonParser_$43.prototype = $extend(json2object_reader_BaseParser.prototype,{ case "url": this.loadObjectFieldReflect(($_=new JsonParser_$44(this.errors,this.putils,1),$bind($_,$_.loadJson)),field,"url",assigned,pos); break; + case "voiceOverTrack": + this.value.voiceOverTrack = this.loadObjectField(($_=new JsonParser_$48(this.errors,this.putils,1),$bind($_,$_.loadJson)),field,"voiceOverTrack",assigned,this.value.voiceOverTrack,pos); + break; default: this.errors.push(json2object_Error.UnknownVariable(field.name,this.putils.convertPosition(field.namePos))); } @@ -1357,7 +1360,7 @@ JsonParser_$43.prototype = $extend(json2object_reader_BaseParser.prototype,{ this.objectErrors(assigned,pos); } ,getAuto: function() { - return { author : new JsonParser_$44([],this.putils,0).loadJson(new hxjsonast_Json(hxjsonast_JsonValue.JNull,new hxjsonast_Position("",0,1))), duration : new JsonParser_$45([],this.putils,0).loadJson(new hxjsonast_Json(hxjsonast_JsonValue.JNull,new hxjsonast_Position("",0,1))), isIframe : new JsonParser_$46([],this.putils,0).loadJson(new hxjsonast_Json(hxjsonast_JsonValue.JNull,new hxjsonast_Position("",0,1))), isTemp : new JsonParser_$46([],this.putils,0).loadJson(new hxjsonast_Json(hxjsonast_JsonValue.JNull,new hxjsonast_Position("",0,1))), subs : new JsonParser_$48([],this.putils,0).loadJson(new hxjsonast_Json(hxjsonast_JsonValue.JNull,new hxjsonast_Position("",0,1))), title : new JsonParser_$44([],this.putils,0).loadJson(new hxjsonast_Json(hxjsonast_JsonValue.JNull,new hxjsonast_Position("",0,1))), url : new JsonParser_$44([],this.putils,0).loadJson(new hxjsonast_Json(hxjsonast_JsonValue.JNull,new hxjsonast_Position("",0,1)))}; + return { author : new JsonParser_$44([],this.putils,0).loadJson(new hxjsonast_Json(hxjsonast_JsonValue.JNull,new hxjsonast_Position("",0,1))), duration : new JsonParser_$45([],this.putils,0).loadJson(new hxjsonast_Json(hxjsonast_JsonValue.JNull,new hxjsonast_Position("",0,1))), isIframe : new JsonParser_$46([],this.putils,0).loadJson(new hxjsonast_Json(hxjsonast_JsonValue.JNull,new hxjsonast_Position("",0,1))), isTemp : new JsonParser_$46([],this.putils,0).loadJson(new hxjsonast_Json(hxjsonast_JsonValue.JNull,new hxjsonast_Position("",0,1))), subs : new JsonParser_$48([],this.putils,0).loadJson(new hxjsonast_Json(hxjsonast_JsonValue.JNull,new hxjsonast_Position("",0,1))), title : new JsonParser_$44([],this.putils,0).loadJson(new hxjsonast_Json(hxjsonast_JsonValue.JNull,new hxjsonast_Position("",0,1))), url : new JsonParser_$44([],this.putils,0).loadJson(new hxjsonast_Json(hxjsonast_JsonValue.JNull,new hxjsonast_Position("",0,1))), voiceOverTrack : new JsonParser_$48([],this.putils,0).loadJson(new hxjsonast_Json(hxjsonast_JsonValue.JNull,new hxjsonast_Position("",0,1)))}; } ,__class__: JsonParser_$43 }); @@ -2801,7 +2804,7 @@ StringTools.hex = function(n,digits) { var _$Types_VideoItemTools = function() { }; _$Types_VideoItemTools.__name__ = true; _$Types_VideoItemTools.withUrl = function(item,url) { - return { url : url, title : item.title, author : item.author, duration : item.duration, subs : item.subs, isTemp : item.isTemp, isIframe : item.isIframe}; + return { url : url, title : item.title, author : item.author, duration : item.duration, subs : item.subs, voiceOverTrack : item.voiceOverTrack, isTemp : item.isTemp, isIframe : item.isIframe}; }; var VideoList = function() { this.items = []; @@ -4606,8 +4609,11 @@ server_Main.prototype = { haxe_Log.trace("Global network is disabled in config",{ fileName : "src/server/Main.hx", lineNumber : 139, className : "server.Main", methodName : "runServer"}); } else if(!this.isNoState) { server_Utils.getGlobalIp(function(ip) { + if(ip.indexOf(":") != -1) { + ip = "[" + ip + "]"; + } _gthis.globalIp = ip; - haxe_Log.trace("Global: http://" + _gthis.globalIp + ":" + _gthis.port,{ fileName : "src/server/Main.hx", lineNumber : 143, className : "server.Main", methodName : "runServer"}); + haxe_Log.trace("Global: http://" + _gthis.globalIp + ":" + _gthis.port,{ fileName : "src/server/Main.hx", lineNumber : 145, className : "server.Main", methodName : "runServer"}); }); } var dir = "" + this.rootDir + "/res"; @@ -4692,7 +4698,7 @@ server_Main.prototype = { var field = _g1[_g]; ++_g; if(Reflect.field(config,field) == null) { - haxe_Log.trace("Warning: config field \"" + field + "\" is unknown",{ fileName : "src/server/Main.hx", lineNumber : 212, className : "server.Main", methodName : "getUserConfig"}); + haxe_Log.trace("Warning: config field \"" + field + "\" is unknown",{ fileName : "src/server/Main.hx", lineNumber : 214, className : "server.Main", methodName : "getUserConfig"}); } config[field] = Reflect.field(customConfig,field); } @@ -4703,14 +4709,14 @@ server_Main.prototype = { var emote = _g1[_g]; ++_g; if(emoteCopies_h[emote.name]) { - haxe_Log.trace("Warning: emote name \"" + emote.name + "\" has copy",{ fileName : "src/server/Main.hx", lineNumber : 218, className : "server.Main", methodName : "getUserConfig"}); + haxe_Log.trace("Warning: emote name \"" + emote.name + "\" has copy",{ fileName : "src/server/Main.hx", lineNumber : 220, className : "server.Main", methodName : "getUserConfig"}); } emoteCopies_h[emote.name] = true; if(!this.verbose) { continue; } if(emoteCopies_h[emote.image]) { - haxe_Log.trace("Warning: emote url of name \"" + emote.name + "\" has copy",{ fileName : "src/server/Main.hx", lineNumber : 222, className : "server.Main", methodName : "getUserConfig"}); + haxe_Log.trace("Warning: emote url of name \"" + emote.name + "\" has copy",{ fileName : "src/server/Main.hx", lineNumber : 224, className : "server.Main", methodName : "getUserConfig"}); } emoteCopies_h[emote.image] = true; } @@ -4748,7 +4754,7 @@ server_Main.prototype = { js_node_Fs.writeFileSync("" + folder + "/users.json",JSON.stringify({ admins : users1, bans : _g, salt : users.salt},null,"\t")); } ,saveState: function() { - haxe_Log.trace("Saving state...",{ fileName : "src/server/Main.hx", lineNumber : 261, className : "server.Main", methodName : "saveState"}); + haxe_Log.trace("Saving state...",{ fileName : "src/server/Main.hx", lineNumber : 263, className : "server.Main", methodName : "saveState"}); var json = JSON.stringify(this.getCurrentState(),null,"\t"); js_node_Fs.writeFileSync(this.statePath,json); this.writeUsers(this.userList); @@ -4763,7 +4769,7 @@ server_Main.prototype = { if(!sys_FileSystem.exists(this.statePath)) { return; } - haxe_Log.trace("Loading state...",{ fileName : "src/server/Main.hx", lineNumber : 284, className : "server.Main", methodName : "loadState"}); + haxe_Log.trace("Loading state...",{ fileName : "src/server/Main.hx", lineNumber : 286, className : "server.Main", methodName : "loadState"}); var state = JSON.parse(js_node_Fs.readFileSync(this.statePath,{ encoding : "utf8"})); this.videoList.setItems(state.videoList); this.videoList.isOpen = state.isPlaylistOpen; @@ -4782,7 +4788,7 @@ server_Main.prototype = { this.videoTimer.pause(); } ,logError: function(type,data) { - haxe_Log.trace(type,{ fileName : "src/server/Main.hx", lineNumber : 302, className : "server.Main", methodName : "logError", customParams : [data]}); + haxe_Log.trace(type,{ fileName : "src/server/Main.hx", lineNumber : 304, className : "server.Main", methodName : "logError", customParams : [data]}); var crashesFolder = "" + this.rootDir + "/user/crashes"; server_Utils.ensureDir(crashesFolder); var name = DateTools.format(new Date(),"%Y-%m-%d_%H_%M_%S") + "-" + type; @@ -4800,7 +4806,7 @@ server_Main.prototype = { if(_gthis.clients.length == 0) { return; } - haxe_Log.trace("Ping " + url,{ fileName : "src/server/Main.hx", lineNumber : 319, className : "server.Main", methodName : "initIntergationHandlers"}); + haxe_Log.trace("Ping " + url,{ fileName : "src/server/Main.hx", lineNumber : 321, className : "server.Main", methodName : "initIntergationHandlers"}); js_node_Http.get(url,null,function(r) { }); }; @@ -4820,13 +4826,13 @@ server_Main.prototype = { password += this.config.salt; var hash = haxe_crypto_Sha256.encode(password); this.userList.admins.push({ name : name, hash : hash}); - haxe_Log.trace("Admin " + name + " added.",{ fileName : "src/server/Main.hx", lineNumber : 342, className : "server.Main", methodName : "addAdmin"}); + haxe_Log.trace("Admin " + name + " added.",{ fileName : "src/server/Main.hx", lineNumber : 344, className : "server.Main", methodName : "addAdmin"}); } ,removeAdmin: function(name) { HxOverrides.remove(this.userList.admins,Lambda.find(this.userList.admins,function(item) { return item.name == name; })); - haxe_Log.trace("Admin " + name + " removed.",{ fileName : "src/server/Main.hx", lineNumber : 349, className : "server.Main", methodName : "removeAdmin"}); + haxe_Log.trace("Admin " + name + " removed.",{ fileName : "src/server/Main.hx", lineNumber : 351, className : "server.Main", methodName : "removeAdmin"}); } ,replayLog: function(events) { var _gthis = this; @@ -4893,7 +4899,7 @@ server_Main.prototype = { var ip = this.clientIp(req); var id = this.freeIds.length > 0 ? this.freeIds.shift() : this.clients.length; var name = "Guest " + (id + 1); - haxe_Log.trace(HxOverrides.dateStr(new Date()),{ fileName : "src/server/Main.hx", lineNumber : 408, className : "server.Main", methodName : "onConnect", customParams : ["" + name + " connected (" + ip + ")"]}); + haxe_Log.trace(HxOverrides.dateStr(new Date()),{ fileName : "src/server/Main.hx", lineNumber : 410, className : "server.Main", methodName : "onConnect", customParams : ["" + name + " connected (" + ip + ")"]}); var isAdmin = this.config.localAdmins && req.socket.localAddress == ip; var client = new Client(ws,req,id,name,0); client.uuid = uuid; @@ -4907,7 +4913,7 @@ server_Main.prototype = { var obj = _gthis.wsEventParser.fromJson(data.toString()); if(_gthis.wsEventParser.errors.length > 0 || _gthis.noTypeObj(obj)) { var errors = "" + ("Wrong request for type \"" + obj.type + "\":") + "\n" + json2object_ErrorUtils.convertErrorArray(_gthis.wsEventParser.errors); - haxe_Log.trace(errors,{ fileName : "src/server/Main.hx", lineNumber : 425, className : "server.Main", methodName : "onConnect"}); + haxe_Log.trace(errors,{ fileName : "src/server/Main.hx", lineNumber : 427, className : "server.Main", methodName : "onConnect"}); _gthis.serverMessage(client,errors); return; } @@ -5058,7 +5064,7 @@ server_Main.prototype = { if(!internal) { return; } - haxe_Log.trace(HxOverrides.dateStr(new Date()),{ fileName : "src/server/Main.hx", lineNumber : 487, className : "server.Main", methodName : "onMessage", customParams : ["Client " + client.name + " disconnected"]}); + haxe_Log.trace(HxOverrides.dateStr(new Date()),{ fileName : "src/server/Main.hx", lineNumber : 489, className : "server.Main", methodName : "onMessage", customParams : ["Client " + client.name + " disconnected"]}); server_Utils.sortedPush(this.freeIds,client.id); HxOverrides.remove(this.clients,client); this.sendClientList(); @@ -5199,7 +5205,7 @@ server_Main.prototype = { this.send(client,{ type : "LoginError"}); return; } - haxe_Log.trace(HxOverrides.dateStr(new Date()),{ fileName : "src/server/Main.hx", lineNumber : 575, className : "server.Main", methodName : "onMessage", customParams : ["Client " + client.name + " logged as " + name]}); + haxe_Log.trace(HxOverrides.dateStr(new Date()),{ fileName : "src/server/Main.hx", lineNumber : 577, className : "server.Main", methodName : "onMessage", customParams : ["Client " + client.name + " logged as " + name]}); client.name = name; client.setGroupFlag(ClientGroup.User,true); this.checkBan(client); @@ -5212,7 +5218,7 @@ server_Main.prototype = { var oldName = client.name; client.name = "Guest " + (this.clients.indexOf(client) + 1); client.setGroupFlag(ClientGroup.User,false); - haxe_Log.trace(HxOverrides.dateStr(new Date()),{ fileName : "src/server/Main.hx", lineNumber : 596, className : "server.Main", methodName : "onMessage", customParams : ["Client " + oldName + " logout to " + client.name]}); + haxe_Log.trace(HxOverrides.dateStr(new Date()),{ fileName : "src/server/Main.hx", lineNumber : 598, className : "server.Main", methodName : "onMessage", customParams : ["Client " + oldName + " logout to " + client.name]}); this.send(client,{ type : data.type, logout : { oldClientName : oldName, clientName : client.name, clients : this.clientList()}}); this.sendClientListExcept(client); break; @@ -5527,7 +5533,7 @@ server_Main.prototype = { client.setGroupFlag(ClientGroup.Banned,!isOutdated); if(isOutdated) { HxOverrides.remove(this.userList.bans,ban); - haxe_Log.trace("" + client.name + " ban removed",{ fileName : "src/server/Main.hx", lineNumber : 1008, className : "server.Main", methodName : "checkBan"}); + haxe_Log.trace("" + client.name + " ban removed",{ fileName : "src/server/Main.hx", lineNumber : 1010, className : "server.Main", methodName : "checkBan"}); this.sendClientList(); } break; diff --git a/res/client.js b/res/client.js index 5bab611..d26396f 100644 --- a/res/client.js +++ b/res/client.js @@ -484,7 +484,7 @@ StringTools.hex = function(n,digits) { var _$Types_VideoItemTools = function() { }; _$Types_VideoItemTools.__name__ = true; _$Types_VideoItemTools.withUrl = function(item,url) { - return { url : url, title : item.title, author : item.author, duration : item.duration, subs : item.subs, isTemp : item.isTemp, isIframe : item.isIframe}; + return { url : url, title : item.title, author : item.author, duration : item.duration, subs : item.subs, voiceOverTrack : item.voiceOverTrack, isTemp : item.isTemp, isIframe : item.isIframe}; }; var VideoList = function() { this.items = []; @@ -764,6 +764,8 @@ client_Buttons.init = function(main) { var isRawSingleVideo = value != "" && main.isRawPlayerLink(value) && main.isSingleVideoLink(value); window.document.querySelector("#mediatitleblock").style.display = isRawSingleVideo ? "" : "none"; window.document.querySelector("#subsurlblock").style.display = isRawSingleVideo ? "" : "none"; + var tmp = value.length > 0 ? "" : "none"; + window.document.querySelector("#voiceoverblock").style.display = tmp; var panel = window.document.querySelector("#addfromurl"); var oldH = panel.style.height; panel.style.height = ""; @@ -1253,6 +1255,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.mask = new EReg("\\${([0-9]+)-([0-9]+)}","g"); + this.gotFirstPageInteraction = false; this.disabledReconnection = false; this.gotInitialConnection = false; this.isConnected = false; @@ -1290,6 +1293,7 @@ var client_Main = function() { _gthis.openWebSocket(); }); client_JsApi.init(this,this.player); + window.document.addEventListener("click",$bind(this,this.onFirstInteraction)); }; client_Main.__name__ = true; client_Main.main = function() { @@ -1317,7 +1321,19 @@ client_Main.serverMessage = function(text,isText,withTimestamp) { msgBuf.scrollTop = msgBuf.scrollHeight; }; client_Main.prototype = { - settingsPatcher: function(data,version) { + onFirstInteraction: function() { + if(this.gotFirstPageInteraction) { + return; + } + if(!this.player.isVideoLoaded()) { + return; + } + this.gotFirstPageInteraction = true; + this.player.unmute(); + this.player.play(); + window.document.removeEventListener("click",$bind(this,this.onFirstInteraction)); + } + ,settingsPatcher: function(data,version) { switch(version) { case 1: data.hotkeysEnabled = true; @@ -1540,7 +1556,7 @@ client_Main.prototype = { } 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, subs : data.subs, isIframe : data.isIframe == true}, atEnd : atEnd}}); + _gthis.send({ type : "AddVideo", addVideo : { item : { url : data.url, title : data.title, author : _gthis.personal.name, duration : data.duration, isTemp : isTemp, subs : data.subs, voiceOverTrack : data.voiceOverTrack, isIframe : data.isIframe == true}, atEnd : atEnd}}); if(callback != null) { callback(); } @@ -1609,7 +1625,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 : 420, 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 : 433, 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) { @@ -1663,9 +1679,11 @@ client_Main.prototype = { if(this.player.getDuration() <= this.player.getTime() + synchThreshold) { return; } - if(!data.getTime.paused) { - this.player.play(); - } else { + if(this.player.isPaused()) { + if(!data.getTime.paused) { + this.player.play(); + } + } else if(data.getTime.paused) { this.player.pause(); } this.player.setPauseIndicator(!data.getTime.paused); @@ -2361,6 +2379,13 @@ client_Main.prototype = { ,getYoutubePlaylistLimit: function() { return this.config.youtubePlaylistLimit; } + ,isAutoplayAllowed: function() { + var navigator = $global.navigator; + if(navigator.getAutoplayPolicy != null) { + return navigator.getAutoplayPolicy("mediaelement"); + } + return this.gotFirstPageInteraction; + } ,isVerbose: function() { return this.config.isVerbose; } @@ -2370,6 +2395,10 @@ client_Main.prototype = { } }; var client_Player = function(main) { + this.voiceOverVolume = 0.3; + this.needsVolumeReset = false; + this.isAudioTrackLoaded = false; + this.voiceOverInput = window.document.querySelector("#voiceoverurl"); this.skipSetRate = false; this.skipSetTime = false; this.isLoaded = false; @@ -2378,7 +2407,8 @@ var client_Player = function(main) { this.videoList = new VideoList(); this.main = main; this.youtube = new client_players_Youtube(main,this); - this.players = [this.youtube,new client_players_Streamable(main,this)]; + this.streamable = new client_players_Streamable(main,this); + this.players = [this.youtube,this.streamable]; this.iframePlayer = new client_players_Iframe(main,this); this.rawPlayer = new client_players_Raw(main,this); this.initItemButtons(); @@ -2426,21 +2456,31 @@ client_Player.prototype = { var _this = this.videoList; client_JsApi.fireVideoRemoveEvents(_this.items[_this.pos]); this.player.removeVideo(); + this.removeExternalAudioTrack(); } this.main.blinkTabWithTitle("*Video*"); } this.player = newPlayer; } - ,getVideoData: function(data,callback) { + ,getVideoData: function(req,callback) { + var _gthis = this; var player = Lambda.find(this.players,function(player) { - return player.isSupportedLink(data.url); + return player.isSupportedLink(req.url); }); if(player == null) { player = this.rawPlayer; } - player.getVideoData(data,callback); + player.getVideoData(req,function(data) { + var voiceOverTrack = StringTools.trim(_gthis.voiceOverInput.value); + data.voiceOverTrack = voiceOverTrack; + _gthis.voiceOverInput.value = ""; + callback(data); + }); } ,isRawPlayerLink: function(url) { + if(this.streamable.isSupportedLink(url)) { + return true; + } return !Lambda.exists(this.players,function(player) { return player.isSupportedLink(url); }); @@ -2460,12 +2500,55 @@ client_Player.prototype = { this.isLoaded = false; if(this.main.isVideoEnabled) { this.player.loadVideo(item); + this.setExternalAudioTrack(item); } else { this.onCanBePlayed(); } client_JsApi.fireVideoChangeEvents(item); window.document.querySelector("#currenttitle").textContent = item.title; } + ,setExternalAudioTrack: function(item) { + var _gthis = this; + this.removeExternalAudioTrack(); + var tmp = item.voiceOverTrack; + if(tmp == null) { + return; + } + if(tmp.length == 0) { + return; + } + this.audioTrack = new Audio(tmp); + if(!this.main.isAutoplayAllowed()) { + this.audioTrack.muted = true; + } + this.audioTrack.oncanplay = function() { + _gthis.audioTrack.oncanplay = null; + _gthis.audioTrack.onerror = null; + return _gthis.isAudioTrackLoaded = true; + }; + this.audioTrack.onerror = function(e) { + haxe_Log.trace(e,{ fileName : "src/client/Player.hx", lineNumber : 174, className : "client.Player", methodName : "setExternalAudioTrack"}); + _gthis.audioTrack.oncanplay = null; + _gthis.audioTrack.onerror = null; + _gthis.isAudioTrackLoaded = false; + _gthis.audioTrack = null; + _gthis.setVolume(1); + }; + } + ,removeExternalAudioTrack: function() { + this.isAudioTrackLoaded = false; + this.needsVolumeReset = false; + if(this.audioTrack == null) { + return; + } + var tmp = this.audioTrack; + if(tmp != null) { + tmp.pause(); + } + this.audioTrack.src = null; + this.audioTrack = null; + this.needsVolumeReset = true; + } ,setSupportedPlayer: function(url,isIframe) { var currentPlayer = Lambda.find(this.players,function(p) { return p.isSupportedLink(url); @@ -2515,6 +2598,10 @@ client_Player.prototype = { this.isLoaded = true; } ,onPlay: function() { + var tmp = this.audioTrack; + if(tmp != null) { + tmp.play(); + } if((this.main.personal.group & 4) == 0) { return; } @@ -2527,6 +2614,10 @@ client_Player.prototype = { } ,onPause: function() { var _gthis = this; + var tmp = this.audioTrack; + if(tmp != null) { + tmp.pause(); + } var _this = this.videoList; var tmp = _this.items[_this.pos]; if(tmp == null) { @@ -2540,7 +2631,7 @@ client_Player.prototype = { return; } } - if(this.main.hasLeaderOnPauseRequest() && this.videoList.items.length > 0 && this.getTime() > 1 && !this.main.hasLeader()) { + if(this.main.hasLeaderOnPauseRequest() && this.videoList.items.length > 0 && this.getTime() > 1 && this.isLoaded && !this.main.hasLeader()) { client_JsApi.once("SetLeader",function(event) { if(event.setLeader.clientName != _gthis.main.personal.name) { return; @@ -2557,6 +2648,9 @@ client_Player.prototype = { this.main.send({ type : "Pause", pause : { time : this.getTime()}}); } ,onSetTime: function() { + if(this.audioTrack != null) { + this.audioTrack.currentTime = this.getTime(); + } if(this.skipSetTime) { this.skipSetTime = false; return; @@ -2567,6 +2661,9 @@ client_Player.prototype = { this.main.send({ type : "SetTime", setTime : { time : this.getTime()}}); } ,onRateChange: function() { + if(this.audioTrack != null) { + this.audioTrack.playbackRate = this.getPlaybackRate(); + } if(this.skipSetRate) { this.skipSetRate = false; return; @@ -2758,6 +2855,9 @@ client_Player.prototype = { return _this.items[_this.pos].duration; } ,isVideoLoaded: function() { + if(this.player == null) { + return false; + } return this.player.isVideoLoaded(); } ,play: function() { @@ -2771,6 +2871,16 @@ client_Player.prototype = { return; } this.player.play(); + if(this.needsVolumeReset) { + this.setVolume(1); + } + if(this.audioTrack != null) { + this.setVolume(0.3); + var tmp = this.audioTrack; + if(tmp != null) { + tmp.play(); + } + } } ,pause: function() { if(!this.main.isSyncActive) { @@ -2783,6 +2893,10 @@ client_Player.prototype = { return; } this.player.pause(); + var tmp = this.audioTrack; + if(tmp != null) { + tmp.pause(); + } } ,getTime: function() { if(this.player == null) { @@ -2808,6 +2922,9 @@ client_Player.prototype = { } this.skipSetTime = isLocal; this.player.setTime(time); + if(this.audioTrack != null) { + this.audioTrack.currentTime = time; + } } ,getPlaybackRate: function() { if(this.player == null) { @@ -2833,6 +2950,9 @@ client_Player.prototype = { } this.skipSetRate = isLocal; this.player.setPlaybackRate(rate); + if(this.audioTrack != null) { + this.audioTrack.playbackRate = rate; + } } ,skipAd: function() { var _gthis = this; @@ -2865,10 +2985,59 @@ client_Player.prototype = { } }; http.onError = function(msg) { - haxe_Log.trace(msg,{ fileName : "src/client/Player.hx", lineNumber : 484, className : "client.Player", methodName : "skipAd"}); + haxe_Log.trace(msg,{ fileName : "src/client/Player.hx", lineNumber : 564, className : "client.Player", methodName : "skipAd"}); }; http.request(); } + ,isPaused: function() { + if(this.player == null) { + return true; + } + if(!this.player.isVideoLoaded()) { + return true; + } + return this.player.isPaused(); + } + ,getVolume: function() { + if(this.player == null) { + return 1; + } + if(!this.player.isVideoLoaded()) { + return 1; + } + return this.player.getVolume(); + } + ,setVolume: function(volume) { + if(this.player == null) { + return; + } + if(!this.player.isVideoLoaded()) { + return; + } + this.player.setVolume(volume); + } + ,unmute: function() { + if(this.player == null) { + return; + } + if(!this.player.isVideoLoaded()) { + return; + } + this.player.unmute(); + if(this.audioTrack != null) { + this.audioTrack.muted = false; + } + if(this.audioTrack == null && this.almostEq(this.getVolume(),this.voiceOverVolume,0.01)) { + this.setVolume(1); + } + } + ,almostEq: function(a,b,diff) { + if(a > b - diff) { + return a < b + diff; + } else { + return false; + } + } }; var client_Settings = function() { }; client_Settings.__name__ = true; @@ -3109,6 +3278,9 @@ client_players_Iframe.prototype = { } ,pause: function() { } + ,isPaused: function() { + return false; + } ,getTime: function() { return 0; } @@ -3119,6 +3291,13 @@ client_players_Iframe.prototype = { } ,setPlaybackRate: function(rate) { } + ,getVolume: function() { + return 1; + } + ,setVolume: function(volume) { + } + ,unmute: function() { + } }; var client_players_Raw = function(main,player) { this.isHlsLoaded = false; @@ -3233,6 +3412,9 @@ client_players_Raw.prototype = { }; this.video.onpause = ($_=this.player,$bind($_,$_.onPause)); this.video.onratechange = ($_=this.player,$bind($_,$_.onRateChange)); + if(!this.main.isAutoplayAllowed()) { + this.video.muted = true; + } this.playerEl.appendChild(this.video); } if(isHls) { @@ -3321,6 +3503,9 @@ client_players_Raw.prototype = { ,pause: function() { this.video.pause(); } + ,isPaused: function() { + return this.video.paused; + } ,getTime: function() { return this.video.currentTime; } @@ -3333,6 +3518,15 @@ client_players_Raw.prototype = { ,setPlaybackRate: function(rate) { this.video.playbackRate = rate; } + ,getVolume: function() { + return this.video.volume; + } + ,setVolume: function(volume) { + this.video.volume = volume; + } + ,unmute: function() { + this.video.muted = false; + } }; var client_players_RawSubs = function() { }; client_players_RawSubs.__name__ = true; @@ -3677,7 +3871,8 @@ client_players_Youtube.prototype = { var title = item.snippet.title; var duration = _gthis.convertTime(item.contentDetails.duration); if(duration == 0) { - callback({ duration : 356400, title : title, url : "<iframe src=\"https://www.youtube.com/embed/" + id + "\" frameborder=\"0\"\n\t\t\t\t\t\t\tallow=\"accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture\"\n\t\t\t\t\t\t\tallowfullscreen></iframe>", isIframe : true}); + var mute = _gthis.main.isAutoplayAllowed() ? "" : "&mute=1"; + callback({ duration : 356400, title : title, url : "<iframe src=\"https://www.youtube.com/embed/" + id + "?autoplay=1" + mute + "\" frameborder=\"0\"\n\t\t\t\t\t\t\tallow=\"accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture\"\n\t\t\t\t\t\t\tallowfullscreen></iframe>", isIframe : true}); continue; } callback({ duration : duration, title : title, url : url}); @@ -3756,7 +3951,7 @@ client_players_Youtube.prototype = { } callback({ duration : _gthis.tempYoutube.getDuration()}); }, onError : function(e) { - haxe_Log.trace("Error " + e.data,{ fileName : "src/client/players/Youtube.hx", lineNumber : 186, className : "client.players.Youtube", methodName : "getRemoteDataFallback"}); + haxe_Log.trace("Error " + e.data,{ fileName : "src/client/players/Youtube.hx", lineNumber : 187, className : "client.players.Youtube", methodName : "getRemoteDataFallback"}); if(_gthis.playerEl.contains(video)) { _gthis.playerEl.removeChild(video); } @@ -3779,7 +3974,10 @@ client_players_Youtube.prototype = { this.video = window.document.createElement("div"); this.video.id = "videoplayer"; this.playerEl.appendChild(this.video); - this.youtube = new YT.Player(this.video.id,{ videoId : this.extractVideoId(item.url), playerVars : { autoplay : 1, playsinline : 1, modestbranding : 1, rel : 0, showinfo : 0}, events : { onReady : function(e) { + this.youtube = new YT.Player(this.video.id,{ videoId : this.extractVideoId(item.url), playerVars : { autoplay : 1, playsinline : 1, rel : 0}, events : { onReady : function(e) { + if(!_gthis.main.isAutoplayAllowed()) { + e.target.mute(); + } _gthis.isLoaded = true; _gthis.youtube.pauseVideo(); }, onStateChange : function(e) { @@ -3804,7 +4002,7 @@ client_players_Youtube.prototype = { }, onPlaybackRateChange : function(e) { _gthis.player.onRateChange(); }, onError : function(e) { - haxe_Log.trace("Error " + e.data,{ fileName : "src/client/players/Youtube.hx", lineNumber : 243, className : "client.players.Youtube", methodName : "loadVideo"}); + haxe_Log.trace("Error " + e.data,{ fileName : "src/client/players/Youtube.hx", lineNumber : 245, className : "client.players.Youtube", methodName : "loadVideo"}); var tmp = _gthis.player.getCurrentItem(); if(tmp == null) { return; @@ -3818,8 +4016,8 @@ client_players_Youtube.prototype = { var info = event.getYoutubeVideoInfo.response; var tmp = _gthis.getBestStreamFormat(info); if(tmp == null) { - haxe_Log.trace("format not found in response info:",{ fileName : "src/client/players/Youtube.hx", lineNumber : 256, className : "client.players.Youtube", methodName : "rawSourceFallback"}); - haxe_Log.trace(info,{ fileName : "src/client/players/Youtube.hx", lineNumber : 257, className : "client.players.Youtube", methodName : "rawSourceFallback"}); + haxe_Log.trace("format not found in response info:",{ fileName : "src/client/players/Youtube.hx", lineNumber : 258, className : "client.players.Youtube", methodName : "rawSourceFallback"}); + haxe_Log.trace(info,{ fileName : "src/client/players/Youtube.hx", lineNumber : 259, className : "client.players.Youtube", methodName : "rawSourceFallback"}); return; } _gthis.player.changeVideoSrc(tmp.url); @@ -3830,6 +4028,7 @@ client_players_Youtube.prototype = { info.formats = info.formats != null ? info.formats : []; info.adaptiveFormats = info.adaptiveFormats != null ? info.adaptiveFormats : []; var formats = info.adaptiveFormats.concat(info.formats); + haxe_Log.trace(formats,{ fileName : "src/client/players/Youtube.hx", lineNumber : 276, className : "client.players.Youtube", methodName : "getBestStreamFormat"}); var qPriority = [1080,720,480,360,240]; var _g = 0; while(_g < qPriority.length) { @@ -3872,6 +4071,9 @@ client_players_Youtube.prototype = { ,pause: function() { this.youtube.pauseVideo(); } + ,isPaused: function() { + return this.youtube.getPlayerState() == 2; + } ,getTime: function() { return this.youtube.getCurrentTime(); } @@ -3884,6 +4086,18 @@ client_players_Youtube.prototype = { ,setPlaybackRate: function(rate) { this.youtube.setPlaybackRate(rate); } + ,getVolume: function() { + if(this.youtube.isMuted()) { + return 0; + } + return this.youtube.getVolume() / 100; + } + ,setVolume: function(volume) { + this.youtube.setVolume(volume * 100 | 0); + } + ,unmute: function() { + this.youtube.unMute(); + } }; var haxe_Exception = function(message,previous,native) { Error.call(this,message); diff --git a/res/css/des.css b/res/css/des.css index 343f0ad..bd5606d 100644 --- a/res/css/des.css +++ b/res/css/des.css @@ -124,6 +124,7 @@ button { button:not(:first-child) { margin-left: .5rem; } + .server-whisper button { margin-left: 0; font-style: italic; @@ -140,6 +141,7 @@ button.active { button:hover:not(.active) { background-color: var(--background-chat); } + .info header button:hover:not(.active) { background-color: transparent; } @@ -180,6 +182,7 @@ button span { border-color: transparent; transition: border-color ease-in-out 800ms; } + #leader_btn.hint { border-radius: .5rem; border: .125rem solid; @@ -413,7 +416,8 @@ header h4 { } #mediatitle, -#subsurl { +#subsurl, +#voiceoverurl { margin-left: 2rem; flex-grow: 1; } diff --git a/res/index.html b/res/index.html index 9ab9d03..ec557c6 100644 --- a/res/index.html +++ b/res/index.html @@ -96,6 +96,9 @@ <div id="subsurlblock" class="display-flex" style="display: none;"> <input id="subsurl" type="text" placeholder="${subtitlesUrlOptional}"> </div> + <div id="voiceoverblock" class="display-flex" style="display: none;"> + <input id="voiceoverurl" type="text" placeholder="${voiceOverAudioTrackUrlOptional}"> + </div> <div> <label> <input class="add-temp" type="checkbox" checked="checked">${addAsTemporary} diff --git a/res/langs/en.json b/res/langs/en.json index 3f5d47a..488af78 100644 --- a/res/langs/en.json +++ b/res/langs/en.json @@ -82,6 +82,7 @@ "mediaUrl": "Media URL", "optionalTitle": "Title (optional)", "subtitlesUrlOptional": "Subtitles URL (optional)", + "voiceOverAudioTrackUrlOptional": "Voice-over audio URL (optional)", "addTemplateUrl": "Add template URL", "queueNext": "Queue next", "queueLast": "Queue last", diff --git a/res/langs/ru.json b/res/langs/ru.json index 932fb94..49260cb 100644 --- a/res/langs/ru.json +++ b/res/langs/ru.json @@ -80,8 +80,9 @@ "voteForSkip": "Голосовать за пропуск", "addAsTemporary": "Добавить как временный", "mediaUrl": "Ссылка на видео", - "optionalTitle": "Заголовок (необязательно)", - "subtitlesUrlOptional": "Ссылка на субтитры (необязательно)", + "optionalTitle": "Заголовок (опционально)", + "subtitlesUrlOptional": "Ссылка на субтитры (опционально)", + "voiceOverAudioTrackUrlOptional": "Ссылка на аудиодорожку (опционально)", "addTemplateUrl": "Добавить пример ссылки", "queueNext": "След.", "queueLast": "В конец", diff --git a/src/Types.hx b/src/Types.hx index 0e89b85..8567f7b 100644 --- a/src/Types.hx +++ b/src/Types.hx @@ -13,6 +13,7 @@ typedef VideoData = { var ?title:String; var ?url:String; var ?subs:String; + var ?voiceOverTrack:String; var ?isIframe:Bool; } @@ -106,6 +107,7 @@ typedef VideoItem = { var author:String; var duration:Float; var ?subs:String; + var ?voiceOverTrack:String; var isTemp:Bool; var isIframe:Bool; } @@ -118,6 +120,7 @@ private class VideoItemTools { author: item.author, duration: item.duration, subs: item.subs, + voiceOverTrack: item.voiceOverTrack, isTemp: item.isTemp, isIframe: item.isIframe }; diff --git a/src/client/Buttons.hx b/src/client/Buttons.hx index de3aa27..c3581e2 100644 --- a/src/client/Buttons.hx +++ b/src/client/Buttons.hx @@ -218,6 +218,7 @@ class Buttons { && main.isSingleVideoLink(value); ge("#mediatitleblock").style.display = isRawSingleVideo ? "" : "none"; ge("#subsurlblock").style.display = isRawSingleVideo ? "" : "none"; + ge("#voiceoverblock").style.display = value.length > 0 ? "" : "none"; final panel = ge("#addfromurl"); final oldH = panel.style.height; // save for animation panel.style.height = ""; // to calculate height from content diff --git a/src/client/IPlayer.hx b/src/client/IPlayer.hx index 903902e..032ac97 100644 --- a/src/client/IPlayer.hx +++ b/src/client/IPlayer.hx @@ -12,8 +12,12 @@ interface IPlayer { function isVideoLoaded():Bool; function play():Void; function pause():Void; + function isPaused():Bool; function getTime():Float; function setTime(time:Float):Void; function getPlaybackRate():Float; function setPlaybackRate(rate:Float):Void; + function getVolume():Float; + function setVolume(volume:Float):Void; + function unmute():Void; } diff --git a/src/client/Main.hx b/src/client/Main.hx index 2850c75..248e205 100644 --- a/src/client/Main.hx +++ b/src/client/Main.hx @@ -48,6 +48,7 @@ class Main { final player:Player; var onTimeGet:Timer; var onBlinkTab:Null<Timer>; + var gotFirstPageInteraction = false; static function main():Void { new Main(); @@ -94,6 +95,17 @@ class Main { openWebSocket(); }); JsApi.init(this, player); + + document.addEventListener("click", onFirstInteraction); + } + + function onFirstInteraction():Void { + if (gotFirstPageInteraction) return; + if (!player.isVideoLoaded()) return; + gotFirstPageInteraction = true; + player.unmute(); + player.play(); + document.removeEventListener("click", onFirstInteraction); } function settingsPatcher(data:Any, version:Int):Any { @@ -319,6 +331,7 @@ class Main { duration: data.duration, isTemp: isTemp, subs: data.subs, + voiceOverTrack: data.voiceOverTrack, isIframe: data.isIframe == true }, atEnd: atEnd @@ -526,8 +539,11 @@ class Main { } if (player.isVideoLoaded()) forceSyncNextTick = false; if (player.getDuration() <= player.getTime() + synchThreshold) return; - if (!data.getTime.paused) player.play(); - else player.pause(); + if (player.isPaused()) { + if (!data.getTime.paused) player.play(); + } else { + if (data.getTime.paused) player.pause(); + } player.setPauseIndicator(!data.getTime.paused); if (Math.abs(time - newTime) < synchThreshold) return; // +0.5s for buffering @@ -1199,6 +1215,15 @@ class Main { return config.youtubePlaylistLimit; } + public function isAutoplayAllowed():Bool { + final navigator:{ + getAutoplayPolicy:(type:String) -> Bool + } = cast Browser.navigator; + if (navigator.getAutoplayPolicy != null) return + navigator.getAutoplayPolicy("mediaelement"); + return gotFirstPageInteraction; + } + public function isVerbose():Bool { return config.isVerbose; } diff --git a/src/client/Player.hx b/src/client/Player.hx index bc64053..f40c34c 100644 --- a/src/client/Player.hx +++ b/src/client/Player.hx @@ -10,7 +10,9 @@ import client.players.Streamable; import client.players.Youtube; import haxe.Http; import haxe.Json; +import js.html.Audio; import js.html.Element; +import js.html.InputElement; class Player { final main:Main; @@ -25,13 +27,21 @@ class Player { var isLoaded = false; var skipSetTime = false; var skipSetRate = false; + var streamable:Streamable; + + final voiceOverInput:InputElement = cast ge("#voiceoverurl"); + var audioTrack:Null<Audio>; + var isAudioTrackLoaded = false; + var needsVolumeReset = false; + final voiceOverVolume = 0.3; public function new(main:Main):Void { this.main = main; youtube = new Youtube(main, this); + streamable = new Streamable(main, this); players = [ youtube, - new Streamable(main, this) + streamable ]; iframePlayer = new Iframe(main, this); rawPlayer = new Raw(main, this); @@ -97,19 +107,26 @@ class Player { if (player != null) { JsApi.fireVideoRemoveEvents(videoList.currentItem); player.removeVideo(); + removeExternalAudioTrack(); } main.blinkTabWithTitle("*Video*"); } player = newPlayer; } - public function getVideoData(data:VideoDataRequest, callback:(data:VideoData) -> Void):Void { - var player = players.find(player -> player.isSupportedLink(data.url)); + public function getVideoData(req:VideoDataRequest, callback:(data:VideoData) -> Void):Void { + var player = players.find(player -> player.isSupportedLink(req.url)); player ??= rawPlayer; - player.getVideoData(data, callback); + player.getVideoData(req, data -> { + final voiceOverTrack = voiceOverInput.value.trim(); + data.voiceOverTrack = voiceOverTrack; + voiceOverInput.value = ""; + callback(data); + }); } public function isRawPlayerLink(url:String):Bool { + if (streamable.isSupportedLink(url)) return true; return !players.exists(player -> player.isSupportedLink(url)); } @@ -129,6 +146,7 @@ class Player { isLoaded = false; if (main.isVideoEnabled) { player.loadVideo(item); + setExternalAudioTrack(item); } else { onCanBePlayed(); } @@ -136,6 +154,42 @@ class Player { ge("#currenttitle").textContent = item.title; } + function setExternalAudioTrack(item:VideoItem):Void { + removeExternalAudioTrack(); + final voiceOverTrack = item.voiceOverTrack ?? return; + if (voiceOverTrack.length == 0) return; + audioTrack = new Audio(voiceOverTrack); + if (!main.isAutoplayAllowed()) { + audioTrack.muted = true; + } + inline function cleanAudioEvents() { + audioTrack.oncanplay = null; + audioTrack.onerror = null; + } + audioTrack.oncanplay = () -> { + cleanAudioEvents(); + isAudioTrackLoaded = true; + } + audioTrack.onerror = e -> { + trace(e); + cleanAudioEvents(); + isAudioTrackLoaded = false; + audioTrack = null; + setVolume(1); + } + } + + function removeExternalAudioTrack():Void { + isAudioTrackLoaded = false; + needsVolumeReset = false; + if (audioTrack == null) return; + + audioTrack?.pause(); + audioTrack.src = null; + audioTrack = null; + needsVolumeReset = true; + } + function setSupportedPlayer(url:String, isIframe:Bool):Void { final currentPlayer = players.find(p -> p.isSupportedLink(url)); if (currentPlayer != null) setPlayer(currentPlayer); @@ -171,6 +225,8 @@ class Player { } public function onPlay():Void { + audioTrack?.play(); + if (!main.isLeader()) return; main.send({ type: Play, @@ -186,6 +242,8 @@ class Player { } public function onPause():Void { + audioTrack?.pause(); + final item = videoList.currentItem ?? return; // do not send pause if video is ended if (getTime() >= item.duration - 0.01) return; @@ -193,8 +251,10 @@ class Player { if (player == rawPlayer && youtube.isSupportedLink(item.url)) { if (getTime() >= item.duration - 1) return; } - final hasAutoPause = main.hasLeaderOnPauseRequest() && videoList.length > 0 - && getTime() > 1; + final hasAutoPause = main.hasLeaderOnPauseRequest() + && videoList.length > 0 + && getTime() > 1 + && isLoaded; if (hasAutoPause && !main.hasLeader()) { JsApi.once(SetLeader, event -> { final name = event.setLeader.clientName; @@ -220,6 +280,10 @@ class Player { } public function onSetTime():Void { + if (audioTrack != null) { + audioTrack.currentTime = getTime(); + } + if (skipSetTime) { skipSetTime = false; return; @@ -234,6 +298,9 @@ class Player { } public function onRateChange():Void { + if (audioTrack != null) { + audioTrack.playbackRate = getPlaybackRate(); + } if (skipSetRate) { skipSetRate = false; return; @@ -410,6 +477,7 @@ class Player { } public function isVideoLoaded():Bool { + if (player == null) return false; return player.isVideoLoaded(); } @@ -418,6 +486,12 @@ class Player { if (player == null) return; if (!player.isVideoLoaded()) return; player.play(); + if (needsVolumeReset) setVolume(1); + + if (audioTrack != null) { + setVolume(0.3); + audioTrack?.play(); + } } public function pause():Void { @@ -425,6 +499,8 @@ class Player { if (player == null) return; if (!player.isVideoLoaded()) return; player.pause(); + + audioTrack?.pause(); } public function getTime():Float { @@ -439,6 +515,8 @@ class Player { if (!player.isVideoLoaded()) return; skipSetTime = isLocal; player.setTime(time); + + if (audioTrack != null) audioTrack.currentTime = time; } public function getPlaybackRate():Float { @@ -453,6 +531,8 @@ class Player { if (!player.isVideoLoaded()) return; skipSetRate = isLocal; player.setPlaybackRate(rate); + + if (audioTrack != null) audioTrack.playbackRate = rate; } public function skipAd():Void { @@ -484,4 +564,36 @@ class Player { http.onError = msg -> trace(msg); http.request(); } + + public function isPaused():Bool { + if (player == null) return true; + if (!player.isVideoLoaded()) return true; + return player.isPaused(); + } + + public function getVolume():Float { + if (player == null) return 1; + if (!player.isVideoLoaded()) return 1; + return player.getVolume(); + } + + public function setVolume(volume:Float):Void { + if (player == null) return; + if (!player.isVideoLoaded()) return; + player.setVolume(volume); + } + + public function unmute():Void { + if (player == null) return; + if (!player.isVideoLoaded()) return; + player.unmute(); + if (audioTrack != null) audioTrack.muted = false; + if (audioTrack == null && almostEq(getVolume(), voiceOverVolume, 0.01)) { + setVolume(1); + } + } + + function almostEq(a:Float, b:Float, diff:Float):Bool { + return a > b - diff && a < b + diff; + } } diff --git a/src/client/players/Iframe.hx b/src/client/players/Iframe.hx index e07f814..56cf319 100644 --- a/src/client/players/Iframe.hx +++ b/src/client/players/Iframe.hx @@ -34,7 +34,8 @@ class Iframe implements IPlayer { function isValidIframe(iframe:Element):Bool { if (iframe.children.length != 1) return false; - return (iframe.firstChild.nodeName == "IFRAME" || iframe.firstChild.nodeName == "OBJECT"); + return (iframe.firstChild.nodeName == "IFRAME" + || iframe.firstChild.nodeName == "OBJECT"); } public function loadVideo(item:VideoItem):Void { @@ -66,6 +67,10 @@ class Iframe implements IPlayer { public function pause():Void {} + public function isPaused():Bool { + return false; + } + public function getTime():Float { return 0; } @@ -77,4 +82,12 @@ class Iframe implements IPlayer { } public function setPlaybackRate(rate:Float):Void {} + + public function getVolume():Float { + return 1; + } + + public function setVolume(volume:Float) {} + + public function unmute():Void {} } diff --git a/src/client/players/Raw.hx b/src/client/players/Raw.hx index 3f69958..5c5b7a4 100644 --- a/src/client/players/Raw.hx +++ b/src/client/players/Raw.hx @@ -67,7 +67,7 @@ class Raw implements IPlayer { callback({ duration: video.duration, title: title, - subs: subs + subs: subs, }); } Utils.prepend(playerEl, video); @@ -115,6 +115,7 @@ class Raw implements IPlayer { } video.onpause = player.onPause; video.onratechange = player.onRateChange; + if (!main.isAutoplayAllowed()) video.muted = true; playerEl.appendChild(video); } if (isHls) initHlsSource(video, url); @@ -185,6 +186,10 @@ class Raw implements IPlayer { video.pause(); } + public function isPaused():Bool { + return video.paused; + } + public function getTime():Float { return video.currentTime; } @@ -200,4 +205,16 @@ class Raw implements IPlayer { public function setPlaybackRate(rate:Float):Void { video.playbackRate = rate; } + + public function getVolume():Float { + return video.volume; + } + + public function setVolume(volume:Float):Void { + video.volume = volume; + } + + public function unmute():Void { + video.muted = false; + } } diff --git a/src/client/players/Youtube.hx b/src/client/players/Youtube.hx index cde2ef8..3ad9030 100644 --- a/src/client/players/Youtube.hx +++ b/src/client/players/Youtube.hx @@ -86,10 +86,11 @@ class Youtube implements IPlayer { final duration = convertTime(duration); // duration is PT0S for streams if (duration == 0) { + final mute = main.isAutoplayAllowed() ? "" : "&mute=1"; callback({ duration: 99 * 60 * 60, title: title, - url: '<iframe src="https://www.youtube.com/embed/$id" frameborder="0" + url: '<iframe src="https://www.youtube.com/embed/$id?autoplay=1$mute" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>', isIframe: true @@ -211,13 +212,14 @@ class Youtube implements IPlayer { videoId: extractVideoId(item.url), playerVars: { autoplay: 1, + // play videos inline instead of fullscreen on iOS playsinline: 1, - modestbranding: 1, + // related videos only from same channel rel: 0, - showinfo: 0 }, events: { onReady: e -> { + if (!main.isAutoplayAllowed()) e.target.mute(); isLoaded = true; youtube.pauseVideo(); }, @@ -271,6 +273,7 @@ class Youtube implements IPlayer { info.formats ??= []; info.adaptiveFormats ??= []; final formats = info.adaptiveFormats.concat(info.formats); + trace(formats); final qPriority = [1080, 720, 480, 360, 240]; for (q in qPriority) { final quality = '${q}p'; @@ -304,6 +307,10 @@ class Youtube implements IPlayer { youtube.pauseVideo(); } + public function isPaused():Bool { + return youtube.getPlayerState() == PAUSED; + } + public function getTime():Float { return youtube.getCurrentTime(); } @@ -319,4 +326,17 @@ class Youtube implements IPlayer { public function setPlaybackRate(rate:Float):Void { youtube.setPlaybackRate(rate); } + + public function getVolume():Float { + if (youtube.isMuted()) return 0; + return youtube.getVolume() / 100; + } + + public function setVolume(volume:Float):Void { + youtube.setVolume(Std.int(volume * 100)); + } + + public function unmute():Void { + youtube.unMute(); + } } diff --git a/src/server/Main.hx b/src/server/Main.hx index a0b255e..9c2f6e2 100644 --- a/src/server/Main.hx +++ b/src/server/Main.hx @@ -139,6 +139,8 @@ class Main { trace("Global network is disabled in config"); } else { if (!isNoState) Utils.getGlobalIp(ip -> { + final isIp6 = ip.contains(":"); + if (isIp6) ip = '[$ip]'; globalIp = ip; trace('Global: http://$globalIp:$port'); }); |
