屏幕分享

屏幕分享

功能描述

本文主要介绍如何在 TRTC Web SDK 实现屏幕分享功能。

前提条件

  1. 本文介绍的教程基于 TRTC Web SDK v4.15.0+ 版本,若您的 SDK 低于该版本,可以参考旧版教程

    • 在 v4.15.0+ 版本,SDK 支持一个 Client 同时推主流 + 辅流,即可在一个 Client 同时推摄像头 + 屏幕分享流。
    • 在 v4.15.0 之前的版本,若需要同时推摄像头 + 屏幕分享,需要单独创建额外的 Client 来推屏幕分享流。
  2. TRTC Web SDK 屏幕分享支持度请查看 浏览器支持情况。同时 SDK 提供 TRTC.isScreenShareSupported 接口判断当前浏览器是否支持屏幕分享。

  3. 推辅流支持 Chrome 69+, Safari 11+, Firefox 59+, Edge 79+,只要是支持推屏幕分享的环境,都支持推辅流。

什么是主流和辅流?

在 TRTC Web SDK 中,一个 Client 实例可以同时推两路流,其中:

  • 一路称为主流(主音视频流),一般用于推摄像头和麦克风,也可以推业务侧自定义采集的流。
  • 一路称为辅流(辅助视频流),用于推屏幕分享流。

实现流程

  1. 【推流端】创建和发布屏幕分享流

    const clientA = TRTC.createClient({
      mode: 'rtc',
      sdkAppId: 140000000, // 填写您的 sdkAppId
      userId: 'userA', // 填写您的 userId
      userSig: 'userA_sig', // 填写 userId 对应的 userSig
    })
    await clientA.join({ roomId: 6969 });
    const shareStream = TRTC.createStream({ 
      userId: 'userA', 
      screen: true, // 采集屏幕分享
    })
    await shareStream.initialize();
    await clientA.publish(shareStream, { isAuxiliary: true }); // 将 isAuxiliary 设为 true,将以辅流的形式进行推流。
    
  2. 【拉流端】拉流并播放

    const clientB = TRTC.createClient({
      mode: 'rtc',
      sdkAppId: 140000000, // 填写您的 sdkAppId
      userId: 'userB', // 填写您的 userId
      userSig: 'userB_sig', // 填写 userId 对应的 userSig
    })
    clientB.on('stream-added', async event => {
      const remoteStream = event.stream;
      // 订阅远端流
      await clientB.subscribe(remoteStream);
      // 主路视频流,一般是推麦克风、摄像头的那路流
      if (remoteStream.getType() === 'main') {
        // 1. 在页面中放置一个 id 为 `${remoteStream.getUserId()}_main` 的 div 标签,用于在 div 标签内播放主路流。业务侧可自定义 div 标签的 id,此处只是举例说明。
        // 2. 播放主路视频流
        await remoteStream.play(`${remoteStream.getUserId()}_main`)
      } else if (remoteStream.getType() === 'auxiliary') {
        // 辅路视频流,一般是推屏幕分享的那路流。
        // 1. 在页面中放置一个 id 为 `${remoteStream.getUserId()}_screen` 的 div 标签,用于在 div 标签内播放屏幕分享。业务侧可自定义 div 标签的 id,此处只是举例说明。
        // 2. 播放屏幕分享
        await remoteStream.play(`${remoteStream.getUserId()}_screen`)
      }
    });
    await clientB.join({ roomId: 6969 });
    
  3. 同时推摄像头 + 屏幕分享

    const localStream = TRTC.createStream({ 
      userId: 'userA',
      audio: true, // 采集麦克风
      video: true // 采集摄像头
    })
    const shareStream = TRTC.createStream({ 
      userId: 'userA', 
      screen: true, // 采集屏幕分享
    })
    await localStream.initialize();
    await shareStream.initialize();
    await clientA.publish(localStream);
    await clientA.publish(shareStream, { isAuxiliary: true })
    

    注意:若辅流中含有音频(例如屏幕分享 + 系统音频),则 SDK 会将辅流的音频混音到主流音频中推流,拉流端需要订阅主流来订阅音频流。

  4. 停止屏幕分享

    // 取消推流
    await clientA.unpublish(shareStream);
    // 停止采集屏幕分享
    shareStream.close();
    

    另外用户还可能会通过浏览器自带的按钮停止屏幕分享,因此屏幕分享流需要监听屏幕分享停止事件,并进行相应的处理。

    // 屏幕分享流监听屏幕分享停止事件
    shareStream.on('screen-sharing-stopped', async event => {
      // 取消推流
      await clientA.unpublish(shareStream);
      // 停止采集屏幕分享
      shareStream.close();
    });
    
  5. 屏幕分享采集系统音频

    屏幕分享采集系统声音只支持 Chrome M74+

    • 在 Windows 和 Chrome OS 上,可以捕获整个系统的音频。
    • 在 Linux 和 Mac 上,只能捕获选项卡(某个 Tab 页面)的音频。
    • 其它 Chrome 版本、其它系统、其它浏览器均不支持。
    // 创建屏幕分享流 screenAudio 请设置为 true, 不支持同时采集系统和麦克风音量,请勿同时设置 audio 属性为 true
    const shareStream = TRTC.createStream({ 
      userId: 'userA' 
      screen: true, 
      screenAudio: true // 采集系统音频
      // audio: false 是否采集麦克风,不支持同时采集系统音频和麦克风,请勿同时设置 audio 和 screen 属性为 true
    });
    await shareStream.initialize();
    ...
    

    在弹出的对话框中勾选分享音频,发布的 stream 将会带上系统声音。

