vue項(xiàng)目基于WebRTC實(shí)現(xiàn)一對(duì)一音視頻通話
效果
前端代碼
<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">等待對(duì)方接聽...</p> <img style="width: 60px;" @click="hangUp" src="@/assets/guaDuang.png" alt=""> </div> <div v-if="called && calling"> <p>收到視頻邀請(qǐng)...</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í)例,播放對(duì)方的視頻 peer:null, localStream:null, } }, methods:{ // 發(fā)起方發(fā)起視頻請(qǐng)求 async callRemote(){ let that = this; console.log('發(fā)起視頻'); that.caller = true; that.calling = true; // await getLocalStream() // 向信令服務(wù)器發(fā)送發(fā)起請(qǐng)求的事件 await that.getLocalStream(); that.wsSocket.emit('callRemote', roomId) }, // 接收方同意視頻請(qǐng)求 acceptCall(){ console.log('同意視頻邀請(qǐng)'); 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'); // 對(duì)應(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同意視頻的請(qǐng)求 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來獲取對(duì)方的音視頻流 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來獲取對(duì)方的音視頻流 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對(duì)象上 // 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)一對(duì)一音視頻通話的文章就介紹到這了,更多相關(guān)vue音視頻通話內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vue+springboot實(shí)現(xiàn)項(xiàng)目的CORS跨域請(qǐng)求
這篇文章主要介紹了vue+springboot實(shí)現(xiàn)項(xiàng)目的CORS跨域請(qǐng)求,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-09-09el-table實(shí)現(xiàn)轉(zhuǎn)置表格的示例代碼(行列互換)
這篇文章主要介紹了el-table實(shí)現(xiàn)轉(zhuǎn)置表格的示例代碼(行列互換),本文結(jié)合示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2024-02-02Vue 2.5.2下axios + express 本地請(qǐng)求404的解決方法
下面小編就為大家分享一篇Vue 2.5.2下axios + express 本地請(qǐng)求404的解決方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2018-02-02vue+webpack實(shí)現(xiàn)異步加載三種用法示例詳解
這篇文章主要介紹了vue+webpack實(shí)現(xiàn)異步加載的三種用法,文中給大家提到了vue+webpack實(shí)現(xiàn)異步組件加載的代碼,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友參考下吧2018-04-04淺談vue3中ref、toRef、toRefs?和?reactive的區(qū)別
本文主要介紹了淺談vue3中ref、toRef、toRefs?和?reactive的區(qū)別,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-07-07vue使用vuedraggable對(duì)列表進(jìn)行拖拽排序
vuedraggable 是一個(gè)基于 Vue 的拖拽排序組件,它可以讓你輕松地在 Vue 應(yīng)用中實(shí)現(xiàn)拖拽排序功能,下面就跟隨小編一起來了解下它的具體應(yīng)用吧2024-12-12