Compare commits
	
		
			2 Commits
		
	
	
		
			ddc49e1a50
			...
			15374be668
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 15374be668 | |||
| 3edca0d5a7 | 
@ -451,7 +451,7 @@
 | 
			
		||||
                    <div class="avatar-container" id="avatarContainer">
 | 
			
		||||
                        <div class="avatar" id="avatar">
 | 
			
		||||
                            <!-- 使用相对路径引用图片 -->
 | 
			
		||||
                            <img src="./tx.png" alt="头像" onerror="this.style.display='none'; this.parentElement.innerHTML='AI';">
 | 
			
		||||
                            <img src="./tx.png" alt="头像" onerror="this.style.display='none'; this.parentElement.innerHTML='一和零';">
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <!-- <div class="avatar-name">AI助手</div> -->
 | 
			
		||||
                    </div>
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										231
									
								
								src/index.js
									
									
									
									
									
								
							
							
						
						
									
										231
									
								
								src/index.js
									
									
									
									
									
								
							@ -9,8 +9,6 @@ class WebRTCChat {
 | 
			
		||||
    constructor() {
 | 
			
		||||
        console.log('WebRTCChat 构造函数开始执行');
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        
 | 
			
		||||
        // 初始化历史消息(异步)
 | 
			
		||||
        this.initializeHistory();
 | 
			
		||||
        
 | 
			
		||||
@ -30,6 +28,10 @@ class WebRTCChat {
 | 
			
		||||
        this.precreatedStreams = new Map(); // 预创建的视频流
 | 
			
		||||
        this.importantVideos = ['d-3s.mp4', 's-1.mp4']; // 重要视频列表
 | 
			
		||||
        this.isInitialized = false;
 | 
			
		||||
        // 添加视频加载状态标志
 | 
			
		||||
        this.isVideoReady = false;
 | 
			
		||||
        this.isDefaultVideoLoaded = false;
 | 
			
		||||
        this.retryCount = 0; // 添加重试计数器
 | 
			
		||||
 | 
			
		||||
        // 添加视频相关属性
 | 
			
		||||
        this.videoSender = null; // WebRTC视频发送器
 | 
			
		||||
@ -63,20 +65,22 @@ class WebRTCChat {
 | 
			
		||||
 | 
			
		||||
        console.log('WebRTC 聊天应用初始化完成');
 | 
			
		||||
        
 | 
			
		||||
        this.initializeElements();
 | 
			
		||||
        this.initializeSocket();
 | 
			
		||||
        this.initializeElements();
 | 
			
		||||
        this.loadVideoMapping();
 | 
			
		||||
        this.loadVideoList();
 | 
			
		||||
        this.loadDefaultVideo();
 | 
			
		||||
        
 | 
			
		||||
        // 只预加载视频资源,不显示视频
 | 
			
		||||
        this.preloadVideoResources();
 | 
			
		||||
        this.bindEvents();
 | 
			
		||||
 | 
			
		||||
        // 在初始化完成后预加载常用视频
 | 
			
		||||
        setTimeout(() => {
 | 
			
		||||
            this.logMessage('开始预加载常用视频...', 'info');
 | 
			
		||||
            this.preloadCommonVideos().catch(error => {
 | 
			
		||||
                this.logMessage(`预加载过程出错: ${error.message}`, 'error');
 | 
			
		||||
            });
 | 
			
		||||
        }, 500); // 延迟2秒开始预加载,避免影响
 | 
			
		||||
        // setTimeout(() => {
 | 
			
		||||
        //     this.logMessage('开始预加载常用视频...', 'info');
 | 
			
		||||
        //     this.preloadCommonVideos().catch(error => {
 | 
			
		||||
        //         this.logMessage(`预加载过程出错: ${error.message}`, 'error');
 | 
			
		||||
        //     });
 | 
			
		||||
        // }, 500);
 | 
			
		||||
 | 
			
		||||
        // 预创建重要视频流
 | 
			
		||||
        setTimeout(() => {
 | 
			
		||||
@ -88,6 +92,30 @@ class WebRTCChat {
 | 
			
		||||
        window.webrtcApp = this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 新增方法:预加载视频资源
 | 
			
		||||
    async preloadVideoResources() {
 | 
			
		||||
        try {
 | 
			
		||||
            await this.loadDefaultVideo();
 | 
			
		||||
            // 预创建视频流但不设置到video元素
 | 
			
		||||
            const defaultStream = await this.createVideoStreamOptimized(this.defaultVideo);
 | 
			
		||||
            this.precreatedStreams.set(this.defaultVideo, defaultStream);
 | 
			
		||||
            
 | 
			
		||||
            this.isVideoReady = true;
 | 
			
		||||
            this.isDefaultVideoLoaded = true;
 | 
			
		||||
            
 | 
			
		||||
            // 启用开始通话按钮
 | 
			
		||||
            // if (this.startButton) {
 | 
			
		||||
            //     this.startButton.disabled = false;
 | 
			
		||||
            //     this.startButton.style.opacity = '1';
 | 
			
		||||
            // }
 | 
			
		||||
            
 | 
			
		||||
            console.log('视频资源预加载完成,可以开始通话', 'success');
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            this.logMessage(`视频资源预加载失败: ${error.message}`, 'error');
 | 
			
		||||
            this.isVideoReady = false;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    initializeElements() {
 | 
			
		||||
        // 视频元素
 | 
			
		||||
        this.localVideo = document.getElementById('localVideo');
 | 
			
		||||
@ -111,6 +139,14 @@ class WebRTCChat {
 | 
			
		||||
        this.stopButton = document.getElementById('stopButton');
 | 
			
		||||
        this.muteButton = document.getElementById('muteButton');
 | 
			
		||||
        this.sendTextButton = document.getElementById('sendTextButton');
 | 
			
		||||
        
 | 
			
		||||
        // 初始状态下禁用开始通话按钮,直到视频加载完成
 | 
			
		||||
        if (this.startButton) {
 | 
			
		||||
            this.startButton.disabled = true;
 | 
			
		||||
            this.startButton.style.opacity = '0.5';
 | 
			
		||||
            // this.startButton.title = '加载中,请稍候...';
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // this.startVoiceButton = document.getElementById('startVoiceButton');
 | 
			
		||||
        // this.stopVoiceButton = document.getElementById('stopVoiceButton');
 | 
			
		||||
        // this.defaultVideoButton = document.getElementById('defaultVideoButton');
 | 
			
		||||
@ -165,7 +201,7 @@ class WebRTCChat {
 | 
			
		||||
        // 通话开始处理
 | 
			
		||||
        this.socket.on('call-started', (data) => {
 | 
			
		||||
            console.log('通话已开始', 'success');
 | 
			
		||||
            this.startDefaultVideoStream();
 | 
			
		||||
            // 移除这里的视频流启动,因为现在在startCall中处理
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // 场景切换处理
 | 
			
		||||
@ -204,7 +240,7 @@ class WebRTCChat {
 | 
			
		||||
            this.videoMapping = data.mapping;
 | 
			
		||||
            this.interactionVideo = data.mapping['8-4-sh'];
 | 
			
		||||
            this.defaultVideo = data.mapping["default"];
 | 
			
		||||
            this.logMessage('视频映射加载成功', 'success');
 | 
			
		||||
            console.log('映射加载成功', 'success');
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            this.logMessage('加载视频映射失败: ' + error.message, 'error');
 | 
			
		||||
        }
 | 
			
		||||
@ -264,18 +300,35 @@ class WebRTCChat {
 | 
			
		||||
 | 
			
		||||
    async startDefaultVideoStream() {
 | 
			
		||||
        try {
 | 
			
		||||
            this.logMessage('开始创建默认视频流', 'info');
 | 
			
		||||
            console.log('开始创建默认视频流', 'info');
 | 
			
		||||
            
 | 
			
		||||
            // 显示视频元素
 | 
			
		||||
            if (this.recordedVideo) {
 | 
			
		||||
                this.recordedVideo.style.display = 'block';
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            // 添加加载状态
 | 
			
		||||
            this.recordedVideo.classList.add('loading');
 | 
			
		||||
            
 | 
			
		||||
            // 创建默认视频的MediaStream
 | 
			
		||||
            const defaultStream = this.precreatedStreams.get(this.defaultVideo);
 | 
			
		||||
            let defaultStream = this.precreatedStreams.get(this.defaultVideo);
 | 
			
		||||
            
 | 
			
		||||
            // 检查流是否有效,如果无效则重新创建
 | 
			
		||||
            if (!defaultStream || defaultStream.getTracks().length === 0 || 
 | 
			
		||||
                defaultStream.getTracks().some(track => track.readyState === 'ended')) {
 | 
			
		||||
                console.log('预创建流无效,重新创建默认视频流');
 | 
			
		||||
                try {
 | 
			
		||||
                    defaultStream = await this.createVideoStreamOptimized(this.defaultVideo);
 | 
			
		||||
                    this.precreatedStreams.set(this.defaultVideo, defaultStream);
 | 
			
		||||
                } catch (createError) {
 | 
			
		||||
                    throw new Error(`重新创建默认视频流失败: ${createError.message}`);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            // 等待流稳定
 | 
			
		||||
            await new Promise(resolve => setTimeout(resolve, 500));
 | 
			
		||||
            await new Promise(resolve => setTimeout(resolve, 1000));
 | 
			
		||||
            
 | 
			
		||||
            // 检查流是否有效
 | 
			
		||||
            // 再次检查流是否有效
 | 
			
		||||
            if (!defaultStream || defaultStream.getTracks().length === 0) {
 | 
			
		||||
                throw new Error('默认视频流创建失败');
 | 
			
		||||
            }
 | 
			
		||||
@ -311,20 +364,39 @@ class WebRTCChat {
 | 
			
		||||
            // 确保视频开始播放
 | 
			
		||||
            try {
 | 
			
		||||
                await this.recordedVideo.play();
 | 
			
		||||
                this.logMessage('默认视频开始播放', 'success');
 | 
			
		||||
                // this.logMessage('默认视频开始播放', 'success');
 | 
			
		||||
                
 | 
			
		||||
                // 移除加载状态,添加播放状态
 | 
			
		||||
                this.recordedVideo.classList.remove('loading');
 | 
			
		||||
                this.recordedVideo.classList.add('playing');
 | 
			
		||||
            } catch (playError) {
 | 
			
		||||
                this.logMessage(`默认视频播放失败: ${playError.message}`, 'error');
 | 
			
		||||
                // this.logMessage(`默认视频播放失败: ${playError.message}`, 'error');
 | 
			
		||||
                this.recordedVideo.classList.remove('loading');
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            this.logMessage('默认视频流创建成功', 'success');
 | 
			
		||||
            // 再次检查视频是否准备就绪
 | 
			
		||||
            // this.startButton.disabled = false;
 | 
			
		||||
            
 | 
			
		||||
            console.log('默认流创建成功', 'success');
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            this.logMessage('创建默认视频流失败: ' + error.message, 'error');
 | 
			
		||||
            console.log('创建默认视频流失败: ' + error.message, 'error');
 | 
			
		||||
 | 
			
		||||
            // 隐藏视频元素
 | 
			
		||||
            if (this.recordedVideo) {
 | 
			
		||||
                this.recordedVideo.style.display = 'none';
 | 
			
		||||
            }
 | 
			
		||||
            this.recordedVideo.classList.remove('loading');
 | 
			
		||||
            
 | 
			
		||||
            // 添加重试机制
 | 
			
		||||
            if (!this.retryCount) this.retryCount = 0;
 | 
			
		||||
            if (this.retryCount < 2) {
 | 
			
		||||
                this.retryCount++;
 | 
			
		||||
                console.log(`尝试重新创建默认视频流 (${this.retryCount}/2)`);
 | 
			
		||||
                setTimeout(() => this.startDefaultVideoStream(), 1000);
 | 
			
		||||
            } else {
 | 
			
		||||
                this.retryCount = 0;
 | 
			
		||||
                console.log('默认视频流创建失败,已达到最大重试次数', 'error');
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -355,7 +427,7 @@ class WebRTCChat {
 | 
			
		||||
    async precreateImportantVideos() {
 | 
			
		||||
        if (this.isInitialized) return;
 | 
			
		||||
        
 | 
			
		||||
        this.logMessage('开始预创建重要视频流...', 'info');
 | 
			
		||||
        console.log('开始预创建重要流...', 'info');
 | 
			
		||||
        
 | 
			
		||||
        for (const videoFile of [this.interactionVideo, this.defaultVideo]) {
 | 
			
		||||
            try {
 | 
			
		||||
@ -366,8 +438,14 @@ class WebRTCChat {
 | 
			
		||||
                this.logMessage(`预创建视频流失败: ${videoFile} - ${error.message}`, 'error');
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        // 启用开始通话按钮
 | 
			
		||||
            if (this.startButton) {
 | 
			
		||||
                this.startButton.disabled = false;
 | 
			
		||||
                this.startButton.style.opacity = '1';
 | 
			
		||||
            }
 | 
			
		||||
        
 | 
			
		||||
        this.isInitialized = true;
 | 
			
		||||
        
 | 
			
		||||
        this.logMessage('重要视频流预创建完成', 'success');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -988,8 +1066,14 @@ class WebRTCChat {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bindEvents() {
 | 
			
		||||
        // 开始通话按钮
 | 
			
		||||
        this.startButton.onclick = () => this.startCall();
 | 
			
		||||
        // 开始通话按钮 - 添加视频准备状态检查
 | 
			
		||||
        this.startButton.onclick = () => {
 | 
			
		||||
            if (!this.isVideoReady) {
 | 
			
		||||
                this.logMessage('还在加载中,请稍候...', 'warning');
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            this.startCall();
 | 
			
		||||
        };
 | 
			
		||||
        
 | 
			
		||||
        // 停止通话按钮 - 改为调用 userDisconnect
 | 
			
		||||
        this.stopButton.onclick = () => this.userDisconnect();
 | 
			
		||||
@ -1042,13 +1126,27 @@ class WebRTCChat {
 | 
			
		||||
 | 
			
		||||
    async startCall() {
 | 
			
		||||
        try {
 | 
			
		||||
            // 立即隐藏开始通话按钮
 | 
			
		||||
            // 检查所有必要条件
 | 
			
		||||
            if (!this.isVideoReady || !this.isDefaultVideoLoaded) {
 | 
			
		||||
                this.logMessage('视频资源尚未准备就绪,请稍候...', 'warning');
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (!this.socket || !this.socket.connected) {
 | 
			
		||||
                this.logMessage('网络连接未就绪,请稍候...', 'warning');
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            // 隐藏开始通话按钮
 | 
			
		||||
            this.startButton.style.display = 'none';
 | 
			
		||||
            // 显示等待连接提示
 | 
			
		||||
            this.showConnectionWaiting();
 | 
			
		||||
            // 切换到通话中图标
 | 
			
		||||
            this.switchToCallingIcon();
 | 
			
		||||
            
 | 
			
		||||
            // 现在才开始显示视频
 | 
			
		||||
            await this.startDefaultVideoStream();
 | 
			
		||||
            
 | 
			
		||||
            // 添加更详细的错误处理
 | 
			
		||||
            console.log('开始请求麦克风权限...');
 | 
			
		||||
            this.localStream = await navigator.mediaDevices.getUserMedia({
 | 
			
		||||
@ -1061,6 +1159,7 @@ class WebRTCChat {
 | 
			
		||||
            await this.startVoiceRecording();
 | 
			
		||||
            
 | 
			
		||||
            this.startButton.disabled = true;
 | 
			
		||||
            this.startButton.style.opacity = '0.5'
 | 
			
		||||
            this.stopButton.disabled = false;
 | 
			
		||||
            
 | 
			
		||||
            // 显示结束通话按钮
 | 
			
		||||
@ -1085,17 +1184,18 @@ class WebRTCChat {
 | 
			
		||||
            this.socket.emit('call-started');
 | 
			
		||||
            
 | 
			
		||||
            // 开始播放当前场景的默认视频
 | 
			
		||||
            await this.precreateImportantVideos();
 | 
			
		||||
            // await this.precreateImportantVideos();
 | 
			
		||||
 | 
			
		||||
            // 隐藏等待连接提示
 | 
			
		||||
            this.hideConnectionWaiting();
 | 
			
		||||
            
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            this.logMessage(`开始通话失败: ${error.message}`, 'error');
 | 
			
		||||
            // 恢复开始通话按钮
 | 
			
		||||
            this.startButton.style.display = 'block';
 | 
			
		||||
            // 如果出错,隐藏等待连接提示并恢复到默认图标
 | 
			
		||||
            this.hideConnectionWaiting();
 | 
			
		||||
            this.switchToDefaultIcon();
 | 
			
		||||
            this.logMessage('无法访问麦克风: ' + error.message, 'error');
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -1133,21 +1233,41 @@ class WebRTCChat {
 | 
			
		||||
        this.stopButton.style.display = 'none';
 | 
			
		||||
        this.stopButton.disabled = true;
 | 
			
		||||
        
 | 
			
		||||
        // 隐藏视频元素
 | 
			
		||||
        if (this.recordedVideo) {
 | 
			
		||||
            this.recordedVideo.style.display = 'none';
 | 
			
		||||
            this.recordedVideo.srcObject = null;
 | 
			
		||||
        }
 | 
			
		||||
        if (this.recordedVideoBuffer) {
 | 
			
		||||
            this.recordedVideoBuffer.style.display = 'none';
 | 
			
		||||
            this.recordedVideoBuffer.srcObject = null;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // 显示开始通话按钮
 | 
			
		||||
        this.startButton.disabled = true;
 | 
			
		||||
        this.startButton.style.display = 'block';
 | 
			
		||||
        this.startButton.disabled = false;
 | 
			
		||||
        this.startButton.style.opacity = '0.5';
 | 
			
		||||
        
 | 
			
		||||
        // 移除页面刷新,保持websocket连接
 | 
			
		||||
        // setTimeout(() => {
 | 
			
		||||
        //     window.location.reload();
 | 
			
		||||
        // }, 300);
 | 
			
		||||
        
 | 
			
		||||
        setTimeout(() => {
 | 
			
		||||
            // 显示头像,隐藏视频
 | 
			
		||||
            if (this.videoContainer) {
 | 
			
		||||
                this.videoContainer.classList.remove('calling');
 | 
			
		||||
            }
 | 
			
		||||
        }, 300);
 | 
			
		||||
        // 清理视频缓存和预创建流
 | 
			
		||||
        this.clearVideoCache();
 | 
			
		||||
        
 | 
			
		||||
        // setTimeout(() => {
 | 
			
		||||
        //     // 显示头像,隐藏视频
 | 
			
		||||
        //     if (this.videoContainer) {
 | 
			
		||||
        //         this.videoContainer.classList.remove('calling');
 | 
			
		||||
        //     }
 | 
			
		||||
            
 | 
			
		||||
        //     // 重新初始化重要视频流
 | 
			
		||||
        //     this.precreateImportantVideos().then(() => {
 | 
			
		||||
        //         // 重新启动默认视频流
 | 
			
		||||
        //         this.startDefaultVideoStream();
 | 
			
		||||
        //     });
 | 
			
		||||
        // }, 300);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 新增:用户主动断开连接的方法
 | 
			
		||||
@ -1155,16 +1275,36 @@ class WebRTCChat {
 | 
			
		||||
        // 发送用户关闭连接事件到后端
 | 
			
		||||
        if (this.socket && this.socket.connected) {
 | 
			
		||||
            this.socket.emit('user-disconnect');
 | 
			
		||||
            
 | 
			
		||||
            // 等待服务器确认断开后再刷新
 | 
			
		||||
            this.socket.on('disconnect', () => {
 | 
			
		||||
                console.log('WebSocket已断开,准备刷新页面...');
 | 
			
		||||
                setTimeout(() => {
 | 
			
		||||
                    window.location.reload();
 | 
			
		||||
                }, 500);
 | 
			
		||||
            });
 | 
			
		||||
            
 | 
			
		||||
            // 主动断开连接
 | 
			
		||||
            setTimeout(() => {
 | 
			
		||||
                this.socket.disconnect();
 | 
			
		||||
            }, 200);
 | 
			
		||||
            
 | 
			
		||||
            // 兜底机制:如果2秒内没有正常断开,强制刷新
 | 
			
		||||
            setTimeout(() => {
 | 
			
		||||
                if (this.socket && this.socket.connected) {
 | 
			
		||||
                    console.log('WebSocket断开超时,强制刷新页面');
 | 
			
		||||
                    window.location.reload();
 | 
			
		||||
                }
 | 
			
		||||
            }, 2000);
 | 
			
		||||
        } else {
 | 
			
		||||
            // 如果socket已经断开,直接刷新
 | 
			
		||||
            setTimeout(() => {
 | 
			
		||||
                window.location.reload();
 | 
			
		||||
            }, 100);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // 调用停止通话
 | 
			
		||||
        this.stopCall();
 | 
			
		||||
        
 | 
			
		||||
        // 延迟刷新,确保服务器处理完断开逻辑
 | 
			
		||||
        console.log('用户主动断开,300ms后刷新页面清除缓存...');
 | 
			
		||||
        setTimeout(() => {
 | 
			
		||||
            window.location.reload();
 | 
			
		||||
        }, 300);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 清除视频缓存的方法
 | 
			
		||||
@ -1194,6 +1334,19 @@ class WebRTCChat {
 | 
			
		||||
        // 清除视频流映射缓存
 | 
			
		||||
        this.videoStreams.clear();
 | 
			
		||||
        
 | 
			
		||||
        // 清除预创建的视频流
 | 
			
		||||
        if (this.precreatedStreams) {
 | 
			
		||||
            this.precreatedStreams.forEach((stream, videoFile) => {
 | 
			
		||||
                if (stream) {
 | 
			
		||||
                    stream.getTracks().forEach(track => track.stop());
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            this.precreatedStreams.clear();
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // 重置初始化状态,允许重新预创建
 | 
			
		||||
        this.isInitialized = false;
 | 
			
		||||
        
 | 
			
		||||
        // 重置视频相关状态
 | 
			
		||||
        this.currentVideo = null;
 | 
			
		||||
        this.activeVideoElement = 'main';
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user