Home Reference Source

src/media/ManagedStream.ts

import {ImageCapture} from 'image-capture';
import nanoid from 'nanoid';

import {StreamRecorder} from './StreamRecorder';

/**
 * This class provides access to media control functions during a call
 * This class must never be directly instantiated
 */
export class ManagedStream {
	private mediaStream: MediaStream;
	private local: boolean;
	private id: string;

	/** @ignore */
	public constructor(mediaStream: MediaStream, local = false) {
		/** @private */
		this.mediaStream = mediaStream;
		/** @private */
		this.local = local;
		/** @private */
		this.id = nanoid();
	}

	/**
	 * The id of this stream
	 * @return {boolean}
	 */
	public getId(): string {
		return this.id;
	}

	/**
	 * Indicates if this managed stream is local. This means that its sources are
	 * local devices of this machine and that it was obtained of a getUserMedia call
	 * and not from a RTCPeerConnection
	 * @return {boolean}
	 */
	public isLocal(): boolean {
		return this.local;
	}

	/**
	 * Indicates if this managed stream is remote. This means that its sources are not
	 * local devices of this machine and that it was obtained from a RTCPeerConnection
	 * and not from a getUserMedia call
	 * @return {boolean}
	 */
	public isRemote(): boolean {
		return !this.local;
	}

	/**
	 * Provides access to the underlying WebRTC MediaStream object
	 * @return {MediaStream}
	 */
	public getMediaStream(): MediaStream {
		return this.mediaStream;
	}

	/**
	 * Provides access to all the tracks of the managed stream
	 * @return {MediaStreamTrack[]}
	 */
	public getTracks(): MediaStreamTrack[] {
		return this.mediaStream.getTracks();
	}

	/**
	 * Provides access to the audio tracks of the managed stream
	 * @return {MediaStreamTrack[]}
	 */
	public getAudioTracks(): MediaStreamTrack[] {
		return this.mediaStream.getAudioTracks();
	}

	/**
	 * Provides access to the video tracks of the managed stream
	 * @return {MediaStreamTrack[]}
	 */
	public getVideoTracks(): MediaStreamTrack[] {
		return this.mediaStream.getVideoTracks();
	}

	/**
	 * Provides access to the video tracks that are not a screen of the managed stream
	 * @return {MediaStreamTrack[]}
	 */
	public getCameraTracks(): MediaStreamTrack[] {
		return this.mediaStream.getVideoTracks().filter(track => !track.label.includes('screen'));
	}

	/**
	 * Provides access to the screen tracks of the managed stream
	 * @return {MediaStreamTrack[]}
	 */
	public getScreenTracks(): MediaStreamTrack[] {
		return this.mediaStream.getVideoTracks().filter(
			track => ['screen', 'window', 'web-contents-media-stream'].some(p => track.label.startsWith(p + ':')),
		);
	}

	/**
	 * Allows to know if the managed stream has audio tracks
	 * @return {boolean} A boolean value indicating if the managed stream has at least one audio track
	 */
	public hasAudioTracks(): boolean {
		return this.getAudioTracks().length > 0;
	}

	/**
	 * Allows to know if the managed stream has video tracks
	 * @return {boolean} A boolean value indicating if the managed stream has at least one video track
	 */
	public hasVideoTracks(): boolean {
		return this.getVideoTracks().length > 0;
	}

	/**
	 * Allows to know if the managed stream has camera tracks
	 * @return {boolean} A boolean value indicating if the managed stream has at least one camera track
	 */
	public hasCameraTracks(): boolean {
		return this.getCameraTracks().length > 0;
	}

	/**
	 * Allows to know if the managed stream has screen tracks
	 * @return {boolean} A boolean value indicating if the managed stream has at least one screen track
	 */
	public hasScreenTracks(): boolean {
		return this.getScreenTracks().length > 0;
	}

	/**
	 * Stops every track of the underlying MediaStream
	 * @return {ManagedStream}
	 */
	public stop(): void {
		this.getTracks().forEach((track) => {
			if (track.enabled) {
				track.stop();
			}
		});
	}

	/**
	 * Indicates if every audio track is muted
	 * @return {boolean}
	 */
	public isAudioMuted(): boolean {
		return this.getAudioTracks().every(track => !track.enabled);
	}

	/**
	 * Indicates if every video track is muted
	 * @return {boolean}
	 */
	public isVideoMuted(): boolean {
		return this.getVideoTracks().every(track => !track.enabled);
	}

	/**
	 * Indicates if every screen track is muted
	 * @return {boolean}
	 */
	public isScreenMuted(): boolean {
		return this.getScreenTracks().every(track => !track.enabled);
	}

	/**
	 * Mutes or unmutes audio tracks of this stream
	 * @param {boolean} [mute] If true disables audio tracks, if false enables them
	 * If not argument is provided it toggles the mute state on audio tracks
	 */
	public muteAudio(mute = !this.isAudioMuted()): void {
		this.getAudioTracks().forEach(track => track.enabled = !mute);
	}

	/**
	 * Mutes or unmutes video tracks of this stream
	 * @param {boolean} [mute] If true disables video tracks, if false enables them
	 * If not argument is provided it toggles the mute state on video tracks
	 */
	public muteVideo(mute = !this.isVideoMuted()): void {
		this.getVideoTracks().forEach(track => track.enabled = !mute);
	}

	/**
	 * Mutes or unmutes screen tracks of this stream
	 * @param {boolean} [mute] If true disables screen tracks, if false enables them
	 * If not argument is provided it toggles the mute state on screen tracks
	 */
	public muteScreen(mute = !this.isScreenMuted()): void {
		this.getScreenTracks().forEach(track => track.enabled = !mute);
	}

	/**
	 * Allows taking captures of the video tracks of this stream
	 * @return {Promise<Blob[]>} A promise containing an array of Blobs with a capture
	 * of the video. One Blob for each video track of this stream
	 */
	public takePhotos(): Promise<Blob[]> {
		const photos = this.mediaStream.getVideoTracks()
			.map(track => new ImageCapture(track))
			.map(captureDevice => (captureDevice.takePhoto() as Promise<Blob>));
		return Promise.all(photos);
	}

	/**
	 * Creates a StreamRecorder object that will allow recording the stream
	 * @return {StreamRecorder}
	 */
	public createStreamRecorder(): StreamRecorder {
		return new StreamRecorder(this.mediaStream);
	}
}