286 lines
9.4 KiB
TypeScript
286 lines
9.4 KiB
TypeScript
import { IReduxState } from '../../app/types';
|
|
import JitsiMeetJS from '../lib-jitsi-meet';
|
|
import { updateSettings } from '../settings/actions';
|
|
import { ISettingsState } from '../settings/reducer';
|
|
import { parseURLParams } from '../util/parseURLParams';
|
|
|
|
import logger from './logger';
|
|
import { IDevicesState } from './types';
|
|
|
|
export * from './functions.any';
|
|
|
|
const webrtcKindToJitsiKindTranslator = {
|
|
audioinput: 'audioInput',
|
|
audiooutput: 'audioOutput',
|
|
videoinput: 'videoInput'
|
|
};
|
|
|
|
/**
|
|
* Detects the use case when the labels are not available if the A/V permissions
|
|
* are not yet granted.
|
|
*
|
|
* @param {Object} state - The redux state.
|
|
* @returns {boolean} - True if the labels are already initialized and false
|
|
* otherwise.
|
|
*/
|
|
export function areDeviceLabelsInitialized(state: IReduxState) {
|
|
// TODO: Replace with something that doesn't use APP when the conference.js logic is reactified.
|
|
if (APP.conference._localTracksInitialized) {
|
|
return true;
|
|
}
|
|
|
|
for (const type of [ 'audioInput', 'audioOutput', 'videoInput' ]) {
|
|
const availableDevices = state['features/base/devices'].availableDevices;
|
|
|
|
if ((availableDevices[type as keyof typeof availableDevices] || []).find(d => Boolean(d.label))) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Get device id of the audio output device which is currently in use.
|
|
* Empty string stands for default device.
|
|
*
|
|
* @returns {string}
|
|
*/
|
|
export function getAudioOutputDeviceId() {
|
|
return JitsiMeetJS.mediaDevices.getAudioOutputDevice();
|
|
}
|
|
|
|
/**
|
|
* Finds the real device id of the default device of the given type.
|
|
*
|
|
* @param {Object} state - The redux state.
|
|
* @param {*} kind - The type of the device. One of "audioInput",
|
|
* "audioOutput", and "videoInput". Also supported is all lowercase versions
|
|
* of the preceding types.
|
|
* @returns {string|undefined}
|
|
*/
|
|
export function getDefaultDeviceId(state: IReduxState, kind: string) {
|
|
const kindToSearch = webrtcKindToJitsiKindTranslator[kind as keyof typeof webrtcKindToJitsiKindTranslator] || kind;
|
|
const availableDevices = state['features/base/devices'].availableDevices;
|
|
const defaultDevice = (availableDevices[kindToSearch as keyof typeof availableDevices] || [])
|
|
.find(d => d.deviceId === 'default');
|
|
|
|
// Find the device with a matching group id.
|
|
const matchingDevice = (availableDevices[kindToSearch as keyof typeof availableDevices] || [])
|
|
.find(d => d.deviceId !== 'default' && d.groupId === defaultDevice?.groupId);
|
|
|
|
if (matchingDevice) {
|
|
return matchingDevice.deviceId;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Finds a device with a label that matches the passed label and returns its id.
|
|
*
|
|
* @param {Object} state - The redux state.
|
|
* @param {string} label - The label.
|
|
* @param {string} kind - The type of the device. One of "audioInput",
|
|
* "audioOutput", and "videoInput". Also supported is all lowercase versions
|
|
* of the preceding types.
|
|
* @returns {string|undefined}
|
|
*/
|
|
export function getDeviceIdByLabel(state: IReduxState, label: string, kind: string) {
|
|
const kindToSearch = webrtcKindToJitsiKindTranslator[kind as keyof typeof webrtcKindToJitsiKindTranslator] || kind;
|
|
|
|
const availableDevices = state['features/base/devices'].availableDevices;
|
|
const device
|
|
= (availableDevices[kindToSearch as keyof typeof availableDevices] || [])
|
|
.find(d => d.label === label);
|
|
|
|
if (device) {
|
|
return device.deviceId;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Finds a device with a label that matches the passed id and returns its label.
|
|
*
|
|
* @param {Object} state - The redux state.
|
|
* @param {string} id - The device id.
|
|
* @param {string} kind - The type of the device. One of "audioInput",
|
|
* "audioOutput", and "videoInput". Also supported is all lowercase versions
|
|
* of the preceding types.
|
|
* @returns {string|undefined}
|
|
*/
|
|
export function getDeviceLabelById(state: IReduxState, id: string, kind: string) {
|
|
const kindToSearch = webrtcKindToJitsiKindTranslator[kind as keyof typeof webrtcKindToJitsiKindTranslator] || kind;
|
|
|
|
const availableDevices = state['features/base/devices'].availableDevices;
|
|
const device
|
|
= (availableDevices[kindToSearch as keyof typeof availableDevices] || [])
|
|
.find(d => d.deviceId === id);
|
|
|
|
if (device) {
|
|
return device.label;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the devices set in the URL.
|
|
*
|
|
* @param {Object} state - The redux state.
|
|
* @returns {Object|undefined}
|
|
*/
|
|
export function getDevicesFromURL(state: IReduxState) {
|
|
const urlParams
|
|
= parseURLParams(state['features/base/connection'].locationURL ?? '');
|
|
|
|
const audioOutput = urlParams['devices.audioOutput'];
|
|
const videoInput = urlParams['devices.videoInput'];
|
|
const audioInput = urlParams['devices.audioInput'];
|
|
|
|
if (!audioOutput && !videoInput && !audioInput) {
|
|
return undefined;
|
|
}
|
|
|
|
const devices: IDevicesState['availableDevices'] = {};
|
|
|
|
audioOutput && (devices.audioOutput = audioOutput);
|
|
videoInput && (devices.videoInput = videoInput);
|
|
audioInput && (devices.audioInput = audioInput);
|
|
|
|
return devices;
|
|
}
|
|
|
|
/**
|
|
* Converts an array of media devices into an object organized by device kind.
|
|
*
|
|
* @param {Array<MediaDeviceInfo>} devices - Available media devices.
|
|
* @private
|
|
* @returns {Object} An object with the media devices split by type. The keys
|
|
* are device type and the values are arrays with devices matching the device
|
|
* type.
|
|
*/
|
|
export function groupDevicesByKind(devices: MediaDeviceInfo[]): IDevicesState['availableDevices'] {
|
|
return {
|
|
audioInput: devices.filter(device => device.kind === 'audioinput'),
|
|
audioOutput: devices.filter(device => device.kind === 'audiooutput'),
|
|
videoInput: devices.filter(device => device.kind === 'videoinput')
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Filters audio devices from a list of MediaDeviceInfo objects.
|
|
*
|
|
* @param {Array<MediaDeviceInfo>} devices - Unfiltered media devices.
|
|
* @private
|
|
* @returns {Array<MediaDeviceInfo>} Filtered audio devices.
|
|
*/
|
|
export function filterAudioDevices(devices: MediaDeviceInfo[]) {
|
|
return devices.filter(device => device.kind === 'audioinput');
|
|
}
|
|
|
|
/**
|
|
* We want to strip any device details that are not very user friendly, like usb ids put in brackets at the end.
|
|
*
|
|
* @param {string} label - Device label to format.
|
|
*
|
|
* @returns {string} - Formatted string.
|
|
*/
|
|
export function formatDeviceLabel(label: string) {
|
|
|
|
let formattedLabel = label;
|
|
|
|
// Remove braked description at the end as it contains non user friendly strings i.e.
|
|
// Microsoft® LifeCam HD-3000 (045e:0779:31dg:d1231)
|
|
const ix = formattedLabel.lastIndexOf('(');
|
|
|
|
if (ix !== -1) {
|
|
formattedLabel = formattedLabel.substr(0, ix);
|
|
}
|
|
|
|
return formattedLabel;
|
|
}
|
|
|
|
/**
|
|
* Returns a list of objects containing all the microphone device ids and labels.
|
|
*
|
|
* @param {Object} state - The state of the application.
|
|
* @returns {Object[]}
|
|
*/
|
|
export function getAudioInputDeviceData(state: IReduxState) {
|
|
return state['features/base/devices'].availableDevices.audioInput?.map(
|
|
({ deviceId, label }) => {
|
|
return {
|
|
deviceId,
|
|
label
|
|
};
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Returns a list of objectes containing all the output device ids and labels.
|
|
*
|
|
* @param {Object} state - The state of the application.
|
|
* @returns {Object[]}
|
|
*/
|
|
export function getAudioOutputDeviceData(state: IReduxState) {
|
|
return state['features/base/devices'].availableDevices.audioOutput?.map(
|
|
({ deviceId, label }) => {
|
|
return {
|
|
deviceId,
|
|
label
|
|
};
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Returns a list of all the camera device ids.
|
|
*
|
|
* @param {Object} state - The state of the application.
|
|
* @returns {string[]}
|
|
*/
|
|
export function getVideoDeviceIds(state: IReduxState) {
|
|
return state['features/base/devices'].availableDevices.videoInput?.map(({ deviceId }) => deviceId);
|
|
}
|
|
|
|
/**
|
|
* Set device id of the audio output device which is currently in use.
|
|
* Empty string stands for default device.
|
|
*
|
|
* @param {string} newId - New audio output device id.
|
|
* @param {Function} dispatch - The Redux dispatch function.
|
|
* @param {boolean} userSelection - Whether this is a user selection update.
|
|
* @param {?string} newLabel - New audio output device label to store.
|
|
* @returns {Promise}
|
|
*/
|
|
export function setAudioOutputDeviceId(
|
|
newId = 'default',
|
|
dispatch: Function,
|
|
userSelection = false,
|
|
newLabel?: string): Promise<any> {
|
|
|
|
logger.debug(`setAudioOutputDevice: ${String(newLabel)}[${newId}]`);
|
|
|
|
if (!JitsiMeetJS.mediaDevices.isDeviceChangeAvailable('output')) {
|
|
logger.warn('Adjusting audio output is not supported');
|
|
|
|
return Promise.resolve();
|
|
}
|
|
|
|
return JitsiMeetJS.mediaDevices.setAudioOutputDevice(newId)
|
|
.then(() => {
|
|
const newSettings: Partial<ISettingsState> = {
|
|
audioOutputDeviceId: newId,
|
|
userSelectedAudioOutputDeviceId: undefined,
|
|
userSelectedAudioOutputDeviceLabel: undefined
|
|
};
|
|
|
|
if (userSelection) {
|
|
newSettings.userSelectedAudioOutputDeviceId = newId;
|
|
newSettings.userSelectedAudioOutputDeviceLabel = newLabel;
|
|
} else {
|
|
// a flow workaround, I needed to add 'userSelectedAudioOutputDeviceId: undefined'
|
|
delete newSettings.userSelectedAudioOutputDeviceId;
|
|
delete newSettings.userSelectedAudioOutputDeviceLabel;
|
|
}
|
|
|
|
return dispatch(updateSettings(newSettings));
|
|
});
|
|
}
|