initaial
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 34s

This commit is contained in:
Song367 2025-08-06 20:13:33 +08:00
parent 5cd134aa29
commit e281545435

View File

@ -27,6 +27,9 @@ class WebRTCChat {
this.currentVideo = null;
this.videoStreams = new Map(); // 存储不同视频的MediaStream
this.currentVideoStream = null;
this.precreatedStreams = new Map(); // 预创建的视频流
this.importantVideos = ['d-3s.mp4', 's-1.mp4']; // 重要视频列表
this.isInitialized = false;
// 添加视频相关属性
this.videoSender = null; // WebRTC视频发送器
@ -75,6 +78,13 @@ class WebRTCChat {
});
}, 500); // 延迟2秒开始预加载避免影响
// 预创建重要视频流
setTimeout(() => {
this.precreateImportantVideos().catch(error => {
this.logMessage(`预创建重要视频流失败: ${error.message}`, 'error');
});
}, 1000);
window.webrtcApp = this;
}
@ -331,6 +341,116 @@ class WebRTCChat {
});
}
// 预创建重要视频流
async precreateImportantVideos() {
if (this.isInitialized) return;
this.logMessage('开始预创建重要视频流...', 'info');
for (const videoFile of [this.interactionVideo, this.defaultVideo]) {
try {
const stream = await this.createVideoStreamOptimized(videoFile);
this.precreatedStreams.set(videoFile, stream);
this.logMessage(`预创建视频流成功: ${videoFile}`, 'success');
} catch (error) {
this.logMessage(`预创建视频流失败: ${videoFile} - ${error.message}`, 'error');
}
}
this.isInitialized = true;
this.logMessage('重要视频流预创建完成', 'success');
}
// 优化的视频流创建方法
async createVideoStreamOptimized(videoFile) {
try {
// 先测试视频文件是否存在
await this.testVideoFile(videoFile);
// 创建video元素
const video = document.createElement('video');
video.src = `/videos/${videoFile}`;
video.muted = true;
video.loop = true;
video.autoplay = false;
video.crossOrigin = 'anonymous';
video.playsInline = true;
video.preload = 'auto';
// 等待视频加载
await new Promise((resolve, reject) => {
video.onloadeddata = () => {
video.currentTime = 0;
resolve();
};
video.onerror = reject;
});
// 创建优化的Canvas
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d', {
alpha: false, // 禁用透明度以提高性能
willReadFrequently: false // 优化Canvas性能
});
canvas.width = video.videoWidth || 640;
canvas.height = video.videoHeight || 480;
// 绘制第一帧
await new Promise((resolve) => {
const drawFirstFrame = () => {
if (video.readyState >= video.HAVE_CURRENT_DATA) {
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
resolve();
} else {
setTimeout(drawFirstFrame, 10);
}
};
drawFirstFrame();
});
// 启动视频播放
await video.play();
// 优化的帧绘制 - 使用更高效的节流
let animationId;
let lastFrameTime = 0;
const targetFPS = 30;
const frameInterval = 1000 / targetFPS;
const drawFrame = (currentTime) => {
if (currentTime - lastFrameTime >= frameInterval) {
if (video.readyState >= video.HAVE_CURRENT_DATA) {
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
}
lastFrameTime = currentTime;
}
animationId = requestAnimationFrame(drawFrame);
};
animationId = requestAnimationFrame(drawFrame);
// 创建MediaStream
const stream = canvas.captureStream(30);
// 存储清理函数
stream._cleanup = () => {
if (animationId) {
cancelAnimationFrame(animationId);
}
video.pause();
video.src = '';
video.load();
};
return stream;
} catch (error) {
this.logMessage(`优化视频流创建失败 ${videoFile}: ${error.message}`, 'error');
throw error;
}
}
async createVideoStream(videoFile) {
// 检查缓存,但为每个视频创建独立的播放实例
const cacheKey = `${videoFile}_${Date.now()}`; // 添加时间戳确保唯一性
@ -589,61 +709,64 @@ class WebRTCChat {
// 使用replaceTrack方式切换视频
async switchVideoWithReplaceTrack(videoFile, type = '', text = '') {
try {
this.logMessage(`开始使用replaceTrack切换视频: ${videoFile}`, 'info');
this.logMessage(`开始优化切换视频: ${videoFile}`, 'info');
let newVideoStream;
let isUsingPrecreated = false;
// 首先检查预创建的视频流
if (this.precreatedStreams.has(videoFile)) {
newVideoStream = this.precreatedStreams.get(videoFile);
isUsingPrecreated = true;
this.logMessage(`使用预创建视频流: ${videoFile}`, 'success');
} else {
// 检查缓存
if (this.videoStreams.has(videoFile)) {
newVideoStream = this.videoStreams.get(videoFile);
this.logMessage(`使用缓存视频流: ${videoFile}`, 'success');
} else {
// 创建新的视频流
newVideoStream = await this.createVideoStream(videoFile);
}
}
// 创建新的视频流
const newVideoStream = await this.createVideoStream(videoFile);
const newVideoTrack = newVideoStream.getVideoTracks()[0];
if (!newVideoTrack) {
throw new Error('新视频流中没有视频轨道');
}
// 如果有WebRTC连接且有视频发送器使用replaceTrack
// WebRTC轨道替换
if (this.peerConnection && this.videoSender) {
await this.videoSender.replaceTrack(newVideoTrack);
this.logMessage('WebRTC视频轨道替换成功', 'success');
}
// 同时更新本地视频显示
// 更新本地视频显示
if (this.recordedVideo) {
// 停止当前视频流
if (this.currentVideoStream) {
// 停止当前视频流(但不停止预创建的流)
if (this.currentVideoStream && !this.precreatedStreams.has(this.currentVideo)) {
this.currentVideoStream.getTracks().forEach(track => track.stop());
}
// 设置新的视频流
this.recordedVideo.srcObject = newVideoStream;
this.currentVideoStream = newVideoStream;
this.currentVideo = videoFile;
// 使用预创建流时减少等待时间
const waitTime = isUsingPrecreated ? 50 : 200;
await new Promise(resolve => setTimeout(resolve, waitTime));
// 确保视频播放
try {
await this.recordedVideo.play();
this.logMessage(`本地视频切换成功: ${videoFile}`, 'success');
} catch (playError) {
this.logMessage(`本地视频播放失败: ${playError.message}`, 'error');
this.logMessage(`视频播放失败: ${playError.message}`, 'error');
}
}
// 记录切换信息
if (type && text) {
this.logMessage(`视频切换完成 - 类型: ${type}, 文本: ${text}`, 'info');
}
this.logMessage(`视频切换完成: ${videoFile} (${type})`, 'success');
return true;
} catch (error) {
this.logMessage(`replaceTrack视频切换失败: ${error.message}`, 'error');
console.error('switchVideoWithReplaceTrack error:', error);
// 回退到原有的切换方式
try {
await this.switchVideoStream(videoFile, type, text);
this.logMessage('已回退到原有视频切换方式', 'info');
} catch (fallbackError) {
this.logMessage(`回退切换也失败: ${fallbackError.message}`, 'error');
}
this.logMessage(`视频切换失败: ${error.message}`, 'error');
return false;
}
}
@ -917,8 +1040,8 @@ class WebRTCChat {
// 开始通话按钮
this.startButton.onclick = () => this.startCall();
// 停止通话按钮
this.stopButton.onclick = () => this.stopCall();
// 停止通话按钮 - 改为调用 userDisconnect
this.stopButton.onclick = () => this.userDisconnect();
// 静音按钮
// this.muteButton.onclick = () => this.toggleMute();
@ -1011,7 +1134,7 @@ class WebRTCChat {
this.socket.emit('call-started');
// 开始播放当前场景的默认视频
await this.startDefaultVideoStream();
await this.precreateImportantVideos();
// 隐藏等待连接提示
this.hideConnectionWaiting();
@ -1026,16 +1149,16 @@ class WebRTCChat {
}
stopCall() {
// 隐藏等待连接提示
this.hideConnectionWaiting();
// 恢复到默认图标
this.switchToDefaultIcon();
// 发送用户关闭连接事件到后端
if (this.socket && this.socket.connected) {
this.socket.emit('user-disconnect');
}
// 只有用户主动点击关闭通话时才发送断开事件
// 移除自动发送 user-disconnect 事件
// if (this.socket && this.socket.connected) {
// this.socket.emit('user-disconnect');
// }
// 停止音频处理器
if (this.audioProcessor) {
@ -1059,23 +1182,38 @@ class WebRTCChat {
this.stopButton.style.display = 'none';
this.stopButton.disabled = true;
// 显示开始通话按钮
this.startButton.style.display = 'block';
this.startButton.disabled = false;
// 移除页面刷新保持websocket连接
// setTimeout(() => {
// window.location.reload();
// }, 300);
// 延迟刷新,确保服务器处理完断开逻辑
console.log('通话已结束5秒后刷新页面清除缓存...');
setTimeout(() => {
window.location.reload();
this.startButton.style.display = 'block';
this.startButton.disabled = false;
}, 300);
setTimeout(() => {
// 显示头像,隐藏视频
// 显示头像,隐藏视频
if (this.videoContainer) {
this.videoContainer.classList.remove('calling');
}
}, 300);
}
// 新增:用户主动断开连接的方法
userDisconnect() {
// 发送用户关闭连接事件到后端
if (this.socket && this.socket.connected) {
this.socket.emit('user-disconnect');
}
// 调用停止通话
this.stopCall();
// 延迟刷新,确保服务器处理完断开逻辑
console.log('用户主动断开300ms后刷新页面清除缓存...');
setTimeout(() => {
window.location.reload();
}, 300);
}
// 清除视频缓存的方法