您当前的位置:首页 > 电脑百科 > 程序开发 > 移动端 > 百科

在音频通话中,如何将WebRTC音频通话升级为视频通话

时间:2021-12-23 11:13:38  来源:博客园  作者:充满元气的java爱好者

我们有时候在音频通话过程中,想要改成视频通话。如果挂断当前的通话再重新发起视频通话就会显得比较麻烦。
因此很多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-2021.js"></script>

js#

先来把元素拿到

const startBtn = document.getElementById('startBtn');
const callBtn = document.getElementById('callBtn');
const upgradeToVideoBtn = document.getElementById('upgradeBtn');
const hangupBtn = document.getElementById('hangupBtn');
const localVideo = document.getElementById('localVideo');   // 本地预览
const remoteVideo = document.getElementById('remoteVideo'); // 接收方

监听器#

设置一些监听

localVideo.addEventListener('loadedmetadata', function () {
    console.log(`localVideo 宽高: ${this.videoWidth}px, ${this.videoHeight}px`);
});

remoteVideo.addEventListener('loadedmetadata', function () {
    console.log(`remoteVideo 宽高: ${this.videoWidth}px, ${this.videoHeight}px`);
});

let startTime;
remoteVideo.onresize = () => {
    console.log(`remoteVideo onresize 宽高: ${remoteVideo.videoWidth}x${remoteVideo.videoHeight}`);
    if (startTime) {
        const elapsedTime = window.performance.now() - startTime;
        console.log(`建立连接耗时: ${elapsedTime.toFixed(3)}ms`);
        startTime = null;
    }
};

startBtn.onclick = start;
callBtn.onclick = call;
upgradeToVideoBtn.onclick = upgrade;
hangupBtn.onclick = hangup;

打一些状态变化的log

function onCreateSessionDescriptionError(error) {
    console.log(`rustfisher.com:创建会话描述失败, session description err: ${error.toString()}`);
}

function onIceStateChange(pc, event) {
    if (pc) {
        console.log(`rustfisher.com:${getName(pc)} ICE状态: ${pc.iceConnectionState}`);
        console.log('rustfisher.com:ICE状态变化: ', event);
    }
}

function onAddIceCandidateSuccess(pc) {
    console.log(`rustfisher.com:${getName(pc)} addIceCandidate success 添加ICE候选成功`);
}

function onAddIceCandidateError(pc, error) {
    console.log(`rustfisher.com:${getName(pc)} 添加ICE候选失败 failed to add ICE Candidate: ${error.toString()}`);
}

function onSetLocalSuccess(pc) {
    console.log(`rustfisher.com:${getName(pc)} setLocalDescription 成功`);
}

function onSetSessionDescriptionError(error) {
    console.log(`rustfisher.com:设置会话描述失败: ${error.toString()}`);
}

function onSetRemoteSuccess(pc) {
    console.log(`rustfisher.com:${getName(pc)} 设置远程描述成功 setRemoteDescription complete`);
}

// 辅助方法
function getName(pc) {
    return (pc === pc1) ? 'pc1' : 'pc2';
}

function getOtherPc(pc) {
    return (pc === pc1) ? pc2 : pc1;
}

开始#

获取本地的音频数据流,交给localVideo

function gotStream(stream) {
    console.log('获取到了本地数据流');
    localVideo.srcObject = stream;
    localStream = stream;
    callBtn.disabled = false;
}

function start() {
    console.log('请求本地数据流 纯音频');
    startBtn.disabled = true;
    navigator.mediaDevices
        .getUserMedia({ audio: true, video: false })
        .then(gotStream)
        .catch(e => alert(`getUserMedia() error: ${e.name}`));
}

call#

发起音频呼叫

