UNPKG

webcodecs-encoder

Version:

A TypeScript library for browser environments to encode video (H.264/AVC, VP9, VP8) and audio (AAC, Opus) using the WebCodecs API and mux them into MP4 or WebM containers with real-time streaming support. New function-first API design.

1 lines 14.7 kB
{"version":3,"sources":["../../src/utils/can-encode.ts"],"sourcesContent":["/**\n * Encode capability verification\n */\n\nimport { EncodeOptions, VideoConfig, AudioConfig } from \"../types\";\n\n/**\n * Verify encode capability\n *\n * @param options Encode options\n * @returns Whether encoding is possible\n */\nexport async function canEncode(options?: EncodeOptions): Promise<boolean> {\n try {\n // Check basic WebCodecs support\n if (!isWebCodecsSupported()) {\n return false;\n }\n\n // Test with default configuration\n if (!options) {\n return await testDefaultConfiguration();\n }\n\n // Check video configuration (only if video is specified)\n const hasVideoConfig = options.video && typeof options.video === \"object\";\n const hasVideo = hasVideoConfig || !options.audio;\n if (hasVideo) {\n const videoCodec = hasVideoConfig\n ? (options.video as VideoConfig).codec || \"avc\"\n : \"avc\";\n const videoSupported = await testVideoCodecSupport(videoCodec, options);\n if (!videoSupported) {\n return false;\n }\n }\n\n // Check audio configuration (only if audio is explicitly specified)\n const hasAudioConfig = options.audio && typeof options.audio === \"object\";\n if (hasAudioConfig) {\n const audioCodec = (options.audio as AudioConfig).codec || \"aac\";\n const audioSupported = await testAudioCodecSupport(audioCodec, options);\n if (!audioSupported) {\n return false;\n }\n } else if (options.audio === undefined && !hasVideoConfig) {\n // Only check audio for default configuration\n const audioSupported = await testAudioCodecSupport(\"aac\", options);\n if (!audioSupported) {\n return false;\n }\n }\n\n return true;\n } catch (error) {\n // If error occurs, assume not supported\n console.warn(\"canEncode error:\", error);\n return false;\n }\n}\n\n/**\n * Check basic WebCodecs support\n */\nfunction isWebCodecsSupported(): boolean {\n try {\n return (\n typeof VideoEncoder !== \"undefined\" &&\n typeof AudioEncoder !== \"undefined\" &&\n typeof VideoFrame !== \"undefined\" &&\n typeof AudioData !== \"undefined\"\n );\n } catch {\n return false;\n }\n}\n\n/**\n * Check encode capability with default configuration\n */\nasync function testDefaultConfiguration(): Promise<boolean> {\n try {\n const defaultWidth = 640;\n const defaultHeight = 480;\n const defaultFrameRate = 30;\n\n // Test H.264 (AVC)\n const videoConfig: VideoEncoderConfig = {\n codec: generateAvcCodecString(\n defaultWidth,\n defaultHeight,\n defaultFrameRate,\n ),\n width: defaultWidth,\n height: defaultHeight,\n bitrate: 1_000_000,\n framerate: defaultFrameRate,\n };\n\n const videoSupport = await VideoEncoder.isConfigSupported(videoConfig);\n if (!videoSupport.supported) {\n return false;\n }\n\n // Test AAC\n const audioConfig: AudioEncoderConfig = {\n codec: \"mp4a.40.2\", // AAC-LC\n sampleRate: 48000,\n numberOfChannels: 2,\n bitrate: 128_000,\n };\n\n const audioSupport = await AudioEncoder.isConfigSupported(audioConfig);\n return audioSupport.supported || false;\n } catch {\n return false;\n }\n}\n\n/**\n * Check video codec support\n */\nasync function testVideoCodecSupport(\n codec: string,\n options?: EncodeOptions,\n): Promise<boolean> {\n try {\n const videoOptions =\n options?.video && typeof options.video === \"object\" ? options.video : {};\n const codecString = getVideoCodecString(\n codec,\n options?.width || 640,\n options?.height || 480,\n options?.frameRate || 30,\n );\n const config: VideoEncoderConfig = {\n codec: codecString,\n width: options?.width || 640,\n height: options?.height || 480,\n bitrate: videoOptions.bitrate || 1_000_000,\n framerate: options?.frameRate || 30,\n };\n\n // Add optional detailed settings\n if (videoOptions.hardwareAcceleration) {\n config.hardwareAcceleration = videoOptions.hardwareAcceleration;\n }\n\n if (videoOptions.latencyMode) {\n config.latencyMode = videoOptions.latencyMode;\n }\n\n const support = await VideoEncoder.isConfigSupported(config);\n return support.supported || false;\n } catch {\n return false;\n }\n}\n\n/**\n * Check audio codec support\n */\nasync function testAudioCodecSupport(\n codec: string,\n options?: EncodeOptions,\n): Promise<boolean> {\n try {\n const codecString = getAudioCodecString(codec);\n const audioOptions =\n typeof options?.audio === \"object\" ? options.audio : {};\n\n const config: AudioEncoderConfig = {\n codec: codecString,\n sampleRate: audioOptions.sampleRate || 48000,\n numberOfChannels: audioOptions.channels || 2,\n bitrate: audioOptions.bitrate || 128_000,\n };\n\n // Add bitrateMode setting for AAC\n if (codec === \"aac\" && audioOptions.bitrateMode) {\n (config as any).bitrateMode = audioOptions.bitrateMode;\n }\n\n const support = await AudioEncoder.isConfigSupported(config);\n return support.supported || false;\n } catch {\n return false;\n }\n}\n\n/**\n * Convert video codec name to WebCodecs string\n */\nfunction getVideoCodecString(\n codec: string,\n width = 640,\n height = 480,\n frameRate = 30,\n): string {\n switch (codec) {\n case \"avc\":\n return generateAvcCodecString(width, height, frameRate);\n case \"hevc\":\n return \"hev1.1.6.L93.B0\"; // H.265 Main Profile\n case \"vp9\":\n return \"vp09.00.10.08\"; // VP9 Profile 0\n case \"vp8\":\n return \"vp8\"; // VP8\n case \"av1\":\n return \"av01.0.04M.08\"; // AV1 Main Profile Level 4.0\n default:\n return codec; // Return as is (for custom codec strings)\n }\n}\n\n/**\n * Dynamically generate AVC (H.264) codec string\n * Uses same logic as encoder-worker.ts\n */\nexport function generateAvcCodecString(\n width: number,\n height: number,\n frameRate: number,\n profile?: \"high\" | \"main\" | \"baseline\",\n): string {\n const mbPerSec = Math.ceil(width / 16) * Math.ceil(height / 16) * frameRate;\n let level: number;\n if (mbPerSec <= 108000) level = 31;\n else if (mbPerSec <= 216000) level = 32;\n else if (mbPerSec <= 245760) level = 40;\n else if (mbPerSec <= 589824) level = 50;\n else if (mbPerSec <= 983040) level = 51;\n else level = 52;\n\n const chosenProfile =\n profile ?? (width >= 1280 || height >= 720 ? \"high\" : \"baseline\");\n const profileHex =\n chosenProfile === \"high\" ? \"64\" : chosenProfile === \"main\" ? \"4d\" : \"42\";\n const levelHex = level.toString(16).padStart(2, \"0\");\n\n return `avc1.${profileHex}00${levelHex}`;\n}\n\n/**\n * Try multiple AVC profiles and return the first supported one\n */\nexport async function generateSupportedAvcCodecString(\n width: number,\n height: number,\n frameRate: number,\n bitrate: number,\n preferredProfile?: \"high\" | \"main\" | \"baseline\",\n): Promise<string | null> {\n // Define profile order based on preference\n const profiles: (\"high\" | \"main\" | \"baseline\")[] = preferredProfile\n ? (([preferredProfile, \"main\", \"baseline\", \"high\"] as const).filter(\n (p, i, arr) => arr.indexOf(p) === i, // remove duplicates\n ) as (\"high\" | \"main\" | \"baseline\")[])\n : [\"high\", \"main\", \"baseline\"];\n\n // Try each profile in order\n for (const profile of profiles) {\n const codecString = generateAvcCodecString(\n width,\n height,\n frameRate,\n profile,\n );\n\n try {\n const config: VideoEncoderConfig = {\n codec: codecString,\n width,\n height,\n bitrate,\n framerate: frameRate,\n };\n\n const support = await VideoEncoder.isConfigSupported(config);\n if (support.supported) {\n return codecString;\n }\n } catch (error) {\n console.warn(\n `Failed to check support for AVC profile ${profile}:`,\n error,\n );\n }\n }\n\n return null;\n}\n\n/**\n * Convert audio codec name to WebCodecs string\n */\nfunction getAudioCodecString(codec: string): string {\n switch (codec) {\n case \"aac\":\n return \"mp4a.40.2\"; // AAC-LC\n case \"opus\":\n return \"opus\"; // Opus\n default:\n return codec; // Return as is (for custom codec strings)\n }\n}\n\n/**\n * Check support for specific codec and profile (for advanced users)\n */\nexport async function canEncodeWithProfile(\n videoCodec: string,\n audioCodec?: string,\n profile?: {\n width: number;\n height: number;\n framerate: number;\n videoBitrate: number;\n audioBitrate?: number;\n },\n): Promise<{ video: boolean; audio: boolean; overall: boolean }> {\n const result = { video: false, audio: false, overall: false };\n\n try {\n // Check video\n if (videoCodec) {\n const videoConfig: VideoEncoderConfig = {\n codec: getVideoCodecString(videoCodec),\n width: profile?.width || 1920,\n height: profile?.height || 1080,\n bitrate: profile?.videoBitrate || 2_000_000,\n framerate: profile?.framerate || 30,\n };\n\n const videoSupport = await VideoEncoder.isConfigSupported(videoConfig);\n result.video = videoSupport.supported || false;\n }\n\n // Check audio\n if (audioCodec) {\n const audioConfig: AudioEncoderConfig = {\n codec: getAudioCodecString(audioCodec),\n sampleRate: 48000,\n numberOfChannels: 2,\n bitrate: profile?.audioBitrate || 128_000,\n };\n\n const audioSupport = await AudioEncoder.isConfigSupported(audioConfig);\n result.audio = audioSupport.supported || false;\n } else {\n result.audio = true; // Consider supported if no audio\n }\n\n result.overall = result.video && result.audio;\n return result;\n } catch (error) {\n console.warn(\"canEncodeWithProfile error:\", error);\n return result;\n }\n}\n"],"mappings":"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","names":[]}