yirtc-sdk-web
Version:
基于WebRTC的实时音视频SDK
262 lines (261 loc) • 9.19 kB
JavaScript
/**
* 信令模块 - 负责处理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);
}
}