UNPKG

yirtc-sdk-web

Version:

基于WebRTC的实时音视频SDK

287 lines (286 loc) 12 kB
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; /** * Web 平台的媒体适配器实现 */ import { Logger } from '../../utils/logger'; /** * Web 媒体适配器 * 实现 Web 平台的媒体设备操作 */ export class WebMediaAdapter { /** * 构造函数 */ constructor() { this.audioContext = null; this.logger = new Logger('WebMediaAdapter'); } /** * 获取用户媒体流 * @param constraints 媒体约束 * @returns Promise<MediaStream> */ getUserMedia(constraints) { return __awaiter(this, void 0, void 0, function* () { try { const stream = yield navigator.mediaDevices.getUserMedia(constraints); this.logger.debug('获取用户媒体流成功', constraints); return stream; } catch (error) { this.logger.error('获取用户媒体流失败', error); throw error; } }); } /** * 获取屏幕共享流 * @param constraints 屏幕共享约束 * @returns Promise<MediaStream> */ getDisplayMedia(constraints) { return __awaiter(this, void 0, void 0, function* () { try { // 将自定义约束转换为标准约束 const standardConstraints = {}; if (constraints === null || constraints === void 0 ? void 0 : constraints.video) { standardConstraints.video = constraints.video; } else { standardConstraints.video = true; } if (constraints === null || constraints === void 0 ? void 0 : constraints.audio) { standardConstraints.audio = constraints.audio; } // 添加Chrome特定选项 if ((constraints === null || constraints === void 0 ? void 0 : constraints.selfBrowserSurface) || (constraints === null || constraints === void 0 ? void 0 : constraints.systemAudio)) { if (!standardConstraints.video || standardConstraints.video === true) { standardConstraints.video = {}; } if (!standardConstraints.video.mandatory) { standardConstraints.video.mandatory = {}; } // Chrome特定选项 if (constraints.selfBrowserSurface === 'exclude') { standardConstraints.video.mandatory.chromeMediaSource = 'desktop'; standardConstraints.video.mandatory.chromeMediaSourceId = 'screen:0'; } if (constraints.systemAudio === 'include') { if (!standardConstraints.audio) { standardConstraints.audio = {}; } standardConstraints.audio.mandatory = { chromeMediaSource: 'desktop' }; } } const stream = yield navigator.mediaDevices.getDisplayMedia(standardConstraints); this.logger.debug('获取屏幕共享流成功', constraints); return stream; } catch (error) { this.logger.error('获取屏幕共享流失败', error); throw error; } }); } /** * 枚举媒体设备 * @returns Promise<DeviceInfo[]> */ enumerateDevices() { return __awaiter(this, void 0, void 0, function* () { try { const devices = yield navigator.mediaDevices.enumerateDevices(); const deviceInfos = devices.map(device => ({ deviceId: device.deviceId, kind: device.kind, label: device.label || `${device.kind} (${device.deviceId.substring(0, 8)}...)`, groupId: device.groupId })); this.logger.debug('枚举媒体设备成功', deviceInfos); return deviceInfos; } catch (error) { this.logger.error('枚举媒体设备失败', error); throw error; } }); } /** * 检查浏览器是否支持指定的媒体约束 * @param constraints 媒体约束 * @returns Promise<boolean> */ isSupportedConstraints(constraints) { return __awaiter(this, void 0, void 0, function* () { try { const supportedConstraints = navigator.mediaDevices.getSupportedConstraints(); // 检查所有请求的约束是否被支持 for (const constraint in constraints) { if (constraints.hasOwnProperty(constraint) && constraints[constraint] && !supportedConstraints[constraint]) { return false; } } return true; } catch (error) { this.logger.error('检查媒体约束支持失败', error); return false; } }); } /** * 播放媒体流 * @param stream 媒体流 * @param element 视频/音频元素 * @returns Promise<void> */ playStream(stream, element) { return __awaiter(this, void 0, void 0, function* () { return new Promise((resolve, reject) => { try { element.srcObject = stream; const onLoadedMetadata = () => { element.removeEventListener('loadedmetadata', onLoadedMetadata); element.play() .then(() => { this.logger.debug('媒体流播放成功'); resolve(); }) .catch(error => { this.logger.error('媒体流播放失败', error); reject(error); }); }; element.addEventListener('loadedmetadata', onLoadedMetadata); } catch (error) { this.logger.error('设置媒体流失败', error); reject(error); } }); }); } /** * 停止媒体流 * @param stream 媒体流 */ stopStream(stream) { try { stream.getTracks().forEach(track => { track.stop(); }); this.logger.debug('停止媒体流成功'); } catch (error) { this.logger.error('停止媒体流失败', error); } } /** * 获取音频音量 * @param stream 媒体流 * @returns Promise<number> 音量大小 (0-1) */ getAudioLevel(stream) { return __awaiter(this, void 0, void 0, function* () { try { // 懒加载AudioContext if (!this.audioContext) { this.audioContext = new (window.AudioContext || window.webkitAudioContext)(); } const audioTrack = stream.getAudioTracks()[0]; if (!audioTrack) { return 0; } const mediaStreamSource = this.audioContext.createMediaStreamSource(stream); const analyser = this.audioContext.createAnalyser(); analyser.fftSize = 256; mediaStreamSource.connect(analyser); const dataArray = new Uint8Array(analyser.frequencyBinCount); analyser.getByteFrequencyData(dataArray); // 计算平均音量 let sum = 0; for (let i = 0; i < dataArray.length; i++) { sum += dataArray[i]; } const average = sum / dataArray.length; const volume = average / 255; // 归一化到0-1范围 // 断开连接以避免内存泄漏 mediaStreamSource.disconnect(); return volume; } catch (error) { this.logger.error('获取音频音量失败', error); return 0; } }); } /** * 截取视频帧 * @param videoTrack 视频轨道 * @returns Promise<Blob> 图片Blob */ captureFrame(videoTrack) { return __awaiter(this, void 0, void 0, function* () { return new Promise((resolve, reject) => { try { // 创建一个临时视频元素 const video = document.createElement('video'); video.autoplay = true; video.muted = true; video.style.display = 'none'; document.body.appendChild(video); // 创建一个只包含指定视频轨道的新流 const stream = new MediaStream([videoTrack]); video.srcObject = stream; // 当视频加载完成后截图 video.onloadedmetadata = () => { // 确保视频已经有足够的帧可用 setTimeout(() => { // 创建画布并绘制当前视频帧 const canvas = document.createElement('canvas'); canvas.width = video.videoWidth; canvas.height = video.videoHeight; const ctx = canvas.getContext('2d'); if (!ctx) { reject(new Error('无法创建2D上下文')); return; } ctx.drawImage(video, 0, 0, canvas.width, canvas.height); // 将画布内容转换为Blob canvas.toBlob((blob) => { if (blob) { resolve(blob); } else { reject(new Error('无法创建图像Blob')); } // 清理 document.body.removeChild(video); }, 'image/jpeg', 0.95); }, 100); }; video.onerror = (error) => { document.body.removeChild(video); reject(error); }; } catch (error) { this.logger.error('截取视频帧失败', error); reject(error); } }); }); } }