【m】界面分为3个页面

This commit is contained in:
zhangzheng
2026-03-04 18:40:19 +08:00
parent 93b56da25e
commit 6d0dc478e4
9 changed files with 430 additions and 6 deletions

View File

@@ -0,0 +1,61 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>VideoCall - 连接界面</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
<link rel="stylesheet" href="../css/style.css">
</head>
<body class="h-screen w-screen flex flex-col text-white bg-grid relative">
<!--
============================================================
初始连接界面
============================================================
-->
<div class="h-full w-full flex items-center justify-center bg-black/90">
<div class="text-center max-w-md px-8">
<div class="w-24 h-24 bg-gradient-to-br from-indigo-500 to-purple-600 rounded-full flex items-center justify-center mx-auto mb-8 shadow-lg">
<i class="fas fa-video text-white text-4xl"></i>
</div>
<h1 class="text-3xl font-bold text-white mb-2">VideoCall</h1>
<p class="text-gray-400 mb-8">一对一视频通话</p>
<div class="space-y-4 mb-8">
<div class="glass rounded-xl p-4">
<label class="block text-sm font-medium text-gray-300 mb-2">连接ID</label>
<input type="text"
id="connectionIdInput"
placeholder="输入连接ID"
class="w-full bg-transparent border border-white/20 rounded-lg px-4 py-3 text-white placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-indigo-500"
autocomplete="off">
</div>
<p class="text-xs text-gray-500">
连接ID是用于建立点对点通话的唯一标识由发起方生成并分享给接收方。
</p>
</div>
<div class="flex flex-col sm:flex-row gap-4 justify-center">
<button id="connectBtn" class="flex-1 px-6 py-3 bg-indigo-600 hover:bg-indigo-700 rounded-xl transition-colors flex items-center justify-center gap-2">
<i class="fas fa-phone"></i>
<span>加入通话</span>
</button>
<button id="createCallBtn" class="flex-1 px-6 py-3 glass hover:bg-white/10 rounded-xl transition-colors flex items-center justify-center gap-2">
<i class="fas fa-plus"></i>
<span>创建通话</span>
</button>
</div>
</div>
</div>
<!-- 通知组件 -->
<div id="notification" class="fixed top-20 left-1/2 transform -translate-x-1/2 glass px-6 py-3 rounded-full flex items-center gap-3 opacity-0 pointer-events-none transition-all duration-300 z-50 translate-y-[-20px]">
<i class="fas fa-info-circle text-indigo-400"></i>
<span class="text-sm" id="notificationText">通知内容</span>
</div>
<!-- 引入模块化JavaScript文件 -->
<script type="module" src="connect.js"></script>
</body>
</html>

View File

@@ -0,0 +1,120 @@
/**
* 连接界面逻辑
* 处理初始连接、创建通话和加入通话的功能
*/
import store from '../store.js';
// 通知函数
function showNotification(message, type = 'info') {
const notification = document.getElementById('notification');
const notificationText = document.getElementById('notificationText');
if (notification && notificationText) {
notificationText.textContent = message;
// 清除之前的类
notification.className = 'fixed top-20 left-1/2 transform -translate-x-1/2 glass px-6 py-3 rounded-full flex items-center gap-3 opacity-0 pointer-events-none transition-all duration-300 z-50 translate-y-[-20px]';
// 根据类型添加不同的图标
const iconElement = notification.querySelector('i');
if (iconElement) {
iconElement.className = 'fas fa-info-circle text-indigo-400';
switch (type) {
case 'success':
iconElement.className = 'fas fa-check-circle text-green-400';
break;
case 'error':
iconElement.className = 'fas fa-exclamation-circle text-red-400';
break;
case 'warning':
iconElement.className = 'fas fa-exclamation-triangle text-yellow-400';
break;
}
}
// 显示通知
notification.classList.remove('opacity-0', 'translate-y-[-20px]');
notification.classList.add('opacity-100', 'translate-y-0');
// 3秒后隐藏
setTimeout(() => {
notification.classList.remove('opacity-100', 'translate-y-0');
notification.classList.add('opacity-0', 'translate-y-[-20px]');
}, 3000);
}
}
// 加入通话
function joinCall() {
const connectionId = document.getElementById('connectionIdInput').value.trim();
if (connectionId) {
showNotification(`正在加入通话 (${connectionId})`);
// 保存连接ID到本地存储
localStorage.setItem('connectionId', connectionId);
// 跳转到通话界面
window.location.href = '../index.html';
} else {
showNotification('请输入连接ID', 'error');
}
}
// 创建通话
function createCall() {
showNotification('正在创建通话...');
// 生成随机连接ID
const connectionId = 'conn_' + Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
// 保存连接ID到本地存储
localStorage.setItem('connectionId', connectionId);
// 跳转到通话界面
window.location.href = '../index.html';
}
// 绑定事件监听器
function bindEvents() {
// 连接按钮
const connectBtn = document.getElementById('connectBtn');
if (connectBtn) {
connectBtn.addEventListener('click', joinCall);
}
// 创建通话按钮
const createCallBtn = document.getElementById('createCallBtn');
if (createCallBtn) {
createCallBtn.addEventListener('click', createCall);
}
// 输入框回车事件
const connectionIdInput = document.getElementById('connectionIdInput');
if (connectionIdInput) {
connectionIdInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
joinCall();
}
});
}
}
// 页面加载完成后初始化
window.addEventListener('DOMContentLoaded', () => {
bindEvents();
// 检查本地存储中是否有连接ID
const savedConnectionId = localStorage.getItem('connectionId');
if (savedConnectionId) {
const connectionIdInput = document.getElementById('connectionIdInput');
if (connectionIdInput) {
connectionIdInput.value = savedConnectionId;
}
}
});
// 导出全局函数
window.showNotification = showNotification;
window.joinCall = joinCall;
window.createCall = createCall;

