diff --git a/server.js b/server.js index 315e9f5..acdad29 100644 --- a/server.js +++ b/server.js @@ -136,7 +136,7 @@ const scenes = [ }, { name: '喝茶', - defaultVideo: '8-5-hc-bd-female.mp4', + defaultVideo: '8-8-hc-bd-2.mp4', interactionVideo: '8-5-hc-sh-female.mp4', tag: 'tea', apiKey: 'bot-20250805140055-ccdr6' // 喝茶场景的API key diff --git a/src/index.js b/src/index.js index b8c39d9..0daf054 100644 --- a/src/index.js +++ b/src/index.js @@ -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()}`; // 添加时间戳确保唯一性 @@ -580,160 +700,50 @@ class WebRTCChat { // } // } - // 修改原有的switchVideoStream方法,使用新的平滑切换 + // 修改视频切换方法,直接使用预加载视频切换,不使用WebRTC传输 async switchVideoStream(videoFile, type = '', text = '') { - // 使用平滑切换方法 - return await this.switchVideoStreamSmooth(videoFile, type, text); - } - - // 使用replaceTrack方式切换视频 - async switchVideoWithReplaceTrack(videoFile, type = '', text = '') { try { - this.logMessage(`开始使用replaceTrack切换视频: ${videoFile}`, 'info'); + this.logMessage(`开始切换视频: ${videoFile} (${type})`, 'info'); - // 创建新的视频流 - const newVideoStream = await this.createVideoStream(videoFile); - const newVideoTrack = newVideoStream.getVideoTracks()[0]; + let newVideoStream; + let isUsingPrecreated = false; - if (!newVideoTrack) { - throw new Error('新视频流中没有视频轨道'); + // 首先检查预创建的视频流 + 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); + } } - // 如果有WebRTC连接且有视频发送器,使用replaceTrack - if (this.peerConnection && this.videoSender) { - await this.videoSender.replaceTrack(newVideoTrack); - this.logMessage('WebRTC视频轨道替换成功', 'success'); - } - - // 同时更新本地视频显示 + // 直接切换本地视频显示,不使用WebRTC传输 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 ? 10 : 50; + await new Promise(resolve => setTimeout(resolve, waitTime)); - // 确保视频播放 try { await this.recordedVideo.play(); - this.logMessage(`本地视频切换成功: ${videoFile}`, 'success'); + this.logMessage(`视频切换完成: ${videoFile} (${type})`, 'success'); } catch (playError) { - this.logMessage(`本地视频播放失败: ${playError.message}`, 'error'); - } - } - - // 记录切换信息 - if (type && text) { - this.logMessage(`视频切换完成 - 类型: ${type}, 文本: ${text}`, 'info'); - } - - 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'); - } - - return false; - } - } - - // 新增平滑视频切换方法 - async switchVideoStreamSmooth(videoFile, type = '', text = '') { - try { - this.logMessage(`开始平滑切换视频流: ${videoFile} (${type})`, 'info'); - - // 检查是否已缓存,如果已缓存则不显示加载指示器 - const isCached = this.videoStreams.has(videoFile); - if (!isCached) { - this.showVideoLoading(); - } - - // 确定当前活跃的视频元素和缓冲元素 - const currentVideo = this.activeVideoElement === 'main' ? this.recordedVideo : this.recordedVideoBuffer; - const bufferVideo = this.activeVideoElement === 'main' ? this.recordedVideoBuffer : this.recordedVideo; - - // 检查是否已缓存 - let newStream; - - if (isCached) { - const cachedStream = this.videoStreams.get(videoFile); - if (cachedStream && cachedStream.getTracks().length > 0) { - newStream = cachedStream; - this.logMessage(`使用缓存视频流: ${videoFile}`, 'success'); - } else { - newStream = await this.createVideoStream(videoFile); - } - } else { - newStream = await this.createVideoStream(videoFile); - } - - // 在缓冲视频元素中预加载新视频 - bufferVideo.srcObject = newStream; - - // 等待缓冲视频准备就绪 - await new Promise((resolve, reject) => { - const timeout = setTimeout(() => { - reject(new Error('视频加载超时')); - }, 5000); - - const onCanPlay = () => { - clearTimeout(timeout); - bufferVideo.removeEventListener('canplay', onCanPlay); - bufferVideo.removeEventListener('error', onError); - resolve(); - }; - - const onError = (error) => { - clearTimeout(timeout); - bufferVideo.removeEventListener('canplay', onCanPlay); - bufferVideo.removeEventListener('error', onError); - reject(error); - }; - - bufferVideo.addEventListener('canplay', onCanPlay); - bufferVideo.addEventListener('error', onError); - - // 开始播放缓冲视频 - bufferVideo.play().catch(onError); - }); - - // 只有显示了加载指示器才隐藏 - if (!isCached) { - this.hideVideoLoading(); - } - - // 执行淡入淡出切换 - // await this.performVideoTransition(currentVideo, bufferVideo); - - // 更新当前视频流和活跃元素 - this.currentVideoStream = newStream; - this.currentVideo = videoFile; - this.activeVideoElement = this.activeVideoElement === 'main' ? 'buffer' : 'main'; - - // 停止旧视频流(延迟停止避免闪烁) - setTimeout(() => { - if (currentVideo.srcObject && currentVideo.srcObject !== newStream) { - currentVideo.srcObject.getTracks().forEach(track => track.stop()); - currentVideo.srcObject = null; - } - }, 1000); - - // 更新WebRTC连接中的视频轨道 - if (this.peerConnection && this.videoSender) { - const newVideoTrack = newStream.getVideoTracks()[0]; - if (newVideoTrack) { - await this.videoSender.replaceTrack(newVideoTrack); + this.logMessage(`视频播放失败: ${playError.message}`, 'error'); } } @@ -746,12 +756,241 @@ class WebRTCChat { this.logMessage(`成功切换到视频流: ${videoFile}`, 'success'); } + return true; + } catch (error) { - this.logMessage(`平滑切换视频流失败: ${error.message}`, 'error'); - // 确保隐藏加载指示器 + this.logMessage(`视频切换失败: ${error.message}`, 'error'); + return false; + } + } + + // 添加视频帧缓存方法 + captureVideoFrame(videoElement) { + const canvas = document.createElement('canvas'); + const ctx = canvas.getContext('2d'); + + canvas.width = videoElement.videoWidth || videoElement.clientWidth; + canvas.height = videoElement.videoHeight || videoElement.clientHeight; + + // 绘制当前视频帧到canvas + ctx.drawImage(videoElement, 0, 0, canvas.width, canvas.height); + + return canvas.toDataURL('image/jpeg', 0.8); + } + + // 创建静态帧显示元素 + createFrameOverlay(frameData) { + const overlay = document.createElement('div'); + overlay.style.position = 'absolute'; + overlay.style.top = '0'; + overlay.style.left = '0'; + overlay.style.width = '100%'; + overlay.style.height = '100%'; + overlay.style.backgroundImage = `url(${frameData})`; + overlay.style.backgroundSize = 'cover'; + overlay.style.backgroundPosition = 'center'; + overlay.style.backgroundRepeat = 'no-repeat'; + overlay.style.zIndex = '10'; + overlay.style.pointerEvents = 'none'; + overlay.style.willChange = 'auto'; // 优化渲染性能 + overlay.id = 'video-frame-overlay'; + + return overlay; + } + + // 新增平滑视频切换方法 - 等待完全传输后切换 + async switchVideoStreamSmooth(videoFile, type = '', text = '') { + try { + this.logMessage(`开始等待视频流传输完成: ${videoFile} (${type})`, 'info'); + + // 1. 获取当前和缓冲视频元素 + const currentVideo = this.activeVideoElement === 'main' ? this.recordedVideo : this.recordedVideoBuffer; + const bufferVideo = this.activeVideoElement === 'main' ? this.recordedVideoBuffer : this.recordedVideo; + + // 2. 检查是否已缓存 + const isCached = this.videoStreams.has(videoFile); + if (!isCached) { + this.showVideoLoading(); + } + + // 3. 准备新视频流 + let newStream; + if (isCached) { + const cachedStream = this.videoStreams.get(videoFile); + if (cachedStream && cachedStream.getTracks().length > 0) { + newStream = cachedStream; + this.logMessage(`使用缓存视频流: ${videoFile}`, 'success'); + } else { + newStream = await this.createVideoStream(videoFile); + } + } else { + newStream = await this.createVideoStream(videoFile); + } + + // 4. 在缓冲视频元素中预加载新视频 + bufferVideo.srcObject = newStream; + + // 5. 等待视频流完全传输和准备就绪 + await new Promise((resolve, reject) => { + const timeout = setTimeout(() => { + reject(new Error('视频流传输超时')); + }, 15000); // 增加超时时间到15秒,确保有足够时间传输 + + let frameCount = 0; + const minFrames = 10; // 增加最小帧数确保传输完整 + let isStreamReady = false; + + const onReady = () => { + clearTimeout(timeout); + bufferVideo.removeEventListener('canplay', onCanPlay); + bufferVideo.removeEventListener('canplaythrough', onCanPlayThrough); + bufferVideo.removeEventListener('timeupdate', onTimeUpdate); + bufferVideo.removeEventListener('error', onError); + bufferVideo.removeEventListener('loadeddata', onLoadedData); + resolve(); + }; + + // 检查视频数据是否完全加载 + const onLoadedData = () => { + this.logMessage(`视频数据开始加载: ${videoFile}`, 'info'); + }; + + // 检查视频是否可以流畅播放(数据传输充足) + const onCanPlayThrough = () => { + this.logMessage(`视频流传输充足,可以流畅播放: ${videoFile}`, 'success'); + isStreamReady = true; + + // 开始播放并等待稳定帧 + bufferVideo.play().then(() => { + const checkFrameStability = () => { + if (bufferVideo.currentTime > 0) { + frameCount++; + if (frameCount >= minFrames && isStreamReady) { + // 额外等待确保传输稳定 + setTimeout(() => { + onReady(); + }, 200); // 等待200ms确保传输稳定 + } else { + requestAnimationFrame(checkFrameStability); + } + } else { + requestAnimationFrame(checkFrameStability); + } + }; + requestAnimationFrame(checkFrameStability); + }).catch(reject); + }; + + // 基本播放准备就绪 + const onCanPlay = () => { + if (!isStreamReady) { + this.logMessage(`视频基本准备就绪,等待完全传输: ${videoFile}`, 'info'); + // 如果还没有canplaythrough事件,继续等待 + } + }; + + // 时间更新监听(备用检查) + const onTimeUpdate = () => { + if (bufferVideo.currentTime > 0 && isStreamReady) { + frameCount++; + if (frameCount >= minFrames) { + setTimeout(() => { + onReady(); + }, 200); + } + } + }; + + const onError = (error) => { + clearTimeout(timeout); + bufferVideo.removeEventListener('canplay', onCanPlay); + bufferVideo.removeEventListener('canplaythrough', onCanPlayThrough); + bufferVideo.removeEventListener('timeupdate', onTimeUpdate); + bufferVideo.removeEventListener('error', onError); + bufferVideo.removeEventListener('loadeddata', onLoadedData); + reject(error); + }; + + // 检查当前状态 + if (bufferVideo.readyState >= 4) { // HAVE_ENOUGH_DATA + onCanPlayThrough(); + } else { + bufferVideo.addEventListener('loadeddata', onLoadedData); + bufferVideo.addEventListener('canplay', onCanPlay); + bufferVideo.addEventListener('canplaythrough', onCanPlayThrough); + bufferVideo.addEventListener('timeupdate', onTimeUpdate); + bufferVideo.addEventListener('error', onError); + } + }); + + // 6. 最终确认视频流完全准备就绪 + await new Promise(resolve => { + let confirmCount = 0; + const maxConfirms = 3; + + const finalCheck = () => { + if (bufferVideo.readyState >= 4 && bufferVideo.currentTime > 0) { + confirmCount++; + if (confirmCount >= maxConfirms) { + this.logMessage(`视频流传输完成,准备切换: ${videoFile}`, 'success'); + resolve(); + } else { + setTimeout(finalCheck, 50); // 每50ms检查一次 + } + } else { + setTimeout(finalCheck, 50); + } + }; + finalCheck(); + }); + + // 7. 执行无缝切换(当前视频继续播放直到新视频完全准备好) + bufferVideo.style.zIndex = '2'; + currentVideo.style.zIndex = '1'; + + this.logMessage(`视频切换完成: ${videoFile}`, 'success'); + + // 8. 隐藏加载指示器 + if (!isCached) { + this.hideVideoLoading(); + } + + // 9. 更新状态 + this.currentVideoStream = newStream; + this.currentVideo = videoFile; + this.activeVideoElement = this.activeVideoElement === 'main' ? 'buffer' : 'main'; + + // 10. 延迟清理旧视频流 + setTimeout(() => { + if (currentVideo.srcObject && currentVideo.srcObject !== newStream) { + currentVideo.srcObject.getTracks().forEach(track => track.stop()); + currentVideo.srcObject = null; + } + }, 1000); + + // 11. 更新WebRTC连接 + if (this.peerConnection && this.videoSender) { + const newVideoTrack = newStream.getVideoTracks()[0]; + if (newVideoTrack) { + await this.videoSender.replaceTrack(newVideoTrack); + } + } + + // 12. 更新显示信息 + if (text) { + this.currentVideoName.textContent = `交互视频: ${videoFile} (${type}: ${text})`; + this.logMessage(`成功切换到交互视频流: ${videoFile} (${type}: ${text})`, 'success'); + } else { + this.currentVideoName.textContent = `视频流: ${videoFile}`; + this.logMessage(`成功切换到视频流: ${videoFile}`, 'success'); + } + + } catch (error) { + this.logMessage(`视频流切换失败: ${error.message}`, 'error'); + this.hideVideoLoading(); - // 如果切换失败,尝试回到默认视频 + // 回退到默认视频 if (videoFile !== this.defaultVideo) { this.logMessage('尝试回到默认视频', 'info'); await this.switchVideoStreamSmooth(this.defaultVideo, 'fallback'); @@ -759,32 +998,7 @@ class WebRTCChat { } } - // 执行视频过渡动画 - async performVideoTransition(currentVideo, bufferVideo) { - return new Promise((resolve) => { - // 添加切换类 - currentVideo.classList.add('switching'); - bufferVideo.classList.add('switching'); - - // 等待CSS过渡完成 - setTimeout(() => { - // 移除切换类 - currentVideo.classList.remove('switching'); - bufferVideo.classList.remove('switching'); - - // 交换z-index - if (currentVideo.style.zIndex === '2' || !currentVideo.style.zIndex) { - currentVideo.style.zIndex = '1'; - bufferVideo.style.zIndex = '2'; - } else { - currentVideo.style.zIndex = '2'; - bufferVideo.style.zIndex = '1'; - } - - resolve(); - }, 500); // 与CSS过渡时间一致 - }); - } + // 显示加载指示器 showVideoLoading() { @@ -804,8 +1018,8 @@ class WebRTCChat { // 开始通话按钮 this.startButton.onclick = () => this.startCall(); - // 停止通话按钮 - this.stopButton.onclick = () => this.stopCall(); + // 停止通话按钮 - 改为调用 userDisconnect + this.stopButton.onclick = () => this.userDisconnect(); // 静音按钮 // this.muteButton.onclick = () => this.toggleMute(); @@ -855,12 +1069,10 @@ class WebRTCChat { async startCall() { try { - // 显示等待连接提示 - this.showConnectionWaiting(); - // 立即隐藏开始通话按钮 this.startButton.style.display = 'none'; - + // 显示等待连接提示 + this.showConnectionWaiting(); // 切换到通话中图标 this.switchToCallingIcon(); @@ -900,8 +1112,8 @@ class WebRTCChat { this.socket.emit('call-started'); // 开始播放当前场景的默认视频 - await this.startDefaultVideoStream(); - + await this.precreateImportantVideos(); + // 隐藏等待连接提示 this.hideConnectionWaiting(); @@ -915,16 +1127,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) { @@ -948,24 +1160,37 @@ class WebRTCChat { this.stopButton.style.display = 'none'; this.stopButton.disabled = true; - // // 显示头像,隐藏视频 - // if (this.videoContainer) { - // this.videoContainer.classList.remove('calling'); - // } + // 显示开始通话按钮 + this.startButton.style.display = 'block'; + this.startButton.disabled = false; + + // 移除页面刷新,保持websocket连接 + // setTimeout(() => { + // window.location.reload(); + // }, 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('通话已结束,5秒后刷新页面清除缓存...'); + console.log('用户主动断开,300ms后刷新页面清除缓存...'); setTimeout(() => { window.location.reload(); - this.startButton.style.display = 'block'; - this.startButton.disabled = false; - }, 2000); - - setTimeout(() => { - // 显示头像,隐藏视频 - if (this.videoContainer) { - this.videoContainer.classList.remove('calling'); - } }, 300); } @@ -1036,18 +1261,8 @@ class WebRTCChat { this.peerConnection.addTrack(track, this.localStream); }); - // 添加初始视频流(默认视频) - try { - const initialVideoStream = await this.createVideoStream(this.defaultVideo); - const videoTrack = initialVideoStream.getVideoTracks()[0]; - if (videoTrack) { - this.videoSender = this.peerConnection.addTrack(videoTrack, initialVideoStream); - this.currentVideoStream = initialVideoStream; - this.logMessage('初始视频轨道已添加到WebRTC连接', 'success'); - } - } catch (error) { - this.logMessage(`添加初始视频轨道失败: ${error.message}`, 'error'); - } + // 移除视频轨道添加逻辑,只使用音频进行WebRTC通信 + // 视频将直接在本地切换,不通过WebRTC传输 // 处理远程流 this.peerConnection.ontrack = (event) => { diff --git a/src/minimaxi_stream.js b/src/minimaxi_stream.js index ecc757b..3b8a5ce 100644 --- a/src/minimaxi_stream.js +++ b/src/minimaxi_stream.js @@ -68,10 +68,10 @@ async function processAudioQueue() { const sayName = '8-4-sh' const targetVideo = window.webrtcApp.interactionVideo // 如果是第一个音频片段,触发视频切换 - if (isFirstChunk && sayName != window.webrtcApp.currentVideoTag && window.webrtcApp && window.webrtcApp.switchVideoWithReplaceTrack) { + if (isFirstChunk && sayName != window.webrtcApp.currentVideoTag && window.webrtcApp && window.webrtcApp.switchVideoStream) { try { console.log('--------------触发视频切换:', sayName); - window.webrtcApp.switchVideoWithReplaceTrack(targetVideo, 'audio', '8-4-sh'); + window.webrtcApp.switchVideoStream(targetVideo, 'audio', '8-4-sh'); isFirstChunk = false; window.webrtcApp.currentVideoTag = sayName; } catch (error) { @@ -86,13 +86,18 @@ async function processAudioQueue() { } isProcessingQueue = false; + + // 等待当前音频播放完成后再切换回默认视频 + while (isPlaying) { + await new Promise(resolve => setTimeout(resolve, 100)); + } + const text = 'default' - // await new Promise(resolve => setTimeout(resolve, 500)); console.log("音频结束------------------------:", window.webrtcApp.currentVideoTag, isPlaying) if (window.webrtcApp.currentVideoTag != text) { isFirstChunk = true window.webrtcApp.currentVideoTag = text - window.webrtcApp.switchVideoWithReplaceTrack(window.webrtcApp.defaultVideo, 'audio', text); + window.webrtcApp.switchVideoStream(window.webrtcApp.defaultVideo, 'audio', text); } console.log('音频队列处理完成'); } diff --git a/videos/8-8-hc-bd-2.mp4 b/videos/8-8-hc-bd-2.mp4 new file mode 100644 index 0000000..0eb440c Binary files /dev/null and b/videos/8-8-hc-bd-2.mp4 differ