diff options
author | metamuffin <metamuffin@disroot.org> | 2024-01-26 05:11:13 +0100 |
---|---|---|
committer | metamuffin <metamuffin@disroot.org> | 2024-01-26 05:11:13 +0100 |
commit | 9e88a60734a53a6a2ce55fc174e2f2fd8e073eaa (patch) | |
tree | a083095e651ee6959f1f844af6e9256ce1f93ce6 | |
parent | 45a1ac348e2ca9cd4e05ee33d88e4dafc5336126 (diff) | |
download | jellything-9e88a60734a53a6a2ce55fc174e2f2fd8e073eaa.tar jellything-9e88a60734a53a6a2ce55fc174e2f2fd8e073eaa.tar.bz2 jellything-9e88a60734a53a6a2ce55fc174e2f2fd8e073eaa.tar.zst |
jvtt works in player
-rw-r--r-- | common/src/user.rs | 4 | ||||
-rw-r--r-- | stream/src/lib.rs | 2 | ||||
-rw-r--r-- | web/script/player/jhls.d.ts | 4 | ||||
-rw-r--r-- | web/script/player/mod.ts | 5 | ||||
-rw-r--r-- | web/script/player/track.ts | 70 |
5 files changed, 74 insertions, 11 deletions
diff --git a/common/src/user.rs b/common/src/user.rs index f6c955b..81e1cc9 100644 --- a/common/src/user.rs +++ b/common/src/user.rs @@ -98,7 +98,9 @@ impl UserPermission { Transcode | ManageSelf | FederatedContent - | StreamFormat(JhlsIndex | HlsMaster | HlsVariant | Matroska | Snippet | Webvtt) + | StreamFormat( + JhlsIndex | Jvtt | HlsMaster | HlsVariant | Matroska | Snippet | Webvtt + ) ) } } diff --git a/stream/src/lib.rs b/stream/src/lib.rs index 833af3e..906e638 100644 --- a/stream/src/lib.rs +++ b/stream/src/lib.rs @@ -42,7 +42,7 @@ pub fn stream_head(spec: &StreamSpec) -> StreamHead { StreamFormat::JhlsIndex => StreamHead { content_type: "application/jellything-seekindex+json", range_supported: false }, StreamFormat::Webvtt => StreamHead { content_type: "text/vtt", range_supported: false }, StreamFormat::Snippet => StreamHead { content_type: webm_or_mkv, range_supported: false }, - StreamFormat::Jvtt => StreamHead { content_type: "applcation/jellything-vtt+json", range_supported: false }, + StreamFormat::Jvtt => StreamHead { content_type: "application/jellything-vtt+json", range_supported: false }, } } diff --git a/web/script/player/jhls.d.ts b/web/script/player/jhls.d.ts index 9642c66..73c1e3c 100644 --- a/web/script/player/jhls.d.ts +++ b/web/script/player/jhls.d.ts @@ -97,3 +97,7 @@ export interface NodeUserData { watched: WatchedState } export type WatchedState = "none" | "watched" | "pending" | { progress: number } + +export interface JvttCue extends TimeRange { + content: string +}
\ No newline at end of file diff --git a/web/script/player/mod.ts b/web/script/player/mod.ts index fb075d7..ca28e1c 100644 --- a/web/script/player/mod.ts +++ b/web/script/player/mod.ts @@ -12,6 +12,7 @@ import { TrackKind, get_track_kind } from "./mediacaps.ts"; import { Player } from "./player.ts"; import { Popup } from "./popup.ts"; import { Playersync } from "./sync.ts" +import { MSEPlayerTrack } from "./track.ts"; globalThis.addEventListener("DOMContentLoaded", () => { if (document.body.classList.contains("player")) { @@ -186,9 +187,9 @@ function initialize_player(el: HTMLElement, node_id: string) { show_stats.map(do_show => e("div", player.active_tracks.map(tracks => !do_show ? e("div") : e("div", { class: "jsp-stats" }, player.downloader.bandwidth.map(b => e("pre", `estimated bandwidth: ${show.metric(b, "B/s")} | ${show.metric(b * 8, "b/s")}`)), - ...tracks.map((t, i) => t.profile.map(p => + ...tracks.map((t, i) => t instanceof MSEPlayerTrack ? t.profile.map(p => e("pre", `track ${i}: ` + (p ? `profile ${p.id} (${show_profile(p)})` : `remux`)) - )) + ) : "") ) ))), logger.element, diff --git a/web/script/player/track.ts b/web/script/player/track.ts index d0b7f2a..75ffdca 100644 --- a/web/script/player/track.ts +++ b/web/script/player/track.ts @@ -9,23 +9,78 @@ import { OVar } from "../jshelper/mod.ts"; import { profile_to_partial_track, track_to_content_type } from "./mediacaps.ts"; import { BufferRange, Player } from "./player.ts"; import { EncodingProfileExt, ProfileSelector } from "./profiles.ts"; +import { JvttCue } from "./jhls.d.ts"; +import { get_track_kind } from "./mediacaps.ts"; 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 { +export abstract class PlayerTrack { + public static async new(player: Player, node_id: string, track_index: number, metadata: SourceTrack): Promise<PlayerTrack | undefined> { + const kind = get_track_kind(metadata.kind) + if (kind == "subtitles") return await VttPlayerTrack.new(player, node_id, track_index, metadata) + else return await MSEPlayerTrack.new(player, node_id, track_index, metadata) + } + + constructor( + public track_index: number, + ) { } + public buffered = new OVar<BufferRange[]>([]); + public abort = new AbortController() + async update(_target: number) { } +} + +export class VttPlayerTrack extends PlayerTrack { + private track: TextTrack + + public static async new(player: Player, node_id: string, track_index: number, metadata: SourceTrack): Promise<VttPlayerTrack | undefined> { + 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 index!: JvttCue[] & { 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 VttPlayerTrack(player, node_id, track_index, metadata, index) + return t + } catch (e) { + if (e instanceof TypeError) { + player.set_pers("Cannot download subtitles: Network Error") + } else throw e + } + } + constructor( + private player: Player, + private node_id: string, + track_index: number, + private metadata: SourceTrack, + public cues: JvttCue[], + ) { + super(track_index) + this.track = this.player.video.addTextTrack("subtitles", metadata.name, metadata.language) + for (const cue of cues) { + this.track.addCue(new VTTCue(cue.start, cue.end, cue.content)) + } + this.track.mode = "showing" + this.abort.signal.addEventListener("abort", () => { + // TODO disable subtitles properly + this.track.mode = "hidden" + }) + } +} + +export class MSEPlayerTrack extends PlayerTrack { public 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 profile_selector: ProfileSelector - public abort = new AbortController() + public profile = new OVar<EncodingProfileExt | undefined>(undefined); - public static async new(player: Player, node_id: string, track_index: number, metadata: SourceTrack): Promise<PlayerTrack | undefined> { + public static async new(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 @@ -34,7 +89,7 @@ export class PlayerTrack { catch (_) { player.set_pers("Error: Failed to fetch node") } if (index.error) return player.set_pers("server error: " + index.error), undefined - const t = new PlayerTrack(player, node_id, track_index, metadata, index) + const t = new MSEPlayerTrack(player, node_id, track_index, metadata, index) await t.init() return t } catch (e) { @@ -46,10 +101,11 @@ export class PlayerTrack { constructor( private player: Player, private node_id: string, - public track_index: number, + track_index: number, private metadata: SourceTrack, public index: JhlsTrackIndex, ) { + super(track_index) this.profile_selector = new ProfileSelector(player, this, player.downloader.bandwidth) } async init() { |