fix(prejoin): Store prejoin tracks in 'features/base/tracks'

This commit is contained in:
Vlad Piersec 2020-06-19 10:03:26 +03:00 committed by Saúl Ibarra Corretgé
parent ec6ed6e8ec
commit 4f169988a3
17 changed files with 178 additions and 559 deletions

View File

@ -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(() => {

View File

@ -35,6 +35,10 @@
cursor: initial;
color: #fff;
background-color: #a4b8d1;
&:hover {
background-color: #a4b8d1;
}
}
svg {

View File

@ -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.
*

View File

@ -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.
*

View File

@ -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 ]));
}
});
};
}

View File

@ -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.
*

View File

@ -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 ] }));

View File

@ -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';

View File

@ -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.
*

View File

@ -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)
};
}

View File

@ -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'.
*

View File

@ -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);
});

View File

@ -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,

View File

@ -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,

View File

@ -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)
};
}

View File

@ -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)
};

View File

@ -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');
}