++
This commit is contained in:
138
client/public/call-view-controller.js
Normal file
138
client/public/call-view-controller.js
Normal file
@@ -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
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,22 +1,18 @@
|
|||||||
/**
|
|
||||||
* 主入口文件
|
|
||||||
* 初始化应用,连接各个模块
|
|
||||||
* SPA架构:connect视图和call视图在同一页面切换
|
|
||||||
*/
|
|
||||||
import store from './store.js';
|
import store from './store.js';
|
||||||
import UIRenderer from './renderer.js';
|
import UIRenderer from './renderer.js';
|
||||||
import { showNotification, randomMeetingId } from './utils.js';
|
import { showNotification, randomMeetingId } from './utils.js';
|
||||||
import chatMessage from './chatmessage.js';
|
import chatMessage from './chatmessage.js';
|
||||||
|
import { createCallViewController } from './call-view-controller.js';
|
||||||
import {
|
import {
|
||||||
bindConnectViewEvents,
|
bindConnectViewEvents,
|
||||||
initWebSocket,
|
initWebSocket,
|
||||||
loadUserSettings
|
loadUserSettings
|
||||||
} from './connectview.js';
|
} from './connectview.js';
|
||||||
import { createInviteController } from './invite-controller.js';
|
import { createInviteController } from './invite-controller.js';
|
||||||
// 全局变量
|
|
||||||
let connectionId = "";
|
let connectionId = '';
|
||||||
// 当前视图状态:'connect' 或 'call'(可用于未来扩展)
|
|
||||||
let currentView = 'connect';
|
let currentView = 'connect';
|
||||||
|
|
||||||
function updateConnectionId(nextConnectionId) {
|
function updateConnectionId(nextConnectionId) {
|
||||||
connectionId = nextConnectionId || '';
|
connectionId = nextConnectionId || '';
|
||||||
|
|
||||||
@@ -25,33 +21,27 @@ function updateConnectionId(nextConnectionId) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
async function switchToCallView(targetConnectionId) {
|
||||||
* 切换到call视图(创建/加入通话后)
|
|
||||||
* @param {string} connectionId - 连接ID
|
|
||||||
*/
|
|
||||||
async function switchToCallView(connectionId) {
|
|
||||||
const connectView = document.getElementById('connectView');
|
const connectView = document.getElementById('connectView');
|
||||||
const callView = document.getElementById('callView');
|
const callView = document.getElementById('callView');
|
||||||
|
|
||||||
if (connectView) connectView.classList.add('hidden');
|
if (connectView) {
|
||||||
if (callView) callView.classList.remove('hidden');
|
connectView.classList.add('hidden');
|
||||||
|
}
|
||||||
|
if (callView) {
|
||||||
|
callView.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
|
||||||
currentView = 'call';
|
currentView = 'call';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 初始化渲染器
|
|
||||||
const renderer = new UIRenderer(store);
|
const renderer = new UIRenderer(store);
|
||||||
|
|
||||||
// 加入通话
|
await store.joinCall(targetConnectionId);
|
||||||
await store.joinCall(connectionId);
|
await store.setUp(targetConnectionId);
|
||||||
|
|
||||||
// 设置WebRTC连接
|
|
||||||
await store.setUp(connectionId);
|
|
||||||
|
|
||||||
renderer.renderHeaderTitle();
|
renderer.renderHeaderTitle();
|
||||||
|
callViewController.bindDomEvents();
|
||||||
// 绑定DOM事件
|
|
||||||
bindCallViewDomEvents();
|
|
||||||
|
|
||||||
console.log('Video call app initialized successfully');
|
console.log('Video call app initialized successfully');
|
||||||
return true;
|
return true;
|
||||||
@@ -71,177 +61,54 @@ const inviteController = createInviteController({
|
|||||||
setConnectionId: updateConnectionId
|
setConnectionId: updateConnectionId
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
const callViewController = createCallViewController({
|
||||||
* 处理加入通话
|
store,
|
||||||
* @param {string} connectionId - 连接ID
|
chatMessage,
|
||||||
*/
|
notify: showNotification
|
||||||
async function handleJoinCall(connectionId) {
|
});
|
||||||
showNotification(`正在加入通话 (${connectionId})`);
|
|
||||||
updateConnectionId(connectionId);
|
async function handleJoinCall(targetConnectionId) {
|
||||||
await switchToCallView(connectionId);
|
showNotification(`正在加入通话 (${targetConnectionId})`);
|
||||||
|
updateConnectionId(targetConnectionId);
|
||||||
|
await switchToCallView(targetConnectionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理创建通话
|
|
||||||
*/
|
|
||||||
async function handleCreateCall() {
|
async function handleCreateCall() {
|
||||||
showNotification('正在创建通话...');
|
showNotification('正在创建通话...');
|
||||||
//const connectionId = 'conn_' + Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
|
|
||||||
|
|
||||||
const connectionId = randomMeetingId();
|
const nextConnectionId = randomMeetingId();
|
||||||
updateConnectionId(connectionId);
|
updateConnectionId(nextConnectionId);
|
||||||
await switchToCallView(connectionId);
|
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 () => {
|
window.addEventListener('DOMContentLoaded', async () => {
|
||||||
try {
|
try {
|
||||||
// 显示connect视图,隐藏call视图
|
|
||||||
const connectView = document.getElementById('connectView');
|
const connectView = document.getElementById('connectView');
|
||||||
const callView = document.getElementById('callView');
|
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';
|
currentView = 'connect';
|
||||||
|
|
||||||
// 加载用户设置
|
|
||||||
loadUserSettings();
|
loadUserSettings();
|
||||||
|
|
||||||
// 初始化WebSocket连接(在connect视图就建立WebSocket)
|
|
||||||
await initWebSocket();
|
await initWebSocket();
|
||||||
inviteController.bindSignalHandlers();
|
inviteController.bindSignalHandlers();
|
||||||
|
|
||||||
// 绑定connect视图事件(加入通话、创建通话等)
|
|
||||||
bindConnectViewEvents(handleJoinCall, handleCreateCall);
|
|
||||||
inviteController.bindDialogEvents();
|
inviteController.bindDialogEvents();
|
||||||
|
bindConnectViewEvents(handleJoinCall, handleCreateCall);
|
||||||
|
|
||||||
// 检查是否有保存的连接ID,填入输入框
|
|
||||||
const savedConnectionId = localStorage.getItem('connectionId');
|
const savedConnectionId = localStorage.getItem('connectionId');
|
||||||
if (savedConnectionId) {
|
if (savedConnectionId) {
|
||||||
updateConnectionId(savedConnectionId);
|
updateConnectionId(savedConnectionId);
|
||||||
const connectionIdInput = document.getElementById('connectionIdInput');
|
const connectionIdInput = document.getElementById('connectionIdInput');
|
||||||
if (connectionIdInput) connectionIdInput.value = savedConnectionId;
|
if (connectionIdInput) {
|
||||||
|
connectionIdInput.value = savedConnectionId;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const invitePayload = inviteController.getInvitePayloadFromUrl();
|
const invitePayload = inviteController.getInvitePayloadFromUrl();
|
||||||
@@ -256,5 +123,4 @@ window.addEventListener('DOMContentLoaded', async () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 导出全局变量
|
|
||||||
export { store };
|
export { store };
|
||||||
|
|||||||
Reference in New Issue
Block a user