import { OVar, e } from "../jshelper/mod.ts"; import { JhlsMetadata, TimeRange } from "./jhls.d.ts"; import { SegmentDownloader } from "./download.ts"; import { PlayerTrack } from "./track.ts"; import { ProfileSelector } from "./profiles.ts"; export interface BufferRange extends TimeRange { status: "buffered" | "loading" | "queued" } export class Player { public video = e("video") public media_source = new MediaSource(); public tracks = new OVar([]); public downloader: SegmentDownloader = new SegmentDownloader(); public profile_selector!: ProfileSelector 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(undefined) public error = new OVar(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.profile_selector = new ProfileSelector(this.downloader.bandwidth, metadata) this.media_source.addEventListener("sourceopen", async () => { this.tracks.value.push(new PlayerTrack(this, this.node_id, 0, metadata.tracks[0])) this.tracks.value.push(new PlayerTrack(this, 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 } }