第11章 事件处理

在本章中,我们将学习如何处理RecordRTC的各种事件,包括录制状态变化、数据可用和错误事件等。

11.1 录制状态事件

监听录制过程中的状态变化事件:

录制状态事件

// 录制状态变化事件处理
class RecordingEventHandler {
    constructor(recorder) {
        this.recorder = recorder;
        this.setupEventListeners();
    }
    
    setupEventListeners() {
        // 监听录制开始
        this.recorder.onstart = () => {
            console.log('录制已开始');
            this.updateUI('recording');
        };
        
        // 监听录制暂停
        this.recorder.onpause = () => {
            console.log('录制已暂停');
            this.updateUI('paused');
        };
        
        // 监听录制恢复
        this.recorder.onresume = () => {
            console.log('录制已恢复');
            this.updateUI('recording');
        };
        
        // 监听录制停止
        this.recorder.onstop = () => {
            console.log('录制已停止');
            this.updateUI('stopped');
        };
        
        // 监听录制数据可用
        this.recorder.ondataavailable = (blob) => {
            console.log('录制数据可用:', blob.size);
            this.handleDataAvailable(blob);
        };
    }
    
    updateUI(state) {
        // 更新用户界面状态
        const statusElement = document.getElementById('recordingStatus');
        if (statusElement) {
            statusElement.textContent = `录制状态: ${state}`;
        }
    }
    
    handleDataAvailable(blob) {
        // 处理可用的录制数据
        console.log('接收到录制数据块');
        
        // 可以实时上传或处理
        // this.uploadChunk(blob);
    }
}

11.2 数据可用事件

使用timeSlice配置处理实时数据事件:

数据可用事件

// 数据可用事件处理
var options = {
    type: 'video',
    mimeType: 'video/webm',
    timeSlice: 1000, // 每秒触发一次
    
    // 数据可用回调
    ondataavailable: function(blob) {
        console.log('接收到数据块:', blob.size, 'bytes');
        
        // 实时处理数据块
        handleDataChunk(blob);
    },
    
    // 录制开始回调
    onstart: function() {
        console.log('录制开始');
        updateRecordingUI(true);
    },
    
    // 录制停止回调
    onstop: function() {
        console.log('录制停止');
        updateRecordingUI(false);
    }
};

// 处理数据块
function handleDataChunk(blob) {
    // 实时上传数据块
    uploadDataChunk(blob)
        .then(response => {
            console.log('数据块上传成功');
        })
        .catch(error => {
            console.error('数据块上传失败:', error);
        });
}

// 上传数据块
function uploadDataChunk(blob) {
    var formData = new FormData();
    formData.append('chunk', blob);
    formData.append('timestamp', Date.now());
    
    return fetch('/api/upload-chunk', {
        method: 'POST',
        body: formData
    });
}

// 更新录制UI
function updateRecordingUI(isRecording) {
    var button = document.getElementById('recordButton');
    button.textContent = isRecording ? '停止录制' : '开始录制';
    button.onclick = isRecording ? stopRecording : startRecording;
}

11.3 错误事件处理

处理录制过程中可能出现的错误:

错误事件处理

// 错误事件处理
class RecordingErrorhandler {
    constructor(recorder) {
        this.recorder = recorder;
        this.setupErrorHandling();
    }
    
    setupErrorHandling() {
        // 设置错误处理回调
        this.recorder.onerror = (error) => {
            console.error('录制过程中发生错误:', error);
            this.handleError(error);
        };
        
        // 监听媒体流错误
        if (this.recorder.stream) {
            this.recorder.stream.addEventListener('error', (error) => {
                console.error('媒体流错误:', error);
                this.handleStreamError(error);
            });
        }
    }
    
    handleError(error) {
        // 根据错误类型处理
        switch (error.name) {
            case 'SecurityError':
                this.showErrorMessage('安全错误:请确保在HTTPS环境下运行');
                break;
            case 'NotSupportedError':
                this.showErrorMessage('不支持错误:浏览器不支持此功能');
                break;
            case 'InvalidStateError':
                this.showErrorMessage('状态错误:录制器状态异常');
                break;
            case 'OverconstrainedError':
                this.showErrorMessage('约束错误:无法满足媒体约束条件');
                break;
            default:
                this.showErrorMessage('未知错误:' + error.message);
        }
        
        // 记录错误日志
        this.logError(error);
    }
    
    handleStreamError(error) {
        console.error('媒体流错误:', error);
        this.showErrorMessage('媒体流错误:' + error.message);
    }
    
    showErrorMessage(message) {
        // 显示错误消息给用户
        const errorElement = document.getElementById('errorMessage');
        if (errorElement) {
            errorElement.textContent = message;
            errorElement.style.display = 'block';
            
            // 3秒后自动隐藏
            setTimeout(() => {
                errorElement.style.display = 'none';
            }, 3000);
        }
    }
    
    logError(error) {
        // 发送错误日志到服务器
        fetch('/api/log-error', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({
                error: error.message,
                name: error.name,
                stack: error.stack,
                timestamp: new Date().toISOString()
            })
        });
    }
}

11.4 媒体设备事件

处理媒体设备相关事件:

媒体设备事件