View File

@@ -0,0 +1,50 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>VideoCall - 通话结束</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
<link rel="stylesheet" href="../css/style.css">
</head>
<body class="h-screen w-screen flex flex-col text-white bg-grid relative">
<!--
============================================================
结束通话界面
============================================================
-->
<div class="h-full w-full flex items-center justify-center bg-black/80">
<div class="text-center max-w-md px-8">
<div class="w-20 h-20 bg-gradient-to-br from-indigo-500 to-purple-600 rounded-full flex items-center justify-center mx-auto mb-6 shadow-lg">
<i class="fas fa-video-slash text-white text-3xl"></i>
</div>
<h2 class="text-2xl font-bold text-white mb-4">通话已结束</h2>
<p class="text-gray-400 mb-8" id="disconnectReason">连接已断开,请检查网络连接后重试</p>
<div class="flex flex-col sm:flex-row gap-3 justify-center">
<button id="reconnectBtn" class="px-6 py-3 bg-indigo-600 hover:bg-indigo-700 rounded-xl transition-colors flex items-center justify-center gap-2">
<i class="fas fa-redo"></i>
<span>重新连接</span>
</button>
<button id="leaveBtn" class="px-6 py-3 glass hover:bg-white/10 rounded-xl transition-colors flex items-center justify-center gap-2">
<i class="fas fa-sign-out-alt"></i>
<span>返回</span>
</button>
</div>
<div class="mt-8 text-xs text-gray-500">
<p>连接ID: <span id="disconnectConnectionId">--</span></p>
<p>断开时间: <span id="disconnectTime">--</span></p>
</div>
</div>
</div>
<!-- 通知组件 -->
<div id="notification" class="fixed top-20 left-1/2 transform -translate-x-1/2 glass px-6 py-3 rounded-full flex items-center gap-3 opacity-0 pointer-events-none transition-all duration-300 z-50 translate-y-[-20px]">
<i class="fas fa-info-circle text-indigo-400"></i>
<span class="text-sm" id="notificationText">通知内容</span>
</div>
<!-- 引入模块化JavaScript文件 -->
<script type="module" src="endcall.js"></script>
</body>
</html>

View File

@@ -0,0 +1,98 @@
/**
* 结束通话界面逻辑
* 处理通话结束后的操作,如重新连接或返回连接界面
*/
// 通知函数
function showNotification(message, type = 'info') {
const notification = document.getElementById('notification');
const notificationText = document.getElementById('notificationText');
if (notification && notificationText) {
notificationText.textContent = message;
// 清除之前的类
notification.className = 'fixed top-20 left-1/2 transform -translate-x-1/2 glass px-6 py-3 rounded-full flex items-center gap-3 opacity-0 pointer-events-none transition-all duration-300 z-50 translate-y-[-20px]';
// 根据类型添加不同的图标
const iconElement = notification.querySelector('i');
if (iconElement) {
iconElement.className = 'fas fa-info-circle text-indigo-400';
switch (type) {
case 'success':
iconElement.className = 'fas fa-check-circle text-green-400';
break;
case 'error':
iconElement.className = 'fas fa-exclamation-circle text-red-400';
break;
case 'warning':
iconElement.className = 'fas fa-exclamation-triangle text-yellow-400';
break;
}
}
// 显示通知
notification.classList.remove('opacity-0', 'translate-y-[-20px]');
notification.classList.add('opacity-100', 'translate-y-0');
// 3秒后隐藏
setTimeout(() => {
notification.classList.remove('opacity-100', 'translate-y-0');
notification.classList.add('opacity-0', 'translate-y-[-20px]');
}, 3000);
}
}
// 重新连接
function reconnectCall() {
showNotification('正在重新连接...');
// 跳转到通话界面
window.location.href = '../index.html';
}
// 离开
function leaveCall() {
// 清除本地存储中的连接ID
localStorage.removeItem('connectionId');
// 跳转到连接界面
window.location.href = '../connect/connect.html';
}
// 绑定事件监听器
function bindEvents() {
// 重新连接按钮
const reconnectBtn = document.getElementById('reconnectBtn');
if (reconnectBtn) {
reconnectBtn.addEventListener('click', reconnectCall);
}
// 离开按钮
const leaveBtn = document.getElementById('leaveBtn');
if (leaveBtn) {
leaveBtn.addEventListener('click', leaveCall);
}
}
// 页面加载完成后初始化
window.addEventListener('DOMContentLoaded', () => {
bindEvents();
// 更新断开连接信息
const disconnectConnectionId = document.getElementById('disconnectConnectionId');
const disconnectTime = document.getElementById('disconnectTime');
if (disconnectConnectionId) {
disconnectConnectionId.textContent = localStorage.getItem('connectionId') || '--';
}
if (disconnectTime) {
disconnectTime.textContent = new Date().toLocaleString();
}
});
// 导出全局函数
window.showNotification = showNotification;
window.reconnectCall = reconnectCall;
window.leaveCall = leaveCall;

