diff options
Diffstat (limited to 'src')
| -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 |
6 files changed, 190 insertions, 31 deletions
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); + } +} |
