From 3035fcddbf2bd8d455f8019abec7aeb2fcd275b8 Mon Sep 17 00:00:00 2001 From: Vanilagy <1696106+Vanilagy@users.noreply.github.com> Date: Thu, 2 Jan 2025 23:12:00 +0100 Subject: [PATCH] Add functions that test codec encodability --- src/codec.ts | 106 +++++++++++++++++++++++++++++++++++++++++++-- src/index.ts | 8 ++++ src/media-drain.ts | 4 +- 3 files changed, 113 insertions(+), 5 deletions(-) diff --git a/src/codec.ts b/src/codec.ts index 367e2f5..33e5fdf 100644 --- a/src/codec.ts +++ b/src/codec.ts @@ -383,11 +383,17 @@ export const buildAudioCodecString = (codec: AudioCodec, numberOfChannels: numbe // Default to standard AAC-LC for higher sample rates return 'mp4a.40.2'; // AAC-LC } else if (codec === 'mp3') { - return 'mp3'; // Easy, this one + return 'mp3'; } else if (codec === 'opus') { - return 'opus'; // Easy, this one + return 'opus'; } else if (codec === 'vorbis') { - return 'vorbis'; // Also easy, this one + return 'vorbis'; + } else if (codec === 'flac') { + return 'flac'; + } else if (codec === 'ulaw') { + return 'ulaw'; + } else if (codec === 'alaw') { + return 'alaw'; } throw new TypeError(`Unhandled codec '${codec}'.`); @@ -825,3 +831,97 @@ export const validateSubtitleMetadata = (metadata: SubtitleMetadata | undefined) throw new TypeError('Subtitle metadata config description must be a string.'); } }; + +/** @public */ +export const canEncode = (codec: MediaCodec) => { + if ((VIDEO_CODECS as readonly string[]).includes(codec)) { + return canEncodeVideo(codec as VideoCodec); + } else if ((AUDIO_CODECS as readonly string[]).includes(codec)) { + return canEncodeAudio(codec as AudioCodec); + } else if ((SUBTITLE_CODECS as readonly string[]).includes(codec)) { + return canEncodeSubtitles(codec as SubtitleCodec); + } + + throw new Error('Unhandled codec.'); +}; + +/** @public */ +export const canEncodeVideo = async (codec: VideoCodec, { width = 1280, height = 720, bitrate = 1e6 }: { + width?: number; + height?: number; + bitrate?: 1e6; +} = {}) => { + if (typeof VideoEncoder === 'undefined') { + return false; + } + + const support = await VideoEncoder.isConfigSupported({ + codec: buildVideoCodecString(codec, width, height, bitrate), + width, + height, + bitrate, + ...getVideoEncoderConfigExtension(codec), + }); + + return support.supported === true; +}; + +/** @public */ +export const canEncodeAudio = async (codec: AudioCodec, { numberOfChannels = 2, sampleRate = 48000, bitrate = 128e3 }: { + numberOfChannels?: number; + sampleRate?: number; + bitrate?: number; +} = {}) => { + if (typeof AudioEncoder === 'undefined') { + return false; + } + + if ((PCM_CODECS as readonly string[]).includes(codec)) { + return false; // TODO write encoder + } + + const support = await AudioEncoder.isConfigSupported({ + codec: buildAudioCodecString(codec, numberOfChannels, sampleRate), + numberOfChannels, + sampleRate, + bitrate, + ...getAudioEncoderConfigExtension(codec), + }); + + return support.supported === true; +}; + +/** @public */ +export const getEncodableCodecs = async (): Promise => { + const [videoCodecs, audioCodecs, subtitleCodecs] = await Promise.all([ + getEncodableVideoCodecs(), + getEncodableAudioCodecs(), + getEncodableSubtitleCodecs(), + ]); + + return [...videoCodecs, ...audioCodecs, ...subtitleCodecs]; +}; + +/** @public */ +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export const canEncodeSubtitles = async (codec: SubtitleCodec) => { + return true; +}; + +/** @public */ +export const getEncodableVideoCodecs = async (): Promise => { + const bools = await Promise.all(VIDEO_CODECS.map(canEncode)); + return VIDEO_CODECS.filter((_, i) => bools[i]); +}; + +/** @public */ +export const getEncodableAudioCodecs = async (): Promise => { + const bools = await Promise.all(AUDIO_CODECS.map(canEncode)); + return AUDIO_CODECS.filter((_, i) => bools[i]); +}; + +/** @public */ +export const getEncodableSubtitleCodecs = async (): Promise => { + const bools = await Promise.all(SUBTITLE_CODECS.map(canEncode)); + return SUBTITLE_CODECS.filter((_, i) => bools[i]); +}; diff --git a/src/index.ts b/src/index.ts index 1b1c489..a46c8c0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -33,6 +33,14 @@ export { SUBTITLE_CODECS, SubtitleCodec, MediaCodec, + canEncode, + canEncodeVideo, + canEncodeAudio, + canEncodeSubtitles, + getEncodableCodecs, + getEncodableVideoCodecs, + getEncodableAudioCodecs, + getEncodableSubtitleCodecs, } from './codec'; export { Target, ArrayBufferTarget, StreamTarget, StreamTargetChunk, StreamTargetOptions } from './target'; export { Rotation, TransformationMatrix, AnyIterable } from './misc'; diff --git a/src/media-drain.ts b/src/media-drain.ts index c38e75f..a043869 100644 --- a/src/media-drain.ts +++ b/src/media-drain.ts @@ -282,7 +282,7 @@ export abstract class BaseMediaFrameDrain< } } - await decoder.flush(); + if (!terminated) await decoder.flush(); decoder.close(); decoderIsFlushed = true; @@ -452,7 +452,7 @@ export abstract class BaseMediaFrameDrain< await chunks.return(); - await decoder.flush(); + if (!terminated) await decoder.flush(); decoder.close(); if (!firstFrameQueued && lastFrame) {