// 媒体设备事件处理
class MediaDeviceEventHandler {
    constructor() {
        this.setupDeviceEvents();
    }
    
    setupDeviceEvents() {
        // 监听设备变化
        navigator.mediaDevices.addEventListener('devicechange', () => {
            console.log('媒体设备发生变化');
            this.handleDeviceChange();
        });
        
        // 监听连接状态变化
        if (navigator.connection) {
            navigator.connection.addEventListener('change', () => {
                console.log('网络连接状态变化');
                this.handleConnectionChange();
            });
        }
    }
    
    async handleDeviceChange() {
        try {
            // 重新枚举设备
            const devices = await navigator.mediaDevices.enumerateDevices();
            const videoDevices = devices.filter(d => d.kind === 'videoinput');
            const audioDevices = devices.filter(d => d.kind === 'audioinput');
            
            console.log('视频设备数量:', videoDevices.length);
            console.log('音频设备数量:', audioDevices.length);
            
            // 更新设备选择UI
            this.updateDeviceSelectors(videoDevices, audioDevices);
        } catch (error) {
            console.error('枚举设备失败:', error);
        }
    }
    
    handleConnectionChange() {
        const connection = navigator.connection;
        console.log('网络类型:', connection.effectiveType);
        console.log('下行速度:', connection.downlink, 'Mbps');
        console.log('往返时间:', connection.rtt, 'ms');
        
        // 根据网络状况调整录制质量
        this.adjustRecordingQuality(connection);
    }
    
    updateDeviceSelectors(videoDevices, audioDevices) {
        // 更新视频设备选择器
        const videoSelect = document.getElementById('videoDeviceSelect');
        if (videoSelect) {
            videoSelect.innerHTML = '';
            videoDevices.forEach((device, index) => {
                const option = document.createElement('option');
                option.value = device.deviceId;
                option.textContent = device.label || `摄像头 ${index + 1}`;
                videoSelect.appendChild(option);
            });
        }
        
        // 更新音频设备选择器
        const audioSelect = document.getElementById('audioDeviceSelect');
        if (audioSelect) {
            audioSelect.innerHTML = '';
            audioDevices.forEach((device, index) => {
                const option = document.createElement('option');
                option.value = device.deviceId;
                option.textContent = device.label || `麦克风 ${index + 1}`;
                audioSelect.appendChild(option);
            });
        }
    }
    
    adjustRecordingQuality(connection) {
        // 根据网络状况调整录制配置
        const config = {};
        
        if (connection.effectiveType === 'slow-2g' || connection.effectiveType === '2g') {
            // 低速网络,降低质量
            config.video = { width: 640, height: 480 };
            config.bitrate = 512 * 1024; // 0.5Mbps
        } else if (connection.effectiveType === '3g') {
            // 中速网络,中等质量
            config.video = { width: 1280, height: 720 };
            config.bitrate = 1024 * 1024; // 1Mbps
        } else {
            // 高速网络,高质量
            config.video = { width: 1920, height: 1080 };
            config.bitrate = 2048 * 1024; // 2Mbps
        }
        
        console.log('调整录制配置:', config);
        return config;
    }
}

11.5 自定义事件系统

创建自定义事件系统来扩展RecordRTC事件处理:

自定义事件系统

// 自定义事件系统
class CustomEventSystem {
    constructor() {
        this.events = {};
    }
    
    // 订阅事件
    on(eventName, callback) {
        if (!this.events[eventName]) {
            this.events[eventName] = [];
        }
        this.events[eventName].push(callback);
    }
    
    // 取消订阅事件
    off(eventName, callback) {
        if (this.events[eventName]) {
            const index = this.events[eventName].indexOf(callback);
            if (index > -1) {
                this.events[eventName].splice(index, 1);
            }
        }
    }
    
    // 触发事件
    emit(eventName, data) {
        if (this.events[eventName]) {
            this.events[eventName].forEach(callback => {
                callback(data);
            });
        }
    }
}

// 录制事件管理器
class RecordingEventManager extends CustomEventSystem {
    constructor(recorder) {
        super();
        this.recorder = recorder;
        this.setupRecordingEvents();
    }
    
    setupRecordingEvents() {
        // 扩展原生事件
        const originalStart = this.recorder.startRecording;
        this.recorder.startRecording = () => {
            originalStart.call(this.recorder);
            this.emit('recordingStarted');
        };
        
        const originalStop = this.recorder.stopRecording;
        this.recorder.stopRecording = (callback) => {
            originalStop.call(this.recorder, () => {
                this.emit('recordingStopped');
                if (callback) callback();
            });
        };
        
        // 监听数据可用事件
        this.recorder.ondataavailable = (blob) => {
            this.emit('chunkAvailable', blob);
        };
        
        // 监听错误事件
        this.recorder.onerror = (error) => {
            this.emit('recordingError', error);
        };
    }
}

// 使用示例
// const eventManager = new RecordingEventManager(recorder);

// 订阅自定义事件
// eventManager.on('recordingStarted', () => {
//     console.log('自定义录制开始事件');
// });

// eventManager.on('chunkAvailable', (blob) => {
//     console.log('自定义数据块事件:', blob.size);
// });

// eventManager.on('recordingError', (error) => {
//     console.log('自定义错误事件:', error);
// });