@azure/communication-react
Version:
React library for building modern communication user experiences utilizing Azure Communication Services
259 lines • 14 kB
JavaScript
// 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