aboutsummaryrefslogtreecommitdiff
path: root/web/script
diff options
context:
space:
mode:
authormetamuffin <metamuffin@disroot.org>2023-10-01 16:02:41 +0200
committermetamuffin <metamuffin@disroot.org>2023-10-01 16:02:41 +0200
commit3c1c3325ecebd6f5c7bc33a107c6efc4cda8b3ea (patch)
tree895ea285ebc52bb4e0e044d044d466f8e41ef599 /web/script
parent9559da7f56310e8b40e1bce0b073147b99301008 (diff)
downloadjellything-3c1c3325ecebd6f5c7bc33a107c6efc4cda8b3ea.tar
jellything-3c1c3325ecebd6f5c7bc33a107c6efc4cda8b3ea.tar.bz2
jellything-3c1c3325ecebd6f5c7bc33a107c6efc4cda8b3ea.tar.zst
jhls does not work bc segments to short
Diffstat (limited to 'web/script')
-rw-r--r--web/script/player/mod.ts187
1 files changed, 138 insertions, 49 deletions
diff --git a/web/script/player/mod.ts b/web/script/player/mod.ts
index 20f47ca..6a73f5f 100644
--- a/web/script/player/mod.ts
+++ b/web/script/player/mod.ts
@@ -1,11 +1,41 @@
import { OVar, e } from "../jshelper/mod.ts";
+export interface JhlsMetadata {
+ tracks: JhlsTrack[],
+ duration: number,
+}
+export interface JhlsTrack {
+ info: SourceTrack,
+ segments: { start: number, end: number }[],
+}
+export interface SourceTrack {
+ kind: SourceTrackKind,
+ name: string,
+ codec: string,
+ language: string,
+}
+export interface SourceTrackKind {
+ video?: {
+ width: number,
+ height: number,
+ fps: number,
+ },
+ audio?: {
+ channels: number,
+ sample_rate: number,
+ bit_depth: number,
+ },
+ subtitles?: boolean,
+}
+
document.addEventListener("DOMContentLoaded", () => {
if (document.body.classList.contains("player")) {
+ if (!globalThis.MediaSource) return alert("Media Source Extension API required")
const node_id = globalThis.location.pathname.split("/")[2];
const main = document.getElementById("main")!;
+ document.getElementsByTagName("footer")[0].remove()
initialize_player(main, node_id)
}
})
@@ -13,24 +43,17 @@ document.addEventListener("DOMContentLoaded", () => {
function initialize_player(el: HTMLElement, node_id: string) {
el.innerHTML = "" // clear the body
- const buffering_state = new OVar(true)
- const playing_state = new OVar(false)
- const current_time = new OVar(0)
- const remaining_time = new OVar(0)
-
- const vel = e("video")
- vel.src = `/n/${node_id}/stream?tracks=1&tracks=0&format=matroska&webm=true`
-
- const toggle_playing = () => vel.paused ? vel.play() : vel.pause()
+ const player = new Player(node_id)
+ const toggle_playing = () => player.playing.value ? player.pause() : player.play()
let pri_current: HTMLElement;
let pri: HTMLElement;
const controls = e("div", { class: "jsp-controls" },
- playing_state.map(playing => e("button", playing ? "||" : "|>", { onclick: toggle_playing })),
+ player.playing.map(playing => e("button", playing ? "||" : "|>", { onclick() { } })),
e("p", { class: "jsp-status" },
- current_time.map(v => e("span", display_time(v))), e("br"),
- remaining_time.map(v => e("span", display_time(v)))
+ player.position.map(v => e("span", display_time(v))), e("br"),
+ player.position.map(v => e("span", display_time(v - player.duration.value)))
),
pri = e("div", { class: "jsp-pri" }, pri_current = e("div", { class: "jsp-pri-current" })),
e("button", "X", {
@@ -41,57 +64,39 @@ function initialize_player(el: HTMLElement, node_id: string) {
})
)
+ player.position.onchangeinit(p => pri_current.style.width = (p / player.duration.value * 100) + "%")
- vel.onloadedmetadata = () => {
-
- }
- vel.ondurationchange = () => {
-
- }
- vel.ontimeupdate = () => {
- remaining_time.value = vel.currentTime - vel.duration;
- current_time.value = vel.currentTime
- pri_current.style.width = (vel.currentTime / vel.duration * 100) + "%"
- }
- vel.onplay = () => {
- buffering_state.value = false;
- }
- vel.onwaiting = () => {
- buffering_state.value = true;
- }
- vel.onplaying = () => {
- playing_state.value = true;
- }
- vel.onpause = () => {
- playing_state.value = false
- }
+ const pel = e("div", { class: "jsp" },
+ player.video,
+ player.buffering_status.map(b => e("div", { class: "jsp-overlay" },
+ b ? e("p", { class: "jsp-buffering" }, b) : undefined
+ )),
+ controls,
+ )
+ el.append(pel)
- mouse_idle(vel, 1000, idle => {
+ mouse_idle(pel, 1000, idle => {
controls.style.opacity = idle ? "0" : "1"
- vel.style.cursor = idle ? "none" : "default"
+ pel.style.cursor = idle ? "none" : "default"
})
- vel.addEventListener("click", toggle_playing)
+ player.video.addEventListener("click", toggle_playing)
pri.addEventListener("mousedown", ev => {
const r = pri.getBoundingClientRect()
const p = (ev.clientX - r.left) / (r.right - r.left)
- vel.currentTime = p * vel.duration
+ player.seek(p * player.duration.value)
})
document.body.addEventListener("keydown", k => {
- if (k.code == "Period") vel["seekToNextFrame" as "play"]()
- else if (k.code == "Space") toggle_playing()
- else if (k.code == "ArrowLeft") vel.currentTime -= 5
- else if (k.code == "ArrowRight") vel.currentTime += 5
- else if (k.code == "ArrowUp") vel.currentTime -= 60
- else if (k.code == "ArrowDown") vel.currentTime += 60
+ // if (k.code == "Period") vel["seekToNextFrame" as "play"]()
+ if (k.code == "Space") toggle_playing()
+ 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)
+ else if (k.code == "ArrowDown") player.seek(player.position.value + 60)
else return;
k.preventDefault()
})
- el.append(e("div", { class: "jsp" },
- vel,
- controls,
- ))
}
function mouse_idle(e: HTMLElement, timeout: number, cb: (b: boolean) => unknown) {
@@ -118,3 +123,87 @@ function display_time(t: number): string {
while (t > 1) t -= 1, s++;
return (h ? h + "h" : "") + (m ? m + "m" : "") + (s ? s + "s" : "")
}
+
+class Player {
+ public video = e("video")
+ private media_source = new MediaSource();
+
+ public position = new OVar(0)
+ public duration = new OVar(1)
+ public playing = 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.video.onplay = () => {
+ this.buffering_status.value = undefined;
+ }
+ this.video.onwaiting = () => {
+ this.buffering_status.value = "Buffering...";
+ }
+ this.video.onplaying = () => {
+ this.playing.value = true;
+ }
+ this.video.onpause = () => {
+ this.playing.value = false
+ }
+
+ 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 = "Fetching initial segments..."
+
+ this.duration.value = metadata.duration
+ this.video.src = URL.createObjectURL(this.media_source)
+ this.media_source.addEventListener("sourceopen", () => {
+ console.log("sourceopen");
+ this.segment(metadata)
+ })
+
+ }
+ async segment(metadata: JhlsMetadata) {
+ const srcbuf = this.media_source.addSourceBuffer("video/webm")
+ srcbuf.mode = "segments"
+ srcbuf.addEventListener("updatestart", () => {
+ console.log("updatestart");
+ })
+ srcbuf.addEventListener("updateend", () => {
+ console.log("updateend");
+ })
+ srcbuf.addEventListener("update", () => {
+ console.log("update");
+ })
+ srcbuf.addEventListener("error", () => {
+ console.log("error");
+ })
+ let i = 0;
+ for (const seg of metadata.tracks[0].segments) {
+ if (i++ > 5) break
+ const res = await fetch(`/n/${encodeURIComponent(this.node_id)}/stream?format=hlsseg&index=${i}&tracks=0`)
+ if (!res.ok) return this.error.value = "Cannot download media segment."
+ const segbuf = await res.arrayBuffer()
+ console.log("aaa");
+ srcbuf.timestampOffset = seg.start
+ srcbuf.appendBuffer(segbuf)
+ }
+ }
+
+ play() {
+ console.log("play");
+ this.video.play()
+ }
+ pause() {
+ console.log("pause");
+ this.video.pause()
+ }
+ seek(_p: number) { }
+
+}