aboutsummaryrefslogtreecommitdiff
path: root/web/script/player/profiles.ts_
blob: 943639cb75a6d4d55af7e53c58371d240da7863d (plain)
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
}