在开发屏幕共享、在线录制这类Web功能时,Display Capture API是绕不开的核心接口。它通过navigator.mediaDevices.getDisplayMedia()方法让浏览器弹出一个选择窗口,用户授权后返回包含屏幕画面(有时还包含系统音频)的MediaStream。如果项目使用TypeScript,直接用any来接这个返回值虽然能跑通,但会失去编译期的类型保护,后续操作轨道、添加约束、释放资源时都可能出现运行时错误。本文详细讲解如何为屏幕采集的媒体流数据定义严谨的TypeScript类型。

一、理解Display Capture API的核心类型结构
Display Capture API并不复杂,它的核心是navigator.mediaDevices.getDisplayMedia()方法。这个方法接收一个DisplayMediaStreamOptions类型的参数,返回一个Promise<MediaStream>。在较新版本的TypeScript中,官方的lib.dom.d.ts已经内置了这些声明,可以直接使用:
// 内置声明的大致结构(lib.dom.d.ts)
interface MediaDevices {
getDisplayMedia(options?: DisplayMediaStreamOptions): Promise<MediaStream>;
}
interface DisplayMediaStreamOptions {
video?: boolean | MediaTrackConstraints;
audio?: boolean | MediaTrackConstraints;
// 一些浏览器还支持 monitorTypeSurfaces、preferCurrentTab 等扩展字段
}其中MediaStream本身是一个标准DOM接口,它包含的方法和属性在类型文件中都有完整定义,例如getVideoTracks()返回MediaStreamTrack[],getAudioTracks()同样返回轨道数组,active属性是一个布尔值表示流是否处于活跃状态。
需要特别注意的是MediaTrackConstraints与普通摄像头约束的区别。屏幕采集场景下,约束项中常用的有width、height、frameRate、displaySurface(指定采集显示器、窗口还是浏览器标签页)等。像displaySurface这类字段属于较新的规范,部分TypeScript版本可能没有收录,需要自行扩展。
二、自定义类型定义与扩展约束接口
当内置类型不满足需求,或者需要为业务层封装更明确的类型时,建议自己定义一套类型。首先是采集约束的扩展,可以基于MediaTrackConstraints做交叉类型扩展:
// 扩展屏幕采集的视频约束
interface DisplayVideoConstraints extends MediaTrackConstraints {
displaySurface?: 'monitor' | 'window' | 'browser';
logicalSurface?: boolean;
cursor?: 'always' | 'motion' | 'never';
}
// 屏幕采集选项
interface ScreenCaptureOptions {
video: DisplayVideoConstraints | boolean;
audio?: boolean | MediaTrackConstraints;
preferCurrentTab?: boolean;
}
// 业务层的采集结果封装
interface ScreenCaptureResult {
stream: MediaStream;
videoTrack: MediaStreamTrack | null;
audioTrack: MediaStreamTrack | null;
}这样定义的好处是把浏览器的扩展能力显式写进了类型系统。比如displaySurface限定了三个字面量联合类型,一旦传错字符串,编译阶段就会报错,而不是等到运行时才发现约束被浏览器忽略。
接着可以封装一个类型安全的采集函数,把Promise的返回值处理成结构化的ScreenCaptureResult,让调用方拿到流的同时就拿到了轨道引用,方便后续监听结束事件和手动停止采集:
async function captureScreen(
options: ScreenCaptureOptions
): Promise<ScreenCaptureResult> {
const stream = await navigator.mediaDevices.getDisplayMedia(options);
const videoTrack = stream.getVideoTracks()[0] ?? null;
const audioTrack = stream.getAudioTracks()[0] ?? null;
// 用户点击浏览器自带的停止共享按钮时触发
videoTrack?.addEventListener('ended', () => {
console.log('屏幕共享已结束');
});
return { stream, videoTrack, audioTrack };
}这里用?? null处理轨道可能不存在的情况,是屏幕采集中很常见的场景——用户可能选择不共享系统音频,音频轨道就是空的。类型上明确标注MediaStreamTrack | null,强制调用方在使用前做空值判断,避免不必要的运行时异常。
三、常见类型问题与解决方案
第一个常见问题是旧版TypeScript没有getDisplayMedia的声明,编译器直接报“属性不存在于MediaDevices上”的错误。解决办法是在项目中添加一个声明文件,例如display-capture.d.ts,通过接口合并(declaration merging)补充缺失的方法:
// display-capture.d.ts
interface MediaDevices {
getDisplayMedia(
constraints: DisplayMediaStreamOptions
): Promise<MediaStream>;
}第二个问题是访问轨道上不存在于标准类型中的属性。有些业务需要判断轨道设置里的displaySurface实际值,可以先用getSettings()获取设置对象,再通过类型断言或自定义的Settings扩展接口来访问:
interface DisplayTrackSettings extends MediaTrackSettings {
displaySurface?: string;
logicalSurface?: boolean;
cursor?: string;
}
function readDisplaySurface(track: MediaStreamTrack): string {
const settings = track.getSettings() as DisplayTrackSettings;
return settings.displaySurface ?? 'unknown';
}类型断言要谨慎使用,这里断言的前提是规范保证浏览器会在设置中回填这些字段。如果对兼容性要求高,建议加上in运算符的运行时检查,双保险。
第三个问题是停止采集时的资源释放。很多写法直接stream.getTracks().forEach(track => track.stop()),这段代码本身类型没问题,但更好的做法是把停止逻辑也纳入类型封装,例如给ScreenCaptureResult增加一个stop方法,或者封装独立的stopCapture(result: ScreenCaptureResult)函数,保证调用方只能通过定义好的入口释放资源,降低轨道泄漏导致摄像头或屏幕共享指示灯常亮的概率。
四、完整的类型安全采集模块示例
最后把上述内容整合成一个可以直接复用的模块,包含类型定义、采集函数、停止函数以及结束事件的类型化回调:
export interface ScreenCaptureResult {
stream: MediaStream;
videoTrack: MediaStreamTrack | null;
audioTrack: MediaStreamTrack | null;
stop: () => void;
}
export async function startScreenCapture(
onEnded: () => void
): Promise<ScreenCaptureResult> {
const stream = await navigator.mediaDevices.getDisplayMedia({
video: {
width: { ideal: 1920 },
height: { ideal: 1080 },
frameRate: { ideal: 30 },
},
audio: false,
});
const videoTrack = stream.getVideoTracks()[0] ?? null;
videoTrack?.addEventListener('ended', onEnded);
const stop = (): void => {
stream.getTracks().forEach((track) => track.stop());
onEnded();
};
return { stream, videoTrack, audioTrack: null, stop };
}这个模块把约束、轨道访问、事件监听、资源释放全部收敛到类型清晰的接口后面,业务代码只需要处理ScreenCaptureResult,不需要直接接触底层的DOM细节。
总的来说,为Display Capture API定义类型的核心思路是:优先使用lib.dom.d.ts内置的MediaStream和MediaStreamTrack,对规范中较新的字段通过接口扩展和声明合并补齐,再结合业务需要封装出结构化的结果类型。这样既保留了类型检查的严格性,又为将来规范演进留下了灵活的扩展空间。
TypeScriptDisplay Capture API媒体流数据类型修改时间:2026-09-02 21:47:08