-
-
+
+ { messageKey
+ ?
+ { this.props.t(messageKey) }
- :
- { this.props.t(key) }
-
}
- { !_isRecording
- && showSpinner
- &&
}
+ :
+ { this.props.t(circularLabelKey) }
+ }
);
}
+
+ /**
+ * Clears the timeout for automatically hiding {@link RecordingLabel}.
+ *
+ * @private
+ * @returns {void}
+ */
+ _clearAutoHideTimeout() {
+ clearTimeout(this._autohideTimeout);
+ }
+
+ /**
+ * Sets a timeout to automatically hide {@link RecordingLabel}.
+ *
+ * @private
+ * @returns {void}
+ */
+ _setHideTimeout() {
+ this._autohideTimeout = setTimeout(() => {
+ this.setState({ hidden: true });
+ }, 5000);
+ }
}
-/**
- * Maps (parts of) the Redux state to the associated {@code RecordingLabel}
- * component's props.
- *
- * @param {Object} state - The Redux state.
- * @private
- * @returns {{
- * _filmstripVisible: boolean,
- * _isRecording: boolean,
- * _labelDisplayConfiguration: Object,
- * _recordingType: string
- * }}
- */
-function _mapStateToProps(state) {
- const { visible } = state['features/filmstrip'];
- const {
- labelDisplayConfiguration,
- recordingState,
- recordingType
- } = state['features/recording'];
-
- return {
- _filmstripVisible: visible,
- _isRecording: recordingState === JitsiRecordingStatus.ON,
- _labelDisplayConfiguration: labelDisplayConfiguration,
- _recordingType: recordingType
- };
-}
-
-export default translate(connect(_mapStateToProps)(RecordingLabel));
+export default translate(RecordingLabel);
diff --git a/react/features/recording/components/index.js b/react/features/recording/components/index.js
index 70a2733bd..5320d60ea 100644
--- a/react/features/recording/components/index.js
+++ b/react/features/recording/components/index.js
@@ -1,2 +1,3 @@
export { StartLiveStreamDialog, StopLiveStreamDialog } from './LiveStream';
+export { StartRecordingDialog, StopRecordingDialog } from './Recording';
export { default as RecordingLabel } from './RecordingLabel';
diff --git a/react/features/recording/functions.js b/react/features/recording/functions.js
new file mode 100644
index 000000000..ad215a4a9
--- /dev/null
+++ b/react/features/recording/functions.js
@@ -0,0 +1,18 @@
+import { JitsiRecordingConstants } from '../base/lib-jitsi-meet';
+
+/**
+ * Searches in the passed in redux state for an active recording session of the
+ * passed in mode.
+ *
+ * @param {Object} state - The redux state to search in.
+ * @param {string} mode - Find an active recording session of the given mode.
+ * @returns {Object|undefined}
+ */
+export function getActiveSession(state, mode) {
+ const { sessionDatas } = state['features/recording'];
+ const { status: statusConstants } = JitsiRecordingConstants;
+
+ return sessionDatas.find(sessionData => sessionData.mode === mode
+ && (sessionData.status === statusConstants.ON
+ || sessionData.status === statusConstants.PENDING));
+}
diff --git a/react/features/recording/index.js b/react/features/recording/index.js
index c2bba6b0a..755b50deb 100644
--- a/react/features/recording/index.js
+++ b/react/features/recording/index.js
@@ -1,6 +1,6 @@
export * from './actions';
export * from './components';
export * from './constants';
+export * from './functions';
-import './middleware';
import './reducer';
diff --git a/react/features/recording/middleware.js b/react/features/recording/middleware.js
deleted file mode 100644
index 15041ed81..000000000
--- a/react/features/recording/middleware.js
+++ /dev/null
@@ -1,27 +0,0 @@
-// @flow
-
-import { MiddlewareRegistry } from '../base/redux';
-import UIEvents from '../../../service/UI/UIEvents';
-
-import { TOGGLE_RECORDING } from './actionTypes';
-
-declare var APP: Object;
-
-/**
- * Implements the middleware of the feature recording.
- *
- * @param {Store} store - The redux store.
- * @returns {Function}
- */
-// eslint-disable-next-line no-unused-vars
-MiddlewareRegistry.register(store => next => action => {
- switch (action.type) {
- case TOGGLE_RECORDING:
- if (typeof APP === 'object') {
- APP.UI.emitEvent(UIEvents.TOGGLE_RECORDING);
- }
- break;
- }
-
- return next(action);
-});
diff --git a/react/features/recording/reducer.js b/react/features/recording/reducer.js
index 188f866e5..047bcc348 100644
--- a/react/features/recording/reducer.js
+++ b/react/features/recording/reducer.js
@@ -1,34 +1,60 @@
import { ReducerRegistry } from '../base/redux';
-import {
- HIDE_RECORDING_LABEL,
- RECORDING_STATE_UPDATED,
- SET_RECORDING_TYPE
-} from './actionTypes';
+import { RECORDING_SESSION_UPDATED } from './actionTypes';
+
+const DEFAULT_STATE = {
+ sessionDatas: []
+};
/**
* Reduces the Redux actions of the feature features/recording.
*/
-ReducerRegistry.register('features/recording', (state = {}, action) => {
- switch (action.type) {
- case HIDE_RECORDING_LABEL:
- return {
- ...state,
- labelDisplayConfiguration: null
- };
+ReducerRegistry.register('features/recording',
+ (state = DEFAULT_STATE, action) => {
+ switch (action.type) {
+ case RECORDING_SESSION_UPDATED:
+ return {
+ ...state,
+ sessionDatas:
+ _updateSessionDatas(state.sessionDatas, action.sessionData)
+ };
- case RECORDING_STATE_UPDATED:
- return {
- ...state,
- ...action.recordingState
- };
+ default:
+ return state;
+ }
+ });
- case SET_RECORDING_TYPE:
- return {
- ...state,
- recordingType: action.recordingType
- };
+/**
+ * Updates the known information on recording sessions.
+ *
+ * @param {Array} sessionDatas - The current sessions in the redux store.
+ * @param {Object} newSessionData - The updated session data.
+ * @private
+ * @returns {Array} The session datas with the updated session data added.
+ */
+function _updateSessionDatas(sessionDatas, newSessionData) {
+ const hasExistingSessionData = sessionDatas.find(
+ sessionData => sessionData.id === newSessionData.id);
+ let newSessionDatas;
- default:
- return state;
+ if (hasExistingSessionData) {
+ newSessionDatas = sessionDatas.map(sessionData => {
+ if (sessionData.id === newSessionData.id) {
+ return {
+ ...newSessionData
+ };
+ }
+
+ // Nothing to update for this session data so pass it back in.
+ return sessionData;
+ });
+ } else {
+ // If the session data is not present, then there is nothing to update
+ // and instead it needs to be added to the known session datas.
+ newSessionDatas = [
+ ...sessionDatas,
+ { ...newSessionData }
+ ];
}
-});
+
+ return newSessionDatas;
+}
diff --git a/react/features/toolbox/components/web/Toolbox.js b/react/features/toolbox/components/web/Toolbox.js
index fb3b9bfcb..cd1018b6d 100644
--- a/react/features/toolbox/components/web/Toolbox.js
+++ b/react/features/toolbox/components/web/Toolbox.js
@@ -11,6 +11,7 @@ import {
} from '../../../analytics';
import { openDialog } from '../../../base/dialog';
import { translate } from '../../../base/i18n';
+import { JitsiRecordingConstants } from '../../../base/lib-jitsi-meet';
import {
PARTICIPANT_ROLE,
getLocalParticipant,
@@ -27,7 +28,13 @@ import {
isDialOutEnabled
} from '../../../invite';
import { openKeyboardShortcutsDialog } from '../../../keyboard-shortcuts';
-import { RECORDING_TYPES, toggleRecording } from '../../../recording';
+import {
+ StartLiveStreamDialog,
+ StartRecordingDialog,
+ StopLiveStreamDialog,
+ StopRecordingDialog,
+ getActiveSession
+} from '../../../recording';
import { SettingsButton } from '../../../settings';
import { toggleSharedVideo } from '../../../shared-video';
import { toggleChat, toggleProfile } from '../../../side-panel';
@@ -95,6 +102,11 @@ type Props = {
*/
_feedbackConfigured: boolean,
+ /**
+ * The current file recording session, if any.
+ */
+ _fileRecordingSession: Object,
+
/**
* Whether or not the app is currently in full screen.
*/
@@ -112,10 +124,9 @@ type Props = {
_isGuest: boolean,
/**
- * Whether or not the conference is currently being recorded by the local
- * participant.
+ * The current live streaming session, if any.
*/
- _isRecording: boolean,
+ _liveStreamingSession: ?Object,
/**
* The ID of the local participant.
@@ -137,12 +148,6 @@ type Props = {
*/
_recordingEnabled: boolean,
- /**
- * Whether the recording feature is live streaming (jibri) or is file
- * recording (jirecon).
- */
- _recordingType: String,
-
/**
* Whether or not the local participant is screensharing.
*/
@@ -214,12 +219,13 @@ class Toolbox extends Component
{
= this._onToolbarOpenSpeakerStats.bind(this);
this._onToolbarOpenVideoQuality
= this._onToolbarOpenVideoQuality.bind(this);
-
this._onToolbarToggleChat = this._onToolbarToggleChat.bind(this);
this._onToolbarToggleEtherpad
= this._onToolbarToggleEtherpad.bind(this);
this._onToolbarToggleFullScreen
= this._onToolbarToggleFullScreen.bind(this);
+ this._onToolbarToggleLiveStreaming
+ = this._onToolbarToggleLiveStreaming.bind(this);
this._onToolbarToggleProfile
= this._onToolbarToggleProfile.bind(this);
this._onToolbarToggleRaiseHand
@@ -462,6 +468,22 @@ class Toolbox extends Component {
this.props.dispatch(setFullScreen(fullScreen));
}
+ /**
+ * Dispatches an action to show a dialog for starting or stopping a live
+ * streaming session.
+ *
+ * @private
+ * @returns {void}
+ */
+ _doToggleLiveStreaming() {
+ const { _liveStreamingSession } = this.props;
+ const dialogToDisplay = _liveStreamingSession
+ ? StopLiveStreamDialog : StartLiveStreamDialog;
+
+ this.props.dispatch(
+ openDialog(dialogToDisplay, { session: _liveStreamingSession }));
+ }
+
/**
* Dispatches an action to show or hide the profile edit panel.
*
@@ -495,7 +517,12 @@ class Toolbox extends Component {
* @returns {void}
*/
_doToggleRecording() {
- this.props.dispatch(toggleRecording());
+ const { _fileRecordingSession } = this.props;
+ const dialog = _fileRecordingSession
+ ? StopRecordingDialog : StartRecordingDialog;
+
+ this.props.dispatch(
+ openDialog(dialog, { session: _fileRecordingSession }));
}
/**
@@ -764,6 +791,25 @@ class Toolbox extends Component {
this._doToggleFullScreen();
}
+ _onToolbarToggleLiveStreaming: () => void;
+
+ /**
+ * Starts the process for enabling or disabling live streaming.
+ *
+ * @private
+ * @returns {void}
+ */
+ _onToolbarToggleLiveStreaming() {
+ sendAnalytics(createToolbarEvent(
+ 'livestreaming.button',
+ {
+ 'is_streaming': Boolean(this.props._liveStreamingSession),
+ type: JitsiRecordingConstants.mode.STREAM
+ }));
+
+ this._doToggleLiveStreaming();
+ }
+
_onToolbarToggleProfile: () => void;
/**
@@ -805,8 +851,12 @@ class Toolbox extends Component {
* @returns {void}
*/
_onToolbarToggleRecording() {
- // No analytics handling is added here for the click as this action will
- // exercise the old toolbar UI flow, which includes analytics handling.
+ sendAnalytics(createToolbarEvent(
+ 'recording.button',
+ {
+ 'is_recording': Boolean(this.props._fileRecordingSession),
+ type: JitsiRecordingConstants.mode.FILE
+ }));
this._doToggleRecording();
}
@@ -891,6 +941,30 @@ class Toolbox extends Component {
);
}
+ /**
+ * Renders an {@code OverflowMenuItem} for starting or stopping a live
+ * streaming of the current conference.
+ *
+ * @private
+ * @returns {ReactElement}
+ */
+ _renderLiveStreamingButton() {
+ const { _liveStreamingSession, t } = this.props;
+
+ const translationKey = _liveStreamingSession
+ ? 'dialog.stopLiveStreaming'
+ : 'dialog.startLiveStreaming';
+
+ return (
+
+ );
+ }
+
/**
* Renders the list elements of the overflow menu.
*
@@ -904,6 +978,7 @@ class Toolbox extends Component {
_feedbackConfigured,
_fullScreen,
_isGuest,
+ _recordingEnabled,
_sharingVideo,
t
} = this.props;
@@ -929,7 +1004,12 @@ class Toolbox extends Component {
text = { _fullScreen
? t('toolbar.exitFullScreen')
: t('toolbar.enterFullScreen') } />,
- this._renderRecordingButton(),
+ _recordingEnabled
+ && this._shouldShowButton('livestreaming')
+ && this._renderLiveStreamingButton(),
+ _recordingEnabled
+ && this._shouldShowButton('recording')
+ && this._renderRecordingButton(),
this._shouldShowButton('sharedvideo')
&& {
}
/**
- * Renders an {@code OverflowMenuItem} depending on the current recording
- * state.
+ * Renders an {@code OverflowMenuItem} to start or stop recording of the
+ * current conference.
*
* @private
* @returns {ReactElement|null}
*/
_renderRecordingButton() {
- const {
- _isRecording,
- _recordingEnabled,
- _recordingType,
- t
- } = this.props;
+ const { _fileRecordingSession, t } = this.props;
- if (!_recordingEnabled || !this._shouldShowButton('recording')) {
- return null;
- }
-
- let iconClass, translationKey;
-
- if (_recordingType === RECORDING_TYPES.JIBRI) {
- iconClass = 'icon-public';
- translationKey = _isRecording
- ? 'dialog.stopLiveStreaming'
- : 'dialog.startLiveStreaming';
- } else {
- iconClass = 'icon-camera-take-picture';
- translationKey = _isRecording
- ? 'dialog.stopRecording'
- : 'dialog.startRecording';
- }
+ const translationKey = _fileRecordingSession
+ ? 'dialog.stopRecording'
+ : 'dialog.startRecording';
return (
@@ -1055,7 +1116,6 @@ function _mapStateToProps(state) {
enableRecording,
iAmRecorder
} = state['features/base/config'];
- const { isRecording, recordingType } = state['features/recording'];
const sharedVideoStatus = state['features/shared-video'].status;
const { current } = state['features/side-panel'];
const {
@@ -1083,14 +1143,15 @@ function _mapStateToProps(state) {
_hideInviteButton:
iAmRecorder || (!addPeopleEnabled && !dialOutEnabled),
_isGuest: state['features/base/jwt'].isGuest,
- _isRecording: isRecording,
+ _fileRecordingSession:
+ getActiveSession(state, JitsiRecordingConstants.mode.FILE),
_fullScreen: fullScreen,
+ _liveStreamingSession:
+ getActiveSession(state, JitsiRecordingConstants.mode.STREAM),
_localParticipantID: localParticipant.id,
_overflowMenuVisible: overflowMenuVisible,
_raisedHand: localParticipant.raisedHand,
- _recordingEnabled: isModerator && enableRecording
- && (conference && conference.isRecordingSupported()),
- _recordingType: recordingType,
+ _recordingEnabled: isModerator && enableRecording,
_screensharing: localVideo && localVideo.videoType === 'desktop',
_sharingVideo: sharedVideoStatus === 'playing'
|| sharedVideoStatus === 'start'
diff --git a/react/features/toolbox/reducer.js b/react/features/toolbox/reducer.js
index 468c7d6fb..88c9f1dc1 100644
--- a/react/features/toolbox/reducer.js
+++ b/react/features/toolbox/reducer.js
@@ -49,8 +49,7 @@ function _getInitialState() {
alwaysVisible: false,
/**
- * The indicator which determines whether the Toolbox is enabled. For
- * example, modules/UI/recording/Recording.js disables the Toolbox.
+ * The indicator which determines whether the Toolbox is enabled.
*
* @type {boolean}
*/
diff --git a/react/features/video-quality/components/VideoQualityLabel.web.js b/react/features/video-quality/components/VideoQualityLabel.web.js
index f27e74a05..da063e683 100644
--- a/react/features/video-quality/components/VideoQualityLabel.web.js
+++ b/react/features/video-quality/components/VideoQualityLabel.web.js
@@ -4,6 +4,7 @@ import React, { Component } from 'react';
import { connect } from 'react-redux';
import { translate } from '../../base/i18n';
+import { CircularLabel } from '../../base/label';
import { MEDIA_TYPE } from '../../base/media';
import { getTrackByMediaTypeAndParticipant } from '../../base/tracks';
@@ -49,20 +50,14 @@ export class VideoQualityLabel extends Component {
_audioOnly: PropTypes.bool,
/**
- * Whether or not a connection to a conference has been established.
+ * The message to show within the label.
*/
- _conferenceStarted: PropTypes.bool,
+ _labelKey: PropTypes.string,
/**
- * Whether or not the filmstrip is displayed with remote videos. Used to
- * determine display classes to set.
+ * The message to show within the label's tooltip.
*/
- _filmstripVisible: PropTypes.bool,
-
- /**
- * The current video resolution (height) to display a label for.
- */
- _resolution: PropTypes.number,
+ _tooltipKey: PropTypes.string,
/**
* The redux representation of the JitsiTrack displayed on large video.
@@ -75,42 +70,6 @@ export class VideoQualityLabel extends Component {
t: PropTypes.func
};
- /**
- * Initializes a new {@code VideoQualityLabel} instance.
- *
- * @param {Object} props - The read-only React Component props with which
- * the new instance is to be initialized.
- */
- constructor(props) {
- super(props);
-
- this.state = {
- /**
- * Whether or not the filmstrip is transitioning from not visible
- * to visible. Used to set a transition class for animation.
- *
- * @type {boolean}
- */
- togglingToVisible: false
- };
- }
-
- /**
- * Updates the state for whether or not the filmstrip is being toggled to
- * display after having being hidden.
- *
- * @inheritdoc
- * @param {Object} nextProps - The read-only props which this Component will
- * receive.
- * @returns {void}
- */
- componentWillReceiveProps(nextProps) {
- this.setState({
- togglingToVisible: nextProps._filmstripVisible
- && !this.props._filmstripVisible
- });
- }
-
/**
* Implements React's {@link Component#render()}.
*
@@ -120,95 +79,76 @@ export class VideoQualityLabel extends Component {
render() {
const {
_audioOnly,
- _conferenceStarted,
- _filmstripVisible,
- _resolution,
+ _labelKey,
+ _tooltipKey,
_videoTrack,
t
} = this.props;
- // FIXME The _conferenceStarted check is used to be defensive against
- // toggling audio only mode while there is no conference and hides the
- // need for error handling around audio only mode toggling.
- if (!_conferenceStarted) {
- return null;
- }
- // Determine which classes should be set on the component. These classes
- // will used to help with animations and setting position.
- const baseClasses = 'video-state-indicator moveToCorner';
- const filmstrip
- = _filmstripVisible ? 'with-filmstrip' : 'without-filmstrip';
- const opening = this.state.togglingToVisible ? 'opening' : '';
- const classNames
- = `${baseClasses} ${filmstrip} ${opening}`;
-
- let labelContent;
- let tooltipKey;
+ let className, labelContent, tooltipKey;
if (_audioOnly) {
- labelContent = ;
+ className = 'audio-only';
+ labelContent = t('videoStatus.audioOnly');
tooltipKey = 'videoStatus.labelTooltipAudioOnly';
} else if (!_videoTrack || _videoTrack.muted) {
- labelContent = ;
+ className = 'no-video';
+ labelContent = t('videoStatus.audioOnly');
tooltipKey = 'videoStatus.labelTooiltipNoVideo';
} else {
- const translationKeys
- = this._mapResolutionToTranslationsKeys(_resolution);
-
- labelContent = t(translationKeys.labelKey);
- tooltipKey = translationKeys.tooltipKey;
+ className = 'current-video-quality';
+ labelContent = t(_labelKey);
+ tooltipKey = _tooltipKey;
}
return (
-
-
-
- { labelContent }
-
-
-
+
+
+ { labelContent }
+
+
);
}
+}
- /**
- * Matches the passed in resolution with a translation keys for describing
- * the resolution. The passed in resolution will be matched with a known
- * resolution that it is at least greater than or equal to.
- *
- * @param {number} resolution - The video height to match with a
- * translation.
- * @private
- * @returns {Object}
- */
- _mapResolutionToTranslationsKeys(resolution) {
- // Set the default matching resolution of the lowest just in case a
- // match is not found.
- let highestMatchingResolution = RESOLUTIONS[0];
+/**
+ * Matches the passed in resolution with a translation keys for describing
+ * the resolution. The passed in resolution will be matched with a known
+ * resolution that it is at least greater than or equal to.
+ *
+ * @param {number} resolution - The video height to match with a
+ * translation.
+ * @private
+ * @returns {Object}
+ */
+function _mapResolutionToTranslationsKeys(resolution) {
+ // Set the default matching resolution of the lowest just in case a match is
+ // not found.
+ let highestMatchingResolution = RESOLUTIONS[0];
- for (let i = 0; i < RESOLUTIONS.length; i++) {
- const knownResolution = RESOLUTIONS[i];
+ for (let i = 0; i < RESOLUTIONS.length; i++) {
+ const knownResolution = RESOLUTIONS[i];
- if (resolution >= knownResolution) {
- highestMatchingResolution = knownResolution;
- } else {
- break;
- }
+ if (resolution >= knownResolution) {
+ highestMatchingResolution = knownResolution;
+ } else {
+ break;
}
-
- const labelKey
- = RESOLUTION_TO_TRANSLATION_KEY[highestMatchingResolution];
-
- return {
- labelKey,
- tooltipKey: `${labelKey}Tooltip`
- };
}
+
+ const labelKey
+ = RESOLUTION_TO_TRANSLATION_KEY[highestMatchingResolution];
+
+ return {
+ labelKey,
+ tooltipKey: `${labelKey}Tooltip`
+ };
}
/**
@@ -219,15 +159,13 @@ export class VideoQualityLabel extends Component {
* @private
* @returns {{
* _audioOnly: boolean,
- * _conferenceStarted: boolean,
- * _filmstripVisible: true,
- * _resolution: number,
+ * _labelKey: string,
+ * _tooltipKey: string,
* _videoTrack: Object
* }}
*/
function _mapStateToProps(state) {
- const { audioOnly, conference } = state['features/base/conference'];
- const { visible } = state['features/filmstrip'];
+ const { audioOnly } = state['features/base/conference'];
const { resolution, participantId } = state['features/large-video'];
const videoTrackOnLargeVideo = getTrackByMediaTypeAndParticipant(
state['features/base/tracks'],
@@ -235,11 +173,13 @@ function _mapStateToProps(state) {
participantId
);
+ const translationKeys
+ = audioOnly ? {} : _mapResolutionToTranslationsKeys(resolution);
+
return {
_audioOnly: audioOnly,
- _conferenceStarted: Boolean(conference),
- _filmstripVisible: visible,
- _resolution: resolution,
+ _labelKey: translationKeys.labelKey,
+ _tooltipKey: translationKeys.tooltipKey,
_videoTrack: videoTrackOnLargeVideo
};
}
diff --git a/react/features/videosipgw/middleware.js b/react/features/videosipgw/middleware.js
index f29a3f7fd..f44c02de4 100644
--- a/react/features/videosipgw/middleware.js
+++ b/react/features/videosipgw/middleware.js
@@ -52,76 +52,9 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
break;
}
- case SIP_GW_INVITE_ROOMS: {
- const { status } = getState()['features/videosipgw'];
-
- if (status === JitsiSIPVideoGWStatus.STATUS_UNDEFINED) {
- dispatch(showErrorNotification({
- descriptionKey: 'recording.unavailable',
- descriptionArguments: {
- serviceName: '$t(videoSIPGW.serviceName)'
- },
- titleKey: 'videoSIPGW.unavailableTitle'
- }));
-
- return;
- } else if (status === JitsiSIPVideoGWStatus.STATUS_BUSY) {
- dispatch(showWarningNotification({
- descriptionKey: 'videoSIPGW.busy',
- titleKey: 'videoSIPGW.busyTitle'
- }));
-
- return;
- } else if (status !== JitsiSIPVideoGWStatus.STATUS_AVAILABLE) {
- logger.error(`Unknown sip videogw status ${status}`);
-
- return;
- }
-
- for (const room of action.rooms) {
- const { id: sipAddress, name: displayName } = room;
-
- if (sipAddress && displayName) {
- const newSession = action.conference
- .createVideoSIPGWSession(sipAddress, displayName);
-
- if (newSession instanceof Error) {
- const e = newSession;
-
- if (e) {
- switch (e.message) {
- case JitsiSIPVideoGWStatus.ERROR_NO_CONNECTION: {
- dispatch(showErrorNotification({
- descriptionKey: 'videoSIPGW.errorInvite',
- titleKey: 'videoSIPGW.errorInviteTitle'
- }));
-
- return;
- }
- case JitsiSIPVideoGWStatus.ERROR_SESSION_EXISTS: {
- dispatch(showWarningNotification({
- titleKey: 'videoSIPGW.errorAlreadyInvited',
- titleArguments: { displayName }
- }));
-
- return;
- }
- }
- }
- logger.error(
- 'Unknown error trying to create sip videogw session',
- e);
-
- return;
- }
-
- newSession.start();
- } else {
- logger.error(`No display name or sip number for ${
- JSON.stringify(room)}`);
- }
- }
- }
+ case SIP_GW_INVITE_ROOMS:
+ _inviteRooms(action.rooms, action.conference, dispatch);
+ break;
}
return result;
@@ -144,6 +77,62 @@ function _availabilityChanged(status: string) {
};
}
+/**
+ * Processes the action from the actionType {@code SIP_GW_INVITE_ROOMS} by
+ * inviting rooms into the conference or showing an error message.
+ *
+ * @param {Array} rooms - The conference rooms to invite.
+ * @param {Object} conference - The JitsiConference to invite the rooms to.
+ * @param {Function} dispatch - The redux dispatch function for emitting state
+ * changes (queuing error notifications).
+ * @private
+ * @returns {void}
+ */
+function _inviteRooms(rooms, conference, dispatch) {
+ for (const room of rooms) {
+ const { id: sipAddress, name: displayName } = room;
+
+ if (sipAddress && displayName) {
+ const newSession = conference
+ .createVideoSIPGWSession(sipAddress, displayName);
+
+ if (newSession instanceof Error) {
+ const e = newSession;
+
+ switch (e.message) {
+ case JitsiSIPVideoGWStatus.ERROR_NO_CONNECTION: {
+ dispatch(showErrorNotification({
+ descriptionKey: 'videoSIPGW.errorInvite',
+ titleKey: 'videoSIPGW.errorInviteTitle'
+ }));
+
+ return;
+ }
+ case JitsiSIPVideoGWStatus.ERROR_SESSION_EXISTS: {
+ dispatch(showWarningNotification({
+ titleKey: 'videoSIPGW.errorAlreadyInvited',
+ titleArguments: { displayName }
+ }));
+
+ return;
+ }
+ }
+
+ logger.error(
+ 'Unknown error trying to create sip videogw session',
+ e);
+
+ return;
+ }
+
+ newSession.start();
+ } else {
+ logger.error(`No display name or sip number for ${
+ JSON.stringify(room)}`);
+ }
+ }
+}
+
/**
* Signals that a session we created has a change in its status.
*
@@ -173,6 +162,17 @@ function _sessionStateChanged(
descriptionKey: 'videoSIPGW.errorInviteFailed'
});
}
+ case JitsiSIPVideoGWStatus.STATE_OFF: {
+ if (event.failureReason === JitsiSIPVideoGWStatus.STATUS_BUSY) {
+ return showErrorNotification({
+ descriptionKey: 'videoSIPGW.busy',
+ titleKey: 'videoSIPGW.busyTitle'
+ });
+ } else if (event.failureReason) {
+ logger.error(`Unknown sip videogw error ${event.newState} ${
+ event.failureReason}`);
+ }
+ }
}
// nothing to show
diff --git a/service/UI/UIEvents.js b/service/UI/UIEvents.js
index 007bb42b3..630c31208 100644
--- a/service/UI/UIEvents.js
+++ b/service/UI/UIEvents.js
@@ -64,12 +64,10 @@ export default {
* @see {TOGGLE_FILMSTRIP}
*/
TOGGLED_FILMSTRIP: 'UI.toggled_filmstrip',
- TOGGLE_RECORDING: 'UI.toggle_recording',
TOGGLE_SCREENSHARING: 'UI.toggle_screensharing',
TOGGLED_SHARED_DOCUMENT: 'UI.toggled_shared_document',
HANGUP: 'UI.hangup',
LOGOUT: 'UI.logout',
- RECORDING_TOGGLED: 'UI.recording_toggled',
VIDEO_DEVICE_CHANGED: 'UI.video_device_changed',
AUDIO_DEVICE_CHANGED: 'UI.audio_device_changed',
AUDIO_OUTPUT_DEVICE_CHANGED: 'UI.audio_output_device_changed',