webrtc 入门第三章 建立连接

webrtc 入门第三章 建立连接

一、介绍
1、概述

​ 在前面的章节我们学习了通过webrtc的基本操作实现了获取本地媒体流、音视频的获取与操作。在得到本地的媒体流后我们就需要将本地媒体数据发送到远端,远端街道媒体流后渲染可视化,从而达到通话的目的。

​ RTCPeerConnection 连接的核心pai接口,使用它可以将本地流发送到远端,同时也可以将远端媒体流发送到本地从而实现连接。在使用过程中需要用到信令服务器中转信息和STUN服务器打桩服务。

二、实践
1、RTCPeerConnection 连接对象

1.RTCPeerConnection 后文简称pc连接对象。本地为Local对象,远端为Remote对象,在一对一音视频通话场景中pc对象总是成对出现。

方法名参数说明
RTCPeerConnectionRTCConfiguration连接配置参数RTCPeerConnection接口代表一个本地到远端的webrtc连接,这个连接提供了创建,保持,监控,关闭连接的方法实现,在创建时需要向其传入配置参数,及ICE配置信息
pc.createOfferRTCOfferOptions对象创建提议Offer方法,此方法会返回SDP offer信息,即RTCSessionDescription对象
pc.setLocalDescriptionRTCSessionDescription 对象设置本地SDP描述信息
pc.setRemoteDescriptionRTCSessionDescription 对象设置远端SDP描述信息,接收到远端发来的SDP信息后使用本方法
pc.createAnswerRTCAnswerOptions 对象,可选创建应答Answer方法,此方法会返回SDPAnswer信息,即RTCSessionDescription 对象
RTCPIceCandidatewevrtc网络信息,端口,ip等。
pc.addIceCandidateRTCPIceCandidate对象pc连接添加对方法的IceCandidate对象,得到对放的网络地址等
2.流程图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u3UNWW3f-1646320648144)(C:\Users\Administrator\Desktop\liucheng.png)]

3、连接流程

WebRTC连接流程比较复杂,学习过程只考虑将本地Peer-A的流发送到远端Peer-B,具体流程如下(A-本地,B-远端)

1.A获取到本地媒体流 MediaStram:通过getUserMedia 方法获取到本地的音视频流数据

2.A生成本地连接对象PC-A:创建一个RTCPeerConnection接口,该接口提供创建、保持、关闭等方法,在设置前需要设置ICE服务器地址

varconfiguration={"iceServers": [{"url": "stun:stun.1.google.com:19302"}]},
that.peerConnA = new RTCPeerConnection(that.configuration)

3.A将本地视频流加入PC-A :

that.localStream.getTracks().forEach((track) => {
    that.peerConnA.addTrack(track, that.localStream)
})

4.A创建提议Offer: that.peerConnA.createOffer() 返回一个RTCPSessionDescription对象,主要是SDP信息是会话的描述信息,两个端连接过程中通过他来进行互相的信息交换,达到媒体协商。

5.A-设置本地描述:创建offer成功后设置本地的描述信息 that.peerConnA.setLocalDescription(event)

6.A将Offer发送给B:通常需要一个信令服务器例如websocket 来转发offer数据

7.B生成PC-B对象:同A端一样,B端也要生成一个RCTPeerConnection对象来应答A端发送的Answer,媒体流等

8.B端设置远端描述:当B端接收到来自A端的offer信息后使用setRemoteDescription() 方法设置来自远端A的描述信息

9.B端生成应答Answer信息:B端使用pc.ceateAnswer()方法生成一个应答A端RTCPSessionDescription对象,主要包括SDP信息,应答Answer和提议Offer是成对出现的。

10.B端设置本地描述:B端创建Answer后设置本地的描述信息 that.peerConnB.setLocalDescription(event)

11.B端返回Answer给A端:通过信令服务器将Answer发送给A端

12.A端设置来自B端的Answer描述信息:当A端通过websocket信令服务获得到的Answer信息后,调用that.peerConnA.setRemoteDescription()方法设置远端的描述

