优化衔接处bug

This commit is contained in:
宋居成 2025-07-29 02:50:08 +08:00
parent a96fc86d42
commit f0bf3b6184
3 changed files with 164 additions and 63 deletions

View File

@ -87,16 +87,16 @@ const connectedClients = new Map();
// 视频映射配置
const videoMapping = {
'say-6s-m-e': '1-m.mp4',
'default': '0.mp4',
'say-5s-amplitude': '2.mp4',
'say-5s-m-e': '4.mp4',
'say-5s-m-sw': '5.mp4',
'say-3s-m-sw': '6.mp4',
// 'say-6s-m-e': '1-m.mp4',
'default': 'd-3s.mp4',
// 'say-5s-amplitude': '2.mp4',
// 'say-5s-m-e': '4.mp4',
// 'say-5s-m-sw': '5.mp4',
'say-3s-m-sw': 's-1.mp4',
};
// 默认视频流配置
const DEFAULT_VIDEO = '0.mp4';
const DEFAULT_VIDEO = 'd-3s.mp4';
const INTERACTION_TIMEOUT = 10000; // 10秒后回到默认视频
// 获取视频列表

View File

@ -19,11 +19,14 @@ class WebRTCChat {
this.mediaRecorder = null;
this.audioChunks = [];
this.videoMapping = {};
this.defaultVideo = '0.mp4';
this.defaultVideo = 'd-3s.mp4';
this.currentVideoTag = 'default';
this.currentVideo = null;
this.videoStreams = new Map(); // 存储不同视频的MediaStream
this.currentVideoStream = null;
this.audioEndedListenerAdded = false; // 标志位,避免重复添加监听器
this.animationFrameIds = new Map(); // 存储每个视频的动画帧ID
this.canvasElements = new Map(); // 存储每个视频的canvas元素
// 添加视频相关属性
this.videoSender = null; // WebRTC视频发送器
@ -299,6 +302,9 @@ class WebRTCChat {
try {
this.logMessage(`开始创建视频流: ${videoFile}`, 'info');
// 清理之前的动画循环和canvas
this.cleanupVideoResources(videoFile);
// 先测试视频文件是否存在
await this.testVideoFile(videoFile);
@ -367,6 +373,8 @@ class WebRTCChat {
// 绘制视频到canvas
let lastDrawTime = 0;
let isDrawing = false;
let animationId;
const drawFrame = () => {
const now = performance.now();
if (video.readyState >= video.HAVE_CURRENT_DATA && !isDrawing && (now - lastDrawTime > 16)) {
@ -375,12 +383,16 @@ class WebRTCChat {
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
isDrawing = false;
}
requestAnimationFrame(drawFrame);
animationId = requestAnimationFrame(drawFrame);
};
// 开始绘制帧
drawFrame();
// 存储动画帧ID和canvas引用
this.animationFrameIds.set(videoFile, animationId);
this.canvasElements.set(videoFile, canvas);
// 从canvas创建MediaStream
const stream = canvas.captureStream(30);
@ -391,68 +403,56 @@ class WebRTCChat {
this.logMessage(`视频流创建成功: ${videoFile}`, 'success');
if (videoFile === this.defaultVideo) {
let lastCurrentTime = 0;
video.addEventListener('timeupdate', async () => {
if (videoFile === this.defaultVideo && !this.audioEndedListenerAdded) {
this.audioEndedListenerAdded = true;
// 清理之前可能存在的监听器
if (this.currentTimeUpdateHandler) {
video.removeEventListener('timeupdate', this.currentTimeUpdateHandler);
}
// 在handleTimeUpdate中使用节流
const throttledTimeUpdate = this.throttle(handleTimeUpdate, 100);
const handleTimeUpdate = async () => {
const currentTime = video.currentTime;
const duration = video.duration;
// 方法2a检查是否接近结束最后0.1秒)
// 检查是否接近结束最后0.1秒)
if (duration - currentTime <= 0.1) {
console.log('视频即将播放完成');
// 处理即将结束的逻辑
if (videoFile === this.defaultVideo) {
let lastCurrentTime = 0;
video.addEventListener('timeupdate', async () => {
const currentTime = video.currentTime;
const duration = video.duration;
// 检查音频是否正在播放从minimaxi_stream.js获取isPlaying状态
const isAudioPlaying = window.isPlaying || false;
// 如果音频没有播放,且当前不是默认视频,则切换到默认视频
if (!isAudioPlaying) {
const currentVideoFile = this.currentVideo;
if (currentVideoFile !== this.defaultVideo) {
console.log('音频已停止,当前视频不是默认视频,准备切换到默认视频');
// 检查音频是否正在播放从minimaxi_stream.js获取isPlaying状态
const isAudioPlaying = window.isPlaying || false; // 需要确保isPlaying是全局可访问的
// 移除监听器
this.cleanupTimeUpdateListener();
// 如果音频没有播放,且当前不是默认视频,则切换到默认视频
if (!isAudioPlaying) {
const currentVideoFile = this.currentVideo; // 获取当前播放的视频文件名
if (currentVideoFile !== this.defaultVideo) {
console.log('音频已停止,当前视频不是默认视频,准备切换到默认视频');
// 停止当前视频的循环
if (this.recordedVideo) {
this.recordedVideo.loop = false;
}
// 切换到默认视频
try {
await this.switchVideoWithReplaceTrack(this.defaultVideo, 'auto-switch', 'audio-ended');
console.log('已自动切换到默认视频');
} catch (error) {
console.error('自动切换到默认视频失败:', error);
}
}
// 停止当前视频的循环
if (this.recordedVideo) {
this.recordedVideo.loop = false;
}
lastCurrentTime = currentTime;
});
// 切换到默认视频
try {
await this.switchVideoWithReplaceTrack(this.defaultVideo, 'auto', 'default');
console.log('已自动切换到默认视频');
} catch (error) {
console.error('自动切换到默认视频失败:', error);
}
}
}
}
// 方法2b检查是否已经到达结尾
if (currentTime >= duration) {
console.log('视频播放完成');
// 处理播放完成的逻辑
}
// 方法2c检查时间是否停止更新可能表示播放结束
if (Math.abs(currentTime - lastCurrentTime) < 0.01) {
// 时间没有更新,可能播放结束或暂停
if (currentTime >= duration) {
console.log('视频播放完成(通过时间停止检测)');
}
}
lastCurrentTime = currentTime;
});
};
// 保存监听器引用以便后续清理
this.currentTimeUpdateHandler = throttledTimeUpdate;
video.addEventListener('timeupdate', throttledTimeUpdate);
}
// 使用有限缓存策略最多缓存3个视频流
@ -469,6 +469,8 @@ class WebRTCChat {
return stream;
} catch (error) {
// 确保在错误情况下也清理资源
this.cleanupVideoResources(videoFile);
this.logMessage(`创建视频流失败 ${videoFile}: ${error.message}`, 'error');
throw error;
}
@ -486,7 +488,7 @@ class WebRTCChat {
// });
// 特别确保添加了5.mp4从日志看这是常用视频
videosToPreload.add('6.mp4');
videosToPreload.add('s-1.mp4');
// 开始预加载
for (const videoFile of videosToPreload) {
@ -546,6 +548,7 @@ class WebRTCChat {
// 现在停止旧的视频流
if (this.currentVideoStream !== newStream) {
const oldStream = this.currentVideoStream;
const oldVideo = this.currentVideo;
setTimeout(() => {
if (oldStream) {
oldStream.getTracks().forEach(track => {
@ -553,6 +556,10 @@ class WebRTCChat {
this.logMessage(`已停止旧轨道: ${track.kind}`, 'info');
});
}
// 清理旧视频的资源
if (oldVideo) {
this.cleanupVideoResources(oldVideo);
}
}, 1000); // 延迟1秒停止旧流确保新流已经稳定
}
@ -602,9 +609,11 @@ class WebRTCChat {
// 同时更新本地视频显示
if (this.recordedVideo) {
// 停止当前视频流
// 停止当前视频流和动画循环
if (this.currentVideoStream) {
this.currentVideoStream.getTracks().forEach(track => track.stop());
// 清理当前视频的资源
this.cleanupVideoResources(this.currentVideo);
}
// 设置新的视频流
@ -993,6 +1002,29 @@ class WebRTCChat {
this.messageLog.scrollTop = this.messageLog.scrollHeight;
}
// 添加清理监听器的方法
cleanupTimeUpdateListener() {
if (this.currentTimeUpdateHandler && this.recordedVideo) {
this.recordedVideo.removeEventListener('timeupdate', this.currentTimeUpdateHandler);
this.currentTimeUpdateHandler = null;
this.audioEndedListenerAdded = false;
}
}
// 添加节流函数
throttle(func, limit) {
let inThrottle;
return function() {
const args = arguments;
const context = this;
if (!inThrottle) {
func.apply(context, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
}
}
checkVideoStreamStatus() {
const status = {
hasStream: !!this.currentVideoStream,
@ -1026,6 +1058,68 @@ class WebRTCChat {
// 检查当前视频流状态
this.checkVideoStreamStatus();
}
// 清理视频资源的方法
cleanupVideoResources(videoFile) {
// 停止动画循环
if (this.animationFrameIds.has(videoFile)) {
const animationId = this.animationFrameIds.get(videoFile);
if (animationId) {
cancelAnimationFrame(animationId);
}
this.animationFrameIds.delete(videoFile);
}
// 清理canvas
if (this.canvasElements.has(videoFile)) {
const canvas = this.canvasElements.get(videoFile);
if (canvas) {
const ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
this.canvasElements.delete(videoFile);
}
}
// 清理所有视频资源
cleanupAllVideoResources() {
// 停止所有动画循环
for (const [videoFile, animationId] of this.animationFrameIds) {
if (animationId) {
cancelAnimationFrame(animationId);
}
}
this.animationFrameIds.clear();
// 清理所有canvas
for (const [videoFile, canvas] of this.canvasElements) {
if (canvas) {
const ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
}
this.canvasElements.clear();
}
// 添加销毁方法
destroy() {
this.cleanupAllVideoResources();
this.cleanupTimeUpdateListener();
// 清理其他资源
if (this.localStream) {
this.localStream.getTracks().forEach(track => track.stop());
}
if (this.currentVideoStream) {
this.currentVideoStream.getTracks().forEach(track => track.stop());
}
// 清理视频流缓存
for (const [key, stream] of this.videoStreams) {
stream.getTracks().forEach(track => track.stop());
}
this.videoStreams.clear();
}
}
// 页面加载完成后初始化应用
@ -1037,3 +1131,10 @@ document.addEventListener('DOMContentLoaded', () => {
console.error('WebRTCChat 初始化失败:', error);
}
});
// 在页面卸载时清理资源
window.addEventListener('beforeunload', () => {
if (window.webrtcApp) {
window.webrtcApp.destroy();
}
});

View File

@ -65,7 +65,7 @@ async function processAudioQueue() {
if (!window.isPlaying && audioQueue.length > 0) {
const audioItem = audioQueue.shift();
const sayName = 'say-3s-m-sw'
const targetVideo = '6.mp4'
const targetVideo = 's-1.mp4'
// 如果是第一个音频片段,触发视频切换
if (sayName != window.webrtcApp.currentVideoTag && window.webrtcApp && window.webrtcApp.handleTextInput) {
try {