1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
|
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<VttPlayerTrack | undefined> {
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.buffered.value = [{ start: 0, end: player.duration.value, status: "buffered" }]
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<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
}
|