aboutsummaryrefslogtreecommitdiff
path: root/web/script
diff options
context:
space:
mode:
Diffstat (limited to 'web/script')
-rw-r--r--web/script/player/mediacaps.ts33
-rw-r--r--web/script/player/player.ts5
-rw-r--r--web/script/player/track.ts109
3 files changed, 98 insertions, 49 deletions
diff --git a/web/script/player/mediacaps.ts b/web/script/player/mediacaps.ts
index b722904..1fec967 100644
--- a/web/script/player/mediacaps.ts
+++ b/web/script/player/mediacaps.ts
@@ -1,4 +1,4 @@
-import { SourceTrack, SourceTrackKind } from "./jhls.d.ts";
+import { EncodingProfile, SourceTrack, SourceTrackKind } from "./jhls.d.ts";
const cache = new Map<string, boolean>()
@@ -47,6 +47,29 @@ async function test_media_capability_inner(track: SourceTrack) {
return res?.supported ?? false
}
+export function track_to_content_type(track: SourceTrack): string | undefined {
+ const codec = MASTROSKA_CODEC_MAP[track.codec]
+ if (!codec) return
+ return `${get_track_kind(track.kind)}/webm; codecs="${codec}"`
+}
+export function profile_to_partial_track(profile: EncodingProfile): SourceTrack {
+ if (profile.audio) {
+ return {
+ codec: FFMPEG_ENCODER_CODEC_MAP[profile.audio.codec],
+ kind: { audio: { bit_depth: 16, channels: 2, sample_rate: 48000 } },
+ name: "test audio",
+ language: "en"
+ }
+ } else if (profile.video) {
+ return {
+ codec: FFMPEG_ENCODER_CODEC_MAP[profile.video.codec],
+ kind: { video: { fps: 30, height: 1080, width: 1090 } },
+ language: "en",
+ name: "test video"
+ }
+ } else throw new Error("todo: subtitles");
+}
+
const MASTROSKA_CODEC_MAP: { [key: string]: string } = {
"V_VP9": "vp9",
"V_VP8": "vp8",
@@ -58,6 +81,14 @@ const MASTROSKA_CODEC_MAP: { [key: string]: string } = {
"S_TEXT/WEBVTT": "webvtt",
}
+const FFMPEG_ENCODER_CODEC_MAP: { [key: string]: string } = {
+ "libsvtav1": "V_AV1",
+ "libvpx": "V_VP8",
+ "libvpx-vp9": "V_VP9",
+ "opus": "A_OPUS",
+ "libopus": "A_OPUS",
+}
+
export function get_track_kind(track: SourceTrackKind): "audio" | "video" | "subtitles" {
if (track.audio) return "audio"
if (track.video) return "video"
diff --git a/web/script/player/player.ts b/web/script/player/player.ts
index 8425279..696fab2 100644
--- a/web/script/player/player.ts
+++ b/web/script/player/player.ts
@@ -85,8 +85,9 @@ export class Player {
this.profile_selector = new ProfileSelector(this, 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.set_pers("Initializing Media Extensions...")
+ this.tracks.value.push(await PlayerTrack.new(this, this.node_id, 0, metadata.tracks[0]))
+ this.tracks.value.push(await PlayerTrack.new(this, this.node_id, 1, metadata.tracks[1]))
this.tracks.change()
this.set_pers("Fetching initial segments...")
this.update()
diff --git a/web/script/player/track.ts b/web/script/player/track.ts
index 3f1e073..cc21763 100644
--- a/web/script/player/track.ts
+++ b/web/script/player/track.ts
@@ -1,103 +1,120 @@
+import { SourceTrack, TimeRange } from "./jhls.d.ts";
import { OVar } from "../jshelper/mod.ts";
-import { JhlsTrack, TimeRange } from "./jhls.d.ts";
+import { JhlsTrack } from "./jhls.d.ts";
+import { profile_to_partial_track, track_to_content_type } from "./mediacaps.ts";
import { BufferRange, Player } from "./player.ts";
import { EncodingProfileExt } from "./profiles.ts";
-const TARGET_BUFFER_DURATION = 10
-const MIN_BUFFER_DURATION = 1
+export const TARGET_BUFFER_DURATION = 10
+export 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)
+ 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);
+ public static async new(player: Player, node_id: string, track_index: number, metadata: JhlsTrack) {
+ const t = new PlayerTrack(player, node_id, track_index, metadata)
+ await t.init()
+ return t
+ }
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"
+ ) { }
+ async init() {
+ await this.player.profile_selector.select_optimal_profile(this.track_index, this.profile);
+ const ct = track_to_content_type(this.track_from_profile())!
+ console.log("source buffer content-type: " + ct);
+ this.source_buffer = this.player.media_source.addSourceBuffer(ct);
+ 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.current_load.cb();
+ this.loading.delete(this.current_load.index);
+ this.current_load = undefined;
}
- this.update_buf_ranges()
- this.tick_append()
- })
+ 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);
- })
+ });
+ }
+
+ track_from_profile(): SourceTrack {
+ if (this.profile.value) return profile_to_partial_track(this.profile.value)
+ else return this.metadata.info
}
update_buf_ranges() {
- const ranges: BufferRange[] = []
+ 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" })
+ ranges.push({ ...this.metadata.segments[r], status: "loading" });
}
- this.buffered.value = ranges
+ this.buffered.value = ranges;
}
async update(target: number) {
- this.update_buf_ranges() // TODO required?
+ this.update_buf_ranges(); // TODO required?
- const blocking = []
+ 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.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))
+ blocking.push(this.load(i));
else
- this.load(i)
+ this.load(i);
}
- await Promise.all(blocking)
+ await Promise.all(blocking);
}
check_buf_collision(start: number, end: number) {
- const EPSILON = 0.01
+ const EPSILON = 0.01;
for (const r of this.buffered.value)
if (r.end - EPSILON > start && r.start < end - EPSILON)
- return false
- return true
+ 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)
+ 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)
+ 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()
- })
+ this.append_queue.push({ buf, ...this.metadata.segments[index], index, cb });
+ this.tick_append();
+ });
}
tick_append() {
- if (this.source_buffer.updating) return
+ 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
+ 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.changeType(track_to_content_type(this.track_from_profile())!);
+ this.source_buffer.timestampOffset = seg.start;
this.source_buffer.appendBuffer(seg.buf);
}