注意事项

  1. 一个房间只能推一路辅流。

  2. SDK 默认使用 1080p 参数配置来采集屏幕分享,具体参考接口:LocalStream.setScreenProfile

  3. 在 v4.15.0 版本开始,屏幕分享流支持以辅流的形式推流,如下差异需要注意:

  • 在 v4.15.0 之前的版本,屏幕分享流是以主流的形式推流的,其他 Web 用户拉流的 RemoteStream.getType() 返回值是 'main'。业务侧需要通过 RemoteStream.getUserId() 来标识是否为屏幕分享流。
  • 在 v4.15.0+ 版本:
    • 默认情况下,屏幕分享以主流的形式推流,向下兼容。
    • 若在 Client.publish(stream, { isAuxiliary: true }) 时将 isAuxiliary 参数设为 true,则会以辅流的形式推流。其他 Web 用户拉流的 RemoteStream.getType() 返回值是 'auxiliary'。业务侧可直接通过 'auxiliary' 来标识屏幕分享流。

常见问题

  1. Safari 屏幕分享出现报错 getDisplayMedia must be called from a user gesture handler

    这是因为 Safari 限制了 getDisplayMedia 屏幕采集的接口,必须在用户点击事件的回调函数执行的 1 秒内才可以调用。

    参考:webkit issue

    // good
    async function onClick() {
      // 建议在 onClick 执行时,先执行采集逻辑
      const screenStream = TRTC.createStream({ screen: true });
      await screenStream.initialize();
      await client.join({ roomId: 123123 });
    }
    // bad
    async function onClick() {
      await client.join({ roomId: 123123 });
      // 进房可能耗时超过 1s,可能会采集失败
      const screenStream = TRTC.createStream({ screen: true });
      await screenStream.initialize();
    }
    
  2. Mac Chrome 在已授权屏幕录制的情况下屏幕分享失败,出现 "NotAllowedError: Permission denied by system" 或者 "NotReadableError: Could not start video source" 错误信息,Chrome bug。解决方案:打开【设置】> 点击【安全性与隐私】> 点击【隐私】> 点击【屏幕录制】> 关闭 Chrome 屏幕录制授权 > 重新打开 Chrome 屏幕录制授权 > 关闭 Chrome 浏览器 > 重新打开 Chrome 浏览器。

  3. WebRTC 屏幕分享已知问题及规避方案

  4. Electron 使用 TRTC Web SDK 屏幕分享

  5. 判断用户选择的屏幕分享类型:整个屏幕、窗口、Chrome 标签页。

    // 在屏幕分享采集成功后
    const shareStream = TRTC.createStream({ screenAudio: true, screen: true, userId });
    await shareStream.initialize();
    // 根据 displaySurface 来判断采集的类型。
    const { displaySurface } = shareStream.getVideoTrack().getSettings();
    // 例如:monitor 为整个屏幕、window 为某个应用窗口、browser 为 Chrome 某个标签页
    

    参考:displaySurface