diff options
| author | RblSb <msrblsb@gmail.com> | 2021-07-05 17:09:56 +0300 |
|---|---|---|
| committer | RblSb <msrblsb@gmail.com> | 2021-07-05 17:09:56 +0300 |
| commit | cf78d464be214eedcd7909001ece2aa0a216d136 (patch) | |
| tree | 5801ef0ca01589ed923c961a93b30278df9ea9d3 | |
| parent | ede45cea8706eb8540e466df9861c2af8ebf9c44 (diff) | |
vtt/srt subs support
| -rw-r--r-- | res/client.js | 417 | ||||
| -rw-r--r-- | res/index.html | 8 | ||||
| -rw-r--r-- | src/PathTools.hx | 11 | ||||
| -rw-r--r-- | src/client/Buttons.hx | 4 | ||||
| -rw-r--r-- | src/client/ClientSettings.hx | 1 | ||||
| -rw-r--r-- | src/client/Main.hx | 24 | ||||
| -rw-r--r-- | src/client/players/Raw.hx | 42 | ||||
| -rw-r--r-- | src/client/players/RawSubs.hx | 139 |
8 files changed, 477 insertions, 169 deletions
diff --git a/res/client.js b/res/client.js index b1891da..acc335f 100644 --- a/res/client.js +++ b/res/client.js @@ -101,6 +101,9 @@ EReg.prototype = { throw haxe_Exception.thrown("EReg::matched"); } } + ,split: function(s) { + return s.replace(this.r,"#__delim__#").split("#__delim__#"); + } }; var HxOverrides = function() { }; HxOverrides.__name__ = true; @@ -224,6 +227,11 @@ Lang.get = function(key) { } }; Math.__name__ = true; +var PathTools = function() { }; +PathTools.__name__ = true; +PathTools.urlExtension = function(url) { + return StringTools.trim(haxe_io_Path.extension(new EReg("[#?]","").split(url)[0])).toLowerCase(); +}; var Reflect = function() { }; Reflect.__name__ = true; Reflect.field = function(o,field) { @@ -627,9 +635,7 @@ client_Buttons.init = function(main) { var value = mediaUrl.value; var isRawSingleVideo = value != "" && main.isRawPlayerLink(value) && main.isSingleVideoLink(value); window.document.querySelector("#mediatitleblock").style.display = isRawSingleVideo ? "" : "none"; - if(client_JsApi.hasSubtitleSupport()) { - window.document.querySelector("#subsurlblock").style.display = isRawSingleVideo ? "" : "none"; - } + return window.document.querySelector("#subsurlblock").style.display = isRawSingleVideo ? "" : "none"; }; mediaUrl.onfocus = mediaUrl.oninput; window.document.querySelector("#insert_template").onclick = function(e) { @@ -1035,9 +1041,9 @@ var client_Main = function() { this.isConnected = false; this.personal = new Client("Unknown",0); this.filters = []; - this.globalIp = ""; this.pageTitle = window.document.title; this.clients = []; + this.globalIp = ""; this.forceSyncNextTick = false; this.isSyncActive = true; var _gthis = this; @@ -1046,7 +1052,7 @@ var client_Main = function() { if(this.host == "") { this.host = "localhost"; } - client_Settings.init({ version : 2, name : "", hash : "", isExtendedPlayer : false, playerSize : 1, chatSize : 300, synchThreshold : 2, isSwapped : false, isUserListHidden : true, latestLinks : [], hotkeysEnabled : true},$bind(this,this.settingsPatcher)); + client_Settings.init({ version : 3, name : "", hash : "", isExtendedPlayer : false, playerSize : 1, chatSize : 300, synchThreshold : 2, isSwapped : false, isUserListHidden : true, latestLinks : [], latestSubs : [], hotkeysEnabled : true},$bind(this,this.settingsPatcher)); this.settings = client_Settings.read(); this.initListeners(); this.onTimeGet = new haxe_Timer(this.settings.synchThreshold * 1000); @@ -1076,6 +1082,9 @@ client_Main.prototype = { data.hotkeysEnabled = true; break; case 2: + data.latestSubs = []; + break; + case 3: throw haxe_Exception.thrown("skipped version " + version); default: throw haxe_Exception.thrown("skipped version " + version); @@ -1145,11 +1154,10 @@ client_Main.prototype = { _gthis.addVideoUrl(true); } }; - window.document.querySelector("#subsurl").onkeydown = function(e) { - if(e.keyCode == 13) { - _gthis.addVideoUrl(true); - } - }; + new client_InputWithHistory(window.document.querySelector("#subsurl"),this.settings.latestSubs,10,function(value) { + _gthis.addVideoUrl(true); + return false; + }); window.document.querySelector("#ce_queue_next").onclick = function(e) { _gthis.addIframe(false); }; @@ -1189,13 +1197,18 @@ client_Main.prototype = { } ,addVideoUrl: function(atEnd) { var mediaUrl = window.document.querySelector("#mediaurl"); + var subsUrl = window.document.querySelector("#subsurl"); var isTemp = window.document.querySelector("#addfromurl").querySelector(".add-temp").checked; var url = mediaUrl.value; + var subs = subsUrl.value; if(url.length == 0) { return; } mediaUrl.value = ""; client_InputWithHistory.pushIfNotLast(this.settings.latestLinks,url); + if(subs.length != 0) { + client_InputWithHistory.pushIfNotLast(this.settings.latestSubs,subs); + } client_Settings.write(this.settings); var _this_r = new RegExp(", ?(https?)","g".split("u").join("")); var links = url.replace(_this_r,"|$1").split("|"); @@ -1328,7 +1341,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 : 360, 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 : 378, 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) { @@ -2541,11 +2554,8 @@ client_players_Raw.prototype = { return; } this.titleInput.value = ""; - var subs = ""; - if(client_JsApi.hasSubtitleSupport()) { - subs = StringTools.trim(this.subsInput.value); - this.subsInput.value = ""; - } + var subs = StringTools.trim(this.subsInput.value); + this.subsInput.value = ""; var video = window.document.createElement("video"); video.src = url; video.onerror = function(e) { @@ -2592,28 +2602,35 @@ client_players_Raw.prototype = { } if(this.video != null) { this.video.src = url; - if(isHls) { - this.initHlsSource(this.video,url); + var _g = 0; + var _g1 = this.video.children; + while(_g < _g1.length) { + var element = _g1[_g]; + ++_g; + if(element.nodeName != "TRACK") { + continue; + } + element.remove(); } - this.restartControlsHider(); - return; + } else { + this.video = window.document.createElement("video"); + this.video.id = "videoplayer"; + this.video.src = url; + this.video.oncanplaythrough = ($_=this.player,$bind($_,$_.onCanBePlayed)); + this.video.onseeking = ($_=this.player,$bind($_,$_.onSetTime)); + this.video.onplay = function(e) { + _gthis.playAllowed = true; + _gthis.player.onPlay(); + }; + this.video.onpause = ($_=this.player,$bind($_,$_.onPause)); + this.video.onratechange = ($_=this.player,$bind($_,$_.onRateChange)); + this.playerEl.appendChild(this.video); } - this.video = window.document.createElement("video"); - this.video.id = "videoplayer"; - this.video.src = url; - this.restartControlsHider(); - this.video.oncanplaythrough = ($_=this.player,$bind($_,$_.onCanBePlayed)); - this.video.onseeking = ($_=this.player,$bind($_,$_.onSetTime)); - this.video.onplay = function(e) { - _gthis.playAllowed = true; - _gthis.player.onPlay(); - }; - this.video.onpause = ($_=this.player,$bind($_,$_.onPause)); - this.video.onratechange = ($_=this.player,$bind($_,$_.onRateChange)); - this.playerEl.appendChild(this.video); if(isHls) { this.initHlsSource(this.video,url); } + this.restartControlsHider(); + client_players_RawSubs.loadSubs(item,this.video); } ,restartControlsHider: function() { var _gthis = this; @@ -2677,6 +2694,69 @@ client_players_Raw.prototype = { this.video.playbackRate = rate; } }; +var client_players_RawSubs = function() { }; +client_players_RawSubs.__name__ = true; +client_players_RawSubs.loadSubs = function(item,video) { + var ext = PathTools.urlExtension(item.subs); + if(client_JsApi.hasSubtitleSupport(ext)) { + return; + } + var url = "/proxy?url=" + client_players_RawSubs.encodeURI(item.subs); + switch(ext) { + case "ass": + break; + case "srt": + client_players_RawSubs.parseSrt(video,url); + break; + case "vtt": + client_players_RawSubs.onParsed(video,"VTT subtitles",url); + break; + } +}; +client_players_RawSubs.parseSrt = function(video,url) { + window.fetch(url).then(function(response) { + return response.text(); + }).then(function(text) { + var subs = []; + var blocks = StringTools.replace(text,"\r\n","\n").split("\n\n"); + var _g = 0; + while(_g < blocks.length) { + var lines = blocks[_g++].split("\n"); + if(lines.length < 3) { + continue; + } + var _g1 = []; + var _g2 = 2; + var _g3 = lines.length; + while(_g2 < _g3) _g1.push(lines[_g2++]); + subs.push({ counter : lines[0], time : StringTools.replace(lines[1],",","."), text : _g1.join("\n")}); + } + var data = "WEBVTT\n\n"; + var _g = 0; + while(_g < subs.length) { + var sub = subs[_g]; + ++_g; + data += "" + sub.counter + "\n"; + data += "" + sub.time + "\n"; + data += "" + sub.text + "\n\n"; + } + haxe_Log.trace(data,{ fileName : "src/client/players/RawSubs.hx", lineNumber : 64, className : "client.players.RawSubs", methodName : "parseSrt"}); + var url = "data:text/plain;base64," + haxe_crypto_Base64.encode(haxe_io_Bytes.ofString(data)); + client_players_RawSubs.onParsed(video,"SRT subtitles",url); + }); +}; +client_players_RawSubs.onParsed = function(video,name,dataUrl) { + var trackEl = window.document.createElement("track"); + trackEl.label = name; + trackEl.kind = "subtitles"; + trackEl.src = dataUrl; + trackEl.default = true; + trackEl.track.mode = "showing"; + video.appendChild(trackEl); +}; +client_players_RawSubs.encodeURI = function(data) { + return encodeURI(data); +}; var client_players_Youtube = function(main,player) { this.matchSeconds = new EReg("([0-9]+)S",""); this.matchMinutes = new EReg("([0-9]+)M",""); @@ -3023,6 +3103,167 @@ haxe_ValueException.prototype = $extend(haxe_Exception.prototype,{ return this.value; } }); +var haxe_io_Bytes = function(data) { + this.length = data.byteLength; + this.b = new Uint8Array(data); + this.b.bufferValue = data; + data.hxBytes = this; + data.bytes = this.b; +}; +haxe_io_Bytes.__name__ = true; +haxe_io_Bytes.ofString = function(s,encoding) { + if(encoding == haxe_io_Encoding.RawNative) { + var buf = new Uint8Array(s.length << 1); + var _g = 0; + var _g1 = s.length; + while(_g < _g1) { + var i = _g++; + var c = s.charCodeAt(i); + buf[i << 1] = c & 255; + buf[i << 1 | 1] = c >> 8; + } + return new haxe_io_Bytes(buf.buffer); + } + var a = []; + var i = 0; + while(i < s.length) { + var c = s.charCodeAt(i++); + if(55296 <= c && c <= 56319) { + c = c - 55232 << 10 | s.charCodeAt(i++) & 1023; + } + if(c <= 127) { + a.push(c); + } else if(c <= 2047) { + a.push(192 | c >> 6); + a.push(128 | c & 63); + } else if(c <= 65535) { + a.push(224 | c >> 12); + a.push(128 | c >> 6 & 63); + a.push(128 | c & 63); + } else { + a.push(240 | c >> 18); + a.push(128 | c >> 12 & 63); + a.push(128 | c >> 6 & 63); + a.push(128 | c & 63); + } + } + return new haxe_io_Bytes(new Uint8Array(a).buffer); +}; +haxe_io_Bytes.ofData = function(b) { + var hb = b.hxBytes; + if(hb != null) { + return hb; + } + return new haxe_io_Bytes(b); +}; +haxe_io_Bytes.prototype = { + getString: function(pos,len,encoding) { + if(pos < 0 || len < 0 || pos + len > this.length) { + throw haxe_Exception.thrown(haxe_io_Error.OutsideBounds); + } + if(encoding == null) { + encoding = haxe_io_Encoding.UTF8; + } + var s = ""; + var b = this.b; + var i = pos; + var max = pos + len; + switch(encoding._hx_index) { + case 0: + while(i < max) { + var c = b[i++]; + if(c < 128) { + if(c == 0) { + break; + } + s += String.fromCodePoint(c); + } else if(c < 224) { + var code = (c & 63) << 6 | b[i++] & 127; + s += String.fromCodePoint(code); + } else if(c < 240) { + var code1 = (c & 31) << 12 | (b[i++] & 127) << 6 | b[i++] & 127; + s += String.fromCodePoint(code1); + } else { + var u = (c & 15) << 18 | (b[i++] & 127) << 12 | (b[i++] & 127) << 6 | b[i++] & 127; + s += String.fromCodePoint(u); + } + } + break; + case 1: + while(i < max) { + var c = b[i++] | b[i++] << 8; + s += String.fromCodePoint(c); + } + break; + } + return s; + } + ,toString: function() { + return this.getString(0,this.length); + } +}; +var haxe_io_Encoding = $hxEnums["haxe.io.Encoding"] = { __ename__:true,__constructs__:null + ,UTF8: {_hx_name:"UTF8",_hx_index:0,__enum__:"haxe.io.Encoding",toString:$estr} + ,RawNative: {_hx_name:"RawNative",_hx_index:1,__enum__:"haxe.io.Encoding",toString:$estr} +}; +haxe_io_Encoding.__constructs__ = [haxe_io_Encoding.UTF8,haxe_io_Encoding.RawNative]; +var haxe_crypto_Base64 = function() { }; +haxe_crypto_Base64.__name__ = true; +haxe_crypto_Base64.encode = function(bytes,complement) { + if(complement == null) { + complement = true; + } + var str = new haxe_crypto_BaseCode(haxe_crypto_Base64.BYTES).encodeBytes(bytes).toString(); + if(complement) { + switch(bytes.length % 3) { + case 1: + str += "=="; + break; + case 2: + str += "="; + break; + default: + } + } + return str; +}; +var haxe_crypto_BaseCode = function(base) { + var len = base.length; + var nbits = 1; + while(len > 1 << nbits) ++nbits; + if(nbits > 8 || len != 1 << nbits) { + throw haxe_Exception.thrown("BaseCode : base length must be a power of two."); + } + this.base = base; + this.nbits = nbits; +}; +haxe_crypto_BaseCode.__name__ = true; +haxe_crypto_BaseCode.prototype = { + encodeBytes: function(b) { + var nbits = this.nbits; + var base = this.base; + var size = b.length * 8 / nbits | 0; + var out = new haxe_io_Bytes(new ArrayBuffer(size + (b.length * 8 % nbits == 0 ? 0 : 1))); + var buf = 0; + var curbits = 0; + var mask = (1 << nbits) - 1; + var pin = 0; + var pout = 0; + while(pout < size) { + while(curbits < nbits) { + curbits += 8; + buf <<= 8; + buf |= b.b[pin++]; + } + curbits -= nbits; + out.b[pout++] = base.b[buf >> curbits & mask]; + } + if(curbits > 0) { + out.b[pout++] = base.b[buf << nbits - curbits & mask]; + } + return out; + } +}; var haxe_crypto_Sha256 = function() { }; haxe_crypto_Sha256.__name__ = true; @@ -3315,107 +3556,6 @@ haxe_http_HttpJs.prototype = $extend(haxe_http_HttpBase.prototype,{ } } }); -var haxe_io_Bytes = function(data) { - this.length = data.byteLength; - this.b = new Uint8Array(data); - this.b.bufferValue = data; - data.hxBytes = this; - data.bytes = this.b; -}; -haxe_io_Bytes.__name__ = true; -haxe_io_Bytes.ofString = function(s,encoding) { - if(encoding == haxe_io_Encoding.RawNative) { - var buf = new Uint8Array(s.length << 1); - var _g = 0; - var _g1 = s.length; - while(_g < _g1) { - var i = _g++; - var c = s.charCodeAt(i); - buf[i << 1] = c & 255; - buf[i << 1 | 1] = c >> 8; - } - return new haxe_io_Bytes(buf.buffer); - } - var a = []; - var i = 0; - while(i < s.length) { - var c = s.charCodeAt(i++); - if(55296 <= c && c <= 56319) { - c = c - 55232 << 10 | s.charCodeAt(i++) & 1023; - } - if(c <= 127) { - a.push(c); - } else if(c <= 2047) { - a.push(192 | c >> 6); - a.push(128 | c & 63); - } else if(c <= 65535) { - a.push(224 | c >> 12); - a.push(128 | c >> 6 & 63); - a.push(128 | c & 63); - } else { - a.push(240 | c >> 18); - a.push(128 | c >> 12 & 63); - a.push(128 | c >> 6 & 63); - a.push(128 | c & 63); - } - } - return new haxe_io_Bytes(new Uint8Array(a).buffer); -}; -haxe_io_Bytes.ofData = function(b) { - var hb = b.hxBytes; - if(hb != null) { - return hb; - } - return new haxe_io_Bytes(b); -}; -haxe_io_Bytes.prototype = { - getString: function(pos,len,encoding) { - if(pos < 0 || len < 0 || pos + len > this.length) { - throw haxe_Exception.thrown(haxe_io_Error.OutsideBounds); - } - if(encoding == null) { - encoding = haxe_io_Encoding.UTF8; - } - var s = ""; - var b = this.b; - var i = pos; - var max = pos + len; - switch(encoding._hx_index) { - case 0: - while(i < max) { - var c = b[i++]; - if(c < 128) { - if(c == 0) { - break; - } - s += String.fromCodePoint(c); - } else if(c < 224) { - var code = (c & 63) << 6 | b[i++] & 127; - s += String.fromCodePoint(code); - } else if(c < 240) { - var code1 = (c & 31) << 12 | (b[i++] & 127) << 6 | b[i++] & 127; - s += String.fromCodePoint(code1); - } else { - var u = (c & 15) << 18 | (b[i++] & 127) << 12 | (b[i++] & 127) << 6 | b[i++] & 127; - s += String.fromCodePoint(u); - } - } - break; - case 1: - while(i < max) { - var c = b[i++] | b[i++] << 8; - s += String.fromCodePoint(c); - } - break; - } - return s; - } -}; -var haxe_io_Encoding = $hxEnums["haxe.io.Encoding"] = { __ename__:true,__constructs__:null - ,UTF8: {_hx_name:"UTF8",_hx_index:0,__enum__:"haxe.io.Encoding",toString:$estr} - ,RawNative: {_hx_name:"RawNative",_hx_index:1,__enum__:"haxe.io.Encoding",toString:$estr} -}; -haxe_io_Encoding.__constructs__ = [haxe_io_Encoding.UTF8,haxe_io_Encoding.RawNative]; var haxe_io_Error = $hxEnums["haxe.io.Error"] = { __ename__:true,__constructs__:null ,Blocked: {_hx_name:"Blocked",_hx_index:0,__enum__:"haxe.io.Error",toString:$estr} ,Overflow: {_hx_name:"Overflow",_hx_index:1,__enum__:"haxe.io.Error",toString:$estr} @@ -3457,6 +3597,13 @@ haxe_io_Path.withoutExtension = function(path) { s.ext = null; return s.toString(); }; +haxe_io_Path.extension = function(path) { + var s = new haxe_io_Path(path); + if(s.ext == null) { + return ""; + } + return s.ext; +}; haxe_io_Path.prototype = { toString: function() { return (this.dir == null ? "" : this.dir + (this.backslash ? "\\" : "/")) + this.file + (this.ext == null ? "" : "." + this.ext); @@ -3636,6 +3783,8 @@ client_JsApi.videoChange = []; client_JsApi.videoRemove = []; client_JsApi.onceListeners = []; client_Settings.isSupported = false; +haxe_crypto_Base64.CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +haxe_crypto_Base64.BYTES = haxe_io_Bytes.ofString(haxe_crypto_Base64.CHARS); js_youtube_Youtube.isLoadedAPI = false; client_Main.main(); })(typeof exports != "undefined" ? exports : typeof window != "undefined" ? window : typeof self != "undefined" ? self : this, typeof window != "undefined" ? window : typeof global != "undefined" ? global : typeof self != "undefined" ? self : this); diff --git a/res/index.html b/res/index.html index 766ec7c..f070816 100644 --- a/res/index.html +++ b/res/index.html @@ -96,11 +96,11 @@ <input id="subsurl" type="text" placeholder="${subtitlesUrlOptional}"> </div> <div> - <button id="queue_next">${queueNext}</button> - <button id="queue_end">${queueLast}</button> <label> <input class="add-temp" type="checkbox" checked="checked">${addAsTemporary} </label> + <button id="queue_next">${queueNext}</button> + <button id="queue_end">${queueLast}</button> </div> </div> <div class="collapse" id="customembed" aria-expanded="false"> @@ -108,11 +108,11 @@ <textarea id="customembed-content" rows="5" placeholder="${pasteEmbedCodeAndClick} '${queueNext}' ${or} '${queueLast}'. ${acceptableEmbedCodesAre} <iframe> ${or} <object>. ${customEmbedsCannotBeSynchronized}."></textarea> <div> - <button id="ce_queue_next">${queueNext}</button> - <button id="ce_queue_end">${queueLast}</button> <label> <input class="add-temp" type="checkbox" checked="checked">${addAsTemporary} </label> + <button id="ce_queue_next">${queueNext}</button> + <button id="ce_queue_end">${queueLast}</button> </div> </div> <!-- Queue --> diff --git a/src/PathTools.hx b/src/PathTools.hx new file mode 100644 index 0000000..69a0044 --- /dev/null +++ b/src/PathTools.hx @@ -0,0 +1,11 @@ +package; + +import haxe.io.Path; + +using StringTools; + +class PathTools { + public static function urlExtension(url:String) { + return Path.extension(~/[#?]/.split(url)[0]).trim().toLowerCase(); + } +} diff --git a/src/client/Buttons.hx b/src/client/Buttons.hx index ee42f44..7df6486 100644 --- a/src/client/Buttons.hx +++ b/src/client/Buttons.hx @@ -158,9 +158,7 @@ class Buttons { final isRawSingleVideo = value != "" && main.isRawPlayerLink(value) && main.isSingleVideoLink(value); ge("#mediatitleblock").style.display = isRawSingleVideo ? "" : "none"; - if (JsApi.hasSubtitleSupport()) { - ge("#subsurlblock").style.display = isRawSingleVideo ? "" : "none"; - } + ge("#subsurlblock").style.display = isRawSingleVideo ? "" : "none"; } mediaUrl.onfocus = mediaUrl.oninput; diff --git a/src/client/ClientSettings.hx b/src/client/ClientSettings.hx index 03f1d23..c787824 100644 --- a/src/client/ClientSettings.hx +++ b/src/client/ClientSettings.hx @@ -11,5 +11,6 @@ typedef ClientSettings = { isSwapped:Bool, isUserListHidden:Bool, latestLinks:Array<String>, + latestSubs:Array<String>, hotkeysEnabled:Bool } diff --git a/src/client/Main.hx b/src/client/Main.hx index e9cda39..c5dca67 100644 --- a/src/client/Main.hx +++ b/src/client/Main.hx @@ -26,7 +26,7 @@ using ClientTools; using StringTools; class Main { - static inline var SETTINGS_VERSION = 2; + static inline var SETTINGS_VERSION = 3; public final settings:ClientSettings; public var isSyncActive = true; @@ -65,6 +65,7 @@ class Main { isSwapped: false, isUserListHidden: true, latestLinks: [], + latestSubs: [], hotkeysEnabled: true } Settings.init(defaults, settingsPatcher); @@ -93,6 +94,9 @@ class Main { case 1: final data:ClientSettings = data; data.hotkeysEnabled = true; + case 2: + final data:ClientSettings = data; + data.latestSubs = []; case SETTINGS_VERSION, _: throw 'skipped version $version'; } @@ -154,9 +158,10 @@ class Main { ge("#mediatitle").onkeydown = (e:KeyboardEvent) -> { if (e.keyCode == KeyCode.Return) addVideoUrl(true); } - ge("#subsurl").onkeydown = (e:KeyboardEvent) -> { - if (e.keyCode == KeyCode.Return) addVideoUrl(true); - } + new InputWithHistory(cast ge("#subsurl"), settings.latestSubs, 10, value -> { + addVideoUrl(true); + return false; + }); ge("#ce_queue_next").onclick = e -> addIframe(false); ge("#ce_queue_end").onclick = e -> addIframe(true); @@ -205,12 +210,17 @@ class Main { function addVideoUrl(atEnd:Bool):Void { final mediaUrl:InputElement = cast ge("#mediaurl"); + final subsUrl:InputElement = cast ge("#subsurl"); final checkbox:InputElement = cast ge("#addfromurl").querySelector(".add-temp"); final isTemp = checkbox.checked; final url = mediaUrl.value; + final subs = subsUrl.value; if (url.length == 0) return; mediaUrl.value = ""; InputWithHistory.pushIfNotLast(settings.latestLinks, url); + if (subs.length != 0) { + InputWithHistory.pushIfNotLast(settings.latestSubs, subs); + } Settings.write(settings); final url = ~/, ?(https?)/g.replace(url, "|$1"); final links = url.split("|"); @@ -351,8 +361,7 @@ class Main { public function getPlaylistLinks():Array<String> { final items = player.getItems(); return [ - for (item in items) - item.url + for (item in items) item.url ]; } @@ -801,7 +810,8 @@ class Main { function onChatVideoLoaded(e:Event):Void { final el:VideoElement = cast e.target; if (emoteMaxSize == null) { - emoteMaxSize = Std.parseInt(window.getComputedStyle(el).getPropertyValue("max-width")); + emoteMaxSize = Std.parseInt(window.getComputedStyle(el) + .getPropertyValue("max-width")); } // fixes default video tag size in chat when tab unloads videos in background // (some browsers optimization i guess) diff --git a/src/client/players/Raw.hx b/src/client/players/Raw.hx index ea51e97..5889732 100644 --- a/src/client/players/Raw.hx +++ b/src/client/players/Raw.hx @@ -54,11 +54,8 @@ class Raw implements IPlayer { } titleInput.value = ""; - var subs = ""; - if (JsApi.hasSubtitleSupport()) { - subs = subsInput.value.trim(); - subsInput.value = ""; - } + final subs = subsInput.value.trim(); + subsInput.value = ""; final video = document.createVideoElement(); video.src = url; video.onerror = e -> { @@ -101,24 +98,27 @@ class Raw implements IPlayer { } if (video != null) { video.src = url; - if (isHls) initHlsSource(video, url); - restartControlsHider(); - return; - } - video = document.createVideoElement(); - video.id = "videoplayer"; - video.src = url; - restartControlsHider(); - video.oncanplaythrough = player.onCanBePlayed; - video.onseeking = player.onSetTime; - video.onplay = e -> { - playAllowed = true; - player.onPlay(); + for (element in video.children) { + if (element.nodeName != "TRACK") continue; + element.remove(); + } + } else { + video = document.createVideoElement(); + video.id = "videoplayer"; + video.src = url; + video.oncanplaythrough = player.onCanBePlayed; + video.onseeking = player.onSetTime; + video.onplay = e -> { + playAllowed = true; + player.onPlay(); + } + video.onpause = player.onPause; + video.onratechange = player.onRateChange; + playerEl.appendChild(video); } - video.onpause = player.onPause; - video.onratechange = player.onRateChange; - playerEl.appendChild(video); if (isHls) initHlsSource(video, url); + restartControlsHider(); + RawSubs.loadSubs(item, video); } function restartControlsHider():Void { diff --git a/src/client/players/RawSubs.hx b/src/client/players/RawSubs.hx new file mode 100644 index 0000000..4fcf3b6 --- /dev/null +++ b/src/client/players/RawSubs.hx @@ -0,0 +1,139 @@ +package client.players; + +import Types.VideoItem; +import haxe.crypto.Base64; +import haxe.io.Bytes; +import js.Browser.document; +import js.Browser.window; +import js.html.VideoElement; + +using StringTools; + +private typedef Duration = { + h:Int, + m:Int, + s:Int, + ms:Int +} + +class RawSubs { + public static function loadSubs(item:VideoItem, video:VideoElement):Void { + final ext = PathTools.urlExtension(item.subs); + // do not load subs if there is custom plugin + if (JsApi.hasSubtitleSupport(ext)) return; + final url = '/proxy?url=${encodeURI(item.subs)}'; + + switch ext { + case "ass": + case "srt": + parseSrt(video, url); + case "vtt": + onParsed(video, "VTT subtitles", url); + // parseVtt(video, url); + } + } + + static function parseSrt(video:VideoElement, url:String):Void { + window.fetch(url).then(response -> { + return response.text(); + }).then(text -> { + final subs:Array<{ + counter:String, + time:String, + text:String + }> = []; + final blocks = text.replace("\r\n", "\n").split("\n\n"); + for (block in blocks) { + final lines = block.split("\n"); + if (lines.length < 3) continue; + final textLines = [ + for (i in 2...lines.length) lines[i] + ]; + subs.push({ + counter: lines[0], + time: lines[1].replace(",", "."), + text: textLines.join("\n") + }); + } + var data = "WEBVTT\n\n"; + for (sub in subs) { + data += '${sub.counter}\n'; + data += '${sub.time}\n'; + data += '${sub.text}\n\n'; + } + trace(data); + final textBase64 = "data:text/plain;base64,"; + final url = textBase64 + Base64.encode(Bytes.ofString(data)); + onParsed(video, "SRT subtitles", url); + }); + } + + // function saveSubs() { + // final options = [ + // for (i in 0...subsSelect.options.length) + // subsSelect.item(i) + // ]; + // var inputId = (cast el("#id-offset") : InputElement).value; + // var id = Std.parseInt(inputId); + // if (id == null) id = 1; + // final data:Array<String> = options.map(element -> { + // final value = element.textContent; + // final firstTime = Std.parseFloat(value.split("|")[0]); + // final secondTime = Std.parseFloat(value.split("|")[1]); + // // 00:00:00,498 --> 00:00:02,827 + // var time = '$id\n'; + // time += srvTimeFormat(stringDuration(firstTime)); + // time += " --> "; + // time += srvTimeFormat(stringDuration(secondTime)); + // time += '\ntext$id\n'; + // id++; + // return time; + // }); + // final data = data.join("\n"); + // Utils.saveFile("subs.srv", TextPlain, data); + // } + + function stringDuration(seconds:Float):Duration { + final h = Std.int(seconds / 60 / 60); + final m = Std.int(seconds / 60) - h * 60; + final s = Std.int(seconds % 60); + final ms = Std.int((seconds - Std.int(seconds)) * 1000); + return { + h: h, + m: m, + s: s, + ms: ms + } + } + + function srvTimeFormat(time:Duration):String { + final h = '${time.h}'.lpad("0", 2); + final m = '${time.m}'.lpad("0", 2); + final s = '${time.s}'.lpad("0", 2); + final ms = '${time.ms}'.rpad("0", 3); + return '$h:$m:$s,$ms'; + } + + static function parseVtt(video:VideoElement, url:String):Void { + window.fetch(url).then(response -> response.text()).then(data -> { + final textBase64 = "data:text/plain;base64,"; + final url = textBase64 + Base64.encode(Bytes.ofString(data)); + onParsed(video, "VTT subtitles", url); + }); + } + + static function onParsed(video:VideoElement, name:String, dataUrl:String) { + final trackEl = document.createTrackElement(); + trackEl.label = name; + trackEl.kind = "subtitles"; + trackEl.src = dataUrl; + trackEl.default_ = true; + final track = trackEl.track; + track.mode = SHOWING; + video.appendChild(trackEl); + } + + static function encodeURI(data:String):String { + return js.Syntax.code("encodeURI({0})", data); + } +} |
