第14章 故障排除

在本章中,我们将解决使用RecordRTC过程中遇到的常见问题,包括权限问题、兼容性问题和性能问题等。

14.1 权限相关问题

处理摄像头和麦克风权限相关的常见问题:

权限问题解决方案

// 权限问题诊断和解决
class PermissionTroubleshooter {
    // 检查权限状态
    static async checkPermissions() {
        try {
            // 检查媒体设备权限
            const devices = await navigator.mediaDevices.enumerateDevices();
            const hasVideo = devices.some(device => device.kind === 'videoinput');
            const hasAudio = devices.some(device => device.kind === 'audioinput');
            
            console.log('设备权限状态:', {
                videoDevices: hasVideo,
                audioDevices: hasAudio,
                totalDevices: devices.length
            });
            
            return { hasVideo, hasAudio };
        } catch (error) {
            console.error('检查权限失败:', error);
            return { hasVideo: false, hasAudio: false };
        }
    }
    
    // 请求媒体权限
    static async requestMediaPermissions(constraints = { video: true, audio: true }) {
        try {
            console.log('正在请求媒体权限...');
            const stream = await navigator.mediaDevices.getUserMedia(constraints);
            
            console.log('权限获取成功');
            
            // 立即停止流以避免占用
            stream.getTracks().forEach(track => track.stop());
            
            return { success: true, stream: null };
        } catch (error) {
            console.error('权限请求失败:', error);
            
            // 根据错误类型提供解决方案
            switch (error.name) {
                case 'NotAllowedError':
                    return {
                        success: false,
                        error: '用户拒绝了权限请求',
                        solution: '请手动授予权限:点击地址栏左侧的锁图标,然后允许摄像头和麦克风访问'
                    };
                    
                case 'NotFoundError':
                    return {
                        success: false,
                        error: '未找到媒体设备',
                        solution: '请检查摄像头和麦克风是否正确连接,或尝试其他设备'
                    };
                    
                case 'NotReadableError':
                    return {
                        success: false,
                        error: '设备无法读取',
                        solution: '设备可能被其他应用占用,请关闭其他使用摄像头的应用'
                    };
                    
                case 'OverconstrainedError':
                    return {
                        success: false,
                        error: '约束条件无法满足',
                        solution: '尝试降低分辨率或帧率要求'
                    };
                    
                case 'SecurityError':
                    return {
                        success: false,
                        error: '安全错误',
                        solution: '请确保在HTTPS环境下运行,或在localhost上测试'
                    };
                    
                default:
                    return {
                        success: false,
                        error: '未知错误: ' + error.message,
                        solution: '请检查控制台错误信息,或尝试刷新页面后重试'
                    };
            }
        }
    }
    
    // 权限恢复工具
    static async recoverPermissions() {
        // 尝试不同组合的权限请求
        const permissionCombinations = [
            { video: true, audio: true },  // 音视频
            { video: true, audio: false }, // 仅视频
            { video: false, audio: true }, // 仅音频
            { 
                video: { 
                    width: { ideal: 640 }, 
                    height: { ideal: 480 } 
                }, 
                audio: true 
            } // 低分辨率
        ];
        
        for (let i = 0; i < permissionCombinations.length; i++) {
            console.log(`尝试权限组合 ${i + 1}/${permissionCombinations.length}`);
            const result = await this.requestMediaPermissions(permissionCombinations[i]);
            
            if (result.success) {
                console.log('权限恢复成功');
                return { success: true, constraints: permissionCombinations[i] };
            }
            
            console.log('当前组合失败:', result.error);
        }
        
        return { success: false, error: '所有权限组合都失败' };
    }
}

