diff --git a/client/public/call-view-controller.js b/client/public/call-view-controller.js new file mode 100644 index 0000000..2f50250 --- /dev/null +++ b/client/public/call-view-controller.js @@ -0,0 +1,138 @@ +export function createCallViewController({ store, chatMessage, notify }) { + let isBound = false; + + function toggleSidebar() { + chatMessage.toggleSidebar(); + } + + function toggleMute() { + const state = store.getState(); + const currentState = state.session.localUser.mediaState.audio; + store.updateLocalMedia('audio', !currentState); + } + + function toggleVideo() { + const state = store.getState(); + const currentState = state.session.localUser.mediaState.video; + store.updateLocalMedia('video', !currentState); + } + + function toggleLocalVideo() { + toggleVideo(); + } + + function toggleRecording() { + const state = store.getState(); + const currentState = state.session.localUser.mediaState.recording || false; + store.updateLocalMedia('recording', !currentState); + + if (!currentState) { + notify('\u5f00\u59cb\u5f55\u5236'); + } else { + notify('\u505c\u6b62\u5f55\u5236'); + } + } + + function toggleMoreOptions() { + const menu = document.getElementById('moreOptionsMenu'); + if (menu) { + menu.classList.toggle('hidden'); + } + } + + function changeResolution(width, height) { + store.changeResolution(width, height); + const menu = document.getElementById('moreOptionsMenu'); + if (menu) { + menu.classList.add('hidden'); + } + } + + function endCall() { + const dialog = document.getElementById('endCallDialog'); + if (dialog) { + dialog.classList.remove('hidden'); + } + } + + function cancelEndCall() { + const dialog = document.getElementById('endCallDialog'); + if (dialog) { + dialog.classList.add('hidden'); + } + } + + function confirmEndCall() { + cancelEndCall(); + store.endCall(); + notify('\u901a\u8bdd\u5df2\u7ed3\u675f'); + } + + function handleKeydown(event) { + if (event.code === 'Space' && !event.target.matches('input, textarea')) { + event.preventDefault(); + toggleMute(); + } + + if (event.ctrlKey && event.key === 'v') { + event.preventDefault(); + toggleVideo(); + } + } + + function handleDocumentClick(event) { + const moreOptionsMenu = document.getElementById('moreOptionsMenu'); + const moreOptionsButton = document.getElementById('moreOptionsBtn'); + + if ( + moreOptionsMenu && + moreOptionsButton && + !moreOptionsMenu.contains(event.target) && + !moreOptionsButton.contains(event.target) + ) { + moreOptionsMenu.classList.add('hidden'); + } + } + + function bindButton(buttonId, handler) { + const button = document.getElementById(buttonId); + if (button && !button.dataset.bound) { + button.addEventListener('click', handler); + button.dataset.bound = 'true'; + } + } + + function exposeWindowHandlers() { + window.toggleSidebar = toggleSidebar; + window.toggleMute = toggleMute; + window.toggleVideo = toggleVideo; + window.toggleLocalVideo = toggleLocalVideo; + window.toggleRecording = toggleRecording; + window.toggleMoreOptions = toggleMoreOptions; + window.changeResolution = changeResolution; + window.endCall = endCall; + window.cancelEndCall = cancelEndCall; + window.confirmEndCall = confirmEndCall; + } + + function bindDomEvents() { + exposeWindowHandlers(); + + if (isBound) { + return; + } + + chatMessage.bindMessageEvents(); + document.addEventListener('keydown', handleKeydown); + document.addEventListener('click', handleDocumentClick); + bindButton('cancelEndCall', cancelEndCall); + bindButton('confirmEndCall', confirmEndCall); + bindButton('moreOptionsBtn', toggleMoreOptions); + + isBound = true; + } + + return { + bindDomEvents + }; +} diff --git a/client/public/main.js b/client/public/main.js index fe146e8..944cc63 100644 --- a/client/public/main.js +++ b/client/public/main.js @@ -1,22 +1,18 @@ -/** - * 主入口文件 - * 初始化应用,连接各个模块 - * SPA架构:connect视图和call视图在同一页面切换 - */ import store from './store.js'; import UIRenderer from './renderer.js'; import { showNotification, randomMeetingId } from './utils.js'; import chatMessage from './chatmessage.js'; +import { createCallViewController } from './call-view-controller.js'; import { bindConnectViewEvents, initWebSocket, loadUserSettings } from './connectview.js'; import { createInviteController } from './invite-controller.js'; -// 全局变量 -let connectionId = ""; -// 当前视图状态:'connect' 或 'call'(可用于未来扩展) + +let connectionId = ''; let currentView = 'connect'; + function updateConnectionId(nextConnectionId) { connectionId = nextConnectionId || ''; @@ -25,34 +21,28 @@ function updateConnectionId(nextConnectionId) { } } -/** - * 切换到call视图(创建/加入通话后) - * @param {string} connectionId - 连接ID - */ -async function switchToCallView(connectionId) { +async function switchToCallView(targetConnectionId) { const connectView = document.getElementById('connectView'); const callView = document.getElementById('callView'); - - if (connectView) connectView.classList.add('hidden'); - if (callView) callView.classList.remove('hidden'); - + + if (connectView) { + connectView.classList.add('hidden'); + } + if (callView) { + callView.classList.remove('hidden'); + } + currentView = 'call'; - + try { - // 初始化渲染器 const renderer = new UIRenderer(store); - - // 加入通话 - await store.joinCall(connectionId); - - // 设置WebRTC连接 - await store.setUp(connectionId); - + + await store.joinCall(targetConnectionId); + await store.setUp(targetConnectionId); + renderer.renderHeaderTitle(); - - // 绑定DOM事件 - bindCallViewDomEvents(); - + callViewController.bindDomEvents(); + console.log('Video call app initialized successfully'); return true; } catch (error) { @@ -71,177 +61,54 @@ const inviteController = createInviteController({ setConnectionId: updateConnectionId }); -/** - * 处理加入通话 - * @param {string} connectionId - 连接ID - */ -async function handleJoinCall(connectionId) { - showNotification(`正在加入通话 (${connectionId})`); - updateConnectionId(connectionId); - await switchToCallView(connectionId); +const callViewController = createCallViewController({ + store, + chatMessage, + notify: showNotification +}); + +async function handleJoinCall(targetConnectionId) { + showNotification(`正在加入通话 (${targetConnectionId})`); + updateConnectionId(targetConnectionId); + await switchToCallView(targetConnectionId); } -/** - * 处理创建通话 - */ async function handleCreateCall() { showNotification('正在创建通话...'); - //const connectionId = 'conn_' + Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15); - const connectionId = randomMeetingId(); - updateConnectionId(connectionId); - await switchToCallView(connectionId); + const nextConnectionId = randomMeetingId(); + updateConnectionId(nextConnectionId); + await switchToCallView(nextConnectionId); } -/** - * 绑定call视图DOM事件 - */ -function bindCallViewDomEvents() { - // 切换侧边栏 - window.toggleSidebar = function () { - chatMessage.toggleSidebar(); - }; - - // 切换麦克风 - window.toggleMute = function () { - const state = store.getState(); - const currentState = state.session.localUser.mediaState.audio; - store.updateLocalMedia('audio', !currentState); - }; - - // 切换视频 - window.toggleVideo = function () { - const state = store.getState(); - const currentState = state.session.localUser.mediaState.video; - store.updateLocalMedia('video', !currentState); - }; - - // 切换本地视频(用于悬停控制) - window.toggleLocalVideo = function () { - window.toggleVideo(); - }; - - // 切换录屏 - window.toggleRecording = function () { - const state = store.getState(); - const currentState = state.session.localUser.mediaState.recording || false; - store.updateLocalMedia('recording', !currentState); - - // 显示录制状态通知 - if (!currentState) { - showNotification('开始录制'); - } else { - showNotification('停止录制'); - } - }; - - // 更多选项菜单切换 - window.toggleMoreOptions = function () { - const menu = document.getElementById('moreOptionsMenu'); - if (menu) { - menu.classList.toggle('hidden'); - } - }; - - // 切换视频分辨率 - window.changeResolution = function (width, height) { - store.changeResolution(width, height); - // 关闭菜单 - const menu = document.getElementById('moreOptionsMenu'); - if (menu) { - menu.classList.add('hidden'); - } - }; - - // 结束通话 - window.endCall = function () { - // 显示确认对话框 - document.getElementById('endCallDialog').classList.remove('hidden'); - }; - - // 取消结束通话 - window.cancelEndCall = function () { - document.getElementById('endCallDialog').classList.add('hidden'); - }; - - // 确认结束通话 - window.confirmEndCall = function () { - document.getElementById('endCallDialog').classList.add('hidden'); - store.endCall(); - showNotification('通话已结束'); - }; - - // 绑定消息相关事件 - chatMessage.bindMessageEvents(); - - // 键盘快捷键 - document.addEventListener('keydown', (event) => { - // 空格键静音 - if (event.code === 'Space' && !event.target.matches('input, textarea')) { - event.preventDefault(); - window.toggleMute(); - } - - // Ctrl+V 切换视频 - if (event.ctrlKey && event.key === 'v') { - event.preventDefault(); - window.toggleVideo(); - } - }); - - // 绑定对话框事件 - const cancelEndCall = document.getElementById('cancelEndCall'); - const confirmEndCall = document.getElementById('confirmEndCall'); - if (cancelEndCall) cancelEndCall.addEventListener('click', window.cancelEndCall); - if (confirmEndCall) confirmEndCall.addEventListener('click', window.confirmEndCall); - - // 更多选项按钮事件 - const moreOptionsBtn = document.getElementById('moreOptionsBtn'); - if (moreOptionsBtn) { - moreOptionsBtn.addEventListener('click', window.toggleMoreOptions); - } - - // 点击外部关闭更多选项菜单 - document.addEventListener('click', function(event) { - const moreOptionsMenu = document.getElementById('moreOptionsMenu'); - const moreOptionsBtnEl = document.getElementById('moreOptionsBtn'); - if (moreOptionsMenu && moreOptionsBtnEl && - !moreOptionsMenu.contains(event.target) && - !moreOptionsBtnEl.contains(event.target)) { - moreOptionsMenu.classList.add('hidden'); - } - }); - - inviteController.bindDialogEvents(); -} - -// 页面加载完成后初始化(SPA入口) window.addEventListener('DOMContentLoaded', async () => { try { - // 显示connect视图,隐藏call视图 const connectView = document.getElementById('connectView'); const callView = document.getElementById('callView'); - if (connectView) connectView.classList.remove('hidden'); - if (callView) callView.classList.add('hidden'); + + if (connectView) { + connectView.classList.remove('hidden'); + } + if (callView) { + callView.classList.add('hidden'); + } + currentView = 'connect'; - // 加载用户设置 loadUserSettings(); - // 初始化WebSocket连接(在connect视图就建立WebSocket) await initWebSocket(); inviteController.bindSignalHandlers(); - - // 绑定connect视图事件(加入通话、创建通话等) - bindConnectViewEvents(handleJoinCall, handleCreateCall); inviteController.bindDialogEvents(); + bindConnectViewEvents(handleJoinCall, handleCreateCall); - // 检查是否有保存的连接ID,填入输入框 const savedConnectionId = localStorage.getItem('connectionId'); if (savedConnectionId) { updateConnectionId(savedConnectionId); const connectionIdInput = document.getElementById('connectionIdInput'); - if (connectionIdInput) connectionIdInput.value = savedConnectionId; + if (connectionIdInput) { + connectionIdInput.value = savedConnectionId; + } } const invitePayload = inviteController.getInvitePayloadFromUrl(); @@ -256,5 +123,4 @@ window.addEventListener('DOMContentLoaded', async () => { } }); -// 导出全局变量 export { store };