yirtc-sdk-web
Version:
基于WebRTC的实时音视频SDK
431 lines (430 loc) • 15.5 kB
JavaScript
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;
}
}