diff options
Diffstat (limited to 'web/script/player/track')
-rw-r--r-- | web/script/player/track/create.ts | 10 | ||||
-rw-r--r-- | web/script/player/track/mod.ts | 4 | ||||
-rw-r--r-- | web/script/player/track/mse.ts | 83 | ||||
-rw-r--r-- | web/script/player/track/vtt.ts | 6 |
4 files changed, 56 insertions, 47 deletions
diff --git a/web/script/player/track/create.ts b/web/script/player/track/create.ts index 1aaf12c..95bccca 100644 --- a/web/script/player/track/create.ts +++ b/web/script/player/track/create.ts @@ -3,15 +3,13 @@ which is licensed under the GNU Affero General Public License (version 3); see /COPYING. Copyright (C) 2025 metamuffin <metamuffin.org> */ -import { get_track_kind } from "../mediacaps.ts"; import { VttPlayerTrack } from "./vtt.ts"; import { MSEPlayerTrack } from "./mse.ts"; import { Player } from "../player.ts"; -import { SourceTrack } from "../jhls.d.ts"; import { PlayerTrack } from "./mod.ts"; +import { TrackInfo } from "../types_stream.ts"; -export function create_track(player: Player, node_id: string, track_index: number, metadata: SourceTrack): PlayerTrack | undefined { - const kind = get_track_kind(metadata.kind) - if (kind == "subtitles") return new VttPlayerTrack(player, node_id, track_index, metadata) - else return new MSEPlayerTrack(player, node_id, track_index, metadata) +export function create_track(player: Player, base_url: string, segment_index: number, track_index: number, track_info: TrackInfo): PlayerTrack | undefined { + if (track_info.kind == "subtitles") return new VttPlayerTrack(player, base_url, track_index, track_info) + else return new MSEPlayerTrack(player, base_url, segment_index, track_index, track_info) } diff --git a/web/script/player/track/mod.ts b/web/script/player/track/mod.ts index 0c7c1c0..99b348c 100644 --- a/web/script/player/track/mod.ts +++ b/web/script/player/track/mod.ts @@ -4,11 +4,11 @@ Copyright (C) 2025 metamuffin <metamuffin.org> */ /// <reference lib="dom" /> -import { TimeRange } from "../jhls.d.ts"; +import { TimeRange } from "../types_stream.ts"; import { OVar } from "../../jshelper/mod.ts"; import { BufferRange } from "../player.ts"; -export const TARGET_BUFFER_DURATION = 10 +export const TARGET_BUFFER_DURATION = 15 export const MIN_BUFFER_DURATION = 1 export interface AppendRange extends TimeRange { buf: ArrayBuffer, index: number, cb: () => void } diff --git a/web/script/player/track/mse.ts b/web/script/player/track/mse.ts index d1a8c12..237b6f6 100644 --- a/web/script/player/track/mse.ts +++ b/web/script/player/track/mse.ts @@ -3,41 +3,42 @@ which is licensed under the GNU Affero General Public License (version 3); see /COPYING. Copyright (C) 2025 metamuffin <metamuffin.org> */ -import { JhlsTrackIndex, SourceTrack } from "../jhls.d.ts"; import { OVar } from "../../jshelper/mod.ts"; -import { profile_to_partial_track, track_to_content_type } from "../mediacaps.ts"; +import { test_media_capability, track_to_content_type } from "../mediacaps.ts"; import { BufferRange, Player } from "../player.ts"; -import { EncodingProfileExt, ProfileSelector } from "../profiles.ts"; import { PlayerTrack, AppendRange, TARGET_BUFFER_DURATION, MIN_BUFFER_DURATION } from "./mod.ts"; -import { show_profile } from "../mod.ts"; import { e } from "../../jshelper/src/element.ts"; +import { FormatInfo, FragmentIndex, StreamContainer, TrackInfo } from "../types_stream.ts"; +import { show_format } from "../mod.ts"; + +interface UsableFormat { format_index: number, usable_index: number, format: FormatInfo, container: StreamContainer } export class MSEPlayerTrack extends PlayerTrack { public source_buffer!: SourceBuffer; private current_load?: AppendRange; private loading = new Set<number>(); private append_queue: AppendRange[] = []; - public profile_selector: ProfileSelector; - public profile = new OVar<EncodingProfileExt | undefined>(undefined); - public index?: JhlsTrackIndex + public index?: FragmentIndex + public active_format = new OVar<UsableFormat | undefined>(undefined); + public usable_formats: UsableFormat[] = [] constructor( private player: Player, - private node_id: string, + private base_url: string, + private segment_index: number, track_index: number, - public metadata: SourceTrack, + public trackinfo: TrackInfo, ) { super(track_index); - this.profile_selector = new ProfileSelector(player, this, player.downloader.bandwidth_avail); this.init() } async init() { this.buffered.value = [{ start: 0, end: this.player.duration.value, status: "loading" }] try { - const res = await fetch(`/n/${encodeURIComponent(this.node_id)}/stream?format=jhlsi&track=${this.track_index}`, { headers: { "Accept": "application/json" } }); + const res = await fetch(`${this.base_url}?fragmentindex&segment=${this.segment_index}&track=${this.track_index}`, { headers: { "Accept": "application/json" } }); if (!res.ok) return this.player.error.value = "Cannot download index.", undefined; - let index!: JhlsTrackIndex & { error: string; }; + let index!: FragmentIndex & { error: string; }; try { index = await res.json(); } catch (_) { this.player.set_pers("Error: Failed to fetch node"); } if (index.error) return this.player.set_pers("server error: " + index.error), undefined; @@ -49,10 +50,21 @@ export class MSEPlayerTrack extends PlayerTrack { } this.buffered.value = [] - const canplay = await this.profile_selector.select_optimal_profile(this.track_index, this.profile); - if (!canplay) return this.player.set_track_enabled(this.track_index, false) - const ct = track_to_content_type(this.track_from_profile())!; - console.log(`track ${this.track_index} source buffer content-type: ${ct}`); + console.log(this.trackinfo); + + for (let i = 0; i < this.trackinfo.formats.length; i++) { + const format = this.trackinfo.formats[i]; + for (const container of format.containers) { + if (container != "webm" && container != "mpeg4") continue; + if (await test_media_capability(format, container)) + this.usable_formats.push({ container, format, format_index: i, usable_index: this.usable_formats.length }) + } + } + if (!this.usable_formats.length) + return this.player.logger?.log("No availble format is supported by this device. The track can't be played back.") + this.active_format.value = this.usable_formats[0] + + const ct = track_to_content_type(this.active_format.value!.format, this.active_format.value!.container); this.source_buffer = this.player.media_source.addSourceBuffer(ct); this.abort.signal.addEventListener("abort", () => { console.log(`destroy source buffer for track ${this.track_index}`); @@ -81,10 +93,6 @@ export class MSEPlayerTrack extends PlayerTrack { this.update(this.player.video.currentTime) } - track_from_profile(): SourceTrack { - if (this.profile.value) return profile_to_partial_track(this.profile.value); - else return this.metadata; - } update_buf_ranges() { if (!this.index) return; @@ -97,7 +105,7 @@ export class MSEPlayerTrack extends PlayerTrack { }); } for (const r of this.loading) { - ranges.push({ ...this.index.fragments[r], status: "loading" }); + ranges.push({ ...this.index[r], status: "loading" }); } this.buffered.value = ranges; } @@ -107,8 +115,8 @@ export class MSEPlayerTrack extends PlayerTrack { this.update_buf_ranges(); // TODO required? const blocking = []; - for (let i = 0; i < this.index.fragments.length; i++) { - const frag = this.index.fragments[i]; + for (let i = 0; i < this.index.length; i++) { + const frag = this.index[i]; if (frag.end < target) continue; if (frag.start >= target + TARGET_BUFFER_DURATION) break; if (!this.check_buf_collision(frag.start, frag.end)) continue; @@ -129,13 +137,13 @@ export class MSEPlayerTrack extends PlayerTrack { async load(index: number) { this.loading.add(index); - await this.profile_selector.select_optimal_profile(this.track_index, this.profile); - const url = `/n/${encodeURIComponent(this.node_id)}/stream?format=frag&webm=true&track=${this.track_index}&index=${index}${this.profile.value ? `&profile=${this.profile.value.id}` : ""}`; + // TODO update format selection + const url = `${this.base_url}?fragment&segment=${this.segment_index}&track=${this.track_index}&format=${this.active_format.value!.format_index}&index=${index}&container=${this.active_format.value!.container}`; const buf = await this.player.downloader.download(url); await new Promise<void>(cb => { if (!this.index) return; if (this.abort.signal.aborted) return; - this.append_queue.push({ buf, ...this.index.fragments[index], index, cb }); + this.append_queue.push({ buf, ...this.index[index], index, cb }); this.tick_append(); }); } @@ -146,9 +154,10 @@ export class MSEPlayerTrack extends PlayerTrack { this.append_queue.splice(0, 1); this.current_load = frag; // TODO why is appending so unreliable?! sometimes it does not add it - this.source_buffer.changeType(track_to_content_type(this.track_from_profile())!); - this.source_buffer.timestampOffset = this.profile.value !== undefined ? frag.start : 0 - console.log(`append track ${this.track_index}`); + this.source_buffer.changeType(track_to_content_type(this.active_format.value!.format, this.active_format.value!.container)); + this.source_buffer.timestampOffset = this.active_format.value?.container == "mpeg4" ? frag.start : 0 + // this.source_buffer.timestampOffset = this.active_format.value?.format.remux ? 0 : frag.start + // this.source_buffer.timestampOffset = 0 this.source_buffer.appendBuffer(frag.buf); } } @@ -156,15 +165,17 @@ export class MSEPlayerTrack extends PlayerTrack { public debug(): OVar<HTMLElement> { const rtype = (t: string, b: BufferRange[]) => { const c = b.filter(r => r.status == t); - return `${c.length} range${c.length != 1 ? "s" : ""}, ${c.reduce((a, v) => a + v.end - v.start, 0).toFixed(2)}s` + // ${c.length} range${c.length != 1 ? "s" : ""} + return `${c.reduce((a, v) => a + v.end - v.start, 0).toFixed(2)}s` } - return this.profile.liftA2(this.buffered, (p, b) => + return this.active_format.liftA2(this.buffered, (p, b) => e("pre", - `mse track ${this.track_index}: ${(p ? `profile ${p.id} (${show_profile(p)})` : `remux`)}` - + `\n\ttype: ${track_to_content_type(this.track_from_profile())}` - + `\n\tbuffered: ${rtype("buffered", b)}` - + `\n\tqueued: ${rtype("queued", b)}` - + `\n\tloading: ${rtype("loading", b)}` + p ? + `mse track ${this.track_index}: format ${p.format_index} (${p.format.remux ? "remux" : "transcode"})` + + `\n\tformat: ${show_format(p.format)}` + + `\n\tbuffer type: ${track_to_content_type(p.format, p.container)}` + + `\n\tbuffered: ${rtype("buffered", b)} / queued: ${rtype("queued", b)} / loading: ${rtype("loading", b)}` + : "" ) as HTMLElement ) } diff --git a/web/script/player/track/vtt.ts b/web/script/player/track/vtt.ts index ea4951c..3dd7670 100644 --- a/web/script/player/track/vtt.ts +++ b/web/script/player/track/vtt.ts @@ -4,8 +4,8 @@ Copyright (C) 2025 metamuffin <metamuffin.org> */ import { e } from "../../jshelper/src/element.ts"; -import { SourceTrack, JvttCue } from "../jhls.d.ts"; import { Player } from "../player.ts"; +import { JvttCue, TrackInfo } from "../types_stream.ts"; import { PlayerTrack } from "./mod.ts"; export class VttPlayerTrack extends PlayerTrack { @@ -16,10 +16,10 @@ export class VttPlayerTrack extends PlayerTrack { private player: Player, private node_id: string, track_index: number, - private metadata: SourceTrack, + private track_info: TrackInfo, ) { super(track_index); - this.track = this.player.video.addTextTrack("subtitles", this.metadata.name, this.metadata.language); + this.track = this.player.video.addTextTrack("subtitles", this.track_info.name, this.track_info.language); this.buffered.value = [{ start: 0, end: this.player.duration.value, status: "loading" }] this.init() } |