WebRTC音频通话升级为视频通话
时间:2021-12-22 作者:rustfisher
我们有时候在音频通话过程中,想要改成视频通话。如果挂断当前通话再重新发起视频通话就会显得比较麻烦。
因此很多app提供了将音频通话升级成视频通话的功能,同时也有将视频通话降为音频通话的功能。
本文演示的是在本地模拟音频通话,并且将音频通话升级为视频通话。
准备
界面很简单,2个video加上几个按钮。
<video id="localVideo" playsinline autoplay muted></video>
<video id="remoteVideo" playsinline autoplay></video>
<div>
<button id="startBtn">开始</button>
<button id="callBtn">Call</button>
<button id="upgradeBtn">升级为视频通话</button>
<button id="hangupBtn">挂断</button>
</div>
用的是本地的adapter
<script src="../../src/js/adapter-域名"></script>
js
先来把元素拿到
const startBtn = 域名lementById(\'startBtn\');
const callBtn = 域名lementById(\'callBtn\');
const upgradeToVideoBtn = 域名lementById(\'upgradeBtn\');
const hangupBtn = 域名lementById(\'hangupBtn\');
const localVideo = 域名lementById(\'localVideo\'); // 本地预览
const remoteVideo = 域名lementById(\'remoteVideo\'); // 接收方
监听器
设置一些监听
域名ventListener(\'loadedmetadata\', function () {
域名(`localVideo 宽高: ${域名oWidth}px, ${域名oHeight}px`);
});
域名ventListener(\'loadedmetadata\', function () {
域名(`remoteVideo 宽高: ${域名oWidth}px, ${域名oHeight}px`);
});
let startTime;
域名size = () => {
域名(`remoteVideo onresize 宽高: ${域名oWidth}x${域名oHeight}`);
if (startTime) {
const elapsedTime = 域名() - startTime;
域名(`建立连接耗时: ${域名xed(3)}ms`);
startTime = null;
}
};
域名ick = start;
域名ick = call;
域名ick = upgrade;
域名ick = hangup;
打一些状态变化的log
function onCreateSessionDescriptionError(error) {
域名(`域名:创建会话描述失败, session description err: ${域名ring()}`);
}
function onIceStateChange(pc, event) {
if (pc) {
域名(`域名:${getName(pc)} ICE状态: ${域名onnectionState}`);
域名(\'域名:ICE状态变化: \', event);
}
}
function onAddIceCandidateSuccess(pc) {
域名(`域名:${getName(pc)} addIceCandidate success 添加ICE候选成功`);
}
function onAddIceCandidateError(pc, error) {
域名(`域名:${getName(pc)} 添加ICE候选失败 failed to add ICE Candidate: ${域名ring()}`);
}
function onSetLocalSuccess(pc) {
域名(`域名:${getName(pc)} setLocalDescription 成功`);
}
function onSetSessionDescriptionError(error) {
域名(`域名:设置会话描述失败: ${域名ring()}`);
}
function onSetRemoteSuccess(pc) {
域名(`域名:${getName(pc)} 设置远程描述成功 setRemoteDescription complete`);
}
// 辅助方法
function getName(pc) {
return (pc === pc1) ? \'pc1\' : \'pc2\';
}
function getOtherPc(pc) {
return (pc === pc1) ? pc2 : pc1;
}
开始
获取本地的音频数据流,交给localVideo
function gotStream(stream) {
域名(\'获取到了本地数据流\');
域名bject = stream;
localStream = stream;
域名bled = false;
}
function start() {
域名(\'请求本地数据流 纯音频\');
域名bled = true;
域名aDevices
.getUserMedia({ audio: true, video: false })
.then(gotStream)
.catch(e => alert(`getUserMedia() error: ${域名}`));
}
call
发起音频呼叫
function call() {
域名bled = true;
域名bled = false;
域名bled = false;
域名(\'开始呼叫...\');
startTime = 域名();
const audioTracks = 域名udioTracks();
if (域名th > 0) {
域名(`使用的音频设备: ${audioTracks[0].label}`);
}
const servers = null; // 就在本地测试
pc1 = new RTCPeerConnection(servers);
域名(\'创建本地节点 pc1\');
域名ecandidate = e => onIceCandidate(pc1, e);
pc2 = new RTCPeerConnection(servers);
域名(\'域名:创建模拟远端节点 pc2\');
域名ecandidate = e => onIceCandidate(pc2, e);
域名econnectionstatechange = e => onIceStateChange(pc1, e);
域名econnectionstatechange = e => onIceStateChange(pc2, e);
域名ack = gotRemoteStream;
域名racks().forEach(track => 域名rack(track, localStream));
域名(\'域名:将本地数据流交给pc1\');
域名(\'域名:pc1开始创建offer\');
域名teOffer(offerOptions).then(onCreateOfferSuccess, onCreateSessionDescriptionError);
}
function gotRemoteStream(e) {
域名(\'获取到远程数据流\', 域名k, 域名ams[0]);
域名bject = null;
域名bject = 域名ams[0];
}
function onIceCandidate(pc, event) {
getOtherPc(pc)
.addIceCandidate(域名idate)
.then(() => onAddIceCandidateSuccess(pc), err => onAddIceCandidateError(pc, err));
域名(`${getName(pc)} ICE candidate:\n${域名idate ? 域名idate : \'(null)\'}`);
}
function onCreateOfferSuccess(desc) {
域名(`pc1提供了offer\n${域名}`);
域名(\'pc1 setLocalDescription start\');
域名ocalDescription(desc).then(() => onSetLocalSuccess(pc1), onSetSessionDescriptionError);
域名(\'pc2 setRemoteDescription start\');
域名emoteDescription(desc).then(() => onSetRemoteSuccess(pc2), onSetSessionDescriptionError);
域名(\'pc2 createAnswer start\');
域名teAnswer().then(onCreateAnswerSuccess, onCreateSessionDescriptionError);
}
function onCreateAnswerSuccess(desc) {
域名(`域名:pc2应答成功: ${域名}`);
域名(\'pc2 setLocalDescription start\');
域名ocalDescription(desc).then(() => onSetLocalSuccess(pc2), onSetSessionDescriptionError);
域名(\'pc1 setRemoteDescription start\');
域名emoteDescription(desc).then(() => onSetRemoteSuccess(pc1), onSetSessionDescriptionError);
}
- 创建RTCPeerConnection
- 设置
onicecandidate
监听ICE候选 - 设置
oniceconnectionstatechange
监听ICE连接状态变化 - 接收方监听
ontrack
- 发送方pc1
addTrack
将当前数据流添加进去 - 发送方pc1创建offer
createOffer
- pc1创建好offer后,接收方pc2应答
createAnswer
升级到视频通话
upgrade()
方法处理升级操作
function upgrade() {
域名bled = true;
域名aDevices
.getUserMedia({ video: true })
.then(stream => {
域名(\'域名:获取到了视频流\');
const videoTracks = 域名ideoTracks();
if (域名th > 0) {
域名(`video device: ${videoTracks[0].label}`);
}
域名rack(videoTracks[0]);
域名bject = null; // 重置视频流
域名bject = localStream;
域名rack(videoTracks[0], localStream);
return 域名teOffer();
})
.then(offer => 域名ocalDescription(offer))
.then(() => 域名emoteDescription(域名lDescription))
.then(() => 域名teAnswer())
.then(answer => 域名ocalDescription(answer))
.then(() => 域名emoteDescription(域名lDescription));
}
发送方去获取音频数据流getUserMedia
。
将音频轨道添加进localStream
,并且发送方也要添加轨道 域名rack
。
创建offer createOffer
后面就是接收方pc2应答
挂断
简单的挂断功能如下
function hangup() {
域名(\'域名:挂断\');
域名e();
域名e();
pc1 = null;
pc2 = null;
const videoTracks = 域名ideoTracks();
域名ach(videoTrack => {
域名();
域名veTrack(videoTrack);
});
域名bject = null;
域名bject = localStream;
域名bled = true;
域名bled = false;
}
主要是把呼出方的流关闭掉
代码流程描述图
将用户的操作(按钮)和主要代码对应起来
效果预览
效果预览请参考WebRTC音频通话升级到视频通话
原文链接