initaial
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 34s
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 34s
This commit is contained in:
parent
5cd134aa29
commit
e281545435
230
src/index.js
230
src/index.js
@ -27,6 +27,9 @@ class WebRTCChat {
|
|||||||
this.currentVideo = null;
|
this.currentVideo = null;
|
||||||
this.videoStreams = new Map(); // 存储不同视频的MediaStream
|
this.videoStreams = new Map(); // 存储不同视频的MediaStream
|
||||||
this.currentVideoStream = null;
|
this.currentVideoStream = null;
|
||||||
|
this.precreatedStreams = new Map(); // 预创建的视频流
|
||||||
|
this.importantVideos = ['d-3s.mp4', 's-1.mp4']; // 重要视频列表
|
||||||
|
this.isInitialized = false;
|
||||||
|
|
||||||
// 添加视频相关属性
|
// 添加视频相关属性
|
||||||
this.videoSender = null; // WebRTC视频发送器
|
this.videoSender = null; // WebRTC视频发送器
|
||||||
@ -75,6 +78,13 @@ class WebRTCChat {
|
|||||||
});
|
});
|
||||||
}, 500); // 延迟2秒开始预加载,避免影响
|
}, 500); // 延迟2秒开始预加载,避免影响
|
||||||
|
|
||||||
|
// 预创建重要视频流
|
||||||
|
setTimeout(() => {
|
||||||
|
this.precreateImportantVideos().catch(error => {
|
||||||
|
this.logMessage(`预创建重要视频流失败: ${error.message}`, 'error');
|
||||||
|
});
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
window.webrtcApp = this;
|
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) {
|
async createVideoStream(videoFile) {
|
||||||
// 检查缓存,但为每个视频创建独立的播放实例
|
// 检查缓存,但为每个视频创建独立的播放实例
|
||||||
const cacheKey = `${videoFile}_${Date.now()}`; // 添加时间戳确保唯一性
|
const cacheKey = `${videoFile}_${Date.now()}`; // 添加时间戳确保唯一性
|
||||||
@ -589,61 +709,64 @@ class WebRTCChat {
|
|||||||
// 使用replaceTrack方式切换视频
|
// 使用replaceTrack方式切换视频
|
||||||
async switchVideoWithReplaceTrack(videoFile, type = '', text = '') {
|
async switchVideoWithReplaceTrack(videoFile, type = '', text = '') {
|
||||||
try {
|
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];
|
const newVideoTrack = newVideoStream.getVideoTracks()[0];
|
||||||
|
|
||||||
if (!newVideoTrack) {
|
if (!newVideoTrack) {
|
||||||
throw new Error('新视频流中没有视频轨道');
|
throw new Error('新视频流中没有视频轨道');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果有WebRTC连接且有视频发送器,使用replaceTrack
|
// WebRTC轨道替换
|
||||||
if (this.peerConnection && this.videoSender) {
|
if (this.peerConnection && this.videoSender) {
|
||||||
await this.videoSender.replaceTrack(newVideoTrack);
|
await this.videoSender.replaceTrack(newVideoTrack);
|
||||||
this.logMessage('WebRTC视频轨道替换成功', 'success');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 同时更新本地视频显示
|
// 更新本地视频显示
|
||||||
if (this.recordedVideo) {
|
if (this.recordedVideo) {
|
||||||
// 停止当前视频流
|
// 停止当前视频流(但不停止预创建的流)
|
||||||
if (this.currentVideoStream) {
|
if (this.currentVideoStream && !this.precreatedStreams.has(this.currentVideo)) {
|
||||||
this.currentVideoStream.getTracks().forEach(track => track.stop());
|
this.currentVideoStream.getTracks().forEach(track => track.stop());
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置新的视频流
|
|
||||||
this.recordedVideo.srcObject = newVideoStream;
|
this.recordedVideo.srcObject = newVideoStream;
|
||||||
this.currentVideoStream = newVideoStream;
|
this.currentVideoStream = newVideoStream;
|
||||||
|
this.currentVideo = videoFile;
|
||||||
|
|
||||||
|
// 使用预创建流时减少等待时间
|
||||||
|
const waitTime = isUsingPrecreated ? 50 : 200;
|
||||||
|
await new Promise(resolve => setTimeout(resolve, waitTime));
|
||||||
|
|
||||||
// 确保视频播放
|
|
||||||
try {
|
try {
|
||||||
await this.recordedVideo.play();
|
await this.recordedVideo.play();
|
||||||
this.logMessage(`本地视频切换成功: ${videoFile}`, 'success');
|
|
||||||
} catch (playError) {
|
} catch (playError) {
|
||||||
this.logMessage(`本地视频播放失败: ${playError.message}`, 'error');
|
this.logMessage(`视频播放失败: ${playError.message}`, 'error');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 记录切换信息
|
this.logMessage(`视频切换完成: ${videoFile} (${type})`, 'success');
|
||||||
if (type && text) {
|
|
||||||
this.logMessage(`视频切换完成 - 类型: ${type}, 文本: ${text}`, 'info');
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logMessage(`replaceTrack视频切换失败: ${error.message}`, 'error');
|
this.logMessage(`视频切换失败: ${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');
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -917,8 +1040,8 @@ class WebRTCChat {
|
|||||||
// 开始通话按钮
|
// 开始通话按钮
|
||||||
this.startButton.onclick = () => this.startCall();
|
this.startButton.onclick = () => this.startCall();
|
||||||
|
|
||||||
// 停止通话按钮
|
// 停止通话按钮 - 改为调用 userDisconnect
|
||||||
this.stopButton.onclick = () => this.stopCall();
|
this.stopButton.onclick = () => this.userDisconnect();
|
||||||
|
|
||||||
// 静音按钮
|
// 静音按钮
|
||||||
// this.muteButton.onclick = () => this.toggleMute();
|
// this.muteButton.onclick = () => this.toggleMute();
|
||||||
@ -1011,7 +1134,7 @@ class WebRTCChat {
|
|||||||
this.socket.emit('call-started');
|
this.socket.emit('call-started');
|
||||||
|
|
||||||
// 开始播放当前场景的默认视频
|
// 开始播放当前场景的默认视频
|
||||||
await this.startDefaultVideoStream();
|
await this.precreateImportantVideos();
|
||||||
|
|
||||||
// 隐藏等待连接提示
|
// 隐藏等待连接提示
|
||||||
this.hideConnectionWaiting();
|
this.hideConnectionWaiting();
|
||||||
@ -1026,16 +1149,16 @@ class WebRTCChat {
|
|||||||
}
|
}
|
||||||
|
|
||||||
stopCall() {
|
stopCall() {
|
||||||
|
|
||||||
// 隐藏等待连接提示
|
// 隐藏等待连接提示
|
||||||
this.hideConnectionWaiting();
|
this.hideConnectionWaiting();
|
||||||
// 恢复到默认图标
|
// 恢复到默认图标
|
||||||
this.switchToDefaultIcon();
|
this.switchToDefaultIcon();
|
||||||
|
|
||||||
// 发送用户关闭连接事件到后端
|
// 只有用户主动点击关闭通话时才发送断开事件
|
||||||
if (this.socket && this.socket.connected) {
|
// 移除自动发送 user-disconnect 事件
|
||||||
this.socket.emit('user-disconnect');
|
// if (this.socket && this.socket.connected) {
|
||||||
}
|
// this.socket.emit('user-disconnect');
|
||||||
|
// }
|
||||||
|
|
||||||
// 停止音频处理器
|
// 停止音频处理器
|
||||||
if (this.audioProcessor) {
|
if (this.audioProcessor) {
|
||||||
@ -1059,23 +1182,38 @@ class WebRTCChat {
|
|||||||
this.stopButton.style.display = 'none';
|
this.stopButton.style.display = 'none';
|
||||||
this.stopButton.disabled = true;
|
this.stopButton.disabled = true;
|
||||||
|
|
||||||
|
// 显示开始通话按钮
|
||||||
|
this.startButton.style.display = 'block';
|
||||||
|
this.startButton.disabled = false;
|
||||||
|
|
||||||
|
// 移除页面刷新,保持websocket连接
|
||||||
|
// setTimeout(() => {
|
||||||
|
// window.location.reload();
|
||||||
|
// }, 300);
|
||||||
|
|
||||||
// 延迟刷新,确保服务器处理完断开逻辑
|
|
||||||
console.log('通话已结束,5秒后刷新页面清除缓存...');
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
window.location.reload();
|
// 显示头像,隐藏视频
|
||||||
|
|
||||||
this.startButton.style.display = 'block';
|
|
||||||
this.startButton.disabled = false;
|
|
||||||
}, 300);
|
|
||||||
setTimeout(() => {
|
|
||||||
// 显示头像,隐藏视频
|
|
||||||
if (this.videoContainer) {
|
if (this.videoContainer) {
|
||||||
this.videoContainer.classList.remove('calling');
|
this.videoContainer.classList.remove('calling');
|
||||||
}
|
}
|
||||||
}, 300);
|
}, 300);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增:用户主动断开连接的方法
|
||||||
|
userDisconnect() {
|
||||||
|
// 发送用户关闭连接事件到后端
|
||||||
|
if (this.socket && this.socket.connected) {
|
||||||
|
this.socket.emit('user-disconnect');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用停止通话
|
||||||
|
this.stopCall();
|
||||||
|
|
||||||
|
// 延迟刷新,确保服务器处理完断开逻辑
|
||||||
|
console.log('用户主动断开,300ms后刷新页面清除缓存...');
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.reload();
|
||||||
|
}, 300);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 清除视频缓存的方法
|
// 清除视频缓存的方法
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user