View File

@@ -9,6 +9,12 @@
<link rel="stylesheet" href="css/style.css"> <link rel="stylesheet" href="css/style.css">
</head> </head>
<body class="h-screen w-screen flex flex-col text-white bg-grid relative"> <body class="h-screen w-screen flex flex-col text-white bg-grid relative">
<!--
============================================================
注意:此文件为视频通话界面
初始连接界面请访问 connect.html
============================================================
-->
<!-- <!--
============================================================ ============================================================
@@ -192,6 +198,10 @@
<p class="text-sm text-gray-400 mt-1" id="connectingText">等待对方接受邀请</p> <p class="text-sm text-gray-400 mt-1" id="connectingText">等待对方接受邀请</p>
</div> </div>
</div> </div>
</div> </div>
<!-- <!--

View File

@@ -325,8 +325,39 @@ function initWebRTC() {
console.log('Initializing WebRTC...'); console.log('Initializing WebRTC...');
} }
// 页面加载完成后初始化应用 // 页面加载完成后初始化
document.addEventListener('DOMContentLoaded', initApp); window.addEventListener('DOMContentLoaded', async () => {
try {
// 检查本地存储中是否有连接ID
const connectionId = localStorage.getItem('connectionId');
if (!connectionId) {
// 如果没有连接ID跳转到连接界面
window.location.href = './connect/connect.html';
return;
}
// 初始化 store
store.init();
// 加入通话
store.joinCall(connectionId);
// 初始化渲染器
const renderer = new UIRenderer(store);
// 绑定DOM事件
bindDomEvents();
// 绑定WebSocket事件
bindWebSocketEvents();
console.log('Video call app initialized successfully');
} catch (error) {
console.error('Error initializing app:', error);
showNotification('初始化失败,请刷新页面重试', 'error');
}
});
// 导出全局变量 // 导出全局变量
export { store, renderer, apiClient, wsManager }; export { store, renderer, apiClient, wsManager };

View File

