aboutsummaryrefslogtreecommitdiff
path: root/web/script/player/track.ts
blob: 3f1e073b29fc65f4177a1d84005ced266c418a15 (plain)
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);

        }
    }
}