1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
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
}
|