diff options
-rw-r--r-- | web/js-player.css | 5 | ||||
m--------- | web/script/jshelper | 0 | ||||
-rw-r--r-- | web/script/player/download.ts | 42 | ||||
-rw-r--r-- | web/script/player/mod.ts | 19 | ||||
-rw-r--r-- | web/script/player/player.ts | 8 | ||||
-rw-r--r-- | web/script/player/track.ts | 18 |
6 files changed, 70 insertions, 22 deletions
diff --git a/web/js-player.css b/web/js-player.css index 2ab25f3..490c8dd 100644 --- a/web/js-player.css +++ b/web/js-player.css @@ -76,3 +76,8 @@ font-size: larger; color: grey; } +.jsp-stats { + position: absolute; + bottom: var(--csize); + right: 30px; +} diff --git a/web/script/jshelper b/web/script/jshelper -Subproject 1a42804b2df0c443588863e77c1c4c619a33533 +Subproject 78fe4b0459dd34737303ec97647a83cecfbd1dc diff --git a/web/script/player/download.ts b/web/script/player/download.ts new file mode 100644 index 0000000..63085b8 --- /dev/null +++ b/web/script/player/download.ts @@ -0,0 +1,42 @@ +import { OVar } from "../jshelper/mod.ts"; + +interface Measurement { time: number, duration: number, size: number } +export class SegmentDownloader { + private measurements: Measurement[] = [] + + public bandwidth = new OVar(Infinity) + + constructor() { } + + async download(url: string): Promise<ArrayBuffer> { + const dl_start = performance.now(); + const res = await fetch(url) + const dl_header = performance.now(); + if (!res.ok) throw new Error("aaaaa"); + const buf = await res.arrayBuffer() + const dl_body = performance.now(); + + if (buf.byteLength > 500 * 1000) { + const m = { + time: dl_start, + duration: (dl_body - dl_header) / 1000, + size: buf.byteLength + } + console.log(m); + this.measurements.push(m) + this.update_bandwidth() + } + return buf; + } + + update_bandwidth() { + while (this.measurements.length > 32) + this.measurements.splice(0, 1) + const total_size = this.measurements.reduce((a, v) => v.size + a, 0) + const total_duration = this.measurements.reduce((a, v) => v.duration + a, 0) + const average = total_size / total_duration + console.log(total_size, average, this.measurements); + + this.bandwidth.value = average + } +} diff --git a/web/script/player/mod.ts b/web/script/player/mod.ts index 02b8a12..bf294aa 100644 --- a/web/script/player/mod.ts +++ b/web/script/player/mod.ts @@ -3,6 +3,7 @@ which is licensed under the GNU Affero General Public License (version 3); see /COPYING. Copyright (C) 2023 metamuffin <metamuffin.org> */ +import { OVar, show } from "../jshelper/mod.ts"; import { e } from "../jshelper/mod.ts"; import { Player } from "./player.ts"; @@ -20,6 +21,7 @@ function initialize_player(el: HTMLElement, node_id: string) { el.innerHTML = "" // clear the body const player = new Player(node_id) + const show_stats = new OVar(false); const toggle_playing = () => player.playing.value ? player.pause() : player.play() const pri_map = (v: number) => (v / player.duration.value * 100) + "%" @@ -29,8 +31,8 @@ function initialize_player(el: HTMLElement, node_id: string) { const controls = e("div", { class: "jsp-controls" }, player.playing.map(playing => e("button", playing ? "||" : "|>", { onclick: toggle_playing })), e("p", { class: "jsp-status" }, - player.position.map(v => e("span", display_time(v))), e("br"), - player.position.map(v => e("span", display_time(v - player.duration.value))) + player.position.map(v => e("span", show.duration(v))), e("br"), + player.position.map(v => e("span", show.duration(v - player.duration.value))) ), pri = e("div", { class: "jsp-pri" }, pri_current = e("div", { class: "jsp-pri-current" }), @@ -64,6 +66,9 @@ function initialize_player(el: HTMLElement, node_id: string) { player.buffering_status.map(b => e("div", { class: "jsp-overlay" }, b ? e("p", { class: "jsp-buffering" }, b) : undefined )), + show_stats.map(do_show => !do_show ? e("div") : e("div", {class: "jsp-stats"}, + player.downloader.bandwidth.map(b => e("pre", `estimated bandwidth: ${show.byte_size(b)}`)) + )), controls, ) el.append(pel) @@ -82,6 +87,7 @@ function initialize_player(el: HTMLElement, node_id: string) { document.body.addEventListener("keydown", k => { if (k.code == "Period") player.pause(), player.frame_forward() if (k.code == "Space") toggle_playing() + else if (k.code == "KeyV") show_stats.value = !show_stats.value else if (k.code == "ArrowLeft") player.seek(player.position.value - 5) else if (k.code == "ArrowRight") player.seek(player.position.value + 5) else if (k.code == "ArrowUp") player.seek(player.position.value - 60) @@ -108,12 +114,3 @@ function mouse_idle(e: HTMLElement, timeout: number, cb: (b: boolean) => unknown }, timeout) } } -function display_time(t: number): string { - if (t < 0) return "-" + display_time(-t) - let h = 0, m = 0, s = 0; - while (t > 3600) t -= 3600, h++; - while (t > 60) t -= 60, m++; - while (t > 1) t -= 1, s++; - return (h ? h + "h" : "") + (m ? m + "m" : "") + (s ? s + "s" : "") -} - diff --git a/web/script/player/player.ts b/web/script/player/player.ts index 5c38dc8..08975b2 100644 --- a/web/script/player/player.ts +++ b/web/script/player/player.ts @@ -1,12 +1,14 @@ import { OVar, e } from "../jshelper/mod.ts"; import { JhlsMetadata, TimeRange } from "./jhls.d.ts"; +import { SegmentDownloader } from "./download.ts"; import { PlayerTrack } from "./track.ts"; export interface BufferRange extends TimeRange { status: "buffered" | "loading" | "queued" } export class Player { public video = e("video") - private media_source = new MediaSource(); + public media_source = new MediaSource(); public tracks = new OVar<PlayerTrack[]>([]); + public downloader: SegmentDownloader = new SegmentDownloader(); public position = new OVar(0) public duration = new OVar(1) @@ -66,8 +68,8 @@ export class Player { 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.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() diff --git a/web/script/player/track.ts b/web/script/player/track.ts index b089932..e64cdf3 100644 --- a/web/script/player/track.ts +++ b/web/script/player/track.ts @@ -1,6 +1,6 @@ import { OVar } from "../jshelper/mod.ts"; import { JhlsTrack, TimeRange } from "./jhls.d.ts"; -import { BufferRange } from "./player.ts"; +import { BufferRange, Player } from "./player.ts"; const TARGET_BUFFER_DURATION = 15 const MIN_BUFFER_DURATION = 1 @@ -12,8 +12,13 @@ export class PlayerTrack { 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") + 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") this.source_buffer.mode = "segments" this.source_buffer.addEventListener("updateend", () => { if (this.current_load) { @@ -70,10 +75,7 @@ export class PlayerTrack { 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() - + const buf = await this.player.downloader.download(`/n/${encodeURIComponent(this.node_id)}/stream?format=hlsseg&tracks=${this.track_index}&index=${index}`) await new Promise<void>(cb => { this.append_queue.push({ buf, ...this.metadata.segments[index], index, cb }) this.tick_append() @@ -89,4 +91,4 @@ export class PlayerTrack { this.current_load = seg } } -}
\ No newline at end of file +} |