UNPKG

@azure/communication-react

Version:

React library for building modern communication user experiences utilizing Azure Communication Services

259 lines • 14 kB
// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. 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()); }); }; /** * ProxyDeviceManager proxies DeviceManager and subscribes to all events that affect device manager state. State updates * are set on the provided context. Also any queries for state are proxied and stored in state as well. Only one device * manager should exist for a given CallClient so if CallClient.getDeviceManager is called multiple times, either a * cached ProxyDeviceManager should be returned or the existing ProxyDeviceManager should be destructed via destructor() * and a new ProxyDeviceManager created. */ class ProxyDeviceManager { constructor(deviceManager, context) { this.setDeviceManager = () => { // isSpeakerSelectionAvailable, selectedMicrophone, and selectedSpeaker are properties on DeviceManager. Since they // are not functions we can't proxy them so we'll update whenever we think they may need updating such as at // construction time or when certain events happen. this._context.setDeviceManagerIsSpeakerSelectionAvailable(this._deviceManager.isSpeakerSelectionAvailable); this._context.setDeviceManagerSelectedMicrophone(this._deviceManager.selectedMicrophone); this._context.setDeviceManagerSelectedSpeaker(this._deviceManager.selectedSpeaker); }; this.subscribe = () => { this._deviceManager.on('videoDevicesUpdated', this.videoDevicesUpdated); this._deviceManager.on('audioDevicesUpdated', this.audioDevicesUpdated); this._deviceManager.on('selectedMicrophoneChanged', this.selectedMicrophoneChanged); this._deviceManager.on('selectedSpeakerChanged', this.selectedSpeakerChanged); // Subscribe to browser camera permissions changes if (navigator.permissions) { try { navigator.permissions.query({ name: 'camera' }).then((cameraPermissions) => { cameraPermissions.addEventListener('change', this.permissionsApiStateChangeHandler); }); } catch (e) { console.info('Could not subscribe to Permissions API Camera changed events, API is not supported by browser', e); } // Subscribe to browser microphone permissions changes try { navigator.permissions.query({ name: 'microphone' }).then((micPermissions) => { micPermissions.addEventListener('change', this.permissionsApiStateChangeHandler); }); } catch (e) { console.info('Could not subscribe to Permissions API Microphone changed events, API is not supported by browser', e); } } }; /** * This is used to unsubscribe DeclarativeDeviceManager from the DeviceManager events. */ this.unsubscribe = () => { this._deviceManager.off('videoDevicesUpdated', this.videoDevicesUpdated); this._deviceManager.off('audioDevicesUpdated', this.audioDevicesUpdated); this._deviceManager.off('selectedMicrophoneChanged', this.selectedMicrophoneChanged); this._deviceManager.off('selectedSpeakerChanged', this.selectedSpeakerChanged); if (navigator.permissions) { // Unsubscribe from browser camera permissions changes try { navigator.permissions.query({ name: 'camera' }).then((cameraPermissions) => { cameraPermissions.removeEventListener('change', this.permissionsApiStateChangeHandler); }); } catch (e) { console.info('Could not Unsubscribe to Permissions API Camera changed events, API is not supported by browser', e); } // Unsubscribe from browser microphone permissions changes try { navigator.permissions.query({ name: 'microphone' }).then((micPermissions) => { micPermissions.removeEventListener('change', this.permissionsApiStateChangeHandler); }); } catch (e) { console.info('Could not Unsubscribe to Permissions API Camera changed events, API is not supported by browser', e); } } }; this.permissionsApiStateChangeHandler = () => __awaiter(this, void 0, void 0, function* () { yield this.updateDevicePermissionState(); }); /** * Used to set a camera inside the proxy device manager. * * @param videoDeviceInfo VideoDeviceInfo */ this.selectCamera = (videoDeviceInfo) => { this._context.setDeviceManagerSelectedCamera(videoDeviceInfo); }; this.videoDevicesUpdated = () => __awaiter(this, void 0, void 0, function* () { // Device Manager always has a camera with '' name if there are no real camera devices available. // We don't want to show that in the UI. const realCameras = (yield this._deviceManager.getCameras()).filter(c => !!c.name); this._context.setDeviceManagerCameras(dedupeById(realCameras)); }); this.audioDevicesUpdated = () => __awaiter(this, void 0, void 0, function* () { this._context.setDeviceManagerMicrophones(dedupeById(yield this._deviceManager.getMicrophones())); if (this._deviceManager.isSpeakerSelectionAvailable) { this._context.setDeviceManagerSpeakers(dedupeById(yield this._deviceManager.getSpeakers())); } }); this.selectedMicrophoneChanged = () => { this._context.setDeviceManagerSelectedMicrophone(this._deviceManager.selectedMicrophone); }; this.selectedSpeakerChanged = () => { this._context.setDeviceManagerSelectedSpeaker(this._deviceManager.selectedSpeaker); }; this.updateDevicePermissionState = (sdkDeviceAccessState) => __awaiter(this, void 0, void 0, function* () { let hasCameraPermission = !!(sdkDeviceAccessState === null || sdkDeviceAccessState === void 0 ? void 0 : sdkDeviceAccessState.video); let hasMicPermission = !!(sdkDeviceAccessState === null || sdkDeviceAccessState === void 0 ? void 0 : sdkDeviceAccessState.audio); // Supplement the SDK values with values from the Permissions API to get a better understanding of the device // permission state. The SDK only uses the getUserMedia API to determine the device permission state. However, // this returns false if the camera is in use by another application. The Permissions API can provide more // information about the device permission state, but is not supported yet in Firefox or Android WebView. // Note: It also has the limitation where it cannot detect if the device is blocked by the Operating System // permissions. if (navigator.permissions) { try { const [cameraPermissions, micPermissions] = yield Promise.all([navigator.permissions.query({ name: 'camera' }), navigator.permissions.query({ name: 'microphone' })]); // Use logical OR to combine the SDK state with the Permissions API state. // This way we can get a more accurate picture of the device permission state. // If the SDK state is 'granted', we don't need to check the Permissions API state. hasCameraPermission || (hasCameraPermission = cameraPermissions.state === 'granted'); hasMicPermission || (hasMicPermission = micPermissions.state === 'granted'); } catch (e) { console.info('Permissions API is not supported by browser', e); } } this._context.setDeviceManagerDeviceAccess({ video: hasCameraPermission, audio: hasMicPermission }); this.setDeviceManager(); }); this._deviceManager = deviceManager; this._context = context; this.setDeviceManager(); this.subscribe(); } // eslint-disable-next-line @typescript-eslint/no-explicit-any get(target, prop) { switch (prop) { case 'getCameras': { return this._context.withAsyncErrorTeedToState(() => { return target.getCameras().then((cameras) => { // Device Manager always has a camera with '' name if there are no real camera devices available. // We don't want to show that in the UI. const realCameras = cameras.filter(c => !!c.name); this._context.setDeviceManagerCameras(dedupeById(realCameras)); return realCameras; }); }, 'DeviceManager.getCameras'); } case 'getMicrophones': { return this._context.withAsyncErrorTeedToState(() => { return target.getMicrophones().then((microphones) => { this._context.setDeviceManagerMicrophones(dedupeById(microphones)); return microphones; }); }, 'DeviceManager.getMicrophones'); } case 'getSpeakers': { return this._context.withAsyncErrorTeedToState(() => { return target.getSpeakers().then((speakers) => { this._context.setDeviceManagerSpeakers(dedupeById(speakers)); return speakers; }); }, 'DeviceManager.getSpeakers'); } case 'selectMicrophone': { return this._context.withAsyncErrorTeedToState((...args) => { return target.selectMicrophone(...args).then(() => { this._context.setDeviceManagerSelectedMicrophone(target.selectedMicrophone); }); }, 'DeviceManager.selectMicrophone'); } case 'selectSpeaker': { return this._context.withAsyncErrorTeedToState((...args) => { return target.selectSpeaker(...args).then(() => { this._context.setDeviceManagerSelectedSpeaker(target.selectedSpeaker); }); }, 'DeviceManager.selectSpeaker'); } case 'askDevicePermission': { return this._context.withAsyncErrorTeedToState((...args) => { return target.askDevicePermission(...args).then((deviceAccess) => __awaiter(this, void 0, void 0, function* () { yield this.updateDevicePermissionState(deviceAccess); return deviceAccess; })); }, 'DeviceManager.askDevicePermission'); } default: return Reflect.get(target, prop); } } } // TODO: Remove this when SDK no longer returns duplicate audio and video devices /** Helper function to dedupe duplicate audio and video devices obtained from SDK */ const dedupeById = (devices) => { const ids = new Set(); const uniqueDevices = []; devices.forEach((device) => { if (!ids.has(device.id)) { uniqueDevices.push(device); ids.add(device.id); } }); return uniqueDevices; }; /** * Creates a declarative DeviceManager by proxying DeviceManager with ProxyDeviceManager. The declarative DeviceManager * will put state updates in the given context. * * @param deviceManager - DeviceManager from SDK * @param context - CallContext from StatefulCallClient * * @private */ export const deviceManagerDeclaratify = (deviceManager, context, internalContext) => { const proxyDeviceManager = new ProxyDeviceManager(deviceManager, context); Object.defineProperty(deviceManager, 'unsubscribe', { configurable: false, value: () => proxyDeviceManager.unsubscribe() }); Object.defineProperty(deviceManager, 'selectCamera', { configurable: false, value: (videoDeviceInfo) => proxyDeviceManager.selectCamera(videoDeviceInfo) }); Object.defineProperty(deviceManager, 'getUnparentedVideoStreams', { configurable: false, value: () => internalContext.getUnparentedRenderInfos() }); return new Proxy(deviceManager, proxyDeviceManager); }; //# sourceMappingURL=DeviceManagerDeclarative.js.map