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.
342 lines (331 loc) • 11.3 kB
text/typescript
type Frame = VideoFrame | HTMLCanvasElement | OffscreenCanvas | ImageBitmap | ImageData;
interface VideoFile {
file: File | Blob;
type: string;
}
type VideoSource = Frame[] | AsyncIterable<Frame> | MediaStream | VideoFile;
type QualityPreset = 'low' | 'medium' | 'high' | 'lossless';
interface VideoConfig {
codec?: 'avc' | 'hevc' | 'vp9' | 'vp8' | 'av1';
bitrate?: number;
hardwareAcceleration?: 'no-preference' | 'prefer-hardware' | 'prefer-software';
latencyMode?: 'quality' | 'realtime';
keyFrameInterval?: number;
}
interface AudioConfig {
codec?: 'aac' | 'opus';
bitrate?: number;
sampleRate?: number;
channels?: number;
bitrateMode?: 'constant' | 'variable';
}
interface ProgressInfo {
percent: number;
processedFrames: number;
totalFrames?: number;
fps: number;
stage: string;
estimatedRemainingMs?: number;
}
interface EncodeOptions {
width?: number;
height?: number;
frameRate?: number;
quality?: QualityPreset;
video?: VideoConfig | false;
audio?: AudioConfig | false;
container?: 'mp4' | 'webm';
onProgress?: (progress: ProgressInfo) => void;
onError?: (error: EncodeError) => void;
}
type EncodeErrorType = 'not-supported' | 'initialization-failed' | 'configuration-error' | 'invalid-input' | 'encoding-failed' | 'video-encoding-error' | 'audio-encoding-error' | 'muxing-failed' | 'cancelled' | 'timeout' | 'worker-error' | 'filesystem-error' | 'unknown';
declare class EncodeError extends Error {
type: EncodeErrorType;
cause?: unknown;
constructor(type: EncodeErrorType, message: string, cause?: unknown);
}
interface EncoderConfig {
width: number;
height: number;
frameRate: number;
videoBitrate: number;
audioBitrate: number;
/**
* Controls bitrate distribution for AAC. "constant" produces constant
* bitrate (CBR) output while "variable" enables variable bitrate (VBR).
* Not all browsers respect this setting. Chrome 119+ improves CBR support.
*/
audioBitrateMode?: "constant" | "variable";
sampleRate: number;
channels: number;
container?: "mp4" | "webm";
codec?: {
video?: "avc" | "hevc" | "vp9" | "vp8" | "av1";
audio?: "aac" | "opus";
};
/**
* Optional codec string overrides passed directly to the encoders.
* For example: `{ video: 'avc1.640028', audio: 'mp4a.40.2' }`.
*/
codecString?: {
video?: string;
audio?: string;
};
latencyMode?: "quality" | "realtime";
/** Preference for hardware or software encoding. */
hardwareAcceleration?: "prefer-hardware" | "prefer-software" | "no-preference";
/** Drop new video frames when the number of queued frames exceeds `maxQueueDepth`. */
dropFrames?: boolean;
/** Maximum number of queued video frames before dropping. Defaults to `Infinity`. */
maxQueueDepth?: number;
/** Total frames for progress calculation if known in advance. */
totalFrames?: number;
/** Force a key frame every N video frames. */
keyFrameInterval?: number;
/**
* How to handle the first timestamp of a track.
* 'offset': Offsets all timestamps so the first one is 0.
* 'strict': Requires the first timestamp to be 0 (default).
*/
firstTimestampBehavior?: "offset" | "strict";
/** Additional VideoEncoder configuration overrides. */
videoEncoderConfig?: Partial<VideoEncoderConfig>;
/** Additional AudioEncoder configuration overrides. */
audioEncoderConfig?: Partial<AudioEncoderConfig>;
}
declare enum ProcessingStage {
Initializing = "initializing",
VideoEncoding = "video-encoding",
AudioEncoding = "audio-encoding",
Muxing = "muxing",
Finalizing = "finalizing"
}
declare enum EncoderErrorType {
NotSupported = "not-supported",
InitializationFailed = "initialization-failed",
ConfigurationError = "configuration-error",
EncodingFailed = "encoding-failed",// Generic encoding error
VideoEncodingError = "video-encoding-error",// Specific video encoding error
AudioEncodingError = "audio-encoding-error",// Specific audio encoding error
MuxingFailed = "muxing-failed",
Cancelled = "cancelled",
Timeout = "timeout",
InternalError = "internal-error",
WorkerError = "worker-error",
ValidationError = "validation-error"
}
interface InitializeWorkerMessage {
type: "initialize";
config: EncoderConfig;
totalFrames?: number;
}
interface AddVideoFrameMessage {
type: "addVideoFrame";
frame: VideoFrame;
timestamp: number;
}
interface AddAudioDataMessage {
type: "addAudioData";
audioData?: Float32Array[];
/** Optional AudioData object to be encoded directly. */
audio?: AudioData;
timestamp: number;
format: AudioSampleFormat;
sampleRate: number;
numberOfFrames: number;
numberOfChannels: number;
}
interface FinalizeWorkerMessage {
type: "finalize";
}
interface CancelWorkerMessage {
type: "cancel";
}
type WorkerMessage = InitializeWorkerMessage | AddVideoFrameMessage | AddAudioDataMessage | FinalizeWorkerMessage | CancelWorkerMessage;
interface WorkerInitializedMessage {
type: "initialized";
actualVideoCodec?: string;
actualAudioCodec?: string;
}
interface ProgressMessage {
type: "progress";
processedFrames: number;
totalFrames?: number;
}
interface WorkerFinalizedMessage {
type: "finalized";
output: Uint8Array | null;
}
interface QueueSizeMessage {
type: "queueSize";
videoQueueSize: number;
audioQueueSize: number;
}
interface WorkerDataChunkMessage {
type: "dataChunk";
chunk: Uint8Array;
isHeader?: boolean;
offset?: number;
container: "mp4" | "webm";
}
interface WorkerErrorMessage {
type: "error";
errorDetail: {
message: string;
type: EncoderErrorType;
stack?: string;
};
}
interface WorkerCancelledMessage {
type: "cancelled";
}
type MainThreadMessage = WorkerInitializedMessage | ProgressMessage | WorkerFinalizedMessage | QueueSizeMessage | WorkerDataChunkMessage | WorkerErrorMessage | WorkerCancelledMessage;
type VideoEncoderConstructor = typeof VideoEncoder;
type AudioEncoderConstructor = typeof AudioEncoder;
type AudioDataConstructor = typeof AudioData;
type VideoEncoderGetter = () => VideoEncoderConstructor | undefined;
type AudioEncoderGetter = () => AudioEncoderConstructor | undefined;
type AudioDataGetter = () => AudioDataConstructor | undefined;
/**
* コアエンコード関数の実装
*/
/**
* ビデオエンコードのメイン関数
*
* @param source エンコードするビデオソース
* @param options エンコードオプション
* @returns エンコードされたバイナリデータ
*/
declare function encode(source: VideoSource, options?: EncodeOptions): Promise<Uint8Array>;
/**
* ストリーミングエンコード関数の実装
*/
/**
* ストリーミングエンコード関数
*
* @param source エンコードするビデオソース
* @param options エンコードオプション
* @returns エンコードされたチャンクのAsyncGenerator
*/
declare function encodeStream(source: VideoSource, options?: EncodeOptions): AsyncGenerator<Uint8Array>;
/**
* エンコード可能性の検証
*/
/**
* エンコード可能性の検証
*
* @param options エンコードオプション
* @returns エンコード可能かどうか
*/
declare function canEncode(options?: EncodeOptions): Promise<boolean>;
/**
* カスタムエンコーダーファクトリ
*/
/**
* エンコーダー関数のファクトリ
* 設定を事前に部分適用した専用エンコーダー関数を作成
*/
interface EncoderFactory {
/**
* ワンショットエンコード
*/
encode(source: VideoSource, additionalOptions?: Partial<EncodeOptions>): Promise<Uint8Array>;
/**
* ストリーミングエンコード
*/
encodeStream(source: VideoSource, additionalOptions?: Partial<EncodeOptions>): AsyncGenerator<Uint8Array>;
/**
* 設定された設定を取得
*/
getConfig(): EncodeOptions;
/**
* 新しい設定でファクトリを拡張
*/
extend(newOptions: Partial<EncodeOptions>): EncoderFactory;
}
/**
* カスタムエンコーダーファクトリを作成
*
* @param baseOptions 基本エンコードオプション
* @returns 設定済みエンコーダーファクトリ
*/
declare function createEncoder(baseOptions?: EncodeOptions): EncoderFactory;
/**
* 事前定義されたエンコーダーファクトリ
*/
declare const encoders: {
/**
* YouTube向け高品質エンコーダー
*/
youtube: EncoderFactory;
/**
* Twitter向け最適化エンコーダー
*/
twitter: EncoderFactory;
/**
* Discord向け最適化エンコーダー
*/
discord: EncoderFactory;
/**
* Web再生向けバランス型エンコーダー
*/
web: EncoderFactory;
/**
* 軽量・高速エンコーダー
*/
fast: EncoderFactory;
/**
* 高品質・低圧縮エンコーダー
*/
lossless: EncoderFactory;
/**
* VP9ストリーミング用エンコーダー
*/
vp9Stream: EncoderFactory;
};
/**
* 使用例とヘルパー関数
*/
declare const examples: {
/**
* プラットフォーム別のエンコーダーを取得
*/
getEncoderForPlatform(platform: "youtube" | "twitter" | "discord" | "web"): EncoderFactory;
/**
* 解像度ベースのエンコーダーを作成
*/
createByResolution(width: number, height: number): EncoderFactory;
/**
* ファイルサイズ制約ベースのエンコーダーを作成
*/
createForFileSize(targetSizeMB: number, durationSeconds: number): EncoderFactory;
};
interface MediaStreamRecorderOptions extends EncodeOptions {
/** 最初のタイムスタンプの処理方法 */
firstTimestampBehavior?: "offset" | "strict";
}
declare class MediaStreamRecorder {
private options;
private communicator;
private videoReader?;
private audioReader?;
private videoTrack?;
private audioTrack?;
private audioSource?;
private recording;
private onErrorCallback?;
private onProgressCallback?;
private config;
constructor(options?: MediaStreamRecorderOptions);
static isSupported(): boolean;
startRecording(stream: MediaStream, additionalOptions?: Partial<MediaStreamRecorderOptions>): Promise<void>;
private initializeWorker;
private processVideo;
private processAudio;
stopRecording(): Promise<Uint8Array | null>;
cancel(): void;
private cleanup;
getActualVideoCodec(): string | null;
getActualAudioCodec(): string | null;
}
export { type AudioConfig, type AudioDataGetter, type AudioEncoderGetter, EncodeError, type EncodeErrorType, type EncodeOptions, type EncoderConfig, EncoderErrorType, type EncoderFactory, type Frame, type MainThreadMessage, MediaStreamRecorder, ProcessingStage, type ProgressInfo, type QualityPreset, type VideoConfig, type VideoEncoderGetter, type VideoFile, type VideoSource, type WorkerMessage, canEncode, createEncoder, encode, encodeStream, encoders, examples };