/* 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 */ /// import { FormatInfo, StreamContainer } from "./types_stream.ts"; const cache = new Map() // 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(format: FormatInfo, container: StreamContainer): Promise { const cache_key = JSON.stringify(format) + container const cached = cache.get(cache_key); if (cached !== undefined) return cached const r = await test_media_capability_inner(format, container) console.log(`${r ? "positive" : "negative"} media capability test finished for codec=${format.codec}`); cache.set(cache_key, r) return r } async function test_media_capability_inner(format: FormatInfo, container: StreamContainer) { if (format.codec.startsWith("S_") || format.codec.startsWith("D_")) { // TODO do we need to check this? return format.codec == "S_TEXT/WEBVTT" || format.codec == "S_TEXT/UTF8" || format.codec == "D_WEBVTT/SUBTITLES" } let res; if (format.codec.startsWith("A_")) { res = await navigator.mediaCapabilities.decodingInfo({ type: "media-source", audio: { contentType: track_to_content_type(format, container), samplerate: format.samplerate, channels: "" + format.channels, bitrate: format.bitrate, } }) } if (format.codec.startsWith("V_")) { res = await navigator.mediaCapabilities.decodingInfo({ type: "media-source", video: { contentType: track_to_content_type(format, container), framerate: 30, // TODO get average framerate from server width: format.width ?? 1920, height: format.height ?? 1080, bitrate: format.bitrate } }) } return res?.supported ?? false } export function track_to_content_type(format: FormatInfo, container: StreamContainer): string { let c = CONTAINER_TO_MIME_TYPE[container]; if (format.codec.startsWith("A_")) c = c.replace("video/", "audio/") return `${c}; codecs="${MASTROSKA_CODEC_MAP[format.codec]}"` } const MASTROSKA_CODEC_MAP: { [key: string]: string } = { "V_VP9": "vp9", "V_VP8": "vp8", "V_AV1": "av1", "V_MPEG4/ISO/AVC": "avc1.4d002a", "V_MPEGH/ISO/HEVC": "h265", "A_OPUS": "opus", "A_VORBIS": "vorbis", "S_TEXT/WEBVTT": "webvtt", "D_WEBVTT/SUBTITLES": "webvtt", } const CONTAINER_TO_MIME_TYPE: { [key in StreamContainer]: string } = { webvtt: "text/webvtt", webm: "video/webm", matroska: "video/x-matroska", mpeg4: "video/mp4", jvtt: "application/jellything-vtt+json" }