您当前的位置: 首页 > 慢生活 > 程序人生 网站首页程序人生
webrtc實戰實現端對端即p2p視頻通話-運行環境及創建端offer與接收端answer的代碼編寫
发布时间:2022-03-20 20:18:28编辑:雪饮阅读()
運行環境:
運行環境這個webrtc需要調用最常見就是攝像頭,那麽是必須要有https權限,據説在本地環境下就不需要,但是本地環境下如果實現端到端,就算是模擬實現了有什麽意義?端對端自然是要在公網的。當然或許有些特殊場景會用到本地環境算是例外咯。
所以這裏必須要有https環境,我有一個站點就是https環境,我將代碼部署到裏面即可。
交換offer與answer
那麽接下來創建端offer與answer之間的關係是這樣的。
對於創建端創建了offer之後先設置自己本地會話描述為自己生成的offer,然後自己需要將生成的這個offer發送給answer端,接收端接收到創建端發來的offer就將發來的offer設置為接收端的遠端會話描述,然後接收端生成answer並將接收端本地會話描述用answer設置,然後接收端將自己生成的answer發送給創建端,創建端則將接收端發來的answer設置為自己的遠端會話。
這樣就完成了offer與answer的交換。
那麽offer和answer都是webrtc概念中某個api的實例,並不是普通的字符串,但是他們都可以toJSON之後進行發送,接收端用JSON.parse解析成json對象后再使用RTCSessionDescription構造時候傳遞參數即為該json對象就能實例化一個會話描述。
上面的順序很重要,順序不對了,好像就有問題了。
交換candidate
在上面offer與answer交換過程中好像是設置本地會話之後就能產生onicecandidate的回調,在該回調中就能拿到candidate,然後創建端與接收端通過addIceCandidate就能添加彼此的candidate,但是與上面offer與answer的交換一樣,這裏也將面臨對象序列化傳遞問題,那麽同樣的有RTCIceCandidate構造可以支持json對象來構造一個candidate。
而這裏交換candidate時候的順序好像是先將接收端的candidate給創建端,然後創建端的
candidate再給接收端。
建立連接
前面offer與answer的交換只是解決呼叫與應答的場景。
交換candidate是爲了獲取對端的連接信息,比如對方ip地址是多少,端口是多少之類的。
但是即便交換了仍然無法連接,因爲這個candidate的ip地址一般都是内網ip地址并非公網,内網ip地址在公網是無法直接訪問的,需要使用nat技術對來源或目標如端口或ip的修改以實現共通。
那麽在webrtc中這東西可以通過stun或turn服務器來實現。
那麽接下來我這裏采用的是turn服務器。
關於建立連接對turn或者stun等的選擇可以參考這篇文章就講的很細緻了。
那麽這裏我使用我已經建立好的turn服務器。
iceServers=[{
urls:'turn:47.240.19.5:3478',
credential: 'xy220807',
username:'xy'
}];
至於turn服務器下節再講。
代碼編寫與實現
從上面的邏輯流程來看,縂不可能手動交換offer與answer以及candidate吧,實際上是還需要一個信令服務器,這個信令服務器官方標準是socket.io,但實際上我覺得websocket也是可以的,從理論上來講。
這裏我就不折騰了,直接采用手動的方式,這樣更利於理解整個交互流程。
代碼方面就沒有做的很專業,只是很demo的
那麽創建端create.html如:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>create</title>
<script src="https://www.w3school.com.cn/jquery/jquery-1.11.1.min.js"></script>
<!-- 最新版本的 Bootstrap 核心 CSS 文件 -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css" integrity="sha384-HSMxcRTRxnN+Bdg0JdbxYKrThecOKuH5zCYotlSAcp1+c8xmyTe9GYg1l9a69psu" crossorigin="anonymous">
<!-- 可选的 Bootstrap 主题文件(一般不用引入) -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap-theme.min.css" integrity="sha384-6pzBo3FDv/PJ8r2KRkGHifhEocL+1X2rVCTTkUfGk7/0pbek5mMa1upzvWbrUbOZ" crossorigin="anonymous">
<!-- 最新的 Bootstrap 核心 JavaScript 文件 -->
<script src="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js" integrity="sha384-aJ21OjlMXNL5UyIl/XNwTMqvzeRMZH2w8c5cRVpzpU8Y5bApTppSuUkhZXN0VxHd" crossorigin="anonymous"></script>
<script>
var offer;
async function start(){
//发起端
var iceServers=[{
urls:'turn:47.240.19.5:3478',
credential: 'xy220807',
username:'xy'
}];
peerA = new RTCPeerConnection( {
iceServers: iceServers
});
//发起端添加视频流数据
var stream = await navigator.mediaDevices.getUserMedia({video:true,audio:false});
stream.getTracks().forEach(track => {
peerA.addTrack(track, stream)
})
// 添加 candidate(当 peerA 执行 setLocalDescription 函数时会触发 onicecandidate 事件)
peerA.onicecandidate = event => {
if (event.candidate) {
console.log("生成了創建端的candidate",event.candidate);
$("#candidate_json").html(JSON.stringify(event.candidate));
//peerB.addIceCandidate(event.candidate)
}
}
// 检测连接状态
peerA.onconnectionstatechange = event => {
console.log("創建端的對等連接狀態",peerA.connectionState);
if (peerA.connectionState === 'connected') {
console.log('創建端对等连接成功!')
}
}
//發起端接收視頻流
peerA.ontrack=async function (event){
let [ remoteStream ] = event.streams
console.log("從receive端發來了remoteStream",remoteStream);
$("#friend")[0].srcObject=remoteStream;
$("#friend")[0].oncanplay=function(){
$("#friend")[0].play();
};
}
//渲染发起端自己的本地视频流
console.log("create端,本地stream",stream);
$("#my")[0].srcObject=stream;
$("#my")[0].oncanplay=function(){
$("#my")[0].play();
};
offer = await peerA.createOffer()
console.log("offer",offer);
console.log("offer json",offer.toJSON());
$("#offer_json").html(JSON.stringify(offer.toJSON()));
await peerA.setLocalDescription(offer);
}
async function sumitAnswer(){
var answer=$("#answer_input").val();
answer=JSON.parse(answer);
answer=await new RTCSessionDescription(answer);
console.log("answer",answer);
await peerA.setRemoteDescription(answer)
}
async function sumitReceiveCandidate(){
var receive_candidate=$("#receive_candidate").val();
receive_candidate=JSON.parse(receive_candidate);
var addReceiveIceCandidateRes=await peerA.addIceCandidate(new RTCIceCandidate(receive_candidate));
console.log("創建端完成了Candidate的設置",addReceiveIceCandidateRes);
}
</script>
</head>
<body>
<div style="justify-content: center;width:100%;display: flex;" id="videoArea">
<div style="width:100rem;">
<h1 style="text-align: center">我</h1>
<video id="my" height="128" style="border:1px solid pink;width: 100%;"></video>
</div>
<div style="width:100rem;">
<h1 style="text-align: center">对方</h1>
<div>
<video id="friend" height="128" style="border:1px solid blue;width:100%;"></video>
</div>
</div>
</div>
<button onclick="start()">創建offer</button>
<div id="offer">
<label>創建端生成的offer:</label><span id="offer_json"></span>
</div>
<div id="candidate" style="margin-top:10px">
<label>生成的創建端candidate:</label><span id="candidate_json"></span>
</div>
<div style="margin-top:10px">
<textarea id="answer_input" style="display: block" cols="15" rows="5"></textarea>
<button onclick="sumitAnswer()">提交answer</button>
</div>
<div style="margin-top:10px">
<textarea id="receive_candidate" style="display: block"></textarea>
<button onclick="sumitReceiveCandidate()">提交接收端Candidate</button>
</div>
</body>
</html>
然後是接收端
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>receive</title>
<script src="https://www.w3school.com.cn/jquery/jquery-1.11.1.min.js"></script>
<!-- 最新版本的 Bootstrap 核心 CSS 文件 -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css" integrity="sha384-HSMxcRTRxnN+Bdg0JdbxYKrThecOKuH5zCYotlSAcp1+c8xmyTe9GYg1l9a69psu" crossorigin="anonymous">
<!-- 可选的 Bootstrap 主题文件(一般不用引入) -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap-theme.min.css" integrity="sha384-6pzBo3FDv/PJ8r2KRkGHifhEocL+1X2rVCTTkUfGk7/0pbek5mMa1upzvWbrUbOZ" crossorigin="anonymous">
<!-- 最新的 Bootstrap 核心 JavaScript 文件 -->
<script src="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js" integrity="sha384-aJ21OjlMXNL5UyIl/XNwTMqvzeRMZH2w8c5cRVpzpU8Y5bApTppSuUkhZXN0VxHd" crossorigin="anonymous"></script>
<script>
var offer;
var answer;
async function sumitOffer(){
var iceServers=[{
urls:'turn:47.240.19.5:3478',
credential: 'xy220807',
username:'xy'
}];
peerB = new RTCPeerConnection({
iceServers:iceServers
});
//接收端添加视频流数据
var stream = await navigator.mediaDevices.getUserMedia({video:true,audio:false});
stream.getTracks().forEach(track => {
peerB.addTrack(track, stream)
})
// 检测连接状态
peerB.onconnectionstatechange = event => {
console.log("接收端的對等連接狀態",peerB.connectionState);
if (peerB.connectionState === 'connected') {
console.log('接收端对等连接成功!')
}
}
//接收端接收視頻流
peerB.ontrack=async function (event){
let [ remoteStream ] = event.streams
console.log("從create端發來了remoteStream",remoteStream);
$("#friend")[0].srcObject=remoteStream;
$("#friend")[0].oncanplay=function(){
$("#friend")[0].play();
};
}
//渲染接收端自己的本地视频流
console.log("receive端,本地stream",stream);
$("#my")[0].srcObject=stream;
$("#my")[0].oncanplay=function(){
$("#my")[0].play();
};
peerB.onicecandidate = event => {
if (event.candidate) {
console.log("生成了接收端的candidate",event.candidate);
console.log("生成了接收端的candidate(string)",JSON.stringify(event.candidate));
$("#receive_candidate_json").html(JSON.stringify(event.candidate));
}
}
var offer=$("#offer_input").val();
offer=JSON.parse(offer);
offer=await new RTCSessionDescription(offer);
console.log("offer",offer);
await peerB.setRemoteDescription(offer);
answer = await peerB.createAnswer();
console.log("answer",answer);
console.log("answer json",answer.toJSON());
console.log("接收端生成了answer",JSON.stringify(answer.toJSON()));
$("#answer_json").html(JSON.stringify(answer.toJSON()));
await peerB.setLocalDescription(answer);
}
async function sumitCreateCandidate(){
var createCandidate=$("#create_candidate").val();
createCandidate=JSON.parse(createCandidate);
var addIceCandidateRes=await peerB.addIceCandidate(new RTCIceCandidate(createCandidate));
console.log("接收端完成了Candidate的設置",addIceCandidateRes);
}
</script>
</head>
<body>
<div style="justify-content: center;width:100%;display: flex;" id="videoArea">
<div style="width:100rem;">
<h1 style="text-align: center">我</h1>
<video id="my" height="128" style="border:1px solid pink;width: 100%;"></video>
</div>
<div style="width:100rem;">
<h1 style="text-align: center">对方</h1>
<div>
<video id="friend" height="128" style="border:1px solid blue;width:100%;"></video>
</div>
</div>
</div>
<div style="margin-top:10px">
<div>
<div>
<label>offer:</label>
</div>
<div>
<textarea id="offer_input"></textarea>
</div>
</div>
<button onclick="sumitOffer()">提交offer(生成answer)</button>
</div>
<div id="answer" style="margin-top:10px">
<label>生成的接收端answer:</label><span id="answer_json"></span>
</div>
<div id="receive_candidate" style="margin-top:10px">
<label>生成的接收端candidate:</label><span id="receive_candidate_json"></span>
</div>
<div style="margin-top:10px">
<div>
<div>
<label>創建端candidate:</label>
</div>
<div>
<textarea id="create_candidate"></textarea>
</div>
</div>
<button onclick="sumitCreateCandidate()">提交創建端candidate</button>
</div>
</body>
</html>
很糙的界面。。。。

关键字词:webrtc,offer,answer,candidate,順序,流程