ref(TS) Convert base/tracks to TS (#12219)
This commit is contained in:
parent
fb2cfaa204
commit
3426960d5a
|
@ -1,6 +1,13 @@
|
||||||
|
import { IStore } from "./react/features/app/types";
|
||||||
|
|
||||||
export {};
|
export {};
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
const APP: any;
|
const APP: {
|
||||||
|
store: IStore;
|
||||||
|
UI: any;
|
||||||
|
API: any;
|
||||||
|
conference: any;
|
||||||
|
};
|
||||||
const interfaceConfig: any;
|
const interfaceConfig: any;
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,6 +52,7 @@ export interface IJitsiConference {
|
||||||
muteParticipant: Function;
|
muteParticipant: Function;
|
||||||
on: Function;
|
on: Function;
|
||||||
removeTrack: Function;
|
removeTrack: Function;
|
||||||
|
replaceTrack: Function;
|
||||||
sendCommand: Function;
|
sendCommand: Function;
|
||||||
sendEndpointMessage: Function;
|
sendEndpointMessage: Function;
|
||||||
sessionId: string;
|
sessionId: string;
|
||||||
|
|
|
@ -54,7 +54,9 @@ export const VIDEO_MUTISM_AUTHORITY = {
|
||||||
*
|
*
|
||||||
* @enum {string}
|
* @enum {string}
|
||||||
*/
|
*/
|
||||||
export const VIDEO_TYPE = {
|
export const VIDEO_TYPE: { [key: string]: VideoType; } = {
|
||||||
CAMERA: 'camera',
|
CAMERA: 'camera',
|
||||||
DESKTOP: 'desktop'
|
DESKTOP: 'desktop'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type VideoType = 'camera' | 'desktop';
|
||||||
|
|
|
@ -1,25 +1,26 @@
|
||||||
/* global APP */
|
import { createTrackMutedEvent } from '../../analytics/AnalyticsEvents';
|
||||||
|
import { sendAnalytics } from '../../analytics/functions';
|
||||||
import {
|
import { IStore } from '../../app/types';
|
||||||
createTrackMutedEvent,
|
import { showErrorNotification, showNotification } from '../../notifications/actions';
|
||||||
sendAnalytics
|
import { NOTIFICATION_TIMEOUT, NOTIFICATION_TIMEOUT_TYPE } from '../../notifications/constants';
|
||||||
} from '../../analytics';
|
import { getCurrentConference } from '../conference/functions';
|
||||||
import { NOTIFICATION_TIMEOUT_TYPE, showErrorNotification, showNotification } from '../../notifications';
|
import { IJitsiConference } from '../conference/reducer';
|
||||||
import { getCurrentConference } from '../conference';
|
import { getMultipleVideoSendingSupportFeatureFlag } from '../config/functions.any';
|
||||||
import { getMultipleVideoSendingSupportFeatureFlag } from '../config';
|
|
||||||
import { JitsiTrackErrors, JitsiTrackEvents } from '../lib-jitsi-meet';
|
import { JitsiTrackErrors, JitsiTrackEvents } from '../lib-jitsi-meet';
|
||||||
|
// eslint-disable-next-line lines-around-comment
|
||||||
|
// @ts-ignore
|
||||||
import { createLocalTrack } from '../lib-jitsi-meet/functions';
|
import { createLocalTrack } from '../lib-jitsi-meet/functions';
|
||||||
|
import { setAudioMuted, setScreenshareMuted, setVideoMuted } from '../media/actions';
|
||||||
import {
|
import {
|
||||||
CAMERA_FACING_MODE,
|
CAMERA_FACING_MODE,
|
||||||
|
MediaType,
|
||||||
MEDIA_TYPE,
|
MEDIA_TYPE,
|
||||||
setAudioMuted,
|
VideoType,
|
||||||
setScreenshareMuted,
|
|
||||||
setVideoMuted,
|
|
||||||
VIDEO_MUTISM_AUTHORITY,
|
VIDEO_MUTISM_AUTHORITY,
|
||||||
VIDEO_TYPE
|
VIDEO_TYPE
|
||||||
} from '../media';
|
} from '../media/constants';
|
||||||
import { getLocalParticipant } from '../participants';
|
import { getLocalParticipant } from '../participants/functions';
|
||||||
import { updateSettings } from '../settings';
|
import { updateSettings } from '../settings/actions';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
SET_NO_SRC_DATA_NOTIFICATION_UID,
|
SET_NO_SRC_DATA_NOTIFICATION_UID,
|
||||||
|
@ -43,6 +44,7 @@ import {
|
||||||
getTrackByJitsiTrack
|
getTrackByJitsiTrack
|
||||||
} from './functions';
|
} from './functions';
|
||||||
import logger from './logger';
|
import logger from './logger';
|
||||||
|
import { TrackOptions } from './types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a given local track to the conference.
|
* Add a given local track to the conference.
|
||||||
|
@ -50,8 +52,8 @@ import logger from './logger';
|
||||||
* @param {JitsiLocalTrack} newTrack - The local track to be added to the conference.
|
* @param {JitsiLocalTrack} newTrack - The local track to be added to the conference.
|
||||||
* @returns {Function}
|
* @returns {Function}
|
||||||
*/
|
*/
|
||||||
export function addLocalTrack(newTrack) {
|
export function addLocalTrack(newTrack: any) {
|
||||||
return async (dispatch, getState) => {
|
return async (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||||
const conference = getCurrentConference(getState());
|
const conference = getCurrentConference(getState());
|
||||||
|
|
||||||
if (conference) {
|
if (conference) {
|
||||||
|
@ -82,8 +84,8 @@ export function addLocalTrack(newTrack) {
|
||||||
*
|
*
|
||||||
* @returns {Function}
|
* @returns {Function}
|
||||||
*/
|
*/
|
||||||
export function createDesiredLocalTracks(...desiredTypes) {
|
export function createDesiredLocalTracks(...desiredTypes: any) {
|
||||||
return (dispatch, getState) => {
|
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
|
|
||||||
dispatch(destroyLocalDesktopTrackIfExists());
|
dispatch(destroyLocalDesktopTrackIfExists());
|
||||||
|
@ -118,7 +120,7 @@ export function createDesiredLocalTracks(...desiredTypes) {
|
||||||
|
|
||||||
// We need to create the desired tracks which are not already available.
|
// We need to create the desired tracks which are not already available.
|
||||||
const createTypes
|
const createTypes
|
||||||
= desiredTypes.filter(type => availableTypes.indexOf(type) === -1);
|
= desiredTypes.filter((type: MediaType) => availableTypes.indexOf(type) === -1);
|
||||||
|
|
||||||
createTypes.length
|
createTypes.length
|
||||||
&& dispatch(createLocalTracksA({ devices: createTypes }));
|
&& dispatch(createLocalTracksA({ devices: createTypes }));
|
||||||
|
@ -132,8 +134,8 @@ export function createDesiredLocalTracks(...desiredTypes) {
|
||||||
* @param {Object} [options] - For info @see JitsiMeetJS.createLocalTracks.
|
* @param {Object} [options] - For info @see JitsiMeetJS.createLocalTracks.
|
||||||
* @returns {Function}
|
* @returns {Function}
|
||||||
*/
|
*/
|
||||||
export function createLocalTracksA(options = {}) {
|
export function createLocalTracksA(options: TrackOptions = {}) {
|
||||||
return (dispatch, getState) => {
|
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||||
const devices
|
const devices
|
||||||
= options.devices || [ MEDIA_TYPE.AUDIO, MEDIA_TYPE.VIDEO ];
|
= options.devices || [ MEDIA_TYPE.AUDIO, MEDIA_TYPE.VIDEO ];
|
||||||
const store = {
|
const store = {
|
||||||
|
@ -154,7 +156,7 @@ export function createLocalTracksA(options = {}) {
|
||||||
for (const device of devices) {
|
for (const device of devices) {
|
||||||
if (getLocalTrack(
|
if (getLocalTrack(
|
||||||
getState()['features/base/tracks'],
|
getState()['features/base/tracks'],
|
||||||
device,
|
device as MediaType,
|
||||||
/* includePending */ true)) {
|
/* includePending */ true)) {
|
||||||
throw new Error(`Local track for ${device} already exists`);
|
throw new Error(`Local track for ${device} already exists`);
|
||||||
}
|
}
|
||||||
|
@ -170,7 +172,7 @@ export function createLocalTracksA(options = {}) {
|
||||||
},
|
},
|
||||||
store)
|
store)
|
||||||
.then(
|
.then(
|
||||||
localTracks => {
|
(localTracks: any[]) => {
|
||||||
// Because GUM is called for 1 device (which is actually
|
// Because GUM is called for 1 device (which is actually
|
||||||
// a media type 'audio', 'video', 'screen', etc.) we
|
// a media type 'audio', 'video', 'screen', etc.) we
|
||||||
// should not get more than one JitsiTrack.
|
// should not get more than one JitsiTrack.
|
||||||
|
@ -184,15 +186,15 @@ export function createLocalTracksA(options = {}) {
|
||||||
if (gumProcess.canceled) {
|
if (gumProcess.canceled) {
|
||||||
return _disposeTracks(localTracks)
|
return _disposeTracks(localTracks)
|
||||||
.then(() =>
|
.then(() =>
|
||||||
dispatch(_trackCreateCanceled(device)));
|
dispatch(_trackCreateCanceled(device as MediaType)));
|
||||||
}
|
}
|
||||||
|
|
||||||
return dispatch(trackAdded(localTracks[0]));
|
return dispatch(trackAdded(localTracks[0]));
|
||||||
},
|
},
|
||||||
reason =>
|
(reason: Error) =>
|
||||||
dispatch(
|
dispatch(
|
||||||
gumProcess.canceled
|
gumProcess.canceled
|
||||||
? _trackCreateCanceled(device)
|
? _trackCreateCanceled(device as MediaType)
|
||||||
: _onCreateLocalTracksRejected(
|
: _onCreateLocalTracksRejected(
|
||||||
reason,
|
reason,
|
||||||
device)));
|
device)));
|
||||||
|
@ -230,12 +232,12 @@ export function createLocalTracksA(options = {}) {
|
||||||
*/
|
*/
|
||||||
export function destroyLocalTracks(track = null) {
|
export function destroyLocalTracks(track = null) {
|
||||||
if (track) {
|
if (track) {
|
||||||
return dispatch => {
|
return (dispatch: IStore['dispatch']) => {
|
||||||
dispatch(_disposeAndRemoveTracks([ track ]));
|
dispatch(_disposeAndRemoveTracks([ track ]));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return (dispatch, getState) => {
|
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||||
// First wait until any getUserMedia in progress is settled and then get
|
// First wait until any getUserMedia in progress is settled and then get
|
||||||
// rid of all local tracks.
|
// rid of all local tracks.
|
||||||
_cancelGUMProcesses(getState)
|
_cancelGUMProcesses(getState)
|
||||||
|
@ -257,7 +259,7 @@ export function destroyLocalTracks(track = null) {
|
||||||
* track: Track
|
* track: Track
|
||||||
* }}
|
* }}
|
||||||
*/
|
*/
|
||||||
export function noDataFromSource(track) {
|
export function noDataFromSource(track: any) {
|
||||||
return {
|
return {
|
||||||
type: TRACK_NO_DATA_FROM_SOURCE,
|
type: TRACK_NO_DATA_FROM_SOURCE,
|
||||||
track
|
track
|
||||||
|
@ -270,8 +272,8 @@ export function noDataFromSource(track) {
|
||||||
* @param {JitsiLocalTrack} jitsiTrack - The track.
|
* @param {JitsiLocalTrack} jitsiTrack - The track.
|
||||||
* @returns {Function}
|
* @returns {Function}
|
||||||
*/
|
*/
|
||||||
export function showNoDataFromSourceVideoError(jitsiTrack) {
|
export function showNoDataFromSourceVideoError(jitsiTrack: any) {
|
||||||
return async (dispatch, getState) => {
|
return async (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||||
let notificationInfo;
|
let notificationInfo;
|
||||||
|
|
||||||
const track = getTrackByJitsiTrack(getState()['features/base/tracks'], jitsiTrack);
|
const track = getTrackByJitsiTrack(getState()['features/base/tracks'], jitsiTrack);
|
||||||
|
@ -289,7 +291,7 @@ export function showNoDataFromSourceVideoError(jitsiTrack) {
|
||||||
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
|
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
|
||||||
|
|
||||||
notificationInfo = {
|
notificationInfo = {
|
||||||
uid: notificationAction.uid
|
uid: notificationAction?.uid
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
dispatch(trackNoDataFromSourceNotificationInfoChanged(jitsiTrack, notificationInfo));
|
dispatch(trackNoDataFromSourceNotificationInfoChanged(jitsiTrack, notificationInfo));
|
||||||
|
@ -311,7 +313,8 @@ export function showNoDataFromSourceVideoError(jitsiTrack) {
|
||||||
* shareOptions: Object
|
* shareOptions: Object
|
||||||
* }}
|
* }}
|
||||||
*/
|
*/
|
||||||
export function toggleScreensharing(enabled, audioOnly = false, ignoreDidHaveVideo = false, shareOptions = {}) {
|
export function toggleScreensharing(enabled: boolean, audioOnly = false,
|
||||||
|
ignoreDidHaveVideo = false, shareOptions = {}) {
|
||||||
return {
|
return {
|
||||||
type: TOGGLE_SCREENSHARING,
|
type: TOGGLE_SCREENSHARING,
|
||||||
enabled,
|
enabled,
|
||||||
|
@ -333,8 +336,8 @@ export function toggleScreensharing(enabled, audioOnly = false, ignoreDidHaveVid
|
||||||
* will be used.
|
* will be used.
|
||||||
* @returns {Function}
|
* @returns {Function}
|
||||||
*/
|
*/
|
||||||
export function replaceLocalTrack(oldTrack, newTrack, conference) {
|
export function replaceLocalTrack(oldTrack: any, newTrack: any, conference?: IJitsiConference) {
|
||||||
return async (dispatch, getState) => {
|
return async (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||||
conference
|
conference
|
||||||
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
@ -355,8 +358,8 @@ export function replaceLocalTrack(oldTrack, newTrack, conference) {
|
||||||
* @param {JitsiLocalTrack|null} newTrack - The track to use instead.
|
* @param {JitsiLocalTrack|null} newTrack - The track to use instead.
|
||||||
* @returns {Function}
|
* @returns {Function}
|
||||||
*/
|
*/
|
||||||
function replaceStoredTracks(oldTrack, newTrack) {
|
function replaceStoredTracks(oldTrack: any, newTrack: any) {
|
||||||
return async (dispatch, getState) => {
|
return async (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||||
// We call dispose after doing the replace because dispose will
|
// We call dispose after doing the replace because dispose will
|
||||||
// try and do a new o/a after the track removes itself. Doing it
|
// try and do a new o/a after the track removes itself. Doing it
|
||||||
// after means the JitsiLocalTrack.conference is already
|
// after means the JitsiLocalTrack.conference is already
|
||||||
|
@ -395,14 +398,14 @@ function replaceStoredTracks(oldTrack, newTrack) {
|
||||||
* @param {(JitsiLocalTrack|JitsiRemoteTrack)} track - JitsiTrack instance.
|
* @param {(JitsiLocalTrack|JitsiRemoteTrack)} track - JitsiTrack instance.
|
||||||
* @returns {{ type: TRACK_ADDED, track: Track }}
|
* @returns {{ type: TRACK_ADDED, track: Track }}
|
||||||
*/
|
*/
|
||||||
export function trackAdded(track) {
|
export function trackAdded(track: any) {
|
||||||
return async (dispatch, getState) => {
|
return async (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||||
track.on(
|
track.on(
|
||||||
JitsiTrackEvents.TRACK_MUTE_CHANGED,
|
JitsiTrackEvents.TRACK_MUTE_CHANGED,
|
||||||
() => dispatch(trackMutedChanged(track)));
|
() => dispatch(trackMutedChanged(track)));
|
||||||
track.on(
|
track.on(
|
||||||
JitsiTrackEvents.TRACK_VIDEOTYPE_CHANGED,
|
JitsiTrackEvents.TRACK_VIDEOTYPE_CHANGED,
|
||||||
type => dispatch(trackVideoTypeChanged(track, type)));
|
(type: VideoType) => dispatch(trackVideoTypeChanged(track, type)));
|
||||||
|
|
||||||
// participantId
|
// participantId
|
||||||
const local = track.isLocal();
|
const local = track.isLocal();
|
||||||
|
@ -434,13 +437,13 @@ export function trackAdded(track) {
|
||||||
// Set the notification ID so that other parts of the application know that this was
|
// Set the notification ID so that other parts of the application know that this was
|
||||||
// displayed in the context of the current device.
|
// displayed in the context of the current device.
|
||||||
// I.E. The no-audio-signal notification shouldn't be displayed if this was already shown.
|
// I.E. The no-audio-signal notification shouldn't be displayed if this was already shown.
|
||||||
dispatch(setNoSrcDataNotificationUid(notificationAction.uid));
|
dispatch(setNoSrcDataNotificationUid(notificationAction?.uid));
|
||||||
|
|
||||||
noDataFromSourceNotificationInfo = { uid: notificationAction.uid };
|
noDataFromSourceNotificationInfo = { uid: notificationAction?.uid };
|
||||||
} else {
|
} else {
|
||||||
const timeout = setTimeout(() => dispatch(
|
const timeout = setTimeout(() => dispatch(
|
||||||
showNoDataFromSourceVideoError(track)),
|
showNoDataFromSourceVideoError(track)),
|
||||||
NOTIFICATION_TIMEOUT_TYPE.MEDIUM);
|
NOTIFICATION_TIMEOUT.MEDIUM);
|
||||||
|
|
||||||
noDataFromSourceNotificationInfo = { timeout };
|
noDataFromSourceNotificationInfo = { timeout };
|
||||||
}
|
}
|
||||||
|
@ -486,7 +489,7 @@ export function trackAdded(track) {
|
||||||
* track: Track
|
* track: Track
|
||||||
* }}
|
* }}
|
||||||
*/
|
*/
|
||||||
export function trackMutedChanged(track) {
|
export function trackMutedChanged(track: any) {
|
||||||
return {
|
return {
|
||||||
type: TRACK_UPDATED,
|
type: TRACK_UPDATED,
|
||||||
track: {
|
track: {
|
||||||
|
@ -507,7 +510,7 @@ export function trackMutedChanged(track) {
|
||||||
* track: Track
|
* track: Track
|
||||||
* }}
|
* }}
|
||||||
*/
|
*/
|
||||||
export function trackMuteUnmuteFailed(track, wasMuting) {
|
export function trackMuteUnmuteFailed(track: any, wasMuting: boolean) {
|
||||||
return {
|
return {
|
||||||
type: TRACK_MUTE_UNMUTE_FAILED,
|
type: TRACK_MUTE_UNMUTE_FAILED,
|
||||||
track,
|
track,
|
||||||
|
@ -525,7 +528,7 @@ export function trackMuteUnmuteFailed(track, wasMuting) {
|
||||||
* track: Track
|
* track: Track
|
||||||
* }}
|
* }}
|
||||||
*/
|
*/
|
||||||
export function trackNoDataFromSourceNotificationInfoChanged(track, noDataFromSourceNotificationInfo) {
|
export function trackNoDataFromSourceNotificationInfoChanged(track: any, noDataFromSourceNotificationInfo?: Object) {
|
||||||
return {
|
return {
|
||||||
type: TRACK_UPDATED,
|
type: TRACK_UPDATED,
|
||||||
track: {
|
track: {
|
||||||
|
@ -545,7 +548,7 @@ export function trackNoDataFromSourceNotificationInfoChanged(track, noDataFromSo
|
||||||
* track: Track
|
* track: Track
|
||||||
* }}
|
* }}
|
||||||
*/
|
*/
|
||||||
export function trackRemoved(track) {
|
export function trackRemoved(track: any) {
|
||||||
track.removeAllListeners(JitsiTrackEvents.TRACK_MUTE_CHANGED);
|
track.removeAllListeners(JitsiTrackEvents.TRACK_MUTE_CHANGED);
|
||||||
track.removeAllListeners(JitsiTrackEvents.TRACK_VIDEOTYPE_CHANGED);
|
track.removeAllListeners(JitsiTrackEvents.TRACK_VIDEOTYPE_CHANGED);
|
||||||
track.removeAllListeners(JitsiTrackEvents.NO_DATA_FROM_SOURCE);
|
track.removeAllListeners(JitsiTrackEvents.NO_DATA_FROM_SOURCE);
|
||||||
|
@ -567,7 +570,7 @@ export function trackRemoved(track) {
|
||||||
* track: Track
|
* track: Track
|
||||||
* }}
|
* }}
|
||||||
*/
|
*/
|
||||||
export function trackVideoStarted(track) {
|
export function trackVideoStarted(track: any) {
|
||||||
return {
|
return {
|
||||||
type: TRACK_UPDATED,
|
type: TRACK_UPDATED,
|
||||||
track: {
|
track: {
|
||||||
|
@ -587,7 +590,7 @@ export function trackVideoStarted(track) {
|
||||||
* track: Track
|
* track: Track
|
||||||
* }}
|
* }}
|
||||||
*/
|
*/
|
||||||
export function trackVideoTypeChanged(track, videoType) {
|
export function trackVideoTypeChanged(track: any, videoType: VideoType) {
|
||||||
return {
|
return {
|
||||||
type: TRACK_UPDATED,
|
type: TRACK_UPDATED,
|
||||||
track: {
|
track: {
|
||||||
|
@ -607,7 +610,7 @@ export function trackVideoTypeChanged(track, videoType) {
|
||||||
* track: Track
|
* track: Track
|
||||||
* }}
|
* }}
|
||||||
*/
|
*/
|
||||||
export function trackStreamingStatusChanged(track, streamingStatus) {
|
export function trackStreamingStatusChanged(track: any, streamingStatus: string) {
|
||||||
return {
|
return {
|
||||||
type: TRACK_UPDATED,
|
type: TRACK_UPDATED,
|
||||||
track: {
|
track: {
|
||||||
|
@ -624,8 +627,8 @@ export function trackStreamingStatusChanged(track, streamingStatus) {
|
||||||
* @private
|
* @private
|
||||||
* @returns {Function}
|
* @returns {Function}
|
||||||
*/
|
*/
|
||||||
function _addTracks(tracks) {
|
function _addTracks(tracks: any[]) {
|
||||||
return dispatch => Promise.all(tracks.map(t => dispatch(trackAdded(t))));
|
return (dispatch: IStore['dispatch']) => Promise.all(tracks.map(t => dispatch(trackAdded(t))));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -640,16 +643,16 @@ function _addTracks(tracks) {
|
||||||
* about here is to be sure that the {@code getUserMedia} callbacks have
|
* about here is to be sure that the {@code getUserMedia} callbacks have
|
||||||
* completed (i.e. Returned from the native side).
|
* completed (i.e. Returned from the native side).
|
||||||
*/
|
*/
|
||||||
function _cancelGUMProcesses(getState) {
|
function _cancelGUMProcesses(getState: IStore['getState']) {
|
||||||
const logError
|
const logError
|
||||||
= error =>
|
= (error: Error) =>
|
||||||
logger.error('gumProcess.cancel failed', JSON.stringify(error));
|
logger.error('gumProcess.cancel failed', JSON.stringify(error));
|
||||||
|
|
||||||
return Promise.all(
|
return Promise.all(
|
||||||
getState()['features/base/tracks']
|
getState()['features/base/tracks']
|
||||||
.filter(t => t.local)
|
.filter(t => t.local)
|
||||||
.map(({ gumProcess }) =>
|
.map(({ gumProcess }: any) =>
|
||||||
gumProcess && gumProcess.cancel().catch(logError)));
|
gumProcess?.cancel().catch(logError)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -659,8 +662,8 @@ function _cancelGUMProcesses(getState) {
|
||||||
* @protected
|
* @protected
|
||||||
* @returns {Function}
|
* @returns {Function}
|
||||||
*/
|
*/
|
||||||
export function _disposeAndRemoveTracks(tracks) {
|
export function _disposeAndRemoveTracks(tracks: any[]) {
|
||||||
return dispatch =>
|
return (dispatch: IStore['dispatch']) =>
|
||||||
_disposeTracks(tracks)
|
_disposeTracks(tracks)
|
||||||
.then(() =>
|
.then(() =>
|
||||||
Promise.all(tracks.map(t => dispatch(trackRemoved(t)))));
|
Promise.all(tracks.map(t => dispatch(trackRemoved(t)))));
|
||||||
|
@ -674,11 +677,11 @@ export function _disposeAndRemoveTracks(tracks) {
|
||||||
* @returns {Promise} - A Promise resolved once {@link JitsiTrack.dispose()} is
|
* @returns {Promise} - A Promise resolved once {@link JitsiTrack.dispose()} is
|
||||||
* done for every track from the list.
|
* done for every track from the list.
|
||||||
*/
|
*/
|
||||||
function _disposeTracks(tracks) {
|
function _disposeTracks(tracks: any) {
|
||||||
return Promise.all(
|
return Promise.all(
|
||||||
tracks.map(t =>
|
tracks.map((t: any) =>
|
||||||
t.dispose()
|
t.dispose()
|
||||||
.catch(err => {
|
.catch((err: Error) => {
|
||||||
// Track might be already disposed so ignore such an error.
|
// Track might be already disposed so ignore such an error.
|
||||||
// Of course, re-throw any other error(s).
|
// Of course, re-throw any other error(s).
|
||||||
if (err.name !== JitsiTrackErrors.TRACK_IS_DISPOSED) {
|
if (err.name !== JitsiTrackErrors.TRACK_IS_DISPOSED) {
|
||||||
|
@ -697,8 +700,8 @@ function _disposeTracks(tracks) {
|
||||||
* @private
|
* @private
|
||||||
* @returns {Function}
|
* @returns {Function}
|
||||||
*/
|
*/
|
||||||
function _onCreateLocalTracksRejected(error, device) {
|
function _onCreateLocalTracksRejected(error: Error, device: string) {
|
||||||
return dispatch => {
|
return (dispatch: IStore['dispatch']) => {
|
||||||
// If permissions are not allowed, alert the user.
|
// If permissions are not allowed, alert the user.
|
||||||
dispatch({
|
dispatch({
|
||||||
type: TRACK_CREATE_ERROR,
|
type: TRACK_CREATE_ERROR,
|
||||||
|
@ -724,11 +727,10 @@ function _onCreateLocalTracksRejected(error, device) {
|
||||||
* @private
|
* @private
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
function _shouldMirror(track) {
|
function _shouldMirror(track: any) {
|
||||||
return (
|
return (
|
||||||
track
|
track?.isLocal()
|
||||||
&& track.isLocal()
|
&& track?.isVideoTrack()
|
||||||
&& track.isVideoTrack()
|
|
||||||
|
|
||||||
// XXX The type of the return value of JitsiLocalTrack's
|
// XXX The type of the return value of JitsiLocalTrack's
|
||||||
// getCameraFacingMode happens to be named CAMERA_FACING_MODE as
|
// getCameraFacingMode happens to be named CAMERA_FACING_MODE as
|
||||||
|
@ -736,7 +738,7 @@ function _shouldMirror(track) {
|
||||||
// of the value on the right side of the equality check is defined
|
// of the value on the right side of the equality check is defined
|
||||||
// by jitsi-meet. The type definitions are surely compatible today
|
// by jitsi-meet. The type definitions are surely compatible today
|
||||||
// but that may not be the case tomorrow.
|
// but that may not be the case tomorrow.
|
||||||
&& track.getCameraFacingMode() === CAMERA_FACING_MODE.USER);
|
&& track?.getCameraFacingMode() === CAMERA_FACING_MODE.USER);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -752,7 +754,7 @@ function _shouldMirror(track) {
|
||||||
* trackType: MEDIA_TYPE
|
* trackType: MEDIA_TYPE
|
||||||
* }}
|
* }}
|
||||||
*/
|
*/
|
||||||
function _trackCreateCanceled(mediaType) {
|
function _trackCreateCanceled(mediaType: MediaType) {
|
||||||
return {
|
return {
|
||||||
type: TRACK_CREATE_CANCELED,
|
type: TRACK_CREATE_CANCELED,
|
||||||
trackType: mediaType
|
trackType: mediaType
|
||||||
|
@ -765,7 +767,7 @@ function _trackCreateCanceled(mediaType) {
|
||||||
* @returns {Function}
|
* @returns {Function}
|
||||||
*/
|
*/
|
||||||
export function destroyLocalDesktopTrackIfExists() {
|
export function destroyLocalDesktopTrackIfExists() {
|
||||||
return (dispatch, getState) => {
|
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||||
const videoTrack = getLocalVideoTrack(getState()['features/base/tracks']);
|
const videoTrack = getLocalVideoTrack(getState()['features/base/tracks']);
|
||||||
const isDesktopTrack = videoTrack && videoTrack.videoType === VIDEO_TYPE.DESKTOP;
|
const isDesktopTrack = videoTrack && videoTrack.videoType === VIDEO_TYPE.DESKTOP;
|
||||||
|
|
||||||
|
@ -782,10 +784,10 @@ export function destroyLocalDesktopTrackIfExists() {
|
||||||
* @param {number} uid - Notification UID.
|
* @param {number} uid - Notification UID.
|
||||||
* @returns {{
|
* @returns {{
|
||||||
* type: SET_NO_AUDIO_SIGNAL_UID,
|
* type: SET_NO_AUDIO_SIGNAL_UID,
|
||||||
* uid: number
|
* uid: string
|
||||||
* }}
|
* }}
|
||||||
*/
|
*/
|
||||||
export function setNoSrcDataNotificationUid(uid) {
|
export function setNoSrcDataNotificationUid(uid?: string) {
|
||||||
return {
|
return {
|
||||||
type: SET_NO_SRC_DATA_NOTIFICATION_UID,
|
type: SET_NO_SRC_DATA_NOTIFICATION_UID,
|
||||||
uid
|
uid
|
||||||
|
@ -803,7 +805,7 @@ export function setNoSrcDataNotificationUid(uid) {
|
||||||
* name: string
|
* name: string
|
||||||
* }}
|
* }}
|
||||||
*/
|
*/
|
||||||
export function updateLastTrackVideoMediaEvent(track, name) {
|
export function updateLastTrackVideoMediaEvent(track: any, name: string) {
|
||||||
return {
|
return {
|
||||||
type: TRACK_UPDATE_LAST_VIDEO_MEDIA_EVENT,
|
type: TRACK_UPDATE_LAST_VIDEO_MEDIA_EVENT,
|
||||||
track,
|
track,
|
||||||
|
@ -817,10 +819,10 @@ export function updateLastTrackVideoMediaEvent(track, name) {
|
||||||
* @returns {Function}
|
* @returns {Function}
|
||||||
*/
|
*/
|
||||||
export function toggleCamera() {
|
export function toggleCamera() {
|
||||||
return async (dispatch, getState) => {
|
return async (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const tracks = state['features/base/tracks'];
|
const tracks = state['features/base/tracks'];
|
||||||
const localVideoTrack = getLocalVideoTrack(tracks).jitsiTrack;
|
const localVideoTrack = getLocalVideoTrack(tracks)?.jitsiTrack;
|
||||||
const currentFacingMode = localVideoTrack.getCameraFacingMode();
|
const currentFacingMode = localVideoTrack.getCameraFacingMode();
|
||||||
|
|
||||||
/**
|
/**
|
|
@ -1,39 +1,44 @@
|
||||||
/* global APP */
|
import { IState, IStore } from '../../app/types';
|
||||||
|
import { IStateful } from '../app/types';
|
||||||
import {
|
import {
|
||||||
getMultipleVideoSendingSupportFeatureFlag,
|
getMultipleVideoSendingSupportFeatureFlag,
|
||||||
getMultipleVideoSupportFeatureFlag
|
getMultipleVideoSupportFeatureFlag
|
||||||
} from '../config/functions.any';
|
} from '../config/functions.any';
|
||||||
import { isMobileBrowser } from '../environment/utils';
|
import { isMobileBrowser } from '../environment/utils';
|
||||||
import JitsiMeetJS, { JitsiTrackErrors, browser } from '../lib-jitsi-meet';
|
import JitsiMeetJS, { JitsiTrackErrors, browser } from '../lib-jitsi-meet';
|
||||||
import { MEDIA_TYPE, VIDEO_TYPE, setAudioMuted } from '../media';
|
import { setAudioMuted } from '../media/actions';
|
||||||
import { getParticipantByIdOrUndefined, getVirtualScreenshareParticipantOwnerId } from '../participants';
|
import { MediaType, MEDIA_TYPE, VIDEO_TYPE } from '../media/constants';
|
||||||
import { toState } from '../redux';
|
import { getParticipantByIdOrUndefined, getVirtualScreenshareParticipantOwnerId } from '../participants/functions';
|
||||||
|
import { Participant } from '../participants/types';
|
||||||
|
import { toState } from '../redux/functions';
|
||||||
import {
|
import {
|
||||||
getUserSelectedCameraDeviceId,
|
getUserSelectedCameraDeviceId,
|
||||||
getUserSelectedMicDeviceId
|
getUserSelectedMicDeviceId
|
||||||
} from '../settings';
|
} from '../settings/functions.any';
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
import loadEffects from './loadEffects';
|
import loadEffects from './loadEffects';
|
||||||
import logger from './logger';
|
import logger from './logger';
|
||||||
|
import { ITrack } from './reducer';
|
||||||
|
import { TrackOptions } from './types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns root tracks state.
|
* Returns root tracks state.
|
||||||
*
|
*
|
||||||
* @param {Object} state - Global state.
|
* @param {IState} state - Global state.
|
||||||
* @returns {Object} Tracks state.
|
* @returns {Object} Tracks state.
|
||||||
*/
|
*/
|
||||||
export const getTrackState = state => state['features/base/tracks'];
|
export const getTrackState = (state: IState) => state['features/base/tracks'];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the passed media type is muted for the participant.
|
* Checks if the passed media type is muted for the participant.
|
||||||
*
|
*
|
||||||
* @param {Object} participant - Participant reference.
|
* @param {Participant} participant - Participant reference.
|
||||||
* @param {MEDIA_TYPE} mediaType - Media type.
|
* @param {MediaType} mediaType - Media type.
|
||||||
* @param {Object} state - Global state.
|
* @param {IState} state - Global state.
|
||||||
* @returns {boolean} - Is the media type muted for the participant.
|
* @returns {boolean} - Is the media type muted for the participant.
|
||||||
*/
|
*/
|
||||||
export function isParticipantMediaMuted(participant, mediaType, state) {
|
export function isParticipantMediaMuted(participant: Participant, mediaType: MediaType, state: IState) {
|
||||||
if (!participant) {
|
if (!participant) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -52,22 +57,22 @@ export function isParticipantMediaMuted(participant, mediaType, state) {
|
||||||
/**
|
/**
|
||||||
* Checks if the participant is audio muted.
|
* Checks if the participant is audio muted.
|
||||||
*
|
*
|
||||||
* @param {Object} participant - Participant reference.
|
* @param {Participant} participant - Participant reference.
|
||||||
* @param {Object} state - Global state.
|
* @param {IState} state - Global state.
|
||||||
* @returns {boolean} - Is audio muted for the participant.
|
* @returns {boolean} - Is audio muted for the participant.
|
||||||
*/
|
*/
|
||||||
export function isParticipantAudioMuted(participant, state) {
|
export function isParticipantAudioMuted(participant: Participant, state: IState) {
|
||||||
return isParticipantMediaMuted(participant, MEDIA_TYPE.AUDIO, state);
|
return isParticipantMediaMuted(participant, MEDIA_TYPE.AUDIO, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the participant is video muted.
|
* Checks if the participant is video muted.
|
||||||
*
|
*
|
||||||
* @param {Object} participant - Participant reference.
|
* @param {Participant} participant - Participant reference.
|
||||||
* @param {Object} state - Global state.
|
* @param {IState} state - Global state.
|
||||||
* @returns {boolean} - Is video muted for the participant.
|
* @returns {boolean} - Is video muted for the participant.
|
||||||
*/
|
*/
|
||||||
export function isParticipantVideoMuted(participant, state) {
|
export function isParticipantVideoMuted(participant: Participant, state: IState) {
|
||||||
return isParticipantMediaMuted(participant, MEDIA_TYPE.VIDEO, state);
|
return isParticipantMediaMuted(participant, MEDIA_TYPE.VIDEO, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,7 +88,7 @@ export function isParticipantVideoMuted(participant, state) {
|
||||||
* shared.
|
* shared.
|
||||||
* @returns {Promise<JitsiLocalTrack>}
|
* @returns {Promise<JitsiLocalTrack>}
|
||||||
*/
|
*/
|
||||||
export async function createLocalPresenterTrack(options, desktopHeight) {
|
export async function createLocalPresenterTrack(options: TrackOptions, desktopHeight: number) {
|
||||||
const { cameraDeviceId } = options;
|
const { cameraDeviceId } = options;
|
||||||
|
|
||||||
// compute the constraints of the camera track based on the resolution
|
// compute the constraints of the camera track based on the resolution
|
||||||
|
@ -130,11 +135,11 @@ export async function createLocalPresenterTrack(options, desktopHeight) {
|
||||||
* @param {boolean} [options.fireSlowPromiseEvent] - Whether lib-jitsi-meet
|
* @param {boolean} [options.fireSlowPromiseEvent] - Whether lib-jitsi-meet
|
||||||
* should check for a slow {@code getUserMedia} request and fire a
|
* should check for a slow {@code getUserMedia} request and fire a
|
||||||
* corresponding event.
|
* corresponding event.
|
||||||
* @param {Object} store - The redux store in the context of which the function
|
* @param {IStore} store - The redux store in the context of which the function
|
||||||
* is to execute and from which state such as {@code config} is to be retrieved.
|
* is to execute and from which state such as {@code config} is to be retrieved.
|
||||||
* @returns {Promise<JitsiLocalTrack[]>}
|
* @returns {Promise<JitsiLocalTrack[]>}
|
||||||
*/
|
*/
|
||||||
export function createLocalTracksF(options = {}, store) {
|
export function createLocalTracksF(options: TrackOptions = {}, store?: IStore) {
|
||||||
let { cameraDeviceId, micDeviceId } = options;
|
let { cameraDeviceId, micDeviceId } = options;
|
||||||
const {
|
const {
|
||||||
desktopSharingSourceDevice,
|
desktopSharingSourceDevice,
|
||||||
|
@ -147,7 +152,9 @@ export function createLocalTracksF(options = {}, store) {
|
||||||
if (typeof APP !== 'undefined') {
|
if (typeof APP !== 'undefined') {
|
||||||
// TODO The app's settings should go in the redux store and then the
|
// TODO The app's settings should go in the redux store and then the
|
||||||
// reliance on the global variable APP will go away.
|
// reliance on the global variable APP will go away.
|
||||||
store || (store = APP.store); // eslint-disable-line no-param-reassign
|
if (!store) {
|
||||||
|
store = APP.store; // eslint-disable-line no-param-reassign
|
||||||
|
}
|
||||||
|
|
||||||
const state = store.getState();
|
const state = store.getState();
|
||||||
|
|
||||||
|
@ -159,6 +166,7 @@ export function createLocalTracksF(options = {}, store) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
const state = store.getState();
|
const state = store.getState();
|
||||||
const {
|
const {
|
||||||
desktopSharingFrameRate,
|
desktopSharingFrameRate,
|
||||||
|
@ -168,7 +176,7 @@ export function createLocalTracksF(options = {}, store) {
|
||||||
const constraints = options.constraints ?? state['features/base/config'].constraints;
|
const constraints = options.constraints ?? state['features/base/config'].constraints;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
loadEffects(store).then(effectsArray => {
|
loadEffects(store).then((effectsArray: Object[]) => {
|
||||||
// Filter any undefined values returned by Promise.resolve().
|
// Filter any undefined values returned by Promise.resolve().
|
||||||
const effects = effectsArray.filter(effect => Boolean(effect));
|
const effects = effectsArray.filter(effect => Boolean(effect));
|
||||||
|
|
||||||
|
@ -181,7 +189,7 @@ export function createLocalTracksF(options = {}, store) {
|
||||||
desktopSharingSources,
|
desktopSharingSources,
|
||||||
|
|
||||||
// Copy array to avoid mutations inside library.
|
// Copy array to avoid mutations inside library.
|
||||||
devices: options.devices.slice(0),
|
devices: options.devices?.slice(0),
|
||||||
effects,
|
effects,
|
||||||
firefox_fake_device, // eslint-disable-line camelcase
|
firefox_fake_device, // eslint-disable-line camelcase
|
||||||
firePermissionPromptIsShownEvent,
|
firePermissionPromptIsShownEvent,
|
||||||
|
@ -190,7 +198,7 @@ export function createLocalTracksF(options = {}, store) {
|
||||||
resolution,
|
resolution,
|
||||||
timeout
|
timeout
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch((err: Error) => {
|
||||||
logger.error('Failed to create local tracks', options.devices, err);
|
logger.error('Failed to create local tracks', options.devices, err);
|
||||||
|
|
||||||
return Promise.reject(err);
|
return Promise.reject(err);
|
||||||
|
@ -207,7 +215,7 @@ export function createLocalTracksF(options = {}, store) {
|
||||||
* @todo Refactor to not use APP.
|
* @todo Refactor to not use APP.
|
||||||
*/
|
*/
|
||||||
export function createPrejoinTracks() {
|
export function createPrejoinTracks() {
|
||||||
const errors = {};
|
const errors: any = {};
|
||||||
const initialDevices = [ 'audio' ];
|
const initialDevices = [ 'audio' ];
|
||||||
const requestedAudio = true;
|
const requestedAudio = true;
|
||||||
let requestedVideo = false;
|
let requestedVideo = false;
|
||||||
|
@ -235,8 +243,8 @@ export function createPrejoinTracks() {
|
||||||
tryCreateLocalTracks = createLocalTracksF({
|
tryCreateLocalTracks = createLocalTracksF({
|
||||||
devices: initialDevices,
|
devices: initialDevices,
|
||||||
firePermissionPromptIsShownEvent: true
|
firePermissionPromptIsShownEvent: true
|
||||||
})
|
}, APP.store)
|
||||||
.catch(err => {
|
.catch((err: Error) => {
|
||||||
if (requestedAudio && requestedVideo) {
|
if (requestedAudio && requestedVideo) {
|
||||||
|
|
||||||
// Try audio only...
|
// Try audio only...
|
||||||
|
@ -258,7 +266,7 @@ export function createPrejoinTracks() {
|
||||||
}
|
}
|
||||||
logger.error('Should never happen');
|
logger.error('Should never happen');
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch((err: Error) => {
|
||||||
// Log this just in case...
|
// Log this just in case...
|
||||||
if (!requestedAudio) {
|
if (!requestedAudio) {
|
||||||
logger.error('The impossible just happened', err);
|
logger.error('The impossible just happened', err);
|
||||||
|
@ -273,7 +281,7 @@ export function createPrejoinTracks() {
|
||||||
})
|
})
|
||||||
: [];
|
: [];
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch((err: Error) => {
|
||||||
// Log this just in case...
|
// Log this just in case...
|
||||||
if (!requestedVideo) {
|
if (!requestedVideo) {
|
||||||
logger.error('The impossible just happened', err);
|
logger.error('The impossible just happened', err);
|
||||||
|
@ -293,10 +301,10 @@ export function createPrejoinTracks() {
|
||||||
/**
|
/**
|
||||||
* Returns local audio track.
|
* Returns local audio track.
|
||||||
*
|
*
|
||||||
* @param {Track[]} tracks - List of all tracks.
|
* @param {ITrack[]} tracks - List of all tracks.
|
||||||
* @returns {(Track|undefined)}
|
* @returns {(Track|undefined)}
|
||||||
*/
|
*/
|
||||||
export function getLocalAudioTrack(tracks) {
|
export function getLocalAudioTrack(tracks: ITrack[]) {
|
||||||
return getLocalTrack(tracks, MEDIA_TYPE.AUDIO);
|
return getLocalTrack(tracks, MEDIA_TYPE.AUDIO);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -309,7 +317,7 @@ export function getLocalAudioTrack(tracks) {
|
||||||
* {@code jitsiTrack} property is {@code undefined}. By default a pending local track is not returned.
|
* {@code jitsiTrack} property is {@code undefined}. By default a pending local track is not returned.
|
||||||
* @returns {(Track|undefined)}
|
* @returns {(Track|undefined)}
|
||||||
*/
|
*/
|
||||||
export function getLocalDesktopTrack(tracks, includePending = false) {
|
export function getLocalDesktopTrack(tracks: ITrack[], includePending = false) {
|
||||||
return (
|
return (
|
||||||
getLocalTracks(tracks, includePending)
|
getLocalTracks(tracks, includePending)
|
||||||
.find(t => t.mediaType === MEDIA_TYPE.SCREENSHARE || t.videoType === VIDEO_TYPE.DESKTOP));
|
.find(t => t.mediaType === MEDIA_TYPE.SCREENSHARE || t.videoType === VIDEO_TYPE.DESKTOP));
|
||||||
|
@ -318,10 +326,10 @@ export function getLocalDesktopTrack(tracks, includePending = false) {
|
||||||
/**
|
/**
|
||||||
* Returns the stored local desktop jitsiLocalTrack.
|
* Returns the stored local desktop jitsiLocalTrack.
|
||||||
*
|
*
|
||||||
* @param {Object} state - The redux state.
|
* @param {IState} state - The redux state.
|
||||||
* @returns {JitsiLocalTrack|undefined}
|
* @returns {JitsiLocalTrack|undefined}
|
||||||
*/
|
*/
|
||||||
export function getLocalJitsiDesktopTrack(state) {
|
export function getLocalJitsiDesktopTrack(state: IState) {
|
||||||
const track = getLocalDesktopTrack(getTrackState(state));
|
const track = getLocalDesktopTrack(getTrackState(state));
|
||||||
|
|
||||||
return track?.jitsiTrack;
|
return track?.jitsiTrack;
|
||||||
|
@ -330,8 +338,8 @@ export function getLocalJitsiDesktopTrack(state) {
|
||||||
/**
|
/**
|
||||||
* Returns local track by media type.
|
* Returns local track by media type.
|
||||||
*
|
*
|
||||||
* @param {Track[]} tracks - List of all tracks.
|
* @param {ITrack[]} tracks - List of all tracks.
|
||||||
* @param {MEDIA_TYPE} mediaType - Media type.
|
* @param {MediaType} mediaType - Media type.
|
||||||
* @param {boolean} [includePending] - Indicates whether a local track is to be
|
* @param {boolean} [includePending] - Indicates whether a local track is to be
|
||||||
* returned if it is still pending. A local track is pending if
|
* returned if it is still pending. A local track is pending if
|
||||||
* {@code getUserMedia} is still executing to create it and, consequently, its
|
* {@code getUserMedia} is still executing to create it and, consequently, its
|
||||||
|
@ -339,7 +347,7 @@ export function getLocalJitsiDesktopTrack(state) {
|
||||||
* track is not returned.
|
* track is not returned.
|
||||||
* @returns {(Track|undefined)}
|
* @returns {(Track|undefined)}
|
||||||
*/
|
*/
|
||||||
export function getLocalTrack(tracks, mediaType, includePending = false) {
|
export function getLocalTrack(tracks: ITrack[], mediaType: MediaType, includePending = false) {
|
||||||
return (
|
return (
|
||||||
getLocalTracks(tracks, includePending)
|
getLocalTracks(tracks, includePending)
|
||||||
.find(t => t.mediaType === mediaType));
|
.find(t => t.mediaType === mediaType));
|
||||||
|
@ -349,7 +357,7 @@ export function getLocalTrack(tracks, mediaType, includePending = false) {
|
||||||
* Returns an array containing the local tracks with or without a (valid)
|
* Returns an array containing the local tracks with or without a (valid)
|
||||||
* {@code JitsiTrack}.
|
* {@code JitsiTrack}.
|
||||||
*
|
*
|
||||||
* @param {Track[]} tracks - An array containing all local tracks.
|
* @param {ITrack[]} tracks - An array containing all local tracks.
|
||||||
* @param {boolean} [includePending] - Indicates whether a local track is to be
|
* @param {boolean} [includePending] - Indicates whether a local track is to be
|
||||||
* returned if it is still pending. A local track is pending if
|
* returned if it is still pending. A local track is pending if
|
||||||
* {@code getUserMedia} is still executing to create it and, consequently, its
|
* {@code getUserMedia} is still executing to create it and, consequently, its
|
||||||
|
@ -357,7 +365,7 @@ export function getLocalTrack(tracks, mediaType, includePending = false) {
|
||||||
* track is not returned.
|
* track is not returned.
|
||||||
* @returns {Track[]}
|
* @returns {Track[]}
|
||||||
*/
|
*/
|
||||||
export function getLocalTracks(tracks, includePending = false) {
|
export function getLocalTracks(tracks: ITrack[], includePending = false) {
|
||||||
// XXX A local track is considered ready only once it has its `jitsiTrack`
|
// XXX A local track is considered ready only once it has its `jitsiTrack`
|
||||||
// property set by the `TRACK_ADDED` action. Until then there is a stub
|
// property set by the `TRACK_ADDED` action. Until then there is a stub
|
||||||
// added just before the `getUserMedia` call with a cancellable
|
// added just before the `getUserMedia` call with a cancellable
|
||||||
|
@ -371,20 +379,20 @@ export function getLocalTracks(tracks, includePending = false) {
|
||||||
/**
|
/**
|
||||||
* Returns local video track.
|
* Returns local video track.
|
||||||
*
|
*
|
||||||
* @param {Track[]} tracks - List of all tracks.
|
* @param {ITrack[]} tracks - List of all tracks.
|
||||||
* @returns {(Track|undefined)}
|
* @returns {(Track|undefined)}
|
||||||
*/
|
*/
|
||||||
export function getLocalVideoTrack(tracks) {
|
export function getLocalVideoTrack(tracks: ITrack[]) {
|
||||||
return getLocalTrack(tracks, MEDIA_TYPE.VIDEO);
|
return getLocalTrack(tracks, MEDIA_TYPE.VIDEO);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the media type of the local video, presenter or video.
|
* Returns the media type of the local video, presenter or video.
|
||||||
*
|
*
|
||||||
* @param {Track[]} tracks - List of all tracks.
|
* @param {ITrack[]} tracks - List of all tracks.
|
||||||
* @returns {MEDIA_TYPE}
|
* @returns {MEDIA_TYPE}
|
||||||
*/
|
*/
|
||||||
export function getLocalVideoType(tracks) {
|
export function getLocalVideoType(tracks: ITrack[]) {
|
||||||
const presenterTrack = getLocalTrack(tracks, MEDIA_TYPE.PRESENTER);
|
const presenterTrack = getLocalTrack(tracks, MEDIA_TYPE.PRESENTER);
|
||||||
|
|
||||||
return presenterTrack ? MEDIA_TYPE.PRESENTER : MEDIA_TYPE.VIDEO;
|
return presenterTrack ? MEDIA_TYPE.PRESENTER : MEDIA_TYPE.VIDEO;
|
||||||
|
@ -393,10 +401,10 @@ export function getLocalVideoType(tracks) {
|
||||||
/**
|
/**
|
||||||
* Returns the stored local video track.
|
* Returns the stored local video track.
|
||||||
*
|
*
|
||||||
* @param {Object} state - The redux state.
|
* @param {IState} state - The redux state.
|
||||||
* @returns {Object}
|
* @returns {Object}
|
||||||
*/
|
*/
|
||||||
export function getLocalJitsiVideoTrack(state) {
|
export function getLocalJitsiVideoTrack(state: IState) {
|
||||||
const track = getLocalVideoTrack(getTrackState(state));
|
const track = getLocalVideoTrack(getTrackState(state));
|
||||||
|
|
||||||
return track?.jitsiTrack;
|
return track?.jitsiTrack;
|
||||||
|
@ -405,10 +413,10 @@ export function getLocalJitsiVideoTrack(state) {
|
||||||
/**
|
/**
|
||||||
* Returns the stored local audio track.
|
* Returns the stored local audio track.
|
||||||
*
|
*
|
||||||
* @param {Object} state - The redux state.
|
* @param {IState} state - The redux state.
|
||||||
* @returns {Object}
|
* @returns {Object}
|
||||||
*/
|
*/
|
||||||
export function getLocalJitsiAudioTrack(state) {
|
export function getLocalJitsiAudioTrack(state: IState) {
|
||||||
const track = getLocalAudioTrack(getTrackState(state));
|
const track = getLocalAudioTrack(getTrackState(state));
|
||||||
|
|
||||||
return track?.jitsiTrack;
|
return track?.jitsiTrack;
|
||||||
|
@ -417,13 +425,13 @@ export function getLocalJitsiAudioTrack(state) {
|
||||||
/**
|
/**
|
||||||
* Returns track of specified media type for specified participant.
|
* Returns track of specified media type for specified participant.
|
||||||
*
|
*
|
||||||
* @param {Track[]} tracks - List of all tracks.
|
* @param {ITrack[]} tracks - List of all tracks.
|
||||||
* @param {Object} participant - Participant Object.
|
* @param {Participant} participant - Participant Object.
|
||||||
* @returns {(Track|undefined)}
|
* @returns {(Track|undefined)}
|
||||||
*/
|
*/
|
||||||
export function getVideoTrackByParticipant(
|
export function getVideoTrackByParticipant(
|
||||||
tracks,
|
tracks: ITrack[],
|
||||||
participant) {
|
participant?: Participant) {
|
||||||
|
|
||||||
if (!participant) {
|
if (!participant) {
|
||||||
return;
|
return;
|
||||||
|
@ -439,11 +447,11 @@ export function getVideoTrackByParticipant(
|
||||||
/**
|
/**
|
||||||
* Returns source name for specified participant id.
|
* Returns source name for specified participant id.
|
||||||
*
|
*
|
||||||
* @param {Object} state - The Redux state.
|
* @param {IState} state - The Redux state.
|
||||||
* @param {string} participantId - Participant ID.
|
* @param {string} participantId - Participant ID.
|
||||||
* @returns {string | undefined}
|
* @returns {string | undefined}
|
||||||
*/
|
*/
|
||||||
export function getSourceNameByParticipantId(state, participantId) {
|
export function getSourceNameByParticipantId(state: IState, participantId: string) {
|
||||||
const participant = getParticipantByIdOrUndefined(state, participantId);
|
const participant = getParticipantByIdOrUndefined(state, participantId);
|
||||||
const tracks = state['features/base/tracks'];
|
const tracks = state['features/base/tracks'];
|
||||||
const track = getVideoTrackByParticipant(tracks, participant);
|
const track = getVideoTrackByParticipant(tracks, participant);
|
||||||
|
@ -454,15 +462,15 @@ export function getSourceNameByParticipantId(state, participantId) {
|
||||||
/**
|
/**
|
||||||
* Returns track of specified media type for specified participant id.
|
* Returns track of specified media type for specified participant id.
|
||||||
*
|
*
|
||||||
* @param {Track[]} tracks - List of all tracks.
|
* @param {ITrack[]} tracks - List of all tracks.
|
||||||
* @param {MEDIA_TYPE} mediaType - Media type.
|
* @param {MediaType} mediaType - Media type.
|
||||||
* @param {string} participantId - Participant ID.
|
* @param {string} participantId - Participant ID.
|
||||||
* @returns {(Track|undefined)}
|
* @returns {(Track|undefined)}
|
||||||
*/
|
*/
|
||||||
export function getTrackByMediaTypeAndParticipant(
|
export function getTrackByMediaTypeAndParticipant(
|
||||||
tracks,
|
tracks: ITrack[],
|
||||||
mediaType,
|
mediaType: MediaType,
|
||||||
participantId) {
|
participantId: string) {
|
||||||
return tracks.find(
|
return tracks.find(
|
||||||
t => Boolean(t.jitsiTrack) && t.participantId === participantId && t.mediaType === mediaType
|
t => Boolean(t.jitsiTrack) && t.participantId === participantId && t.mediaType === mediaType
|
||||||
);
|
);
|
||||||
|
@ -471,11 +479,11 @@ export function getTrackByMediaTypeAndParticipant(
|
||||||
/**
|
/**
|
||||||
* Returns screenshare track of given virtualScreenshareParticipantId.
|
* Returns screenshare track of given virtualScreenshareParticipantId.
|
||||||
*
|
*
|
||||||
* @param {Track[]} tracks - List of all tracks.
|
* @param {ITrack[]} tracks - List of all tracks.
|
||||||
* @param {string} virtualScreenshareParticipantId - Virtual Screenshare Participant ID.
|
* @param {string} virtualScreenshareParticipantId - Virtual Screenshare Participant ID.
|
||||||
* @returns {(Track|undefined)}
|
* @returns {(Track|undefined)}
|
||||||
*/
|
*/
|
||||||
export function getVirtualScreenshareParticipantTrack(tracks, virtualScreenshareParticipantId) {
|
export function getVirtualScreenshareParticipantTrack(tracks: ITrack[], virtualScreenshareParticipantId: string) {
|
||||||
const ownderId = getVirtualScreenshareParticipantOwnerId(virtualScreenshareParticipantId);
|
const ownderId = getVirtualScreenshareParticipantOwnerId(virtualScreenshareParticipantId);
|
||||||
|
|
||||||
return getScreenShareTrack(tracks, ownderId);
|
return getScreenShareTrack(tracks, ownderId);
|
||||||
|
@ -484,16 +492,16 @@ export function getVirtualScreenshareParticipantTrack(tracks, virtualScreenshare
|
||||||
/**
|
/**
|
||||||
* Returns track source names of given screen share participant ids.
|
* Returns track source names of given screen share participant ids.
|
||||||
*
|
*
|
||||||
* @param {Object} state - The entire redux state.
|
* @param {IState} state - The entire redux state.
|
||||||
* @param {string[]} screenShareParticipantIds - Participant ID.
|
* @param {string[]} screenShareParticipantIds - Participant ID.
|
||||||
* @returns {(string[])}
|
* @returns {(string[])}
|
||||||
*/
|
*/
|
||||||
export function getRemoteScreenSharesSourceNames(state, screenShareParticipantIds = []) {
|
export function getRemoteScreenSharesSourceNames(state: IState, screenShareParticipantIds = []) {
|
||||||
const tracks = state['features/base/tracks'];
|
const tracks = state['features/base/tracks'];
|
||||||
|
|
||||||
return getMultipleVideoSupportFeatureFlag(state)
|
return getMultipleVideoSupportFeatureFlag(state)
|
||||||
? screenShareParticipantIds
|
? screenShareParticipantIds
|
||||||
: screenShareParticipantIds.reduce((acc, id) => {
|
: screenShareParticipantIds.reduce((acc: string[], id) => {
|
||||||
const sourceName = getScreenShareTrack(tracks, id)?.jitsiTrack.getSourceName();
|
const sourceName = getScreenShareTrack(tracks, id)?.jitsiTrack.getSourceName();
|
||||||
|
|
||||||
if (sourceName) {
|
if (sourceName) {
|
||||||
|
@ -511,7 +519,7 @@ export function getRemoteScreenSharesSourceNames(state, screenShareParticipantId
|
||||||
* @param {string} ownerId - Screenshare track owner ID.
|
* @param {string} ownerId - Screenshare track owner ID.
|
||||||
* @returns {(Track|undefined)}
|
* @returns {(Track|undefined)}
|
||||||
*/
|
*/
|
||||||
export function getScreenShareTrack(tracks, ownerId) {
|
export function getScreenShareTrack(tracks: ITrack[], ownerId: string) {
|
||||||
return tracks.find(
|
return tracks.find(
|
||||||
t => Boolean(t.jitsiTrack)
|
t => Boolean(t.jitsiTrack)
|
||||||
&& t.participantId === ownerId
|
&& t.participantId === ownerId
|
||||||
|
@ -522,15 +530,15 @@ export function getScreenShareTrack(tracks, ownerId) {
|
||||||
/**
|
/**
|
||||||
* Returns track source name of specified media type for specified participant id.
|
* Returns track source name of specified media type for specified participant id.
|
||||||
*
|
*
|
||||||
* @param {Track[]} tracks - List of all tracks.
|
* @param {ITrack[]} tracks - List of all tracks.
|
||||||
* @param {MEDIA_TYPE} mediaType - Media type.
|
* @param {MediaType} mediaType - Media type.
|
||||||
* @param {string} participantId - Participant ID.
|
* @param {string} participantId - Participant ID.
|
||||||
* @returns {(string|undefined)}
|
* @returns {(string|undefined)}
|
||||||
*/
|
*/
|
||||||
export function getTrackSourceNameByMediaTypeAndParticipant(
|
export function getTrackSourceNameByMediaTypeAndParticipant(
|
||||||
tracks,
|
tracks: ITrack[],
|
||||||
mediaType,
|
mediaType: MediaType,
|
||||||
participantId) {
|
participantId: string) {
|
||||||
const track = getTrackByMediaTypeAndParticipant(
|
const track = getTrackByMediaTypeAndParticipant(
|
||||||
tracks,
|
tracks,
|
||||||
mediaType,
|
mediaType,
|
||||||
|
@ -543,32 +551,32 @@ export function getTrackSourceNameByMediaTypeAndParticipant(
|
||||||
* Returns the track if any which corresponds to a specific instance
|
* Returns the track if any which corresponds to a specific instance
|
||||||
* of JitsiLocalTrack or JitsiRemoteTrack.
|
* of JitsiLocalTrack or JitsiRemoteTrack.
|
||||||
*
|
*
|
||||||
* @param {Track[]} tracks - List of all tracks.
|
* @param {ITrack[]} tracks - List of all tracks.
|
||||||
* @param {(JitsiLocalTrack|JitsiRemoteTrack)} jitsiTrack - JitsiTrack instance.
|
* @param {(JitsiLocalTrack|JitsiRemoteTrack)} jitsiTrack - JitsiTrack instance.
|
||||||
* @returns {(Track|undefined)}
|
* @returns {(Track|undefined)}
|
||||||
*/
|
*/
|
||||||
export function getTrackByJitsiTrack(tracks, jitsiTrack) {
|
export function getTrackByJitsiTrack(tracks: ITrack[], jitsiTrack: any) {
|
||||||
return tracks.find(t => t.jitsiTrack === jitsiTrack);
|
return tracks.find(t => t.jitsiTrack === jitsiTrack);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns tracks of specified media type.
|
* Returns tracks of specified media type.
|
||||||
*
|
*
|
||||||
* @param {Track[]} tracks - List of all tracks.
|
* @param {ITrack[]} tracks - List of all tracks.
|
||||||
* @param {MEDIA_TYPE} mediaType - Media type.
|
* @param {MediaType} mediaType - Media type.
|
||||||
* @returns {Track[]}
|
* @returns {Track[]}
|
||||||
*/
|
*/
|
||||||
export function getTracksByMediaType(tracks, mediaType) {
|
export function getTracksByMediaType(tracks: ITrack[], mediaType: MediaType) {
|
||||||
return tracks.filter(t => t.mediaType === mediaType);
|
return tracks.filter(t => t.mediaType === mediaType);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the local video camera track in the given set of tracks is muted.
|
* Checks if the local video camera track in the given set of tracks is muted.
|
||||||
*
|
*
|
||||||
* @param {Track[]} tracks - List of all tracks.
|
* @param {ITrack[]} tracks - List of all tracks.
|
||||||
* @returns {Track[]}
|
* @returns {ITrack[]}
|
||||||
*/
|
*/
|
||||||
export function isLocalCameraTrackMuted(tracks) {
|
export function isLocalCameraTrackMuted(tracks: ITrack[]) {
|
||||||
const presenterTrack = getLocalTrack(tracks, MEDIA_TYPE.PRESENTER);
|
const presenterTrack = getLocalTrack(tracks, MEDIA_TYPE.PRESENTER);
|
||||||
const videoTrack = getLocalTrack(tracks, MEDIA_TYPE.VIDEO);
|
const videoTrack = getLocalTrack(tracks, MEDIA_TYPE.VIDEO);
|
||||||
|
|
||||||
|
@ -588,13 +596,13 @@ export function isLocalCameraTrackMuted(tracks) {
|
||||||
/**
|
/**
|
||||||
* Checks if the first local track in the given tracks set is muted.
|
* Checks if the first local track in the given tracks set is muted.
|
||||||
*
|
*
|
||||||
* @param {Track[]} tracks - List of all tracks.
|
* @param {ITrack[]} tracks - List of all tracks.
|
||||||
* @param {MEDIA_TYPE} mediaType - The media type of tracks to be checked.
|
* @param {MediaType} mediaType - The media type of tracks to be checked.
|
||||||
* @returns {boolean} True if local track is muted or false if the track is
|
* @returns {boolean} True if local track is muted or false if the track is
|
||||||
* unmuted or if there are no local tracks of the given media type in the given
|
* unmuted or if there are no local tracks of the given media type in the given
|
||||||
* set of tracks.
|
* set of tracks.
|
||||||
*/
|
*/
|
||||||
export function isLocalTrackMuted(tracks, mediaType) {
|
export function isLocalTrackMuted(tracks: ITrack[], mediaType: MediaType) {
|
||||||
const track = getLocalTrack(tracks, mediaType);
|
const track = getLocalTrack(tracks, mediaType);
|
||||||
|
|
||||||
return !track || track.muted;
|
return !track || track.muted;
|
||||||
|
@ -603,10 +611,10 @@ export function isLocalTrackMuted(tracks, mediaType) {
|
||||||
/**
|
/**
|
||||||
* Checks if the local video track is of type DESKtOP.
|
* Checks if the local video track is of type DESKtOP.
|
||||||
*
|
*
|
||||||
* @param {Object} state - The redux state.
|
* @param {IState} state - The redux state.
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
export function isLocalVideoTrackDesktop(state) {
|
export function isLocalVideoTrackDesktop(state: IState) {
|
||||||
const videoTrack = getLocalVideoTrack(getTrackState(state));
|
const videoTrack = getLocalVideoTrack(getTrackState(state));
|
||||||
|
|
||||||
return videoTrack && videoTrack.videoType === VIDEO_TYPE.DESKTOP;
|
return videoTrack && videoTrack.videoType === VIDEO_TYPE.DESKTOP;
|
||||||
|
@ -617,12 +625,12 @@ export function isLocalVideoTrackDesktop(state) {
|
||||||
* Returns true if the remote track of the given media type and the given
|
* Returns true if the remote track of the given media type and the given
|
||||||
* participant is muted, false otherwise.
|
* participant is muted, false otherwise.
|
||||||
*
|
*
|
||||||
* @param {Track[]} tracks - List of all tracks.
|
* @param {ITrack[]} tracks - List of all tracks.
|
||||||
* @param {MEDIA_TYPE} mediaType - The media type of tracks to be checked.
|
* @param {MediaType} mediaType - The media type of tracks to be checked.
|
||||||
* @param {*} participantId - Participant ID.
|
* @param {string} participantId - Participant ID.
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
export function isRemoteTrackMuted(tracks, mediaType, participantId) {
|
export function isRemoteTrackMuted(tracks: ITrack[], mediaType: MediaType, participantId: string) {
|
||||||
const track = getTrackByMediaTypeAndParticipant(
|
const track = getTrackByMediaTypeAndParticipant(
|
||||||
tracks, mediaType, participantId);
|
tracks, mediaType, participantId);
|
||||||
|
|
||||||
|
@ -633,10 +641,10 @@ export function isRemoteTrackMuted(tracks, mediaType, participantId) {
|
||||||
* Returns whether or not the current environment needs a user interaction with
|
* Returns whether or not the current environment needs a user interaction with
|
||||||
* the page before any unmute can occur.
|
* the page before any unmute can occur.
|
||||||
*
|
*
|
||||||
* @param {Object} state - The redux state.
|
* @param {IState} state - The redux state.
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
export function isUserInteractionRequiredForUnmute(state) {
|
export function isUserInteractionRequiredForUnmute(state: IState) {
|
||||||
return browser.isUserInteractionRequiredForUnmute()
|
return browser.isUserInteractionRequiredForUnmute()
|
||||||
&& window
|
&& window
|
||||||
&& window.self !== window.top
|
&& window.self !== window.top
|
||||||
|
@ -652,7 +660,7 @@ export function isUserInteractionRequiredForUnmute(state) {
|
||||||
* @param {Object} state - The redux state.
|
* @param {Object} state - The redux state.
|
||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
export function setTrackMuted(track, muted, state) {
|
export function setTrackMuted(track: any, muted: boolean, state: IState) {
|
||||||
muted = Boolean(muted); // eslint-disable-line no-param-reassign
|
muted = Boolean(muted); // eslint-disable-line no-param-reassign
|
||||||
|
|
||||||
// Ignore the check for desktop track muted operation. When the screenshare is terminated by clicking on the
|
// Ignore the check for desktop track muted operation. When the screenshare is terminated by clicking on the
|
||||||
|
@ -665,7 +673,7 @@ export function setTrackMuted(track, muted, state) {
|
||||||
|
|
||||||
const f = muted ? 'mute' : 'unmute';
|
const f = muted ? 'mute' : 'unmute';
|
||||||
|
|
||||||
return track[f]().catch(error => {
|
return track[f]().catch((error: Error) => {
|
||||||
// Track might be already disposed so ignore such an error.
|
// Track might be already disposed so ignore such an error.
|
||||||
if (error.name !== JitsiTrackErrors.TRACK_IS_DISPOSED) {
|
if (error.name !== JitsiTrackErrors.TRACK_IS_DISPOSED) {
|
||||||
logger.error(`set track ${f} failed`, error);
|
logger.error(`set track ${f} failed`, error);
|
||||||
|
@ -681,9 +689,9 @@ export function setTrackMuted(track, muted, state) {
|
||||||
* @param {Function|Object} stateful - The redux store or {@code getState} function.
|
* @param {Function|Object} stateful - The redux store or {@code getState} function.
|
||||||
* @returns {boolean} - Whether toggle camera should be enabled.
|
* @returns {boolean} - Whether toggle camera should be enabled.
|
||||||
*/
|
*/
|
||||||
export function isToggleCameraEnabled(stateful) {
|
export function isToggleCameraEnabled(stateful: IStateful) {
|
||||||
const state = toState(stateful);
|
const state = toState(stateful);
|
||||||
const { videoInput } = state['features/base/devices'].availableDevices;
|
const { videoInput } = state['features/base/devices'].availableDevices;
|
||||||
|
|
||||||
return isMobileBrowser() && videoInput.length > 1;
|
return isMobileBrowser() && Number(videoInput?.length) > 1;
|
||||||
}
|
}
|
|
@ -1,28 +1,30 @@
|
||||||
// @flow
|
|
||||||
|
|
||||||
import { batch } from 'react-redux';
|
import { batch } from 'react-redux';
|
||||||
|
|
||||||
|
import { IStore } from '../../app/types';
|
||||||
import { _RESET_BREAKOUT_ROOMS } from '../../breakout-rooms/actionTypes';
|
import { _RESET_BREAKOUT_ROOMS } from '../../breakout-rooms/actionTypes';
|
||||||
import { hideNotification } from '../../notifications';
|
import { hideNotification } from '../../notifications/actions';
|
||||||
import { isPrejoinPageVisible } from '../../prejoin/functions';
|
import { isPrejoinPageVisible } from '../../prejoin/functions';
|
||||||
import { getCurrentConference } from '../conference/functions';
|
import { getCurrentConference } from '../conference/functions';
|
||||||
import { getMultipleVideoSendingSupportFeatureFlag } from '../config';
|
import { getMultipleVideoSendingSupportFeatureFlag } from '../config/functions.any';
|
||||||
import { getAvailableDevices } from '../devices/actions';
|
import { getAvailableDevices } from '../devices/actions';
|
||||||
import {
|
import {
|
||||||
CAMERA_FACING_MODE,
|
|
||||||
MEDIA_TYPE,
|
|
||||||
SET_AUDIO_MUTED,
|
SET_AUDIO_MUTED,
|
||||||
SET_CAMERA_FACING_MODE,
|
SET_CAMERA_FACING_MODE,
|
||||||
SET_VIDEO_MUTED,
|
SET_VIDEO_MUTED,
|
||||||
VIDEO_MUTISM_AUTHORITY,
|
|
||||||
TOGGLE_CAMERA_FACING_MODE,
|
TOGGLE_CAMERA_FACING_MODE,
|
||||||
toggleCameraFacingMode,
|
SET_SCREENSHARE_MUTED
|
||||||
SET_SCREENSHARE_MUTED,
|
} from '../media/actionTypes';
|
||||||
|
import { toggleCameraFacingMode, setScreenshareMuted } from '../media/actions';
|
||||||
|
import {
|
||||||
|
CAMERA_FACING_MODE,
|
||||||
|
MEDIA_TYPE,
|
||||||
|
VIDEO_MUTISM_AUTHORITY,
|
||||||
VIDEO_TYPE,
|
VIDEO_TYPE,
|
||||||
setScreenshareMuted,
|
SCREENSHARE_MUTISM_AUTHORITY,
|
||||||
SCREENSHARE_MUTISM_AUTHORITY
|
MediaType
|
||||||
} from '../media';
|
} from '../media/constants';
|
||||||
import { MiddlewareRegistry, StateListenerRegistry } from '../redux';
|
import MiddlewareRegistry from '../redux/MiddlewareRegistry';
|
||||||
|
import StateListenerRegistry from '../redux/StateListenerRegistry';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
TRACK_ADDED,
|
TRACK_ADDED,
|
||||||
|
@ -47,11 +49,10 @@ import {
|
||||||
isUserInteractionRequiredForUnmute,
|
isUserInteractionRequiredForUnmute,
|
||||||
setTrackMuted
|
setTrackMuted
|
||||||
} from './functions';
|
} from './functions';
|
||||||
|
import { ITrack } from './reducer';
|
||||||
|
|
||||||
import './subscriber';
|
import './subscriber';
|
||||||
|
|
||||||
declare var APP: Object;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Middleware that captures LIB_DID_DISPOSE and LIB_DID_INIT actions and,
|
* Middleware that captures LIB_DID_DISPOSE and LIB_DID_INIT actions and,
|
||||||
* respectively, creates/destroys local media tracks. Also listens to
|
* respectively, creates/destroys local media tracks. Also listens to
|
||||||
|
@ -267,7 +268,7 @@ StateListenerRegistry.register(
|
||||||
* @private
|
* @private
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
function _handleNoDataFromSourceErrors(store, action) {
|
function _handleNoDataFromSourceErrors(store: IStore, action: any) {
|
||||||
const { getState, dispatch } = store;
|
const { getState, dispatch } = store;
|
||||||
|
|
||||||
const track = getTrackByJitsiTrack(getState()['features/base/tracks'], action.track.jitsiTrack);
|
const track = getTrackByJitsiTrack(getState()['features/base/tracks'], action.track.jitsiTrack);
|
||||||
|
@ -323,9 +324,9 @@ function _handleNoDataFromSourceErrors(store, action) {
|
||||||
* {@code mediaType} in the specified {@code store}.
|
* {@code mediaType} in the specified {@code store}.
|
||||||
*/
|
*/
|
||||||
function _getLocalTrack(
|
function _getLocalTrack(
|
||||||
{ getState }: { getState: Function },
|
{ getState }: { getState: Function; },
|
||||||
mediaType: MEDIA_TYPE,
|
mediaType: MediaType,
|
||||||
includePending: boolean = false) {
|
includePending = false) {
|
||||||
return (
|
return (
|
||||||
getLocalTrack(
|
getLocalTrack(
|
||||||
getState()['features/base/tracks'],
|
getState()['features/base/tracks'],
|
||||||
|
@ -340,11 +341,11 @@ function _getLocalTrack(
|
||||||
* @param {Track} track - The redux action dispatched in the specified store.
|
* @param {Track} track - The redux action dispatched in the specified store.
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
function _removeNoDataFromSourceNotification({ getState, dispatch }, track) {
|
function _removeNoDataFromSourceNotification({ getState, dispatch }: IStore, track: ITrack) {
|
||||||
const t = getTrackByJitsiTrack(getState()['features/base/tracks'], track.jitsiTrack);
|
const t = getTrackByJitsiTrack(getState()['features/base/tracks'], track.jitsiTrack);
|
||||||
const { jitsiTrack, noDataFromSourceNotificationInfo = {} } = t || {};
|
const { jitsiTrack, noDataFromSourceNotificationInfo = {} } = t || {};
|
||||||
|
|
||||||
if (noDataFromSourceNotificationInfo && noDataFromSourceNotificationInfo.uid) {
|
if (noDataFromSourceNotificationInfo?.uid) {
|
||||||
dispatch(hideNotification(noDataFromSourceNotificationInfo.uid));
|
dispatch(hideNotification(noDataFromSourceNotificationInfo.uid));
|
||||||
dispatch(trackNoDataFromSourceNotificationInfoChanged(jitsiTrack, undefined));
|
dispatch(trackNoDataFromSourceNotificationInfoChanged(jitsiTrack, undefined));
|
||||||
}
|
}
|
||||||
|
@ -361,7 +362,8 @@ function _removeNoDataFromSourceNotification({ getState, dispatch }, track) {
|
||||||
* @private
|
* @private
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
async function _setMuted(store, { ensureTrack, authority, muted }, mediaType: MEDIA_TYPE) {
|
async function _setMuted(store: IStore, { ensureTrack, authority, muted }: {
|
||||||
|
authority: number; ensureTrack: boolean; muted: boolean; }, mediaType: MediaType) {
|
||||||
const { dispatch, getState } = store;
|
const { dispatch, getState } = store;
|
||||||
const localTrack = _getLocalTrack(store, mediaType, /* includePending */ true);
|
const localTrack = _getLocalTrack(store, mediaType, /* includePending */ true);
|
||||||
const state = getState();
|
const state = getState();
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { MediaType } from '../media/constants';
|
||||||
import { PARTICIPANT_ID_CHANGED } from '../participants/actionTypes';
|
import { PARTICIPANT_ID_CHANGED } from '../participants/actionTypes';
|
||||||
import ReducerRegistry from '../redux/ReducerRegistry';
|
import ReducerRegistry from '../redux/ReducerRegistry';
|
||||||
import { set } from '../redux/functions';
|
import { set } from '../redux/functions';
|
||||||
|
@ -14,14 +15,18 @@ import {
|
||||||
TRACK_WILL_CREATE
|
TRACK_WILL_CREATE
|
||||||
} from './actionTypes';
|
} from './actionTypes';
|
||||||
|
|
||||||
interface ITrack {
|
export interface ITrack {
|
||||||
isReceivingData: boolean;
|
isReceivingData: boolean;
|
||||||
jitsiTrack: any;
|
jitsiTrack: any;
|
||||||
lastMediaEvent?: string;
|
lastMediaEvent?: string;
|
||||||
local: boolean;
|
local: boolean;
|
||||||
mediaType: string;
|
mediaType: MediaType;
|
||||||
mirror: boolean;
|
mirror: boolean;
|
||||||
muted: boolean;
|
muted: boolean;
|
||||||
|
noDataFromSourceNotificationInfo?: {
|
||||||
|
timeout?: number;
|
||||||
|
uid?: string;
|
||||||
|
};
|
||||||
participantId: string;
|
participantId: string;
|
||||||
streamingStatus?: string;
|
streamingStatus?: string;
|
||||||
videoStarted: boolean;
|
videoStarted: boolean;
|
||||||
|
|
|
@ -1,13 +1,9 @@
|
||||||
// @flow
|
|
||||||
|
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
import { StateListenerRegistry } from '../../base/redux';
|
import StateListenerRegistry from '../redux/StateListenerRegistry';
|
||||||
|
|
||||||
import { isLocalCameraTrackMuted } from './functions';
|
import { isLocalCameraTrackMuted } from './functions';
|
||||||
|
|
||||||
declare var APP: Object;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notifies when the list of currently sharing participants changes.
|
* Notifies when the list of currently sharing participants changes.
|
||||||
*/
|
*/
|
|
@ -0,0 +1,20 @@
|
||||||
|
export interface TrackOptions {
|
||||||
|
cameraDeviceId?: string | null;
|
||||||
|
constraints?: {
|
||||||
|
video?: {
|
||||||
|
height?: {
|
||||||
|
ideal?: number;
|
||||||
|
max?: number;
|
||||||
|
min?: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
desktopSharingSourceDevice?: string;
|
||||||
|
desktopSharingSources?: string[];
|
||||||
|
devices?: string[];
|
||||||
|
facingMode?: string;
|
||||||
|
firePermissionPromptIsShownEvent?: boolean;
|
||||||
|
fireSlowPromiseEvent?: boolean;
|
||||||
|
micDeviceId?: string | null;
|
||||||
|
timeout?: number;
|
||||||
|
}
|
|
@ -64,7 +64,7 @@ const Prejoin: React.FC<PrejoinProps> = ({ navigation }: PrejoinProps) => {
|
||||||
(state: IState) => state['features/base/responsive-ui']?.aspectRatio
|
(state: IState) => state['features/base/responsive-ui']?.aspectRatio
|
||||||
);
|
);
|
||||||
const localParticipant = useSelector((state: IState) => getLocalParticipant(state));
|
const localParticipant = useSelector((state: IState) => getLocalParticipant(state));
|
||||||
const isDisplayNameMandatory = useSelector(state => isDisplayNameRequired(state));
|
const isDisplayNameMandatory = useSelector((state: IState) => isDisplayNameRequired(state));
|
||||||
const roomName = useSelector((state: IState) => getConferenceName(state));
|
const roomName = useSelector((state: IState) => getConferenceName(state));
|
||||||
const participantName = localParticipant?.name;
|
const participantName = localParticipant?.name;
|
||||||
const [ displayName, setDisplayName ]
|
const [ displayName, setDisplayName ]
|
||||||
|
|
|
@ -1,26 +1,25 @@
|
||||||
// @flow
|
import { IState } from '../app/types';
|
||||||
|
import { getRoomName } from '../base/conference/functions';
|
||||||
import { getRoomName } from '../base/conference';
|
import { getDialOutStatusUrl, getDialOutUrl } from '../base/config/functions.web';
|
||||||
import { getDialOutStatusUrl, getDialOutUrl } from '../base/config/functions';
|
import { isAudioMuted, isVideoMutedByUser } from '../base/media/functions';
|
||||||
import { isAudioMuted, isVideoMutedByUser } from '../base/media';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Selector for the visibility of the 'join by phone' button.
|
* Selector for the visibility of the 'join by phone' button.
|
||||||
*
|
*
|
||||||
* @param {Object} state - The state of the app.
|
* @param {IState} state - The state of the app.
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
export function isJoinByPhoneButtonVisible(state: Object): boolean {
|
export function isJoinByPhoneButtonVisible(state: IState): boolean {
|
||||||
return Boolean(getDialOutUrl(state) && getDialOutStatusUrl(state));
|
return Boolean(getDialOutUrl(state) && getDialOutStatusUrl(state));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Selector for determining if the device status strip is visible or not.
|
* Selector for determining if the device status strip is visible or not.
|
||||||
*
|
*
|
||||||
* @param {Object} state - The state of the app.
|
* @param {IState} state - The state of the app.
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
export function isDeviceStatusVisible(state: Object): boolean {
|
export function isDeviceStatusVisible(state: IState): boolean {
|
||||||
return !(isAudioMuted(state) && isVideoMutedByUser(state))
|
return !(isAudioMuted(state) && isVideoMutedByUser(state))
|
||||||
&& !state['features/base/config'].startSilent;
|
&& !state['features/base/config'].startSilent;
|
||||||
}
|
}
|
||||||
|
@ -28,91 +27,91 @@ export function isDeviceStatusVisible(state: Object): boolean {
|
||||||
/**
|
/**
|
||||||
* Selector for determining if the display name is mandatory.
|
* Selector for determining if the display name is mandatory.
|
||||||
*
|
*
|
||||||
* @param {Object} state - The state of the app.
|
* @param {IState} state - The state of the app.
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
export function isDisplayNameRequired(state: Object): boolean {
|
export function isDisplayNameRequired(state: IState): boolean {
|
||||||
return state['features/prejoin']?.isDisplayNameRequired
|
return Boolean(state['features/prejoin']?.isDisplayNameRequired
|
||||||
|| state['features/base/config']?.requireDisplayName;
|
|| state['features/base/config']?.requireDisplayName);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Selector for determining if the prejoin display name field is visible.
|
* Selector for determining if the prejoin display name field is visible.
|
||||||
*
|
*
|
||||||
* @param {Object} state - The state of the app.
|
* @param {IState} state - The state of the app.
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
export function isPrejoinDisplayNameVisible(state: Object): boolean {
|
export function isPrejoinDisplayNameVisible(state: IState): boolean {
|
||||||
return !state['features/base/config'].prejoinConfig?.hideDisplayName;
|
return !state['features/base/config'].prejoinConfig?.hideDisplayName;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the text for the prejoin status bar.
|
* Returns the text for the prejoin status bar.
|
||||||
*
|
*
|
||||||
* @param {Object} state - The state of the app.
|
* @param {IState} state - The state of the app.
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
export function getDeviceStatusText(state: Object): string {
|
export function getDeviceStatusText(state: IState): string {
|
||||||
return state['features/prejoin']?.deviceStatusText;
|
return state['features/prejoin']?.deviceStatusText;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the type of the prejoin status bar: 'ok'|'warning'.
|
* Returns the type of the prejoin status bar: 'ok'|'warning'.
|
||||||
*
|
*
|
||||||
* @param {Object} state - The state of the app.
|
* @param {IState} state - The state of the app.
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
export function getDeviceStatusType(state: Object): string {
|
export function getDeviceStatusType(state: IState): string {
|
||||||
return state['features/prejoin']?.deviceStatusType;
|
return state['features/prejoin']?.deviceStatusType;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the 'conferenceUrl' used for dialing out.
|
* Returns the 'conferenceUrl' used for dialing out.
|
||||||
*
|
*
|
||||||
* @param {Object} state - The state of the app.
|
* @param {IState} state - The state of the app.
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
export function getDialOutConferenceUrl(state: Object): string {
|
export function getDialOutConferenceUrl(state: IState): string {
|
||||||
return `${getRoomName(state)}@${state['features/base/config'].hosts.muc}`;
|
return `${getRoomName(state)}@${state['features/base/config'].hosts?.muc}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Selector for getting the dial out country.
|
* Selector for getting the dial out country.
|
||||||
*
|
*
|
||||||
* @param {Object} state - The state of the app.
|
* @param {IState} state - The state of the app.
|
||||||
* @returns {Object}
|
* @returns {Object}
|
||||||
*/
|
*/
|
||||||
export function getDialOutCountry(state: Object): Object {
|
export function getDialOutCountry(state: IState) {
|
||||||
return state['features/prejoin'].dialOutCountry;
|
return state['features/prejoin'].dialOutCountry;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Selector for getting the dial out number (without prefix).
|
* Selector for getting the dial out number (without prefix).
|
||||||
*
|
*
|
||||||
* @param {Object} state - The state of the app.
|
* @param {IState} state - The state of the app.
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
export function getDialOutNumber(state: Object): string {
|
export function getDialOutNumber(state: IState): string {
|
||||||
return state['features/prejoin'].dialOutNumber;
|
return state['features/prejoin'].dialOutNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Selector for getting the dial out status while calling.
|
* Selector for getting the dial out status while calling.
|
||||||
*
|
*
|
||||||
* @param {Object} state - The state of the app.
|
* @param {IState} state - The state of the app.
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
export function getDialOutStatus(state: Object): string {
|
export function getDialOutStatus(state: IState): string {
|
||||||
return state['features/prejoin'].dialOutStatus;
|
return state['features/prejoin'].dialOutStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the full dial out number (containing country code and +).
|
* Returns the full dial out number (containing country code and +).
|
||||||
*
|
*
|
||||||
* @param {Object} state - The state of the app.
|
* @param {IState} state - The state of the app.
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
export function getFullDialOutNumber(state: Object): string {
|
export function getFullDialOutNumber(state: IState): string {
|
||||||
const dialOutNumber = getDialOutNumber(state);
|
const dialOutNumber = getDialOutNumber(state);
|
||||||
const country = getDialOutCountry(state);
|
const country = getDialOutCountry(state);
|
||||||
|
|
||||||
|
@ -122,20 +121,20 @@ export function getFullDialOutNumber(state: Object): string {
|
||||||
/**
|
/**
|
||||||
* Selector for getting the error if any while creating streams.
|
* Selector for getting the error if any while creating streams.
|
||||||
*
|
*
|
||||||
* @param {Object} state - The state of the app.
|
* @param {IState} state - The state of the app.
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
export function getRawError(state: Object): string {
|
export function getRawError(state: IState): string {
|
||||||
return state['features/prejoin']?.rawError;
|
return state['features/prejoin']?.rawError;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Selector for getting the visibility state for the 'JoinByPhoneDialog'.
|
* Selector for getting the visibility state for the 'JoinByPhoneDialog'.
|
||||||
*
|
*
|
||||||
* @param {Object} state - The state of the app.
|
* @param {IState} state - The state of the app.
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
export function isJoinByPhoneDialogVisible(state: Object): boolean {
|
export function isJoinByPhoneDialogVisible(state: IState): boolean {
|
||||||
return state['features/prejoin']?.showJoinByPhoneDialog;
|
return state['features/prejoin']?.showJoinByPhoneDialog;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -143,28 +142,28 @@ export function isJoinByPhoneDialogVisible(state: Object): boolean {
|
||||||
* Returns true if the prejoin page is enabled and no flag
|
* Returns true if the prejoin page is enabled and no flag
|
||||||
* to bypass showing the page is present.
|
* to bypass showing the page is present.
|
||||||
*
|
*
|
||||||
* @param {Object} state - The state of the app.
|
* @param {IState} state - The state of the app.
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
export function isPrejoinPageVisible(state: Object): boolean {
|
export function isPrejoinPageVisible(state: IState): boolean {
|
||||||
return navigator.product !== 'ReactNative'
|
return Boolean(navigator.product !== 'ReactNative'
|
||||||
&& state['features/base/config'].prejoinConfig?.enabled
|
&& state['features/base/config'].prejoinConfig?.enabled
|
||||||
&& state['features/prejoin']?.showPrejoin
|
&& state['features/prejoin']?.showPrejoin
|
||||||
&& !(state['features/base/config'].enableForcedReload && state['features/prejoin'].skipPrejoinOnReload);
|
&& !(state['features/base/config'].enableForcedReload && state['features/prejoin'].skipPrejoinOnReload));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if we should auto-knock in case lobby is enabled for the room.
|
* Returns true if we should auto-knock in case lobby is enabled for the room.
|
||||||
*
|
*
|
||||||
* @param {Object} state - The state of the app.
|
* @param {IState} state - The state of the app.
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
export function shouldAutoKnock(state: Object): boolean {
|
export function shouldAutoKnock(state: IState): boolean {
|
||||||
const { iAmRecorder, iAmSipGateway, autoKnockLobby, prejoinConfig } = state['features/base/config'];
|
const { iAmRecorder, iAmSipGateway, autoKnockLobby, prejoinConfig } = state['features/base/config'];
|
||||||
const { userSelectedSkipPrejoin } = state['features/base/settings'];
|
const { userSelectedSkipPrejoin } = state['features/base/settings'];
|
||||||
const isPrejoinEnabled = prejoinConfig?.enabled;
|
const isPrejoinEnabled = prejoinConfig?.enabled;
|
||||||
|
|
||||||
return ((isPrejoinEnabled && !userSelectedSkipPrejoin)
|
return Boolean(((isPrejoinEnabled && !userSelectedSkipPrejoin)
|
||||||
|| autoKnockLobby || (iAmRecorder && iAmSipGateway))
|
|| autoKnockLobby || (iAmRecorder && iAmSipGateway))
|
||||||
&& !state['features/lobby'].knocking;
|
&& !state['features/lobby'].knocking);
|
||||||
}
|
}
|
Loading…
Reference in New Issue