// 使用示例
async function troubleshootPermissions() {
    // 检查当前权限状态
    const permissionStatus = await PermissionTroubleshooter.checkPermissions();
    console.log('当前权限状态:', permissionStatus);
    
    // 如果没有权限,尝试恢复
    if (!permissionStatus.hasVideo || !permissionStatus.hasAudio) {
        const recoveryResult = await PermissionTroubleshooter.recoverPermissions();
        console.log('权限恢复结果:', recoveryResult);
        
        if (recoveryResult.success) {
            console.log('可以使用以下约束开始录制:', recoveryResult.constraints);
        }
    }
}

14.2 兼容性问题

解决不同浏览器和设备的兼容性问题:

兼容性问题解决方案

// 浏览器兼容性检测和解决方案
class CompatibilityTroubleshooter {
    // 检测浏览器支持情况
    static detectBrowserSupport() {
        const supportInfo = {
            isRecordingSupported: RecordRTC.isRecordingSupported(),
            browser: this.getBrowserInfo(),
            mediaDevices: !!navigator.mediaDevices,
            getUserMedia: !!navigator.mediaDevices?.getUserMedia,
            getDisplayMedia: !!navigator.mediaDevices?.getDisplayMedia,
            webAudio: !!window.AudioContext || !!window.webkitAudioContext
        };
        
        console.log('浏览器支持信息:', supportInfo);
        return supportInfo;
    }
    
    // 获取浏览器信息
    static getBrowserInfo() {
        const userAgent = navigator.userAgent;
        const info = {
            name: 'unknown',
            version: 'unknown',
            isMobile: /Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(userAgent)
        };
        
        // 检测浏览器类型和版本
        if (userAgent.includes('Chrome') && !userAgent.includes('Edg')) {
            info.name = 'Chrome';
            const match = userAgent.match(/Chrome\/(\d+)/);
            if (match) info.version = match[1];
        } else if (userAgent.includes('Firefox')) {
            info.name = 'Firefox';
            const match = userAgent.match(/Firefox\/(\d+)/);
            if (match) info.version = match[1];
        } else if (userAgent.includes('Safari') && !userAgent.includes('Chrome')) {
            info.name = 'Safari';
            const match = userAgent.match(/Version\/(\d+)/);
            if (match) info.version = match[1];
        } else if (userAgent.includes('Edg')) {
            info.name = 'Edge';
            const match = userAgent.match(/Edg\/(\d+)/);
            if (match) info.version = match[1];
        }
        
        return info;
    }
    
    // 获取兼容的MIME类型
    static getCompatibleMimeTypes() {
        const mimeTypes = [
            'video/webm;codecs=vp9',
            'video/webm;codecs=vp8',
            'video/webm',
            'video/mp4',
            'audio/webm',
            'audio/wav',
            'audio/mp3'
        ];
        
        const supported = [];
        
        mimeTypes.forEach(mimeType => {
            try {
                const isSupported = MediaRecorder.isTypeSupported(mimeType);
                if (isSupported) {
                    supported.push(mimeType);
                }
            } catch (e) {
                // 忽略不支持的检查
            }
        });
        
        console.log('支持的MIME类型:', supported);
        return supported;
    }
    
    // 获取浏览器特定的解决方案
    static getBrowserSpecificSolution() {
        const browserInfo = this.getBrowserInfo();
        const solutions = {
            Chrome: {
                minVersion: 49,
                issues: [
                    '确保启用WebRTC功能',
                    '检查扩展程序是否干扰',
                    '更新到最新版本'
                ]
            },
            Firefox: {
                minVersion: 52,
                issues: [
                    '检查about:config中的media.peerconnection.enabled',
                    '确保启用媒体功能',
                    '更新到最新版本'
                ]
            },
            Safari: {
                minVersion: 11,
                issues: [
                    '需要在设置中启用"请求桌面站点"功能',
                    '某些功能仅在桌面版中可用',
                    '考虑使用其他浏览器以获得更好支持'
                ]
            },
            Edge: {
                minVersion: 79, // 基于Chromium的版本
                issues: [
                    '更新到最新版本',
                    '检查企业策略设置',
                    '确保启用相关功能'
                ]
            }
        };
        
        const solution = solutions[browserInfo.name] || {
            minVersion: 0,
            issues: ['请使用现代浏览器']
        };
        
        return {
            browser: browserInfo,
            solution: solution,
            isSupported: browserInfo.version >= solution.minVersion
        };
    }
    
