6、屏幕共享:getDisplayMedia详解、屏幕共享权限、屏幕共享与摄像头切换、录制屏幕流
屏幕共享,说白了就是把你的电脑屏幕内容,实时传给对方。这在远程会议、在线教育、游戏直播里太常见了。我最早接触这个功能时,以为就是简单调个API,结果踩了不少坑。今天咱们就把这块彻底讲透。
6.1 getDisplayMedia 详解
核心API就一个:navigator.mediaDevices.getDisplayMedia()。它和getUserMedia很像,但抓的是屏幕,不是摄像头。
关键区别:getDisplayMedia 不需要用户提前授权,浏览器会弹出一个选择窗口,让用户自己选「分享整个屏幕」还是「分享某个窗口」。
来看个最基础的调用:
async function startScreenShare() {
try {
const stream = await navigator.mediaDevices.getDisplayMedia({
video: {
cursor: "always" // 显示鼠标指针
},
audio: false // 默认不采集系统声音
});
// 把流赋给 video 元素
const video = document.getElementById("screenVideo");
video.srcObject = stream;
} catch (err) {
console.error("屏幕共享失败:", err);
}
}
嗯,这里要注意:audio参数虽然可以传true,但浏览器支持很有限。我在项目中试过,Chrome 能采集系统音频,但 Firefox 直接忽略。所以别指望它做录屏的音频源。
6.2 屏幕共享权限
权限这块和摄像头不太一样。摄像头需要提前申请权限,屏幕共享是「按需授权」——每次调用getDisplayMedia,浏览器都会弹出选择框。
我曾经遇到一个坑:用户第一次选了「整个屏幕」,第二次想只分享某个窗口,结果浏览器直接用了上次的选择。解决办法是每次调用前,先停止之前的流:
let screenStream = null;
async function startScreenShare() {
// 先清理旧的流
if (screenStream) {
screenStream.getTracks().forEach(track => track.stop());
}
screenStream = await navigator.mediaDevices.getDisplayMedia({
video: true
});
}
注意:用户随时可以点击浏览器地址栏旁边的「停止共享」按钮。你需要监听 track 的 ended 事件,做相应的 UI 处理。
screenStream.getVideoTracks()[0].addEventListener("ended", () => {
console.log("用户停止了屏幕共享");
// 切换回摄像头或显示提示
});
6.3 屏幕共享与摄像头切换
实际场景中,用户经常需要在「共享屏幕」和「显示摄像头」之间切换。比如开会时,先共享PPT,然后切回自己的脸。
我的做法是维护两个流:一个屏幕流,一个摄像头流。切换时,把对应的流赋给 video 元素:
let cameraStream = null;
let screenStream = null;
async function switchToCamera() {
if (screenStream) {
screenStream.getTracks().forEach(track => track.stop());
screenStream = null;
}
if (!cameraStream) {
cameraStream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true
});
}
document.getElementById("mainVideo").srcObject = cameraStream;
}
async function switchToScreen() {
if (cameraStream) {
cameraStream.getTracks().forEach(track => track.stop());
cameraStream = null;
}
screenStream = await navigator.mediaDevices.getDisplayMedia({
video: true
});
document.getElementById("mainVideo").srcObject = screenStream;
}
小技巧:切换时不要直接替换流,而是先停止旧流的所有 track,再赋新流。否则浏览器可能会保留旧的资源占用。
6.4 录制屏幕流
录制屏幕流,说白了就是把getDisplayMedia拿到的流,喂给MediaRecorder。这个组合我用了很多次,录课程、录bug复现过程,都很方便。
核心代码:
let mediaRecorder = null;
let recordedChunks = [];
function startRecording(stream) {
recordedChunks = [];
// 指定编码格式,我一般用 video/webm
mediaRecorder = new MediaRecorder(stream, {
mimeType: "video/webm;codecs=vp9"
});
mediaRecorder.ondataavailable = (event) => {
if (event.data.size > 0) {
recordedChunks.push(event.data);
}
};
mediaRecorder.onstop = () => {
const blob = new Blob(recordedChunks, {
type: "video/webm"
});
const url = URL.createObjectURL(blob);
// 可以下载或预览
const a = document.createElement("a");
a.href = url;
a.download = "screen-recording.webm";
a.click();
};
mediaRecorder.start(1000); // 每秒生成一个 chunk
}
function stopRecording() {
if (mediaRecorder && mediaRecorder.state !== "inactive") {
mediaRecorder.stop();
}
}
你想想看,这里有个细节:mimeType 如果浏览器不支持,会抛异常。我建议先检测:
const mimeTypes = [
"video/webm;codecs=vp9",
"video/webm;codecs=vp8",
"video/webm"
];
function getSupportedMimeType() {
for (const type of mimeTypes) {
if (MediaRecorder.isTypeSupported(type)) {
return type;
}
}
return "video/webm"; // 兜底
}
知识体系图
下面这张图,把屏幕共享的核心流程串起来了:
常见问题与避坑
| 问题 | 原因 | 解决方案 |
|---|---|---|
| getDisplayMedia 返回 null | 用户取消了选择 | 捕获异常,提示用户重新操作 |
| 录制没有声音 | audio: true 不一定生效 | 单独用 getUserMedia 采集麦克风,混流后录制 |
| 切换时画面卡住 | 旧流没有完全停止 | 遍历所有 track 调用 stop() |
| 录制的视频无法播放 | mimeType 浏览器不支持 | 用 isTypeSupported 检测 |
曾经踩过的坑:有一次用户共享了高分辨率屏幕(4K),结果录制的视频文件巨大,而且播放卡顿。后来我加了分辨率限制:getDisplayMedia({ video: { width: 1920, height: 1080 } }),问题解决。
屏幕共享这块,说白了就是「获取流 → 传输或录制」。但每个环节都有小陷阱。我个人习惯是先把getDisplayMedia的异常处理写全,再考虑功能扩展。毕竟用户取消共享、浏览器不支持、权限被拒,这些情况都得兜住。
嗯,今天就聊到这儿。代码示例可以直接复制去用,但记得根据你的场景调整参数。
公众号:蓝海资料掘金营,微信deep3321