通话挂起是指在通话过程中一方临时暂停对话,并通过友好的方式提示对方进行等待; 通话恢复是指由通话挂起恢复正常通话。
这两项技术经常用于电话客服或者电话转接的场景,比如当电话客服需要时间进行某项操作时,会提示客户进行等待,而为了让客户保持耐心,在等待期间会播放一段轻松的音乐。
将 RTCRtpTransceiver.direction 属性和 RTCRtpSender.replaceTrack() 方法相结合,可以实现通话挂起与恢复功能。
我们来设想一个场景,一位客户正在与客服通话,客户提出了一项业务请求,客服表示可以满足但是需要一段时间处理,此时客服将通话挂起,提示客户进行等待。
客服方将从话简采集到的语音替换为一段音乐,并停止播放从客户端发送过来的语音,将收发器的方向设置为只发送(sendonly)。
通过本章对RTP媒体API的介绍,我们已经知道,调用replaceTrack替换媒体轨道不需要进行重新协商,而对收发器方向的修改则需要重新进行ICE协商,在doOffer流程中生成了本地提案,并通过信令服务器发送给客户方。客服端代码逻辑如下所示。
async function playMusicOnHold() {
try {
// audio是RTP收发器,musicTrack是播放音乐的音频轨道
await audio.sender.replaceTrack(musicTrack);
// 使接收到的音频静音
audio.receiver.track.enabled = false;
// 将direction设为只发送(sendonly),此时需要重新协商
audio.direction = 'sendonly';
// 生成提案并发送给对等方
await doOffer();
} catch (err) {
console.error(err);
}
}
客户方也会进行相应处理,按照业务逻辑,客户方此时不应再发送语音数据,同时应该能够收听到等待音乐。
当客户方接收到客服方通过信令服务器传输过来的包含了sendonly的提案后,首先调用setRemoteDescription应用提案,然后调用replaceTrack()方法传人参数null移除音轨这会导致停止发送音频数据。
客户方相应地将direction调整为只接收(recvonly),与客服方的sendonly 保持一致这避免了方向不一致导致反复协商。最后在doAnswer中生成自己的会话描述信息并发送给客服方,完成重新协商的过程。
async function handleSendonlyOffer() {
try {
// 首先应用sendonly提案
await pc.setRemoteDescription(sendonlyOffer);
// 停止发送音频
await audio.sender.replaceTrack(null);
// 相应地将direction调整为只接收
audio.direction = 'recvonly';
// 回应提案
await doAnswer();
} catch (err) {
// 错误处理
}
}
客服办理完业务,停止挂起状态,恢复通话,代码逻辑如代码清单所示。首先使用从话筒采集到的音频轨道替换挂起状态播放的音乐,这个步骤可以马上生效,不需要进行重新协商。然后取消接收音频静音,这样就可以听到客户那边的语音了。将direction改为收发(sendrecv),这一步需要进行重新协商,在doOffer流程中生成了本地提案,并通过信令服务器发送给客户方。
async function stopOnHoldMusic() {
// audio是RTP收发器,micTrack是从话简采集的音频轨道
await audio.sender.replaceTrack(micTrack);
// 取消接收音频静音
audio.receiver.track.enabled = true;
// 将方向设为收发(sendrecv)
audio.direction = 'sendrecv';
// 生成提案并发送给对等方
await doOffer();
}
客户方的音乐播放停止了,而且应该能够听到客服的语音信息,但是因为收发器方向 还是只接收的状态,所以无法对话。
如下面的代码清单所示,对客服方取消挂起操作进行回应,客户方收到了从信令服务器发送过来的提案SDP信息,调用setRemoteDescription()方法进行设置,随后使用replaceTrack加入话简音频轨道并将接收器方向改为收发(sendrecv),最后在doAnswer 流程中生成自己的会话描述信息并发送给客服方,完成ICE重新协商过程。至此,客户方可以正常发送自己的语音了,双方通话恢复正常。
async function onOffHold() {
try {
// 首先应用sendrecv提案
await pc.setRemoteDescription(sendrecvOffer);
// 开始发送话简音轨
await audio.sender.replaceTrack(micTrack);
// 将收发方向设为sendrecv
audio.direction = 'sendrecv';
// 生成本地SDP应答,回复给对方
await doAnswer();
} catch (err) {
// 错误处理
}
}