import { SourceTrack, JvttCue } from "../jhls.d.ts"; import { Player } from "../player.ts"; import { PlayerTrack } from "./mod.ts"; export class VttPlayerTrack extends PlayerTrack { private track: TextTrack; public cues?: JvttCue[] constructor( private player: Player, private node_id: string, track_index: number, private metadata: SourceTrack, ) { super(track_index); this.track = this.player.video.addTextTrack("subtitles", this.metadata.name, this.metadata.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(`/n/${encodeURIComponent(this.node_id)}/stream?format=jvtt&track=${this.track_index}`, { headers: { "Accept": "application/json" } }); if (!res.ok) return this.player.error.value = "Cannot download index.", undefined; let ai!: JvttCue[] & { 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() } } function create_cue(cue: JvttCue): 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 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 { const o: Record = {} 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 }