13.交换ICE候选地址信息:建立连接时,会回调onicecandidate事件,传递ice候选地址,同样也需要websocket信令服务来做消息转发,对方接受到以后调用addIceCandidate()方法设置接收到的候选地址

14.交换与使用媒体流:当一方执行addTrack后,另一方的RTCPerrConnection会触发track事件回调,在回调事件中可以获得对方的轨道里的媒体流,这样就能播放对方的流媒体。

that.peerConnB.addEventListener("track", that.getRemoteStream)
getRemoteStream: function (event) {
  let that = this
      if (that.remoteVideo.srcObject !== event.streams[0]) {
             that.remoteVideo.srcObject = event.streams[0]
             this.remoteVideo.play();
             console.log("开始获取远端视频流")
      }
}
4、示例
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>RTCPeerConnection 连接测试</title>
</head>
<body>
<div class="app">
    <input type="button" title="开始" value="开始" v-on:click="start"/>
    <input type="button" title="呼叫" value="呼叫" v-on:click="call"/>
    <input type="button" title="挂断" value="挂断" v-on:click="stop"/>
    <input type="button" value="同步" v-on:click="canPlay"/>
    <hr>

    <span>当前使用视频设备:{[videoDeviceName]}</span>
    <br> <br>
    <span>当前使用音频设备:{[audioDeviceName]}</span>
    <hr>

    {{/*    本地视频*/}}
    <video id="localVideo" class="localVideo" height="240px" width="280px" playsinline autoplay muted></video>
    {{/*    远端视频*/}}
    <video id="remoteVideo" class="remoteVideo" height="240px" width="280px" playsinline autoplay muted></video>
    {{/*    本地mp4文件*/}}

    <br> <br>
    <hr>
    <span>测试本地mp4文件</span>

    <br>

    <br>
    <video id="myVideo" class="myVideo" autoplay="autoplay" width="400" height="200" controls loop muted
           v-on:oncanplay="canPlay">
        <source src="/static/file/capture.mp4" type="video/mp4">
    </video>

