上一小节用Mesh方案实现了多人音视频,代码非常简单。
然后再来看看如何使用 MCU方案来实现,没钱!那就再来看看 SFU方案 如何实现,还没那技术!这节就来看看如何使用peerjs
库来实现音视频通话。这个库用起来相当方便,简直是有手就行!!但很奇怪,关于这个库的文章比较少,看了几篇都是几年前的???还是说我没找到???OK,到此为止。有我这一篇就够了,看了===会了。
这里先给出官方地址,它的github地址是这里,文档地址是这里,可自行前往。
介绍
peerjs库的示例代码很简单,使用起来主要分为两个部分:Data connections
和Media calls
,这俩也就对应peerConnectionDataChannel
和peerConnectionMedia
,也就是媒体流和数据通道。使用时只需要传入一个id,然后需要传输数据则调用peer.connect()
,或者音视频则调用peer.call()
方法。之后,peerjs就会自动帮你进行连接,省略了SDP
交换和ICE
候选的过程,直接一步到位给你连接上。你只需要监听对应的事件就行,传输数据监听data
类型,音视频监听stream
类型,相当简单,在事件中就能拿到数据或媒体流。
peerjs库还配有后端PeerServer库,当然你也可以自行实现。后端的话,也是非常简单,示例代码已经写的很详细了,这里就不说了。这里只提一个点,就是当你使用peerjs库和PeerServer库时,要注意单独给PeerServer起个服务。就是说你在后端要起两个服务,一个专门给peer使用,另一个才是socket服务。这里一定要注意,不然的话,你会发现连着一段时间后自动断开重连了,或者报错了。关于这个问题,废了好些劲,在论坛里找到2015年的帖子,仔细看了才解决的!!!帖子在这里,里面只是列出了大概的,但提到了解决方法。
剩下的,关于peer的事件和属性啥的,都在文档里写着,稍微看看就知道咋用了。还是看不懂的话,可以看看我的代码,下面会列出来。
实操
首先,这里先把后端给列出来,此代码是在webrtc之实现简单信令服务器的基础上新增的。加下面这段代码就行了
// 必须专门起个服务给 peerjs 不然会隔段时间 自动断开
const { ExpressPeerServer } = require("peer");
const peerApp = express();
const peerServer = http.createServer(peerApp);
peerApp.use("/",ExpressPeerServer(peerServer,{
debug: true,
path: "/myapp",
}));
peerServer.listen(8089,() => {
console.log('服务启动成功 *:8089');
})
server.listen(9000,() => {
console.log('服务启动成功 *:9000');
})
io.on('connection',(socket) => {
onListener(socket)
})
OK,后端起个服务就行了。
再看看前端如何使用的,这里也是分为了音视频通话和消息发送两种。还记得之前的音视频通话过程吗?在进行通话和发消息之前先询问对方是否愿意,如果愿意再进行连接,不愿意就换一个。这里也是一样的逻辑,代码就不列出来了,忘了就往回看看。
当接收方收到发起方的询问时,如果选择了同意,那么会回复发起方,同时本地进行初始化peer
。在open
事件中可以拿到自动生成的id
,有了ID再触发peerId
事件,将ID发送给发起方。然后发起方监听peerId
事件,触发onReceivePeerId
事件进行初始化peer
,与接收到的ID进行连接。
突然想到一个问题,既然peer
的ID是可以自定义的,那就可以在回复发起方的询问时创建一个ID,然后在回复时一起发送过去,这样发起方就不用再额外监听peerId
事件了,在收到接收方的回复时就可以进行peer
的初始化了。唯一的点在于当发起方初始化后,接收方是否已经初始化完成了,他没初始化完成肯定是连接不上。那就还得一个事件来监听是否初始化完成,还是算了吧0.0
现在分析一下过程:
- 接收方在同意音视频/消息通讯后,进行初始化
peer
,然后监听一系列事件。并在peer
初始化完成后将id
发送给发起方。 - 发起方接收到回复后,同意的话则设置连接状态为连接中,然后等待接收方的
peerId
。 - 发起方接收到
peerId
后,进行初始化peer
,并连接对方。双方完成连接开始通讯。
看一看代码实现:
// 初始化 peer
const initPeer = (isReceiver = false, noticeType) => {
const toUserId = remoteUserInfo.id; // 远程ID
const randomId = getRandom();
localPeer.value = new Peer(randomId, {
host: "localhost",
port: 8089,
path: "/myapp",
debug: 3,
});
localPeer.value.on("open", function (id) {
if (isReceiver) {
// 如果是接收方 将自己的id 发送给对方
linkSocket.value.emit("peerId", {
...userInfo,
peerId: id,
toUserId,
noticeType,
});
// data channel
localPeer.value.on("connection", (conn) => {
localPeerConn.value = conn;
onConnEvent(conn);
});
// call channel
localPeer.value.on("call", (call) => {
localPeerCall.value = call;
call.answer(localStream.value);
onCallEvent(call, noticeType);
});
} else {
if (noticeType === "message") {
const conn = localPeer.value.connect(remoteUserInfo.peerId);
localPeerConn.value = conn;
onConnEvent(conn);
} else {
const call = localPeer.value.call(
remoteUserInfo.peerId,
localStream.value
);
localPeerCall.value = call;
onCallEvent(call, noticeType);
}
}
});
// 监听错误事件
localPeer.value.on("error", (error) => {
console.log(error, "error");
if (error.type === "network") {
// 尝试重新连接
// setTimeout(() => {
// localPeer.value.reconnect();
// }, 5000);
} else {
handleOnError();
}
});
// 监听关闭事件
localPeer.value.on("close", () => {
console.log("断开连接");
handleOnClose();
});
};
// 消息通信事件
const onConnEvent = (conn) => {
// 连接打开时
conn.on("open", () => {
console.log("连接成功");
remoteUserInfo.status = "1"; // 连接成功
// 连接成功
linkSocket.value.emit("connectionStatus", {
...userInfo,
toUserId: remoteUserInfo.id,
status: 1,
});
// 接收对方发送的消息
conn.on("data", (data) => {
console.log("receive", data);
messageList.value.push({
...remoteUserInfo,
toUserId: userInfo.id,
msg: data,
});
});
});
// 连接关闭 一方关闭 另一方也可以接收到消息
// conn.on("close", () => {
// console.log("断开连接");
// handleOnClose();
// });
// 连接错误
// conn.on("error", (error) => {
// console.log(error, "error");
// handleOnError();
// });
};
// 音视频通信事件
const onCallEvent = (call, noticeType) => {
call.on("stream", (stream) => {
console.log("收到对方的流", stream);
setDomStream(noticeType, stream, true);
});
call.on("error", (error) => {
console.log(error, "error");
handleOnError();
});
call.on("close", () => {
console.log("call close");
handleOnClose();
});
};
// 接收到对方的peerId
const onReceivePeerId = async (data) => {
const { peerId, noticeType } = data;
remoteUserInfo.peerId = peerId;
remoteUserInfo.noticeType = noticeType;
console.log("接收到对方peerId", peerId);
try {
if (noticeType !== "message") {
await initDevices();
await initMedia(noticeType);
}
// 初始化 peer
initPeer(false, noticeType);
userInfo.deviceStatus = 0;
} catch (error) {
console.log(error, "error");
userInfo.deviceStatus = 5;
}
};
到这里,双方的音视频通话就实现了,要加一些基本功能的话,可以参考之前写的webrtc之音视频实用功能。
如果是双方发消息的话,可以参考下面这个代码:
const handleSendMsg = () => {
if (!localPeerConn.value || !message.value) return;
switch (remoteUserInfo.status) {
case "3":
ElMessage({
message: "对方已离线!",
type: "error",
});
break;
case "4":
ElMessage({
message: "连接失败,请稍后再试!",
type: "error",
});
break;
case "5":
ElMessage({
message: "对方已离开!",
type: "error",
});
break;
default:
localPeerConn.value.send(message.value);
messageList.value.push({
...userInfo,
toUserId: remoteUserInfo.id,
msg: message.value,
});
message.value = "";
break;
}
};
小节
本小节实现了使用peerjs
库来实现音视频通话和消息通讯。简单介绍了peerjs
的使用,以及分析了其使用的过程,其实和原生使用webrtc
没什么太大区别,只是屏蔽了SDP
交换和ICE
收集的过程。另外,你需要配置ssl
、STUN
协议、TURN
协议和一些服务端的配置也是可以加的。
peerjs
简单,但是你并不知道其中的实现过程,出了问题比较难排查。而自己使用webrtc
来实现,复杂,但你能清楚的知道每个步骤。
下一小节暂定