fix(prejoin): Store prejoin tracks in 'features/base/tracks'
This commit is contained in:
parent
ec6ed6e8ec
commit
4f169988a3
|
@ -118,10 +118,7 @@ import { mediaPermissionPromptVisibilityChanged } from './react/features/overlay
|
|||
import { suspendDetected } from './react/features/power-monitor';
|
||||
import {
|
||||
initPrejoin,
|
||||
isPrejoinPageEnabled,
|
||||
isPrejoinPageVisible,
|
||||
replacePrejoinAudioTrack,
|
||||
replacePrejoinVideoTrack
|
||||
isPrejoinPageEnabled
|
||||
} from './react/features/prejoin';
|
||||
import { createRnnoiseProcessorPromise } from './react/features/rnnoise';
|
||||
import { toggleScreenshotCaptureEffect } from './react/features/screenshot-capture';
|
||||
|
@ -1409,18 +1406,6 @@ export default {
|
|||
useVideoStream(newStream) {
|
||||
return new Promise((resolve, reject) => {
|
||||
_replaceLocalVideoTrackQueue.enqueue(onFinish => {
|
||||
/**
|
||||
* When the prejoin page is visible there is no conference object
|
||||
* created. The prejoin tracks are managed separately,
|
||||
* so this updates the prejoin video track.
|
||||
*/
|
||||
if (isPrejoinPageVisible(APP.store.getState())) {
|
||||
return APP.store.dispatch(replacePrejoinVideoTrack(newStream))
|
||||
.then(resolve)
|
||||
.catch(reject)
|
||||
.then(onFinish);
|
||||
}
|
||||
|
||||
APP.store.dispatch(
|
||||
replaceLocalTrack(this.localVideo, newStream, room))
|
||||
.then(() => {
|
||||
|
@ -1474,18 +1459,6 @@ export default {
|
|||
useAudioStream(newStream) {
|
||||
return new Promise((resolve, reject) => {
|
||||
_replaceLocalAudioTrackQueue.enqueue(onFinish => {
|
||||
/**
|
||||
* When the prejoin page is visible there is no conference object
|
||||
* created. The prejoin tracks are managed separately,
|
||||
* so this updates the prejoin audio stream.
|
||||
*/
|
||||
if (isPrejoinPageVisible(APP.store.getState())) {
|
||||
return APP.store.dispatch(replacePrejoinAudioTrack(newStream))
|
||||
.then(resolve)
|
||||
.catch(reject)
|
||||
.then(onFinish);
|
||||
}
|
||||
|
||||
APP.store.dispatch(
|
||||
replaceLocalTrack(this.localAudio, newStream, room))
|
||||
.then(() => {
|
||||
|
|
|
@ -35,6 +35,10 @@
|
|||
cursor: initial;
|
||||
color: #fff;
|
||||
background-color: #a4b8d1;
|
||||
|
||||
&:hover {
|
||||
background-color: #a4b8d1;
|
||||
}
|
||||
}
|
||||
|
||||
svg {
|
||||
|
|
|
@ -4,6 +4,17 @@ import { toState } from '../redux';
|
|||
|
||||
import { VIDEO_MUTISM_AUTHORITY } from './constants';
|
||||
|
||||
/**
|
||||
* Determines whether audio is currently muted.
|
||||
*
|
||||
* @param {Function|Object} stateful - The redux store, state, or
|
||||
* {@code getState} function.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isAudioMuted(stateful: Function | Object) {
|
||||
return Boolean(toState(stateful)['features/base/media'].audio.muted);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether video is currently muted by the audio-only authority.
|
||||
*
|
||||
|
|
|
@ -9,7 +9,7 @@ export * from './functions.any';
|
|||
* @returns {void}
|
||||
*/
|
||||
export function getCurrentCameraDeviceId(state: Object) {
|
||||
return state['features/base/settings'].cameraDeviceId;
|
||||
return getDeviceIdByType(state, 'isVideoTrack');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -19,7 +19,7 @@ export function getCurrentCameraDeviceId(state: Object) {
|
|||
* @returns {void}
|
||||
*/
|
||||
export function getCurrentMicDeviceId(state: Object) {
|
||||
return state['features/base/settings'].micDeviceId;
|
||||
return getDeviceIdByType(state, 'isAudioTrack');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -32,6 +32,22 @@ export function getCurrentOutputDeviceId(state: Object) {
|
|||
return state['features/base/settings'].audioOutputDeviceId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the deviceId for the corresponding local track type.
|
||||
*
|
||||
* @param {Object} state - The state of the application.
|
||||
* @param {string} isType - Can be 'isVideoTrack' | 'isAudioTrack'.
|
||||
* @returns {string}
|
||||
*/
|
||||
function getDeviceIdByType(state: Object, isType: string) {
|
||||
const [ deviceId ] = state['features/base/tracks']
|
||||
.map(t => t.jitsiTrack)
|
||||
.filter(t => t && t.isLocal() && t[isType]())
|
||||
.map(t => t.getDeviceId());
|
||||
|
||||
return deviceId || '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the saved display name.
|
||||
*
|
||||
|
|
|
@ -267,56 +267,70 @@ export function toggleScreensharing() {
|
|||
* @returns {Function}
|
||||
*/
|
||||
export function replaceLocalTrack(oldTrack, newTrack, conference) {
|
||||
return (dispatch, getState) => {
|
||||
return async (dispatch, getState) => {
|
||||
conference
|
||||
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
|| (conference = getState()['features/base/conference'].conference);
|
||||
|
||||
return conference.replaceTrack(oldTrack, newTrack)
|
||||
if (conference) {
|
||||
await conference.replaceTrack(oldTrack, newTrack);
|
||||
}
|
||||
|
||||
return dispatch(replaceStoredTracks(oldTrack, newTrack));
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces a stored track with another.
|
||||
*
|
||||
* @param {JitsiLocalTrack|null} oldTrack - The track to dispose.
|
||||
* @param {JitsiLocalTrack|null} newTrack - The track to use instead.
|
||||
* @returns {Function}
|
||||
*/
|
||||
function replaceStoredTracks(oldTrack, newTrack) {
|
||||
return dispatch => {
|
||||
// We call dispose after doing the replace because dispose will
|
||||
// try and do a new o/a after the track removes itself. Doing it
|
||||
// after means the JitsiLocalTrack.conference is already
|
||||
// cleared, so it won't try and do the o/a.
|
||||
const disposePromise
|
||||
= oldTrack
|
||||
? dispatch(_disposeAndRemoveTracks([ oldTrack ]))
|
||||
: Promise.resolve();
|
||||
|
||||
return disposePromise
|
||||
.then(() => {
|
||||
// We call dispose after doing the replace because dispose will
|
||||
// try and do a new o/a after the track removes itself. Doing it
|
||||
// after means the JitsiLocalTrack.conference is already
|
||||
// cleared, so it won't try and do the o/a.
|
||||
const disposePromise
|
||||
= oldTrack
|
||||
? dispatch(_disposeAndRemoveTracks([ oldTrack ]))
|
||||
: Promise.resolve();
|
||||
if (newTrack) {
|
||||
// The mute state of the new track should be
|
||||
// reflected in the app's mute state. For example,
|
||||
// if the app is currently muted and changing to a
|
||||
// new track that is not muted, the app's mute
|
||||
// state should be falsey. As such, emit a mute
|
||||
// event here to set up the app to reflect the
|
||||
// track's mute state. If this is not done, the
|
||||
// current mute state of the app will be reflected
|
||||
// on the track, not vice-versa.
|
||||
const setMuted
|
||||
= newTrack.isVideoTrack()
|
||||
? setVideoMuted
|
||||
: setAudioMuted;
|
||||
const isMuted = newTrack.isMuted();
|
||||
|
||||
return disposePromise
|
||||
.then(() => {
|
||||
if (newTrack) {
|
||||
// The mute state of the new track should be
|
||||
// reflected in the app's mute state. For example,
|
||||
// if the app is currently muted and changing to a
|
||||
// new track that is not muted, the app's mute
|
||||
// state should be falsey. As such, emit a mute
|
||||
// event here to set up the app to reflect the
|
||||
// track's mute state. If this is not done, the
|
||||
// current mute state of the app will be reflected
|
||||
// on the track, not vice-versa.
|
||||
const setMuted
|
||||
= newTrack.isVideoTrack()
|
||||
? setVideoMuted
|
||||
: setAudioMuted;
|
||||
const isMuted = newTrack.isMuted();
|
||||
sendAnalytics(createTrackMutedEvent(
|
||||
newTrack.getType(),
|
||||
'track.replaced',
|
||||
isMuted));
|
||||
logger.log(`Replace ${newTrack.getType()} track - ${
|
||||
isMuted ? 'muted' : 'unmuted'}`);
|
||||
|
||||
sendAnalytics(createTrackMutedEvent(
|
||||
newTrack.getType(),
|
||||
'track.replaced',
|
||||
isMuted));
|
||||
logger.log(`Replace ${newTrack.getType()} track - ${
|
||||
isMuted ? 'muted' : 'unmuted'}`);
|
||||
|
||||
return dispatch(setMuted(isMuted));
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
if (newTrack) {
|
||||
return dispatch(_addTracks([ newTrack ]));
|
||||
}
|
||||
});
|
||||
return dispatch(setMuted(isMuted));
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
if (newTrack) {
|
||||
return dispatch(_addTracks([ newTrack ]));
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
|
|
@ -200,6 +200,18 @@ export function getLocalVideoType(tracks) {
|
|||
return presenterTrack ? MEDIA_TYPE.PRESENTER : MEDIA_TYPE.VIDEO;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the stored local video track.
|
||||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function getLocalJitsiVideoTrack(state) {
|
||||
const track = getLocalVideoTrack(state['features/base/tracks']);
|
||||
|
||||
return track?.jitsiTrack;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns track of specified media type for specified participant id.
|
||||
*
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
import UIEvents from '../../../../service/UI/UIEvents';
|
||||
import { hideNotification } from '../../notifications';
|
||||
import { isPrejoinPageVisible } from '../../prejoin/functions';
|
||||
import { getAvailableDevices } from '../devices/actions';
|
||||
import {
|
||||
CAMERA_FACING_MODE,
|
||||
MEDIA_TYPE,
|
||||
|
@ -15,6 +17,7 @@ import {
|
|||
import { MiddlewareRegistry } from '../redux';
|
||||
|
||||
import {
|
||||
TRACK_ADDED,
|
||||
TOGGLE_SCREENSHARING,
|
||||
TRACK_NO_DATA_FROM_SOURCE,
|
||||
TRACK_REMOVED,
|
||||
|
@ -44,6 +47,15 @@ declare var APP: Object;
|
|||
*/
|
||||
MiddlewareRegistry.register(store => next => action => {
|
||||
switch (action.type) {
|
||||
case TRACK_ADDED: {
|
||||
// The devices list needs to be refreshed when no initial video permissions
|
||||
// were granted and a local video track is added by umuting the video.
|
||||
if (action.track.local) {
|
||||
store.dispatch(getAvailableDevices());
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case TRACK_NO_DATA_FROM_SOURCE: {
|
||||
const result = next(action);
|
||||
|
||||
|
@ -281,7 +293,7 @@ function _setMuted(store, { ensureTrack, authority, muted }, mediaType: MEDIA_TY
|
|||
// anymore, unless it is muted by audioOnly.
|
||||
jitsiTrack && (jitsiTrack.videoType !== 'desktop' || isAudioOnly)
|
||||
&& setTrackMuted(jitsiTrack, muted);
|
||||
} else if (!muted && ensureTrack && typeof APP === 'undefined') {
|
||||
} else if (!muted && ensureTrack && (typeof APP === 'undefined' || isPrejoinPageVisible(store.getState()))) {
|
||||
// FIXME: This only runs on mobile now because web has its own way of
|
||||
// creating local tracks. Adjust the check once they are unified.
|
||||
store.dispatch(createLocalTracksA({ devices: [ mediaType ] }));
|
||||
|
|
|
@ -1,18 +1,3 @@
|
|||
/**
|
||||
* Action type to add a video track to the store.
|
||||
*/
|
||||
export const ADD_PREJOIN_VIDEO_TRACK = 'ADD_PREJOIN_VIDEO_TRACK';
|
||||
|
||||
/**
|
||||
* Action type to add an audio track to the store.
|
||||
*/
|
||||
export const ADD_PREJOIN_AUDIO_TRACK = 'ADD_PREJOIN_AUDIO_TRACK';
|
||||
|
||||
/**
|
||||
* Action type to add a content sharing track to the store.
|
||||
*/
|
||||
export const ADD_PREJOIN_CONTENT_SHARING_TRACK
|
||||
= 'ADD_PREJOIN_CONTENT_SHARING_TRACK';
|
||||
|
||||
/**
|
||||
* Action type to signal the start of the conference.
|
||||
|
@ -68,13 +53,3 @@ export const SET_PREJOIN_DEVICE_ERRORS = 'SET_PREJOIN_DEVICE_ERRORS';
|
|||
* Action type to set the visibility of the prejoin page.
|
||||
*/
|
||||
export const SET_PREJOIN_PAGE_VISIBILITY = 'SET_PREJOIN_PAGE_VISIBILITY';
|
||||
|
||||
/**
|
||||
* Action type to mute/unmute the video while on prejoin page.
|
||||
*/
|
||||
export const SET_PREJOIN_VIDEO_DISABLED = 'SET_PREJOIN_VIDEO_DISABLED';
|
||||
|
||||
/**
|
||||
* Action type to mute/unmute the video while on prejoin page.
|
||||
*/
|
||||
export const SET_PREJOIN_VIDEO_MUTED = 'SET_PREJOIN_VIDEO_MUTED';
|
||||
|
|
|
@ -5,14 +5,17 @@ import uuid from 'uuid';
|
|||
import { getRoomName } from '../base/conference';
|
||||
import { getDialOutStatusUrl, getDialOutUrl } from '../base/config/functions';
|
||||
import { createLocalTrack } from '../base/lib-jitsi-meet';
|
||||
import {
|
||||
getLocalAudioTrack,
|
||||
getLocalVideoTrack,
|
||||
trackAdded,
|
||||
replaceLocalTrack
|
||||
} from '../base/tracks';
|
||||
import { openURLInBrowser } from '../base/util';
|
||||
import { executeDialOutRequest, executeDialOutStatusRequest, getDialInfoPageURL } from '../invite/functions';
|
||||
import { showErrorNotification } from '../notifications';
|
||||
|
||||
import {
|
||||
ADD_PREJOIN_AUDIO_TRACK,
|
||||
ADD_PREJOIN_CONTENT_SHARING_TRACK,
|
||||
ADD_PREJOIN_VIDEO_TRACK,
|
||||
PREJOIN_START_CONFERENCE,
|
||||
SET_DEVICE_STATUS,
|
||||
SET_DIALOUT_COUNTRY,
|
||||
|
@ -20,19 +23,13 @@ import {
|
|||
SET_DIALOUT_STATUS,
|
||||
SET_SKIP_PREJOIN,
|
||||
SET_JOIN_BY_PHONE_DIALOG_VISIBLITY,
|
||||
SET_PREJOIN_AUDIO_DISABLED,
|
||||
SET_PREJOIN_AUDIO_MUTED,
|
||||
SET_PREJOIN_DEVICE_ERRORS,
|
||||
SET_PREJOIN_PAGE_VISIBILITY,
|
||||
SET_PREJOIN_VIDEO_DISABLED,
|
||||
SET_PREJOIN_VIDEO_MUTED
|
||||
SET_PREJOIN_PAGE_VISIBILITY
|
||||
} from './actionTypes';
|
||||
import {
|
||||
getFullDialOutNumber,
|
||||
getAudioTrack,
|
||||
getDialOutConferenceUrl,
|
||||
getDialOutCountry,
|
||||
getVideoTrack,
|
||||
isJoinByPhoneDialogVisible
|
||||
} from './functions';
|
||||
import logger from './logger';
|
||||
|
@ -60,45 +57,6 @@ const STATUS_REQ_FREQUENCY = 2000;
|
|||
*/
|
||||
const STATUS_REQ_CAP = 45;
|
||||
|
||||
/**
|
||||
* Action used to add an audio track to the store.
|
||||
*
|
||||
* @param {Object} value - The track to be added.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function addPrejoinAudioTrack(value: Object) {
|
||||
return {
|
||||
type: ADD_PREJOIN_AUDIO_TRACK,
|
||||
value
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Action used to add a video track to the store.
|
||||
*
|
||||
* @param {Object} value - The track to be added.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function addPrejoinVideoTrack(value: Object) {
|
||||
return {
|
||||
type: ADD_PREJOIN_VIDEO_TRACK,
|
||||
value
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Action used to add a content sharing track to the store.
|
||||
*
|
||||
* @param {Object} value - The track to be added.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function addPrejoinContentSharingTrack(value: Object) {
|
||||
return {
|
||||
type: ADD_PREJOIN_CONTENT_SHARING_TRACK,
|
||||
value
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Polls for status change after dial out.
|
||||
* Changes dialog message based on response, closes the dialog if there is an error,
|
||||
|
@ -232,27 +190,10 @@ export function dialOut(onSuccess: Function, onFail: Function) {
|
|||
*/
|
||||
export function initPrejoin(tracks: Object[], errors: Object) {
|
||||
return async function(dispatch: Function) {
|
||||
const audioTrack = tracks.find(t => t.isAudioTrack());
|
||||
const videoTrack = tracks.find(t => t.isVideoTrack());
|
||||
|
||||
dispatch(setPrejoinDeviceErrors(errors));
|
||||
|
||||
if (audioTrack) {
|
||||
dispatch(addPrejoinAudioTrack(audioTrack));
|
||||
} else {
|
||||
dispatch(setAudioDisabled());
|
||||
}
|
||||
|
||||
if (videoTrack) {
|
||||
if (videoTrack.videoType === 'desktop') {
|
||||
dispatch(addPrejoinContentSharingTrack(videoTrack));
|
||||
dispatch(setPrejoinVideoDisabled(true));
|
||||
} else {
|
||||
dispatch(addPrejoinVideoTrack(videoTrack));
|
||||
}
|
||||
} else {
|
||||
dispatch(setPrejoinVideoDisabled(true));
|
||||
}
|
||||
tracks.forEach(track => dispatch(trackAdded(track)));
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -275,12 +216,12 @@ export function joinConference() {
|
|||
*/
|
||||
export function joinConferenceWithoutAudio() {
|
||||
return async function(dispatch: Function, getState: Function) {
|
||||
const audioTrack = getAudioTrack(getState());
|
||||
const tracks = getState()['features/base/tracks'];
|
||||
const audioTrack = getLocalAudioTrack(tracks)?.jitsiTrack;
|
||||
|
||||
if (audioTrack) {
|
||||
await dispatch(replacePrejoinAudioTrack(null));
|
||||
await dispatch(replaceLocalTrack(audioTrack, null));
|
||||
}
|
||||
dispatch(setAudioDisabled());
|
||||
dispatch(joinConference());
|
||||
};
|
||||
}
|
||||
|
@ -301,21 +242,6 @@ export function openDialInPage() {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the existing audio track with a new one.
|
||||
*
|
||||
* @param {Object} track - The new track.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function replacePrejoinAudioTrack(track: Object) {
|
||||
return async (dispatch: Function, getState: Function) => {
|
||||
const oldTrack = getAudioTrack(getState());
|
||||
|
||||
oldTrack && await oldTrack.dispose();
|
||||
dispatch(addPrejoinAudioTrack(track));
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new audio track based on a device id and replaces the current one.
|
||||
*
|
||||
|
@ -323,11 +249,13 @@ export function replacePrejoinAudioTrack(track: Object) {
|
|||
* @returns {Function}
|
||||
*/
|
||||
export function replaceAudioTrackById(deviceId: string) {
|
||||
return async (dispatch: Function) => {
|
||||
return async (dispatch: Function, getState: Function) => {
|
||||
try {
|
||||
const track = await createLocalTrack('audio', deviceId);
|
||||
const tracks = getState()['features/base/tracks'];
|
||||
const newTrack = await createLocalTrack('audio', deviceId);
|
||||
const oldTrack = getLocalAudioTrack(tracks)?.jitsiTrack;
|
||||
|
||||
dispatch(replacePrejoinAudioTrack(track));
|
||||
dispatch(replaceLocalTrack(oldTrack, newTrack));
|
||||
} catch (err) {
|
||||
dispatch(setDeviceStatusWarning('prejoin.audioTrackError'));
|
||||
logger.log('Error replacing audio track', err);
|
||||
|
@ -335,21 +263,6 @@ export function replaceAudioTrackById(deviceId: string) {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the existing video track with a new one.
|
||||
*
|
||||
* @param {Object} track - The new track.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function replacePrejoinVideoTrack(track: Object) {
|
||||
return async (dispatch: Function, getState: Function) => {
|
||||
const oldTrack = getVideoTrack(getState());
|
||||
|
||||
oldTrack && await oldTrack.dispose();
|
||||
dispatch(addPrejoinVideoTrack(track));
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new video track based on a device id and replaces the current one.
|
||||
*
|
||||
|
@ -357,11 +270,13 @@ export function replacePrejoinVideoTrack(track: Object) {
|
|||
* @returns {Function}
|
||||
*/
|
||||
export function replaceVideoTrackById(deviceId: Object) {
|
||||
return async (dispatch: Function) => {
|
||||
return async (dispatch: Function, getState: Function) => {
|
||||
try {
|
||||
const track = await createLocalTrack('video', deviceId);
|
||||
const tracks = getState()['features/base/tracks'];
|
||||
const newTrack = await createLocalTrack('video', deviceId);
|
||||
const oldTrack = getLocalVideoTrack(tracks)?.jitsiTrack;
|
||||
|
||||
dispatch(replacePrejoinVideoTrack(track));
|
||||
dispatch(replaceLocalTrack(oldTrack, newTrack));
|
||||
} catch (err) {
|
||||
dispatch(setDeviceStatusWarning('prejoin.videoTrackError'));
|
||||
logger.log('Error replacing video track', err);
|
||||
|
@ -369,58 +284,6 @@ export function replaceVideoTrackById(deviceId: Object) {
|
|||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Action used to mark audio muted.
|
||||
*
|
||||
* @param {boolean} value - True for muted.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function setPrejoinAudioMuted(value: boolean) {
|
||||
return {
|
||||
type: SET_PREJOIN_AUDIO_MUTED,
|
||||
value
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Action used to mark video disabled.
|
||||
*
|
||||
* @param {boolean} value - True for muted.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function setPrejoinVideoDisabled(value: boolean) {
|
||||
return {
|
||||
type: SET_PREJOIN_VIDEO_DISABLED,
|
||||
value
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Action used to mark video muted.
|
||||
*
|
||||
* @param {boolean} value - True for muted.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function setPrejoinVideoMuted(value: boolean) {
|
||||
return {
|
||||
type: SET_PREJOIN_VIDEO_MUTED,
|
||||
value
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Action used to mark audio as disabled.
|
||||
*
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function setAudioDisabled() {
|
||||
return {
|
||||
type: SET_PREJOIN_AUDIO_DISABLED
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the device status as OK with the corresponding text.
|
||||
*
|
||||
|
|
|
@ -6,9 +6,11 @@ import React, { Component } from 'react';
|
|||
import { getRoomName } from '../../base/conference';
|
||||
import { translate } from '../../base/i18n';
|
||||
import { Icon, IconPhone, IconVolumeOff } from '../../base/icons';
|
||||
import { isVideoMutedByUser } from '../../base/media';
|
||||
import { ActionButton, InputField, PreMeetingScreen } from '../../base/premeeting';
|
||||
import { connect } from '../../base/redux';
|
||||
import { getDisplayName, updateSettings } from '../../base/settings';
|
||||
import { getLocalJitsiVideoTrack } from '../../base/tracks';
|
||||
import {
|
||||
joinConference as joinConferenceAction,
|
||||
joinConferenceWithoutAudio as joinConferenceWithoutAudioAction,
|
||||
|
@ -16,11 +18,9 @@ import {
|
|||
setJoinByPhoneDialogVisiblity as setJoinByPhoneDialogVisiblityAction
|
||||
} from '../actions';
|
||||
import {
|
||||
getActiveVideoTrack,
|
||||
isJoinByPhoneButtonVisible,
|
||||
isDeviceStatusVisible,
|
||||
isJoinByPhoneDialogVisible,
|
||||
isPrejoinVideoMuted
|
||||
isJoinByPhoneDialogVisible
|
||||
} from '../functions';
|
||||
|
||||
import JoinByPhoneDialog from './dialogs/JoinByPhoneDialog';
|
||||
|
@ -315,8 +315,8 @@ function mapStateToProps(state): Object {
|
|||
roomName: getRoomName(state),
|
||||
showDialog: isJoinByPhoneDialogVisible(state),
|
||||
hasJoinByPhoneButton: isJoinByPhoneButtonVisible(state),
|
||||
showCameraPreview: !isPrejoinVideoMuted(state),
|
||||
videoTrack: getActiveVideoTrack(state)
|
||||
showCameraPreview: !isVideoMutedByUser(state),
|
||||
videoTrack: getLocalJitsiVideoTrack(state)
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -2,25 +2,7 @@
|
|||
|
||||
import { getRoomName } from '../base/conference';
|
||||
import { getDialOutStatusUrl, getDialOutUrl } from '../base/config/functions';
|
||||
|
||||
/**
|
||||
* Mutes or unmutes a track.
|
||||
*
|
||||
* @param {Object} track - The track to be configured.
|
||||
* @param {boolean} shouldMute - If it should mute or not.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
function applyMuteOptionsToTrack(track, shouldMute) {
|
||||
if (track.isMuted() === shouldMute) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (shouldMute) {
|
||||
return track.mute();
|
||||
}
|
||||
|
||||
return track.unmute();
|
||||
}
|
||||
import { isAudioMuted, isVideoMutedByUser } from '../base/media';
|
||||
|
||||
/**
|
||||
* Selector for the visibility of the 'join by phone' button.
|
||||
|
@ -39,73 +21,8 @@ export function isJoinByPhoneButtonVisible(state: Object): boolean {
|
|||
* @returns {boolean}
|
||||
*/
|
||||
export function isDeviceStatusVisible(state: Object): boolean {
|
||||
return !((isAudioDisabled(state) && isPrejoinVideoDisabled(state))
|
||||
|| (isPrejoinAudioMuted(state) && isPrejoinVideoMuted(state)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Selector for getting the active video/content sharing track.
|
||||
*
|
||||
* @param {Object} state - The state of the app.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function getActiveVideoTrack(state: Object): Object {
|
||||
const track = getVideoTrack(state) || getContentSharingTrack(state);
|
||||
|
||||
if (track && track.isActive()) {
|
||||
return track;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list with all the prejoin tracks configured according to
|
||||
* user's preferences.
|
||||
*
|
||||
* @param {Object} state - The state of the app.
|
||||
* @returns {Promise<Object[]>}
|
||||
*/
|
||||
export async function getAllPrejoinConfiguredTracks(state: Object): Promise<Object[]> {
|
||||
const tracks = [];
|
||||
const audioTrack = getAudioTrack(state);
|
||||
const videoTrack = getVideoTrack(state);
|
||||
const csTrack = getContentSharingTrack(state);
|
||||
|
||||
if (csTrack) {
|
||||
tracks.push(csTrack);
|
||||
} else if (videoTrack) {
|
||||
await applyMuteOptionsToTrack(videoTrack, isPrejoinVideoMuted(state));
|
||||
tracks.push(videoTrack);
|
||||
}
|
||||
|
||||
if (audioTrack) {
|
||||
await applyMuteOptionsToTrack(audioTrack, isPrejoinAudioMuted(state));
|
||||
isPrejoinAudioMuted(state) && audioTrack.mute();
|
||||
tracks.push(audioTrack);
|
||||
}
|
||||
|
||||
return tracks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Selector for getting the prejoin audio track.
|
||||
*
|
||||
* @param {Object} state - The state of the app.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function getAudioTrack(state: Object): Object {
|
||||
return state['features/prejoin']?.audioTrack;
|
||||
}
|
||||
|
||||
/**
|
||||
* Selector for getting the prejoin content sharing track.
|
||||
*
|
||||
* @param {Object} state - The state of the app.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function getContentSharingTrack(state: Object): Object {
|
||||
return state['features/prejoin']?.contentSharingTrack;
|
||||
return !(isAudioMuted(state) && isVideoMutedByUser(state))
|
||||
&& !state['features/base/config'].startSilent;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -181,36 +98,6 @@ export function getFullDialOutNumber(state: Object): string {
|
|||
return `+${country.dialCode}${dialOutNumber}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Selector for getting the prejoin video track.
|
||||
*
|
||||
* @param {Object} state - The state of the app.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function getVideoTrack(state: Object): Object {
|
||||
return state['features/prejoin']?.videoTrack;
|
||||
}
|
||||
|
||||
/**
|
||||
* Selector for getting the mute status of the prejoin audio.
|
||||
*
|
||||
* @param {Object} state - The state of the app.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isPrejoinAudioMuted(state: Object): boolean {
|
||||
return state['features/prejoin']?.audioMuted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Selector for getting the mute status of the prejoin video.
|
||||
*
|
||||
* @param {Object} state - The state of the app.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isPrejoinVideoMuted(state: Object): boolean {
|
||||
return state['features/prejoin']?.videoMuted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Selector for getting the error if any while creating streams.
|
||||
*
|
||||
|
@ -221,26 +108,6 @@ export function getRawError(state: Object): string {
|
|||
return state['features/prejoin']?.rawError;
|
||||
}
|
||||
|
||||
/**
|
||||
* Selector for getting state of the prejoin audio.
|
||||
*
|
||||
* @param {Object} state - The state of the app.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isAudioDisabled(state: Object): Object {
|
||||
return state['features/prejoin']?.audioDisabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Selector for getting state of the prejoin video.
|
||||
*
|
||||
* @param {Object} state - The state of the app.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isPrejoinVideoDisabled(state: Object): Object {
|
||||
return state['features/prejoin']?.videoDisabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Selector for getting the visiblity state for the 'JoinByPhoneDialog'.
|
||||
*
|
||||
|
|
|
@ -1,16 +1,9 @@
|
|||
// @flow
|
||||
|
||||
import { SET_AUDIO_MUTED, SET_VIDEO_MUTED } from '../base/media';
|
||||
import { MiddlewareRegistry } from '../base/redux';
|
||||
import { updateSettings } from '../base/settings';
|
||||
|
||||
import {
|
||||
ADD_PREJOIN_AUDIO_TRACK,
|
||||
ADD_PREJOIN_VIDEO_TRACK,
|
||||
PREJOIN_START_CONFERENCE
|
||||
} from './actionTypes';
|
||||
import { setPrejoinAudioMuted, setPrejoinVideoMuted } from './actions';
|
||||
import { getAllPrejoinConfiguredTracks } from './functions';
|
||||
import { PREJOIN_START_CONFERENCE } from './actionTypes';
|
||||
|
||||
declare var APP: Object;
|
||||
|
||||
|
@ -22,62 +15,22 @@ declare var APP: Object;
|
|||
*/
|
||||
MiddlewareRegistry.register(store => next => async action => {
|
||||
switch (action.type) {
|
||||
case ADD_PREJOIN_AUDIO_TRACK: {
|
||||
const { value: audioTrack } = action;
|
||||
|
||||
if (audioTrack) {
|
||||
store.dispatch(
|
||||
updateSettings({
|
||||
micDeviceId: audioTrack.getDeviceId()
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case ADD_PREJOIN_VIDEO_TRACK: {
|
||||
const { value: videoTrack } = action;
|
||||
|
||||
if (videoTrack) {
|
||||
store.dispatch(
|
||||
updateSettings({
|
||||
cameraDeviceId: videoTrack.getDeviceId()
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case PREJOIN_START_CONFERENCE: {
|
||||
const { getState, dispatch } = store;
|
||||
const state = getState();
|
||||
const { userSelectedSkipPrejoin } = state['features/prejoin'];
|
||||
const tracks = state['features/base/tracks'];
|
||||
|
||||
userSelectedSkipPrejoin && dispatch(updateSettings({
|
||||
userSelectedSkipPrejoin
|
||||
}));
|
||||
|
||||
|
||||
const tracks = await getAllPrejoinConfiguredTracks(state);
|
||||
|
||||
APP.conference.prejoinStart(tracks);
|
||||
APP.conference.prejoinStart(tracks.map(t => t.jitsiTrack));
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case SET_AUDIO_MUTED: {
|
||||
store.dispatch(setPrejoinAudioMuted(Boolean(action.muted)));
|
||||
break;
|
||||
}
|
||||
|
||||
case SET_VIDEO_MUTED: {
|
||||
store.dispatch(setPrejoinVideoMuted(Boolean(action.muted)));
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return next(action);
|
||||
});
|
||||
|
|
|
@ -1,28 +1,17 @@
|
|||
import { ReducerRegistry } from '../base/redux';
|
||||
|
||||
import {
|
||||
ADD_PREJOIN_AUDIO_TRACK,
|
||||
ADD_PREJOIN_CONTENT_SHARING_TRACK,
|
||||
ADD_PREJOIN_VIDEO_TRACK,
|
||||
SET_DEVICE_STATUS,
|
||||
SET_DIALOUT_NUMBER,
|
||||
SET_DIALOUT_COUNTRY,
|
||||
SET_DIALOUT_STATUS,
|
||||
SET_JOIN_BY_PHONE_DIALOG_VISIBLITY,
|
||||
SET_SKIP_PREJOIN,
|
||||
SET_PREJOIN_AUDIO_DISABLED,
|
||||
SET_PREJOIN_AUDIO_MUTED,
|
||||
SET_PREJOIN_DEVICE_ERRORS,
|
||||
SET_PREJOIN_PAGE_VISIBILITY,
|
||||
SET_PREJOIN_VIDEO_DISABLED,
|
||||
SET_PREJOIN_VIDEO_MUTED
|
||||
SET_PREJOIN_PAGE_VISIBILITY
|
||||
} from './actionTypes';
|
||||
|
||||
const DEFAULT_STATE = {
|
||||
audioDisabled: false,
|
||||
audioMuted: false,
|
||||
audioTrack: null,
|
||||
contentSharingTrack: null,
|
||||
country: '',
|
||||
deviceStatusText: 'prejoin.configuringDevices',
|
||||
deviceStatusType: 'ok',
|
||||
|
@ -37,10 +26,7 @@ const DEFAULT_STATE = {
|
|||
rawError: '',
|
||||
showPrejoin: true,
|
||||
showJoinByPhoneDialog: false,
|
||||
userSelectedSkipPrejoin: false,
|
||||
videoTrack: null,
|
||||
videoDisabled: false,
|
||||
videoMuted: false
|
||||
userSelectedSkipPrejoin: false
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -49,26 +35,6 @@ const DEFAULT_STATE = {
|
|||
ReducerRegistry.register(
|
||||
'features/prejoin', (state = DEFAULT_STATE, action) => {
|
||||
switch (action.type) {
|
||||
case ADD_PREJOIN_AUDIO_TRACK: {
|
||||
return {
|
||||
...state,
|
||||
audioTrack: action.value
|
||||
};
|
||||
}
|
||||
|
||||
case ADD_PREJOIN_CONTENT_SHARING_TRACK: {
|
||||
return {
|
||||
...state,
|
||||
contentSharingTrack: action.value
|
||||
};
|
||||
}
|
||||
|
||||
case ADD_PREJOIN_VIDEO_TRACK: {
|
||||
return {
|
||||
...state,
|
||||
videoTrack: action.value
|
||||
};
|
||||
}
|
||||
|
||||
case SET_SKIP_PREJOIN: {
|
||||
return {
|
||||
|
@ -83,25 +49,6 @@ ReducerRegistry.register(
|
|||
showPrejoin: action.value
|
||||
};
|
||||
|
||||
case SET_PREJOIN_VIDEO_DISABLED: {
|
||||
return {
|
||||
...state,
|
||||
videoDisabled: action.value
|
||||
};
|
||||
}
|
||||
|
||||
case SET_PREJOIN_VIDEO_MUTED:
|
||||
return {
|
||||
...state,
|
||||
videoMuted: action.value
|
||||
};
|
||||
|
||||
case SET_PREJOIN_AUDIO_MUTED:
|
||||
return {
|
||||
...state,
|
||||
audioMuted: action.value
|
||||
};
|
||||
|
||||
case SET_PREJOIN_DEVICE_ERRORS: {
|
||||
const status = getStatusFromErrors(action.value);
|
||||
|
||||
|
@ -119,13 +66,6 @@ ReducerRegistry.register(
|
|||
};
|
||||
}
|
||||
|
||||
case SET_PREJOIN_AUDIO_DISABLED: {
|
||||
return {
|
||||
...state,
|
||||
audioDisabled: true
|
||||
};
|
||||
}
|
||||
|
||||
case SET_DIALOUT_NUMBER: {
|
||||
return {
|
||||
...state,
|
||||
|
|
|
@ -12,11 +12,6 @@ import { connect } from '../../base/redux';
|
|||
import { AbstractAudioMuteButton } from '../../base/toolbox';
|
||||
import type { AbstractButtonProps } from '../../base/toolbox';
|
||||
import { isLocalTrackMuted } from '../../base/tracks';
|
||||
import {
|
||||
isPrejoinAudioMuted,
|
||||
isAudioDisabled,
|
||||
isPrejoinPageVisible
|
||||
} from '../../prejoin/functions';
|
||||
import { muteLocal } from '../../remote-video-menu/actions';
|
||||
|
||||
declare var APP: Object;
|
||||
|
@ -154,18 +149,8 @@ class AudioMuteButton extends AbstractAudioMuteButton<Props, *> {
|
|||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state): Object {
|
||||
let _audioMuted;
|
||||
let _disabled;
|
||||
|
||||
if (isPrejoinPageVisible(state)) {
|
||||
_audioMuted = isPrejoinAudioMuted(state);
|
||||
_disabled = state['features/base/config'].startSilent;
|
||||
} else {
|
||||
const tracks = state['features/base/tracks'];
|
||||
|
||||
_audioMuted = isLocalTrackMuted(tracks, MEDIA_TYPE.AUDIO);
|
||||
_disabled = state['features/base/config'].startSilent || isAudioDisabled(state);
|
||||
}
|
||||
const _audioMuted = isLocalTrackMuted(state['features/base/tracks'], MEDIA_TYPE.AUDIO);
|
||||
const _disabled = state['features/base/config'].startSilent;
|
||||
|
||||
return {
|
||||
_audioMuted,
|
||||
|
|
|
@ -9,6 +9,7 @@ import {
|
|||
sendAnalytics
|
||||
} from '../../analytics';
|
||||
import { setAudioOnly } from '../../base/audio-only';
|
||||
import { hasAvailableDevices } from '../../base/devices';
|
||||
import { translate } from '../../base/i18n';
|
||||
import {
|
||||
VIDEO_MUTISM_AUTHORITY,
|
||||
|
@ -18,11 +19,6 @@ import { connect } from '../../base/redux';
|
|||
import { AbstractVideoMuteButton } from '../../base/toolbox';
|
||||
import type { AbstractButtonProps } from '../../base/toolbox';
|
||||
import { getLocalVideoType, isLocalVideoTrackMuted } from '../../base/tracks';
|
||||
import {
|
||||
isPrejoinPageVisible,
|
||||
isPrejoinVideoDisabled,
|
||||
isPrejoinVideoMuted
|
||||
} from '../../prejoin/functions';
|
||||
|
||||
declare var APP: Object;
|
||||
|
||||
|
@ -191,19 +187,12 @@ class VideoMuteButton extends AbstractVideoMuteButton<Props, *> {
|
|||
function _mapStateToProps(state): Object {
|
||||
const { enabled: audioOnly } = state['features/base/audio-only'];
|
||||
const tracks = state['features/base/tracks'];
|
||||
let _videoMuted = isLocalVideoTrackMuted(tracks);
|
||||
let _videoDisabled = false;
|
||||
|
||||
if (isPrejoinPageVisible(state)) {
|
||||
_videoMuted = isPrejoinVideoMuted(state);
|
||||
_videoDisabled = isPrejoinVideoDisabled(state);
|
||||
}
|
||||
|
||||
return {
|
||||
_audioOnly: Boolean(audioOnly),
|
||||
_videoDisabled,
|
||||
_videoDisabled: !hasAvailableDevices(state, 'videoInput'),
|
||||
_videoMediaType: getLocalVideoType(tracks),
|
||||
_videoMuted
|
||||
_videoMuted: isLocalVideoTrackMuted(tracks)
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ import { IconArrowDown } from '../../../base/icons';
|
|||
import JitsiMeetJS from '../../../base/lib-jitsi-meet/_';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { ToolboxButtonWithIcon } from '../../../base/toolbox';
|
||||
import { getLocalJitsiVideoTrack } from '../../../base/tracks';
|
||||
import { getMediaPermissionPromptVisibility } from '../../../overlay';
|
||||
import { toggleVideoSettings, VideoSettingsPopup } from '../../../settings';
|
||||
import { isVideoSettingsButtonDisabled } from '../../functions';
|
||||
|
@ -24,6 +25,11 @@ type Props = {
|
|||
*/
|
||||
permissionPromptVisibility: boolean,
|
||||
|
||||
/**
|
||||
* Whether there is a video track or not.
|
||||
*/
|
||||
hasVideoTrack: boolean,
|
||||
|
||||
/**
|
||||
* If the button should be disabled
|
||||
*/
|
||||
|
@ -66,6 +72,17 @@ class VideoSettingsButton extends Component<Props, State> {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the settings icon is disabled.
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
_isIconDisabled() {
|
||||
const { hasVideoTrack, isDisabled } = this.props;
|
||||
|
||||
return (!this.state.hasPermissions || isDisabled) && !hasVideoTrack;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates device permissions.
|
||||
*
|
||||
|
@ -116,14 +133,13 @@ class VideoSettingsButton extends Component<Props, State> {
|
|||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
const { isDisabled, onVideoOptionsClick, visible } = this.props;
|
||||
const iconDisabled = !this.state.hasPermissions || isDisabled;
|
||||
const { onVideoOptionsClick, visible } = this.props;
|
||||
|
||||
return visible ? (
|
||||
<VideoSettingsPopup>
|
||||
<ToolboxButtonWithIcon
|
||||
icon = { IconArrowDown }
|
||||
iconDisabled = { iconDisabled }
|
||||
iconDisabled = { this._isIconDisabled() }
|
||||
onIconClick = { onVideoOptionsClick }>
|
||||
<VideoMuteButton />
|
||||
</ToolboxButtonWithIcon>
|
||||
|
@ -140,6 +156,7 @@ class VideoSettingsButton extends Component<Props, State> {
|
|||
*/
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
hasVideoTrack: Boolean(getLocalJitsiVideoTrack(state)),
|
||||
isDisabled: isVideoSettingsButtonDisabled(state),
|
||||
permissionPromptVisibility: getMediaPermissionPromptVisibility(state)
|
||||
};
|
||||
|
|
|
@ -1,11 +1,6 @@
|
|||
// @flow
|
||||
|
||||
import { hasAvailableDevices } from '../base/devices';
|
||||
import {
|
||||
isAudioDisabled,
|
||||
isPrejoinPageVisible,
|
||||
isPrejoinVideoDisabled
|
||||
} from '../prejoin';
|
||||
|
||||
declare var interfaceConfig: Object;
|
||||
|
||||
|
@ -60,12 +55,9 @@ export function isToolboxVisible(state: Object) {
|
|||
* @returns {boolean}
|
||||
*/
|
||||
export function isAudioSettingsButtonDisabled(state: Object) {
|
||||
const devicesMissing = !hasAvailableDevices(state, 'audioInput')
|
||||
&& !hasAvailableDevices(state, 'audioOutput');
|
||||
|
||||
return isPrejoinPageVisible(state)
|
||||
? devicesMissing || isAudioDisabled(state)
|
||||
: devicesMissing;
|
||||
return (!hasAvailableDevices(state, 'audioInput')
|
||||
&& !hasAvailableDevices(state, 'audioOutput'))
|
||||
|| state['features/base/config'].startSilent;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -75,9 +67,5 @@ export function isAudioSettingsButtonDisabled(state: Object) {
|
|||
* @returns {boolean}
|
||||
*/
|
||||
export function isVideoSettingsButtonDisabled(state: Object) {
|
||||
const devicesMissing = !hasAvailableDevices(state, 'videoInput');
|
||||
|
||||
return isPrejoinPageVisible(state)
|
||||
? devicesMissing || isPrejoinVideoDisabled(state)
|
||||
: devicesMissing;
|
||||
return !hasAvailableDevices(state, 'videoInput');
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue