功能描述
本文主要介绍如何在 TRTC Web SDK 实现屏幕分享功能。
实现流程
-
【推流端】开启屏幕分享
const trtcA = TRTC.create(); await trtcA.enterRoom({ scene: 'rtc', sdkAppId: 140000000, // 填写您的 sdkAppId userId: 'userA', // 填写您的 userId userSig: 'userA_sig', // 填写 userId 对应的 userSig roomId: 6969 }) await trtcA.startScreenShare();
-
【拉流端】播放屏幕分享
const trtcB = TRTC.create(); trtcB.on(TRTC.EVENT.REMOTE_VIDEO_AVAILABLE, ({ userId, streamType }) => { // 主路视频流,一般是推摄像头的那路流 if (streamType === TRTC.TYPE.STREAM_TYPE_MAIN) { // 1. 在页面中放置一个 id 为 `${userId}_main` 的 div 标签,用于在 div 标签内播放主路流。业务侧可自定义 div 标签的 id,此处只是举例说明。 // 2. 播放主路视频流 trtcB.startRemoteVideo({ userId, streamType, view: `${userId}_main` }); } else { // 辅路视频流,一般是推屏幕分享的那路流。 // 1. 在页面中放置一个 id 为 `${userId}_screen` 的 div 标签,用于在 div 标签内播放屏幕分享。业务侧可自定义 div 标签的 id,此处只是举例说明。 // 2. 播放屏幕分享 trtcB.startRemoteVideo({ userId, streamType, view: `${userId}_screen` }); } }); await trtcB.enterRoom({ scene: 'rtc', sdkAppId: 140000000, // 填写您的 sdkAppId userId: 'userB', // 填写您的 userId userSig: 'userB_sig', // 填写 userId 对应的 userSig roomId: 6969 })
-
同时推摄像头 + 屏幕分享
await trtcA.startLocalVideo(); await trtcA.startScreenShare();
-
屏幕分享 + 系统音频
采集系统音频支持 Chrome M74+
- 在 Windows 和 Chrome OS 上,可以采集整个系统的音频。
- 在 Linux 和 Mac 上,只能采集某个页面的音频。
- 其它 Chrome 版本、其它系统、其它浏览器均不支持。
await trtcA.startScreenShare({ option: { systemAudio: true }});
在弹出的对话框中勾选
分享音频
,系统音频会与本地麦克风混音后发布,房间内其他用户会收到 TRTC.EVENT.REMOTE_AUDIO_AVALIABLE 事件 -
停止屏幕分享
// 停止屏幕分享采集及发布 await trtcA.stopScreenShare(); // 房间内的其他用户会收到 TRTC.EVENT.REMOTE_VIDEO_UNAVAILABLE 事件,streamType 是 TRTC.TYPE.STREAM_TYPE_SUB。 trtcB.on(TRTC.EVENT.REMOTE_VIDEO_UNAVAILABLE, ({ userId, streamType }) => { if (streamType === TRTC.TYPE.STREAM_TYPE_SUB) { } })
另外用户还可能会通过浏览器自带的按钮停止屏幕分享,因此屏幕分享流需要监听屏幕分享停止事件,并进行相应的处理。
// 监听屏幕分享停止事件 trtcA.on(TRTC.EVENT.SCREEN_SHARE_STOPPED, () => { console.log('screen sharing was stopped'); });
在 Electron 中屏幕分享
建议优先使用 TRTC Electron SDK
因为 Electron 未实现浏览器支持的 WebRTC 标准的 getDisplayMedia
接口,所以如果网页中包含了 WebRTC 屏幕分享相关的逻辑,将无法在 Electron 中像普通浏览器一样正常使用 TRTC Web SDK 进行屏幕分享。如果只想使用 TRTC Web SDK,请参考以下方案。
实现屏幕分享,需要使用到 Electron 的 API:desktopCapturer.getSources({ types: ['window', 'screen'] })
- 主进程
main.js
监听页面加载完成后,通过desktopCapturer.getSources
获取屏幕分享源列表,并将屏幕分享源列表发送给渲染进程 - 渲染进程监听主进程事件,从而获取屏幕分享源列表
- 创建屏幕分享流并通过 TRTC Web SDK 进行推流
- 通过
navigator.mediaDevices.getUserMedia()
从系统 API 获取屏幕分享的MediaStream
- 在
TRTC.startScreenShare()
的option
参数中传入自定义采集的 videoTrack,推屏幕分享流
- 通过
// 【1】main.js 主进程获取屏幕分享源列表
const { app, BrowserWindow, desktopCapturer, systemPreferences } = require('electron');
function createWindow () {
const win = new BrowserWindow({
// ...
});
win.loadFile('./src/index.html');
win.webContents.on("did-finish-load", async () => {
if (win) {
const sources = await desktopCapturer.getSources({
types: ["window", "screen"],
});
win.webContents.send("SEND_SCREEN_SHARE_SOURCES", sources);
}
});
}
// 【2】渲染进程监听主进程事件拿到屏幕分享源列表
const { ipcRenderer } = require('electron');
let shareSourceList = [];
ipcRenderer.on('SEND_SCREEN_SHARE_SOURCES', async (event, sources) => {
const selectContainer = window.document.getElementById('screen-share-select');
shareSourceList = sources;
sources.forEach(obj => {
const optionElement = document.createElement('option');
optionElement.innerText = `${obj.name}`;
selectContainer.appendChild(optionElement);
});
})
// 【3】渲染进程推屏幕分享
async function startScreenShare() {
const selectContainer = document.getElementById('screen-share-select');
const selectValue = selectContainer.options[selectContainer.selectedIndex].value;
const [ source ] = shareSourceList.filter(obj => obj.name === `${selectValue}`);
try {
const stream = await navigator.mediaDevices.getUserMedia({
audio: false,
video: {
mandatory: {
chromeMediaSource: 'desktop',
chromeMediaSourceId: source.id, // 屏幕分享源 id
minWidth: 1280,
maxWidth: 1280,
minHeight: 720,
maxHeight: 720
}
}
});
const trtc = TRTC.create();
await trtc.enterRoom({
// ...
});
await trtc.startScreenShare({
option: {
videoTrack: stream.getVideoTracks()[0],
}
})
} catch (error) {
console.error('start screen share error = ', error)
}
}
注意事项
- 什么是主流,辅流?
- SDK 默认使用
1080p
参数配置来采集屏幕分享,具体参考接口:startScreenShare
常见问题
-
Safari 屏幕分享出现报错
getDisplayMedia must be called from a user gesture handler
这是因为 Safari 限制了
getDisplayMedia
屏幕采集的接口,必须在用户点击事件的回调函数执行的 1 秒内才可以调用。参考:webkit issue。
// good async function onClick() { // 建议在 onClick 执行时,先执行采集逻辑 await trtcA.startScreenShare(); await trtcA.enterRoom({ roomId: 123123, sdkAppId: 140000000, // 填写您的 sdkAppId userId: 'userA', // 填写您的 userId userSig: 'userA_sig', // 填写 userId 对应的 userSig }); }); // bad async function onClick() { await trtcA.enterRoom({ roomId: 123123, sdkAppId: 140000000, // 填写您的 sdkAppId userId: 'userA', // 填写您的 userId userSig: 'userA_sig', // 填写 userId 对应的 userSig }); }) // 进房可能耗时超过 1s,可能会采集失败 await trtcA.startScreenShare(); }
-
macOS Monterey(12.2.1), 在 Electron 环境下使用屏幕分享需在主进程中请求设备权限
async function checkAndApplyDeviceAccessPrivilege() { const cameraPrivilege = systemPreferences.getMediaAccessStatus('camera'); console.log( `checkAndApplyDeviceAccessPrivilege before apply cameraPrivilege: ${cameraPrivilege}` ); if (cameraPrivilege !== 'granted') { await systemPreferences.askForMediaAccess('camera'); } const micPrivilege = systemPreferences.getMediaAccessStatus('microphone'); console.log( `checkAndApplyDeviceAccessPrivilege before apply micPrivilege: ${micPrivilege}` ); if (micPrivilege !== 'granted') { await systemPreferences.askForMediaAccess('microphone'); } const screenPrivilege = systemPreferences.getMediaAccessStatus('screen'); console.log( `checkAndApplyDeviceAccessPrivilege before apply screenPrivilege: ${screenPrivilege}` ); }
-
Mac Chrome 在已授权屏幕录制的情况下屏幕分享失败,出现 "NotAllowedError: Permission denied by system" 或者 "NotReadableError: Could not start video source" 错误信息,Chrome bug。解决方案:打开【设置】> 点击【安全性与隐私】> 点击【隐私】> 点击【屏幕录制】> 关闭 Chrome 屏幕录制授权 > 重新打开 Chrome 屏幕录制授权 > 关闭 Chrome 浏览器 > 重新打开 Chrome 浏览器。