import JitsiMeetJS from '../lib-jitsi-meet'; import { getUserSelectedOutputDeviceId, updateSettings } from '../settings'; import { ADD_PENDING_DEVICE_REQUEST, CHECK_AND_NOTIFY_FOR_NEW_DEVICE, NOTIFY_CAMERA_ERROR, NOTIFY_MIC_ERROR, REMOVE_PENDING_DEVICE_REQUESTS, SET_AUDIO_INPUT_DEVICE, SET_VIDEO_INPUT_DEVICE, UPDATE_DEVICE_LIST } from './actionTypes'; import { areDeviceLabelsInitialized, getDeviceIdByLabel, getDevicesFromURL, setAudioOutputDeviceId } from './functions'; const logger = require('jitsi-meet-logger').getLogger(__filename); /** * Maps the WebRTC string for device type to the keys used to store configure, * within redux, which devices should be used by default. */ const DEVICE_TYPE_TO_SETTINGS_KEYS = { audioInput: { currentDeviceId: 'micDeviceId', userSelectedDeviceId: 'userSelectedMicDeviceId', userSelectedDeviceLabel: 'userSelectedMicDeviceLabel' }, audioOutput: { currentDeviceId: 'audioOutputDeviceId', userSelectedDeviceId: 'userSelectedAudioOutputDeviceId', userSelectedDeviceLabel: 'userSelectedAudioOutputDeviceLabel' }, videoInput: { currentDeviceId: 'audioOutputDeviceId', userSelectedDeviceId: 'userSelectedCameraDeviceId', userSelectedDeviceLabel: 'userSelectedCameraDeviceLabel' } }; /** * Adds a pending device request. * * @param {Object} request - The request to be added. * @returns {{ * type: ADD_PENDING_DEVICE_REQUEST, * request: Object * }} */ export function addPendingDeviceRequest(request) { return { type: ADD_PENDING_DEVICE_REQUEST, request }; } /** * Configures the initial A/V devices before the conference has started. * * @returns {Function} */ export function configureInitialDevices() { return (dispatch, getState) => { const deviceLabels = getDevicesFromURL(getState()); let updateSettingsPromise; if (deviceLabels) { updateSettingsPromise = dispatch(getAvailableDevices()).then(() => { const state = getState(); if (!areDeviceLabelsInitialized(state)) { // The labels are not available if the A/V permissions are // not yet granted. Object.keys(deviceLabels).forEach(key => { dispatch(addPendingDeviceRequest({ type: 'devices', name: 'setDevice', device: { kind: key.toLowerCase(), label: deviceLabels[key] }, // eslint-disable-next-line no-empty-function responseCallback() {} })); }); return; } const newSettings = {}; Object.keys(deviceLabels).forEach(key => { const label = deviceLabels[key]; const deviceId = getDeviceIdByLabel(state, label, key); if (deviceId) { const settingsTranslationMap = DEVICE_TYPE_TO_SETTINGS_KEYS[key]; newSettings[settingsTranslationMap.currentDeviceId] = deviceId; newSettings[settingsTranslationMap.userSelectedDeviceId] = deviceId; newSettings[settingsTranslationMap.userSelectedDeviceLabel] = label; } }); dispatch(updateSettings(newSettings)); }); } else { updateSettingsPromise = Promise.resolve(); } return updateSettingsPromise .then(() => { const userSelectedAudioOutputDeviceId = getUserSelectedOutputDeviceId(getState()); return setAudioOutputDeviceId(userSelectedAudioOutputDeviceId, dispatch) .catch(ex => logger.warn(`Failed to set audio output device. Default audio output device will be used instead ${ex}`)); }); }; } /** * Queries for connected A/V input and output devices and updates the redux * state of known devices. * * @returns {Function} */ export function getAvailableDevices() { return dispatch => new Promise(resolve => { const { mediaDevices } = JitsiMeetJS; if (mediaDevices.isDeviceListAvailable() && mediaDevices.isDeviceChangeAvailable()) { mediaDevices.enumerateDevices(devices => { dispatch(updateDeviceList(devices)); resolve(devices); }); } else { resolve([]); } }); } /** * Signals that an error occurred while trying to obtain a track from a camera. * * @param {Object} error - The device error, as provided by lib-jitsi-meet. * @param {string} error.name - The constant for the type of the error. * @param {string} error.message - Optional additional information about the * error. * @returns {{ * type: NOTIFY_CAMERA_ERROR, * error: Object * }} */ export function notifyCameraError(error) { return { type: NOTIFY_CAMERA_ERROR, error }; } /** * Signals that an error occurred while trying to obtain a track from a mic. * * @param {Object} error - The device error, as provided by lib-jitsi-meet. * @param {Object} error.name - The constant for the type of the error. * @param {string} error.message - Optional additional information about the * error. * @returns {{ * type: NOTIFY_MIC_ERROR, * error: Object * }} */ export function notifyMicError(error) { return { type: NOTIFY_MIC_ERROR, error }; } /** * Remove all pending device requests. * * @returns {{ * type: REMOVE_PENDING_DEVICE_REQUESTS * }} */ export function removePendingDeviceRequests() { return { type: REMOVE_PENDING_DEVICE_REQUESTS }; } /** * Signals to update the currently used audio input device. * * @param {string} deviceId - The id of the new audio input device. * @returns {{ * type: SET_AUDIO_INPUT_DEVICE, * deviceId: string * }} */ export function setAudioInputDevice(deviceId) { return { type: SET_AUDIO_INPUT_DEVICE, deviceId }; } /** * Signals to update the currently used video input device. * * @param {string} deviceId - The id of the new video input device. * @returns {{ * type: SET_VIDEO_INPUT_DEVICE, * deviceId: string * }} */ export function setVideoInputDevice(deviceId) { return { type: SET_VIDEO_INPUT_DEVICE, deviceId }; } /** * Signals to update the list of known audio and video devices. * * @param {Array} devices - All known available audio input, * audio output, and video input devices. * @returns {{ * type: UPDATE_DEVICE_LIST, * devices: Array * }} */ export function updateDeviceList(devices) { return { type: UPDATE_DEVICE_LIST, devices }; } /** * Signals to check new and old devices for newly added devices and notify. * * @param {Array} newDevices - Array of the new devices. * @param {Array} oldDevices - Array of the old devices. * @returns {{ * type: CHECK_AND_NOTIFY_FOR_NEW_DEVICE, * newDevices: Array, * oldDevices: Array * }} */ export function checkAndNotifyForNewDevice(newDevices, oldDevices) { return { type: CHECK_AND_NOTIFY_FOR_NEW_DEVICE, newDevices, oldDevices }; }