function call() {
    callBtn.disabled = true;
    upgradeToVideoBtn.disabled = false;
    hangupBtn.disabled = false;
    console.log('开始呼叫...');
    startTime = window.performance.now();
    const audioTracks = localStream.getAudioTracks();
    if (audioTracks.length > 0) {
        console.log(`使用的音频设备: ${audioTracks[0].label}`);
    }
    const servers = null; // 就在本地测试
    pc1 = new RTCPeerConnection(servers);
    console.log('创建本地节点 pc1');
    pc1.onicecandidate = e => onIceCandidate(pc1, e);
    pc2 = new RTCPeerConnection(servers);
    console.log('rustfisher.com:创建模拟远端节点 pc2');
    pc2.onicecandidate = e => onIceCandidate(pc2, e);
    pc1.oniceconnectionstatechange = e => onIceStateChange(pc1, e);
    pc2.oniceconnectionstatechange = e => onIceStateChange(pc2, e);
    pc2.ontrack = gotRemoteStream;

    localStream.getTracks().forEach(track => pc1.addTrack(track, localStream));
    console.log('rustfisher.com:将本地数据流交给pc1');

    console.log('rustfisher.com:pc1开始创建offer');
    pc1.createOffer(offerOptions).then(onCreateOfferSuccess, onCreateSessionDescriptionError);
}

function gotRemoteStream(e) {
    console.log('获取到远程数据流', e.track, e.streams[0]);
    remoteVideo.srcObject = null;
    remoteVideo.srcObject = e.streams[0];
}

function onIceCandidate(pc, event) {
    getOtherPc(pc)
        .addIceCandidate(event.candidate)
        .then(() => onAddIceCandidateSuccess(pc), err => onAddIceCandidateError(pc, err));
    console.log(`${getName(pc)} ICE candidate:n${event.candidate ? event.candidate.candidate : '(null)'}`);
}

function onCreateOfferSuccess(desc) {
    console.log(`pc1提供了offern${desc.sdp}`);
    console.log('pc1 setLocalDescription start');
    pc1.setLocalDescription(desc).then(() => onSetLocalSuccess(pc1), onSetSessionDescriptionError);
    console.log('pc2 setRemoteDescription start');
    pc2.setRemoteDescription(desc).then(() => onSetRemoteSuccess(pc2), onSetSessionDescriptionError);
    console.log('pc2 createAnswer start');
    pc2.createAnswer().then(onCreateAnswerSuccess, onCreateSessionDescriptionError);
}

function onCreateAnswerSuccess(desc) {
    console.log(`rustfisher.com:pc2应答成功:  ${desc.sdp}`);
    console.log('pc2 setLocalDescription start');
    pc2.setLocalDescription(desc).then(() => onSetLocalSuccess(pc2), onSetSessionDescriptionError);
    console.log('pc1 setRemoteDescription start');
    pc1.setRemoteDescription(desc).then(() => onSetRemoteSuccess(pc1), onSetSessionDescriptionError);
}
  • 创建RTCPeerConnection
  • 设置onicecandidate监听ICE候选
  • 设置oniceconnectionstatechange监听ICE连接状态变化
  • 接收方监听ontrack
  • 发送方pc1 addTrack将当前数据流添加进去
  • 发送方pc1创建offer createOffer
  • pc1创建好offer后,接收方pc2应答 createAnswer

升级到视频通话#

upgrade()方法处理升级操作

function upgrade() {
    upgradeToVideoBtn.disabled = true;
    navigator.mediaDevices
        .getUserMedia({ video: true })
        .then(stream => {
            console.log('rustfisher.com:获取到了视频流');
            const videoTracks = stream.getVideoTracks();
            if (videoTracks.length > 0) {
                console.log(`video device: ${videoTracks[0].label}`);
            }
            localStream.addTrack(videoTracks[0]);
            localVideo.srcObject = null; // 重置视频流
            localVideo.srcObject = localStream;
            pc1.addTrack(videoTracks[0], localStream);
            return pc1.createOffer();
        })
        .then(offer => pc1.setLocalDescription(offer))
        .then(() => pc2.setRemoteDescription(pc1.localDescription))
        .then(() => pc2.createAnswer())
        .then(answer => pc2.setLocalDescription(answer))
        .then(() => pc1.setRemoteDescription(pc2.localDescription));
}