    // 兼容性测试
    static async runCompatibilityTest() {
        const results = {
            support: this.detectBrowserSupport(),
            mimeTypes: this.getCompatibleMimeTypes(),
            browserSolution: this.getBrowserSpecificSolution()
        };
        
        console.log('兼容性测试结果:', results);
        return results;
    }
}

// 使用示例
async function checkCompatibility() {
    const results = await CompatibilityTroubleshooter.runCompatibilityTest();
    
    if (!results.support.isRecordingSupported) {
        console.warn('当前环境不支持录制功能');
        // 提供替代方案或建议
    }
    
    if (!results.browserSolution.isSupported) {
        console.warn('浏览器版本过低,建议升级');
    }
    
    return results;
}

14.3 性能问题

诊断和解决录制过程中的性能问题:

性能问题解决方案

// 性能问题诊断和优化
class PerformanceTroubleshooter {
    // 监控录制性能
    static monitorRecordingPerformance(recorder) {
        const performanceData = {
            startTime: Date.now(),
            framesProcessed: 0,
            dataGenerated: 0,
            memoryUsage: 0
        };
        
        // 监控数据生成
        if (recorder) {
            const originalOnDataAvailable = recorder.ondataavailable;
            recorder.ondataavailable = function(blob) {
                performanceData.framesProcessed++;
                performanceData.dataGenerated += blob.size;
                
                // 监控内存使用
                if (performance.memory) {
                    performanceData.memoryUsage = performance.memory.usedJSHeapSize;
                }
                
                console.log('性能数据:', {
                    frames: performanceData.framesProcessed,
                    dataSize: performanceData.dataGenerated,
                    memory: Math.round(performanceData.memoryUsage / 1024 / 1024) + 'MB',
                    duration: Date.now() - performanceData.startTime + 'ms'
                });
                
                // 如果定义了原始回调,调用它
                if (originalOnDataAvailable) {
                    originalOnDataAvailable.call(this, blob);
                }
            };
        }
        
        return performanceData;
    }
    
    // 检测性能瓶颈
    static detectPerformanceBottlenecks() {
        const bottlenecks = [];
        
        // 检查CPU使用率(如果可用)
        if (navigator.hardwareConcurrency) {
            const cpuCores = navigator.hardwareConcurrency;
            if (cpuCores < 4) {
                bottlenecks.push({
                    type: 'CPU',
                    severity: 'medium',
                    message: `CPU核心数较少 (${cpuCores}),可能影响录制性能`
                });
            }
        }
        
        // 检查内存
        if (performance.memory) {
            const memory = performance.memory;
            const usageRatio = memory.usedJSHeapSize / memory.jsHeapSizeLimit;
            if (usageRatio > 0.8) {
                bottlenecks.push({
                    type: 'Memory',
                    severity: 'high',
                    message: '内存使用率过高,可能导致页面崩溃'
                });
            }
        }
        
        // 检查设备电池状态(移动设备)
        if (navigator.getBattery) {
            navigator.getBattery().then(battery => {
                if (battery.level < 0.2) {
                    bottlenecks.push({
                        type: 'Battery',
                        severity: 'medium',
                        message: '电池电量低,可能影响录制性能'
                    });
                }
                if (!battery.charging) {
                    bottlenecks.push({
                        type: 'Power',
                        severity: 'low',
                        message: '未充电状态,建议连接充电器'
                    });
                }
            });
        }
        
        console.log('检测到的性能瓶颈:', bottlenecks);
        return bottlenecks;
    }
    
