aboutsummaryrefslogtreecommitdiff
path: root/web/script/player/mediacaps.ts
blob: ad1a0370178c7ead869fb1a6ac82cb6cf320c798 (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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
/*
    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) 2024 metamuffin <metamuffin.org>
*/
/// <reference lib="dom" />
import { EncodingProfile, 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) {
    console.log(track);
    if (track.kind == "subtitles") {
        // TODO do we need to check this?
        return track.codec == "V_TEXT/WEBVTT" || track.codec == "D_WEBVTT/SUBTITLES"
    }
    let res;
    const codec = MASTROSKA_CODEC_MAP[track.codec]
    if (!codec) return console.warn(`unknown codec: ${track.codec}`), false
    if ("audio" in track.kind) {
        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 ("video" in track.kind) {
        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
}

export function track_to_content_type(track: SourceTrack): string | undefined {
    if (track.kind == "subtitles") return "video/webm"
    const codec = MASTROSKA_CODEC_MAP[track.codec]
    if (!codec) return
    return `${get_track_kind(track.kind)}/webm; codecs="${codec}"`
}
export function profile_to_partial_track(profile: EncodingProfile): SourceTrack {
    if (profile.audio) {
        return {
            codec: FFMPEG_ENCODER_CODEC_MAP[profile.audio.codec],
            kind: { audio: { bit_depth: 16, channels: 2, sample_rate: 48000 } },
            name: "test audio",
            language: "en"
        }
    } else if (profile.video) {
        return {
            codec: FFMPEG_ENCODER_CODEC_MAP[profile.video.codec],
            kind: { video: { fps: 30, height: 1080, width: 1090 } },
            language: "en",
            name: "test video"
        }
    } else if (profile.subtitles) {
        return {
            codec: FFMPEG_ENCODER_CODEC_MAP[profile.subtitles.codec],
            kind: "subtitles",
            language: "en",
            name: "test subtitle"
        }
    } else throw new Error("unreachable");
}

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",
    "D_WEBVTT/SUBTITLES": "webvtt",
}

const FFMPEG_ENCODER_CODEC_MAP: { [key: string]: string } = {
    "libsvtav1": "V_AV1",
    "libvpx": "V_VP8",
    "libvpx-vp9": "V_VP9",
    "opus": "A_OPUS",
    "libopus": "A_OPUS",
}

export type TrackKind = "audio" | "video" | "subtitles"
export function get_track_kind(track: SourceTrackKind): TrackKind {
    //@ts-ignore // TODO clean this mess up please
    // TODO why is the subtitle encoded diffenrently sometimes?!
    if (track == "subtitles" || track["subtitles"]) return "subtitles"
    if ("audio" in track) return "audio"
    if ("video" in track) return "video"
    throw new Error("invalid track");
}