发送方去获取音频数据流getUserMedia。
将音频轨道添加进localStream,并且发送方也要添加轨道 pc1.addTrack。
创建offer createOffer

后面就是接收方pc2应答

挂断#

简单的挂断功能如下

function hangup() {
    console.log('rustfisher.com:挂断');
    pc1.close();
    pc2.close();
    pc1 = null;
    pc2 = null;

    const videoTracks = localStream.getVideoTracks();
    videoTracks.forEach(videoTrack => {
        videoTrack.stop();
        localStream.removeTrack(videoTrack);
    });

    localVideo.srcObject = null;
    localVideo.srcObject = localStream;

    hangupBtn.disabled = true;
    callBtn.disabled = false;
}

主要是把呼出方的流关闭掉

代码流程描述图#

将用户的操作(按钮)和主要代码对应起来

在音频通话中,如何将WebRTC音频通话升级为视频通话

 

效果预览#

效果预览请参考WebRTC音频通话升级为视频通话 - AnRFDev - 博客园

原文链接:
https://www.cnblogs.com/rustfisher/p/15717958.html



Tags:WebRTC   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
我们有时候在音频通话过程中,想要改成视频通话。如果挂断当前的通话再重新发起视频通话就会显得比较麻烦。 因此很多app提供了将音频通话升级成视频通话的功能,同时也有将视频...【详细内容】
2021-12-23  Tags: WebRTC  点击:(5)  评论:(0)  加入收藏
【网络通信 -- WebRTC】WebRTC 基础知识 -- ICE 交互总结【1】ICE 的一般概念简介ICE 角色offer (主动发起)的一方为 controlling 角色answer (被动接受)的一方为 controlle...【详细内容】
2021-11-30  Tags: WebRTC  点击:(24)  评论:(0)  加入收藏
前言最近对 WebRTC iOS 端源码进行了下载和编译,网上针对 WebRTC iOS 端的编译文章基本都是几年前的,有些地方已经不适用于最新版的 WebRTC 的编译,简单记录下载&编译的过程,以...【详细内容】
2021-11-10  Tags: WebRTC  点击:(38)  评论:(0)  加入收藏
Web平台上的WebRTC并不是其唯一的媒体API。WebVR说明书于几年前被引入来为浏览器中的虚拟现实设备提供支持。目前已经变为新的WebXR设备API说明书。今年夏天我在ClueCon,Dan...【详细内容】
2021-08-27  Tags: WebRTC  点击:(70)  评论:(0)  加入收藏
作者 | Vitaly Suturikhin翻译 | 徐鋆 低广播延迟已经成为任何关于建设源端站和CDN的招标和竞争中的必要特性。以前这种标准只适用于体育广播,但现在运营商要求每个领域的广...【详细内容】
2021-08-17  Tags: WebRTC  点击:(70)  评论:(0)  加入收藏
所有的基于网络传输的音视频采集播放系统都会存在音视频同步的问题,作为现代互联网实时音视频通信系统的代表,WebRTC 也不例外。本文将对音视频同步的原理以及 WebRTC 的实现...【详细内容】
2021-08-17  Tags: WebRTC  点击:(75)  评论:(0)  加入收藏
本篇博文将会提供一个关于如何使用WebRTC建立一个视频会议App的教程。我们不会将其设计的太复杂,它将会是一个简单的一对一视频会议App,并且仅仅使用了WebRTC APIs和一些其他...【详细内容】
2021-07-29  Tags: WebRTC  点击:(113)  评论:(0)  加入收藏
本文来自《WebRTC Native开发实战》书籍作者许建林在LiveVideoStack线上分享中的内容,详细分析总结 WebRTC 的视频数据流程,并对大型项目如何快速上手:分析方法,如何在工作中按...【详细内容】
2020-09-10  Tags: WebRTC  点击:(66)  评论:(0)  加入收藏
一、什么是WebRTCWebRTC技术是激烈的开放的Web战争中一大突破-Brendan Eich, inventor of JavaScript。简单来说,WebRTC 是一个音视频处理+及时通讯的开源库。在实时通信中,音...【详细内容】
2020-08-12  Tags: WebRTC  点击:(89)  评论:(0)  加入收藏
目录1,自定义摄像头分辨率2,码率限制3,调整编码器顺序4,Mesh模型多方通话5,Janus框架分析6,Janus Web源码分析7,Janus Android源码分析8,基于Janus实现会议系统自定义摄像头分辨率一...【详细内容】
2020-07-26  Tags: WebRTC  点击:(219)  评论:(0)  加入收藏
▌简易百科推荐
我们有时候在音频通话过程中,想要改成视频通话。如果挂断当前的通话再重新发起视频通话就会显得比较麻烦。 因此很多app提供了将音频通话升级成视频通话的功能,同时也有将视频...【详细内容】
2021-12-23  充满元气的java爱好者  博客园  Tags:WebRTC   点击:(5)  评论:(0)  加入收藏
随着移动互联网的发展和智能手机的普及,手机端成为了最大的流量入口,占据着一半以上的流量来源。所以很多企业在开发网站之前,也会考虑到开发手机网站。不过,手机端网站并非是对...【详细内容】
2021-11-03  小刘吃吃吃  搜狐号  Tags:手机网站   点击:(33)  评论:(0)  加入收藏
2010年,谷歌宣布正式退出国内市场,随后,各大厂商推出的手机也不再预装GMS(谷歌移动服务)。 与海外安卓用户不同,国内安卓用户对软件的需求不需要基于来自GMS框架下的Google Play(谷...【详细内容】
2021-10-08  雷科技    Tags:安卓App   点击:(49)  评论:(0)  加入收藏
一、滑屏操作不需要看到欢迎页面,直接做里面的后续操作就好了。也就是我想记住一些用户的习惯, 不要像第一次访问一样,有一个重置与否,给它关闭了。# 重置与否desired_caps["noR...【详细内容】
2021-06-17  美好一点    Tags:Appium   点击:(145)  评论:(0)  加入收藏
架构也因项目而异。不同的项目需求不同,对应的架构也会不同。架构分层API的设计完毕之后。接下来我就会考虑App项目的总体架构了。总体怎样架构,我也以前做过不少尝试。早期的...【详细内容】
2021-05-24  每周IT  今日头条  Tags:App架构   点击:(196)  评论:(0)  加入收藏
很多企业或个人开发者在开发好App之后,会遇到一系列上架难题,本文以华为应用市场为例,帮助你解读如何快速完成应用市场上架提交&审核。...【详细内容】
2021-03-16    知乎  Tags:华为应用市场   点击:(459)  评论:(0)  加入收藏
过去几年以来,Netflix 一直在开发 Prodicle 移动应用,借此在电视节目与电影制作领域推进创新。时至今日,实体生产的具体方式可谓日新月异,不同国家、地区甚至是不同生产体系之间...【详细内容】
2020-11-16      Tags:Netflix   点击:(143)  评论:(0)  加入收藏
作者:ypaapyyang,腾讯 WXG 后台开发工程师,个人公众号:码农课代表。本文旨在分析分布式配置系统的必要性、可行性,及其关键要素,并介绍一款基于该系列分析,在微信研发体系下的实践...【详细内容】
2020-10-20      Tags:分布式   点击:(90)  评论:(0)  加入收藏
与其天天看华为怎么被禁,天天聊鸿蒙OS怎样怎样,不如来点实际的,加入成为华为开发者!安卓的开发者们入坑了吗?华为要打造自己独立的生态,迫不得已,逼出来的国产OS终究要来。 Harmony...【详细内容】
2020-10-12      Tags:HarmonyOS   点击:(1082)  评论:(0)  加入收藏
集成华为HMS SDK的游戏或普通应用,调用接口(比如getCurrentPlayer登录接口)总是报错6003。该错误码官方文档表示:签名证书指纹错误,签名证书指纹主要用于校验应用的真实性,确保应...【详细内容】
2020-10-12      Tags:HMS SDK   点击:(91)  评论:(0)  加入收藏
最新更新
栏目热门
栏目头条