yirtc-sdk-web
Version:
基于WebRTC的实时音视频SDK
287 lines (286 loc) • 12 kB
JavaScript
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);
}
});
});
}
}