aboutsummaryrefslogtreecommitdiff
path: root/ui/client-scripts/src/player/track/vtt.ts
diff options
context:
space:
mode:
authormetamuffin <metamuffin@disroot.org>2026-01-18 23:43:12 +0100
committermetamuffin <metamuffin@disroot.org>2026-01-18 23:43:12 +0100
commited19a428cb5eef84c8cf3fed5fda3afd5fc96305 (patch)
tree39e3167a4f8b7423a15b3a5f56e973554bdb3195 /ui/client-scripts/src/player/track/vtt.ts
parent901dff07ed357694eb35284a58c3cc6c003c53ce (diff)
downloadjellything-ed19a428cb5eef84c8cf3fed5fda3afd5fc96305.tar
jellything-ed19a428cb5eef84c8cf3fed5fda3afd5fc96305.tar.bz2
jellything-ed19a428cb5eef84c8cf3fed5fda3afd5fc96305.tar.zst
Move client scripts to build-crate
Diffstat (limited to 'ui/client-scripts/src/player/track/vtt.ts')
-rw-r--r--ui/client-scripts/src/player/track/vtt.ts96
1 files changed, 96 insertions, 0 deletions
diff --git a/ui/client-scripts/src/player/track/vtt.ts b/ui/client-scripts/src/player/track/vtt.ts
new file mode 100644
index 0000000..2152b97
--- /dev/null
+++ b/ui/client-scripts/src/player/track/vtt.ts
@@ -0,0 +1,96 @@
+/*
+ This file is part of jellything (https://codeberg.org/metamuffin/jellything)
+ which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
+ Copyright (C) 2026 metamuffin <metamuffin.org>
+*/
+import { e } from "../../jshelper/src/element.ts";
+import { Player } from "../player.ts";
+import { SubtitleCue, TrackInfo } from "../types_stream.ts";
+import { PlayerTrack } from "./mod.ts";
+
+export class VttPlayerTrack extends PlayerTrack {
+ private track: TextTrack;
+ public cues?: SubtitleCue[]
+
+ constructor(
+ private player: Player,
+ private node_id: string,
+ track_index: number,
+ private track_info: TrackInfo,
+ ) {
+ super(track_index);
+ this.track = this.player.video.addTextTrack("subtitles", this.track_info.name, this.track_info.language);
+ this.buffered.value = [{ start: 0, end: this.player.duration.value, status: "loading" }]
+ this.init()
+ }
+
+ private on_ready() {
+ if (!this.cues) return
+ this.buffered.value = [{ start: 0, end: this.player.duration.value, status: "buffered" }]
+ for (const cue of this.cues) {
+ this.track.addCue(create_cue(cue));
+ }
+ this.track.mode = "showing";
+ this.abort.signal.addEventListener("abort", () => {
+ // TODO disable subtitles properly
+ this.track.mode = "hidden";
+ });
+ }
+
+ async init() {
+ try {
+ const res = await fetch(`${this.player.base_url}?format=remux&segment=0&container=jvtt&track=${this.track_index}`, { headers: { "Accept": "application/json" } });
+ if (!res.ok) return this.player.error.value = "Cannot download index.", undefined;
+ let ai!: SubtitleCue[] & { error: string; };
+ try { ai = await res.json(); }
+ catch (_) { this.player.set_pers("Error: Failed to fetch node"); }
+ if (ai.error) return this.player.set_pers("server error: " + ai.error), undefined;
+ this.cues = ai;
+ } catch (e) {
+ if (e instanceof TypeError) {
+ this.player.set_pers("Cannot download subtitles: Network Error");
+ return undefined
+ } else throw e;
+ }
+ this.on_ready()
+ }
+
+ public debug(): HTMLElement {
+ return e("pre", `vtt track ${this.track_index}\n\t${this.cues?.length} cues loaded`)
+ }
+}
+
+function create_cue(cue: SubtitleCue): VTTCue {
+ const c = new VTTCue(cue.start, cue.end, cue.content);
+ const props = parse_layout_properties(cue.content.split("\n")[0])
+ if (props) {
+ c.text = cue.content.split("\n").slice(1).join("\n")
+ // TODO re-enable when it works
+ // // TODO this does not work at all...
+ // const region = new VTTRegion()
+ // if ("position" in props && props.position.endsWith("%"))
+ // region.regionAnchorX = parseFloat(props.position.replace("%", ""))
+ // if ("line" in props && props.line.endsWith("%"))
+ // region.regionAnchorY = parseFloat(props.line.replace("%", ""))
+ // if ("align" in props)
+ // c.align = props.align as AlignSetting
+ // c.region = region
+ } else {
+ c.line = -2;
+ }
+ return c
+}
+
+function parse_layout_properties(s: string): undefined | Record<string, string> {
+ const o: Record<string, string> = {}
+ for (const tok of s.split(" ")) {
+ const [k, v, ...rest] = tok.split(":")
+ if (!v || rest.length) return undefined
+ o[k] = v
+ }
+ // some common keys to prevent false positives
+ if ("position" in o) return o
+ if ("align" in o) return o
+ if ("line" in o) return o
+ return undefined
+}