407 lines
13 KiB
JavaScript
407 lines
13 KiB
JavaScript
/**
|
||
* 主入口文件
|
||
* 初始化应用,连接各个模块
|
||
* 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 };
|