vue項(xiàng)目基于WebRTC實(shí)現(xiàn)一對一音視頻通話
效果

前端代碼
<template>
<div class="flex items-center flex-col text-center p-12 h-screen">
<div class="relative h-full mb-4 fBox">
<video id="localVideo"></video>
<video id="remoteVideo"></video>
<div v-if="caller && calling">
<p class="mb-4 text-white">等待對方接聽...</p>
<img style="width: 60px;" @click="hangUp" src="@/assets/guaDuang.png" alt="">
</div>
<div v-if="called && calling">
<p>收到視頻邀請...</p>
<div class="flex">
<img style="width: 60px" @click="hangUp" src="@/assets/guaDuang.png" alt="">
<img style="width: 60px" @click="acceptCall" src="@/assets/jieTing.png" alt="">
</div>
</div>
</div>
<div>
<button @click="callRemote" style="margin-right: 10px">發(fā)起視頻</button>
<button @click="hangUp" style="margin-left: 10px">掛斷視頻</button>
</div>
</div>
</template><script>
import { io, Socket } from "socket.io-client";
let roomId = '001';
export default {
name: 'HelloWorld',
props: {
msg: String
},
data(){
return{
wsSocket:null,//實(shí)例
called:false,// 是否是接收方
caller:false,// 是否是發(fā)起方
calling:false,// 呼叫中
communicating:false,// 視頻通話中
localVideo:null,// video標(biāo)簽實(shí)例,播放本人的視頻
remoteVideo:null,// video標(biāo)簽實(shí)例,播放對方的視頻
peer:null,
localStream:null,
}
},
methods:{
// 發(fā)起方發(fā)起視頻請求
async callRemote(){
let that = this;
console.log('發(fā)起視頻');
that.caller = true;
that.calling = true;
// await getLocalStream()
// 向信令服務(wù)器發(fā)送發(fā)起請求的事件
await that.getLocalStream();
that.wsSocket.emit('callRemote', roomId)
},
// 接收方同意視頻請求
acceptCall(){
console.log('同意視頻邀請');
this.wsSocket.emit('acceptCall', roomId)
},
// 掛斷視頻
hangUp(){
this.wsSocket.emit('hangUp', roomId)
},
reset(){
this.called = false;
this.caller = false;
this.calling = false;
this.communicating = false;
this.peer = null;
this.localVideo.srcObject = null;
this.remoteVideo.srcObject = null;
this.localStream = undefined;
console.log('掛斷結(jié)束視頻-------')
},
// 獲取本地音視頻流
async getLocalStream(){
let that = this;
let obj = { audio: true, video: true };
const stream = await navigator.mediaDevices.getUserMedia(obj); // 獲取音視頻流
that.localVideo.srcObject = stream;
that.localVideo.play();
that.localStream = stream;
return stream;
}
},
mounted() {
let that = this;
that.$nextTick(()=>{
that.localVideo = document.getElementById('localVideo');
that.remoteVideo = document.getElementById('remoteVideo');
})
let sock = io('localhost:3000'); // 對應(yīng)服務(wù)的端口
// 連接成功
sock.on('connectionSuccess', (sock) => {
console.log('連接成功:');
});
sock.emit('joinRoom', roomId) // 前端發(fā)送加入房間事件
sock.on('callRemote', (sock) => {
// 如果是發(fā)送方自己收到這個(gè)事件就不用管
if (!that.caller){ // 不是發(fā)送方(用戶A)
that.called = true; // 接聽方
that.calling = true; // 視頻通話中
}
});
sock.on('acceptCall',async ()=>{
if (that.caller){
// 用戶A收到用戶B同意視頻的請求
that.peer = new RTCPeerConnection();
// 添加本地音視頻流
that.peer.addStream && that.peer.addStream(that.localStream);
// 通過監(jiān)聽onicecandidate事件獲取candidate信息
that.peer.onicecandidate = (event) => {
if (event.candidate) {
console.log('用戶A獲取candidate信息', event.candidate);
// 通過信令服務(wù)器發(fā)送candidate信息給用戶B
sock.emit('sendCandidate', {roomId, candidate: event.candidate})
}
}
// 接下來用戶A和用戶B就可以進(jìn)行P2P通信流
// 監(jiān)聽onaddstream來獲取對方的音視頻流
that.peer.onaddstream = (event) => {
console.log('用戶A收到用戶B的stream',event.stream);
that.calling = false;
that.communicating = true;
that.remoteVideo.srcObject = event.stream;
that.remoteVideo.play();
}
// 生成offer
let offer = await that.peer.createOffer({
offerToReceiveAudio: 1,
offerToReceiveVideo: 1
})
console.log('offer', offer);
// 設(shè)置本地描述的offer
await that.peer.setLocalDescription(offer);
// 通過信令服務(wù)器將offer發(fā)送給用戶B
sock.emit('sendOffer', { offer, roomId })
}
})
// 收到offer
sock.on('sendOffer',async (offer) => {
if (that.called){ // 接收方 - 用戶B
console.log('收到offer',offer);
// 創(chuàng)建自己的RTCPeerConnection
that.peer = new RTCPeerConnection();
// 添加本地音視頻流
const stream = await that.getLocalStream();
that.peer.addStream && that.peer.addStream(stream);
// 通過監(jiān)聽onicecandidate事件獲取candidate信息
that.peer.onicecandidate = (event) => {
if (event.candidate) {
console.log('用戶B獲取candidate信息', event.candidate);
// 通過信令服務(wù)器發(fā)送candidate信息給用戶A
sock.emit('sendCandidate', {roomId, candidate: event.candidate})
}
}
// 接下來用戶A和用戶B就可以進(jìn)行P2P通信流
// 監(jiān)聽onaddstream來獲取對方的音視頻流
that.peer.onaddstream = (event) => {
console.log('用戶B收到用戶A的stream',event.stream);
that.calling = false;
that.communicating = true;
that.remoteVideo.srcObject = event.stream;
that.remoteVideo.play();
}
// 設(shè)置遠(yuǎn)端描述信息
await that.peer.setRemoteDescription(offer);
let answer = await that.peer.createAnswer();
console.log('用戶B生成answer',answer);
await that.peer.setLocalDescription(answer);
// 發(fā)送answer給信令服務(wù)器
sock.emit('sendAnswer', { answer, roomId })
}
})
// 用戶A收到answer
sock.on('sendAnswer',async (answer) => {
if (that.caller){ // 接收方 - 用戶A 判斷是否是發(fā)送方
// console.log('用戶A收到answer',answer);
await that.peer.setRemoteDescription(answer);
}
})
// 收到candidate信息
sock.on('sendCandidate',async (candidate) => {
console.log('收到candidate信息',candidate);
// await that.peer.addIceCandidate(candidate) // 用戶A和用戶B分別收到candidate后,都添加到自己的peer對象上
// await that.peer.addCandidate(candidate)
await that.peer.addIceCandidate(candidate)
})
// 掛斷
sock.on('hangUp',()=>{
that.reset()
})
that.wsSocket = sock;
}
}
</script>服務(wù)端代碼
const socket = require('socket.io');
const http = require('http');
const server = http.createServer()
const io = socket(server, {
cors: {
origin: '*' // 配置跨域
}
});
io.on('connection', sock => {
console.log('連接成功...')
// 向客戶端發(fā)送連接成功的消息
sock.emit('connectionSuccess');
sock.on('joinRoom',(roomId)=>{
sock.join(roomId);
console.log('joinRoom-房間ID:'+roomId);
})
// 廣播有人加入到房間
sock.on('callRemote',(roomId)=>{
io.to(roomId).emit('callRemote')
})
// 廣播同意接聽視頻
sock.on('acceptCall',(roomId)=>{
io.to(roomId).emit('acceptCall')
})
// 接收offer
sock.on('sendOffer',({offer,roomId})=>{
io.to(roomId).emit('sendOffer',offer)
})
// 接收answer
sock.on('sendAnswer',({answer,roomId})=>{
io.to(roomId).emit('sendAnswer',answer)
})
// 收到candidate
sock.on('sendCandidate',({candidate,roomId})=>{
io.to(roomId).emit('sendCandidate',candidate)
})
// 掛斷結(jié)束視頻
sock.on('hangUp',(roomId)=>{
io.to(roomId).emit('hangUp')
})
})
server.listen(3000, () => {
console.log('服務(wù)器啟動(dòng)成功');
});完整代碼gitee地址: https://gitee.com/wade-nian/wdn-webrtc.git
參考文章:基于WebRTC實(shí)現(xiàn)音視頻通話_npm create vite@latest webrtc-client
要是在撥打電話過程中,無法打開攝像頭或者麥克風(fēng),瀏覽器也沒有彈出獲取攝像頭及麥克風(fēng)的權(quán)限運(yùn)行,這是需要進(jìn)行瀏覽器安全源的設(shè)置,步驟如下:
1、在 chrome 中 輸入 chrome://flags/#unsafely-treat-insecure-origin-as-secure
2、找到Insecure origins treated as secure
3、添加你服務(wù)器的地址 例如:http://192.168.1.10:8080
4、選擇Enabled屬性

