aboutsummaryrefslogtreecommitdiff
path: root/web/script/player/mod.ts
diff options
context:
space:
mode:
Diffstat (limited to 'web/script/player/mod.ts')
-rw-r--r--web/script/player/mod.ts186
1 files changed, 2 insertions, 184 deletions
diff --git a/web/script/player/mod.ts b/web/script/player/mod.ts
index caad781..02b8a12 100644
--- a/web/script/player/mod.ts
+++ b/web/script/player/mod.ts
@@ -3,11 +3,8 @@
which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
Copyright (C) 2023 metamuffin <metamuffin.org>
*/
-import { OVar, e } from "../jshelper/mod.ts";
-import { JhlsMetadata, JhlsTrack, TimeRange } from "./jhls.d.ts";
-
-const TARGET_BUFFER_DURATION = 15
-const MIN_BUFFER_DURATION = 1
+import { e } from "../jshelper/mod.ts";
+import { Player } from "./player.ts";
document.addEventListener("DOMContentLoaded", () => {
if (document.body.classList.contains("player")) {
@@ -120,182 +117,3 @@ function display_time(t: number): string {
return (h ? h + "h" : "") + (m ? m + "m" : "") + (s ? s + "s" : "")
}
-interface BufferRange extends TimeRange { status: "buffered" | "loading" | "queued" }
-class Player {
- public video = e("video")
- private media_source = new MediaSource();
- public tracks = new OVar<PlayerTrack[]>([]);
-
- public position = new OVar(0)
- public duration = new OVar(1)
- public playing = new OVar(false)
- public canplay = new OVar(false)
- public buffering_status = new OVar<string | undefined>(undefined)
- public error = new OVar<string | undefined>(undefined)
-
- constructor(private node_id: string) {
- this.video.onloadedmetadata = () => { }
- this.video.ondurationchange = () => { }
- this.video.ontimeupdate = () => {
- this.position.value = this.video.currentTime
- this.update() // TODO maybe not here
- }
- this.video.onplay = () => {
- console.log("play");
- this.buffering_status.value = "Resuming playback...";
- }
- this.video.onwaiting = () => {
- console.log("waiting");
- this.buffering_status.value = "Buffering...";
- this.canplay.value = false;
- }
- this.video.onplaying = () => {
- console.log("playing");
- this.playing.value = true;
- this.buffering_status.value = undefined;
- }
- this.video.onpause = () => {
- console.log("pause");
- this.playing.value = false
- }
- this.video.oncanplay = () => {
- console.log("canplay");
- this.buffering_status.value = undefined
- this.canplay.value = true
- }
- this.video.onseeking = () => {
- console.log("seeking");
- this.buffering_status.value = "Seeking..."
- }
- this.video.onseeked = () => {
- console.log("seeked");
- this.buffering_status.value = undefined
- }
- this.fetch_meta()
- }
-
- async fetch_meta() {
- this.buffering_status.value = "Loading JHLS metadata..."
- const res = await fetch(`/n/${encodeURIComponent(this.node_id)}/stream?format=jhls`)
- if (!res.ok) return this.error.value = "Cannot download JHLS metadata"
- const metadata = await res.json() as JhlsMetadata
- this.buffering_status.value = undefined
-
- this.duration.value = metadata.duration
- this.video.src = URL.createObjectURL(this.media_source)
- this.media_source.addEventListener("sourceopen", async () => {
- this.tracks.value.push(new PlayerTrack(this.media_source, this.node_id, 0, metadata.tracks[0]))
- this.tracks.value.push(new PlayerTrack(this.media_source, this.node_id, 1, metadata.tracks[1]))
- this.tracks.change()
- this.buffering_status.value = "Fetching initial segments..."
- this.update()
- await this.canplay.wait_for(true)
- this.buffering_status.value = undefined
- })
- }
- async update(newt?: number) {
- await Promise.all(this.tracks.value.map(t => t.update(newt ?? this.video.currentTime)))
- }
-
- play() {
- this.video.play()
- }
- pause() {
- this.video.pause()
- }
- frame_forward() {
- //@ts-ignore trust me bro
- this.video["seekToNextFrame"]()
- }
- async seek(p: number) {
- this.buffering_status.value = "Buffering at target..."
- await this.update(p)
- this.video.currentTime = p
- }
-}
-
-interface AppendRange extends TimeRange { buf: ArrayBuffer, index: number, cb: () => void }
-class PlayerTrack {
- private source_buffer: SourceBuffer
- private current_load?: AppendRange
- private loading = new Set<number>()
- public buffered = new OVar<BufferRange[]>([])
- private append_queue: AppendRange[] = []
- constructor(media_source: MediaSource, private node_id: string, private track_index: number, private metadata: JhlsTrack) {
- this.source_buffer = media_source.addSourceBuffer("video/webm")
- 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);
- })
- }
-
- 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)
- const res = await fetch(`/n/${encodeURIComponent(this.node_id)}/stream?format=hlsseg&tracks=${this.track_index}&index=${index}`)
- if (!res.ok) throw new Error(`segment fail i=${index} t=${this.track_index}`);
- const buf = await res.arrayBuffer()
-
- 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.source_buffer.timestampOffset = seg.start
- this.source_buffer.appendBuffer(seg.buf);
- this.append_queue.splice(0, 1)
- this.current_load = seg
- }
- }
-} \ No newline at end of file