2018-06-20 20:19:53 +00:00
|
|
|
// @flow
|
2019-03-27 17:30:56 +00:00
|
|
|
|
|
|
|
import type { Dispatch } from 'redux';
|
|
|
|
|
2019-03-21 12:33:40 +00:00
|
|
|
import {
|
2019-03-27 17:30:56 +00:00
|
|
|
addPendingDeviceRequest,
|
|
|
|
areDeviceLabelsInitialized,
|
2019-03-21 12:33:40 +00:00
|
|
|
getAudioOutputDeviceId,
|
2019-03-25 11:33:41 +00:00
|
|
|
getAvailableDevices,
|
2019-03-27 17:30:56 +00:00
|
|
|
getDeviceIdByLabel,
|
2019-03-25 11:33:41 +00:00
|
|
|
groupDevicesByKind,
|
2021-06-28 11:51:40 +00:00
|
|
|
setAudioInputDeviceAndUpdateSettings,
|
2021-07-30 11:51:47 +00:00
|
|
|
setAudioOutputDevice,
|
2021-06-28 11:51:40 +00:00
|
|
|
setVideoInputDeviceAndUpdateSettings
|
2019-03-21 12:33:40 +00:00
|
|
|
} from '../base/devices';
|
2022-05-23 18:47:07 +00:00
|
|
|
import { isIosMobileBrowser } from '../base/environment/utils';
|
2022-05-24 16:24:22 +00:00
|
|
|
import JitsiMeetJS from '../base/lib-jitsi-meet';
|
2018-06-20 20:19:53 +00:00
|
|
|
import { toState } from '../base/redux';
|
2019-05-07 08:53:01 +00:00
|
|
|
import {
|
|
|
|
getUserSelectedCameraDeviceId,
|
|
|
|
getUserSelectedMicDeviceId,
|
|
|
|
getUserSelectedOutputDeviceId
|
|
|
|
} from '../base/settings';
|
2018-06-20 20:19:53 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the properties for the device selection dialog from Redux state.
|
|
|
|
*
|
|
|
|
* @param {(Function|Object)} stateful -The (whole) redux state, or redux's
|
|
|
|
* {@code getState} function to be used to retrieve the state.
|
2022-05-24 19:04:21 +00:00
|
|
|
* @param {boolean} isDisplayedOnWelcomePage - Indicates whether the device selection dialog is displayed on the
|
|
|
|
* welcome page or not.
|
2018-06-20 20:19:53 +00:00
|
|
|
* @returns {Object} - The properties for the device selection dialog.
|
|
|
|
*/
|
2022-05-24 19:04:21 +00:00
|
|
|
export function getDeviceSelectionDialogProps(stateful: Object | Function, isDisplayedOnWelcomePage) {
|
2022-05-23 18:47:07 +00:00
|
|
|
// On mobile Safari because of https://bugs.webkit.org/show_bug.cgi?id=179363#c30, the old track is stopped
|
|
|
|
// by the browser when a new track is created for preview. That's why we are disabling all previews.
|
2022-05-24 16:24:22 +00:00
|
|
|
const disablePreviews = isIosMobileBrowser();
|
2022-05-23 18:47:07 +00:00
|
|
|
|
2018-06-20 20:19:53 +00:00
|
|
|
const state = toState(stateful);
|
|
|
|
const settings = state['features/base/settings'];
|
2021-02-02 00:20:39 +00:00
|
|
|
const { permissions } = state['features/base/devices'];
|
2022-05-24 19:04:21 +00:00
|
|
|
const inputDeviceChangeSupported = JitsiMeetJS.mediaDevices.isDeviceChangeAvailable('input');
|
2021-09-09 17:23:36 +00:00
|
|
|
const speakerChangeSupported = JitsiMeetJS.mediaDevices.isDeviceChangeAvailable('output');
|
|
|
|
const userSelectedCamera = getUserSelectedCameraDeviceId(state);
|
|
|
|
const userSelectedMic = getUserSelectedMicDeviceId(state);
|
2022-05-24 19:04:21 +00:00
|
|
|
|
|
|
|
// When the previews are disabled we don't need multiple audio input support in order to chage the mic. This is the
|
|
|
|
// case for Safari on iOS.
|
|
|
|
let disableAudioInputChange
|
|
|
|
= !JitsiMeetJS.mediaDevices.isMultipleAudioInputSupported() && !(disablePreviews && inputDeviceChangeSupported);
|
|
|
|
let disableVideoInputSelect = !inputDeviceChangeSupported;
|
2022-04-25 19:01:10 +00:00
|
|
|
let selectedAudioInputId = settings.micDeviceId;
|
2019-05-01 14:13:25 +00:00
|
|
|
let selectedAudioOutputId = getAudioOutputDeviceId();
|
2022-04-25 19:01:10 +00:00
|
|
|
let selectedVideoInputId = settings.cameraDeviceId;
|
2018-06-20 20:19:53 +00:00
|
|
|
|
2019-05-01 14:13:25 +00:00
|
|
|
// audio input change will be a problem only when we are in a
|
|
|
|
// conference and this is not supported, when we open device selection on
|
|
|
|
// welcome page changing input devices will not be a problem
|
|
|
|
// on welcome page we also show only what we have saved as user selected devices
|
2022-05-24 19:04:21 +00:00
|
|
|
if (isDisplayedOnWelcomePage) {
|
2019-05-01 14:13:25 +00:00
|
|
|
disableAudioInputChange = false;
|
2021-09-09 17:23:36 +00:00
|
|
|
disableVideoInputSelect = false;
|
|
|
|
selectedAudioInputId = userSelectedMic;
|
2019-05-07 08:53:01 +00:00
|
|
|
selectedAudioOutputId = getUserSelectedOutputDeviceId(state);
|
2021-09-09 17:23:36 +00:00
|
|
|
selectedVideoInputId = userSelectedCamera;
|
2019-05-01 14:13:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// we fill the device selection dialog with the devices that are currently
|
|
|
|
// used or if none are currently used with what we have in settings(user selected)
|
2018-06-20 20:19:53 +00:00
|
|
|
return {
|
2019-03-28 16:29:30 +00:00
|
|
|
availableDevices: state['features/base/devices'].availableDevices,
|
2019-05-01 14:13:25 +00:00
|
|
|
disableAudioInputChange,
|
2021-09-09 17:23:36 +00:00
|
|
|
disableDeviceChange: !JitsiMeetJS.mediaDevices.isDeviceChangeAvailable(),
|
|
|
|
disableVideoInputSelect,
|
2021-02-02 00:20:39 +00:00
|
|
|
hasAudioPermission: permissions.audio,
|
|
|
|
hasVideoPermission: permissions.video,
|
2022-05-23 18:47:07 +00:00
|
|
|
hideAudioInputPreview: disableAudioInputChange || !JitsiMeetJS.isCollectingLocalStats() || disablePreviews,
|
|
|
|
hideAudioOutputPreview: !speakerChangeSupported || disablePreviews,
|
2021-09-09 17:23:36 +00:00
|
|
|
hideAudioOutputSelect: !speakerChangeSupported,
|
2022-05-24 19:04:21 +00:00
|
|
|
hideVideoInputPreview: !inputDeviceChangeSupported || disablePreviews,
|
2019-05-01 14:13:25 +00:00
|
|
|
selectedAudioInputId,
|
|
|
|
selectedAudioOutputId,
|
|
|
|
selectedVideoInputId
|
2018-06-20 20:19:53 +00:00
|
|
|
};
|
|
|
|
}
|
2019-03-21 12:33:40 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Processes device requests from external applications.
|
|
|
|
*
|
|
|
|
* @param {Dispatch} dispatch - The redux {@code dispatch} function.
|
|
|
|
* @param {Function} getState - The redux function that gets/retrieves the redux
|
|
|
|
* state.
|
|
|
|
* @param {Object} request - The request to be processed.
|
|
|
|
* @param {Function} responseCallback - The callback that will send the
|
|
|
|
* response.
|
2019-03-28 16:29:30 +00:00
|
|
|
* @returns {boolean} - True if the request has been processed and false otherwise.
|
2019-03-21 12:33:40 +00:00
|
|
|
*/
|
2019-03-28 16:29:30 +00:00
|
|
|
export function processExternalDeviceRequest( // eslint-disable-line max-params
|
2019-03-27 17:30:56 +00:00
|
|
|
dispatch: Dispatch<any>,
|
2019-03-25 11:33:41 +00:00
|
|
|
getState: Function,
|
|
|
|
request: Object,
|
|
|
|
responseCallback: Function) {
|
2019-03-28 16:29:30 +00:00
|
|
|
if (request.type !== 'devices') {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
const state = getState();
|
|
|
|
const settings = state['features/base/settings'];
|
|
|
|
let result = true;
|
|
|
|
|
|
|
|
switch (request.name) {
|
|
|
|
case 'isDeviceListAvailable':
|
|
|
|
responseCallback(JitsiMeetJS.mediaDevices.isDeviceListAvailable());
|
|
|
|
break;
|
|
|
|
case 'isDeviceChangeAvailable':
|
|
|
|
responseCallback(
|
|
|
|
JitsiMeetJS.mediaDevices.isDeviceChangeAvailable(
|
|
|
|
request.deviceType));
|
|
|
|
break;
|
|
|
|
case 'isMultipleAudioInputSupported':
|
|
|
|
responseCallback(JitsiMeetJS.isMultipleAudioInputSupported());
|
|
|
|
break;
|
|
|
|
case 'getCurrentDevices':
|
|
|
|
dispatch(getAvailableDevices()).then(devices => {
|
|
|
|
if (areDeviceLabelsInitialized(state)) {
|
2019-04-03 15:06:41 +00:00
|
|
|
const deviceDescriptions = {
|
|
|
|
audioInput: undefined,
|
|
|
|
audioOutput: undefined,
|
|
|
|
videoInput: undefined
|
|
|
|
};
|
|
|
|
const currentlyUsedDeviceIds = new Set([
|
|
|
|
getAudioOutputDeviceId(),
|
|
|
|
settings.micDeviceId,
|
|
|
|
settings.cameraDeviceId
|
|
|
|
]);
|
2019-03-28 16:29:30 +00:00
|
|
|
|
|
|
|
devices.forEach(device => {
|
2019-04-03 15:06:41 +00:00
|
|
|
const { deviceId, kind } = device;
|
2019-03-28 16:29:30 +00:00
|
|
|
|
2019-04-03 15:06:41 +00:00
|
|
|
if (currentlyUsedDeviceIds.has(deviceId)) {
|
|
|
|
switch (kind) {
|
|
|
|
case 'audioinput':
|
|
|
|
deviceDescriptions.audioInput = device;
|
|
|
|
break;
|
|
|
|
case 'audiooutput':
|
|
|
|
deviceDescriptions.audioOutput = device;
|
|
|
|
break;
|
|
|
|
case 'videoinput':
|
|
|
|
deviceDescriptions.videoInput = device;
|
|
|
|
break;
|
|
|
|
}
|
2019-03-28 16:29:30 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2019-04-03 15:06:41 +00:00
|
|
|
responseCallback(deviceDescriptions);
|
2019-03-28 16:29:30 +00:00
|
|
|
} else {
|
|
|
|
// The labels are not available if the A/V permissions are
|
|
|
|
// not yet granted.
|
2019-03-27 17:30:56 +00:00
|
|
|
dispatch(addPendingDeviceRequest({
|
|
|
|
type: 'devices',
|
2019-03-28 16:29:30 +00:00
|
|
|
name: 'getCurrentDevices',
|
2019-03-27 17:30:56 +00:00
|
|
|
responseCallback
|
|
|
|
}));
|
|
|
|
}
|
2019-03-28 16:29:30 +00:00
|
|
|
});
|
2019-03-27 17:30:56 +00:00
|
|
|
|
2019-03-28 16:29:30 +00:00
|
|
|
break;
|
|
|
|
case 'getAvailableDevices':
|
|
|
|
dispatch(getAvailableDevices()).then(devices => {
|
|
|
|
if (areDeviceLabelsInitialized(state)) {
|
|
|
|
responseCallback(groupDevicesByKind(devices));
|
2019-03-27 17:30:56 +00:00
|
|
|
} else {
|
2019-03-28 16:29:30 +00:00
|
|
|
// The labels are not available if the A/V permissions are
|
|
|
|
// not yet granted.
|
|
|
|
dispatch(addPendingDeviceRequest({
|
|
|
|
type: 'devices',
|
|
|
|
name: 'getAvailableDevices',
|
|
|
|
responseCallback
|
|
|
|
}));
|
2019-03-21 12:33:40 +00:00
|
|
|
}
|
2019-03-28 16:29:30 +00:00
|
|
|
});
|
2019-03-21 12:33:40 +00:00
|
|
|
|
2019-03-28 16:29:30 +00:00
|
|
|
break;
|
|
|
|
case 'setDevice': {
|
|
|
|
const { device } = request;
|
|
|
|
|
2020-08-25 22:31:38 +00:00
|
|
|
if (!areDeviceLabelsInitialized(state)) {
|
2019-03-28 16:29:30 +00:00
|
|
|
dispatch(addPendingDeviceRequest({
|
|
|
|
type: 'devices',
|
|
|
|
name: 'setDevice',
|
|
|
|
device,
|
|
|
|
responseCallback
|
|
|
|
}));
|
|
|
|
|
|
|
|
return true;
|
2019-03-21 12:33:40 +00:00
|
|
|
}
|
|
|
|
|
2019-03-28 16:29:30 +00:00
|
|
|
const { label, id } = device;
|
2019-04-08 17:03:45 +00:00
|
|
|
const deviceId = label
|
|
|
|
? getDeviceIdByLabel(state, device.label, device.kind)
|
|
|
|
: id;
|
2019-03-28 16:29:30 +00:00
|
|
|
|
|
|
|
if (deviceId) {
|
|
|
|
switch (device.kind) {
|
2021-07-30 11:51:47 +00:00
|
|
|
case 'audioinput':
|
2021-06-28 11:51:40 +00:00
|
|
|
dispatch(setAudioInputDeviceAndUpdateSettings(deviceId));
|
2019-03-28 16:29:30 +00:00
|
|
|
break;
|
|
|
|
case 'audiooutput':
|
2021-07-30 11:51:47 +00:00
|
|
|
dispatch(setAudioOutputDevice(deviceId));
|
2019-03-28 16:29:30 +00:00
|
|
|
break;
|
|
|
|
case 'videoinput':
|
2021-06-28 11:51:40 +00:00
|
|
|
dispatch(setVideoInputDeviceAndUpdateSettings(deviceId));
|
2019-03-28 16:29:30 +00:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
result = false;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
result = false;
|
2019-03-21 12:33:40 +00:00
|
|
|
}
|
|
|
|
|
2019-03-28 16:29:30 +00:00
|
|
|
responseCallback(result);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
return false;
|
2019-03-21 12:33:40 +00:00
|
|
|
}
|
|
|
|
|
2019-03-28 16:29:30 +00:00
|
|
|
return true;
|
2019-03-21 12:33:40 +00:00
|
|
|
}
|
2019-03-28 16:29:30 +00:00
|
|
|
|