</div>
</body>
<script src="/static/js/Vue.2.5.3.js"></script>
<script type="text/javascript">
    navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
    let vm = new Vue({
        el: ".app",
        delimiters: ['{[', ']}'],
        data: {
            // 测试我的视频
            myVideo: null,
            // 本地视频
            localVideo: null,
            // 本地视频流
            localStream: null,
            // 远端视频流
            remoteVideo: null,
            isOpen: false,
            videoDeviceName: "",
            audioDeviceName: "",
            // ICE service地址
            configuration: {
                "iceServers": [{"url": "stun:stun.1.google.com:19302"}]
            },
            // peerConnA 本地对象
            peerConnA: null,
            // peerConnB 远程对象
            peerConnB: null,
            // 本地视频轨道
            videoTracks: [],
            // 本地音频轨道
            audioTrack: []
        },
        methods: {
            canPlay: function () {
                alert("zheli")
                let fps = 0;
                // 捕捉
                if (this.myVideo.captureStream) {
                    this.localStream = this.myVideo.captureStream(fps)
                } else if (this.capture.mozCaptureStream) {
                    this.localStream = this.myVideo.mozCaptureStream(fps)
                } else {
                    alert("不支持 captureStream")
                }

            },
            stop: function () {
                let that = this
                that.peerConnB.close()
                that.peerConnA.close()
                that.peerConnA = null
                that.peerConnB = null
                console.log("关闭会话")
            }
            ,
            start: async function () {
                let that = this;
                if (that.isOpen) {
                    return
                }
                try {
                    that.localStream = await navigator.mediaDevices.getUserMedia({audio: true, video: true})
                    that.isOpen = true
                    that.localVideo.srcObject = that.localStream
                    that.remoteVideo.srcObject = that.localStream
                    console.log("获取本地流成功", that.localStream)
                    // 获取设备轨道
                    that.videoTracks = that.localStream.getVideoTracks()
                    that.audioTrack = that.localStream.getAudioTracks()
                    if (that.videoTracks.length > 0) {
                        that.videoDeviceName = that.videoTracks[0].label
                    }
                    if (that.audioTrack.length > 0) {
                        that.audioDeviceName = that.audioTrack[0].label
                    }

                } catch (e) {
                    console.log("getUserMedia 错误" + e)
                }
            }
            ,
            call: async function () {
                let that = this;
                console.log("开始呼叫")

                //  监听返回icecandidate 信息
                that.peerConnA = new RTCPeerConnection(that.configuration)
                that.peerConnA.addEventListener("icecandidate", that.onIceCandidateA)
                that.peerConnB = new RTCPeerConnection(that.configuration)
                that.peerConnB.addEventListener("icecandidate", that.onIceCandidateB)
                // 监听ICE状态变化
                that.peerConnA.addEventListener("iceconnectionstatechange", that.onIceStateChangeA)
                that.peerConnB.addEventListener("iceconnectionstatechange", that.onIceStateChangeB)

                // 监听track,获取远端视频流视频
                that.peerConnB.addEventListener("track", that.getRemoteStream)
                // 将本地流加入本地连接
                that.localStream.getTracks().forEach((track) => {
                    that.peerConnA.addTrack(track, that.localStream)
                })


                // 创建通话offer
                try {
                    console.log("peerConnA 创建offer会话开始")
                    const offer = await that.peerConnA.createOffer()
                    await that.onCreateOfferSuccess(offer)
                } catch (e) {
                    console.log("创建会话描述SD失败:", e.toString())
                }
            }
            ,

            // 创建提议offer成功
            onCreateOfferSuccess: async function (event) {
                let that = this
                // 设置连接描述
                console.log("peerConnA 创建offer返回得SDP信息", event.sdp)
                console.log("设置peerConnA得本地描述start...")
                try {
                    await that.peerConnA.setLocalDescription(event)
                    console.log("设置peerConnA得本地描述成功")
                } catch (e) {
                    console.log("设置peerConnA得本地描述错误:", e.toString())
                }

                console.log("设置peerConnB得远端描述 start")
                try {
                    await that.peerConnB.setRemoteDescription(event)
                    console.log("设置peerConnB得远端描述成功")

                } catch (e) {
                    console.log("设置peerConnB得远端描述错误:", e.toString())
                }

                // 开始应答
                console.log("peerConnB创建应答 answer start")
                try {
                    const answer = await that.peerConnB.createAnswer()
                    console.log("peerConnB创建应答成功")
                    await that.onCreateAnswerSuccess(answer)
                } catch (e) {
                    console.log("peerConnB创建应答错误:", e.toString())
                }

            }
            ,

            // 创建answer应答成功
            onCreateAnswerSuccess: async function (answer) {
                let that = this
                console.log("peerConnB创建应答answer数据:", answer)
                console.log("peerConnA与peerConnB交换应答answer信息 start")

                try {
                    await that.peerConnB.setLocalDescription(answer)
                    console.log("设置peerConnB得本地answer 应答远端描述成功")

                } catch (e) {
                    console.log("设置peerConnB得本地answer应答描述错误:", e.toString())
                }

                try {
                    await that.peerConnA.setRemoteDescription(answer)
                    console.log("设置peerConnA得远端answer应答描述成功")

                } catch (e) {
                    console.log("设置peerConnA得远端answer应答描述错误:", e.toString())
                }
            }
            ,

            // 获取远端视频
            getRemoteStream: function (event) {
                let that = this
                console.log("获取远端视频数据如下:")
                console.log(event)
                if (that.remoteVideo.srcObject !== event.streams[0]) {
                    that.remoteVideo.srcObject = event.streams[0]
                    this.remoteVideo.play();
                    console.log("开始获取远端视频流")
                }
            }
            ,
            // 监听ICE状态变化事件回调方法
            onIceStateChangeA: function (event) {
                console.log("监听 peerConnA ICE状态", this.peerConnA.iceConnectionState)
                console.log(event)
            }
            ,
            // 监听ICE状态变化事件回调方法
            onIceStateChangeB: async function (event) {
                console.log("监听 peerConnB ICE状态", this.peerConnB.iceConnectionState)
                console.log(event)
            }
            ,

            onIceCandidateA: async function (event) {
                let that = this

                try {
                    if (event.candidate) {
                        // 直接交换candidate数据,就不需要通过信令服务器传送
                        await that.peerConnB.addIceCandidate(event.candidate)
                        console.log("peerConnB IceCandidate----------")
                        console.log(event)
                        that.onAddIceCandidateSuccess(that.peerConnB)
                    }
                } catch (e) {
                    that.onAddIceCandidateError(that.peerConnB, e)
                }
                console.log("onIceCandidateA data:" + event.candidate)
            }
            ,
            onIceCandidateB: async function (event) {
                let that = this
                try {
                    if (event.candidate) {
                        await that.peerConnA.addIceCandidate(event.candidate)
                        console.log("peerConnA IceCandidate----------")
                        console.log(event)
                        that.onAddIceCandidateSuccess(that.peerConnA)
                    }
                } catch (e) {
                    that.onAddIceCandidateError(that.peerConnA, e)
                }
                console.log("onIceCandidateB data:" + event.candidate)
            },
            //
            onAddIceCandidateSuccess: function (pc) {
                console.log("添加" + this.getPcName(pc) + "      IceCandidate 成功")
            },

            onAddIceCandidateError: function (pc, err) {
                console.log("添加" + this.getPcName(pc) + "       IceCandidate 失败" + err.toString())
            },
            getPcName: function (pc) {
                return (pc === this.peerConnA) ? "peerConnA" : "peerConnB"
            },
        }
        ,
        mounted: function () {
            this.localVideo = document.getElementById('localVideo');
            this.remoteVideo = document.getElementById('remoteVideo');
            this.myVideo = document.getElementById('myVideo');
        }
    })
