aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--matroska/src/bin/mkvdump.rs1
-rw-r--r--web/script/player/mod.ts43
-rw-r--r--web/script/player/player.ts3
-rw-r--r--web/script/player/sync.ts41
-rw-r--r--web/script/player/track/create.ts10
-rw-r--r--web/script/player/track/mse.ts44
-rw-r--r--web/script/player/track/vtt.ts52
7 files changed, 104 insertions, 90 deletions
diff --git a/matroska/src/bin/mkvdump.rs b/matroska/src/bin/mkvdump.rs
index b58adcc..83919d7 100644
--- a/matroska/src/bin/mkvdump.rs
+++ b/matroska/src/bin/mkvdump.rs
@@ -21,3 +21,4 @@ fn main() {
}
}
}
+ \ No newline at end of file
diff --git a/web/script/player/mod.ts b/web/script/player/mod.ts
index a53789f..0f30d45 100644
--- a/web/script/player/mod.ts
+++ b/web/script/player/mod.ts
@@ -11,7 +11,7 @@ import { EncodingProfile } from "./jhls.d.ts";
import { TrackKind, get_track_kind } from "./mediacaps.ts";
import { Player } from "./player.ts";
import { Popup } from "./popup.ts";
-import { Playersync } from "./sync.ts"
+import { Playersync, playersync_controls } from "./sync.ts"
import { MSEPlayerTrack } from "./track/mse.ts";
globalThis.addEventListener("DOMContentLoaded", () => {
@@ -110,49 +110,12 @@ function initialize_player(el: HTMLElement, node_id: string) {
)
return button
}
+
const settings_popup = () => {
const button = e("button", "settings", { class: "icon" })
- let channel_name: HTMLInputElement;
- let channel_name_copy: HTMLInputElement;
new Popup(button, popups, () => e("div", { class: "jsp-settings-popup" },
e("h2", "Settings"),
- e("div", { class: ["jsp-controlgroup", "jsp-playersync-controls"] },
- e("h3", "Playersync"),
- sync_state.map(sync => {
- console.log("aaaaa", sync);
- return sync
- ? e("div",
- e("span", "Sync enabled."),
- e("button", "Disable", {
- onclick: () => { sync_state.value?.destroy(); sync_state.value = undefined }
- }),
- e("p", "Session ID: ",
- channel_name_copy = e("input", { type: "text", disabled: true, value: sync.name }),
- e("button", "content_paste_go", {
- class: "icon",
- onclick: () => {
- logger.log("Session ID copied to clipboard.")
- navigator.clipboard.writeText(channel_name_copy.value)
- }
- })
- ))
- : e("div",
- channel_name = e("input", { type: "text", placeholder: "someroom:example.org" }),
- e("button", "Join", {
- onclick: () => {
- if (!channel_name.value.length) return
- sync_state.value?.destroy()
- sync_state.value = new Playersync(player, logger, channel_name.value)
- }
- }), e("br"),
- e("button", "Create new session", {
- onclick: () => {
- sync_state.value?.destroy()
- sync_state.value = new Playersync(player, logger)
- }
- }))
- })
- )
+ playersync_controls(sync_state, player)
))
return button;
}
diff --git a/web/script/player/player.ts b/web/script/player/player.ts
index 9cedaf5..8fccb24 100644
--- a/web/script/player/player.ts
+++ b/web/script/player/player.ts
@@ -129,6 +129,7 @@ export class Player {
this.set_pers()
})
}
+
async update(newt?: number) {
await Promise.all(this.active_tracks.value.map(t => t.update(newt ?? this.video.currentTime)))
}
@@ -142,7 +143,7 @@ export class Player {
track.abort.abort()
} else if (state && active_index == -1) {
this.logger?.log(`Enabled track ${index}: ${display_track(this.tracks![index])}`)
- this.active_tracks.value.push((await create_track(this, this.node_id, index, this.tracks![index]))!)
+ this.active_tracks.value.push(create_track(this, this.node_id, index, this.tracks![index])!)
if (update) await this.update()
}
this.active_tracks.change()
diff --git a/web/script/player/sync.ts b/web/script/player/sync.ts
index 34a46e3..3c5e3a2 100644
--- a/web/script/player/sync.ts
+++ b/web/script/player/sync.ts
@@ -4,9 +4,50 @@
Copyright (C) 2024 metamuffin <metamuffin.org>
*/
/// <reference lib="dom" />
+import { OVar, e } from "../jshelper/mod.ts";
import { Logger } from "../jshelper/src/log.ts";
import { Player } from "./player.ts"
+export function playersync_controls(sync_state: OVar<undefined | Playersync>, player: Player) {
+ let channel_name: HTMLInputElement;
+ let channel_name_copy: HTMLInputElement;
+ return e("div", { class: ["jsp-controlgroup", "jsp-playersync-controls"] },
+ e("h3", "Playersync"),
+ sync_state.map(sync => sync
+ ? e("div",
+ e("span", "Sync enabled."),
+ e("button", "Disable", {
+ onclick: () => { sync_state.value?.destroy(); sync_state.value = undefined }
+ }),
+ e("p", "Session ID: ",
+ channel_name_copy = e("input", { type: "text", disabled: true, value: sync.name }),
+ e("button", "content_paste_go", {
+ class: "icon",
+ onclick: () => {
+ player.logger?.log("Session ID copied to clipboard.")
+ navigator.clipboard.writeText(channel_name_copy.value)
+ }
+ })
+ ))
+ : e("div",
+ channel_name = e("input", { type: "text", placeholder: "someroom:example.org" }),
+ e("button", "Join", {
+ onclick: () => {
+ if (!channel_name.value.length) return
+ sync_state.value?.destroy()
+ sync_state.value = new Playersync(player, player.logger!, channel_name.value)
+ }
+ }), e("br"),
+ e("button", "Create new session", {
+ onclick: () => {
+ sync_state.value?.destroy()
+ sync_state.value = new Playersync(player, player.logger!)
+ }
+ }))
+ )
+ )
+}
+
function get_username() {
return document.querySelector("nav .account .username")?.textContent ?? "Unknown User"
}
diff --git a/web/script/player/track/create.ts b/web/script/player/track/create.ts
index f674c3a..d63a9ce 100644
--- a/web/script/player/track/create.ts
+++ b/web/script/player/track/create.ts
@@ -1,12 +1,12 @@
import { get_track_kind } from "../mediacaps.ts";
-import { create_vtt_track } from "./vtt.ts";
-import { create_mse_track } from "./mse.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";
-export async function create_track(player: Player, node_id: string, track_index: number, metadata: SourceTrack): Promise<PlayerTrack | undefined> {
+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 await create_vtt_track(player, node_id, track_index, metadata)
- else return await create_mse_track(player, node_id, track_index, metadata)
+ if (kind == "subtitles") return new VttPlayerTrack(player, node_id, track_index, metadata)
+ else return new MSEPlayerTrack(player, node_id, track_index, metadata)
}
diff --git a/web/script/player/track/mse.ts b/web/script/player/track/mse.ts
index 2d17b87..a4320b5 100644
--- a/web/script/player/track/mse.ts
+++ b/web/script/player/track/mse.ts
@@ -5,25 +5,6 @@ import { BufferRange, Player } from "../player.ts";
import { EncodingProfileExt, ProfileSelector } from "../profiles.ts";
import { PlayerTrack, AppendRange, TARGET_BUFFER_DURATION, MIN_BUFFER_DURATION } from "./mod.ts";
-export async function create_mse_track(player: Player, node_id: string, track_index: number, metadata: SourceTrack): Promise<MSEPlayerTrack | undefined> {
- try {
- const res = await fetch(`/n/${encodeURIComponent(player.node_id)}/stream?format=jhlsi&tracks=${track_index}`, { headers: { "Accept": "application/json" } });
- if (!res.ok) return player.error.value = "Cannot download index.", undefined;
- let index!: JhlsTrackIndex & { error: string; };
- try { index = await res.json(); }
- catch (_) { player.set_pers("Error: Failed to fetch node"); }
- if (index.error) return player.set_pers("server error: " + index.error), undefined;
-
- const t = new MSEPlayerTrack(player, node_id, track_index, metadata, index);
- await t.init();
- return t;
- } catch (e) {
- if (e instanceof TypeError) {
- player.set_pers("Cannot download index: Network Error");
- } else throw e;
- }
-}
-
export class MSEPlayerTrack extends PlayerTrack {
public source_buffer!: SourceBuffer;
private current_load?: AppendRange;
@@ -31,18 +12,36 @@ export class MSEPlayerTrack extends PlayerTrack {
private append_queue: AppendRange[] = [];
public profile_selector: ProfileSelector;
public profile = new OVar<EncodingProfileExt | undefined>(undefined);
+ public index?: JhlsTrackIndex
constructor(
private player: Player,
private node_id: string,
track_index: number,
private metadata: SourceTrack,
- public index: JhlsTrackIndex
) {
super(track_index);
this.profile_selector = new ProfileSelector(player, this, player.downloader.bandwidth);
+ 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&tracks=${this.track_index}`, { headers: { "Accept": "application/json" } });
+ if (!res.ok) return this.player.error.value = "Cannot download index.", undefined;
+ let index!: JhlsTrackIndex & { 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;
+ this.index = index
+ } catch (e) {
+ if (e instanceof TypeError) {
+ this.player.set_pers("Cannot download index: Network Error");
+ } else throw e;
+ }
+ this.buffered.value = []
+
await this.profile_selector.select_optimal_profile(this.track_index, this.profile);
const ct = track_to_content_type(this.track_from_profile())!;
console.log(`track ${this.track_index} source buffer content-type: ${ct}`);
@@ -71,6 +70,8 @@ export class MSEPlayerTrack extends PlayerTrack {
this.source_buffer.addEventListener("abort", e => {
console.error("sourcebuffer abort", e);
});
+
+ this.update(this.player.video.currentTime)
}
track_from_profile(): SourceTrack {
if (this.profile.value) return profile_to_partial_track(this.profile.value);
@@ -78,6 +79,7 @@ export class MSEPlayerTrack extends PlayerTrack {
}
update_buf_ranges() {
+ if (!this.index) return;
const ranges: BufferRange[] = [];
for (let i = 0; i < this.source_buffer.buffered.length; i++) {
ranges.push({
@@ -93,6 +95,7 @@ export class MSEPlayerTrack extends PlayerTrack {
}
async update(target: number) {
+ if (!this.index) return;
this.update_buf_ranges(); // TODO required?
const blocking = [];
@@ -122,6 +125,7 @@ export class MSEPlayerTrack extends PlayerTrack {
const url = `/n/${encodeURIComponent(this.node_id)}/stream?format=snippet&webm=true&tracks=${this.track_index}&index=${index}${this.profile.value ? `&profile=${this.profile.value.id}` : ""}`;
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.segments[index], index, cb });
this.tick_append();
diff --git a/web/script/player/track/vtt.ts b/web/script/player/track/vtt.ts
index efdafe0..4dddbc7 100644
--- a/web/script/player/track/vtt.ts
+++ b/web/script/player/track/vtt.ts
@@ -2,40 +2,26 @@ import { SourceTrack, JvttCue } from "../jhls.d.ts";
import { Player } from "../player.ts";
import { PlayerTrack } from "./mod.ts";
-export async function create_vtt_track(player: Player, node_id: string, track_index: number, metadata: SourceTrack): Promise<VttPlayerTrack | undefined> {
- let index: JvttCue[];
- try {
- const res = await fetch(`/n/${encodeURIComponent(player.node_id)}/stream?format=jvtt&tracks=${track_index}`, { headers: { "Accept": "application/json" } });
- if (!res.ok) return player.error.value = "Cannot download index.", undefined;
- let ai!: JvttCue[] & { error: string; };
- try { ai = await res.json(); }
- catch (_) { player.set_pers("Error: Failed to fetch node"); }
- if (ai.error) return player.set_pers("server error: " + ai.error), undefined;
- index = ai;
- } catch (e) {
- if (e instanceof TypeError) {
- player.set_pers("Cannot download subtitles: Network Error");
- return undefined
- } else throw e;
- }
- const t = new VttPlayerTrack(player, node_id, track_index, metadata, index);
- return t;
-}
-
export class VttPlayerTrack extends PlayerTrack {
private track: TextTrack;
+ public cues?: JvttCue[]
constructor(
private player: Player,
private node_id: string,
track_index: number,
private metadata: SourceTrack,
- public cues: JvttCue[]
) {
super(track_index);
- this.buffered.value = [{ start: 0, end: player.duration.value, status: "buffered" }]
- this.track = this.player.video.addTextTrack("subtitles", metadata.name, metadata.language);
- for (const cue of cues) {
+ this.track = this.player.video.addTextTrack("subtitles", this.metadata.name, this.metadata.language);
+ this.buffered.value = [{ start: 0, end: this.player.duration.value, status: "loading" }]
+ this.init()
+ }
+
+ private on_ready() {
+ if (!this.cues) return
+ this.buffered.value = [{ start: 0, end: this.player.duration.value, status: "buffered" }]
+ for (const cue of this.cues) {
this.track.addCue(create_cue(cue));
}
this.track.mode = "showing";
@@ -44,6 +30,24 @@ export class VttPlayerTrack extends PlayerTrack {
this.track.mode = "hidden";
});
}
+
+ async init() {
+ try {
+ const res = await fetch(`/n/${encodeURIComponent(this.node_id)}/stream?format=jvtt&tracks=${this.track_index}`, { headers: { "Accept": "application/json" } });
+ if (!res.ok) return this.player.error.value = "Cannot download index.", undefined;
+ let ai!: JvttCue[] & { error: string; };
+ try { ai = await res.json(); }
+ catch (_) { this.player.set_pers("Error: Failed to fetch node"); }
+ if (ai.error) return this.player.set_pers("server error: " + ai.error), undefined;
+ this.cues = ai;
+ } catch (e) {
+ if (e instanceof TypeError) {
+ this.player.set_pers("Cannot download subtitles: Network Error");
+ return undefined
+ } else throw e;
+ }
+ this.on_ready()
+ }
}
function create_cue(cue: JvttCue): VTTCue {