【m】开始尝试接入后端

This commit is contained in:
zhangzheng
2026-03-04 17:55:55 +08:00
parent fd00100808
commit 93b56da25e
11 changed files with 681 additions and 217 deletions

View File

@@ -257,7 +257,7 @@
<div class="flex items-center gap-3 p-2 rounded-lg bg-white/5" data-user-id="remote">
<div class="relative">
<!-- [DATA_FIELD: remoteUser.avatar] -->
<img src="https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=100&h=100&fit=crop"
<img src=""
class="w-10 h-10 rounded-full object-cover"
data-field="remoteUser.avatar">
<!-- [CONDITIONAL_RENDER: remoteUser.status === 'online'] -->
@@ -278,7 +278,7 @@
<!-- 本地用户项 -->
<div class="flex items-center gap-3 p-2 rounded-lg hover:bg-white/5" data-user-id="local">
<!-- [DATA_FIELD: localUser.avatar] -->
<img src="https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=100&h=100&fit=crop"
<img src=""
class="w-10 h-10 rounded-full object-cover"
data-field="localUser.avatar">
<div class="flex-1">
@@ -328,7 +328,6 @@
</div>
<!-- [DATA_FIELD: message.content] -->
<div class="glass px-3 py-2 rounded-2xl rounded-tl-none text-sm text-gray-200" data-field="message.content">
嗨,能听到我说话吗?
</div>
</div>
</div>
@@ -348,7 +347,6 @@
<span class="text-xs text-gray-500" data-field="message.time">14:32</span>
</div>
<div class="bg-indigo-600 px-3 py-2 rounded-2xl rounded-tr-none text-sm text-white" data-field="message.content">
很清楚!你的画面也很清晰 👍
</div>
</div>
</div>
@@ -506,6 +504,36 @@
</div>
</div>
<!-- 通话请求弹窗 -->
<div id="callRequestDialog" class="fixed inset-0 bg-black/70 flex items-center justify-center z-50 hidden">
<div class="glass rounded-2xl p-6 w-80 max-w-md">
<div class="text-center mb-6">
<div class="w-16 h-16 bg-indigo-500/20 rounded-full flex items-center justify-center mx-auto mb-4">
<i class="fas fa-video text-indigo-500 text-2xl"></i>
</div>
<h3 class="text-xl font-bold mb-2" id="callRequestName">Sarah Chen</h3>
<p class="text-gray-400 text-sm" id="callRequestText">正在请求与您进行视频通话</p>
<div class="mt-4 flex items-center justify-center gap-4">
<img id="callRequestAvatar" src="https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=100&h=100&fit=crop" class="w-16 h-16 rounded-full object-cover border-4 border-indigo-500">
</div>
</div>
<div class="flex gap-3">
<button id="rejectCall" class="flex-1 py-2 rounded-lg glass hover:bg-white/10 transition-colors">
<div class="flex items-center justify-center gap-2">
<i class="fas fa-phone-slash"></i>
<span>拒绝</span>
</div>
</button>
<button id="acceptCall" class="flex-1 py-2 rounded-lg bg-green-500 hover:bg-green-600 transition-colors">
<div class="flex items-center justify-center gap-2">
<i class="fas fa-phone"></i>
<span>接受</span>
</div>
</button>
</div>
</div>
</div>
<!-- 引入模块化JavaScript文件 -->
<script type="module" src="main.js"></script>

View File

