webrtc2-peer
Version:
WebRTC2 Peer - Low-level WebRTC peer connection management for cross-platform real-time communication with signaling, ICE handling, and media streaming
166 lines (138 loc) • 4.98 kB
text/typescript
/**
* MediaManager - Media device and stream management
*/
export interface MediaConstraints {
video?: boolean | MediaTrackConstraints;
audio?: boolean | MediaTrackConstraints;
}
export interface MediaDevice {
deviceId: string;
kind: MediaDeviceKind;
label: string;
groupId: string;
}
export class MediaManager {
private currentStream?: MediaStream;
private devices: MediaDevice[] = [];
async getDevices(): Promise<MediaDevice[]> {
try {
const devices = await navigator.mediaDevices.enumerateDevices();
this.devices = devices.map(device => ({
deviceId: device.deviceId,
kind: device.kind,
label: device.label,
groupId: device.groupId
}));
return this.devices;
} catch (error) {
throw new Error(`Failed to get media devices: ${error}`);
}
}
async getUserMedia(constraints: MediaConstraints = { video: true, audio: true }): Promise<MediaStream> {
try {
const stream = await navigator.mediaDevices.getUserMedia(constraints);
this.currentStream = stream;
return stream;
} catch (error) {
throw new Error(`Failed to get user media: ${error}`);
}
}
async getDisplayMedia(constraints?: MediaStreamConstraints): Promise<MediaStream> {
try {
const stream = await navigator.mediaDevices.getDisplayMedia(constraints);
return stream;
} catch (error) {
throw new Error(`Failed to get display media: ${error}`);
}
}
async switchCamera(deviceId?: string): Promise<MediaStream> {
if (!this.currentStream) {
throw new Error('No active stream to switch camera');
}
const videoTrack = this.currentStream.getVideoTracks()[0];
if (!videoTrack) {
throw new Error('No video track found');
}
const constraints: MediaTrackConstraints = {
deviceId: deviceId ? { exact: deviceId } : undefined
};
try {
const newStream = await navigator.mediaDevices.getUserMedia({
video: constraints,
audio: this.hasAudio()
});
// Replace video track
const newVideoTrack = newStream.getVideoTracks()[0];
if (newVideoTrack) {
this.currentStream.removeTrack(videoTrack);
this.currentStream.addTrack(newVideoTrack);
videoTrack.stop();
}
return this.currentStream;
} catch (error) {
throw new Error(`Failed to switch camera: ${error}`);
}
}
muteAudio(muted: boolean = true): void {
if (!this.currentStream) return;
this.currentStream.getAudioTracks().forEach(track => {
track.enabled = !muted;
});
}
muteVideo(muted: boolean = true): void {
if (!this.currentStream) return;
this.currentStream.getVideoTracks().forEach(track => {
track.enabled = !muted;
});
}
stopStream(stream?: MediaStream): void {
const targetStream = stream || this.currentStream;
if (!targetStream) return;
targetStream.getTracks().forEach(track => {
track.stop();
});
if (targetStream === this.currentStream) {
this.currentStream = undefined;
}
}
getCurrentStream(): MediaStream | undefined {
return this.currentStream;
}
hasVideo(): boolean {
return (this.currentStream?.getVideoTracks().length || 0) > 0;
}
hasAudio(): boolean {
return (this.currentStream?.getAudioTracks().length || 0) > 0;
}
isAudioMuted(): boolean {
const audioTrack = this.currentStream?.getAudioTracks()[0];
return audioTrack ? !audioTrack.enabled : true;
}
isVideoMuted(): boolean {
const videoTrack = this.currentStream?.getVideoTracks()[0];
return videoTrack ? !videoTrack.enabled : true;
}
getVideoDevices(): MediaDevice[] {
return this.devices.filter(device => device.kind === 'videoinput');
}
getAudioDevices(): MediaDevice[] {
return this.devices.filter(device => device.kind === 'audioinput');
}
async checkPermissions(): Promise<{
camera: PermissionState;
microphone: PermissionState;
}> {
try {
const [camera, microphone] = await Promise.all([
navigator.permissions.query({ name: 'camera' as PermissionName }),
navigator.permissions.query({ name: 'microphone' as PermissionName })
]);
return {
camera: camera.state,
microphone: microphone.state
};
} catch (error) {
throw new Error(`Failed to check permissions: ${error}`);
}
}
}