aboutsummaryrefslogtreecommitdiff
path: root/web/script/player
diff options
context:
space:
mode:
authormetamuffin <metamuffin@disroot.org>2023-10-24 07:40:51 +0200
committermetamuffin <metamuffin@disroot.org>2023-10-24 07:40:51 +0200
commitb462195f2dcfe457eae7791c14e4b834b2d5ab29 (patch)
tree492ba43952fa6798320f2b2bb4d4bd5484e2e4f2 /web/script/player
parent6e9ccad881a7f887599bc8f3f6b9ca2424a2cc5e (diff)
parent55f7f06cecd5b6f5661f6f22e8bb3e0448b9713a (diff)
downloadjellything-b462195f2dcfe457eae7791c14e4b834b2d5ab29.tar
jellything-b462195f2dcfe457eae7791c14e4b834b2d5ab29.tar.bz2
jellything-b462195f2dcfe457eae7791c14e4b834b2d5ab29.tar.zst
Merge branch 'master' of codeberg.org:metamuffin/jellything
Diffstat (limited to 'web/script/player')
-rw-r--r--web/script/player/mediacaps.ts66
-rw-r--r--web/script/player/mod.ts3
-rw-r--r--web/script/player/player.ts3
-rw-r--r--web/script/player/profiles.ts13
-rw-r--r--web/script/player/track.ts2
5 files changed, 81 insertions, 6 deletions
diff --git a/web/script/player/mediacaps.ts b/web/script/player/mediacaps.ts
new file mode 100644
index 0000000..ff143c0
--- /dev/null
+++ b/web/script/player/mediacaps.ts
@@ -0,0 +1,66 @@
+import { SourceTrack, SourceTrackKind } from "./jhls.d.ts";
+
+const cache = new Map<string, boolean>()
+
+// TODO this testing method makes the assumption, that if the codec is supported on its own, it can be
+// TODO arbitrarly combined with others that are supported. in reality this is true but the spec does not gurantee it.
+
+export async function test_media_capability(track: SourceTrack): Promise<boolean> {
+ const cache_key = `${get_track_kind(track.kind)};${track.codec}`
+ const cached = cache.get(cache_key);
+ if (cached !== undefined) return cached
+ const r = await test_media_capability_inner(track)
+ console.log(`${r ? "positive" : "negative"} media capability test finished for codec=${track.codec}`);
+ cache.set(cache_key, r)
+ return r
+}
+async function test_media_capability_inner(track: SourceTrack) {
+ if (track.kind.subtitles) {
+ return track.codec == "V_TEXT/WEBVTT" // TODO: actually implement it
+ }
+ let res;
+ const codec = MASTROSKA_CODEC_MAP[track.codec]
+ if (!codec) return console.warn(`unknown codec: ${track.codec}`), false
+ if (track.kind.audio) {
+ res = await navigator.mediaCapabilities.decodingInfo({
+ type: "media-source",
+ audio: {
+ contentType: `audio/webm^; codecs=${codec}`,
+ samplerate: track.kind.audio.sample_rate,
+ channels: "" + track.kind.audio.channels,
+ bitrate: 128 * 1000,
+ }
+ })
+ }
+ if (track.kind.video) {
+ res = await navigator.mediaCapabilities.decodingInfo({
+ type: "media-source",
+ video: {
+ contentType: `video/webm; codecs=${codec}`,
+ framerate: track.kind.video.fps || 30,
+ width: track.kind.video.width,
+ height: track.kind.video.height,
+ bitrate: 5 * 1000 * 1000 // TODO we dont know this but we should in the future
+ }
+ })
+ }
+ return res?.supported ?? false
+}
+
+const MASTROSKA_CODEC_MAP: { [key: string]: string } = {
+ "V_VP9": "vp9",
+ "V_VP8": "vp8",
+ "V_AV1": "av1",
+ "V_MPEG4/ISO/AVC": "h264",
+ "V_MPEGH/ISO/HEVC": "h265",
+ "A_OPUS": "opus",
+ "A_VORBIS": "vorbis",
+ "S_TEXT/WEBVTT": "webvtt",
+}
+
+export function get_track_kind(track: SourceTrackKind): "audio" | "video" | "subtitles" {
+ if (track.audio) return "audio"
+ if (track.video) return "video"
+ if (track.subtitles) return "subtitles"
+ throw new Error("invalid track");
+}
diff --git a/web/script/player/mod.ts b/web/script/player/mod.ts
index 8473280..ce3c113 100644
--- a/web/script/player/mod.ts
+++ b/web/script/player/mod.ts
@@ -9,12 +9,13 @@ import { Logger } from "../jshelper/src/log.ts";
import { EncodingProfile } from "./jhls.d.ts";
import { Player } from "./player.ts";
-document.addEventListener("DOMContentLoaded", () => {
+globalThis.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()
+ globalThis.dispatchEvent(new Event("navigationrequiresreload"))
initialize_player(main, node_id)
}
})
diff --git a/web/script/player/player.ts b/web/script/player/player.ts
index 4c1d9fc..acf2a19 100644
--- a/web/script/player/player.ts
+++ b/web/script/player/player.ts
@@ -21,7 +21,7 @@ export class Player {
private cancel_buffering_pers: undefined | (() => void)
set_pers(s?: string) {
- if (this.cancel_buffering_pers) this.cancel_buffering_pers()
+ if (this.cancel_buffering_pers) this.cancel_buffering_pers(), this.cancel_buffering_pers = undefined
if (s) this.cancel_buffering_pers = this.logger?.log_persistent(s)
}
@@ -38,6 +38,7 @@ export class Player {
}
this.video.onwaiting = () => {
console.log("waiting");
+ if (this.video.currentTime > this.duration.value - 0.2) return this.set_pers("Playback finished")
this.set_pers("Buffering...")
this.canplay.value = false;
}
diff --git a/web/script/player/profiles.ts b/web/script/player/profiles.ts
index a768d47..caa46bd 100644
--- a/web/script/player/profiles.ts
+++ b/web/script/player/profiles.ts
@@ -1,5 +1,6 @@
import { OVar } from "../jshelper/mod.ts";
import { EncodingProfile, JhlsMetadata } from "./jhls.d.ts";
+import { test_media_capability } from "./mediacaps.ts";
import { Player } from "./player.ts";
const PROFILE_UP_FAC = 0.6
@@ -33,19 +34,25 @@ export class ProfileSelector {
if (i.subtitles) return this.profiles_subtitles
return []
}
- select_optimal_profile(track: number, profile: OVar<EncodingProfileExt | undefined>) {
+ async remux_supported(track: number): Promise<boolean> {
+ return await test_media_capability(this.metadata.tracks[track].info)
+ }
+ async select_optimal_profile(track: number, profile: OVar<EncodingProfileExt | undefined>) {
const profs = this.profile_list_for_track(track)
- const co = profile.value?.order ?? -1
+ const sup_remux = await this.remux_supported(track);
+ const min_prof = sup_remux ? -1 : 0
+ const co = profile.value?.order ?? min_prof
const current_bitrate = profile_byterate(profs[co], 5000 * 1000)
const next_bitrate = profile_byterate(profs[co - 1], 5000 * 1000)
// console.log({ current_bitrate, next_bitrate, co, bandwidth: this.bandwidth.value * 8 });
+ if (!sup_remux && !profile.value) profile.value = profs[co];
if (current_bitrate > this.bandwidth.value * PROFILE_DOWN_FAC && co + 1 < profs.length) {
console.log("profile up");
profile.value = profs[co + 1]
this.log_change(track, profile.value)
}
- if (next_bitrate < this.bandwidth.value * PROFILE_UP_FAC && co >= 0) {
+ if (next_bitrate < this.bandwidth.value * PROFILE_UP_FAC && co > min_prof) {
console.log("profile down");
profile.value = profs[co - 1]
this.log_change(track, profile.value)
diff --git a/web/script/player/track.ts b/web/script/player/track.ts
index e2d9d85..4173b12 100644
--- a/web/script/player/track.ts
+++ b/web/script/player/track.ts
@@ -81,7 +81,7 @@ export class PlayerTrack {
async load(index: number) {
this.loading.add(index)
- this.player.profile_selector.select_optimal_profile(this.track_index, this.profile)
+ await this.player.profile_selector.select_optimal_profile(this.track_index, this.profile)
const url = `/n/${encodeURIComponent(this.node_id)}/stream?format=hlsseg&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 => {