@@ -6,11 +6,12 @@ import store from './store.js';
import UIRenderer from './renderer.js';
import apiClient from './api.js';
import wsManager from './websocket.js';
import { mockCallSession } from './models.js';
import { showNotification, generateId } from './utils.js';
// 全局变量
let renderer = null;
let connectionId = "";
/**
* 初始化应用
*/
@@ -30,6 +31,7 @@ function initApp() {
// 初始化WebRTC (如果需要)
// initWebRTC();
console.log('App initialized');
}
@@ -85,6 +87,19 @@ function bindWebSocketEvents() {
store.endCall();
showNotification('通话已结束', 3000);
});
wsManager.on('call-request', (data) => {
console.log('Call request received:', data);
// 显示通话请求弹窗
if (window.showCallRequest) {
const caller = {
name: mockCallSession.remoteUser.name,
avatar:mockCallSession.remoteUser.avatar
};
window.showCallRequest(caller);
connectionId =data.connectionId;
}
});
}
/**
@@ -92,31 +107,31 @@ function bindWebSocketEvents() {
*/
function bindDomEvents() {
// 切换侧边栏
window.toggleSidebar = function() {
window.toggleSidebar = function () {
store.toggleSidebar();
};
// 切换麦克风
window.toggleMute = function(button) {
window.toggleMute = function (button) {
const state = store.getState();
const currentState = state.session.localUser.mediaState.audio;
store.updateLocalMedia('audio', !currentState);
};
// 切换视频
window.toggleVideo = function(button) {
window.toggleVideo = function (button) {
const state = store.getState();
const currentState = state.session.localUser.mediaState.video;
store.updateLocalMedia('video', !currentState);
};
// 切换本地视频(用于悬停控制)
window.toggleLocalVideo = function() {
window.toggleLocalVideo = function () {
window.toggleVideo();
};
// 切换录屏
window.toggleRecording = function(button) {
window.toggleRecording = function (button) {
const state = store.getState();
const currentState = state.session.localUser.mediaState.recording || false;
store.updateLocalMedia('recording', !currentState);
@@ -130,25 +145,64 @@ function bindDomEvents() {
};
// 结束通话
window.endCall = function() {
window.endCall = function () {
// 显示确认对话框
document.getElementById('endCallDialog').classList.remove('hidden');
};
// 取消结束通话
window.cancelEndCall = function() {
window.cancelEndCall = function () {
document.getElementById('endCallDialog').classList.add('hidden');
};
// 确认结束通话
window.confirmEndCall = function() {
window.confirmEndCall = function () {
document.getElementById('endCallDialog').classList.add('hidden');
store.endCall();
showNotification('通话已结束');
};
// 显示通话请求弹窗
window.showCallRequest = function (caller) {
const dialog = document.getElementById('callRequestDialog');
if (dialog) {
// 设置通话请求信息
if (document.getElementById('callRequestName')) {
document.getElementById('callRequestName').textContent = caller.name;
}
if (document.getElementById('callRequestAvatar')) {
document.getElementById('callRequestAvatar').src = caller.avatar;
}
// 显示弹窗
dialog.classList.remove('hidden');
}
};
// 拒绝通话
window.rejectCall = function () {
const dialog = document.getElementById('callRequestDialog');
if (dialog) {
dialog.classList.add('hidden');
}
showNotification('已拒绝通话请求');
// 可以在这里添加发送拒绝通话请求到服务器的逻辑
};
// 接受通话
window.acceptCall = function () {
const dialog = document.getElementById('callRequestDialog');
if (dialog) {
dialog.classList.add('hidden');
}
showNotification('已接受通话请求');
// 可以在这里添加发送接受通话请求到服务器的逻辑
// 然后初始化通话
store.initCall();
store.setUp(connectionId);
};
// 发送消息
window.sendMessage = function() {
window.sendMessage = function () {
const chatInput = document.getElementById('chatInput');
const content = chatInput.value.trim();
@@ -174,19 +228,19 @@ function bindDomEvents() {
};
// 处理聊天输入回车
window.handleChatSubmit = function(event) {
window.handleChatSubmit = function (event) {
if (event.key === 'Enter') {
window.sendMessage();
}
};
// 打开图片选择器
window.openImagePicker = function() {
window.openImagePicker = function () {
document.getElementById('imageInput').click();
};
// 处理图片上传
window.handleImageUpload = function(event) {
window.handleImageUpload = function (event) {
const file = event.target.files[0];
if (file) {
// 检查文件类型
@@ -203,7 +257,7 @@ function bindDomEvents() {
// 读取图片文件
const reader = new FileReader();
reader.onload = function(e) {
reader.onload = function (e) {
const imageUrl = e.target.result;
sendImageMessage(imageUrl, file.name);
};
@@ -253,6 +307,14 @@ function bindDomEvents() {
// 绑定对话框事件
document.getElementById('cancelEndCall').addEventListener('click', window.cancelEndCall);
document.getElementById('confirmEndCall').addEventListener('click', window.confirmEndCall);
// 绑定通话请求对话框事件
if (document.getElementById('rejectCall')) {
document.getElementById('rejectCall').addEventListener('click', window.rejectCall);
}
if (document.getElementById('acceptCall')) {
document.getElementById('acceptCall').addEventListener('click', window.acceptCall);
}
}
/**

View File

@@ -67,7 +67,7 @@ const mockCallSession = {
localUser: {
id: "user-local-001",
name: "我",
avatar: "https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=400&h=400&fit=crop",
avatar: "/images/p1.png",
isHost: true,
mediaState: {
audio: true,
@@ -81,8 +81,8 @@ const mockCallSession = {
// 远端用户信息
remoteUser: {
id: "user-remote-002",
name: "Sarah Chen",
avatar: "https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=400&h=400&fit=crop",
name: "Unity",
avatar: "/images/p2.png",
status: "online", // online | offline | connecting
networkQuality: "excellent", // excellent | good | fair | poor
mediaState: {
@@ -110,8 +110,8 @@ const mockMessages = [
{
id: "msg-002",
senderId: "user-remote-002",
senderName: "Sarah Chen",
senderAvatar: "https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=100&h=100&fit=crop",
senderName: mockCallSession.remoteUser.name,
senderAvatar: mockCallSession.remoteUser.avatar,
content: "嗨,能听到我说话吗?",
type: "text",
timestamp: "2024-01-15T14:32:15.000Z",
@@ -120,8 +120,8 @@ const mockMessages = [
{
id: "msg-003",
senderId: "user-local-001",
senderName: "我",
senderAvatar: "https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=100&h=100&fit=crop",
senderName: mockCallSession.localUser.name,
senderAvatar: mockCallSession.localUser.avatar,
content: "很清楚!你的画面也很清晰 👍",
type: "text",
timestamp: "2024-01-15T14:32:45.000Z",

View File

@@ -3,7 +3,7 @@
* 负责将状态映射到DOM与状态管理解耦
*/
import { formatTime, formatTimestamp, toggleElement, toggleButtonState } from './utils.js';
import {mockCallSession } from './models.js';
class UIRenderer {
constructor(stateManager) {
this.stateManager = stateManager;
@@ -40,14 +40,14 @@ class UIRenderer {
userList: document.getElementById('userList'),
localMediaStatus: document.getElementById('localMediaStatus'),
localMuteIcon: document.querySelector('[data-field="localUser.muteIcon"]'),
// 控制按钮
micBtn: document.getElementById('micBtn'),
videoBtn: document.getElementById('videoBtn'),
recordBtn: document.getElementById('recordBtn'),
connectionQuality: document.getElementById('connectionQuality')
};
// 订阅状态变化
this.unsubscribe = stateManager.subscribe(this.render.bind(this));
// 初始化渲染
this.render(this.stateManager.getState(), { type: 'INIT' });
}
@@ -58,23 +58,27 @@ class UIRenderer {
case 'INIT':
this.renderHeader(state.session);
this.renderRemoteVideo(state.session.remoteUser);
this.renderLocalVideo(state.session.localUser);
this.renderLocalVideo(state.session.localUser, state.localStream);
this.renderControlButtons(state.session.localUser.mediaState);
this.renderChatMessages(state.messages);
this.renderUserList(state.session.localUser, state.session.remoteUser);
break;
case 'DURATION_UPDATE':
this.renderCallDuration(changes.duration);
break;
case 'LOCAL_MEDIA_CHANGE':
this.renderControlButtons(state.session.localUser.mediaState);
this.renderLocalVideo(state.session.localUser);
this.renderLocalVideo(state.session.localUser, state.localStream);
this.renderLocalUserStatus(state.session.localUser);
this.renderUserList(state.session.localUser, state.session.remoteUser);
break;
case 'LOCAL_STREAM_OBTAINED':
this.renderLocalStream(state.localStream);
this.renderLocalVideo(state.session.localUser, state.localStream);
break;
case 'REMOTE_MEDIA_CHANGE':
this.renderRemoteVideo(state.session.remoteUser);
this.renderUserList(state.session.localUser, state.session.remoteUser);
break;
case 'NEW_MESSAGE':
this.renderChatMessages(state.messages);
@@ -140,9 +144,11 @@ class UIRenderer {
}
// 渲染本地视频
renderLocalVideo(localUser) {
renderLocalVideo(localUser, localStream) {
if (this.elements.localVideoPlaceholder) {
toggleElement(this.elements.localVideoPlaceholder, !localUser.mediaState.video);
// 当没有视频流或视频关闭时显示占位符
const shouldShowPlaceholder = !localStream || !localUser.mediaState.video;
toggleElement(this.elements.localVideoPlaceholder, shouldShowPlaceholder);
}
if (this.elements.localAudioWave) {
@@ -192,6 +198,41 @@ class UIRenderer {
}
}
// 渲染侧边栏用户列表
renderUserList(localUser, remoteUser) {
if (!this.elements.userList) return;
// 渲染本地用户
const localUserElement = this.elements.userList.querySelector('[data-user-id="local"]');
if (localUserElement) {
// 渲染本地用户头像
const localAvatar = localUserElement.querySelector('img[data-field="localUser.avatar"]');
if (localAvatar) {
localAvatar.src = localUser.avatar;
}
// 渲染本地用户名字
const localName = localUserElement.querySelector('[data-field="localUser.name"]');
if (localName) {
localName.textContent = localUser.name;
}
}
// 渲染远程用户
const remoteUserElement = this.elements.userList.querySelector('[data-user-id="remote"]');
if (remoteUserElement) {
// 渲染远程用户头像
const remoteAvatar = remoteUserElement.querySelector('img[data-field="remoteUser.avatar"]');
if (remoteAvatar) {
remoteAvatar.src = remoteUser.avatar;
}
// 渲染远程用户名字
const remoteName = remoteUserElement.querySelector('[data-field="remoteUser.name"]');
if (remoteName) {
remoteName.textContent = remoteUser.name;
}
}
}
// 渲染控制按钮
renderControlButtons(mediaState) {
if (this.elements.micBtn) {

View File

@@ -3,9 +3,20 @@
* 使用简单的 Observable 模式,可替换为 Redux/Vuex/Pinia
*/
import { mockCallSession, mockMessages } from './models.js';
import { Signaling, WebSocketSignaling } from "../../module/signaling.js";// 信令管理
import { RenderStreaming } from "../../module/renderstreaming.js"; // WebRTC连接管理
import { getServerConfig, getRTCConfiguration } from "../js/config.js";//服务器配置和RTC配置
// 默认视频流尺寸
const defaultStreamWidth = 1280;
const defaultStreamHeight = 720;
class CallStateManager {
constructor() {
let renderstreaming; // WebRTC连接管理实例
let useWebSocket; // 是否使用WebSocket信令
let connectionId; // 连接ID
// 核心状态
this.state = {
session: { ...mockCallSession },
@@ -20,7 +31,7 @@ class CallStateManager {
this.listeners = [];
// 初始化
this.init();
//this.init();
}
// 订阅状态变化
@@ -43,17 +54,22 @@ class CallStateManager {
this.state.session.duration++;
this.notify({ type: 'DURATION_UPDATE', duration: this.state.session.duration });
}, 1000);
// 初始化配置
this.setupConfig();
// 获取本地摄像头视频流
this.getLocalStream();
// 模拟远端音频活动 (实际应由 WebRTC VAD 检测触发)
this.simulateRemoteActivity();
// 模拟网络质量变化
this.simulateNetworkChange();
}
async setupConfig() {
const res = await getServerConfig();
this.useWebSocket = res.useWebSocket;
}
// 获取本地摄像头视频流
async getLocalStream() {
try {
@@ -103,6 +119,30 @@ class CallStateManager {
// 更新本地媒体状态
async updateLocalMedia(mediaType, value) {
// 如果是开启视频,重新获取摄像头资源
if (mediaType === 'video' && value) {
if (this.state.localStream) {
this.state.localStream = null;
}
//if(this.state.localStream.getVideoTracks().length==0){
// 请求摄像头权限并获取媒体流
this.state.localStream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true
});
// }
await this.getLocalStream();
} else {
// 直接更新媒体状态
this.state.session.localUser.mediaState[mediaType] = value;
this.notify({ type: 'LOCAL_MEDIA_CHANGE', mediaType, value });
// 发送媒体状态到服务器
this.emitMediaStateChange();
}
// 如果是关闭视频,释放摄像头资源
if (mediaType === 'video' && !value && this.state.localStream) {
this.state.localStream.getTracks().forEach(track => {
@@ -122,28 +162,107 @@ class CallStateManager {
});
}
// 如果是开启视频,重新获取摄像头资源
if (mediaType === 'video' && value ) {
if(this.state.localStream){
this.state.localStream=null;
}
//if(this.state.localStream.getVideoTracks().length==0){
// 请求摄像头权限并获取媒体流
this.state.localStream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true
});
// }
await this.getLocalStream();
} else {
// 直接更新媒体状态
this.state.session.localUser.mediaState[mediaType] = value;
this.notify({ type: 'LOCAL_MEDIA_CHANGE', mediaType, value });
// 发送媒体状态到服务器
this.emitMediaStateChange();
}
/**
* 设置WebRTC连接
* @async
* @returns {Promise<void>}
*/
async setUp(connectionId) {
//TODO
this.connectionId = connectionId; // 获取连接ID
codecPreferences.disabled = true; // 禁用编解码器选择
// 创建信令实例
const signaling = useWebSocket ? new WebSocketSignaling() : new Signaling();
const config = getRTCConfiguration(); // 获取RTC配置
this.renderstreaming = new RenderStreaming(signaling, config); // 创建WebRTC连接管理实例
// 连接建立回调
this.renderstreaming.onConnect = () => {
const tracks = this.state.localStream.getTracks(); // 获取本地媒体轨道
for (const track of tracks) {
this.renderstreaming.addTransceiver(track, { direction: 'sendonly' }); // 添加发送轨道
}
setCodecPreferences(); // 设置编解码器偏好
showStatsMessage(); // 显示统计信息
};
// 连接断开回调
this.renderstreaming.onDisconnect = () => {
hangUp(); // 挂断连接
};
// 轨道事件回调
this.renderstreaming.onTrackEvent = (data) => {
const direction = data.transceiver.direction;
if (direction == "sendrecv" || direction == "recvonly") {
if (this.state.remoteStream == null) {
this.state.remoteStream = new MediaStream();
}
this.state.remoteStream.addTrack(data.track);
}
};
// 启动WebRTC连接
await this.renderstreaming.start();
await this.renderstreaming.createConnection(connectionId);
}
/**
* 挂断WebRTC连接
* @async
* @returns {Promise<void>}
*/
async hangUp() {
clearStatsMessage(); // 清除统计信息
messageDiv.style.display = 'block';
messageDiv.innerText = `Disconnect peer on ${connectionId}.`;
// 删除连接并停止WebRTC
await renderstreaming.deleteConnection();
await renderstreaming.stop();
renderstreaming = null;
remoteVideo.srcObject = null; // 清除远程视频源
connectionId = null;
// 启用编解码器选择
if (supportsSetCodecPreferences) {
codecPreferences.disabled = false;
}
}
/**
* 设置编解码器偏好
*/
setCodecPreferences() {
/** @type {RTCRtpCodecCapability[] | null} */
let selectedCodecs = null;
if (supportsSetCodecPreferences) {
const preferredCodec = codecPreferences.options[codecPreferences.selectedIndex];
if (preferredCodec.value !== '') {
const [mimeType, sdpFmtpLine] = preferredCodec.value.split(' ');
const { codecs } = RTCRtpSender.getCapabilities('video');
const selectedCodecIndex = codecs.findIndex(c => c.mimeType === mimeType && c.sdpFmtpLine === sdpFmtpLine);
const selectCodec = codecs[selectedCodecIndex];
selectedCodecs = [selectCodec];
}
}
if (selectedCodecs == null) {
return;
}
// 获取视频收发器并设置编解码器偏好
const transceivers = renderstreaming.getTransceivers().filter(t => t.receiver.track.kind == "video");
if (transceivers && transceivers.length > 0) {
transceivers.forEach(t => t.setCodecPreferences(selectedCodecs));
}
}
// 更新远端媒体状态 (由 WebSocket 消息触发)
updateRemoteMedia(mediaState) {
@@ -228,4 +347,11 @@ class CallStateManager {
// 创建单例实例
const store = new CallStateManager();
// 页面卸载前清理
window.addEventListener('beforeunload', async () => {
if (!store.renderstreaming)
return;
await store.renderstreaming.stop(); // 停止WebRTC连接
}, true);
export default store;

View File

@@ -12,6 +12,8 @@ class WebSocketManager {
this.reconnectAttempts = 0;
this.maxReconnectAttempts = 5;
this.reconnectDelay = 1000;
this.connectionId = null;
this.heartbeatInterval = null;
}
/**
@@ -34,12 +36,23 @@ class WebSocketManager {
console.log('WebSocket connected');
this.isConnected = true;
this.reconnectAttempts = 0;
// 生成连接ID
this.connectionId = this.generateConnectionId();
// 发送连接消息
this.sendConnectMessage();
// 启动心跳
this.startHeartbeat();
this.emit('connect');
};
this.socket.onclose = () => {
console.log('WebSocket disconnected');
this.isConnected = false;
this.stopHeartbeat();
this.emit('disconnect');
this.attemptReconnect();
};
@@ -76,13 +89,28 @@ class WebSocketManager {
/**
* 发送消息
* @param {string} event - 事件名称
* @param {string} type - 消息类型
* @param {Object} data - 消息数据
*/
send(event, data) {
send(type, data) {
if (this.isConnected && this.socket) {
try {
const message = JSON.stringify({ event, data });
let message;
// 根据消息类型构建不同的消息格式
if (type === 'connect' || type === 'disconnect') {
message = JSON.stringify({ type, connectionId: this.connectionId });
} else if (type === 'offer' || type === 'answer' || type === 'candidate') {
message = JSON.stringify({ type, data });
} else if (type === 'broadcast') {
message = JSON.stringify({ type, message: data.message, targetConnectionId: data.targetConnectionId });
} else if (type === 'ping' || type === 'pong') {
message = JSON.stringify({ type });
} else {
// 兼容旧格式,用于自定义事件
message = JSON.stringify({ event: type, data });
}
this.socket.send(message);
} catch (error) {
console.error('Error sending WebSocket message:', error);
@@ -97,36 +125,60 @@ class WebSocketManager {
* @param {Object} message - 消息对象
*/
handleMessage(message) {
switch (message.type) {
case 'user-joined':
this.emit('user-joined', message.data);
break;
case 'user-left':
this.emit('user-left', message.data);
break;
case 'media-state-changed':
this.emit('media-state-changed', message.data);
break;
case 'message-received':
this.emit('message-received', message.data);
break;
case 'network-quality':
this.emit('network-quality', message.data);
break;
case 'call-ended':
this.emit('call-ended', message.data);
break;
case 'ping':
// 处理心跳请求回复pong
this.send('pong', {});
break;
case 'pong':
// 处理心跳响应
this.emit('pong');
break;
default:
this.emit('message', message);
break;
if (message.type) {
switch (message.type) {
case 'user-joined':
this.emit('user-joined', message.data);
break;
case 'user-left':
this.emit('user-left', message.data);
break;
case 'media-state-changed':
this.emit('media-state-changed', message.data);
break;
case 'message-received':
this.emit('message-received', message.data);
break;
case 'network-quality':
this.emit('network-quality', message.data);
break;
case 'call-ended':
this.emit('call-ended', message.data);
break;
case 'call-request':
this.emit('call-request', message.data);
break;
case 'ping':
// 处理心跳请求回复pong
this.send('pong');
break;
case 'pong':
// 处理心跳响应
this.emit('pong');
break;
case 'offer':
this.emit('offer', message.data);
break;
case 'answer':
this.emit('answer', message.data);
break;
case 'candidate':
this.emit('candidate', message.data);
break;
default:
// 处理旧格式消息
if (message.event) {
this.emit(message.event, message.data);
} else {
this.emit('message', message);
}
break;
}
} else if (message.event) {
// 处理旧格式消息
this.emit(message.event, message.data);
} else {
this.emit('message', message);
}
}
@@ -150,6 +202,57 @@ class WebSocketManager {
}
}
/**
* 生成连接ID
* @returns {string} 连接ID
*/
generateConnectionId() {
return 'conn_' + Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
}
/**
* 发送连接消息
*/
sendConnectMessage() {
this.send('connect');
}
/**
* 发送断开连接消息
*/
sendDisconnectMessage() {
this.send('disconnect');
}
/**
* 启动心跳
*/
startHeartbeat() {
this.heartbeatInterval = setInterval(() => {
if (this.isConnected) {
this.send('ping');
}
}, 30000); // 每30秒发送一次心跳
}
/**
* 停止心跳
*/
stopHeartbeat() {
if (this.heartbeatInterval) {
clearInterval(this.heartbeatInterval);
this.heartbeatInterval = null;
}
}
/**
* 获取连接ID
* @returns {string} 连接ID
*/
getConnectionId() {
return this.connectionId;
}
/**
* 订阅事件
* @param {string} event - 事件名称