diff options
| author | metamuffin <metamuffin@disroot.org> | 2026-01-18 23:43:12 +0100 |
|---|---|---|
| committer | metamuffin <metamuffin@disroot.org> | 2026-01-18 23:43:12 +0100 |
| commit | ed19a428cb5eef84c8cf3fed5fda3afd5fc96305 (patch) | |
| tree | 39e3167a4f8b7423a15b3a5f56e973554bdb3195 /ui/client-scripts/src/player/track/vtt.ts | |
| parent | 901dff07ed357694eb35284a58c3cc6c003c53ce (diff) | |
| download | jellything-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.ts | 96 |
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 +} |