    // 优化录制性能
    static getPerformanceOptimizationConfig(currentConfig, bottlenecks) {
        const optimizedConfig = Object.assign({}, currentConfig);
        
        // 根据瓶颈调整配置
        bottlenecks.forEach(bottleneck => {
            switch (bottleneck.type) {
                case 'CPU':
                    // 降低分辨率和帧率
                    if (optimizedConfig.video) {
                        optimizedConfig.video.width = { ideal: 640 };
                        optimizedConfig.video.height = { ideal: 480 };
                        optimizedConfig.video.frameRate = { ideal: 15 };
                    }
                    if (optimizedConfig.canvas) {
                        optimizedConfig.canvas.width = 640;
                        optimizedConfig.canvas.height = 480;
                    }
                    break;
                    
                case 'Memory':
                    // 减少缓冲区大小,增加时间切片
                    optimizedConfig.bufferSize = 2048;
                    optimizedConfig.timeSlice = 1000;
                    optimizedConfig.disableLogs = true;
                    break;
                    
                case 'Battery':
                    // 降低处理强度
                    optimizedConfig.video.frameRate = { ideal: 20 };
                    optimizedConfig.numberOfAudioChannels = 1;
                    break;
            }
        });
        
        console.log('优化后的配置:', optimizedConfig);
        return optimizedConfig;
    }
    
    // 性能测试
    static async runPerformanceTest(stream, duration = 5000) {
        return new Promise((resolve) => {
            const config = {
                type: 'video',
                mimeType: 'video/webm',
                timeSlice: 1000
            };
            
            const recorder = RecordRTC(stream, config);
            const performanceData = this.monitorRecordingPerformance(recorder);
            const bottlenecks = this.detectPerformanceBottlenecks();
            
            recorder.startRecording();
            
            // 测试指定时间后停止
            setTimeout(() => {
                recorder.stopRecording(() => {
                    const results = {
                        performanceData: performanceData,
                        bottlenecks: bottlenecks,
                        optimizedConfig: this.getPerformanceOptimizationConfig(config, bottlenecks)
                    };
                    
                    resolve(results);
                });
            }, duration);
        });
    }
}

// 使用示例
async function troubleshootPerformance(stream) {
    console.log('开始性能测试...');
    const results = await PerformanceTroubleshooter.runPerformanceTest(stream);
    
    console.log('性能测试结果:', results);
    
    if (results.bottlenecks.length > 0) {
        console.warn('发现性能瓶颈,建议使用优化配置');
    }
    
    return results;
}

14.4 常见错误解决方案

处理RecordRTC使用中的常见错误:

常见错误解决方案

// 常见错误诊断和解决方案
class ErrorTroubleshooter {
    // 错误代码映射
    static errorSolutions = {
        'NotAllowedError': {
            title: '权限被拒绝',
            description: '用户没有授予访问摄像头或麦克风的权限',
            solutions: [
                '点击地址栏左侧的锁图标,手动授予权限',
                '刷新页面后再次尝试',
                '检查浏览器设置中的权限管理',
                '确保在HTTPS环境下运行'
            ]
        },
        
        'NotFoundError': {
            title: '未找到设备',
            description: '系统中没有可用的摄像头或麦克风设备',
            solutions: [
                '检查设备是否正确连接',
                '尝试重新连接设备',
                '检查设备是否被其他应用占用',
                '尝试使用不同的设备'
            ]
        },
        
        'NotReadableError': {
            title: '设备无法读取',
            description: '无法从媒体设备读取数据',
            solutions: [
                '关闭可能占用设备的其他应用',
                '重新连接设备',
                '重启浏览器',
                '检查设备驱动程序'
            ]
        },
        
        'OverconstrainedError': {
            title: '约束条件无法满足',
            description: '请求的媒体约束条件过于严格',
            solutions: [
                '降低分辨率要求',
                '减少帧率要求',
                '放宽其他约束条件',
                '尝试不同的设备'
            ]
        },
        
        'SecurityError': {
            title: '安全错误',
            description: '由于安全限制无法访问媒体设备',
            solutions: [
                '确保在HTTPS环境下运行',
                '检查页面是否在安全上下文中',
                '在localhost上测试',
                '检查内容安全策略'
            ]
        },
        
        'TypeError': {
            title: '类型错误',
            description: '传递给RecordRTC的参数类型不正确',
            solutions: [
                '检查MediaStream对象是否有效',
                '确保配置对象格式正确',
                '验证所有参数类型',
                '查看控制台详细错误信息'
            ]
        },
        
        'InvalidStateError': {
            title: '状态错误',
            description: '在不正确的状态下调用方法',
            solutions: [
                '检查录制器的当前状态',
                '确保按正确顺序调用方法',
                '避免重复调用相同方法',
                '重新初始化录制器'
            ]
        }
    };
    
