第7章 摄像头访问

在本章中,我们将学习如何通过RecordRTC访问和使用摄像头设备,包括设备枚举、约束设置和错误处理。

7.1 摄像头访问基础

访问摄像头是使用RecordRTC进行视频录制的前提,需要正确获取媒体流:

基本摄像头访问

// 基本摄像头访问
navigator.mediaDevices.getUserMedia({
    video: true,
    audio: true
}).then(function(stream) {
    // 成功获取媒体流
    console.log('摄像头访问成功');
    
    // 显示预览
    const video = document.getElementById('preview');
    video.srcObject = stream;
    
    // 初始化RecordRTC
    const recorder = RecordRTC(stream, {
        type: 'video'
    });
}).catch(function(error) {
    // 处理错误
    console.error('摄像头访问失败:', error);
});

7.2 设备枚举

可以枚举所有可用的摄像头设备:

设备枚举

// 枚举摄像头设备
async function enumerateCameras() {
    try {
        // 获取所有媒体设备
        const devices = await navigator.mediaDevices.enumerateDevices();
        
        // 筛选出视频输入设备
        const videoDevices = devices.filter(device => 
            device.kind === 'videoinput'
        );
        
        console.log('可用的摄像头设备:', videoDevices);
        
        // 显示设备列表
        const select = document.getElementById('cameraSelect');
        videoDevices.forEach((device, index) => {
            const option = document.createElement('option');
            option.value = device.deviceId;
            option.text = device.label || `摄像头 ${index + 1}`;
            select.appendChild(option);
        });
        
        return videoDevices;
    } catch (error) {
        console.error('枚举设备失败:', error);
    }
}

7.3 摄像头约束设置

可以通过约束条件精确控制摄像头的行为:

摄像头约束设置

// 摄像头约束设置
const cameraConstraints = {
    video: {
        // 分辨率约束
        width: { ideal: 1280 },
        height: { ideal: 720 },
        
        // 帧率约束
        frameRate: { ideal: 30 },
        
        // 摄像头选择
        facingMode: 'user', // 'user' 前置摄像头, 'environment' 后置摄像头
        
        // 特定设备
        // deviceId: 'specific-device-id'
        
        // 高级约束
        aspectRatio: 16/9,
        resizeMode: 'none'
    },
    audio: {
        // 音频约束
        echoCancellation: true,
        noiseSuppression: true,
        autoGainControl: true
    }
};

// 使用约束获取媒体流
navigator.mediaDevices.getUserMedia(cameraConstraints)
    .then(function(stream) {
        // 处理媒体流
        console.log('使用约束获取媒体流成功');
    })
    .catch(function(error) {
        console.error('获取媒体流失败:', error);
    });

7.4 多摄像头支持

可以同时使用多个摄像头:

多摄像头支持

// 多摄像头管理器
class MultiCameraManager {
    constructor() {
        this.streams = [];
        this.recorders = [];
    }
    
    // 添加摄像头
    async addCamera(deviceId) {
        try {
            const constraints = {
                video: {
                    deviceId: deviceId ? { exact: deviceId } : undefined
                },
                audio: false
            };
            
            const stream = await navigator.mediaDevices.getUserMedia(constraints);
            this.streams.push(stream);
            
            // 创建对应的录制器
            const recorder = RecordRTC(stream, {
                type: 'video',
                mimeType: 'video/webm'
            });
            this.recorders.push(recorder);
            
            return stream;
        } catch (error) {
            console.error('添加摄像头失败:', error);
        }
    }
    
    // 开始录制所有摄像头
    startAllRecording() {
        this.recorders.forEach(recorder => {
            recorder.startRecording();
        });
    }
    
    // 停止录制所有摄像头
    stopAllRecording(callback) {
        const blobs = [];
        
        this.recorders.forEach((recorder, index) => {
            recorder.stopRecording(() => {
                const blob = recorder.getBlob();
                blobs.push({
                    index: index,
                    blob: blob,
                    stream: this.streams[index]
                });
                
                // 如果所有录制都完成了
                if (blobs.length === this.recorders.length) {
                    callback(blobs);
                }
            });
        });
    }
    
    // 释放所有资源
    releaseAll() {
        this.streams.forEach(stream => {
            stream.getTracks().forEach(track => track.stop());
        });
        this.streams = [];
        this.recorders = [];
    }
}

7.5 摄像头错误处理

正确处理摄像头访问中的各种错误:

错误处理

// 摄像头错误处理
async function handleCameraAccess() {
    try {
        const stream = await navigator.mediaDevices.getUserMedia({
            video: true,
            audio: true
        });
        
        // 成功处理
        setupCameraStream(stream);
    } catch (error) {
        // 根据错误类型处理
        switch (error.name) {
            case 'NotAllowedError':
                console.error('用户拒绝了摄像头访问权限');
                showPermissionMessage();
                break;
            case 'NotFoundError':
                console.error('未找到摄像头设备');
                showDeviceNotFoundMessage();
                break;
            case 'NotReadableError':
                console.error('摄像头设备无法读取');
                showDeviceBusyMessage();
                break;
            case 'OverconstrainedError':
                console.error('摄像头约束条件无法满足');
                showConstraintErrorMessage();
                break;
            default:
                console.error('摄像头访问未知错误:', error);
                showGenericErrorMessage();
        }
    }
}

// 显示权限提示
function showPermissionMessage() {
    alert('请允许访问摄像头和麦克风权限以继续使用录制功能。');
}

// 显示设备未找到提示
function showDeviceNotFoundMessage() {
    alert('未检测到摄像头设备,请检查设备连接。');
}

7.6 摄像头预览和控制

实现摄像头预览和控制功能:

摄像头控制

// 摄像头控制器
class CameraController {
    constructor(videoElementId) {
        this.videoElement = document.getElementById(videoElementId);
        this.stream = null;
        this.recorder = null;
    }
    
    // 开始摄像头预览
    async startPreview() {
        try {
            this.stream = await navigator.mediaDevices.getUserMedia({
                video: true,
                audio: true
            });
            
            this.videoElement.srcObject = this.stream;
            return this.stream;
        } catch (error) {
            console.error('启动预览失败:', error);
        }
    }
    
    // 切换摄像头
    async switchCamera(deviceId) {
        // 停止当前流
        if (this.stream) {
            this.stream.getTracks().forEach(track => track.stop());
        }
        
        // 获取新流
        try {
            const constraints = {
                video: {
                    deviceId: deviceId ? { exact: deviceId } : undefined
                },
                audio: true
            };
            
            this.stream = await navigator.mediaDevices.getUserMedia(constraints);
            this.videoElement.srcObject = this.stream;
        } catch (error) {
            console.error('切换摄像头失败:', error);
        }
    }
    
    // 调整摄像头设置
    adjustSettings(brightness, contrast, saturation) {
        if (this.stream) {
            const videoTrack = this.stream.getVideoTracks()[0];
            const capabilities = videoTrack.getCapabilities();
            
            // 检查是否支持这些设置
            if (capabilities.brightness) {
                videoTrack.applyConstraints({
                    advanced: [{ brightness: brightness }]
                });
            }
        }
    }
    
    // 停止摄像头
    stopCamera() {
        if (this.stream) {
            this.stream.getTracks().forEach(track => track.stop());
            this.stream = null;
            this.videoElement.srcObject = null;
        }
    }
}