UNPKG

yirtc-sdk-web

Version:

基于WebRTC的实时音视频SDK

413 lines (412 loc) 13.2 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()); }); }; /** * YiRTC SDK 流类 */ import { EventEmitter } from './utils/eventEmitter'; import { Logger } from './utils/logger'; import { VideoProfileType, MediaTypeEnum } from './types'; /** * 流类,负责管理音视频流 */ export class Stream extends EventEmitter { /** * 创建流实例 * @param config 流配置 */ constructor(config) { super(); this.stream = null; this.videoTrack = null; this.audioTrack = null; this.videoElement = null; this.userId = ''; this.isPlaying = false; this.isLocal = true; this.mediaType = MediaTypeEnum.VIDEO; // 默认为视频流 this.config = config; this.logger = new Logger('Stream'); this.streamId = 'stream_' + Math.floor(Math.random() * 10000); this.videoProfile = config.videoProfile || VideoProfileType.STANDARD; // 根据配置设置媒体类型 if (config.screen) { this.mediaType = MediaTypeEnum.SCREEN; } else if (config.video && config.audio) { this.mediaType = MediaTypeEnum.VIDEO; // 音视频流默认标记为视频 } else if (config.audio) { this.mediaType = MediaTypeEnum.AUDIO; } else { this.mediaType = MediaTypeEnum.VIDEO; // 默认为视频 } this.logger.info('流已创建', { streamId: this.streamId, config, mediaType: this.mediaType }); } /** * 初始化流 * @returns Promise<void> */ init() { return __awaiter(this, void 0, void 0, function* () { try { const constraints = {}; if (this.config.audio) { constraints.audio = this.config.microphoneId ? { deviceId: { exact: this.config.microphoneId } } : true; } if (this.config.video) { // 根据视频配置文件设置分辨率 let width = 640; let height = 480; switch (this.videoProfile) { case VideoProfileType.LOW: width = 160; height = 120; break; case VideoProfileType.STANDARD: width = 640; height = 480; break; case VideoProfileType.HIGH: width = 1280; height = 720; break; case VideoProfileType.SUPER: width = 1920; height = 1080; break; } constraints.video = { deviceId: this.config.cameraId ? { exact: this.config.cameraId } : undefined, width: { ideal: width }, height: { ideal: height } }; } else if (this.config.screen) { // 屏幕共享 try { // @ts-ignore this.stream = yield navigator.mediaDevices.getDisplayMedia({ video: true, audio: this.config.audio || false }); } catch (error) { this.logger.error('获取屏幕共享失败', error); throw error; } } if (!this.stream && (this.config.audio || this.config.video)) { this.stream = yield navigator.mediaDevices.getUserMedia(constraints); } if (this.stream) { this.videoTrack = this.stream.getVideoTracks()[0] || null; this.audioTrack = this.stream.getAudioTracks()[0] || null; this.logger.info('流初始化成功', { hasVideo: !!this.videoTrack, hasAudio: !!this.audioTrack }); this.emit('init-success'); } else { throw new Error('没有请求音频或视频'); } } catch (error) { this.logger.error('流初始化失败', error); this.emit('init-fail', { error }); throw error; } }); } /** * 播放流 * @param elementId 视频元素ID * @returns void */ play(elementId) { if (!this.stream) { this.logger.error('流未初始化,无法播放'); return; } try { const element = document.getElementById(elementId); if (!element) { this.logger.error(`找不到ID为 ${elementId} 的元素`); return; } if (!(element instanceof HTMLVideoElement)) { this.logger.error(`ID为 ${elementId} 的元素不是视频元素`); return; } this.videoElement = element; this.videoElement.srcObject = this.stream; this.videoElement.onloadedmetadata = () => { if (this.videoElement) { this.videoElement.play() .then(() => { this.isPlaying = true; this.emit('stream-played', { elementId }); this.logger.info('流开始播放', { elementId }); }) .catch(error => { this.logger.error('播放流失败', error); this.emit('play-failed', { error }); }); } }; } catch (error) { this.logger.error('播放流失败', error); throw error; } } /** * 停止播放 */ stop() { if (this.videoElement) { this.videoElement.srcObject = null; this.videoElement = null; this.isPlaying = false; this.emit('stream-stopped'); this.logger.info('流停止播放'); } } /** * 销毁流 */ destroy() { this.stop(); if (this.stream) { this.stream.getTracks().forEach(track => { track.stop(); }); this.stream = null; this.videoTrack = null; this.audioTrack = null; } this.removeAllListeners(); this.logger.info('流已销毁', { streamId: this.streamId }); } /** * 静音音频 */ muteAudio() { if (this.audioTrack) { this.audioTrack.enabled = false; this.emit('audio-muted'); this.logger.info('音频已静音'); } } /** * 取消静音音频 */ unmuteAudio() { if (this.audioTrack) { this.audioTrack.enabled = true; this.emit('audio-unmuted'); this.logger.info('音频已取消静音'); } } /** * 禁用视频 */ muteVideo() { if (this.videoTrack) { this.videoTrack.enabled = false; this.emit('video-muted'); this.logger.info('视频已禁用'); } } /** * 启用视频 */ unmuteVideo() { if (this.videoTrack) { this.videoTrack.enabled = true; this.emit('video-unmuted'); this.logger.info('视频已启用'); } } /** * 设置音频配置 * @param profile 音频配置 */ setAudioProfile(profile) { this.logger.info('设置音频配置', { profile }); // 在实际应用中,这里需要设置音频的比特率等参数 } /** * 设置视频配置 * @param profile 视频配置 */ setVideoProfile(profile) { this.videoProfile = profile; this.logger.info('设置视频配置', { profile }); // 在实际应用中,这里需要重新配置视频约束并重新获取媒体流 } /** * 设置音频音量 * @param volume 音量值 (0-100) */ setAudioVolume(volume) { if (volume < 0 || volume > 100) { this.logger.warn('音量值必须在0-100之间'); return; } if (this.videoElement) { this.videoElement.volume = volume / 100; this.logger.info('设置音频音量', { volume }); } } /** * 获取音频电平 * @returns number 音频电平 (0-100) */ getAudioLevel() { // 在实际应用中,这里应该使用Web Audio API分析音频电平 return this.audioTrack && this.audioTrack.enabled ? 50 : 0; } /** * 是否有音频 * @returns boolean */ hasAudio() { return !!this.audioTrack; } /** * 是否有视频 * @returns boolean */ hasVideo() { return !!this.videoTrack; } /** * 是否正在播放 * @returns boolean */ isPlayingState() { return this.isPlaying; } /** * 是否可以播放 * @returns boolean */ canPlay() { return !!this.stream; } /** * 获取流ID * @returns string */ getId() { return this.streamId; } /** * 获取用户ID * @returns string | number */ getUserId() { return this.userId; } /** * 设置用户ID * @param uid 用户ID */ setUserId(uid) { this.userId = uid; } /** * 设置是否为本地流 * @param isLocal 是否为本地流 */ setIsLocal(isLocal) { this.isLocal = isLocal; } /** * 是否为本地流 * @returns boolean */ isLocalStream() { return this.isLocal; } /** * 获取MediaStream对象 * @returns MediaStream | null */ getMediaStream() { return this.stream; } /** * 截取视频快照 * @returns Promise<string> 图片的base64编码 */ takeSnapshot() { return __awaiter(this, void 0, void 0, function* () { if (!this.videoElement || !this.videoTrack) { throw new Error('没有视频可截图'); } try { const canvas = document.createElement('canvas'); canvas.width = this.videoElement.videoWidth; canvas.height = this.videoElement.videoHeight; const ctx = canvas.getContext('2d'); if (!ctx) { throw new Error('无法创建canvas上下文'); } ctx.drawImage(this.videoElement, 0, 0, canvas.width, canvas.height); const dataUrl = canvas.toDataURL('image/png'); this.logger.info('截图成功'); return dataUrl; } catch (error) { this.logger.error('截图失败', error); throw error; } }); } /** * 获取媒体类型 * @returns MediaType */ getMediaType() { return this.mediaType; } /** * 设置媒体类型 * @param mediaType 媒体类型 */ setMediaType(mediaType) { this.mediaType = mediaType; this.logger.info('设置媒体类型', { mediaType }); } /** * 是否为屏幕共享流 * @returns boolean */ isScreenStream() { return this.mediaType === MediaTypeEnum.SCREEN; } /** * 是否为音频流 * @returns boolean */ isAudioStream() { return this.mediaType === MediaTypeEnum.AUDIO; } /** * 是否为视频流 * @returns boolean */ isVideoStream() { return this.mediaType === MediaTypeEnum.VIDEO; } }