</script>
</html>

在这里插入图片描述

			本地								远端同步           

在上述程序中未使用信令服务器作交换ICE和SDP数据,而是采用本地直接添加的方式,来实现了RTCPeerConnection的连接流程。

其连接过程也和第三段的流程一样,先获取本地的媒体地址,然后发起协商offer,设置本地描述,收到远端的协商offer后设置远端描述,生成会话offer,设置本地描述后发给提议方,提议方收到应答会话offer后,设置远端描述,整个流程结束。

三、总结

本章介绍了webrtc的连接流程即api,在一对一的对接过程中可以直接使用,其连接过程比较复杂也相当繁琐。学者需要先了解其连接原理和流程,然后再去结合代码即可掌握。另外学者们需要掌握一下几点。

1.对于媒体流的操作转换例如:获取视频的尺寸格式,监听远端视频流的变化,音频大小变化,视频清晰度自适应,编码方式。

2.了解提议(offer)/应答(answer)里的信息:这些是SDP信息包含,如分辨率,格式,编码等。

3.了解Candidate信息:这些也是SDP信息,里面包括媒体协商的信息,主要包括服务信息,如中继,打桩,服务器的ip和端口

地址,然后发起协商offer,设置本地描述,收到远端的协商offer后设置远端描述,生成会话offer,设置本地描述后发给提议方,提议方收到应答会话offer后,设置远端描述,整个流程结束。

三、总结

本章介绍了webrtc的连接流程即api,在一对一的对接过程中可以直接使用,其连接过程比较复杂也相当繁琐。学者需要先了解其连接原理和流程,然后再去结合代码即可掌握。另外学者们需要掌握一下几点。

1.对于媒体流的操作转换例如:获取视频的尺寸格式,监听远端视频流的变化,音频大小变化,视频清晰度自适应,编码方式。

2.了解提议(offer)/应答(answer)里的信息:这些是SDP信息包含,如分辨率,格式,编码等。

3.了解Candidate信息:这些也是SDP信息,里面包括媒体协商的信息,主要包括服务信息,如中继,打桩,服务器的ip和端口

4.通过学习视频连接后,可以进行举一反三实现canvas绘画板的同步功能。

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值