    // 诊断错误
    static diagnoseError(error) {
        const errorName = error.name || error.constructor.name;
        const solution = this.errorSolutions[errorName];
        
        if (solution) {
            return {
                error: errorName,
                title: solution.title,
                description: solution.description,
                solutions: solution.solutions,
                originalError: error
            };
        }
        
        // 未知错误
        return {
            error: errorName,
            title: '未知错误',
            description: error.message || '发生了未知错误',
            solutions: [
                '查看控制台详细错误信息',
                '检查网络连接',
                '刷新页面后重试',
                '联系技术支持'
            ],
            originalError: error
        };
    }
    
    // 显示错误解决方案
    static showSolution(diagnosis) {
        console.group(`🔧 ${diagnosis.title} (${diagnosis.error})`);
        console.log('问题描述:', diagnosis.description);
        console.log('解决方案:');
        diagnosis.solutions.forEach((solution, index) => {
            console.log(`${index + 1}. ${solution}`);
        });
        console.groupEnd();
        
        // 在页面上显示错误信息(如果需要)
        this.displayErrorOnPage(diagnosis);
    }
    
    // 在页面上显示错误
    static displayErrorOnPage(diagnosis) {
        // 创建错误显示元素
        let errorContainer = document.getElementById('error-container');
        if (!errorContainer) {
            errorContainer = document.createElement('div');
            errorContainer.id = 'error-container';
            errorContainer.style.cssText = `
                position: fixed;
                top: 20px;
                right: 20px;
                max-width: 400px;
                background: #fff;
                border: 2px solid #dc3545;
                border-radius: 8px;
                padding: 20px;
                box-shadow: 0 4px 12px rgba(0,0,0,0.15);
                z-index: 10000;
                font-family: Arial, sans-serif;
            `;
            document.body.appendChild(errorContainer);
        }
        
        // 填充错误信息
        errorContainer.innerHTML = `
            

${diagnosis.title}

${diagnosis.description}

解决方案:
    ${diagnosis.solutions.map(solution => `
  1. ${solution}
  2. ` ).join('')}
`; // 5秒后自动隐藏 setTimeout(() => { if (errorContainer.parentElement) { errorContainer.remove(); } }, 5000); } // 全局错误处理 static setupGlobalErrorHandler() { // 捕获未处理的Promise拒绝 window.addEventListener('unhandledrejection', (event) => { console.log('未处理的Promise拒绝:', event.reason); const diagnosis = this.diagnoseError(event.reason); this.showSolution(diagnosis); }); // 捕获JavaScript错误 window.addEventListener('error', (event) => { console.log('JavaScript错误:', event.error); const diagnosis = this.diagnoseError(event.error); this.showSolution(diagnosis); }); } } // 使用示例 // 设置全局错误处理 ErrorTroubleshooter.setupGlobalErrorHandler(); // 处理特定错误 function handleRecordingError(error) { const diagnosis = ErrorTroubleshooter.diagnoseError(error); ErrorTroubleshooter.showSolution(diagnosis); // 返回诊断结果供进一步处理 return diagnosis; }