aboutsummaryrefslogtreecommitdiff
path: root/web/script/player/profiles.ts_
diff options
context:
space:
mode:
Diffstat (limited to 'web/script/player/profiles.ts_')
-rw-r--r--web/script/player/profiles.ts_80
1 files changed, 80 insertions, 0 deletions
diff --git a/web/script/player/profiles.ts_ b/web/script/player/profiles.ts_
new file mode 100644
index 0000000..943639c
--- /dev/null
+++ b/web/script/player/profiles.ts_
@@ -0,0 +1,80 @@
+/*
+ This file is part of jellything (https://codeberg.org/metamuffin/jellything)
+ which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
+ Copyright (C) 2025 metamuffin <metamuffin.org>
+*/
+/// <reference lib="dom" />
+import { OVar } from "../jshelper/mod.ts";
+import { Player } from "./player.ts";
+import { MSEPlayerTrack } from "./track/mse.ts";
+
+const PROFILE_UP_FAC = 0.6
+const PROFILE_DOWN_FAC = 0.8
+
+export interface EncodingProfileExt extends EncodingProfile { id: number, order: number }
+export class ProfileSelector {
+ profiles: EncodingProfileExt[] = []
+ is_init = false
+
+ constructor(
+ private player: Player,
+ private track: MSEPlayerTrack,
+ private bandwidth: OVar<number>
+ ) {
+ }
+ async init() {
+ for (let id = 0; id < this.track.index!.extra_profiles.length; id++) {
+ const p = this.track.index!.extra_profiles[id];
+ // TODO hacky type casting solution
+ if (get_track_kind(this.track.trackinfo.kind) != get_track_kind(p as unknown as SourceTrackKind)) continue
+ if (!await test_media_capability(profile_to_partial_track(p))) continue
+ this.profiles.push({ id, order: 0, ...p })
+ }
+ this.profiles.sort((a, b) => profile_byterate(b) - profile_byterate(a))
+ for (let i = 0; i < this.profiles.length; i++) this.profiles[i].order = i
+ }
+ async remux_supported(track: number): Promise<boolean> {
+ return await test_media_capability(this.player.tracks![track])
+ }
+ async select_optimal_profile(track: number, profile: OVar<EncodingProfileExt | undefined>) {
+ if (!this.is_init) await this.init(), this.is_init = true;
+
+ const sup_remux = await this.remux_supported(track);
+ if (!sup_remux && !this.profiles.length) {
+ this.player.logger?.log("None of the available codecs are supported. This track can't be played back.")
+ return false
+ }
+ const min_prof = sup_remux ? -1 : 0
+ const co = profile.value?.order ?? min_prof
+ // TODO use actual bitrate as a fallback. the server should supply it.
+ const current_bitrate = profile_byterate(this.profiles[co], 500 * 1000)
+ const next_bitrate = profile_byterate(this.profiles[co - 1], 500 * 1000)
+ // console.log({ current_bitrate, next_bitrate, co, bandwidth: this.bandwidth.value * 8 });
+ if (!sup_remux && !profile.value) profile.value = this.profiles[co];
+ if (current_bitrate > this.bandwidth.value * PROFILE_DOWN_FAC && co + 1 < this.profiles.length) {
+ console.log("profile up");
+ profile.value = this.profiles[co + 1]
+ this.log_change(track, profile.value)
+ }
+ if (next_bitrate < this.bandwidth.value * PROFILE_UP_FAC && co > min_prof) {
+ console.log("profile down");
+ profile.value = this.profiles[co - 1]
+ this.log_change(track, profile.value)
+ }
+
+ // profile.value = profs[0]
+ return true
+ }
+
+ log_change(track: number, p: EncodingProfileExt | undefined) {
+ const ps = p ? `transcoding profile ${p.id}` : `remuxed original`
+ this.player.logger?.log(`Track #${track} switched to ${ps}`)
+ }
+}
+
+function profile_byterate(p?: EncodingProfile, fallback = 0): number {
+ if (p?.audio) return p.audio.bitrate / 8
+ if (p?.video) return p.video.bitrate / 8
+ if (p?.subtitles) return 100
+ return fallback
+}