From 10cb9463da1d18594316377446c932070dd44d66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=9C=E6=99=93=E6=B5=A9?= <1923911906@qq.com> Date: Thu, 30 May 2024 02:07:20 +0800 Subject: [PATCH] perf: express and local-core --- packages/local-chat-x-core/index.ts | 4 +- packages/local-chat-x-core/libp2p.ts | 78 ++--- .../src/controllers/libp2p.ts | 316 +++++++++++------- .../src/controllers/peer.ts | 40 ++- packages/web-chat-x-express/src/main.ts | 23 +- .../web-chat-x-vue/src/classes/PeerManager.ts | 108 +++--- 6 files changed, 320 insertions(+), 249 deletions(-) diff --git a/packages/local-chat-x-core/index.ts b/packages/local-chat-x-core/index.ts index 48b445f..814985e 100644 --- a/packages/local-chat-x-core/index.ts +++ b/packages/local-chat-x-core/index.ts @@ -13,7 +13,7 @@ app.use(morgan("combined")); app.get("/test", (_req, res) => { res.send({ type: "message", - message: "you testing the local-webchat-x-core http://127.0.0.1:6666", + message: "you testing the local-webchat-x-core http://127.0.0.1:" + port, }); }); @@ -24,6 +24,6 @@ app.get("/libp2p", async (_req, res) => { app.listen(port, () => { console.log( - `local relay node running with prot ${port},http://127.0.0.1:6666` + `local relay node running with prot ${port},http://127.0.0.1:${port}` ); }); diff --git a/packages/local-chat-x-core/libp2p.ts b/packages/local-chat-x-core/libp2p.ts index 8399c4d..24ff8c1 100644 --- a/packages/local-chat-x-core/libp2p.ts +++ b/packages/local-chat-x-core/libp2p.ts @@ -101,7 +101,7 @@ export default class Libp2pManager { }), pubsubPeerDiscovery({ interval: 10000, - topics: topics, // defaults to ['_peer-discovery._p2p._pubsub'] + topics: topics, listenOnly: false, }), ], @@ -109,15 +109,15 @@ export default class Libp2pManager { }); } private handleListenEvent() { - // this.libp2p.addEventListener("connection:open", (connection) => { - // console.log("connection:open", connection.detail); - // }); - // this.libp2p.addEventListener("connection:close", (connection) => { - // console.log("connection:open", connection.detail); - // }); - // this.libp2p.addEventListener("connection:prune", (connection) => { - // console.log("connection:prune", connection.detail); - // }); + this.libp2p.addEventListener("connection:open", (connection) => { + console.log("connection:open", connection.detail); + }); + this.libp2p.addEventListener("connection:close", (connection) => { + console.log("connection:open", connection.detail); + }); + this.libp2p.addEventListener("connection:prune", (connection) => { + console.log("connection:prune", connection.detail); + }); this.libp2p.addEventListener("peer:connect", (peerId) => { console.log("peer:connect ", peerId.detail); }); @@ -128,40 +128,42 @@ export default class Libp2pManager { console.log("peer:discovery ", peerIdInfo.detail); }); - // this.libp2p.addEventListener("peer:identify", (identifyResult) => { - // console.log("peer:identify ", identifyResult.detail); - // }); - // this.libp2p.addEventListener("peer:update", (peerUpdate) => { - // console.log("peer:update ", peerUpdate.detail); - // }); - // this.libp2p.addEventListener("self:peer:update", (peerUpdate) => { - // console.log("self:peer:update ", peerUpdate.detail); - // console.log("self:peer:update protocols", libp2p?.getProtocols()); - // console.log("self:peer:update multiaddrs", libp2p?.getMultiaddrs()); - // libp2p?.getMultiaddrs().forEach((multiaddr, index) => { - // console.log(`self:peer:update multiaddr ${index} ${multiaddr.toString()}`); - // }); - // console.log("self:peer:update dialQueue", libp2p?.getDialQueue()); - // console.log("self:peer:update connections", libp2p?.getConnections()); - // console.log("self:peer:update peers", libp2p?.getPeers()); - // }); + this.libp2p.addEventListener("peer:identify", (identifyResult) => { + console.log("peer:identify ", identifyResult.detail); + }); + this.libp2p.addEventListener("peer:update", (peerUpdate) => { + console.log("peer:update ", peerUpdate.detail); + }); + this.libp2p.addEventListener("self:peer:update", (peerUpdate) => { + console.log("self:peer:update ", peerUpdate.detail); + console.log("self:peer:update protocols", this.libp2p.getProtocols()); + console.log("self:peer:update multiaddrs", this.libp2p.getMultiaddrs()); + this.libp2p.getMultiaddrs().forEach((multiaddr, index) => { + console.log( + `self:peer:update multiaddr ${index} ${multiaddr.toString()}` + ); + }); + console.log("self:peer:update dialQueue", this.libp2p.getDialQueue()); + console.log("self:peer:update connections", this.libp2p.getConnections()); + console.log("self:peer:update peers", this.libp2p.getPeers()); + }); this.libp2p.addEventListener("start", () => { console.log("start"); - // console.log("protocols", libp2p?.getProtocols()); - // console.log("multiaddrs", libp2p?.getMultiaddrs()); - // console.log("dialQueue", libp2p?.getDialQueue()); - // console.log("connections", libp2p?.getConnections()); - // console.log("peers", libp2p?.getPeers()); + console.log("protocols", this.libp2p.getProtocols()); + console.log("multiaddrs", this.libp2p.getMultiaddrs()); + console.log("dialQueue", this.libp2p.getDialQueue()); + console.log("connections", this.libp2p.getConnections()); + console.log("peers", this.libp2p.getPeers()); }); this.libp2p.addEventListener("stop", () => { console.log("stop"); }); - // this.libp2p.addEventListener("transport:close", (listener) => { - // console.log("transport:close", listener.detail); - // }); - // this.libp2p.addEventListener("transport:listening", (listener) => { - // console.log("transport:listening", listener.detail); - // }); + this.libp2p.addEventListener("transport:close", (listener) => { + console.log("transport:close", listener.detail); + }); + this.libp2p.addEventListener("transport:listening", (listener) => { + console.log("transport:listening", listener.detail); + }); } private handleProtocol() {} diff --git a/packages/web-chat-x-express/src/controllers/libp2p.ts b/packages/web-chat-x-express/src/controllers/libp2p.ts index 566525e..e1cad49 100644 --- a/packages/web-chat-x-express/src/controllers/libp2p.ts +++ b/packages/web-chat-x-express/src/controllers/libp2p.ts @@ -6,148 +6,206 @@ import { circuitRelayServer } from "@libp2p/circuit-relay-v2"; import { webSockets } from "@libp2p/websockets"; import { webRTC } from "@libp2p/webrtc"; import * as filters from "@libp2p/websockets/filters"; -import type { Multiaddr } from "@multiformats/multiaddr"; import { identify } from "@libp2p/identify"; -import { KadDHT, kadDHT } from "@libp2p/kad-dht"; -import { peerIdFromKeys } from "@libp2p/peer-id"; -import { parsePrivateKeySecret } from "../utils/parseSecret.js"; +import { + KadDHT, + kadDHT, + removePrivateAddressesMapper, + removePublicAddressesMapper, +} from "@libp2p/kad-dht"; +import { createEd25519PeerId } from "@libp2p/peer-id-factory"; import { mdns } from "@libp2p/mdns"; import { gossipsub } from "@chainsafe/libp2p-gossipsub"; -import { Server } from "https"; import { pubsubPeerDiscovery } from "@libp2p/pubsub-peer-discovery"; import { tls } from "@libp2p/tls"; +import { prometheusMetrics } from "@libp2p/prometheus-metrics"; import { bootstrap } from "@libp2p/bootstrap"; const topics = [ `webChatX._peer-discovery._p2p._pubsub`, // It's recommended but not required to extend the global space ]; -/** - * 定义 RelayServiceOptions 类型,用于配置启动和停止回调函数 - * */ -type RelayServiceOptions = { - onStarted?: (listenMultiaddrArray: Multiaddr[]) => void; // 当 Relay 服务成功启动时调用的回调,接收监听地址作为参数 - onStopped?: () => void; // 当 Relay 服务成功停止时调用的回调 -}; +export default class Libp2pManager { + libp2p!: Libp2p; + constructor() {} + create() {} -/** - * 启动 Relay 服务的异步函数。接受可选的 RelayServiceOptions 参数以配置启动和停止回调。 - * */ -async function startRelayService( - options?: RelayServiceOptions, - server?: Server -): Promise { - let libp2p: Libp2p | undefined; - - try { - // 创建并初始化 Relay 节点 - libp2p = await createRelayNode(server); - (libp2p.services.dht as KadDHT).setMode("server"); - // 获取 Relay 节点的监听地址,并确保找到一个 IPv4 地址 - const listenMultiaddr = libp2p.getMultiaddrs(); - // 如果提供了 onStarted 回调,则在服务启动后调用它,传入监听地址 - if (options?.onStarted) { - options.onStarted(listenMultiaddr); - } - } catch (error) { - // 如果启动过程中发生错误,打印错误信息并尝试停止已创建的 Relay 节点 - console.error("Failed to start relay service:", error); - if (libp2p) { - await stopRelayService(libp2p); - } - return; + /** + * 创建一个用于充当 Relay 节点的 Libp2p 实例。返回一个 Promise。 + */ + private async createRelayNode(): Promise { + // 配置 Libp2p 参数,包括: + // - 监听地址:设置为任意 IPv4 地址上的 WebSocket 连接 + // - 传输层:使用 WebSocket 传输,应用所有可用过滤器 + // - 连接加密:使用 Noise 加密方案 + // - 流复用:使用 Yamux 流复用协议 + // - 服务:启用 Circuit Relay Server(中继服务) + const peerId = await createEd25519PeerId(); + return await createLibp2p({ + addresses: { + listen: [ + "/ip4/127.0.0.1/tcp/9000/ws", + "/ip4/127.0.0.1/tcp/10000/wss", + "/webrtc", + ], // 替换为实际希望监听的 IP 和端口 + }, + transports: [ + webSockets({ + filter: filters.all, + }), + webRTC({ + rtcConfiguration: { + iceServers: [ + { + urls: "stun:stun.l.google.com:19302", + }, + { + urls: "stun:global.stun.twilio.com:3478", + }, + { + urls: "turn:webchatx.stun.mayuan.work:3478", + username: "dxh", + credential: "187139", + }, + ], + }, + }), + ], + connectionEncryption: [noise(), tls()], + streamMuxers: [yamux()], + metrics: prometheusMetrics(), + services: { + identify: identify(), + relay: circuitRelayServer({ + advertise: true, + }), + dht: kadDHT({ + kBucketSize: 20, + clientMode: false, + }), + aminoDHT: kadDHT({ + protocol: "/ipfs/kad/1.0.0", + peerInfoMapper: removePrivateAddressesMapper, + }), + lanDHT: kadDHT({ + protocol: "/ipfs/lan/kad/1.0.0", + peerInfoMapper: removePublicAddressesMapper, + clientMode: false, + }), + pubsub: gossipsub(), + }, + peerDiscovery: [ + mdns(), + bootstrap({ + list: [ + "/ip4/104.131.131.82/tcp/4001/ipfs/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ", + "/dnsaddr/bootstrap.libp2p.io/ipfs/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN", + "/dnsaddr/bootstrap.libp2p.io/ipfs/QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa", + "/dns/webchatx.mayuan.work/tcp/10000/ws/p2p/12D3KooWFzsY7wUBHwbrz6m9nFfLCDwqLD4LS9JykKxSZ4zqG7Pg", + "/dns/webchatx.mayuan.work/tcp/9000/wss/p2p/12D3KooWFzsY7wUBHwbrz6m9nFfLCDwqLD4LS9JykKxSZ4zqG7Pg", + ], + }), + pubsubPeerDiscovery({ + interval: 10000, + topics: topics, + listenOnly: false, + }), + ], + peerId: peerId, + }); } -} + private handleListenEvent() { + this.libp2p.addEventListener("connection:open", (connection) => { + console.log("connection:open", connection.detail); + }); + this.libp2p.addEventListener("connection:close", (connection) => { + console.log("connection:open", connection.detail); + }); + this.libp2p.addEventListener("connection:prune", (connection) => { + console.log("connection:prune", connection.detail); + }); + this.libp2p.addEventListener("peer:connect", (peerId) => { + console.log("peer:connect ", peerId.detail); + }); + this.libp2p.addEventListener("peer:disconnect", (peerId) => { + console.log("peer:disconnect ", peerId.detail); + }); + this.libp2p.addEventListener("peer:discovery", (peerIdInfo) => { + console.log("peer:discovery ", peerIdInfo.detail); + }); -/** - * 创建一个用于充当 Relay 节点的 Libp2p 实例。返回一个 Promise。 - */ -async function createRelayNode(_server?: Server): Promise { - // 配置 Libp2p 参数,包括: - // - 监听地址:设置为任意 IPv4 地址上的 WebSocket 连接 - // - 传输层:使用 WebSocket 传输,应用所有可用过滤器 - // - 连接加密:使用 Noise 加密方案 - // - 流复用:使用 Yamux 流复用协议 - // - 服务:启用 Circuit Relay Server(中继服务) - const keyPair = await parsePrivateKeySecret(); - const peerId = await peerIdFromKeys(keyPair.public.bytes, keyPair.bytes); - return await createLibp2p({ - addresses: { - listen: [ - "/ip4/127.0.0.1/tcp/9000/wss", - "/ip4/127.0.0.1/tcp/10000/ws", - "/webrtc", - ], // 替换为实际希望监听的 IP 和端口 - }, - transports: [ - webSockets({ - filter: filters.all, - }), - webRTC({ - rtcConfiguration: { - iceServers: [ - { - urls: "stun:stun.l.google.com:19302", - }, - { - urls: "stun:global.stun.twilio.com:3478", - }, - { - urls: "turn:webchatx.stun.mayuan.work:3478", - username: "dxh", - credential: "187139", - }, - ], - }, - }), - ], - connectionEncryption: [noise(), tls()], - streamMuxers: [yamux()], - services: { - identify: identify(), - relay: circuitRelayServer({ - advertise: true, - }), - dht: kadDHT({ - kBucketSize: 20, - clientMode: false, - }), - pubsub: gossipsub(), - }, - peerDiscovery: [ - mdns(), - bootstrap({ - list: [ - "/ip4/104.131.131.82/tcp/4001/ipfs/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ", - "/dnsaddr/bootstrap.libp2p.io/ipfs/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN", - "/dnsaddr/bootstrap.libp2p.io/ipfs/QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa", - "/dns/webchatx.mayuan.work/tcp/10000/ws/p2p/12D3KooWFzsY7wUBHwbrz6m9nFfLCDwqLD4LS9JykKxSZ4zqG7Pg", - "/dns/webchatx.mayuan.work/tcp/9000/wss/p2p/12D3KooWFzsY7wUBHwbrz6m9nFfLCDwqLD4LS9JykKxSZ4zqG7Pg", - ], - }), - pubsubPeerDiscovery({ - interval: 10000, - topics: topics, // defaults to ['_peer-discovery._p2p._pubsub'] - listenOnly: false, - }), - ], - peerId: peerId, - }); -} + this.libp2p.addEventListener("peer:identify", (identifyResult) => { + console.log("peer:identify ", identifyResult.detail); + }); + this.libp2p.addEventListener("peer:update", (peerUpdate) => { + console.log("peer:update ", peerUpdate.detail); + }); + this.libp2p.addEventListener("self:peer:update", (peerUpdate) => { + console.log("self:peer:update ", peerUpdate.detail); + console.log("self:peer:update protocols", this.libp2p.getProtocols()); + console.log("self:peer:update multiaddrs", this.libp2p.getMultiaddrs()); + this.libp2p.getMultiaddrs().forEach((multiaddr, index) => { + console.log( + `self:peer:update multiaddr ${index} ${multiaddr.toString()}` + ); + }); + console.log("self:peer:update dialQueue", this.libp2p.getDialQueue()); + console.log("self:peer:update connections", this.libp2p.getConnections()); + console.log("self:peer:update peers", this.libp2p.getPeers()); + }); + this.libp2p.addEventListener("start", () => { + console.log("start"); + console.log("protocols", this.libp2p.getProtocols()); + console.log("multiaddrs", this.libp2p.getMultiaddrs()); + console.log("dialQueue", this.libp2p.getDialQueue()); + console.log("connections", this.libp2p.getConnections()); + console.log("peers", this.libp2p.getPeers()); + }); + this.libp2p.addEventListener("stop", () => { + console.log("stop"); + }); + this.libp2p.addEventListener("transport:close", (listener) => { + console.log("transport:close", listener.detail); + }); + this.libp2p.addEventListener("transport:listening", (listener) => { + console.log("transport:listening", listener.detail); + }); + } -/** - * 停止指定的 Libp2p 实例(Relay 节点)。接受可选的 RelayServiceOptions 参数以配置停止回调。 - */ -async function stopRelayService( - libp2p?: Libp2p, - options?: RelayServiceOptions -): Promise { - // 如果提供了有效的 Libp2p 实例,调用其 stop 方法停止服务 - if (libp2p) { - await libp2p.stop(); - // 如果提供了 onStopped 回调,在服务成功停止后调用它 - if (options?.onStopped) { - options.onStopped(); + private handleProtocol() {} + /** + * 停止指定的 Libp2p 实例(Relay 节点)。接受可选的 RelayServiceOptions 参数以配置停止回调。 + */ + public async stopRelayService(): Promise { + // 如果提供了有效的 Libp2p 实例,调用其 stop 方法停止服务 + if (this.libp2p) { + console.log("Relay service stopped"); + await this.libp2p.stop(); + } + } + /** + * 启动 Relay 服务的异步函数。接受可选的 RelayServiceOptions 参数以配置启动和停止回调。 + * */ + public async startRelayService(): Promise { + try { + // 创建并初始化 Relay 节点 + this.libp2p = await this.createRelayNode(); + (this.libp2p.services.dht as KadDHT).setMode("server"); + // 获取 Relay 节点的监听地址,并确保找到一个 IPv4 地址 + const listenMultiaddr = this.libp2p.getMultiaddrs(); + // 如果提供了 onStarted 回调,则在服务启动后调用它,传入监听地址 + this.handleListenEvent(); + this.handleProtocol(); + console.log("Relay service started on:"); + listenMultiaddr.forEach((addr) => { + console.log("-> ", addr.toString()); + }); + return this.libp2p; + } catch (error) { + // 如果启动过程中发生错误,打印错误信息并尝试停止已创建的 Relay 节点 + console.error("Failed to start relay service:", error); + if (this.libp2p) { + await this.stopRelayService(); + } + return; } } } - -export { startRelayService }; diff --git a/packages/web-chat-x-express/src/controllers/peer.ts b/packages/web-chat-x-express/src/controllers/peer.ts index bc34ccd..63a6620 100644 --- a/packages/web-chat-x-express/src/controllers/peer.ts +++ b/packages/web-chat-x-express/src/controllers/peer.ts @@ -1,14 +1,30 @@ import { Server } from "http"; -import { ExpressPeerServer } from "peer"; -export function generatePeerServer(server: Server) { - return ExpressPeerServer(server, { - path: "/", - proxied: true, - corsOptions: { - origin: "*", - methods: "GET,HEAD,PUT,PATCH,POST,DELETE", - preflightContinue: false, - optionsSuccessStatus: 204, - }, - }); +import { ExpressPeerServer, PeerServerEvents } from "peer"; +import { Express } from "express"; +export default class PeerServerWrapper { + private readonly peerServer: PeerServerEvents & Express; + + /** + * 构造函数中直接初始化 Peer 服务器。 + * @param server HTTP 服务器实例 + */ + constructor(server: Server) { + this.peerServer = ExpressPeerServer(server, { + path: "/", + proxied: true, + corsOptions: { + origin: "*", + methods: "GET,HEAD,PUT,PATCH,POST,DELETE", + preflightContinue: false, + optionsSuccessStatus: 204, + }, + }); + } + + /** + * 获取已初始化的 ExpressPeerServer 实例。 + */ + public getPeerServer(): PeerServerEvents & Express { + return this.peerServer; + } } diff --git a/packages/web-chat-x-express/src/main.ts b/packages/web-chat-x-express/src/main.ts index c180fa5..41ae445 100644 --- a/packages/web-chat-x-express/src/main.ts +++ b/packages/web-chat-x-express/src/main.ts @@ -1,6 +1,5 @@ import express from "express"; -import { generatePeerServer } from "./controllers/peer.js"; -import { startRelayService } from "./controllers/libp2p.js"; +import PeerServerWrapper from "./controllers/peer.js"; import cors from "cors"; import morgan from "morgan"; const app = express(); @@ -13,15 +12,13 @@ app.use(morgan("combined")); const server = app.listen(port, () => { console.log(`webrtc-peer-express app listen on http://localhost:${port}`); }); -// 启动 Relay 服务并配置启动和停止回调 -startRelayService({ - onStarted: (listenMultiaddrArray) => { - console.log("Relay service started on:"); - listenMultiaddrArray.forEach((addr) => { - console.log("-> ", addr.toString()); - }); - }, - onStopped: () => console.log("Relay service stopped"), -}); -const peerServer = generatePeerServer(server); + +import Libp2pManager from "./controllers/libp2p.js"; +const libp2pManager = new Libp2pManager(); +libp2pManager.startRelayService(); + +// 直接在构造函数中初始化 Peer 服务器 +const peerServerWrapper = new PeerServerWrapper(server); +// 然后你可以通过 getter 方法获取 Peer 服务器实例 +const peerServer = peerServerWrapper.getPeerServer(); app.use("/peer", peerServer); diff --git a/packages/web-chat-x-vue/src/classes/PeerManager.ts b/packages/web-chat-x-vue/src/classes/PeerManager.ts index c40b6b2..ce2c5f4 100644 --- a/packages/web-chat-x-vue/src/classes/PeerManager.ts +++ b/packages/web-chat-x-vue/src/classes/PeerManager.ts @@ -7,7 +7,7 @@ type PeerManagerOption = { }; export class PeerManager { - public nearPeer: Ref; + public nearPeer!: Peer; public nearPeerId: Ref; public dataConnect: Ref; public mediaConnect: Ref; @@ -18,7 +18,6 @@ export class PeerManager { public remotePeerId: Ref; public remoteUser: Ref | undefined; constructor() { - this.nearPeer = ref(); this.nearPeerId = ref(""); this.dataConnect = ref(); this.mediaConnect = ref(); @@ -30,7 +29,7 @@ export class PeerManager { } createPeer = (option: PeerManagerOption) => { // 初始化PeerJS实例 - this.nearPeer.value = new Peer(option.nearPeerId, { + this.nearPeer = new Peer(option.nearPeerId, { host: option.host || "webchatx.mayuan.work", port: option.port || 443, secure: true, @@ -51,7 +50,7 @@ export class PeerManager { }); this.nearPeerId.value = option.nearPeerId; // 监听本地peer ID生成事件 - this.nearPeer.value?.on("open", async (id) => { + this.nearPeer.on("open", async (id) => { if (id == this.nearPeerId.value) { console.log(`Local peer ID: ${this.nearPeerId.value}`); } else { @@ -69,7 +68,7 @@ export class PeerManager { }; // 处理来自其他对端的连接请求 handleIncomingConnections = () => { - this.nearPeer.value?.on("connection", (dataConnection) => { + this.nearPeer.on("connection", (dataConnection) => { this.dataConnect.value = dataConnection; dataConnection.on("data", async (data: any) => { console.log(`Received data:${data}`); @@ -84,21 +83,20 @@ export class PeerManager { ); } - // Step 1: Send call request through data channel - const callRequest = { type: "call_request", video, audio }; + const callPreRequest = { type: "call_pre_request", video, audio }; let responseReceived = false; - // Setup a listener for response on data channel + // 为数据通道上的响应设置侦听器 const onResponse = (event: any) => { if ( - event.data.type === "call_response" && + event.data.type === "call_pre_response" && event.data.from === this.remotePeerId.value ) { responseReceived = true; this.dataConnect.value?.removeListener("data", onResponse); // Remove the listener once response is received if (event.data.accepted) { - // Proceed with media setup - this.handleCallAccepted(video, audio); + // 继续进行媒体设置 + this.callRequest(video, audio); } else { console.log("Call request was declined."); this.releaseMediaStream(); // Clean up if call is not accepted @@ -107,16 +105,16 @@ export class PeerManager { }; this.dataConnect.value?.addListener("data", onResponse); - // Send the call request - await this.dataConnect.value?.send(callRequest); + // 通过数据通道发送呼叫请求 + await this.dataConnect.value?.send(callPreRequest); - // Optionally, add a timeout in case no response is received + // 可选地,在未收到响应的情况下添加超时 const timeoutPromise = new Promise( (_, reject) => setTimeout(() => reject(new Error("No response from peer")), 10000) // Timeout after 10 seconds ); - // Wait for either a response or timeout + // 等待响应或超时 await Promise.race([ new Promise((resolve) => responseReceived && resolve()), timeoutPromise, @@ -127,7 +125,7 @@ export class PeerManager { } }; - handleCallAccepted = async (video: boolean, audio: boolean) => { + callRequest = async (video: boolean, audio: boolean) => { try { this.mediaStream.value = await navigator.mediaDevices.getUserMedia({ video, @@ -142,47 +140,47 @@ export class PeerManager { message: "video视频展示元素初始化失败,请刷新后重试", }); } - // ... rest of the media connection setup ... + this.mediaConnect.value = this.nearPeer.call( + this.remotePeerId.value, + this.mediaStream.value + ); + this.mediaConnect.value.on("stream", async (remoteStream) => { + this.remoteVideoElement.value!.srcObject = remoteStream; + await this.waitForVideoReady(this.remoteVideoElement.value!); + this.elDialogVisible.value = true; + }); + this.mediaConnect.value!.on("close", this.releaseMediaStream); } catch (error) { console.error("Error setting up media after acceptance:", error); this.releaseMediaStream(); } }; listenCall = () => { - this.nearPeer.value?.on("call", async (call: MediaConnection) => { + this.dataConnect.value?.on("data", async () => { + // 在继续之前等待用户通过UI提示的响应 + const userResponse = await this.showCallPrompt(); + + // 通过数据通道发送呼叫请求 + await this.dataConnect.value?.send({ + type: "call_pre_response", + from: this.nearPeerId.value, + ...userResponse, + }); + if (!userResponse.accepted) { + console.log("User declined the call."); + return; + } + }); + this.nearPeer.on("call", async (call: MediaConnection) => { try { - // Hold the call object but don't answer yet; first send a request through data channel + // 阻塞通话对象但暂不接听;首先通过数据通道发送请求 this.mediaConnect.value = call; - // Simulate sending a call request via data channel (assuming dataConnect is already set up) - await this.dataConnect.value?.send({ - type: "call_request", - from: this.nearPeerId.value, - }); - - // Wait for user response via UI prompt before proceeding - const userResponse = await this.showCallPrompt(call); - if (!userResponse.accepted) { - console.log("User declined the call."); - call.close(); // If the user declines, close the call connection - return; - } - - // User accepted, now proceed with setting up media + // 用户已接受,现在继续设置媒体 this.mediaConnect.value = call; - await new Promise((resolve) => { - const callReady = () => { - if (this.mediaStream.value) { - clearTimeout(timeout); - call.answer(this.mediaStream.value); - resolve(); - } else { - clearTimeout(timeout); - timeout = setTimeout(callReady, 1000); - } - }; - let timeout = setTimeout(callReady, 1000); - }); + if (this.mediaStream.value) { + call.answer(this.mediaStream.value); + } call.on("stream", async (remoteStream) => { if (this.nearVideoElement.value && this.remoteVideoElement.value) { @@ -211,20 +209,20 @@ export class PeerManager { }); }; - // Hypothetical function to show a UI prompt for call acceptance and media preferences - showCallPrompt = async (call: MediaConnection) => { + // 函数显示一个UI提示电话接受和媒体偏好 + showCallPrompt = async () => { return new Promise<{ accepted: boolean; video: boolean; audio: boolean; }>((resolve) => { - // Implement your UI logic here to prompt the user for accepting the call and choosing media options - // For example, display a modal with "Accept" and "Decline" buttons and checkboxes for video/audio - // Once the user makes a choice, resolve the promise with an object like: - // { accepted: boolean, video: boolean, audio: boolean } - // Until implemented, assume a placeholder response for demonstration purposes: + // 在此处实现 UI 逻辑以提示用户接受呼叫并选择媒体选项 + // 显示带有“接受”和“拒绝”按钮以及视频/音频复选框​​的模式 + // 一旦用户做出选择,就使用如下对象来解决承诺: + // { 接受:布尔值,视频:布尔值,音频:布尔值 } + // 在实施之前,一个占位符响应用于演示目的 setTimeout(() => { - resolve({ accepted: true, video: true, audio: true }); // Assume user accepts with video and audio + resolve({ accepted: true, video: true, audio: true }); // 用户接受视频和音频 }, 1000); }); };