前言
有个项目需要用到视频技术以及录制音频等,就去研究了一下。当然很多是有个思路之后借助AI的。
图挺复杂的,我就自己画了一个简单点的,也当作一个思路。
自己先建一个vue项目,因为我这里是个demo,所以我router和store都没建。
创建完之后先加载一个socket.io。
npm i socket.io-client
实时视频
首先是加入房间,其实就是弄一个roomId,让两个人都加入之后,服务端发送的时候直接往这个roomid里面的用户发送就行。我这里就都设置为001。
发起邀请
点击发送邀请后会开启本地的视频,生成stream流,然后把video的src设置为这个stream流。
<script setup>
import { ref, onMounted } from 'vue'
import { io } from 'socket.io-client';
const socket = ref(null)
const roomid = '001'
const localStream = ref(null)//本人视频流
const localVideo = ref(null)//播放本人视频的video
const inviter = ref(false)//是否是邀请的人
const Beinviter = ref(false)//是否是被邀请的人
// 获取视频和音频流
const getLocalStream = async () => {
const stream = await navigator.mediaDevices.getUserMedia({
audio: true,
video: { facingMode: "user" }
})
localVideo.value.srcObject = stream
localVideo.value.play()
localStream.value = stream
return stream
}
onMounted(() => {
socket.value = io('http://127.0.0.1:3000', {
transports: ['websocket'], // 指定传输方式,如WebSocket
autoConnect: true, // 是否自动连接
reconnection: true, // 是否自动重新连接
reconnectionAttempts: 3, // 重新连接尝试次数
reconnectionDelay: 1000, // 重新连接延迟时间(毫秒)
})
socket.value.emit('joinroom', roomid)
})
const invite = async () => {
inviter.value = true
await getLocalStream()
// getOffice()
socket.value.emit('invite', roomid)
}
</script>
<template>
<div style="display: flex;text-align: center;">
<div class="box" style="width: 200px; height: 300px; border: 1px solid black; margin: 20px auto;">
<h2>自己的</h2>
<video ref="localVideo" style="width: 100%; height: 100%;" autoplay muted playsinline></video>
</div>
<div class="box" style="width: 200px; height: 300px; border: 1px solid black; margin: 20px auto;">
<h2>对方的</h2>
<video ref="remoteVideo" style="width: 100%; height: 100%;" autoplay muted playsinline></video>
</div>
<div class="box" style="width: 200px; height: 300px; border: 1px solid black; margin: 20px auto;">
<h2>录制的</h2>
<video ref="recorderVideo" style="width: 100%; height: 100%;" controls></video>
</div>
<div class="box" style="width: 200px; height: 50px; border: 1px solid black; margin: 20px auto;">
<h2>录制的音频</h2>
<audio ref="recorderAudio" controls></audio>
</div>
</div>
<div style="width: 100%;
height: 300px;"></div>
<button @click="invite">发起</button>
<button @click="agree">接受</button>
<button @click="beginRecorder">开启录制</button>
<button @click="overRecorder">结束录制</button>
<button @click="beginRecorderAudio">只录制音频</button>
<button @click="overRecorderAudio">结束录制音频</button>
<button>挂断</button>
</template>
<style scoped>
</style>
搭建一个服务端来转发。
const express = require('express');
const http = require('http');
const socketIo = require('socket.io');
const cors = require('cors');
const app = express();
const server = http.createServer(app);
const io = socketIo(server);
let connectedUsers = {};
const PORT = process.env.PORT || 3000;
let index=0
// 允许来自所有来源的跨域请求
app.use(cors());
io.on('connection', (socket) => {
socket.emit('connectsuccess', socket.id)
// 存储连接的用户
connectedUsers[socket.id] = socket;
socket.on('disconnect', () => {
console.log('A user disconnected');
});
socket.on('joinroom', value => {
socket.join(value)
})
socket.on('invite', roomid => {
socket.to(roomid).emit('callRemote')
})
});
server.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});
然后本地的视频就可以了!(后面的再一一实现 )
接收邀请
对方接收到之后,需要首先接收,然后点击接收之后再发送一次给服务端,告诉发起者我同意了。
//接收邀请
socket.value.on('callRemote', () => {
if (!inviter.value) {
Beinviter.value = true
console.log('收到邀请')
}
})
const agree = () => {
socket.value.emit('replyInvite', {
reply: true,
roomid
})
}
同样服务端要转发同样的事件。
//服务端
socket.on('replyInvite', ({ reply, roomid }) => {
console.log(reply)
if (reply) {
socket.to(roomid).emit('otherReply', true)
}
})
邀请者生成offer
邀请者收到对方的同意之后就可以生成offer并发送出去了。
const peer = ref(null)
const offer = ref(null)
peer.value = new RTCPeerConnection()
//收到对方同意邀请
socket.value.on('otherReply', async (value) => {
if (value) {
if (inviter.value) {
await getOffice()//生成offer
console.log('生成offer', offer.value)
socket.value.emit('sendOffer', {
offer: offer.value,
roomid
})
}
}
})
//生成offer
const getOffice = async () => {
if (localStream.value) {
peer.value.addStream(localStream.value)
const offerCreated = await peer.value.createOffer({
offerToReceiveAudio: 1,
offerToReceiveVideo: 1
})
offer.value = offerCreated
await peer.value.setLocalDescription(offerCreated)
// getAnswer()
} else {
console.error('Local stream is not available.')
}
}
同样管理端要转发offer
socket.on('sendOffer', ({ offer, roomid }) => {
console.log(roomid)
socket.to(roomid).emit('otherOffice', offer)
})
被邀请者收到offer。
socket.value.on('otherOffice', async (Office) => {
if (Beinviter.value) {
console.log('收到offer',Office)
}
})
一边点邀请,一边点接收就可以了。
接收者打开本地视频并生成answer
接收者收到offer就可以打开视频并且发送answer了。
socket.value.on('otherOffice', async (Office) => {
if (Beinviter.value) {
offer.value = Office
console.log('收到Office', Office)
//添加本地音视频流
const stream = await getLocalStream()
peer.value.addStream(stream)
//设置远端描述信息SDP
await peer.value.setRemoteDescription(offer.value)
//生成answer
const answer = await peer.value.createAnswer()
console.log('生成answer', answer)
//在本地设置answer信息
await peer.value.setLocalDescription(answer)
//发送answer
socket.value.emit('sendAnswer', { answer: answer, roomid: roomid })
}
})
服务端转发answer