This commit is contained in:
295
modules/devices/mediaDeviceHelper.js
Normal file
295
modules/devices/mediaDeviceHelper.js
Normal file
@@ -0,0 +1,295 @@
|
||||
/* global APP, JitsiMeetJS */
|
||||
|
||||
import {
|
||||
notifyCameraError,
|
||||
notifyMicError
|
||||
} from '../../react/features/base/devices/actions.web';
|
||||
import {
|
||||
flattenAvailableDevices,
|
||||
getAudioOutputDeviceId
|
||||
} from '../../react/features/base/devices/functions.web';
|
||||
import { updateSettings } from '../../react/features/base/settings/actions';
|
||||
import {
|
||||
getUserSelectedCameraDeviceId,
|
||||
getUserSelectedMicDeviceId,
|
||||
getUserSelectedOutputDeviceId
|
||||
} from '../../react/features/base/settings/functions';
|
||||
|
||||
/**
|
||||
* Determines if currently selected audio output device should be changed after
|
||||
* list of available devices has been changed.
|
||||
* @param {MediaDeviceInfo[]} newDevices
|
||||
* @returns {string|undefined} - ID of new audio output device to use, undefined
|
||||
* if audio output device should not be changed.
|
||||
*/
|
||||
function getNewAudioOutputDevice(newDevices) {
|
||||
if (!JitsiMeetJS.mediaDevices.isDeviceChangeAvailable('output')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedAudioOutputDeviceId = getAudioOutputDeviceId();
|
||||
const availableAudioOutputDevices = newDevices.filter(
|
||||
d => d.kind === 'audiooutput');
|
||||
|
||||
// Switch to 'default' audio output device if we don't have the selected one
|
||||
// available anymore.
|
||||
if (selectedAudioOutputDeviceId !== 'default'
|
||||
&& !availableAudioOutputDevices.find(d =>
|
||||
d.deviceId === selectedAudioOutputDeviceId)) {
|
||||
return 'default';
|
||||
}
|
||||
|
||||
const preferredAudioOutputDeviceId = getUserSelectedOutputDeviceId(APP.store.getState());
|
||||
|
||||
// if the preferred one is not the selected and is available in the new devices
|
||||
// we want to use it as it was just added
|
||||
if (preferredAudioOutputDeviceId
|
||||
&& preferredAudioOutputDeviceId !== selectedAudioOutputDeviceId
|
||||
&& availableAudioOutputDevices.find(d => d.deviceId === preferredAudioOutputDeviceId)) {
|
||||
return preferredAudioOutputDeviceId;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if currently selected audio input device should be changed after
|
||||
* list of available devices has been changed.
|
||||
* @param {MediaDeviceInfo[]} newDevices
|
||||
* @param {JitsiLocalTrack} localAudio
|
||||
* @param {boolean} newLabel
|
||||
* @returns {string|undefined} - ID of new microphone device to use, undefined
|
||||
* if audio input device should not be changed.
|
||||
*/
|
||||
function getNewAudioInputDevice(newDevices, localAudio, newLabel) {
|
||||
const availableAudioInputDevices = newDevices.filter(
|
||||
d => d.kind === 'audioinput');
|
||||
const selectedAudioInputDeviceId = getUserSelectedMicDeviceId(APP.store.getState());
|
||||
const selectedAudioInputDevice = availableAudioInputDevices.find(
|
||||
d => d.deviceId === selectedAudioInputDeviceId);
|
||||
const localAudioDeviceId = localAudio?.getDeviceId();
|
||||
const localAudioDevice = availableAudioInputDevices.find(
|
||||
d => d.deviceId === localAudioDeviceId);
|
||||
|
||||
// Here we handle case when no device was initially plugged, but
|
||||
// then it's connected OR new device was connected when previous
|
||||
// track has ended.
|
||||
if (!localAudio || localAudio.disposed || localAudio.isEnded()) {
|
||||
// If we have new audio device and permission to use it was granted
|
||||
// (label is not an empty string), then we will try to use the first
|
||||
// available device.
|
||||
if (selectedAudioInputDevice && selectedAudioInputDeviceId) {
|
||||
return selectedAudioInputDeviceId;
|
||||
} else if (availableAudioInputDevices.length
|
||||
&& availableAudioInputDevices[0].label !== '') {
|
||||
return availableAudioInputDevices[0].deviceId;
|
||||
}
|
||||
} else if (selectedAudioInputDevice
|
||||
&& selectedAudioInputDeviceId !== localAudioDeviceId) {
|
||||
|
||||
if (newLabel) {
|
||||
// If a Firefox user with manual permission prompt chose a different
|
||||
// device from what we have stored as the preferred device we accept
|
||||
// and store that as the new preferred device.
|
||||
APP.store.dispatch(updateSettings({
|
||||
userSelectedMicDeviceId: localAudioDeviceId,
|
||||
userSelectedMicDeviceLabel: localAudioDevice.label
|
||||
}));
|
||||
} else {
|
||||
// And here we handle case when we already have some device working,
|
||||
// but we plug-in a "preferred" (previously selected in settings, stored
|
||||
// in local storage) device.
|
||||
return selectedAudioInputDeviceId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if currently selected video input device should be changed after
|
||||
* list of available devices has been changed.
|
||||
* @param {MediaDeviceInfo[]} newDevices
|
||||
* @param {JitsiLocalTrack} localVideo
|
||||
* @param {boolean} newLabel
|
||||
* @returns {string|undefined} - ID of new camera device to use, undefined
|
||||
* if video input device should not be changed.
|
||||
*/
|
||||
function getNewVideoInputDevice(newDevices, localVideo, newLabel) {
|
||||
const availableVideoInputDevices = newDevices.filter(
|
||||
d => d.kind === 'videoinput');
|
||||
const selectedVideoInputDeviceId = getUserSelectedCameraDeviceId(APP.store.getState());
|
||||
const selectedVideoInputDevice = availableVideoInputDevices.find(
|
||||
d => d.deviceId === selectedVideoInputDeviceId);
|
||||
const localVideoDeviceId = localVideo?.getDeviceId();
|
||||
const localVideoDevice = availableVideoInputDevices.find(
|
||||
d => d.deviceId === localVideoDeviceId);
|
||||
|
||||
// Here we handle case when no video input device was initially plugged,
|
||||
// but then device is connected OR new device was connected when
|
||||
// previous track has ended.
|
||||
if (!localVideo || localVideo.disposed || localVideo.isEnded()) {
|
||||
// If we have new video device and permission to use it was granted
|
||||
// (label is not an empty string), then we will try to use the first
|
||||
// available device.
|
||||
if (selectedVideoInputDevice && selectedVideoInputDeviceId) {
|
||||
return selectedVideoInputDeviceId;
|
||||
} else if (availableVideoInputDevices.length
|
||||
&& availableVideoInputDevices[0].label !== '') {
|
||||
return availableVideoInputDevices[0].deviceId;
|
||||
}
|
||||
} else if (selectedVideoInputDevice
|
||||
&& selectedVideoInputDeviceId !== localVideoDeviceId) {
|
||||
|
||||
if (newLabel) {
|
||||
// If a Firefox user with manual permission prompt chose a different
|
||||
// device from what we have stored as the preferred device we accept
|
||||
// and store that as the new preferred device.
|
||||
APP.store.dispatch(updateSettings({
|
||||
userSelectedCameraDeviceId: localVideoDeviceId,
|
||||
userSelectedCameraDeviceLabel: localVideoDevice.label
|
||||
}));
|
||||
} else {
|
||||
// And here we handle case when we already have some device working,
|
||||
// but we plug-in a "preferred" (previously selected in settings, stored
|
||||
// in local storage) device.
|
||||
return selectedVideoInputDeviceId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
/**
|
||||
* Determines if currently selected media devices should be changed after
|
||||
* list of available devices has been changed.
|
||||
* @param {MediaDeviceInfo[]} newDevices
|
||||
* @param {JitsiLocalTrack} localVideo
|
||||
* @param {JitsiLocalTrack} localAudio
|
||||
* @returns {{
|
||||
* audioinput: (string|undefined),
|
||||
* videoinput: (string|undefined),
|
||||
* audiooutput: (string|undefined)
|
||||
* }}
|
||||
*/
|
||||
getNewMediaDevicesAfterDeviceListChanged( // eslint-disable-line max-params
|
||||
newDevices,
|
||||
localVideo,
|
||||
localAudio,
|
||||
newLabels) {
|
||||
return {
|
||||
audioinput: getNewAudioInputDevice(newDevices, localAudio, newLabels),
|
||||
videoinput: getNewVideoInputDevice(newDevices, localVideo, newLabels),
|
||||
audiooutput: getNewAudioOutputDevice(newDevices)
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks if the only difference between an object of known devices compared
|
||||
* to an array of new devices are only the labels for the devices.
|
||||
* @param {Object} oldDevices
|
||||
* @param {MediaDeviceInfo[]} newDevices
|
||||
* @returns {boolean}
|
||||
*/
|
||||
newDeviceListAddedLabelsOnly(oldDevices, newDevices) {
|
||||
const oldDevicesFlattend = flattenAvailableDevices(oldDevices);
|
||||
|
||||
if (oldDevicesFlattend.length !== newDevices.length) {
|
||||
return false;
|
||||
}
|
||||
oldDevicesFlattend.forEach(oldDevice => {
|
||||
if (oldDevice.label !== '') {
|
||||
return false;
|
||||
}
|
||||
const newDevice = newDevices.find(nd => nd.deviceId === oldDevice.deviceId);
|
||||
|
||||
if (!newDevice || newDevice.label === '') {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Tries to create new local tracks for new devices obtained after device
|
||||
* list changed. Shows error dialog in case of failures.
|
||||
* @param {function} createLocalTracks
|
||||
* @param {string} (cameraDeviceId)
|
||||
* @param {string} (micDeviceId)
|
||||
* @returns {Promise.<JitsiLocalTrack[]>}
|
||||
*/
|
||||
createLocalTracksAfterDeviceListChanged(
|
||||
createLocalTracks,
|
||||
cameraDeviceId,
|
||||
micDeviceId) {
|
||||
let audioTrackError;
|
||||
let videoTrackError;
|
||||
const audioRequested = Boolean(micDeviceId);
|
||||
const videoRequested = Boolean(cameraDeviceId);
|
||||
|
||||
if (audioRequested && videoRequested) {
|
||||
// First we try to create both audio and video tracks together.
|
||||
return (
|
||||
createLocalTracks({
|
||||
devices: [ 'audio', 'video' ],
|
||||
cameraDeviceId,
|
||||
micDeviceId
|
||||
})
|
||||
|
||||
// If we fail to do this, try to create them separately.
|
||||
.catch(() => Promise.all([
|
||||
createAudioTrack(false).then(([ stream ]) => stream),
|
||||
createVideoTrack(false).then(([ stream ]) => stream)
|
||||
]))
|
||||
.then(tracks => {
|
||||
if (audioTrackError) {
|
||||
APP.store.dispatch(notifyMicError(audioTrackError));
|
||||
}
|
||||
|
||||
if (videoTrackError) {
|
||||
APP.store.dispatch(notifyCameraError(videoTrackError));
|
||||
}
|
||||
|
||||
return tracks.filter(t => typeof t !== 'undefined');
|
||||
}));
|
||||
} else if (videoRequested && !audioRequested) {
|
||||
return createVideoTrack();
|
||||
} else if (audioRequested && !videoRequested) {
|
||||
return createAudioTrack();
|
||||
}
|
||||
|
||||
return Promise.resolve([]);
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function createAudioTrack(showError = true) {
|
||||
return (
|
||||
createLocalTracks({
|
||||
devices: [ 'audio' ],
|
||||
cameraDeviceId: null,
|
||||
micDeviceId
|
||||
})
|
||||
.catch(err => {
|
||||
audioTrackError = err;
|
||||
showError && APP.store.dispatch(notifyMicError(err));
|
||||
|
||||
return [];
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function createVideoTrack(showError = true) {
|
||||
return (
|
||||
createLocalTracks({
|
||||
devices: [ 'video' ],
|
||||
cameraDeviceId,
|
||||
micDeviceId: null
|
||||
})
|
||||
.catch(err => {
|
||||
videoTrackError = err;
|
||||
showError && APP.store.dispatch(notifyCameraError(err));
|
||||
|
||||
return [];
|
||||
}));
|
||||
}
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user