aboutsummaryrefslogtreecommitdiff
path: root/web
diff options
context:
space:
mode:
authormetamuffin <metamuffin@disroot.org>2023-10-01 21:30:12 +0200
committermetamuffin <metamuffin@disroot.org>2023-10-01 21:30:12 +0200
commit29c3289bd8441dbe40a91fd7089838730f9fc47f (patch)
treececc63cd12039000d7085d1c14b00c2b89d5017b /web
parent210966261f65ff2f57e82c17ea1348e6474f771b (diff)
downloadjellything-29c3289bd8441dbe40a91fd7089838730f9fc47f.tar
jellything-29c3289bd8441dbe40a91fd7089838730f9fc47f.tar.bz2
jellything-29c3289bd8441dbe40a91fd7089838730f9fc47f.tar.zst
rewriting player
Diffstat (limited to 'web')
-rw-r--r--web/js-player.css19
m---------web/script/jshelper0
-rw-r--r--web/script/player/mod.ts132
3 files changed, 98 insertions, 53 deletions
diff --git a/web/js-player.css b/web/js-player.css
index 24adf5a..2ab25f3 100644
--- a/web/js-player.css
+++ b/web/js-player.css
@@ -29,7 +29,7 @@
background-color: #ffffff10;
}
-.jsp .jsp-controls p.jsp-status {
+.jsp-controls p.jsp-status {
display: inline-block;
width: 6em;
text-align: right;
@@ -38,7 +38,7 @@
margin: 0.4em;
}
-.jsp .jsp-pri {
+.jsp-pri {
position: relative;
flex-grow: 1;
padding: 0px;
@@ -46,19 +46,26 @@
margin: 0px;
width: calc(100% - var(--csize) * 2 - 2px);
height: var(--csize);
+ --pribufsize: calc(var(--csize) * 0.5);
}
-.jsp .jsp-pri-current {
+.jsp-pri-current {
z-index: 101;
position: absolute;
height: var(--csize);
background-color: #ffffff20;
}
-.jsp .jsp-pri-buffered {
+.jsp-pri-buffer {
z-index: 100;
position: absolute;
- height: var(--csize);
+ height: var(--pribufsize);
+}
+.jsp-pri-buffer-buffered {
background-color: #08fa0018;
}
+.jsp-pri-buffer-loading {
+ background-color: #ffd00018;
+}
+
.jsp-overlay {
position: absolute;
bottom: var(--csize);
@@ -68,4 +75,4 @@
margin: 0.2em;
font-size: larger;
color: grey;
-} \ No newline at end of file
+}
diff --git a/web/script/jshelper b/web/script/jshelper
-Subproject 33aaa11e8d88edad10dcb953f063b59c38cfe94
+Subproject 1a42804b2df0c443588863e77c1c4c619a33533
diff --git a/web/script/player/mod.ts b/web/script/player/mod.ts
index aed504a..17cd77e 100644
--- a/web/script/player/mod.ts
+++ b/web/script/player/mod.ts
@@ -34,7 +34,8 @@ export interface SourceTrackKind {
subtitles?: boolean,
}
-const TARGET_BUFFER_DURATION = 30
+const TARGET_BUFFER_DURATION = 15
+const MIN_BUFFER_DURATION = 1
document.addEventListener("DOMContentLoaded", () => {
if (document.body.classList.contains("player")) {
@@ -64,15 +65,19 @@ function initialize_player(el: HTMLElement, node_id: string) {
),
pri = e("div", { class: "jsp-pri" },
pri_current = e("div", { class: "jsp-pri-current" }),
- player.buffered.map(
- ranges => e("div", ...ranges.map(
- r => e("div", {
- class: "jsp-pri-buffered", style: {
- width: pri_map(r.end - r.start),
- left: pri_map(r.start)
- }
- })
- ))
+ player.tracks.map(
+ tracks => e("div", ...tracks.map((t, i) => t.buffered.map(
+ ranges => e("div", ...ranges.map(
+ r => e("div", {
+ class: ["jsp-pri-buffer", `jsp-pri-buffer-${r.status}`],
+ style: {
+ width: pri_map(r.end - r.start),
+ top: `calc(var(--pribufsize)*${i})`,
+ left: pri_map(r.start)
+ }
+ })
+ ))
+ )))
)
),
e("button", "X", {
@@ -147,12 +152,12 @@ interface BufferRange extends Range { status: "buffered" | "downloading" | "queu
class Player {
public video = e("video")
private media_source = new MediaSource();
- public tracks: PlayerTrack[] = [];
+ public tracks = new OVar<PlayerTrack[]>([]);
public position = new OVar(0)
public duration = new OVar(1)
public playing = new OVar(false)
- public buffered = new OVar<BufferRange[]>([])
+ public canplay = new OVar(false)
public buffering_status = new OVar<string | undefined>(undefined)
public error = new OVar<string | undefined>(undefined)
@@ -162,13 +167,14 @@ class Player {
this.video.ontimeupdate = () => {
this.position.value = this.video.currentTime
this.update() // TODO maybe not here
- this.update_buffered_ranges()
}
this.video.onplay = () => {
this.buffering_status.value = undefined;
}
this.video.onwaiting = () => {
+ console.log("waiting");
this.buffering_status.value = "Buffering...";
+ this.canplay.value = false;
}
this.video.onplaying = () => {
this.playing.value = true;
@@ -176,19 +182,12 @@ class Player {
this.video.onpause = () => {
this.playing.value = false
}
- this.fetch_meta()
- }
-
- update_buffered_ranges() {
- const o: BufferRange[] = []
- for (let i = 0; i < this.video.buffered.length; i++) {
- o.push({
- start: this.video.buffered.start(i),
- end: this.video.buffered.end(i),
- status: "buffered"
- })
+ this.video.oncanplay = () => {
+ this.buffering_status.value = undefined
+ console.log("canplay");
+ this.canplay.value = true
}
- this.buffered.value = o;
+ this.fetch_meta()
}
async fetch_meta() {
@@ -200,15 +199,18 @@ class Player {
this.duration.value = metadata.duration
this.video.src = URL.createObjectURL(this.media_source)
- this.media_source.addEventListener("sourceopen", () => {
- this.tracks.push(new PlayerTrack(this.media_source, this.node_id, 0, metadata.tracks[0]))
- this.tracks.push(new PlayerTrack(this.media_source, this.node_id, 1, metadata.tracks[1]))
+ 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.change()
this.buffering_status.value = "Fetching initial segments..."
this.update()
+ await this.canplay.wait_for(true)
+ this.buffering_status.value = undefined
})
}
- update() {
- this.tracks.forEach(t => t.update(this.video.currentTime))
+ async update(newt?: number) {
+ await Promise.all(this.tracks.value.map(t => t.update(newt ?? this.video.currentTime)))
}
play() {
@@ -219,61 +221,97 @@ class Player {
console.log("pause");
this.video.pause()
}
- seek(p: number) {
+ async seek(p: number) {
+ this.pause()
+ await this.update(p)
this.video.currentTime = p
- this.update()
+ this.play()
}
}
+interface AppendRange extends Range { buf: ArrayBuffer, index: number, cb: () => void }
class PlayerTrack {
private source_buffer: SourceBuffer
- private loaded = new Set<number>()
+ private current_load?: AppendRange
private loading = new Set<number>()
- private append_queue: (Range & { buf: ArrayBuffer, cb: () => void })[] = []
+ 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")
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.update_buf_ranges()
this.tick_append()
})
this.source_buffer.addEventListener("error", e => {
console.error("sourcebuffer error", e);
})
}
+
+ update_buf_ranges() {
+ 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: "downloading" })
+ }
+ this.buffered.value = ranges
+ }
+
async update(target: number) {
- console.log(`update track ${this.track_index}`, target, this.loaded, this.loading);
+ this.update_buf_ranges() // TODO required?
+
for (let i = 0; i < this.metadata.segments.length; i++) {
- const segment = this.metadata.segments[i];
- if (segment.start >= target - 1 && segment.end < target + TARGET_BUFFER_DURATION) {
- if (!this.loaded.has(i) && !this.loading.has(i)) {
- this.loading.add(i)
- this.load(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.start <= target + MIN_BUFFER_DURATION)
+ await this.load(i)
+ else
+ this.load(i)
}
}
+ check_buf_collision(start: number, end: number) {
+ for (const r of this.buffered.value)
+ if (r.end > start && r.start < end)
+ return false
+ return true
+ }
+
async load(index: number) {
+ this.loading.add(index)
+ console.log("load", 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()
- this.loading.delete(index)
- this.loaded.add(index)
await new Promise<void>(cb => {
- this.append_queue.push({ buf, ...this.metadata.segments[index], cb })
+ this.append_queue.push({ buf, ...this.metadata.segments[index], index, cb })
this.tick_append()
})
}
tick_append() {
- console.log("tick", this.append_queue);
+ // console.log("tick", this.append_queue);
if (this.source_buffer.updating) return
if (this.append_queue.length) {
const seg = this.append_queue[0];
- console.log("append", this.track_index, seg);
+ // console.log("append", this.track_index, seg);
this.source_buffer.timestampOffset = seg.start
this.source_buffer.appendBuffer(seg.buf);
this.append_queue.splice(0, 1)
+ this.current_load = seg
}
}
} \ No newline at end of file