优化衔接处bug
This commit is contained in:
parent
a96fc86d42
commit
f0bf3b6184
14
server.js
14
server.js
@ -87,16 +87,16 @@ const connectedClients = new Map();
|
|||||||
|
|
||||||
// 视频映射配置
|
// 视频映射配置
|
||||||
const videoMapping = {
|
const videoMapping = {
|
||||||
'say-6s-m-e': '1-m.mp4',
|
// 'say-6s-m-e': '1-m.mp4',
|
||||||
'default': '0.mp4',
|
'default': 'd-3s.mp4',
|
||||||
'say-5s-amplitude': '2.mp4',
|
// 'say-5s-amplitude': '2.mp4',
|
||||||
'say-5s-m-e': '4.mp4',
|
// 'say-5s-m-e': '4.mp4',
|
||||||
'say-5s-m-sw': '5.mp4',
|
// 'say-5s-m-sw': '5.mp4',
|
||||||
'say-3s-m-sw': '6.mp4',
|
'say-3s-m-sw': 's-1.mp4',
|
||||||
};
|
};
|
||||||
|
|
||||||
// 默认视频流配置
|
// 默认视频流配置
|
||||||
const DEFAULT_VIDEO = '0.mp4';
|
const DEFAULT_VIDEO = 'd-3s.mp4';
|
||||||
const INTERACTION_TIMEOUT = 10000; // 10秒后回到默认视频
|
const INTERACTION_TIMEOUT = 10000; // 10秒后回到默认视频
|
||||||
|
|
||||||
// 获取视频列表
|
// 获取视频列表
|
||||||
|
|||||||
211
src/index.js
211
src/index.js
@ -19,11 +19,14 @@ class WebRTCChat {
|
|||||||
this.mediaRecorder = null;
|
this.mediaRecorder = null;
|
||||||
this.audioChunks = [];
|
this.audioChunks = [];
|
||||||
this.videoMapping = {};
|
this.videoMapping = {};
|
||||||
this.defaultVideo = '0.mp4';
|
this.defaultVideo = 'd-3s.mp4';
|
||||||
this.currentVideoTag = 'default';
|
this.currentVideoTag = 'default';
|
||||||
this.currentVideo = null;
|
this.currentVideo = null;
|
||||||
this.videoStreams = new Map(); // 存储不同视频的MediaStream
|
this.videoStreams = new Map(); // 存储不同视频的MediaStream
|
||||||
this.currentVideoStream = null;
|
this.currentVideoStream = null;
|
||||||
|
this.audioEndedListenerAdded = false; // 标志位,避免重复添加监听器
|
||||||
|
this.animationFrameIds = new Map(); // 存储每个视频的动画帧ID
|
||||||
|
this.canvasElements = new Map(); // 存储每个视频的canvas元素
|
||||||
|
|
||||||
// 添加视频相关属性
|
// 添加视频相关属性
|
||||||
this.videoSender = null; // WebRTC视频发送器
|
this.videoSender = null; // WebRTC视频发送器
|
||||||
@ -299,6 +302,9 @@ class WebRTCChat {
|
|||||||
try {
|
try {
|
||||||
this.logMessage(`开始创建视频流: ${videoFile}`, 'info');
|
this.logMessage(`开始创建视频流: ${videoFile}`, 'info');
|
||||||
|
|
||||||
|
// 清理之前的动画循环和canvas
|
||||||
|
this.cleanupVideoResources(videoFile);
|
||||||
|
|
||||||
// 先测试视频文件是否存在
|
// 先测试视频文件是否存在
|
||||||
await this.testVideoFile(videoFile);
|
await this.testVideoFile(videoFile);
|
||||||
|
|
||||||
@ -367,6 +373,8 @@ class WebRTCChat {
|
|||||||
// 绘制视频到canvas
|
// 绘制视频到canvas
|
||||||
let lastDrawTime = 0;
|
let lastDrawTime = 0;
|
||||||
let isDrawing = false;
|
let isDrawing = false;
|
||||||
|
let animationId;
|
||||||
|
|
||||||
const drawFrame = () => {
|
const drawFrame = () => {
|
||||||
const now = performance.now();
|
const now = performance.now();
|
||||||
if (video.readyState >= video.HAVE_CURRENT_DATA && !isDrawing && (now - lastDrawTime > 16)) {
|
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);
|
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
|
||||||
isDrawing = false;
|
isDrawing = false;
|
||||||
}
|
}
|
||||||
requestAnimationFrame(drawFrame);
|
animationId = requestAnimationFrame(drawFrame);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 开始绘制帧
|
// 开始绘制帧
|
||||||
drawFrame();
|
drawFrame();
|
||||||
|
|
||||||
|
// 存储动画帧ID和canvas引用
|
||||||
|
this.animationFrameIds.set(videoFile, animationId);
|
||||||
|
this.canvasElements.set(videoFile, canvas);
|
||||||
|
|
||||||
// 从canvas创建MediaStream
|
// 从canvas创建MediaStream
|
||||||
const stream = canvas.captureStream(30);
|
const stream = canvas.captureStream(30);
|
||||||
|
|
||||||
@ -391,68 +403,56 @@ class WebRTCChat {
|
|||||||
|
|
||||||
this.logMessage(`视频流创建成功: ${videoFile}`, 'success');
|
this.logMessage(`视频流创建成功: ${videoFile}`, 'success');
|
||||||
|
|
||||||
if (videoFile === this.defaultVideo) {
|
if (videoFile === this.defaultVideo && !this.audioEndedListenerAdded) {
|
||||||
let lastCurrentTime = 0;
|
this.audioEndedListenerAdded = true;
|
||||||
video.addEventListener('timeupdate', async () => {
|
|
||||||
|
// 清理之前可能存在的监听器
|
||||||
|
if (this.currentTimeUpdateHandler) {
|
||||||
|
video.removeEventListener('timeupdate', this.currentTimeUpdateHandler);
|
||||||
|
}
|
||||||
|
// 在handleTimeUpdate中使用节流
|
||||||
|
const throttledTimeUpdate = this.throttle(handleTimeUpdate, 100);
|
||||||
|
const handleTimeUpdate = async () => {
|
||||||
const currentTime = video.currentTime;
|
const currentTime = video.currentTime;
|
||||||
const duration = video.duration;
|
const duration = video.duration;
|
||||||
|
|
||||||
// 方法2a:检查是否接近结束(最后0.1秒)
|
// 检查是否接近结束(最后0.1秒)
|
||||||
if (duration - currentTime <= 0.1) {
|
if (duration - currentTime <= 0.1) {
|
||||||
console.log('视频即将播放完成');
|
console.log('视频即将播放完成');
|
||||||
// 处理即将结束的逻辑
|
|
||||||
if (videoFile === this.defaultVideo) {
|
// 检查音频是否正在播放(从minimaxi_stream.js获取isPlaying状态)
|
||||||
let lastCurrentTime = 0;
|
const isAudioPlaying = window.isPlaying || false;
|
||||||
video.addEventListener('timeupdate', async () => {
|
|
||||||
const currentTime = video.currentTime;
|
// 如果音频没有播放,且当前不是默认视频,则切换到默认视频
|
||||||
const duration = video.duration;
|
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) {
|
if (this.recordedVideo) {
|
||||||
const currentVideoFile = this.currentVideo; // 获取当前播放的视频文件名
|
this.recordedVideo.loop = false;
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
lastCurrentTime = currentTime;
|
// 切换到默认视频
|
||||||
});
|
try {
|
||||||
|
await this.switchVideoWithReplaceTrack(this.defaultVideo, 'auto', 'default');
|
||||||
|
console.log('已自动切换到默认视频');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('自动切换到默认视频失败:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
};
|
||||||
// 方法2b:检查是否已经到达结尾
|
|
||||||
if (currentTime >= duration) {
|
// 保存监听器引用以便后续清理
|
||||||
console.log('视频播放完成');
|
this.currentTimeUpdateHandler = throttledTimeUpdate;
|
||||||
// 处理播放完成的逻辑
|
video.addEventListener('timeupdate', throttledTimeUpdate);
|
||||||
}
|
|
||||||
|
|
||||||
// 方法2c:检查时间是否停止更新(可能表示播放结束)
|
|
||||||
if (Math.abs(currentTime - lastCurrentTime) < 0.01) {
|
|
||||||
// 时间没有更新,可能播放结束或暂停
|
|
||||||
if (currentTime >= duration) {
|
|
||||||
console.log('视频播放完成(通过时间停止检测)');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lastCurrentTime = currentTime;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用有限缓存策略(最多缓存3个视频流)
|
// 使用有限缓存策略(最多缓存3个视频流)
|
||||||
@ -469,6 +469,8 @@ class WebRTCChat {
|
|||||||
|
|
||||||
return stream;
|
return stream;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
// 确保在错误情况下也清理资源
|
||||||
|
this.cleanupVideoResources(videoFile);
|
||||||
this.logMessage(`创建视频流失败 ${videoFile}: ${error.message}`, 'error');
|
this.logMessage(`创建视频流失败 ${videoFile}: ${error.message}`, 'error');
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
@ -486,7 +488,7 @@ class WebRTCChat {
|
|||||||
// });
|
// });
|
||||||
|
|
||||||
// 特别确保添加了5.mp4(从日志看这是常用视频)
|
// 特别确保添加了5.mp4(从日志看这是常用视频)
|
||||||
videosToPreload.add('6.mp4');
|
videosToPreload.add('s-1.mp4');
|
||||||
|
|
||||||
// 开始预加载
|
// 开始预加载
|
||||||
for (const videoFile of videosToPreload) {
|
for (const videoFile of videosToPreload) {
|
||||||
@ -546,6 +548,7 @@ class WebRTCChat {
|
|||||||
// 现在停止旧的视频流
|
// 现在停止旧的视频流
|
||||||
if (this.currentVideoStream !== newStream) {
|
if (this.currentVideoStream !== newStream) {
|
||||||
const oldStream = this.currentVideoStream;
|
const oldStream = this.currentVideoStream;
|
||||||
|
const oldVideo = this.currentVideo;
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (oldStream) {
|
if (oldStream) {
|
||||||
oldStream.getTracks().forEach(track => {
|
oldStream.getTracks().forEach(track => {
|
||||||
@ -553,6 +556,10 @@ class WebRTCChat {
|
|||||||
this.logMessage(`已停止旧轨道: ${track.kind}`, 'info');
|
this.logMessage(`已停止旧轨道: ${track.kind}`, 'info');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
// 清理旧视频的资源
|
||||||
|
if (oldVideo) {
|
||||||
|
this.cleanupVideoResources(oldVideo);
|
||||||
|
}
|
||||||
}, 1000); // 延迟1秒停止旧流,确保新流已经稳定
|
}, 1000); // 延迟1秒停止旧流,确保新流已经稳定
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -602,9 +609,11 @@ class WebRTCChat {
|
|||||||
|
|
||||||
// 同时更新本地视频显示
|
// 同时更新本地视频显示
|
||||||
if (this.recordedVideo) {
|
if (this.recordedVideo) {
|
||||||
// 停止当前视频流
|
// 停止当前视频流和动画循环
|
||||||
if (this.currentVideoStream) {
|
if (this.currentVideoStream) {
|
||||||
this.currentVideoStream.getTracks().forEach(track => track.stop());
|
this.currentVideoStream.getTracks().forEach(track => track.stop());
|
||||||
|
// 清理当前视频的资源
|
||||||
|
this.cleanupVideoResources(this.currentVideo);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置新的视频流
|
// 设置新的视频流
|
||||||
@ -993,6 +1002,29 @@ class WebRTCChat {
|
|||||||
this.messageLog.scrollTop = this.messageLog.scrollHeight;
|
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() {
|
checkVideoStreamStatus() {
|
||||||
const status = {
|
const status = {
|
||||||
hasStream: !!this.currentVideoStream,
|
hasStream: !!this.currentVideoStream,
|
||||||
@ -1026,6 +1058,68 @@ class WebRTCChat {
|
|||||||
// 检查当前视频流状态
|
// 检查当前视频流状态
|
||||||
this.checkVideoStreamStatus();
|
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);
|
console.error('WebRTCChat 初始化失败:', error);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 在页面卸载时清理资源
|
||||||
|
window.addEventListener('beforeunload', () => {
|
||||||
|
if (window.webrtcApp) {
|
||||||
|
window.webrtcApp.destroy();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|||||||
@ -65,7 +65,7 @@ async function processAudioQueue() {
|
|||||||
if (!window.isPlaying && audioQueue.length > 0) {
|
if (!window.isPlaying && audioQueue.length > 0) {
|
||||||
const audioItem = audioQueue.shift();
|
const audioItem = audioQueue.shift();
|
||||||
const sayName = 'say-3s-m-sw'
|
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) {
|
if (sayName != window.webrtcApp.currentVideoTag && window.webrtcApp && window.webrtcApp.handleTextInput) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user