第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);
// });