最近在研究WebRTC实时视频传输,撸了一个视频通话的demo出来,这次的教程没有通过p2p方式,使用的方式为端到端的模式
前置知识 SDP
Session Description Protocol,它是一种用于描述多媒体会话的协议,它可以帮助我们描述媒体流的信息,比如媒体流的类型,编码格式,分辨率等等。WebRTC 通过SDP来交换端与端之间的网络和媒体信息
v=0 # SDP版本号
o=- 0 0 IN IP4 120.24.99.xx # 会话标识信息
s=- # 会话名称
t=0 0 # 会话的有效时间
a=group:BUNDLE audio video # 媒体流类型
a=msid-semantic: WMS * # 媒体流标识符
m=audio 9 UDP/TLS/RTP/SAVPF 111 103 104 9 0 8 106 105 13 126 # 音频媒体流
c=IN IP4 120.24.99.xx # 连接信息
a=rtcp:9 IN IP4 0.0.0.0 # RTCP 的 IP 地址
a=candidate:0 1 UDP 2122252543 120.24.99.xx 9 typ host # 候选 IP 地址
# ...等等等
基本操作流程
2.1流程图
图中为前后端交互的方式,本地运行省略第一步的连接请求即可
2.2 实操
代码
<script lang="ts" setup>
import adapter from 'webrtc-adapter';
import { ref, onMounted } from 'vue';
// 创建本地连接远端的WebRtc连接
const pc = new RTCPeerConnection()
// 创建本地和远端的空媒体流
let localStream: MediaStream
let remoteStream: MediaStream
const offerSdp = ref('')
const answerSdp = ref('')
// 初始化
const init = async () => {
// 获取本地端视频标签
const localVideo = document.getElementById('local') as HTMLVideoElement
// 获取远程端视频标签
const remoteVideo = document.getElementById('remote') as HTMLVideoElement
// 获取本地媒体流
localStream = await navigator.mediaDevices.getUserMedia({
audio: true,
video: true,
})
// 设置本地视频流
localVideo.srcObject = localStream
// 添加本地流到PC
localStream.getTracks().forEach((track) => {
pc.addTrack(track, localStream)
})
// 监听远程流
pc.ontrack = (e) => {
remoteVideo.srcObject = e.streams[0]
}
}
// 创建offer(提案)
const createOffer = async () => {
// 创建本地offer,在这里通过信令服务器发送offerSdp
const offer = await pc.createOffer()
await pc.setLocalDescription(offer)
// 监听RTC连接的onicecandidate事件,当ICE服务返回一个新的候选地址时,就会触发该事件
pc.onicecandidate = async (e) => {
if (e.candidate) {
offerSdp.value = JSON.stringify(pc.localDescription)
}
}
}
// 创建answer
const offerSdp2 = ref('')
const createAnswer = async () => {
// 解析字符串
const offer = JSON.parse(offerSdp2.value)
// 该事件在创建新的answer ICE候选项时触发
pc.onicecandidate = async (e) => {
if (e.candidate) {
answerSdp.value = JSON.stringify(pc.localDescription)
}
}
await pc.setRemoteDescription(offer)
const answer = await pc.createAnswer()
await pc.setLocalDescription(answer)
}
// 添加answer(应答)
const answerSdp2 = ref('')
const addAnswer = async () => {
const answer = JSON.parse(answerSdp2.value)
if (!pc.currentRemoteDescription) {
pc.setRemoteDescription(answer)
}
}
onMounted(() => {
console.log(adapter.browserDetails.version);
console.log(adapter.browserDetails.browser);
// 初始化
init()
});
</script>
<template>
<div class="container">
<div>
<video id="local" autoplay playsinline muted></video>
<video id="remote" autoplay playsinline></video>
</div>
<div>
<!-- step1 -->
<p>step1</p>
<el-input v-model="offerSdp"></el-input>
<el-button id="create-offer" type="primary" @click="createOffer">
创建 Offer
</el-button>
<p>step2</p>
<!-- step2 -->
<el-input v-model="offerSdp2" />
<el-button type="success" size="default" @click="createAnswer">
创建 Answer
</el-button>
<el-input v-model="answerSdp" />
<!-- step3 -->
<p>step3</p>
<el-input v-model="answerSdp2" />
<el-button type="primary" size="default" @click="addAnswer">Add Answer</el-button>
</div>
</div>
</template>
<style scoped>
.container{
display: flex;
}
video {
width: 300px;
height: 300px;
}
</style>