aboutsummaryrefslogtreecommitdiff
path: root/web/script/player/profiles.ts
blob: 9284ec583401dfcfe2065b989a604598db894161 (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
81
82
83
84
85
/*
    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) 2023 metamuffin <metamuffin.org>
*/
import { OVar } from "../jshelper/mod.ts";
import { EncodingProfile, JhlsMetadata } from "./jhls.d.ts";
import { profile_to_partial_track, test_media_capability } from "./mediacaps.ts";
import { Player } from "./player.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_video: EncodingProfileExt[] = []
    profiles_audio: EncodingProfileExt[] = []
    profiles_subtitles: EncodingProfileExt[] = []
    remux_bandwidth = new Map<number, { size: number, duration: number }>()

    constructor(private player: Player, private bandwidth: OVar<number>, private metadata: JhlsMetadata) {
    }
    async init() {
        for (let id = 0; id < this.metadata.extra_profiles.length; id++) {
            const p = this.metadata.extra_profiles[id];
            if (!await test_media_capability(profile_to_partial_track(p))) continue
            if (p.audio) this.profiles_audio.push({ id, order: 0, ...p })
            if (p.video) this.profiles_video.push({ id, order: 0, ...p })
            if (p.subtitles) this.profiles_subtitles.push({ id, order: 0, ...p })
        }
        this.profiles_audio.sort((a, b) => profile_byterate(b) - profile_byterate(a))
        this.profiles_video.sort((a, b) => profile_byterate(b) - profile_byterate(a))
        this.profiles_subtitles.sort((a, b) => profile_byterate(b) - profile_byterate(a))
        for (let i = 0; i < this.profiles_audio.length; i++) this.profiles_audio[i].order = i
        for (let i = 0; i < this.profiles_video.length; i++) this.profiles_video[i].order = i
        for (let i = 0; i < this.profiles_subtitles.length; i++) this.profiles_subtitles[i].order = i
    }
    profile_list_for_track(track: number): EncodingProfileExt[] {
        const i = this.metadata.tracks[track].info.kind
        if (i.audio) return this.profiles_audio
        if (i.video) return this.profiles_video
        if (i.subtitles) return this.profiles_subtitles
        return []
    }
    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 sup_remux = await this.remux_supported(track);
        if (!sup_remux && !profs.length) return this.player.logger?.log("None of the available codecs are supported. The Media can't be played back.")
        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(profs[co], 500 * 1000)
        const next_bitrate = profile_byterate(profs[co - 1], 500 * 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 > min_prof) {
            console.log("profile down");
            profile.value = profs[co - 1]
            this.log_change(track, profile.value)
        }

        // profile.value = profs[0]
    }

    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
}