5、點(diǎn)擊右下角的Relaunch即可

到此這篇關(guān)于vue項(xiàng)目基于WebRTC實(shí)現(xiàn)一對一音視頻通話的文章就介紹到這了,更多相關(guān)vue音視頻通話內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Vue使用v-viewer實(shí)現(xiàn)圖片預(yù)覽
這篇文章主要為大家詳細(xì)介紹了Vue使用v-viewer實(shí)現(xiàn)圖片預(yù)覽,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-10-10
解決vue 按鈕多次點(diǎn)擊重復(fù)提交數(shù)據(jù)問題
這篇文章主要介紹了vue 按鈕多次點(diǎn)擊重復(fù)提交數(shù)據(jù)的問題,本文通過實(shí)例結(jié)合的形式給大家介紹的非常詳細(xì),需要的朋友可以參考下2018-05-05
vue實(shí)現(xiàn)消息列表向上無縫滾動(dòng)效果
本文主要實(shí)現(xiàn)vue項(xiàng)目中,消息列表逐條向上無縫滾動(dòng),每條消息展示10秒后再滾動(dòng),為了保證用戶能看清消息主題,未使用第三方插件,本文實(shí)現(xiàn)方法比較簡約,需要的朋友可以參考下2024-06-06
IDEA創(chuàng)建Vue項(xiàng)目的兩種方式總結(jié)
這篇文章主要介紹了IDEA創(chuàng)建Vue項(xiàng)目的兩種方式總結(jié),具有很好的參考價(jià)值,希望對大家有所幫助。2023-04-04
Vue中自動(dòng)生成路由配置文件覆蓋路由配置的思路詳解
這篇文章主要介紹了Vue中自動(dòng)生成路由配置文件覆蓋路由配置的思路詳解,大概思路是讀取@/views下所有index.vue如果當(dāng)前文件下有包含相同路徑則認(rèn)為是它的子路由,需要的朋友可以參考下2024-05-05
element多個(gè)表單校驗(yàn)的實(shí)現(xiàn)
在項(xiàng)目中,經(jīng)常會(huì)遇到表單檢驗(yàn),在這里我分享在實(shí)際項(xiàng)目中遇到多個(gè)表單同時(shí)進(jìn)行校驗(yàn)以及我的解決方法,感興趣的可以了解一下2021-05-05
vue動(dòng)態(tài)綁定組件子父組件多表單驗(yàn)證功能的實(shí)現(xiàn)代碼
這篇文章主要介紹了vue動(dòng)態(tài)綁定組件子父組件多表單驗(yàn)證功能的實(shí)現(xiàn)代碼,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2018-05-05
vue+canvas實(shí)現(xiàn)簡易的九宮格手勢解鎖器
這篇文章主要為大家詳細(xì)介紹了如何流vue+canvas實(shí)現(xiàn)一個(gè)簡易的九宮格手勢解鎖器,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-09-09

