import { ICameraService, MediaStreamConstraints, } from './CameraService'; /** * 浏览器环境下的摄像头服务实现 * 使用标准的 Web API */ export class BrowserCameraService implements ICameraService { /** * 请求摄像头权限 * @returns 是否成功获取权限 */ async requestPermission(): Promise { try { // 检查是否在安全上下文中 if (!window.isSecureContext) { throw new Error( '摄像头访问需要安全上下文(HTTPS)。' + '请使用 HTTPS 访问页面,或在 localhost 环境下测试。' + `当前访问地址:${window.location.href}` ); } // 检查浏览器 API 支持 if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) { throw new Error('浏览器不支持摄像头 API,请使用最新版浏览器'); } // 检查是否有摄像头设备 const devices = await navigator.mediaDevices.enumerateDevices(); const hasCamera = devices.some(device => device.kind === 'videoinput'); if (!hasCamera) { throw new Error('未检测到摄像头设备,请连接摄像头后重试'); } // 请求权限 const stream = await navigator.mediaDevices.getUserMedia({ video: true }); stream.getTracks().forEach(track => track.stop()); return true; } catch (error: any) { console.error('摄像头权限请求失败:', error); throw error; } } /** * 获取媒体流 * @param constraints 媒体约束条件 * @returns MediaStream 对象 */ async getMediaStream( constraints: MediaStreamConstraints = { video: true } ): Promise { try { // 检查浏览器支持 if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) { throw new Error('当前浏览器不支持摄像头访问'); } // 获取媒体流 const stream = await navigator.mediaDevices.getUserMedia( constraints as globalThis.MediaStreamConstraints ); return stream; } catch (error: any) { // 处理不同的错误类型 if (error.name === 'NotAllowedError') { throw new Error('用户拒绝了摄像头权限'); } else if (error.name === 'NotFoundError') { throw new Error('未找到摄像头设备'); } else if (error.name === 'NotReadableError') { throw new Error('摄像头正被其他应用使用'); } else { throw new Error(`获取摄像头失败: ${error.message}`); } } } /** * 从媒体流中捕获照片 * @param stream 媒体流对象 * @returns base64 格式的图片数据 */ async capturePhoto(stream: MediaStream): Promise { return new Promise((resolve, reject) => { try { // 创建 video 元素 const video = document.createElement('video'); video.srcObject = stream; video.autoplay = true; video.playsInline = true; // iOS 需要 video.onloadedmetadata = () => { // 等待视频准备好 video.play(); // 创建 canvas const canvas = document.createElement('canvas'); canvas.width = video.videoWidth; canvas.height = video.videoHeight; // 绘制当前帧 const ctx = canvas.getContext('2d'); if (!ctx) { reject(new Error('无法获取 Canvas 2D Context')); return; } ctx.drawImage(video, 0, 0); // 转换为 base64 const base64 = canvas.toDataURL('image/jpeg', 0.8); // 清理 video.srcObject = null; resolve(base64); }; video.onerror = () => { reject(new Error('视频加载失败')); }; } catch (error) { reject(error); } }); } /** * 停止媒体流 * @param stream 要停止的媒体流 */ stopStream(stream: MediaStream): void { if (stream) { stream.getTracks().forEach((track) => { track.stop(); }); } } }