human: 如何处理七牛云的实时音视频最佳实践

gpt: [实时音视频](https://developer.qiniu.com/rtc) > 使用指南 > Web >最佳实践

#  最佳实践

最近更新时间: 2022-07-14 15:46:51

# 房间对象

在使用 SDK 的第一步，应当是创建一个
[QNRTCClient](https://developer.qiniu.com/rtc/9090/WebQNRTCClient)
对象，之后所有关于房间的连接、发布订阅等操作，均通过该对象完成。

    
    
    const mClient = QNRTC.createClient();
    

这里要注意的点是，同一个页面，该方法仅允许执行一次。即 QNRTCClient 对象应当只创建一个，当作单例对象使用。

# 监听远端用户

不管是先加入房间，还是后加入房间，均通过监听事件获取远端用户信息。这样的逻辑就要求，一定要在加入房间之前，注册好事件监听，这样才可以在加入房间后，立刻收到房间内已有用户的信息。

    
    
    const mClient = QNRTC.createClient()
    
    // 监听远端用户加入房间
    mClient.on("user-joined", (userID, userData) => {
      // ...
    })
    
    // 监听远端用户发布 track
    mClient.on("user-published", (userID, tracks) => {
      // ...
    })
    
    // 注册好前面的事件监听后，再加入房间
    mClient.join("your room token")
      .then(() => {
        console.log("join room success")
      })
    

# 断网重连处理

监控房间连接状态的用法，可以参考文档 [房间管理](https://developer.qiniu.com/rtc/9465/room-
management-web)。这里需要注意的点是，断网重连的处理。

默认情况下，当发生网络异常等情况，SDK 会进入自动重连状态，此时房间连接状态会变为 RECONNECTING，表示重连中。之后就会两种可能：

第一种，此后如果网络状态恢复正常，则 SDK 会完成自动重连，房间连接状态会变为 RECONNECTED，表示重连成功。重连成功后，SDK
会自动恢复原有的发布和订阅状态。也就是说，如果在重连之前有发布过 tracks 或者订阅过 tracks，重连成功后，SDK 会自动重新发布/订阅这些
tracks。

第二种，如果 SDK 自动重连后，仍然无法正常连接网络，则 SDK 会自动退出房间，此时房间连接状态变为
DISCONNECTED，错误码为21003，表示断开连接。在断开连接状态时，[connection-state-
changed](https://developer.qiniu.com/rtc/9090/WebQNRTCClient#connection-state-
changed)中会带有第二个参数
[QNConnectionDisconnectedInfo](https://developer.qiniu.com/rtc/9071/WebQNConnectionDisconnectedInfo)，用来表示断开的原因。此时应当根据断开原因不同，做不同的逻辑处理。具体见如下代码。

    
    
    mClient.on('connection-state-changed', function(connectionState, info) {
      console.log('connection-state-changed', connectionState);
    
      // 当进入连接断开状态
      if (connectionState === QNConnectionState.DISCONNECTED) {
    
        // 监控断开原因
        switch(info.reason) {
    
          // 当异常断开时
          case QNConnectionDisconnectedReason.ERROR:
            break;
    
          // 当被踢出房间时
          case QNConnectionDisconnectedReason.KICKED_OUT:
            break;
    
          // 当调用接口，主动离开房间时
          case QNConnectionDisconnectedReason.LEAVE:
            break;
    
        }
    });
    

此时，最需要关注的是异常断开的情况。一般处理模式有两种：

  * 通知用户：展示 UI，通知用户网络异常，用户可选择调整网络后重试
  * 自动重连：可以在收到异常断开的通知后，主动调用重新加入房间的方法，主动再次重试

# 切换前后摄像头

手机浏览器中，一般会有切换前后摄像头的需求。web 中切换前后摄像头无法一步完成，需要按照如下逻辑。

    
    
    <body>
      <div id="container"></div>
      <button id="switchCamera">切换摄像头</button>
    
      <script>
        const switchCameraBtn = document.getElementById("switchCamera")
        const playContainer = document.getElementById("container")
    
        let facingMode = "user"
        let cameraTrack;
    
        switchCameraBtn.onclick = async () => {
    
          // facingMode 参数用于指定采集前置摄像头还是后置摄像头
          facingMode = facingMode === "user" ? "environment" : "user"
    
          // 之前采集的 camera track 必须销毁
          // 注意，如果该 camera track 发布过，就也要先取消发布
          if (cameraTrack) {
            // unpublish
            // ...
    
            cameraTrack.destroy()
            cameraTrack = undefined;
          }
    
          // 重新采集指定的摄像头
          cameraTrack = await QNRTC.createCameraVideoTrack({ facingMode })
    
          // 再次播放
          cameraTrack.play(playContainer);
        }
      </script>
    
    </body>
    

# local track 断流处理

本地采集的 local track，某些情况下可能会发生断流，一般常见的有下面几种情况：

  * 摄像头被拔出
  * 麦克风被拔出
  * 用户主动停止了屏幕共享
  * 媒体设备故障

当发生断流时，视频 track 会黑屏，音频 track 会没有声音。此时如果该 track 已发布，SDK 会自动取消发布该 track，并触发
QNLocalTrack 对象上的
[ended](https://developer.qiniu.com/rtc/9085/WebQNLocalTrack#ended)
事件，可以通过监听该事件，来处理这种情况。

    
    
    const track = await QNRTC.createScreenVideoTrack({ screenVideoTag: "screen-share" })
    
    track.on("ended", () => {
      console.log(`track: ${track.tag} ended!`)
    
      // 后续逻辑
      // 1. 通知用户
      // 2. 自动重新采集
      // 3. 其他等...
    })
    

> 仅需关注 local track 的 ended 情况，remote track 的 ended 由远端用户自行处理，每个本地用户处理自己的 local
> track 即可。

# 自动播放失败处理

浏览器中为了安全和用户体验等方面考虑，对媒体数据的自动播放做了限制：在没有用户交互操作的情况下，不允许媒体播放。也就是说，在没有用户交互的情况下，如果直接调用
[play](https://developer.qiniu.com/rtc/9076/WebQNTrack#play) 方法播放
track，一定概率会被浏览器阻止，导致播放失败。

针对这种情况，一般处理策略有两种。

第一种，始终保证在用户交互之后，调用 play 方法播放。例如，在一个 button 的点击回调函数中调用。

    
    
    const playBtn = document.getElementById("playBtn")
    const playContainer = document.getElementById("container")
    
    // create track 
    // ...
    
    playBtn.onclick = () => {
      // 这里，点击回调函数中调用
      track.play(playContainer)
    }
    

第二种，可以选择在自动播放失败时，引导用户交互，在用户的交互中，再次播放。

    
    
    <body>
      <div id="container"></div>
      <button id="resumeBtn" disabled>恢复播放</button>
    
      <script>
        const resumeBtn = document.getElementById("resumeBtn")
        const playContainer = document.getElementById("container");
    
        // 用来存储播放失败的 tracks
        let toBeResumedTracks = []
    
        // 引导用户点击的 button，点击后再次执行播放
        resumeBtn.onclick = () => {
          for (const track of toBeResumedTracks) {
            track.play(playContainer)
          }
          // 将数组清空
          toBeResumedTracks = []
          // 隐藏重新播放按钮
          resumeBtn.disabled = true
        }
    
        const mClient = QNRTC.createClient()
    
        mClient.on("user-published", (userID, tracks) => {
          mClient.subscribe(tracks)
            .then(({ videoTracks, audioTracks }) => {
              const tracks = [...videoTracks, ...audioTracks]
    
              for (const track of tracks) {
                // 播放 track
                track.play(playContainer).catch(e => {
                  console.log("play fail", track)
    
                  // 如果播放失败，push 到待恢复数组中
                  toBeResumedTracks.push(track)
    
                  // 显示按钮，引导用户点击
                  resumeBtn.disabled = false
                })
              }
            })
        })
    
        mClient.join("your room token")
          .then(() => {
            console.log("join room success")
          })
      </script>
    </body>
    

以上内容是否对您有帮助？