UNPKG

yirtc-sdk-web

Version:

基于WebRTC的实时音视频SDK

262 lines (261 loc) 9.19 kB
/** * 信令模块 - 负责处理WebSocket连接和信令交换 */ import { EventEmitter } from './utils/eventEmitter'; import { Logger } from './utils/logger'; /** * 信令消息类型 */ export var SignalingMessageType; (function (SignalingMessageType) { SignalingMessageType["JOIN"] = "join"; SignalingMessageType["LEAVE"] = "leave"; SignalingMessageType["OFFER"] = "offer"; SignalingMessageType["ANSWER"] = "answer"; SignalingMessageType["CANDIDATE"] = "candidate"; SignalingMessageType["KEEPALIVE"] = "keepalive"; SignalingMessageType["ERROR"] = "error"; // 错误 })(SignalingMessageType || (SignalingMessageType = {})); /** * 信令类 - 处理与信令服务器的通信 */ export class Signaling extends EventEmitter { /** * 构造函数 * @param config 信令配置 */ constructor(config) { super(); this.ws = null; this.reconnectTimer = null; this.reconnectAttempts = 0; this.connected = false; this.roomId = null; this.userId = null; this.config = Object.assign({ autoReconnect: true, reconnectInterval: 3000, reconnectAttempts: 5 }, config); this.logger = new Logger('Signaling'); } /** * 连接到信令服务器 * @returns Promise<void> */ connect() { return new Promise((resolve, reject) => { if (this.ws && (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING)) { this.logger.warn('WebSocket已经连接或正在连接中'); resolve(); return; } try { this.ws = new WebSocket(this.config.url); this.ws.onopen = () => { this.logger.info('WebSocket连接已建立'); this.connected = true; this.reconnectAttempts = 0; this.emit('open'); resolve(); }; this.ws.onmessage = (event) => { try { const message = JSON.parse(event.data); this.logger.debug('收到消息', message); this.handleMessage(message); } catch (error) { this.logger.error('解析消息失败', error); } }; this.ws.onclose = () => { this.logger.info('WebSocket连接已关闭'); this.connected = false; this.emit('close'); this.attemptReconnect(); }; this.ws.onerror = (error) => { this.logger.error('WebSocket错误', error); this.emit('error', error); reject(error); }; } catch (error) { this.logger.error('创建WebSocket失败', error); reject(error); } }); } /** * 断开与信令服务器的连接 */ disconnect() { if (this.reconnectTimer) { window.clearTimeout(this.reconnectTimer); this.reconnectTimer = null; } if (this.ws) { this.ws.close(); this.ws = null; } this.connected = false; this.logger.info('已断开WebSocket连接'); } /** * 发送消息到信令服务器 * @param type 消息类型 * @param payload 消息内容 * @param to 接收者ID * @returns boolean 是否发送成功 */ sendMessage(type, payload, to = 'all') { if (!this.ws || this.ws.readyState !== WebSocket.OPEN) { this.logger.error('WebSocket未连接,无法发送消息'); return false; } const message = { type, payload, from: this.userId || undefined, to, roomId: this.roomId || undefined, timestamp: Date.now() }; try { this.ws.send(JSON.stringify(message)); this.logger.debug('发送消息', message); return true; } catch (error) { this.logger.error('发送消息失败', error); return false; } } /** * 加入房间 * @param roomId 房间ID * @param userId 用户ID * @param userData 用户数据 * @returns Promise<void> */ joinRoom(roomId, userId, userData) { return new Promise((resolve, reject) => { if (!this.connected) { reject(new Error('WebSocket未连接,请先连接')); return; } this.roomId = roomId; this.userId = userId; const onJoinSuccess = (response) => { this.off('join-success', onJoinSuccess); this.off('join-failed', onJoinFailed); resolve(response); }; const onJoinFailed = (error) => { this.off('join-success', onJoinSuccess); this.off('join-failed', onJoinFailed); reject(error); }; this.once('join-success', onJoinSuccess); this.once('join-failed', onJoinFailed); this.sendMessage(SignalingMessageType.JOIN, { roomId, userId, userData }); }); } /** * 离开房间 */ leaveRoom() { if (this.roomId && this.userId) { this.sendMessage(SignalingMessageType.LEAVE, { roomId: this.roomId, userId: this.userId }); this.roomId = null; this.userId = null; } } /** * 发送SDP offer * @param offer SDP offer * @param targetUserId 目标用户ID */ sendOffer(offer, targetUserId) { return this.sendMessage(SignalingMessageType.OFFER, { sdp: offer }, targetUserId); } /** * 发送SDP answer * @param answer SDP answer * @param targetUserId 目标用户ID */ sendAnswer(answer, targetUserId) { return this.sendMessage(SignalingMessageType.ANSWER, { sdp: answer }, targetUserId); } /** * 发送ICE candidate * @param candidate ICE candidate * @param targetUserId 目标用户ID */ sendCandidate(candidate, targetUserId) { return this.sendMessage(SignalingMessageType.CANDIDATE, { candidate }, targetUserId); } /** * 发送心跳包 */ sendKeepAlive() { return this.sendMessage(SignalingMessageType.KEEPALIVE, { timestamp: Date.now() }); } /** * 处理收到的消息 * @param message 收到的消息 */ handleMessage(message) { switch (message.type) { case SignalingMessageType.JOIN: if (message.payload.success) { this.emit('join-success', message.payload); this.emit('user-joined', Object.assign({ userId: message.from }, message.payload)); } else { this.emit('join-failed', message.payload); } break; case SignalingMessageType.LEAVE: this.emit('user-left', Object.assign({ userId: message.from }, message.payload)); break; case SignalingMessageType.OFFER: this.emit('offer', { from: message.from, sdp: message.payload.sdp }); break; case SignalingMessageType.ANSWER: this.emit('answer', { from: message.from, sdp: message.payload.sdp }); break; case SignalingMessageType.CANDIDATE: this.emit('candidate', { from: message.from, candidate: message.payload.candidate }); break; case SignalingMessageType.ERROR: this.emit('signaling-error', message.payload); break; default: this.logger.warn('收到未知类型的消息', message); break; } } /** * 尝试重新连接 */ attemptReconnect() { if (!this.config.autoReconnect || this.reconnectTimer !== null || (this.config.reconnectAttempts !== undefined && this.reconnectAttempts >= this.config.reconnectAttempts)) { return; } this.reconnectAttempts++; this.logger.info(`尝试重新连接 (${this.reconnectAttempts}/${this.config.reconnectAttempts})`); this.reconnectTimer = window.setTimeout(() => { this.reconnectTimer = null; this.connect().catch(() => { this.attemptReconnect(); }); }, this.config.reconnectInterval); } }