Files
video_socket-server/client/public/main.js
2026-05-18 23:03:28 +08:00

407 lines
13 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 主入口文件
* 初始化应用,连接各个模块
* 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 {
bindConnectViewEvents,
initWebSocket,
loadUserSettings
} from './connectview.js';
// 全局变量
let connectionId = "";
// 当前视图状态:'connect' 或 'call'(可用于未来扩展)
let currentView = 'connect';
let pendingIncomingInvite = null;
let inviteHandlersBound = false;
function getInvitePayloadFromUrl() {
const params = new URLSearchParams(window.location.search);
if (params.get('invite') !== '1') {
return null;
}
return {
name: params.get('callerName') || '邀请方',
avatar: params.get('callerAvatar') || '/images/p2.png',
connectionId: params.get('connectionId') || ''
};
}
function showCallRequestDialog(caller = {}) {
const dialog = document.getElementById('callRequestDialog');
if (!dialog) {
return;
}
const callerName = caller.name || '邀请方';
const callerAvatar = caller.avatar || '/images/p2.png';
const targetConnectionId = caller.connectionId || '';
pendingIncomingInvite = caller;
if (document.getElementById('callRequestName')) {
document.getElementById('callRequestName').textContent = callerName;
}
if (document.getElementById('callRequestAvatar')) {
document.getElementById('callRequestAvatar').src = callerAvatar;
}
if (document.getElementById('callRequestText')) {
document.getElementById('callRequestText').textContent = targetConnectionId
? `正在邀请您加入通话 (${targetConnectionId})`
: '正在请求与您进行视频通话';
}
if (targetConnectionId) {
connectionId = targetConnectionId;
localStorage.setItem('connectionId', targetConnectionId);
}
dialog.classList.remove('hidden');
}
function getCurrentUserProfile() {
try {
const settings = JSON.parse(localStorage.getItem('userSettings') || '{}');
return {
userId: settings.userId || settings.id || '',
name: settings.name || '我',
avatar: settings.avatar || '/images/p1.png'
};
} catch (error) {
console.error('Error parsing user settings:', error);
return {
userId: '',
name: '我',
avatar: '/images/p1.png'
};
}
}
function bindInviteSignalHandlers() {
if (inviteHandlersBound) {
return;
}
store.onSocketEvent('invite-call', (payload) => {
pendingIncomingInvite = {
connectionId: payload.connectionId,
inviterSocketId: payload.inviterSocketId,
inviterUserId: payload.inviterUserId,
name: payload.inviterName || '邀请方',
avatar: payload.inviterAvatar || '/images/p2.png'
};
showCallRequestDialog(pendingIncomingInvite);
showNotification(`${pendingIncomingInvite.name} 邀请你加入通话`);
});
inviteHandlersBound = true;
}
function bindInviteDialogEvents() {
window.showCallRequest = function (caller) {
showCallRequestDialog(caller);
};
window.rejectCall = function () {
const dialog = document.getElementById('callRequestDialog');
if (dialog) {
dialog.classList.add('hidden');
}
if (pendingIncomingInvite) {
try {
store.sendInviteRejected({
connectionId: pendingIncomingInvite.connectionId || '',
targetSocketId: pendingIncomingInvite.inviterSocketId || '',
targetUserId: pendingIncomingInvite.inviterUserId || ''
});
} catch (error) {
console.error('Error rejecting invite:', error);
}
}
pendingIncomingInvite = null;
showNotification('已拒绝通话请求');
};
window.acceptCall = async function () {
const dialog = document.getElementById('callRequestDialog');
if (dialog) {
dialog.classList.add('hidden');
}
const targetConnectionId =
(pendingIncomingInvite && pendingIncomingInvite.connectionId) ||
connectionId ||
localStorage.getItem('connectionId') ||
new URLSearchParams(window.location.search).get('connectionId');
if (!targetConnectionId) {
showNotification('缺少连接ID无法接受邀请', 'error');
return;
}
connectionId = targetConnectionId;
localStorage.setItem('connectionId', targetConnectionId);
if (pendingIncomingInvite) {
try {
store.sendInviteAccepted({
connectionId: targetConnectionId,
targetSocketId: pendingIncomingInvite.inviterSocketId || '',
targetUserId: pendingIncomingInvite.inviterUserId || ''
});
} catch (error) {
console.error('Error accepting invite:', error);
showNotification('接受邀请失败,请稍后重试', 'error');
return;
}
}
showNotification('已接受通话请求');
pendingIncomingInvite = null;
if (currentView !== 'call') {
await switchToCallView(targetConnectionId);
}
};
const rejectCall = document.getElementById('rejectCall');
const acceptCall = document.getElementById('acceptCall');
if (rejectCall && !rejectCall.dataset.bound) {
rejectCall.addEventListener('click', window.rejectCall);
rejectCall.dataset.bound = 'true';
}
if (acceptCall && !acceptCall.dataset.bound) {
acceptCall.addEventListener('click', window.acceptCall);
acceptCall.dataset.bound = 'true';
}
}
/**
* 切换到call视图创建/加入通话后)
* @param {string} connectionId - 连接ID
*/
async function switchToCallView(connectionId) {
const connectView = document.getElementById('connectView');
const callView = document.getElementById('callView');
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);
renderer.renderHeaderTitle();
// 绑定DOM事件
bindCallViewDomEvents();
console.log('Video call app initialized successfully');
return true;
} catch (error) {
console.error('Error initializing app:', error);
showNotification('初始化失败,请刷新页面重试', 'error');
return false;
}
}
/**
* 处理加入通话
* @param {string} connectionId - 连接ID
*/
async function handleJoinCall(connectionId) {
showNotification(`正在加入通话 (${connectionId})`);
localStorage.setItem('connectionId', connectionId);
await switchToCallView(connectionId);
}
/**
* 处理创建通话
*/
async function handleCreateCall() {
showNotification('正在创建通话...');
//const connectionId = 'conn_' + Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
const connectionId = randomMeetingId();
localStorage.setItem('connectionId', connectionId);
await switchToCallView(connectionId);
}
/**
* 绑定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');
}
});
bindInviteDialogEvents();
}
// 页面加载完成后初始化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');
currentView = 'connect';
// 加载用户设置
loadUserSettings();
// 初始化WebSocket连接在connect视图就建立WebSocket
await initWebSocket();
bindInviteSignalHandlers();
// 绑定connect视图事件加入通话、创建通话等
bindConnectViewEvents(handleJoinCall, handleCreateCall);
bindInviteDialogEvents();
// 检查是否有保存的连接ID填入输入框
const savedConnectionId = localStorage.getItem('connectionId');
if (savedConnectionId) {
connectionId = savedConnectionId;
const connectionIdInput = document.getElementById('connectionIdInput');
if (connectionIdInput) connectionIdInput.value = savedConnectionId;
}
const invitePayload = getInvitePayloadFromUrl();
if (invitePayload) {
window.showCallRequest(invitePayload);
}
console.log('SPA initialized, showing connect view');
} catch (error) {
console.error('Error initializing SPA:', error);
showNotification('初始化失败,请刷新页面重试', 'error');
}
});
// 导出全局变量
export { store };