一、MediaRecorder的码率类型缺口
原生的MediaRecorder API在浏览器端扮演着音视频采集与封装的核心角色。TypeScript的标准库lib.dom.d.ts为它提供了相当完整的类型定义,包括MediaRecorderOptions接口。打开类型声明可以看到,常见的可用字段有mimeType、audioBitsPerSecond、videoBitsPerSecond等。但是如果你尝试在创建实例时传入videoBitrateMode字段,在某些TypeScript版本或项目配置中会直接得到类型错误,因为标准库的定义滞后于部分浏览器的实际实现。
比如下面这段代码,在部分环境中会提示videoBitrateMode不存在于MediaRecorderOptions:
const stream = await navigator.mediaDevices.getDisplayMedia({ video: true });
const recorder = new MediaRecorder(stream, {
mimeType: 'video/webm;codecs=vp9',
videoBitsPerSecond: 2_500_000,
videoBitrateMode: 'constant' // 这里可能会报类型错误
});
上面代码中的videoBitrateMode虽然已经被Chromium内核浏览器支持,但在标准类型定义中暂时缺失。Firefox和Safari的MediaRecorder实现也会忽略未知字段,因此不能只靠修改标准库,还需要做类型扩展与运行时兼容检测。

二、通过声明合并补齐videoBitrateMode类型
TypeScript的interface支持声明合并,这是给第三方或标准库类型补充字段最轻量的方式。我们可以在项目的global.d.ts或专门类型声明文件中扩展MediaRecorderOptions。代码如下:
export {};
declare global {
type VideoBitrateMode = 'constant' | 'variable';
interface MediaRecorderOptions {
videoBitrateMode?: VideoBitrateMode;
audioBitrateMode?: VideoBitrateMode;
}
}
注意这里使用了declare global。如果文件本身是模块,直接写interface MediaRecorderOptions会变成模块内部的接口,而不会合并到全局。加上export {}把文件变成模块,再用declare global包裹即可生效。
定义成联合类型的好处是编译期就能挡住拼写错误,比如写成了constnat,TypeScript会立刻报错。如果未来浏览器新增了其他模式,也可以方便地在联合类型中继续扩展。
三、封装支持码率切换的工厂函数
类型扩展只是第一步,实际项目中更推荐封装一个createMediaRecorder工厂函数,统一处理码率模式设置和能力检测。恒定码率CBR适合实时传输,因为输出码率波动小,推流不会因为画面复杂度变化导致带宽突增;动态码率VBR则在保证画质的前提下尽可能压缩体积,适合离线录制。
下面是一个类型安全的封装实现:
type VideoBitrateMode = 'constant' | 'variable';
interface BitrateRecorderOptions extends MediaRecorderOptions {
videoBitrateMode?: VideoBitrateMode;
}
function createBitrateRecorder(
stream: MediaStream,
mode: VideoBitrateMode,
videoBitsPerSecond?: number
): MediaRecorder {
const options: BitrateRecorderOptions = {
videoBitsPerSecond: videoBitsPerSecond ?? 2_500_000,
};
if ('videoBitrateMode' in MediaRecorder.prototype) {
options.videoBitrateMode = mode;
} else if (mode === 'constant') {
// 不支持动态模式时,恒定码率只能通过限制videoBitsPerSecond近似实现
options.videoBitsPerSecond = videoBitsPerSecond ?? 2_500_000;
}
return new MediaRecorder(stream, options);
}
工厂函数内部用in运算符检测MediaRecorder.prototype是否包含videoBitrateMode属性,避免在不支持的浏览器上设置未定义字段。回退逻辑里,即便浏览器不支持VBR模式,设置一个固定的videoBitsPerSecond也能保证编码器尽量维持目标码率。
如果你需要在录制过程中动态切换CBR和VBR,MediaRecorder本身不支持直接修改编码参数。建议的实践是停止当前录制,利用ondataavailable收集数据,然后重新创建MediaRecorder实例切换模式。对于长任务录制,可以将分段数据合并,但需要注意时间戳连续性。
四、兼容性策略与类型安全检查
不同浏览器对videoBitrateMode的支持差异较大。Chromium内核的浏览器从较新版本开始支持该选项,但Safari和Firefox的MediaRecorder实现可能会忽略未知字段。利用TypeScript的类型扩展,我们可以在编译阶段保持一致的类型提示,而在运行时通过能力检测做降级。
除了in检测,还可以定义一个类型守卫函数,区分支持码率模式的RecorderOptions:
function supportsBitrateMode(): boolean {
return typeof MediaRecorder !== 'undefined' &&
'videoBitrateMode' in MediaRecorder.prototype;
}
function assertVideoBitrateMode(
options: MediaRecorderOptions
): options is BitrateRecorderOptions {
return supportsBitrateMode();
}
这样在调用前可以用if语句确保类型收窄。不过要清楚,TypeScript的类型守卫并不能改变运行时行为,它只是帮助编译器推断。
另一个容易踩的坑是,videoBitrateMode选项必须和videoBitsPerSecond配合使用,否则浏览器可能忽略该模式设置。因此封装时最好把两个值放在一起,或者提供默认值。如果只设置mode而不设置videoBitsPerSecond,某些Chromium版本下编码器会使用默认码率,不一定按constant模式约束。
TypeScriptMediaRecorder码率切换修改时间:2026-09-25 14:47:44