diff --git a/WebApp/client/public/onebyone/index.html b/WebApp/client/public/onebyone/index.html index e81f48f..cf418b7 100644 --- a/WebApp/client/public/onebyone/index.html +++ b/WebApp/client/public/onebyone/index.html @@ -148,11 +148,12 @@
- 对方视频 +
diff --git a/WebApp/client/public/onebyone/main.js b/WebApp/client/public/onebyone/main.js index 361e183..823a698 100644 --- a/WebApp/client/public/onebyone/main.js +++ b/WebApp/client/public/onebyone/main.js @@ -317,13 +317,6 @@ function bindDomEvents() { } } -/** - * 初始化WebRTC - */ -function initWebRTC() { - // 这里可以添加WebRTC初始化代码 - console.log('Initializing WebRTC...'); -} // 页面加载完成后初始化 window.addEventListener('DOMContentLoaded', async () => { @@ -338,13 +331,16 @@ window.addEventListener('DOMContentLoaded', async () => { } // 初始化 store - store.init(); - - // 加入通话 - store.joinCall(connectionId); + //await store.init(); // 初始化渲染器 - const renderer = new UIRenderer(store); + new UIRenderer(store); + + // 加入通话 + await store.joinCall(connectionId); + + // 设置WebRTC连接 + await store.setUp(connectionId); // 绑定DOM事件 bindDomEvents(); diff --git a/WebApp/client/public/onebyone/renderer.js b/WebApp/client/public/onebyone/renderer.js index 56a5bfc..bcc0e54 100644 --- a/WebApp/client/public/onebyone/renderer.js +++ b/WebApp/client/public/onebyone/renderer.js @@ -89,6 +89,9 @@ class UIRenderer { this.renderLocalStream(state.localStream); this.renderLocalVideo(state.session.localUser, state.localStream); break; + case 'REMOTE_STREAM_OBTAINED': + this.renderRemoteStream(state.remoteStream); + break; case 'REMOTE_MEDIA_CHANGE': this.renderRemoteVideo(state.session.remoteUser); this.renderUserList(state.session.localUser, state.session.remoteUser); @@ -189,6 +192,22 @@ class UIRenderer { } } + // 渲染远程视频流 + renderRemoteStream(stream) { + if (this.elements.remoteVideo && stream) { + this.elements.remoteVideo.srcObject = stream; + this.elements.remoteVideo.autoplay = true; + console.log('Remote stream set successfully:', this.elements.remoteVideo.srcObject); + + // 隐藏断开连接覆盖层 + if (this.elements.disconnectedOverlay) { + this.elements.disconnectedOverlay.classList.add('hidden'); + } + } else { + console.error('Either remoteVideo element or stream is missing'); + } + } + // 渲染本地用户状态 renderLocalUserStatus(localUser) { // 更新本地媒体状态文本 diff --git a/WebApp/client/public/onebyone/store.js b/WebApp/client/public/onebyone/store.js index 8794365..46517f1 100644 --- a/WebApp/client/public/onebyone/store.js +++ b/WebApp/client/public/onebyone/store.js @@ -15,9 +15,9 @@ const defaultStreamHeight = 720; class CallStateManager { constructor() { - let renderstreaming; // WebRTC连接管理实例 - let useWebSocket; // 是否使用WebSocket信令 - let connectionId; // 连接ID + const renderstreaming=null; // WebRTC连接管理实例 + const useWebSocket=null; // 是否使用WebSocket信令 + const connectionId=null; // 连接ID // 核心状态 this.state = { session: { @@ -33,9 +33,6 @@ class CallStateManager { // 监听器数组 this.listeners = []; - - // 初始化 - //this.init(); } // 订阅状态变化 @@ -52,20 +49,19 @@ class CallStateManager { } // 初始化 - init() { + async init() { // 启动通话时长计时器 this.durationInterval = setInterval(() => { this.state.session.duration++; this.notify({ type: 'DURATION_UPDATE', duration: this.state.session.duration }); }, 1000); // 初始化配置 - this.setupConfig(); + await this.setupConfig(); // 获取本地摄像头视频流 - this.getLocalStream(); - + await this.getLocalStream(); // 模拟远端音频活动 (实际应由 WebRTC VAD 检测触发) - this.simulateRemoteActivity(); + //this.simulateRemoteActivity(); // 模拟网络质量变化 this.simulateNetworkChange(); @@ -176,26 +172,46 @@ class CallStateManager { async setUp(connectionId) { //TODO this.connectionId = connectionId; // 获取连接ID - codecPreferences.disabled = true; // 禁用编解码器选择 + + // 确保本地流已经初始化 + 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 = useWebSocket ? new WebSocketSignaling() : new Signaling(); + const signaling = this.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' }); // 添加发送轨道 + 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'); } - setCodecPreferences(); // 设置编解码器偏好 - showStatsMessage(); // 显示统计信息 }; // 连接断开回调 this.renderstreaming.onDisconnect = () => { - hangUp(); // 挂断连接 + this.hangUp(); // 挂断连接 }; // 轨道事件回调 @@ -206,6 +222,8 @@ class CallStateManager { this.state.remoteStream = new MediaStream(); } this.state.remoteStream.addTrack(data.track); + // 通知UI远程流已更新 + this.notify({ type: 'REMOTE_STREAM_OBTAINED', stream: this.state.remoteStream }); } }; @@ -221,21 +239,21 @@ class CallStateManager { * @returns {Promise} */ async hangUp() { - clearStatsMessage(); // 清除统计信息 - messageDiv.style.display = 'block'; - messageDiv.innerText = `Disconnect peer on ${connectionId}.`; + this.clearStatsMessage(); // 清除统计信息 + this.messageDiv.style.display = 'block'; + this.messageDiv.innerText = `Disconnect peer on ${this.connectionId}.`; // 删除连接并停止WebRTC - await renderstreaming.deleteConnection(); - await renderstreaming.stop(); - renderstreaming = null; - remoteVideo.srcObject = null; // 清除远程视频源 + await this.renderstreaming.deleteConnection(); + await this.renderstreaming.stop(); + this.renderstreaming = null; + this.remoteVideo.srcObject = null; // 清除远程视频源 - connectionId = null; + this.connectionId = null; // 启用编解码器选择 - if (supportsSetCodecPreferences) { - codecPreferences.disabled = false; + if (this.supportsSetCodecPreferences) { + this.codecPreferences.disabled = false; } } /** @@ -245,8 +263,8 @@ class CallStateManager { /** @type {RTCRtpCodecCapability[] | null} */ let selectedCodecs = null; - if (supportsSetCodecPreferences) { - const preferredCodec = codecPreferences.options[codecPreferences.selectedIndex]; + 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'); @@ -261,7 +279,7 @@ class CallStateManager { } // 获取视频收发器并设置编解码器偏好 - const transceivers = renderstreaming.getTransceivers().filter(t => t.receiver.track.kind == "video"); + const transceivers = this.renderstreaming.getTransceivers().filter(t => t.receiver.track.kind == "video"); if (transceivers && transceivers.length > 0) { transceivers.forEach(t => t.setCodecPreferences(selectedCodecs)); } @@ -310,26 +328,26 @@ class CallStateManager { } // 加入通话 - joinCall(connectionId) { + async joinCall(connectionId) { this.state.session.status = 'connecting'; this.notify({ type: 'CALL_STATUS_CHANGE', status: 'connecting' }); showNotification(`正在加入通话 (${connectionId})`); // 初始化 - this.init(); + await this.init(); // 保存连接ID this.connectionId = connectionId; } // 创建通话 - createCall() { + async createCall() { this.state.session.status = 'connecting'; this.notify({ type: 'CALL_STATUS_CHANGE', status: 'connecting' }); showNotification('正在创建通话...'); // 初始化 - this.init(); + await this.init(); } // 模拟远端活动 (开发测试用) @@ -364,6 +382,18 @@ class CallStateManager { // 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; } diff --git a/WebApp/src/class/httphandler.ts b/WebApp/src/class/httphandler.ts index 04897ac..c5b65b0 100644 --- a/WebApp/src/class/httphandler.ts +++ b/WebApp/src/class/httphandler.ts @@ -7,7 +7,7 @@ import Offer from './offer'; import Answer from './answer'; import Candidate from './candidate'; import { v4 as uuid } from 'uuid'; - +import { onGetAllConnectionIds } from './websockethandler'; /** * 断开连接记录类 * 用于记录断开连接的信息 @@ -1073,6 +1073,38 @@ function onGetConnections(req: Request, res: Response): void { res.json({ rooms: rooms, totalRooms: rooms.length }); } + +/** + * @swagger + * /signaling/connection-ids: + * get: + * summary: 获取所有连接ID + * description: 获取所有当前活跃的连接ID + * security: + * - sessionAuth: [] + * responses: + * 200: + * description: 成功获取连接ID列表 + * content: + * application/json: + * schema: + * type: object + * properties: + * connectionIds: + * type: array + * items: + * type: string + * description: 连接ID + * totalCount: + * type: number + * description: 总连接数 + */ +function getAllConnectionIds(req: Request, res: Response): void { + // 获取所有连接ID + const connectionIds = onGetAllConnectionIds(); + // 返回JSON响应,包含连接ID列表和总数量 + res.json({ connectionIds: connectionIds, totalCount: connectionIds.length }); +} /** * 导出HTTP处理器函数 */ @@ -1091,5 +1123,6 @@ export { postOffer, // 处理offer信令消息 postAnswer, // 处理answer信令消息 postCandidate, // 处理candidate信令消息 - onGetConnections // 获取房间和用户信息 + onGetConnections, // 获取房间和用户信息 + getAllConnectionIds // 获取所有连接ID }; diff --git a/WebApp/src/class/websockethandler.ts b/WebApp/src/class/websockethandler.ts index fb55ff4..5ffc5dc 100644 --- a/WebApp/src/class/websockethandler.ts +++ b/WebApp/src/class/websockethandler.ts @@ -60,7 +60,7 @@ function reset(mode: string): void { */ function add(ws: WebSocket): void { // 为新连接创建空的连接ID集合 - var id = new Set(); + const id = new Set(); clients.set(ws, id); // 记录添加WebSocket连接的日志 console.log(`Add WebSocket: ${id}`); @@ -347,7 +347,22 @@ function onCandidate(ws: WebSocket, message: any): void { clearInterval((ws as any).heartbeatTimer); } } + /** + * 处理获取所有连接ID的请求 + * @param ws WebSocket连接实例 + */ + function onGetAllConnectionIds(): string[] { + // 获取所有connectionId + const connectionIds = Array.from(connectionPair.keys()); + // 发送连接ID列表给客户端 + // ws.send(JSON.stringify({ + // type: "connection-ids", + // connectionIds: connectionIds + // })); + return connectionIds; + } + /** * 导出WebSocket处理器函数 */ - export { reset, add, remove, onConnect, onDisconnect, onOffer, onAnswer, onCandidate,onCallConnectionId, onBroadcast, AddHeartbeat, RemoveHeartbeat }; + export { reset, add, remove, onConnect, onDisconnect, onOffer, onAnswer, onCandidate, onCallConnectionId, onBroadcast, onGetAllConnectionIds, AddHeartbeat, RemoveHeartbeat }; diff --git a/WebApp/src/signaling.ts b/WebApp/src/signaling.ts index 636ed47..c523e82 100644 --- a/WebApp/src/signaling.ts +++ b/WebApp/src/signaling.ts @@ -2,6 +2,11 @@ import * as express from 'express'; import * as handler from'./class/httphandler'; const router: express.Router = express.Router(); + +// 不需要会话ID的路由 +router.get('/connection-ids', handler.getAllConnectionIds); + +// 需要会话ID的路由 router.use(handler.checkSessionId); router.get('/connection', handler.getConnection); router.get('/offer', handler.getOffer);