aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.vscode/extensions.json6
-rw-r--r--build/server.js46
-rw-r--r--res/client.js252
-rw-r--r--res/css/des.css6
-rw-r--r--res/index.html3
-rw-r--r--res/langs/en.json1
-rw-r--r--res/langs/ru.json5
-rw-r--r--src/Types.hx3
-rw-r--r--src/client/Buttons.hx1
-rw-r--r--src/client/IPlayer.hx4
-rw-r--r--src/client/Main.hx29
-rw-r--r--src/client/Player.hx124
-rw-r--r--src/client/players/Iframe.hx15
-rw-r--r--src/client/players/Raw.hx19
-rw-r--r--src/client/players/Youtube.hx26
-rw-r--r--src/server/Main.hx2
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');
});
send patches to the email below
yukais@pinapelz.com
include the subject [PATCH repo_name]
pinapelz.com
homepage