/* @flow */ import { createRecordingEvent, sendAnalytics } from '../analytics'; import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../base/app'; import { CONFERENCE_WILL_JOIN, getCurrentConference } from '../base/conference'; import JitsiMeetJS, { JitsiConferenceEvents, JitsiRecordingConstants } from '../base/lib-jitsi-meet'; import { MiddlewareRegistry, StateListenerRegistry } from '../base/redux'; import { playSound, registerSound, stopSound, unregisterSound } from '../base/sounds'; import { clearRecordingSessions, hidePendingRecordingNotification, showPendingRecordingNotification, showRecordingError, showStoppedRecordingNotification, updateRecordingSessionData } from './actions'; import { RECORDING_SESSION_UPDATED } from './actionTypes'; import { RECORDING_OFF_SOUND_ID, RECORDING_ON_SOUND_ID } from './constants'; import { getSessionById } from './functions'; import { RECORDING_OFF_SOUND_FILE, RECORDING_ON_SOUND_FILE } from './sounds'; /** * StateListenerRegistry provides a reliable way to detect the leaving of a * conference, where we need to clean up the recording sessions. */ StateListenerRegistry.register( /* selector */ state => getCurrentConference(state), /* listener */ (conference, { dispatch }) => { if (!conference) { dispatch(clearRecordingSessions()); } } ); /** * The redux middleware to handle the recorder updates in a React way. * * @param {Store} store - The redux store. * @returns {Function} */ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => { let oldSessionData; if (action.type === RECORDING_SESSION_UPDATED) { oldSessionData = getSessionById(getState(), action.sessionData.id); } const result = next(action); switch (action.type) { case APP_WILL_MOUNT: dispatch(registerSound( RECORDING_OFF_SOUND_ID, RECORDING_OFF_SOUND_FILE)); dispatch(registerSound( RECORDING_ON_SOUND_ID, RECORDING_ON_SOUND_FILE)); break; case APP_WILL_UNMOUNT: dispatch(unregisterSound(RECORDING_OFF_SOUND_ID)); dispatch(unregisterSound(RECORDING_ON_SOUND_ID)); break; case CONFERENCE_WILL_JOIN: { const { conference } = action; conference.on( JitsiConferenceEvents.RECORDER_STATE_CHANGED, recorderSession => { if (recorderSession) { recorderSession.getID() && dispatch( updateRecordingSessionData(recorderSession)); recorderSession.getError() && _showRecordingErrorNotification( recorderSession, dispatch); } return; }); break; } case RECORDING_SESSION_UPDATED: { // When in recorder mode no notifications are shown // or extra sounds are also not desired if (getState()['features/base/config'].iAmRecorder) { break; } const updatedSessionData = getSessionById(getState(), action.sessionData.id); const { PENDING, OFF, ON } = JitsiRecordingConstants.status; if (updatedSessionData.status === PENDING && (!oldSessionData || oldSessionData.status !== PENDING)) { dispatch( showPendingRecordingNotification(updatedSessionData.mode)); } else if (updatedSessionData.status !== PENDING) { dispatch( hidePendingRecordingNotification(updatedSessionData.mode)); if (updatedSessionData.status === ON && (!oldSessionData || oldSessionData.status !== ON) && updatedSessionData.mode === JitsiRecordingConstants.mode.FILE) { sendAnalytics(createRecordingEvent('start', 'file')); dispatch(playSound(RECORDING_ON_SOUND_ID)); } else if (updatedSessionData.status === OFF && (!oldSessionData || oldSessionData.status !== OFF)) { dispatch( showStoppedRecordingNotification( updatedSessionData.mode)); if (updatedSessionData.mode === JitsiRecordingConstants.mode.FILE) { let duration = 0; // eslint-disable-next-line max-depth if (oldSessionData && oldSessionData.timestamp) { duration = (Date.now() / 1000) - oldSessionData.timestamp; } sendAnalytics( createRecordingEvent('stop', 'file', duration)); dispatch(stopSound(RECORDING_ON_SOUND_ID)); dispatch(playSound(RECORDING_OFF_SOUND_ID)); } } } break; } } return result; }); /** * Shows a notification about an error in the recording session. A * default notification will display if no error is specified in the passed * in recording session. * * @private * @param {Object} recorderSession - The recorder session model from the * lib. * @param {Dispatch} dispatch - The Redux Dispatch function. * @returns {void} */ function _showRecordingErrorNotification(recorderSession, dispatch) { const isStreamMode = recorderSession.getMode() === JitsiMeetJS.constants.recording.mode.STREAM; switch (recorderSession.getError()) { case JitsiMeetJS.constants.recording.error.SERVICE_UNAVAILABLE: dispatch(showRecordingError({ descriptionKey: 'recording.unavailable', descriptionArguments: { serviceName: isStreamMode ? 'Live Streaming service' : 'Recording service' }, titleKey: isStreamMode ? 'liveStreaming.unavailableTitle' : 'recording.unavailableTitle' })); break; case JitsiMeetJS.constants.recording.error.RESOURCE_CONSTRAINT: dispatch(showRecordingError({ descriptionKey: isStreamMode ? 'liveStreaming.busy' : 'recording.busy', titleKey: isStreamMode ? 'liveStreaming.busyTitle' : 'recording.busyTitle' })); break; default: dispatch(showRecordingError({ descriptionKey: isStreamMode ? 'liveStreaming.error' : 'recording.error', titleKey: isStreamMode ? 'liveStreaming.failedToStart' : 'recording.failedToStart' })); break; } }