From cf78d464be214eedcd7909001ece2aa0a216d136 Mon Sep 17 00:00:00 2001 From: RblSb Date: Mon, 5 Jul 2021 17:09:56 +0300 Subject: vtt/srt subs support --- src/client/players/Raw.hx | 42 ++++++------- src/client/players/RawSubs.hx | 139 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 160 insertions(+), 21 deletions(-) create mode 100644 src/client/players/RawSubs.hx (limited to 'src/client/players') 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 = 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); + } +} -- cgit v1.2.3