/** * 状态管理 * 使用简单的 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配置 import { showNotification } from './utils.js'; // 导入通知函数 // 默认视频流尺寸 const defaultStreamWidth = 1280; const defaultStreamHeight = 720; class CallStateManager { constructor() { const renderstreaming=null; // WebRTC连接管理实例 const useWebSocket=null; // 是否使用WebSocket信令 const connectionId=null; // 连接ID // 核心状态 this.state = { session: { ...mockCallSession, status: 'idle' // 初始状态为空闲 }, messages: [...mockMessages], isSidebarOpen: false, unreadCount: 0, localStream: null, // MediaStream 对象 remoteStream: null // MediaStream 对象 }; // 监听器数组 this.listeners = []; } // 订阅状态变化 subscribe(callback) { this.listeners.push(callback); return () => { this.listeners = this.listeners.filter(cb => cb !== callback); }; } // 通知所有监听器 notify(changes) { this.listeners.forEach(cb => cb(this.state, changes)); } // 初始化 async init() { // 启动通话时长计时器 this.durationInterval = setInterval(() => { this.state.session.duration++; this.notify({ type: 'DURATION_UPDATE', duration: this.state.session.duration }); }, 1000); // 初始化配置 await this.setupConfig(); // 获取本地摄像头视频流 await this.getLocalStream(); // 模拟远端音频活动 (实际应由 WebRTC VAD 检测触发) //this.simulateRemoteActivity(); // 模拟网络质量变化 this.simulateNetworkChange(); } async setupConfig() { const res = await getServerConfig(); this.useWebSocket = res.useWebSocket; } // 获取本地摄像头视频流 async getLocalStream() { try { console.log('Requesting camera permission...'); // 检查浏览器是否支持getUserMedia if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) { console.error('getUserMedia is not supported'); throw new Error('getUserMedia is not supported'); } // 请求摄像头权限并获取媒体流 const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true }); console.log('Stream obtained successfully:', stream); console.log('Video tracks:', stream.getVideoTracks()); console.log('Audio tracks:', stream.getAudioTracks()); this.state.localStream = stream; this.state.session.localUser.mediaState.video = true; this.state.session.localUser.mediaState.audio = true; console.log('Local stream stored, notifying UI...'); // 先通知视频流已获取 this.notify({ type: 'LOCAL_STREAM_OBTAINED', stream }); // 再通知媒体状态变化 this.notify({ type: 'LOCAL_MEDIA_CHANGE', mediaType: 'video', value: true }); this.notify({ type: 'LOCAL_MEDIA_CHANGE', mediaType: 'audio', value: true }); // 发送媒体状态到服务器 this.emitMediaStateChange(); } catch (error) { console.error('Error getting local stream:', error); // 如果获取视频失败,保持视频关闭状态 this.state.session.localUser.mediaState.video = false; this.state.session.localUser.mediaState.audio = false; // 通知媒体状态变化 this.notify({ type: 'LOCAL_MEDIA_CHANGE', mediaType: 'video', value: false }); this.notify({ type: 'LOCAL_MEDIA_CHANGE', mediaType: 'audio', value: false }); } } // 更新本地媒体状态 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 => { if (track.kind === 'video') { track.stop(); } }); } // 如果是音频状态变化,控制本地音频轨道 if (mediaType === 'audio' && this.state.localStream) { this.state.localStream.getTracks().forEach(track => { if (track.kind === 'audio') { track.enabled = value; } }); } } /** * 设置WebRTC连接 * @async * @returns {Promise} */ async setUp(connectionId) { //TODO this.connectionId = connectionId; // 获取连接ID // 设置状态为连接中 this.state.session.status = 'connecting'; this.notify({ type: 'CALL_STATUS_CHANGE', status: 'connecting' }); // 确保本地流已经初始化 if (!this.state.localStream) { console.log('Local stream not available, waiting for initialization...'); // 等待localStream初始化 await new Promise((resolve) => { const checkStream = () => { if (this.state.localStream) { resolve(); } else { setTimeout(checkStream, 100); } }; checkStream(); }); } // 创建信令实例 const signaling = this.useWebSocket ? new WebSocketSignaling() : new Signaling(); const config = getRTCConfiguration(); // 获取RTC配置 this.renderstreaming = new RenderStreaming(signaling, config); // 创建WebRTC连接管理实例 // 连接建立回调 this.renderstreaming.onConnect = () => { // 连接建立后,更新状态为ongoing this.state.session.status = 'ongoing'; this.notify({ type: 'CALL_STATUS_CHANGE', status: 'ongoing' }); if (this.state.localStream) { const tracks = this.state.localStream.getTracks(); // 获取本地媒体轨道 for (const track of tracks) { this.renderstreaming.addTransceiver(track, { direction: 'sendonly' }); // 添加发送轨道 } this.setCodecPreferences(); // 设置编解码器偏好 this.showStatsMessage(); // 显示统计信息 } else { console.error('Local stream is not available'); showNotification('本地视频流不可用', 'error'); } }; // 连接断开回调 this.renderstreaming.onDisconnect = () => { this.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); // 通知UI远程流已更新 this.notify({ type: 'REMOTE_STREAM_OBTAINED', stream: this.state.remoteStream }); } }; // 启动WebRTC连接 await this.renderstreaming.start(); await this.renderstreaming.createConnection(connectionId); } /** * 挂断WebRTC连接 * @async * @returns {Promise} */ async hangUp() { this.clearStatsMessage(); // 清除统计信息 this.messageDiv.style.display = 'block'; this.messageDiv.innerText = `Disconnect peer on ${this.connectionId}.`; // 删除连接并停止WebRTC await this.renderstreaming.deleteConnection(); await this.renderstreaming.stop(); this.renderstreaming = null; this.remoteVideo.srcObject = null; // 清除远程视频源 this.connectionId = null; // 启用编解码器选择 if (this.supportsSetCodecPreferences) { this.codecPreferences.disabled = false; } } /** * 设置编解码器偏好 */ setCodecPreferences() { /** @type {RTCRtpCodecCapability[] | null} */ let selectedCodecs = null; if (this.supportsSetCodecPreferences) { const preferredCodec = this.codecPreferences.options[this.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 = this.renderstreaming.getTransceivers().filter(t => t.receiver.track.kind == "video"); if (transceivers && transceivers.length > 0) { transceivers.forEach(t => t.setCodecPreferences(selectedCodecs)); } } // 更新远端媒体状态 (由 WebSocket 消息触发) updateRemoteMedia(mediaState) { this.state.session.remoteUser.mediaState = { ...this.state.session.remoteUser.mediaState, ...mediaState }; this.notify({ type: 'REMOTE_MEDIA_CHANGE', mediaState }); } // 添加消息 addMessage(message) { this.state.messages.push(message); // 如果侧边栏关闭且不是自己发的,增加未读 if (!this.state.isSidebarOpen && !message.isSelf) { this.state.unreadCount++; } this.notify({ type: 'NEW_MESSAGE', message, unreadCount: this.state.unreadCount }); } // 切换侧边栏 toggleSidebar() { this.state.isSidebarOpen = !this.state.isSidebarOpen; if (this.state.isSidebarOpen) { this.state.unreadCount = 0; } this.notify({ type: 'SIDEBAR_TOGGLE', isOpen: this.state.isSidebarOpen }); } // 结束通话 endCall() { clearInterval(this.durationInterval); this.state.session.status = 'ended'; this.notify({ type: 'CALL_ENDED' }); // 发送结束通话请求到服务器 // [API_CALL: POST /api/call/:callId/leave] // [WEBSOCKET_EMIT: leave-call] } // 加入通话 async joinCall(connectionId) { this.state.session.status = 'connecting'; this.notify({ type: 'CALL_STATUS_CHANGE', status: 'connecting' }); showNotification(`正在加入通话 (${connectionId})`); // 初始化 await this.init(); // 保存连接ID this.connectionId = connectionId; } // 创建通话 async createCall() { this.state.session.status = 'connecting'; this.notify({ type: 'CALL_STATUS_CHANGE', status: 'connecting' }); showNotification('正在创建通话...'); // 初始化 await this.init(); } // 模拟远端活动 (开发测试用) simulateRemoteActivity() { setInterval(() => { if (Math.random() > 0.7) { const isSpeaking = Math.random() > 0.5; this.updateRemoteMedia({ isSpeaking }); } }, 800); } // 模拟网络质量变化 (开发测试用) simulateNetworkChange() { const qualities = ['excellent', 'good', 'fair', 'poor']; setInterval(() => { if (Math.random() > 0.8) { const quality = qualities[Math.floor(Math.random() * qualities.length)]; this.state.session.remoteUser.networkQuality = quality; this.notify({ type: 'NETWORK_CHANGE', quality }); } }, 5000); } // 发送媒体状态到服务器 emitMediaStateChange() { const payload = { userId: this.state.session.localUser.id, ...this.state.session.localUser.mediaState }; console.log('[WebSocket Emit] media-state-changed:', payload); // socket.emit('media-state-changed', payload); } // 显示统计信息 showStatsMessage() { console.log('Showing stats message'); // 这里可以添加显示统计信息的逻辑 } // 清除统计信息 clearStatsMessage() { console.log('Clearing stats message'); // 这里可以添加清除统计信息的逻辑 } // Getters getState() { return this.state; } getLocalUser() { return this.state.session.localUser; } getRemoteUser() { return this.state.session.remoteUser; } getMessages() { return this.state.messages; } getConnectionId() { return this.connectionId; } } // 创建单例实例 const store = new CallStateManager(); // 页面卸载前清理 window.addEventListener('beforeunload', async () => { if (!store.renderstreaming) return; await store.renderstreaming.stop(); // 停止WebRTC连接 }, true); export default store;