跳到正文
裁晨的Blog

使用electron,peerjs实现简单P2P音视频通话及屏幕共享

2023 年的第一周文章,整理一下自己使用 electron,peerjs 实现音视频通话的过程,新的一年与大家共同进步...

技术 1 分钟阅读

2023 年的第一周文章,整理一下自己使用 electron,peerjs 实现音视频通话的过程,新的一年与大家共同进步

  1. 代码使用 TypeScript,vue-setup
  2. 脚手架  electron-vite
  3. 开发环境:window,node:v16.15.1,pnpm:v7.9.0

20230108203757.png

使用 electron 提供的desktopCapturerapi 获取窗口信息

electron 获取窗口以及屏幕

使用该 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)
})
})
}

  1. https://www.electronjs.org/
  2. https://peerjs.com/docs/#api