aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormetamuffin <metamuffin@disroot.org>2024-01-26 05:11:13 +0100
committermetamuffin <metamuffin@disroot.org>2024-01-26 05:11:13 +0100
commit9e88a60734a53a6a2ce55fc174e2f2fd8e073eaa (patch)
treea083095e651ee6959f1f844af6e9256ce1f93ce6
parent45a1ac348e2ca9cd4e05ee33d88e4dafc5336126 (diff)
downloadjellything-9e88a60734a53a6a2ce55fc174e2f2fd8e073eaa.tar
jellything-9e88a60734a53a6a2ce55fc174e2f2fd8e073eaa.tar.bz2
jellything-9e88a60734a53a6a2ce55fc174e2f2fd8e073eaa.tar.zst
jvtt works in player
-rw-r--r--common/src/user.rs4
-rw-r--r--stream/src/lib.rs2
-rw-r--r--web/script/player/jhls.d.ts4
-rw-r--r--web/script/player/mod.ts5
-rw-r--r--web/script/player/track.ts70
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() {