UNPKG

apphouse

Version:

Component library for React that uses observable state management and theme-able components.

356 lines (327 loc) 9.62 kB
import { action, computed, makeObservable, observable, runInAction } from 'mobx'; import { audioLog } from './AudioLog'; import { translate } from './translate'; export interface MicrophoneOption { value: string; label: string; } export interface MicrophoneInfo { channelCount: { min: number | undefined; max: number | undefined; value: number | undefined; }; sampleRate: { min: number | undefined; max: number | undefined; value: number | undefined; }; sampleSize: { min: number | undefined; max: number | undefined; value: number | undefined; }; latency: { min: number | undefined; max: number | undefined; }; autoGainControl: boolean | undefined; echoCancellation: boolean | undefined; noiseSuppression: boolean | undefined; } export default class Microphone { devices: MediaDeviceInfo[]; selectedDevice?: MediaStream; selectedDeviceId?: string; // selected microphone stream: MediaStream | undefined; track: MediaStreamTrack | undefined; constructor() { this.stream = undefined; this.devices = []; this.selectedDevice = undefined; this.selectedDeviceId = undefined; this.track = undefined; makeObservable(this, { currentMicrophone: computed, currentTrack: computed, devices: observable, init: action, isReady: computed, loadDefault: action, loadSystemDevices: action, microphoneDetails: computed, microphoneOptions: computed, onMicrophoneChange: action, resetMicData: action, selectedDevice: observable, selectedDeviceId: observable, setMediaStreamTrack: action, setSelectedDevice: action, stopStreaming: action, pauseStreaming: action, resumeStreaming: action, stream: observable, systemInputs: computed, track: observable, streamingStream: computed }); } get isReady() { const isMicrophoneReady = this.stream !== undefined; isMicrophoneReady && audioLog.logMicrophone('Ready'); return isMicrophoneReady; } get currentTrack() { return this.track; } get currentMicrophone() { const selectedDeviceId = this.selectedDeviceId; const selectedInput = this.microphoneOptions.filter( (option) => option.value === selectedDeviceId ); if (selectedInput.length > 0) { return selectedInput[0]; } return undefined; } get streamingStream(): MediaStream | undefined { if (this.track && this.track.enabled === true) { return this.stream; } return undefined; } get microphoneOptions(): MicrophoneOption[] { if (this.devices.length === 0) { return [ { value: 'none', label: translate('noMic.label') } ]; } return this.devices.map((d) => ({ value: d.deviceId, label: d.label })); } get systemInputs() { return this.devices; } get microphoneDetails(): MicrophoneInfo { const track = this.stream?.getAudioTracks()[0]; if (!track) { return { channelCount: { min: undefined, max: undefined, value: undefined }, sampleRate: { min: undefined, max: undefined, value: undefined }, sampleSize: { min: undefined, max: undefined, value: undefined }, latency: { min: undefined, max: undefined }, autoGainControl: false, echoCancellation: false, noiseSuppression: false }; } const settings = track.getSettings(); const capabilities = track.getCapabilities(); return { channelCount: { min: capabilities.channelCount?.min, max: capabilities.channelCount?.max, value: settings.channelCount }, sampleRate: { min: capabilities.sampleRate?.min, max: capabilities.sampleRate?.max, value: settings.sampleRate }, sampleSize: { min: capabilities.sampleSize?.min, max: capabilities.sampleSize?.max, value: settings.sampleSize }, latency: { // @ts-ignore min: capabilities.latency?.min, // @ts-ignore max: capabilities.latency?.max }, autoGainControl: settings.autoGainControl, echoCancellation: settings.echoCancellation, noiseSuppression: settings.noiseSuppression }; } // Loads all available input devices in the user's computer // and sets the default stream to the user's computer's default // selected input device (audio input/microphone) init = async (): Promise<boolean> => { const defaultLoaded = await this.loadDefault(); const systemDevicesLoaded = await this.loadSystemDevices(); if (defaultLoaded && systemDevicesLoaded) { return true; // default microphone and system devices loaded successfully } return false; }; // Load default device (microphone) and set default // Promise will return true once the default device has been selected loadDefault = async (): Promise<boolean> => { if (typeof window !== 'undefined') { if (window.navigator.mediaDevices) { return window.navigator.mediaDevices .getUserMedia({ audio: true }) .then((device: any) => { runInAction(() => { this.setSelectedDevice(device, 'default'); }); return true; }) .catch((err: any) => { console.error(err); return false; }); } } return new Promise(() => false); }; // Load all microphone options in the system loadSystemDevices = async (): Promise<boolean> => { if (typeof window !== 'undefined') { return window.navigator.mediaDevices .enumerateDevices() .then((devices: any) => { runInAction(() => { this.devices = devices.filter((d: any) => d.kind === 'audioinput'); }); audioLog.logMicrophone( 'Input device list loaded and set successfully' ); return true; }) .catch((err: AsyncGenerator) => { console.error(err); return false; }); } return new Promise(() => false); }; setMediaStreamTrack = (stream: MediaStream | undefined): boolean => { if (stream) { const audioTracks = stream.getAudioTracks(); this.track = audioTracks[0]; this.stream = stream; audioLog.logMicrophone('Set MediaStream', stream); this.resumeStreaming(); return true; } else { console.warn('Attempted to set media stream but stream was undefined'); } return false; }; onMicrophoneChange = async ( newValue: MicrophoneOption | null | undefined ): Promise<boolean> => { if (!newValue) { return false; } const selectedDeviceId = newValue.value; if (typeof window !== 'undefined') { return window.navigator.mediaDevices .getUserMedia({ audio: { deviceId: newValue?.value ?? '' } }) .then((device: MediaStream) => { this.setSelectedDevice(device, selectedDeviceId); return true; }) .catch((err: any) => { console.error(err); return false; }); } return false; }; setSelectedDevice = (device: MediaStream, deviceId: string): void => { this.selectedDevice = device; this.selectedDeviceId = deviceId; audioLog.logMicrophone('Set selected device', device, deviceId); }; startStream = async (): Promise<boolean> => { if (this.selectedDevice) { return new Promise((resolve) => resolve(this.setMediaStreamTrack(this.selectedDevice)) ); } else { return this.loadDefault(); } }; stopStreaming = () => { if (this.track) { this.track.stop(); audioLog.logMicrophone( 'Stopped streaming microphone - To reuse it again, it needs to be reloaded' ); } else { console.warn('Attempted to stop track but track was undefined'); } }; pauseStreaming = () => { // The enabled property on the MediaStreamTrack interface is a Boolean value which is true // if the track is allowed to render the source stream or false if it is not. This can be // used to intentionally mute a track. When enabled, a track's data is output from the // source to the destination; otherwise, empty frames are output. if (this.track) { this.track.enabled = false; } }; resumeStreaming = () => { if (this.track) { this.track.enabled = true; } }; resetMicData = () => { this.stream = undefined; this.track = undefined; audioLog.logMicrophone('Stream reset'); }; printStreamInfo = () => { if (this.stream) { const audioTracks = this.stream.getAudioTracks(); this.track = audioTracks[0]; console.log('Num Audio Tracks', audioTracks.length); console.log('Capabilities', audioTracks[0].getCapabilities()); console.log('Contraints', audioTracks[0].getConstraints()); console.log('Settings', audioTracks[0].getSettings()); const mimeTypes = [ 'audio/wav', 'audio/mpeg', 'audio/ogg', 'audio/opus', 'audio/webm', 'audio/webm;codecs=opus', 'audio/webm;codecs=pcm' ]; mimeTypes.forEach((mimeType) => console.log('format', mimeType, MediaRecorder.isTypeSupported(mimeType)) ); } else { console.log('Stream is undefined'); } }; }