aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRblSb <msrblsb@gmail.com>2021-07-05 17:09:56 +0300
committerRblSb <msrblsb@gmail.com>2021-07-05 17:09:56 +0300
commitcf78d464be214eedcd7909001ece2aa0a216d136 (patch)
tree5801ef0ca01589ed923c961a93b30278df9ea9d3
parentede45cea8706eb8540e466df9861c2af8ebf9c44 (diff)
vtt/srt subs support
-rw-r--r--res/client.js417
-rw-r--r--res/index.html8
-rw-r--r--src/PathTools.hx11
-rw-r--r--src/client/Buttons.hx4
-rw-r--r--src/client/ClientSettings.hx1
-rw-r--r--src/client/Main.hx24
-rw-r--r--src/client/players/Raw.hx42
-rw-r--r--src/client/players/RawSubs.hx139
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}'. &#10;${acceptableEmbedCodesAre} &lt;iframe&gt; ${or} &lt;object&gt;. &#10;${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);
+ }
+}
send patches to the email below
yukais@pinapelz.com
include the subject [PATCH repo_name]
pinapelz.com
homepage