使用electron,peerjs实现简单P2P音视频通话及屏幕共享
2023 年的第一周文章,整理一下自己使用 electron,peerjs 实现音视频通话的过程,新的一年与大家共同进步...
2023 年的第一周文章,整理一下自己使用 electron,peerjs 实现音视频通话的过程,新的一年与大家共同进步
- 代码使用 TypeScript,vue-setup
- 脚手架 electron-vite
- 开发环境:window,node:v16.15.1,pnpm:v7.9.0
使用 electron 提供的
desktopCapturerapi 获取窗口信息使用该 api 的原因是因为在渲染进程中需要使用窗口 id 才能获取到流 实现代码如下
function getScreen() { ipcMain.handle('getScreenList', async (_event) => { const callback = () => { return new Promise((resolve) => { desktopCapturer.getSources({ types: ['screen'] }).then((sources) => { let list: sourcesOption[] = []; for (const item of sources) { list.push({ id: item.id, name: item.name, thumbnail: item.thumbnail.toDataURL(), }); } resolve(list); }); }); }; return await callback(); });}<template> <div class="call" id="call-view"> <div class="head drag"> <div class="left"></div> <div class="center">{{ friend }}</div> <div class="right"></div> </div> <div class="content"> <n-spin :show="loading"> <template #description>等待对方接听...</template> <div class="view card"> <video ref="friendVideoRef" class="video-view"></video> </div> </n-spin> <div class="list"> <div class="card user"> <video ref="userVideoRef" class="user-video"></video> </div> </div> </div> <div class="foot"> <n-space> <n-button strong secondary> <template #icon> <n-icon size="22"><MicOff24Regular /></n-icon> </template> </n-button> <n-button strong secondary @click="onMedia(true)"> <template #icon> <n-icon size="22"><VideoOff24Regular /></n-icon> </template> </n-button> <n-button strong secondary type="error" @click="onCallQuit"> <template #icon> <n-icon size="22"><CloseOutline /></n-icon> </template> </n-button> </n-space> </div> </div></template>const screenList = await window.electron.ipcRenderer.invoke('getScreenList');const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: { // @ts-ignore mandatory: { chromeMediaSource: 'desktop', chromeMediaSourceId: item.id, }, },});获取相机视频流相对简单,只需要在渲染进程中实现即可
const getMediaDevices = (): Promise<MediaDeviceInfo[]> => { return new Promise((resolve) => { navigator.mediaDevices.enumerateDevices().then((res) => { let list: MediaDeviceInfo[] = [] for (const iterator of res) { //从音视频设备中筛选视频设备 if (iterator.kind === 'videoinput') { list.push(iterator) } } resolve(list) }) })}const mediaDeviceList = await getMediaDevices()Peer 官网 可以使用 peer 提供的 server 服务也可以使用 peer-server 搭建自己的服务端
1.导入 peerjs 中方法
//DataConnection为好友对象以实现对好友发送的音视频变更消息进行监听//MediaConnection为连接通话后的音视频对象以实现对音视频流的状态监听import { DataConnection, MediaConnection, Peer } from 'peerjs';2.初始化以及实现基础事件监听
import { DataConnection, MediaConnection, Peer } from 'peerjs';
const TAG = '[PEER]';export class CallClient { client: Peer; frienid?: DataConnection; call?: MediaConnection; remoteStream?: MediaStream; callCallback?: any; hangUpCallback?: any; constructor(user: string) { //传入用户id,自定义 this.client = new Peer(user, { host: '192.168.0.105', port: 9000, path: '/myapp', }); this.event(); } onCallAddEventListener(callback) { this.callCallback = callback; } onHangUpAddEventListener(callback) { this.hangUpCallback = callback; }
event() { this.client.on('open', async (id) => { console.log(TAG, 'peerJs服务连接成功:' + id); }); //当消息连接成功时触发 this.client.on('connection', (val) => { this.frienid = val; this.frienid.on('data', (data) => { this.onFriendData(data); }); }); //当接入通话时触发 this.client.on('call', async (call) => { this.call = call; this.callCallback(); }); this.client.on('close', function () { console.log(TAG, 'close'); }); this.client.on('error', (e) => { console.log(TAG, 'error', e); }); }
onHangUp() { this.call?.close(); }
//实现对音视频自定义消息的处理 onFriendData(data) { console.log(TAG, 'frienid', data); switch (data) { //挂断 case 'closecall': this.call = undefined; this.frienid = undefined; this.remoteStream = undefined; this.hangUpCallback(); break; default: break; } }}3.实现呼叫的方法
//传入好友的用户id,以及本地视频流onCallTo(friendId: string, stream: MediaStream): Promise<MediaStream> { return new Promise((resolve) => { //创建消息连接 this.frienid = this.client.connect(friendId) //处理音视频消息 this.frienid.on('data', (data) => { this.onFriendData(data) }) this.call = this.client.call(friendId, stream) //己方挂断时向对方发送挂断消息 this.call.on('close', () => { console.log(TAG, 'call-close') this.frienid?.send('closecall') }) //监听对方流的加入,并回调自定义渲染方法 this.call.on('stream', (remoteStream) => { this.remoteStream = remoteStream resolve(remoteStream) }) })}4.实现加入通话的方法
//传入本地视频流onJoinCall(stream: MediaStream): Promise<MediaStream> { return new Promise((resolve) => { this.call!.answer(stream) //己方挂断时向对方发送挂断消息 this.call!.on('close', () => { console.log(TAG, 'call-close') this.frienid?.send('closecall') }) //监听对方流的加入,并回调自定义渲染方法 this.call!.on('stream', (remoteStream) => { this.remoteStream = remoteStream resolve(remoteStream) }) })}