加入日志
This commit is contained in:
@@ -61,7 +61,7 @@ export function useWebRTCConnection({
|
||||
useEffect(() => {
|
||||
const webRTCConfig = {
|
||||
iceServers: getIceServers(),
|
||||
socketOptions: getSocketOptions()|| {},
|
||||
socketOptions: getSocketOptions() || {},
|
||||
signalingServer: config.API_URL,
|
||||
};
|
||||
const senderConn = new WebRTC_Initiator(webRTCConfig);
|
||||
@@ -123,6 +123,12 @@ export function useWebRTCConnection({
|
||||
};
|
||||
sender.onDataChannelOpen = () =>
|
||||
broadcastDataToAllPeers(shareContent, sendFiles);
|
||||
|
||||
sender.onError = (error) => {
|
||||
console.error("Sender Error:", error.message, error.context);
|
||||
// Optionally, use putMessageInMs to show a user-friendly error
|
||||
putMessageInMs(`Connection error: ${error.message}`, true);
|
||||
};
|
||||
}
|
||||
|
||||
if (receiver && receiverFileTransfer) {
|
||||
@@ -162,6 +168,11 @@ export function useWebRTCConnection({
|
||||
console.log(`File received from peer ${peerId}: ${file.name}`);
|
||||
onFileReceived(file, peerId || "unknown_peer");
|
||||
};
|
||||
|
||||
receiver.onError = (error) => {
|
||||
console.error("Receiver Error:", error.message, error.context);
|
||||
putMessageInMs(`Connection error: ${error.message}`, false);
|
||||
};
|
||||
}
|
||||
}, [
|
||||
sender,
|
||||
|
||||
@@ -1,100 +1,117 @@
|
||||
// 发起方 流程: 加入房间; 收到 'ready' 事件(新的接收方进入后socker server就会触发这个事件) -> createPeerConnection + createDataChannel -> createAndSendOffer
|
||||
import BaseWebRTC, { WebRTCConfig } from './webrtc_base';
|
||||
import { postLogInDebug } from '@/app/config/api';
|
||||
const developmentEnv = process.env.NEXT_PUBLIC_development!;//开发环境
|
||||
import BaseWebRTC, { WebRTCConfig } from "./webrtc_base";
|
||||
import { postLogInDebug } from "@/app/config/api";
|
||||
const developmentEnv = process.env.NEXT_PUBLIC_development!; //开发环境
|
||||
|
||||
export default class WebRTC_Initiator extends BaseWebRTC {
|
||||
constructor(config: WebRTCConfig) {
|
||||
super(config);
|
||||
this.setupInitiatorSocketListeners();
|
||||
|
||||
}
|
||||
|
||||
private setupInitiatorSocketListeners() {
|
||||
this.socket.on('ready', ({ peerId }) => {//新进入房间的 接收方 peerId
|
||||
this.socket.on("ready", ({ peerId }) => {
|
||||
//新进入房间的 接收方 peerId
|
||||
this.handleReady({ peerId });
|
||||
});
|
||||
// 添加接收方响应的监听
|
||||
this.socket.on('recipient-ready', ({ peerId }) => {
|
||||
if (developmentEnv === 'true')postLogInDebug(`[Initiator] Received recipient-ready from: ${peerId}`);
|
||||
this.socket.on("recipient-ready", ({ peerId }) => {
|
||||
if (developmentEnv === "true")
|
||||
postLogInDebug(`[Initiator] Received recipient-ready from: ${peerId}`);
|
||||
this.handleReady({ peerId });
|
||||
});
|
||||
// 添加answer处理监听器
|
||||
this.socket.on('answer', ({ answer, peerId, from }) => {
|
||||
this.socket.on("answer", ({ answer, peerId, from }) => {
|
||||
this.handleAnswer({ answer, peerId, from });
|
||||
});
|
||||
}
|
||||
|
||||
//发送方收到接收方加入时创建连接
|
||||
private async handleReady({ peerId }: { peerId: string }): Promise<void> {//接收方 peerId
|
||||
// console.log(`Received ready signal from peer ${peerId}`);
|
||||
if (developmentEnv === 'true')postLogInDebug(`Received ready signal from peer ${peerId}`);
|
||||
private async handleReady({ peerId }: { peerId: string }): Promise<void> {
|
||||
//接收方 peerId
|
||||
// this.log('log',`Received ready signal from peer ${peerId}`);
|
||||
if (developmentEnv === "true")
|
||||
postLogInDebug(`Received ready signal from peer ${peerId}`);
|
||||
await this.createPeerConnection(peerId);
|
||||
await this.createDataChannel(peerId);
|
||||
await this.createAndSendOffer(peerId);
|
||||
}
|
||||
private async handleAnswer({ answer, peerId, from }: { answer: RTCSessionDescriptionInit; peerId: string; from: string }): Promise<void> {
|
||||
// console.log(`Handling answer from peer ${from}`);
|
||||
if (developmentEnv === 'true')postLogInDebug(`Handling answer from peer ${from}`);
|
||||
private async handleAnswer({
|
||||
answer,
|
||||
peerId,
|
||||
from,
|
||||
}: {
|
||||
answer: RTCSessionDescriptionInit;
|
||||
peerId: string;
|
||||
from: string;
|
||||
}): Promise<void> {
|
||||
// this.log('log',`Handling answer from peer ${from}`);
|
||||
if (developmentEnv === "true")
|
||||
postLogInDebug(`Handling answer from peer ${from}`);
|
||||
const peerConnection = this.peerConnections.get(from);
|
||||
if (!peerConnection) {
|
||||
console.error(`No peer connection found for peer ${from}`);
|
||||
this.fireError(`No peer connection found for peer ${from}`, { from });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await peerConnection.setRemoteDescription(new RTCSessionDescription(answer));
|
||||
// console.log(`Remote description set for peer ${from}`);
|
||||
|
||||
await peerConnection.setRemoteDescription(
|
||||
new RTCSessionDescription(answer)
|
||||
);
|
||||
// this.log('log', `Remote description set for peer ${from}`);
|
||||
|
||||
// 在设置远程描述后处理队列中的ICE候选
|
||||
await this.addQueuedIceCandidates(from);
|
||||
} catch (error) {
|
||||
console.error('Error handling answer:', error);
|
||||
this.fireError("Error handling answer", { error, from });
|
||||
}
|
||||
}
|
||||
protected async createDataChannel(peerId: string): Promise<void> {
|
||||
|
||||
const peerConnection = this.peerConnections.get(peerId);
|
||||
if (!peerConnection) {
|
||||
console.error(`No peer connection found for peer ${peerId}`);
|
||||
this.fireError(`No peer connection found for peer ${peerId}`, { peerId });
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const dataChannel = peerConnection.createDataChannel('dataChannel', {
|
||||
const dataChannel = peerConnection.createDataChannel("dataChannel", {
|
||||
ordered: true,
|
||||
// reliable: true
|
||||
// reliable: true
|
||||
});
|
||||
// console.log(`Created data channel for peer ${peerId}`);
|
||||
// this.log('log', `Created data channel for peer ${peerId}`);
|
||||
|
||||
dataChannel.bufferedAmountLowThreshold = 262144; //256 KB -- 可以根据需要调整
|
||||
this.setupDataChannel(dataChannel, peerId);
|
||||
this.dataChannels.set(peerId, dataChannel);
|
||||
} catch (error) {
|
||||
console.error(`Error creating data channel for peer ${peerId}:`, error);
|
||||
this.fireError(`Error creating data channel for peer ${peerId}`, {
|
||||
error,
|
||||
peerId,
|
||||
});
|
||||
}
|
||||
}
|
||||
// 如果是发起方,创建并发送offer给信令服务器,以便与接收方协商建立连接。
|
||||
private async createAndSendOffer(peerId: string): Promise<void> {
|
||||
// console.log('createAndSendOffer',peerId);
|
||||
if (developmentEnv === 'true')postLogInDebug(`createAndSendOffer for peerId: ${peerId}`);
|
||||
// this.log('log', `Creating and sending offer to ${peerId}`);
|
||||
if (developmentEnv === "true")
|
||||
postLogInDebug(`createAndSendOffer for peerId: ${peerId}`);
|
||||
const peerConnection = this.peerConnections.get(peerId);
|
||||
if (!peerConnection) {
|
||||
console.error(`No peer connection found for peer ${peerId}`);
|
||||
this.fireError(`No peer connection found for peer ${peerId}`, { peerId });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const offer = await peerConnection.createOffer();
|
||||
await peerConnection.setLocalDescription(offer);
|
||||
// console.log('createAndSendOffer',peerId,this.roomId,offer);
|
||||
this.socket.emit('offer', {
|
||||
// this.log('log','createAndSendOffer',peerId,this.roomId,offer);
|
||||
this.socket.emit("offer", {
|
||||
roomId: this.roomId,
|
||||
peerId: peerId,
|
||||
offer: offer,
|
||||
from: this.socket.id
|
||||
from: this.socket.id,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error creating and sending offer:', error);
|
||||
this.fireError("Error creating and sending offer", { error, peerId });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// 接收方 流程: 加入房间; 收到 'offer' 事件 -> createPeerConnection + createDataChannel -> 发送 answer
|
||||
import BaseWebRTC, { WebRTCConfig } from './webrtc_base';
|
||||
import { postLogInDebug } from '@/app/config/api';
|
||||
const developmentEnv = process.env.NEXT_PUBLIC_development!;//开发环境
|
||||
import BaseWebRTC, { WebRTCConfig } from "./webrtc_base";
|
||||
import { postLogInDebug } from "@/app/config/api";
|
||||
const developmentEnv = process.env.NEXT_PUBLIC_development!; //开发环境
|
||||
|
||||
interface AnswerPayload {
|
||||
answer: RTCSessionDescriptionInit;
|
||||
@@ -14,31 +14,41 @@ export default class WebRTC_Recipient extends BaseWebRTC {
|
||||
}
|
||||
|
||||
private setupRecipientSocketListeners(): void {
|
||||
|
||||
this.socket.on('offer', ({ peerId, offer, from }) => {
|
||||
this.handleOffer({ peerId,offer, from });
|
||||
this.socket.on("offer", ({ peerId, offer, from }) => {
|
||||
this.handleOffer({ peerId, offer, from });
|
||||
});
|
||||
|
||||
this.socket.on('answer', ({ answer, peerId }) => {
|
||||
|
||||
this.socket.on("answer", ({ answer, peerId }) => {
|
||||
this.handleAnswer({ answer, peerId });
|
||||
});
|
||||
|
||||
// 添加发起方重新上线的监听
|
||||
this.socket.on('initiator-online', ({ roomId }) => {
|
||||
console.log(`[Recipient] Received initiator-online for room: ${roomId}`,this.roomId);
|
||||
this.socket.on("initiator-online", ({ roomId }) => {
|
||||
this.log("log", `Received initiator-online for room: ${roomId}`);
|
||||
// 发送准备就绪的响应
|
||||
console.log(`[Recipient] Sending recipient-ready, my peerId: ${this.socket.id}`,this.peerId);
|
||||
this.log(
|
||||
"log",
|
||||
`Sending recipient-ready, my peerId: ${this.socket.id}`,
|
||||
this.peerId
|
||||
);
|
||||
// 发送准备就绪的响应
|
||||
this.socket.emit('recipient-ready', {
|
||||
this.socket.emit("recipient-ready", {
|
||||
roomId: this.roomId,
|
||||
peerId: this.socket.id
|
||||
peerId: this.socket.id,
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
// 接收方 收到 offer 时创建连接
|
||||
private async handleOffer({ peerId, offer, from }: { offer: RTCSessionDescriptionInit; peerId: string; from: string }): Promise<void> {
|
||||
console.log(`Handling offer from peer ${from}`);
|
||||
private async handleOffer({
|
||||
peerId,
|
||||
offer,
|
||||
from,
|
||||
}: {
|
||||
offer: RTCSessionDescriptionInit;
|
||||
peerId: string;
|
||||
from: string;
|
||||
}): Promise<void> {
|
||||
this.log("log", `Handling offer from peer ${from}`);
|
||||
try {
|
||||
// 1. 清理已存在的连接
|
||||
await this.cleanupExistingConnection(from);
|
||||
@@ -49,23 +59,24 @@ export default class WebRTC_Recipient extends BaseWebRTC {
|
||||
|
||||
// 4. 设置远程描述
|
||||
// console.log(`Setting remote description for peer ${from}`);
|
||||
await peerConnection.setRemoteDescription(new RTCSessionDescription(offer));
|
||||
await peerConnection.setRemoteDescription(
|
||||
new RTCSessionDescription(offer)
|
||||
);
|
||||
// 创建并设置本地描述(answer)
|
||||
// console.log(`Creating answer for peer ${from}`);
|
||||
const answer = await peerConnection.createAnswer();
|
||||
await peerConnection.setLocalDescription(answer);
|
||||
// 发送 answer
|
||||
console.log(`Sending answer to peer ${from}`);
|
||||
this.socket.emit('answer', {
|
||||
this.log("log", `Sending answer to peer ${from}`);
|
||||
this.socket.emit("answer", {
|
||||
answer,
|
||||
peerId: from,
|
||||
from: this.socket.id
|
||||
from: this.socket.id,
|
||||
});
|
||||
// 最后处理已缓存的 ICE candidates
|
||||
await this.addQueuedIceCandidates(from);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error handling offer:', error);
|
||||
this.fireError("Error handling offer", { error, from });
|
||||
// 清理失败的连接
|
||||
await this.cleanupExistingConnection(from);
|
||||
}
|
||||
@@ -73,26 +84,31 @@ export default class WebRTC_Recipient extends BaseWebRTC {
|
||||
|
||||
private async handleAnswer({ answer, peerId }: AnswerPayload): Promise<void> {
|
||||
const peerConnection = this.peerConnections.get(peerId);
|
||||
if (!peerConnection) return;
|
||||
if (!peerConnection) {
|
||||
this.fireError("No peer connection for handleAnswer", { peerId });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await peerConnection.setRemoteDescription(new RTCSessionDescription(answer));
|
||||
await peerConnection.setRemoteDescription(
|
||||
new RTCSessionDescription(answer)
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Error handling answer:', error);
|
||||
this.fireError("Error handling answer", { error, peerId });
|
||||
}
|
||||
}
|
||||
|
||||
protected async createDataChannel(peerId: string): Promise<void> {
|
||||
const peerConnection = this.peerConnections.get(peerId);
|
||||
if (!peerConnection) {
|
||||
console.error(`No peer connection found for peer ${peerId}`);
|
||||
this.fireError(`No peer connection found for peer ${peerId}`, { peerId });
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
peerConnection.ondatachannel = (event) => {
|
||||
// console.log(`Received data channel from peer ${peerId}`);
|
||||
// this.log('log', `Received data channel from peer ${peerId}`);
|
||||
this.setupDataChannel(event.channel, peerId);
|
||||
this.dataChannels.set(peerId, event.channel);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+198
-116
@@ -1,9 +1,16 @@
|
||||
// BaseWebRTC.js
|
||||
import io, { Socket, ManagerOptions, SocketOptions } from 'socket.io-client';
|
||||
import { getIceServers, getSocketOptions } from '@/app/config/environment';
|
||||
import { WakeLockManager } from './wakeLockManager';
|
||||
import { postLogInDebug } from '@/app/config/api';
|
||||
const developmentEnv = process.env.NEXT_PUBLIC_development!;//开发环境
|
||||
import io, { Socket, ManagerOptions, SocketOptions } from "socket.io-client";
|
||||
import { getIceServers, getSocketOptions } from "@/app/config/environment";
|
||||
import { WakeLockManager } from "./wakeLockManager";
|
||||
import { postLogInDebug } from "@/app/config/api";
|
||||
const developmentEnv = process.env.NEXT_PUBLIC_development!; //开发环境
|
||||
|
||||
export class WebRTCError extends Error {
|
||||
constructor(message: string, public context?: Record<string, any>) {
|
||||
super(message);
|
||||
this.name = "WebRTCError";
|
||||
}
|
||||
}
|
||||
|
||||
interface JoinRoomResponse {
|
||||
success: boolean;
|
||||
@@ -11,17 +18,22 @@ interface JoinRoomResponse {
|
||||
roomId: string;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
interface CallbackTypes {
|
||||
onDataChannelOpen?: (peerId: string) => void;
|
||||
onDataReceived?: (data: string | ArrayBuffer, peerId: string) => void;
|
||||
onConnectionEstablished?: (peerId: string) => void;
|
||||
onConnectionStateChange?: (state: RTCPeerConnectionState, peerId: string) => void;
|
||||
onConnectionStateChange?: (
|
||||
state: RTCPeerConnectionState,
|
||||
peerId: string
|
||||
) => void;
|
||||
onError?: (error: WebRTCError) => void;
|
||||
}
|
||||
|
||||
export interface WebRTCConfig {
|
||||
iceServers: RTCIceServer[];
|
||||
socketOptions: Partial<ManagerOptions & SocketOptions>;
|
||||
signalingServer: string;// signalingServer: 信令服务器的URL,用于初始化Socket.IO连接。
|
||||
signalingServer: string; // signalingServer: 信令服务器的URL,用于初始化Socket.IO连接。
|
||||
}
|
||||
|
||||
export default class BaseWebRTC {
|
||||
@@ -31,38 +43,44 @@ export default class BaseWebRTC {
|
||||
public peerConnections: Map<string, RTCPeerConnection>;
|
||||
public dataChannels: Map<string, RTCDataChannel>;
|
||||
|
||||
public onDataChannelOpen: CallbackTypes['onDataChannelOpen'] | null;
|
||||
public onDataReceived: CallbackTypes['onDataReceived'] | null;
|
||||
protected onConnectionEstablished: CallbackTypes['onConnectionEstablished'] | null;
|
||||
public onConnectionStateChange: CallbackTypes['onConnectionStateChange'] | null;
|
||||
public onDataChannelOpen: CallbackTypes["onDataChannelOpen"] | null;
|
||||
public onDataReceived: CallbackTypes["onDataReceived"] | null;
|
||||
protected onConnectionEstablished:
|
||||
| CallbackTypes["onConnectionEstablished"]
|
||||
| null;
|
||||
public onConnectionStateChange:
|
||||
| CallbackTypes["onConnectionStateChange"]
|
||||
| null;
|
||||
public onError: CallbackTypes["onError"] | null;
|
||||
|
||||
protected iceCandidatesQueue: Map<string, RTCIceCandidateInit[]>;
|
||||
protected roomId: string | null;
|
||||
protected peerId: string | undefined | null;
|
||||
public isInRoom: boolean;
|
||||
protected isInitiator: boolean;//标记发起方
|
||||
protected isInitiator: boolean; //标记发起方
|
||||
//重连相关
|
||||
protected isSocketDisconnected: boolean;//跟踪 socket 连接状态
|
||||
protected isPeerDisconnected: boolean;//跟踪 P2P 连接状态
|
||||
protected reconnectionInProgress: boolean;//防止重复重连
|
||||
protected isSocketDisconnected: boolean; //跟踪 socket 连接状态
|
||||
protected isPeerDisconnected: boolean; //跟踪 P2P 连接状态
|
||||
protected reconnectionInProgress: boolean; //防止重复重连
|
||||
protected wakeLockManager: WakeLockManager;
|
||||
|
||||
constructor(config: WebRTCConfig) {
|
||||
this.iceServers = config.iceServers;
|
||||
this.socket = io(config.signalingServer, config.socketOptions);
|
||||
this.peerConnections = new Map();// Map<targetPeerId, RTCPeerConnection>
|
||||
this.dataChannels = new Map();// Map<targetPeerId, RTCDataChannel>
|
||||
|
||||
this.peerConnections = new Map(); // Map<targetPeerId, RTCPeerConnection>
|
||||
this.dataChannels = new Map(); // Map<targetPeerId, RTCDataChannel>
|
||||
|
||||
// Callbacks
|
||||
this.onDataChannelOpen = null;//当数据通道建立时的回调
|
||||
this.onDataReceived = null;//接收数据--响应
|
||||
this.onConnectionEstablished = null;//当WebRTC连接建立时触发。
|
||||
this.onConnectionStateChange = null;//监控和响应连接状态的变化
|
||||
|
||||
this.iceCandidatesQueue = new Map();// 为每个peer存储ice候选项
|
||||
this.onDataChannelOpen = null; //当数据通道建立时的回调
|
||||
this.onDataReceived = null; //接收数据--响应
|
||||
this.onConnectionEstablished = null; //当WebRTC连接建立时触发。
|
||||
this.onConnectionStateChange = null; //监控和响应连接状态的变化
|
||||
this.onError = null;
|
||||
|
||||
this.iceCandidatesQueue = new Map(); // 为每个peer存储ice候选项
|
||||
this.roomId = null;
|
||||
this.peerId = null;//自己的 ID
|
||||
this.isInRoom = false;//是否已经加入过房间
|
||||
this.peerId = null; //自己的 ID
|
||||
this.isInRoom = false; //是否已经加入过房间
|
||||
this.setupCommonSocketListeners();
|
||||
|
||||
this.isInitiator = false;
|
||||
@@ -72,61 +90,90 @@ export default class BaseWebRTC {
|
||||
this.reconnectionInProgress = false;
|
||||
this.wakeLockManager = new WakeLockManager();
|
||||
}
|
||||
// region Logging and Error Handling
|
||||
protected log(
|
||||
level: "log" | "warn" | "error",
|
||||
message: string,
|
||||
...args: any[]
|
||||
) {
|
||||
const prefix = `[${this.constructor.name}]`;
|
||||
console[level](prefix, message, ...args);
|
||||
}
|
||||
|
||||
protected fireError(message: string, context?: Record<string, any>) {
|
||||
const error = new WebRTCError(message, context);
|
||||
this.log("error", message, context);
|
||||
this.onError?.(error);
|
||||
}
|
||||
// endregion
|
||||
// 设置信令服务器的事件监听器,用于处理各种信令消息(连接、ICE候选者、offer、answer等)。
|
||||
setupCommonSocketListeners() {
|
||||
this.socket.on('connect', () => {
|
||||
this.peerId = this.socket.id;//保存自己的 ID
|
||||
console.log('Connected to signaling server, peerId:', this.peerId);
|
||||
this.socket.on("connect", () => {
|
||||
this.peerId = this.socket.id; //保存自己的 ID
|
||||
this.log("log", `Connected to signaling server, peerId: ${this.peerId}`);
|
||||
});
|
||||
|
||||
this.socket.on('error', (error) => {
|
||||
console.error('Socket error:', error);
|
||||
this.socket.on("error", (error) => {
|
||||
this.fireError("Socket error", { error });
|
||||
});
|
||||
|
||||
this.socket.on('ice-candidate', ({ candidate, peerId, from }) => {//接受方 peerId
|
||||
this.socket.on("ice-candidate", ({ candidate, peerId, from }) => {
|
||||
//接受方 peerId
|
||||
// console.log(`Received ICE candidate from ${from} for ${peerId}`);
|
||||
this.handleIceCandidate({candidate, peerId, from});
|
||||
this.handleIceCandidate({ candidate, peerId, from });
|
||||
});
|
||||
// 添加 socket 断开连接的监听
|
||||
this.socket.on('disconnect', () => {
|
||||
this.socket.on("disconnect", () => {
|
||||
this.isInRoom = false;
|
||||
this.isSocketDisconnected = true;
|
||||
if(developmentEnv === 'true')postLogInDebug(`${this.peerId} disconnect on socket,isInitiator:${this.isInitiator},isInRoom:${this.isInRoom}`);
|
||||
if (developmentEnv === "true")
|
||||
postLogInDebug(
|
||||
`${this.peerId} disconnect on socket,isInitiator:${this.isInitiator},isInRoom:${this.isInRoom}`
|
||||
);
|
||||
// 尝试重连.//移动端切换到后台之后,P2P连接和socket连接都会断开.在切回来时,才会执行断开的代码,直接在这里重连;发送重连开始新号
|
||||
this.attemptReconnection();
|
||||
});
|
||||
}
|
||||
protected async attemptReconnection(): Promise<void> {
|
||||
if (this.reconnectionInProgress) return;
|
||||
|
||||
if (this.isSocketDisconnected && this.isPeerDisconnected && this.roomId) {//等socket和P2P连接都断开之后再开始重连
|
||||
|
||||
if (this.isSocketDisconnected && this.isPeerDisconnected && this.roomId) {
|
||||
//等socket和P2P连接都断开之后再开始重连
|
||||
this.reconnectionInProgress = true;
|
||||
if(developmentEnv === 'true') {
|
||||
postLogInDebug(`Starting reconnection, socket and peer both disconnected. isInitiator:${this.isInitiator}`);
|
||||
if (developmentEnv === "true") {
|
||||
postLogInDebug(
|
||||
`Starting reconnection, socket and peer both disconnected. isInitiator:${this.isInitiator}`
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
const sendInitiatorOnline = this.isInitiator;
|
||||
await this.joinRoom(this.roomId, this.isInitiator, sendInitiatorOnline);
|
||||
|
||||
|
||||
// 重置状态
|
||||
this.isSocketDisconnected = false;
|
||||
this.isPeerDisconnected = false;
|
||||
} catch (error) {
|
||||
console.error('Reconnection failed:', error);
|
||||
this.fireError("Reconnection failed", { error });
|
||||
} finally {
|
||||
this.reconnectionInProgress = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
protected async handleIceCandidate(
|
||||
{ candidate, peerId, from }: { candidate: RTCIceCandidateInit; peerId: string; from: string }
|
||||
): Promise<void> {
|
||||
// console.log(`Handling ICE candidate from ${from} for ${peerId}`);
|
||||
protected async handleIceCandidate({
|
||||
candidate,
|
||||
peerId,
|
||||
from,
|
||||
}: {
|
||||
candidate: RTCIceCandidateInit;
|
||||
peerId: string;
|
||||
from: string;
|
||||
}): Promise<void> {
|
||||
// this.log('log',`Handling ICE candidate from ${from} for ${peerId}`);
|
||||
const peerConnection = this.peerConnections.get(from);
|
||||
// console.log(`this.peerConnections`,this.peerConnections);
|
||||
// this.log('log',`this.peerConnections`,this.peerConnections);
|
||||
if (!peerConnection) {
|
||||
// console.warn(`No peer connection found for ${from}, queuing candidate`);
|
||||
// this.log('warn',`No peer connection found for ${from}, queuing candidate`);
|
||||
if (!this.iceCandidatesQueue.has(from)) {
|
||||
this.iceCandidatesQueue.set(from, []);
|
||||
}
|
||||
@@ -135,21 +182,23 @@ export default class BaseWebRTC {
|
||||
}
|
||||
try {
|
||||
// 只有在远程描述设置完成且连接未关闭的情况下才添加ICE候选项
|
||||
if (peerConnection.remoteDescription &&
|
||||
peerConnection.signalingState !== 'closed' &&
|
||||
peerConnection.connectionState !== 'closed') {
|
||||
if (
|
||||
peerConnection.remoteDescription &&
|
||||
peerConnection.signalingState !== "closed" &&
|
||||
peerConnection.connectionState !== "closed"
|
||||
) {
|
||||
await peerConnection.addIceCandidate(new RTCIceCandidate(candidate));
|
||||
// console.log(`Successfully added ICE candidate for ${from}`);
|
||||
// this.log('log',`Successfully added ICE candidate for ${from}`);
|
||||
} else {
|
||||
// console.warn(`Remote description not set or connection closed for ${from}, queuing candidate`);
|
||||
// console.warn(`remoteDescription`,peerConnection.remoteDescription,'peerConnection.signalingState',peerConnection.signalingState);
|
||||
// this.log('warn',`Remote description not set or connection closed for ${from}, queuing candidate`);
|
||||
// this.log('warn',`remoteDescription`,peerConnection.remoteDescription,'peerConnection.signalingState',peerConnection.signalingState);
|
||||
if (!this.iceCandidatesQueue.has(from)) {
|
||||
this.iceCandidatesQueue.set(from, []);
|
||||
this.iceCandidatesQueue.set(from, []);
|
||||
}
|
||||
this.iceCandidatesQueue.get(from)?.push(candidate);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`Error adding ICE candidate for ${from}:`, e);
|
||||
this.fireError(`Error adding ICE candidate for ${from}`, { error: e });
|
||||
// 如果添加失败,也将其加入队列
|
||||
if (!this.iceCandidatesQueue.has(from)) {
|
||||
this.iceCandidatesQueue.set(from, []);
|
||||
@@ -162,88 +211,100 @@ export default class BaseWebRTC {
|
||||
const candidates = this.iceCandidatesQueue.get(peerId);
|
||||
const peerConnection = this.peerConnections.get(peerId);
|
||||
|
||||
// console.log(`Attempting to add ${candidates?.length || 0} queued candidates for ${peerId}`);
|
||||
// console.log(`Connection state: ${peerConnection?.connectionState}`);
|
||||
// console.log(`Signaling state: ${peerConnection?.signalingState}`);
|
||||
// this.log('log',`Attempting to add ${candidates?.length || 0} queued candidates for ${peerId}`);
|
||||
// this.log('log',`Connection state: ${peerConnection?.connectionState}`);
|
||||
// this.log('log',`Signaling state: ${peerConnection?.signalingState}`);
|
||||
|
||||
if (!peerConnection || !candidates?.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (peerConnection.remoteDescription &&
|
||||
peerConnection.signalingState !== 'closed' &&
|
||||
peerConnection.connectionState !== 'closed') {
|
||||
|
||||
// console.log(`Adding ${candidates.length} queued candidates for ${peerId}`);
|
||||
if (
|
||||
peerConnection.remoteDescription &&
|
||||
peerConnection.signalingState !== "closed" &&
|
||||
peerConnection.connectionState !== "closed"
|
||||
) {
|
||||
// this.log('log',`Adding ${candidates.length} queued candidates for ${peerId}`);
|
||||
|
||||
for (const candidate of candidates) {
|
||||
try {
|
||||
await peerConnection.addIceCandidate(new RTCIceCandidate(candidate));
|
||||
// console.log(`Successfully added queued candidate for ${peerId}`);
|
||||
}
|
||||
catch (e) {
|
||||
console.error('Error adding queued ice candidates', e);
|
||||
// this.log('log',`Successfully added queued candidate for ${peerId}`);
|
||||
} catch (e) {
|
||||
this.fireError("Error adding queued ice candidates", {
|
||||
error: e,
|
||||
peerId,
|
||||
});
|
||||
}
|
||||
}
|
||||
// 只有在成功添加所有候选项后才清空队列
|
||||
this.iceCandidatesQueue.delete(peerId);
|
||||
|
||||
} else {
|
||||
console.warn(`Connection not ready for ${peerId}, keeping candidates queued`);
|
||||
// console.warn(`remoteDescription`,peerConnection?.remoteDescription);
|
||||
this.log(
|
||||
"warn",
|
||||
`Connection not ready for ${peerId}, keeping candidates queued`
|
||||
);
|
||||
// this.log('warn',`remoteDescription`,peerConnection?.remoteDescription);
|
||||
}
|
||||
}
|
||||
|
||||
protected async createPeerConnection(peerId: string): Promise<RTCPeerConnection> {
|
||||
// console.log('Creating peer connection for:', peerId);
|
||||
protected async createPeerConnection(
|
||||
peerId: string
|
||||
): Promise<RTCPeerConnection> {
|
||||
// this.log('log','Creating peer connection for:', peerId);
|
||||
const peerConnection = this.peerConnections.get(peerId);
|
||||
if (peerConnection) {
|
||||
// console.log('Reusing existing peer connection for:', peerId);
|
||||
// this.log('log','Reusing existing peer connection for:', peerId);
|
||||
return Promise.resolve(peerConnection);
|
||||
}
|
||||
// WebRTC默认提供了强大的加密功能,上线后要改为https协议
|
||||
const newPeerConnection = new RTCPeerConnection({ iceServers: this.iceServers });
|
||||
const newPeerConnection = new RTCPeerConnection({
|
||||
iceServers: this.iceServers,
|
||||
});
|
||||
|
||||
// // 增加更详细的连接状态监控
|
||||
// newPeerConnection.oniceconnectionstatechange = () => {
|
||||
// console.log(`ICE Connection State (${peerId}):`, newPeerConnection.iceConnectionState);
|
||||
// this.log('log',`ICE Connection State (${peerId}):`, newPeerConnection.iceConnectionState);
|
||||
// };
|
||||
|
||||
// newPeerConnection.onsignalingstatechange = () => {
|
||||
// console.log(`Signaling State (${peerId}):`, newPeerConnection.signalingState);
|
||||
// this.log('log',`Signaling State (${peerId}):`, newPeerConnection.signalingState);
|
||||
// };
|
||||
|
||||
newPeerConnection.onconnectionstatechange = () => {
|
||||
// const state = newPeerConnection.connectionState;
|
||||
// console.log(`Connection State (${peerId}):`, state);
|
||||
// this.log('log',`Connection State (${peerId}):`, state);
|
||||
this.handleConnectionStateChange(peerId, newPeerConnection);
|
||||
};
|
||||
// 改进ICE候选项处理
|
||||
newPeerConnection.onicecandidate = (event) => {
|
||||
if (event.candidate) {
|
||||
// console.log(`Sending ICE candidate to ${peerId}:`, event.candidate);
|
||||
this.socket.emit('ice-candidate', {
|
||||
// this.log('log',`Sending ICE candidate to ${peerId}:`, event.candidate);
|
||||
this.socket.emit("ice-candidate", {
|
||||
candidate: event.candidate,
|
||||
peerId: peerId,
|
||||
from: this.socket.id // 添加发送方ID
|
||||
from: this.socket.id, // 添加发送方ID
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// // 添加ICE收集状态监控
|
||||
// newPeerConnection.onicegatheringstatechange = () => {
|
||||
// console.log(`ICE Gathering State (${peerId}):`, newPeerConnection.iceGatheringState);
|
||||
// this.log('log',`ICE Gathering State (${peerId}):`, newPeerConnection.iceGatheringState);
|
||||
// };
|
||||
|
||||
this.peerConnections.set(peerId, newPeerConnection);
|
||||
// console.log('New peer connection created for:', peerId);
|
||||
// this.log('log','New peer connection created for:', peerId);
|
||||
return Promise.resolve(newPeerConnection);
|
||||
}
|
||||
|
||||
protected handleConnectionStateChange(peerId: string, peerConnection: RTCPeerConnection): void {
|
||||
protected handleConnectionStateChange(
|
||||
peerId: string,
|
||||
peerConnection: RTCPeerConnection
|
||||
): void {
|
||||
const state = peerConnection.connectionState;
|
||||
// console.log('Connection state change:', state);
|
||||
|
||||
// this.log('log','Connection state change:', state);
|
||||
|
||||
const stateHandlers = {
|
||||
connected: async () => {
|
||||
this.isPeerDisconnected = false;
|
||||
@@ -258,33 +319,41 @@ export default class BaseWebRTC {
|
||||
disconnected: async () => {
|
||||
await this.cleanupExistingConnection(peerId);
|
||||
this.isPeerDisconnected = true;
|
||||
if (developmentEnv === 'true')postLogInDebug(`p2p disconnected, isInitiator:${this.isInitiator}`);
|
||||
if (developmentEnv === "true")
|
||||
postLogInDebug(`p2p disconnected, isInitiator:${this.isInitiator}`);
|
||||
// 尝试重连
|
||||
this.attemptReconnection();
|
||||
await this.wakeLockManager.releaseWakeLock();
|
||||
},
|
||||
failed: async () => {
|
||||
failed: async () => {
|
||||
this.cleanupExistingConnection(peerId);
|
||||
this.isPeerDisconnected = true;
|
||||
await this.wakeLockManager.releaseWakeLock();
|
||||
},
|
||||
closed: async () => {
|
||||
closed: async () => {
|
||||
this.cleanupExistingConnection(peerId);
|
||||
this.isPeerDisconnected = true;
|
||||
await this.wakeLockManager.releaseWakeLock();
|
||||
},
|
||||
// 以下必须添加,防止报错
|
||||
connecting: () => {console.log("Peer is connecting");},
|
||||
new: () => {console.log("New connection state");}
|
||||
connecting: () => {
|
||||
this.log("log", "Peer is connecting");
|
||||
},
|
||||
new: () => {
|
||||
this.log("log", "New connection state");
|
||||
},
|
||||
};
|
||||
|
||||
stateHandlers[state]?.();
|
||||
this.onConnectionStateChange?.(state, peerId);
|
||||
}
|
||||
|
||||
protected setupDataChannel(dataChannel: RTCDataChannel, peerId: string): void {
|
||||
|
||||
protected setupDataChannel(
|
||||
dataChannel: RTCDataChannel,
|
||||
peerId: string
|
||||
): void {
|
||||
dataChannel.onopen = () => {
|
||||
// console.log(`Data channel opened for peer ${peerId}`);
|
||||
// this.log('log',`Data channel opened for peer ${peerId}`);
|
||||
setTimeout(() => {
|
||||
this.onDataChannelOpen?.(peerId);
|
||||
}, 50);
|
||||
@@ -295,7 +364,11 @@ export default class BaseWebRTC {
|
||||
};
|
||||
}
|
||||
// 加入房间,sendInitiatorOnline表示加入房间之后,是否要发送“发起方重新在线”消息
|
||||
public async joinRoom(roomId: string, isInitiator:boolean, sendInitiatorOnline:boolean = false): Promise<void> {
|
||||
public async joinRoom(
|
||||
roomId: string,
|
||||
isInitiator: boolean,
|
||||
sendInitiatorOnline: boolean = false
|
||||
): Promise<void> {
|
||||
// 如果已经在房间里,直接返回
|
||||
if (this.isInRoom) {
|
||||
return;
|
||||
@@ -304,38 +377,42 @@ export default class BaseWebRTC {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
// 设置超时时间(5秒)
|
||||
const timeout = setTimeout(() => {
|
||||
this.socket.off('joinResponse');
|
||||
reject(new Error('Join room timeout'));
|
||||
this.socket.off("joinResponse");
|
||||
reject(new Error("Join room timeout"));
|
||||
this.isInRoom = false;
|
||||
this.roomId = null;
|
||||
}, 5000);
|
||||
|
||||
|
||||
// 监听加入房间响应--一次
|
||||
this.socket.once('joinResponse', (response: JoinRoomResponse) => {
|
||||
this.socket.once("joinResponse", (response: JoinRoomResponse) => {
|
||||
clearTimeout(timeout); // 清除超时定时器
|
||||
|
||||
|
||||
if (response.success) {
|
||||
this.roomId = roomId;
|
||||
this.isInRoom = true;
|
||||
if(sendInitiatorOnline){
|
||||
this.socket.emit('initiator-online', {
|
||||
roomId: this.roomId
|
||||
if (sendInitiatorOnline) {
|
||||
this.socket.emit("initiator-online", {
|
||||
roomId: this.roomId,
|
||||
});
|
||||
}
|
||||
if (developmentEnv === 'true')postLogInDebug(`peerId:${this.socket.id} Successfully joined room: ${response.roomId},isInitiator:${this.isInitiator},isInRoom:${this.isInRoom}`);
|
||||
if (developmentEnv === "true")
|
||||
postLogInDebug(
|
||||
`peerId:${this.socket.id} Successfully joined room: ${response.roomId},isInitiator:${this.isInitiator},isInRoom:${this.isInRoom}`
|
||||
);
|
||||
resolve();
|
||||
} else {
|
||||
this.isInRoom = false;
|
||||
this.roomId = null;
|
||||
if (developmentEnv === 'true')postLogInDebug(`Failed to join room,message:${response.message}`);
|
||||
console.error('Failed to join room:', response.message);
|
||||
if (developmentEnv === "true")
|
||||
postLogInDebug(`Failed to join room,message:${response.message}`);
|
||||
this.fireError("Failed to join room", { message: response.message });
|
||||
reject(new Error(response.message));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// 发送加入房间请求
|
||||
try {
|
||||
this.socket.emit('join', {roomId});
|
||||
this.socket.emit("join", { roomId });
|
||||
} catch (error) {
|
||||
clearTimeout(timeout);
|
||||
this.isInRoom = false;
|
||||
@@ -361,12 +438,12 @@ export default class BaseWebRTC {
|
||||
//发送给特定对象
|
||||
protected sendToPeer(data: any, peerId: string): boolean {
|
||||
const dataChannel = this.dataChannels.get(peerId);
|
||||
if (dataChannel?.readyState === 'open') {
|
||||
if (dataChannel?.readyState === "open") {
|
||||
dataChannel.send(data);
|
||||
return true;
|
||||
}
|
||||
|
||||
console.warn(`Data channel not ready for peer ${peerId}. Retrying...`);
|
||||
|
||||
this.log("warn", `Data channel not ready for peer ${peerId}. Retrying...`);
|
||||
this.retryDataSend(data, peerId);
|
||||
return false;
|
||||
}
|
||||
@@ -377,14 +454,19 @@ export default class BaseWebRTC {
|
||||
|
||||
const attemptSend = () => {
|
||||
const dataChannel = this.dataChannels.get(peerId);
|
||||
if (dataChannel?.readyState === 'open') {
|
||||
if (dataChannel?.readyState === "open") {
|
||||
dataChannel.send(data);
|
||||
} else if (retryCount < maxRetries) {
|
||||
retryCount++;
|
||||
console.log(`Retrying to send data to peer ${peerId}. Attempt ${retryCount} of ${maxRetries}`);
|
||||
this.log(
|
||||
"log",
|
||||
`Retrying to send data to peer ${peerId}. Attempt ${retryCount} of ${maxRetries}`
|
||||
);
|
||||
setTimeout(attemptSend, 1000);
|
||||
} else {
|
||||
console.error(`Failed to send data to peer ${peerId} after maximum retries`);
|
||||
this.fireError(
|
||||
`Failed to send data to peer ${peerId} after maximum retries`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -422,6 +504,6 @@ export default class BaseWebRTC {
|
||||
}
|
||||
// 抽象方法声明
|
||||
protected createDataChannel(peerId: string) {
|
||||
throw new Error('createDataChannel must be implemented by subclass');
|
||||
throw new Error("createDataChannel must be implemented by subclass");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user