@@ -11,7 +11,10 @@ class UIRenderer {
// 缓存 DOM 元素 // 缓存 DOM 元素
this.elements = { this.elements = {
// 头部 // 头部和底部
header: document.querySelector('header'),
footer: document.querySelector('footer'),
// 头部内容
headerTitle: document.getElementById('headerTitle'), headerTitle: document.getElementById('headerTitle'),
callDuration: document.getElementById('callDuration'), callDuration: document.getElementById('callDuration'),
encryptionBadge: document.getElementById('encryptionBadge'), encryptionBadge: document.getElementById('encryptionBadge'),
@@ -46,12 +49,22 @@ class UIRenderer {
recordBtn: document.getElementById('recordBtn'), recordBtn: document.getElementById('recordBtn'),
connectionQuality: document.getElementById('connectionQuality') connectionQuality: document.getElementById('connectionQuality')
}; };
// 绑定事件监听器
this.bindEventListeners();
// 订阅状态变化 // 订阅状态变化
this.unsubscribe = stateManager.subscribe(this.render.bind(this)); this.unsubscribe = stateManager.subscribe(this.render.bind(this));
// 初始化渲染 // 初始化渲染
this.render(this.stateManager.getState(), { type: 'INIT' }); this.render(this.stateManager.getState(), { type: 'INIT' });
} }
// 绑定事件监听器
bindEventListeners() {
// 事件监听器
}
// 渲染状态变化 // 渲染状态变化
render(state, changes) { render(state, changes) {
switch (changes.type) { switch (changes.type) {
@@ -166,6 +179,11 @@ class UIRenderer {
this.elements.localVideo.autoplay = true; this.elements.localVideo.autoplay = true;
this.elements.localVideo.muted = true; // 本地视频静音,避免回声 this.elements.localVideo.muted = true; // 本地视频静音,避免回声
console.log('srcObject set successfully:', this.elements.localVideo.srcObject); console.log('srcObject set successfully:', this.elements.localVideo.srcObject);
// 隐藏断开连接覆盖层
if (this.elements.disconnectedOverlay) {
this.elements.disconnectedOverlay.classList.add('hidden');
}
} else { } else {
console.error('Either localVideo element or stream is missing'); console.error('Either localVideo element or stream is missing');
} }
@@ -364,8 +382,9 @@ class UIRenderer {
// 渲染通话结束 // 渲染通话结束
renderCallEnded() { renderCallEnded() {
// 可以在这里添加通话结束的UI处理
console.log('Call ended'); console.log('Call ended');
// 跳转到结束通话界面
window.location.href = './endcall/endcall.html';
} }
// 获取状态文本 // 获取状态文本

View File

@@ -6,6 +6,7 @@ import { mockCallSession, mockMessages } from './models.js';
import { Signaling, WebSocketSignaling } from "../../module/signaling.js";// 信令管理 import { Signaling, WebSocketSignaling } from "../../module/signaling.js";// 信令管理
import { RenderStreaming } from "../../module/renderstreaming.js"; // WebRTC连接管理 import { RenderStreaming } from "../../module/renderstreaming.js"; // WebRTC连接管理
import { getServerConfig, getRTCConfiguration } from "../js/config.js";//服务器配置和RTC配置 import { getServerConfig, getRTCConfiguration } from "../js/config.js";//服务器配置和RTC配置
import { showNotification } from './utils.js'; // 导入通知函数
// 默认视频流尺寸 // 默认视频流尺寸
const defaultStreamWidth = 1280; const defaultStreamWidth = 1280;
const defaultStreamHeight = 720; const defaultStreamHeight = 720;
@@ -19,7 +20,10 @@ class CallStateManager {
let connectionId; // 连接ID let connectionId; // 连接ID
// 核心状态 // 核心状态
this.state = { this.state = {
session: { ...mockCallSession }, session: {
...mockCallSession,
status: 'idle' // 初始状态为空闲
},
messages: [...mockMessages], messages: [...mockMessages],
isSidebarOpen: false, isSidebarOpen: false,
unreadCount: 0, unreadCount: 0,
@@ -305,6 +309,29 @@ class CallStateManager {
// [WEBSOCKET_EMIT: leave-call] // [WEBSOCKET_EMIT: leave-call]
} }
// 加入通话
joinCall(connectionId) {
this.state.session.status = 'connecting';
this.notify({ type: 'CALL_STATUS_CHANGE', status: 'connecting' });
showNotification(`正在加入通话 (${connectionId})`);
// 初始化
this.init();
// 保存连接ID
this.connectionId = connectionId;
}
// 创建通话
createCall() {
this.state.session.status = 'connecting';
this.notify({ type: 'CALL_STATUS_CHANGE', status: 'connecting' });
showNotification('正在创建通话...');
// 初始化
this.init();
}
// 模拟远端活动 (开发测试用) // 模拟远端活动 (开发测试用)
simulateRemoteActivity() { simulateRemoteActivity() {
setInterval(() => { setInterval(() => {
@@ -342,6 +369,7 @@ class CallStateManager {
getLocalUser() { return this.state.session.localUser; } getLocalUser() { return this.state.session.localUser; }
getRemoteUser() { return this.state.session.remoteUser; } getRemoteUser() { return this.state.session.remoteUser; }
getMessages() { return this.state.messages; } getMessages() { return this.state.messages; }
getConnectionId() { return this.connectionId; }
} }
// 创建单例实例 // 创建单例实例
@@ -354,4 +382,3 @@ window.addEventListener('beforeunload', async () => {
await store.renderstreaming.stop(); // 停止WebRTC连接 await store.renderstreaming.stop(); // 停止WebRTC连接
}, true); }, true);
export default store; export default store;

View File

@@ -6,3 +6,11 @@ popd
pause pause
node ./build/index.js -s -p 8080 -m private -k ./server.key -c ./server.cert -l dev node ./build/index.js -s -p 8080 -m private -k ./server.key -c ./server.cert -l dev
// 通话测试代码
const caller = {
name: "Sarah Chen",
avatar: "https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=100&h=100&fit=crop"
};
window.showCallRequest(caller);