在 TypeScript 项目中使用 MediaRecorder 录制视频时,videoScalingMode 字段可以控制输出帧在宽高比不匹配时的处理方式。如果把这个字段声明成 string,编译器就无法阻止开发者传入 crop、fill、none 之外的字符串,运行时可能出现录制画面变形或裁剪错误。因此,把值域收窄为明确的联合类型是保证配置安全的第一步。

下面从类型来源、接口扩展、运行时校验以及实际策略几个维度,梳理在 TypeScript 中定义 MediaRecorder 视频缩放模式类型的具体做法。
Video Scaling Mode 的取值来源与类型定义
浏览器的 MediaRecorder 标准至今没有把 videoScalingMode 纳入正式规范,但在一些基于 Chromium 的环境以及部分原生封装中,这个选项已经被广泛使用。它的作用是在输出视频帧时决定如何处理原始画面与目标尺寸不一致的情况。常见取值中,crop 表示保持原始宽高比并裁剪多余区域,fill 表示拉伸画面以完全填充目标尺寸,none 则关闭缩放处理。
对于 TypeScript 开发者来说,第一步就是把这三个字符串字面量组合成联合类型。这样做之后,任何赋值错误都会在编译阶段暴露出来,而不是等到用户实际录制时才发现。同时,联合类型也能让编辑器的自动补全主动提示可选值,减少翻阅文档的成本。
type VideoScalingMode = 'crop' | 'fill' | 'none';
这里使用字符串字面量类型而不是枚举,原因是字符串字面量类型可以直接赋值给 DOM 标准中已有的字符串字段,不需要额外的运行时代码。如果未来浏览器新增取值,只需要扩展这个联合类型即可。需要注意,不要把它声明成 string,否则联合类型的约束力会完全消失。
通过接口扩展承载 videoScalingMode 字段
MediaRecorderOptions 是 TypeScript DOM 库中定义的标准接口,默认只包含 mimeType、audioBitsPerSecond、videoBitsPerSecond 等字段。要加入 videoScalingMode,可以直接创建一个继承 MediaRecorderOptions 的新接口。这种方式不会修改全局类型,适合在局部作用域内使用。
interface MediaRecorderOptionsWithScaling extends MediaRecorderOptions {
videoScalingMode?: VideoScalingMode;
}
function createRecorder(
stream: MediaStream,
options: MediaRecorderOptionsWithScaling
): MediaRecorder {
return new MediaRecorder(stream, options);
}
如果整个项目都需要这个字段,则可以使用模块增强。TypeScript 的声明合并机制允许在全局命名空间中扩展已有接口。在 .d.ts 文件或者含有 declare global 的模块中声明一次,所有引用该类型的代码都能获得更新后的成员。
declare global {
interface MediaRecorderOptions {
videoScalingMode?: VideoScalingMode;
}
}
export {};
这段代码必须放在模块文件中,并且保留 export {} 以让 TypeScript 将文件视为模块。否则 declare global 可能无法正确合并。扩展之后,构造函数和 createRecorder 都能直接接收 videoScalingMode,不需要在业务代码里重复声明。
用类型守卫阻止运行时非法值
类型定义只对编译期有效。当配置数据来自用户输入、服务端下发或反序列化结果时,运行时仍然可能拿到不符合联合类型的字符串。为了防止非法的 videoScalingMode 传入 MediaRecorder,需要增加一层运行时校验。
类型守卫函数可以同时完成值检查和类型收窄。函数接收 unknown 类型,返回布尔值,并在返回 true 时把参数类型收窄为 VideoScalingMode。这样在调用 MediaRecorder 之前就能安全地使用该值。
const VALID_MODES: ReadonlyArray<VideoScalingMode> = ['crop', 'fill', 'none'];
function isVideoScalingMode(value: unknown): value is VideoScalingMode {
return typeof value === 'string' && VALID_MODES.includes(value as VideoScalingMode);
}
function safeCreateRecorder(
stream: MediaStream,
options: MediaRecorderOptionsWithScaling
): MediaRecorder | null {
if (options.videoScalingMode !== undefined && !isVideoScalingMode(options.videoScalingMode)) {
console.warn('非法的视频缩放模式,已回退为默认值');
return null;
}
return new MediaRecorder(stream, options);
}
这里使用 ReadonlyArray 来约束合法值集合,includes 方法在传入 string 时也能够进行遍历判断。如果需要在失败时回退到默认值,可以进一步修改逻辑,例如删除非法字段后再构造实例。这样既保留了类型约束,也保证了运行时安全。
裁剪与填充模式在录制场景中的实际差异
视频缩放模式的选择会直接影响录制文件的画面内容。crop 模式会保持原始画面的宽高比,然后从中心区域裁剪掉超出目标比例的部分,适合不希望画面变形的场景,比如教程录制或人物采访。fill 模式会强制拉伸画面填满整个输出框,如果原始比例与目标比例相差较大,就会出现明显的横向或纵向拉伸。
在实际接入时,建议先读取原始流的宽高和最终输出尺寸,再决定使用哪种模式。例如,用 getSettings 获取 width 和 height,计算比例差。如果比例差在可接受范围内,可以用 fill 来保证无黑边;如果比例差过大,则使用 crop 减少视觉扭曲。
function chooseScalingMode(
inputWidth: number,
inputHeight: number,
outputWidth: number,
outputHeight: number
): VideoScalingMode {
const inputRatio = inputWidth / inputHeight;
const outputRatio = outputWidth / outputHeight;
const diff = Math.abs(inputRatio - outputRatio);
return diff > 0.05 ? 'crop' : 'fill';
}
这个示例根据宽高比差异动态选择模式,diff 大于 0.05 时认为比例差异较大,使用 crop 保留画面主体;否则使用 fill 填满输出。生产环境中还可以结合用户偏好或设备性能做进一步调整。注意这里展示的是业务层策略,MediaRecorder 本身并不会自动根据比例切换模式。
兼容性处理与类型声明沉淀
由于 videoScalingMode 并非所有浏览器都支持,项目在引入类型增强时应同时考虑特性检测。可以先用 typeof MediaRecorder === 'undefined' 判断 API 是否存在,再检查 options 中是否包含 videoScalingMode。某些旧版本浏览器可能忽略未知配置项,但为了行为一致,最好在初始化前做显式处理。
把类型声明集中到一个 d.ts 文件中是最稳妥的沉淀方式。例如在项目根目录创建 media-recorder-scaling.d.ts,内容包含 VideoScalingMode 类型和 declare global 接口扩展。然后确保 tsconfig.json 的 include 数组包含该文件。这样所有业务代码都能共享这份声明,不需要重复定义。
// media-recorder-scaling.d.ts
export type VideoScalingMode = 'crop' | 'fill' | 'none';
declare global {
interface MediaRecorderOptions {
videoScalingMode?: VideoScalingMode;
}
}
如果项目已经使用了某个第三方类型包并且该包尚未包含 videoScalingMode,也可以通过同样的声明合并方式补全。只要接口名称一致,TypeScript 就会自动合并成员,不影响原有类型定义的正常使用。

