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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
|
import { OVar } from "../jshelper/mod.ts";
import { JhlsTrack, TimeRange } from "./jhls.d.ts";
import { BufferRange, Player } from "./player.ts";
import { EncodingProfileExt } from "./profiles.ts";
const TARGET_BUFFER_DURATION = 10
const MIN_BUFFER_DURATION = 1
export interface AppendRange extends TimeRange { buf: ArrayBuffer, index: number, cb: () => void }
export class PlayerTrack {
private source_buffer: SourceBuffer
private current_load?: AppendRange
private loading = new Set<number>()
public buffered = new OVar<BufferRange[]>([])
private append_queue: AppendRange[] = []
public profile = new OVar<EncodingProfileExt | undefined>(undefined)
constructor(
private player: Player,
private node_id: string,
private track_index: number,
private metadata: JhlsTrack
) {
this.source_buffer = this.player.media_source.addSourceBuffer("video/webm; codecs=\"opus,vorbis,vp8,vp9,av1\"")
this.source_buffer.mode = "segments"
this.source_buffer.addEventListener("updateend", () => {
if (this.current_load) {
this.current_load.cb()
this.loading.delete(this.current_load.index)
this.current_load = undefined
}
this.update_buf_ranges()
this.tick_append()
})
this.source_buffer.addEventListener("error", e => {
console.error("sourcebuffer error", e);
})
this.source_buffer.addEventListener("abort", e => {
console.error("sourcebuffer abort", e);
})
}
update_buf_ranges() {
const ranges: BufferRange[] = []
for (let i = 0; i < this.source_buffer.buffered.length; i++) {
ranges.push({
start: this.source_buffer.buffered.start(i),
end: this.source_buffer.buffered.end(i),
status: "buffered"
})
}
for (const r of this.loading) {
ranges.push({ ...this.metadata.segments[r], status: "loading" })
}
this.buffered.value = ranges
}
async update(target: number) {
this.update_buf_ranges() // TODO required?
const blocking = []
for (let i = 0; i < this.metadata.segments.length; i++) {
const seg = this.metadata.segments[i];
if (seg.end < target) continue
if (seg.start >= target + TARGET_BUFFER_DURATION) break
if (!this.check_buf_collision(seg.start, seg.end)) continue
if (seg.start <= target + MIN_BUFFER_DURATION)
blocking.push(this.load(i))
else
this.load(i)
}
await Promise.all(blocking)
}
check_buf_collision(start: number, end: number) {
const EPSILON = 0.01
for (const r of this.buffered.value)
if (r.end - EPSILON > start && r.start < end - EPSILON)
return false
return true
}
async load(index: number) {
this.loading.add(index)
await this.player.profile_selector.select_optimal_profile(this.track_index, this.profile)
const url = `/n/${encodeURIComponent(this.node_id)}/stream?format=hlsseg&webm=true&tracks=${this.track_index}&index=${index}${this.profile.value ? `&profile=${this.profile.value.id}` : ""}`;
const buf = await this.player.downloader.download(url)
await new Promise<void>(cb => {
this.append_queue.push({ buf, ...this.metadata.segments[index], index, cb })
this.tick_append()
})
}
tick_append() {
if (this.source_buffer.updating) return
if (this.append_queue.length) {
const seg = this.append_queue[0];
this.append_queue.splice(0, 1)
this.current_load = seg
// TODO why is appending so unreliable?! sometimes it does not add it
this.source_buffer.changeType("video/webm; codecs=\"opus,vorbis,vp8,vp9,av1\"")
this.source_buffer.timestampOffset = seg.start
this.source_buffer.appendBuffer(seg.buf);
}
}
}
|