最佳实践
在使用 SDK 的第一步,应当是创建一个 QNRTCClient 对象,之后所有关于房间的连接、发布订阅等操作,均通过该对象完成。
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")
})
断网重连处理
监控房间连接状态的用法,可以参考文档 房间管理。这里需要注意的点是,断网重连的处理。
默认情况下,当发生网络异常等情况,SDK 会进入自动重连状态,此时房间连接状态会变为 RECONNECTING,表示重连中。之后就会两种可能:
第一种,此后如果网络状态恢复正常,则 SDK 会完成自动重连,房间连接状态会变为 RECONNECTED,表示重连成功。重连成功后,SDK 会自动恢复原有的发布和订阅状态。也就是说,如果在重连之前有发布过 tracks 或者订阅过 tracks,重连成功后,SDK 会自动重新发布/订阅这些 tracks。
第二种,如果 SDK 自动重连后,仍然无法正常连接网络,则 SDK 会自动退出房间,此时房间连接状态变为 DISCONNECTED,错误码为21003,表示断开连接。在断开连接状态时,connection-state-changed中会带有第二个参数 QNConnectionDisconnectedInfo,用来表示断开的原因。此时应当根据断开原因不同,做不同的逻辑处理。具体见如下代码。
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 事件,可以通过监听该事件,来处理这种情况。
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 方法播放 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>