import { SourceTrack, JvttCue } from "../jhls.d.ts"; import { Player } from "../player.ts"; import { PlayerTrack } from "./mod.ts"; export async function create_vtt_track(player: Player, node_id: string, track_index: number, metadata: SourceTrack): Promise { let index: JvttCue[]; try { const res = await fetch(`/n/${encodeURIComponent(player.node_id)}/stream?format=jvtt&tracks=${track_index}`, { headers: { "Accept": "application/json" } }); if (!res.ok) return player.error.value = "Cannot download index.", undefined; let ai!: JvttCue[] & { error: string; }; try { ai = await res.json(); } catch (_) { player.set_pers("Error: Failed to fetch node"); } if (ai.error) return player.set_pers("server error: " + ai.error), undefined; index = ai; } catch (e) { if (e instanceof TypeError) { player.set_pers("Cannot download subtitles: Network Error"); return undefined } else throw e; } const t = new VttPlayerTrack(player, node_id, track_index, metadata, index); return t; } export class VttPlayerTrack extends PlayerTrack { private track: TextTrack; constructor( private player: Player, private node_id: string, track_index: number, private metadata: SourceTrack, public cues: JvttCue[] ) { super(track_index); this.track = this.player.video.addTextTrack("subtitles", metadata.name, metadata.language); for (const cue of cues) { this.track.addCue(create_cue(cue)); } this.track.mode = "showing"; this.abort.signal.addEventListener("abort", () => { // TODO disable subtitles properly this.track.mode = "hidden"; }); } } 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 }