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
|
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) {
if (track.kind.subtitles) {
return track.codec == "V_TEXT/WEBVTT" // TODO: actually implement it
}
let res;
const codec = MASTROSKA_CODEC_MAP[track.codec]
if (!codec) return console.warn(`unknown codec: ${track.codec}`), false
if (track.kind.audio) {
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 (track.kind.video) {
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 {
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: true },
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",
}
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 function get_track_kind(track: SourceTrackKind): "audio" | "video" | "subtitles" {
if (track.audio) return "audio"
if (track.video) return "video"
if (track.subtitles) return "subtitles"
throw new Error("invalid track");
}
|