UNPKG

yirtc-sdk-web

Version:

基于WebRTC的实时音视频SDK

431 lines (430 loc) 15.5 kB
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; /** * Socket.IO 信令适配器实现 */ import { EventEmitter } from '../../utils/eventEmitter'; import { Logger } from '../../utils/logger'; import { SignalingMessageType } from './interface'; // 注意:需要安装socket.io-client依赖 // npm install socket.io-client --save // 导入socket.io-client import * as SocketIO from 'socket.io-client'; // 保证兼容性的io对象 const io = SocketIO.default || SocketIO; /** * Socket.IO 信令适配器 * 实现基于 Socket.IO 的信令通信 */ export class SocketIOSignaling extends EventEmitter { /** * 构造函数 * @param config 信令配置 */ constructor(config) { super(); this.socket = null; this.reconnectTimer = null; this.reconnectAttempts = 0; this.connected = false; this.roomId = ''; this.userId = ''; // 自定义配置选项 this.socketOptions = {}; // 心跳检测相关 this.heartbeatTimer = null; this.heartbeatInterval = 5000; // 5秒一次心跳 this.heartbeatTimeout = 3000; // 3秒超时 this.missedHeartbeats = 0; this.maxMissedHeartbeats = 2; // 允许最多丢失2次心跳 this.config = Object.assign({ autoReconnect: true, reconnectInterval: 3000, reconnectAttempts: 5 }, config); // 保存Socket.IO特有的配置选项 if (config.hasOwnProperty('options')) { this.socketOptions = config.options || {}; } this.logger = new Logger('SocketIOSignaling'); } /** * 连接到信令服务器 * @returns Promise<void> */ connect() { return new Promise((resolve, reject) => { if (this.socket && this.connected) { this.logger.warn('Socket.IO已经连接'); resolve(); return; } // 先清除可能存在的心跳定时器 this.stopHeartbeat(); try { // 添加连接超时处理 const connectionTimeout = setTimeout(() => { if (!this.connected) { this.logger.error('连接超时'); this.emit('error', new Error('连接超时')); reject(new Error('连接超时')); // 如果连接超时,尝试断开连接 if (this.socket) { this.socket.disconnect(); this.socket = null; } } }, 10000); // 10秒超时 const options = Object.assign({ reconnection: this.config.autoReconnect, reconnectionAttempts: this.config.reconnectAttempts, reconnectionDelay: this.config.reconnectInterval, timeout: 10000 }, this.socketOptions); this.socket = io(this.config.url, options); this.socket.on('connect', () => { clearTimeout(connectionTimeout); this.logger.info('Socket.IO连接已建立'); this.connected = true; this.reconnectAttempts = 0; // 启动心跳检测 this.startHeartbeat(); this.emit('open'); resolve(); }); // 添加心跳响应处理 this.socket.on('heartbeat', (response) => { this.handleHeartbeatResponse(); }); this.socket.on('message', (message) => { try { this.logger.debug('收到消息', message); this.handleMessage(message); } catch (error) { this.logger.error('处理消息失败', error); } }); this.socket.on('disconnect', () => { this.logger.info('Socket.IO连接已关闭'); this.connected = false; this.emit('close'); }); // 添加连接错误事件处理 this.socket.on('connect_error', (error) => { clearTimeout(connectionTimeout); this.logger.error('Socket.IO连接错误', error); this.connected = false; this.emit('error', error); reject(error); }); // 添加连接超时事件处理 this.socket.on('connect_timeout', (timeout) => { clearTimeout(connectionTimeout); this.logger.error('Socket.IO连接超时', timeout); this.connected = false; this.emit('error', new Error('连接超时')); reject(new Error('连接超时')); }); this.socket.on('error', (error) => { clearTimeout(connectionTimeout); this.logger.error('Socket.IO错误', error); this.connected = false; this.emit('error', error); reject(error); }); } catch (error) { this.logger.error('创建Socket.IO连接失败', error); reject(error); } }); } /** * 断开与信令服务器的连接 */ disconnect() { // 停止心跳检测 this.stopHeartbeat(); if (this.socket) { this.socket.disconnect(); this.socket = null; } this.connected = false; this.logger.info('已断开Socket.IO连接'); } /** * 停止心跳检测 */ stopHeartbeat() { if (this.heartbeatTimer) { clearInterval(this.heartbeatTimer); this.heartbeatTimer = null; } this.missedHeartbeats = 0; } /** * 启动心跳检测 */ startHeartbeat() { // 先停止现有的心跳检测 this.stopHeartbeat(); // 如果没有连接,不启动心跳 if (!this.socket || !this.connected) { return; } this.heartbeatTimer = setInterval(() => { this.sendHeartbeat(); }, this.heartbeatInterval); } /** * 发送心跳包 */ sendHeartbeat() { if (!this.socket || !this.connected) { this.stopHeartbeat(); return; } // 增加丢失心跳计数 this.missedHeartbeats++; // 如果超过最大允许丢失次数,则认为连接已断开 if (this.missedHeartbeats > this.maxMissedHeartbeats) { this.logger.warn(`心跳检测失败,连续 ${this.missedHeartbeats} 次没有收到响应,认为连接已断开`); this.handleConnectionLost(); return; } // 发送心跳包 try { this.socket.emit('heartbeat', { timestamp: Date.now() }); // 设置超时处理 setTimeout(() => { // 如果在超时时间内没有收到响应,则不会重置 missedHeartbeats // 下一次心跳时会再次增加,直到超过最大允许值 }, this.heartbeatTimeout); } catch (error) { this.logger.error('发送心跳包失败', error); } } /** * 处理心跳响应 */ handleHeartbeatResponse() { // 重置丢失心跳计数 this.missedHeartbeats = 0; } /** * 处理连接丢失 */ handleConnectionLost() { this.logger.warn('检测到连接已断开'); this.stopHeartbeat(); this.connected = false; // 触发关闭事件 this.emit('close'); // 尝试断开并清理socket if (this.socket) { try { this.socket.disconnect(); } catch (e) { // 忽略错误 } this.socket = null; } } /** * 发送消息到信令服务器 * @param type 消息类型 * @param payload 消息内容 * @param to 接收者ID * @returns boolean 是否发送成功 */ sendMessage(type, payload, to = 'all') { if (!this.socket || !this.connected) { this.logger.error('Socket.IO未连接,无法发送消息'); return false; } const message = { type, from: this.userId, to, roomId: this.roomId, payload }; this.logger.debug('发送消息', message); this.socket.emit('message', message); return true; } /** * 处理接收到的消息 * @param message 消息对象 */ handleMessage(message) { if (!message || !message.type) { this.logger.warn('收到无效消息', message); return; } switch (message.type) { case SignalingMessageType.JOIN: this.emit('join', message); break; case SignalingMessageType.LEAVE: this.emit('leave', message); break; case SignalingMessageType.OFFER: this.emit('offer', message); break; case SignalingMessageType.ANSWER: this.emit('answer', message); break; case SignalingMessageType.CANDIDATE: this.emit('icecandidate', message); break; case SignalingMessageType.ERROR: this.emit('error', message); break; default: this.emit('message', message); break; } } /** * 加入房间 * @param roomId 房间ID * @param userId 用户ID * @param userData 用户数据 * @returns Promise<void> */ joinRoom(roomId, userId, userData) { return __awaiter(this, void 0, void 0, function* () { if (!this.connected) { throw new Error('Not connected to signaling server'); } this.roomId = roomId; this.userId = userId; return new Promise((resolve, reject) => { this.socket.emit(SignalingMessageType.JOIN, { type: SignalingMessageType.JOIN, payload: userData || {}, from: userId, roomId: roomId, timestamp: Date.now() }); const onJoin = (message) => { if (message.roomId === roomId) { this.off('join', onJoin); resolve(); } }; const onError = (message) => { var _a; if (message.roomId === roomId) { this.off('error', onError); reject(new Error(((_a = message.payload) === null || _a === void 0 ? void 0 : _a.message) || 'Failed to join room')); } }; // 设置超时 const timeout = setTimeout(() => { this.off('join', onJoin); this.off('error', onError); reject(new Error('Join room timeout')); }, 10000); this.once('join', (message) => { clearTimeout(timeout); onJoin(message); }); this.once('error', (message) => { clearTimeout(timeout); onError(message); }); }); }); } /** * 离开房间 */ leaveRoom() { if (!this.connected || !this.roomId) { return; } const message = { type: SignalingMessageType.LEAVE, from: this.userId, to: 'all', roomId: this.roomId, payload: { id: this.userId } }; this.logger.debug('离开房间', message); this.socket.emit('message', message); this.roomId = ''; } /** * 发送SDP offer * @param offer SDP offer * @param targetUserId 目标用户ID */ sendOffer(offer, targetUserId) { if (!this.connected || !this.roomId) { this.logger.warn('未连接或未加入房间,无法发送offer'); return false; } const message = { type: SignalingMessageType.OFFER, from: this.userId, to: targetUserId, roomId: this.roomId, payload: offer, timestamp: Date.now() }; this.logger.debug('发送offer', message); this.socket.emit('message', message); return true; } /** * 发送SDP answer * @param answer SDP answer * @param targetUserId 目标用户ID */ sendAnswer(answer, targetUserId) { if (!this.connected || !this.roomId) { this.logger.warn('未连接或未加入房间,无法发送answer'); return false; } const message = { type: SignalingMessageType.ANSWER, from: this.userId, to: targetUserId, roomId: this.roomId, payload: answer, timestamp: Date.now() }; this.logger.debug('发送answer', message); this.socket.emit('message', message); return true; } /** * 发送ICE candidate * @param candidate ICE candidate * @param targetUserId 目标用户ID */ sendCandidate(candidate, targetUserId) { if (!this.connected || !this.roomId) { this.logger.warn('未连接或未加入房间,无法发送candidate'); return false; } const message = { type: SignalingMessageType.CANDIDATE, from: this.userId, to: targetUserId, roomId: this.roomId, payload: candidate, timestamp: Date.now() }; this.logger.debug('发送candidate', message); this.socket.emit('message', message); return true; } }