| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279 | /* @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 { getParticipantDisplayName } from '../base/participants';
import { MiddlewareRegistry, StateListenerRegistry } from '../base/redux';
import {
    playSound,
    registerSound,
    stopSound,
    unregisterSound
} from '../base/sounds';
import { RECORDING_SESSION_UPDATED } from './actionTypes';
import {
    clearRecordingSessions,
    hidePendingRecordingNotification,
    showPendingRecordingNotification,
    showRecordingError,
    showRecordingLimitNotification,
    showStartedRecordingNotification,
    showStoppedRecordingNotification,
    updateRecordingSessionData
} from './actions';
import {
    LIVE_STREAMING_OFF_SOUND_ID,
    LIVE_STREAMING_ON_SOUND_ID,
    RECORDING_OFF_SOUND_ID,
    RECORDING_ON_SOUND_ID
} from './constants';
import { getSessionById, getResourceId } from './functions';
import {
    LIVE_STREAMING_OFF_SOUND_FILE,
    LIVE_STREAMING_ON_SOUND_FILE,
    RECORDING_OFF_SOUND_FILE,
    RECORDING_ON_SOUND_FILE
} from './sounds';
declare var APP: Object;
declare var interfaceConfig: Object;
/**
 * 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(
            LIVE_STREAMING_OFF_SOUND_ID,
            LIVE_STREAMING_OFF_SOUND_FILE));
        dispatch(registerSound(
            LIVE_STREAMING_ON_SOUND_ID,
            LIVE_STREAMING_ON_SOUND_FILE));
        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(LIVE_STREAMING_OFF_SOUND_ID));
        dispatch(unregisterSound(LIVE_STREAMING_ON_SOUND_ID));
        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
        // but we want to indicate those in case of sip gateway
        const {
            iAmRecorder,
            iAmSipGateway,
            recordingLimit
        } = getState()['features/base/config'];
        if (iAmRecorder && !iAmSipGateway) {
            break;
        }
        const updatedSessionData
            = getSessionById(getState(), action.sessionData.id);
        const { initiator, mode, terminator } = updatedSessionData;
        const { PENDING, OFF, ON } = JitsiRecordingConstants.status;
        if (updatedSessionData.status === PENDING
            && (!oldSessionData || oldSessionData.status !== PENDING)) {
            dispatch(showPendingRecordingNotification(mode));
        } else if (updatedSessionData.status !== PENDING) {
            dispatch(hidePendingRecordingNotification(mode));
            if (updatedSessionData.status === ON
                && (!oldSessionData || oldSessionData.status !== ON)) {
                if (typeof recordingLimit === 'object') {
                    // Show notification with additional information to the initiator.
                    dispatch(showRecordingLimitNotification(mode));
                } else {
                    dispatch(showStartedRecordingNotification(mode, initiator, action.sessionData.id));
                }
                sendAnalytics(createRecordingEvent('start', mode));
                let soundID;
                if (mode === JitsiRecordingConstants.mode.FILE) {
                    soundID = RECORDING_ON_SOUND_ID;
                } else if (mode === JitsiRecordingConstants.mode.STREAM) {
                    soundID = LIVE_STREAMING_ON_SOUND_ID;
                }
                if (soundID) {
                    dispatch(playSound(soundID));
                }
                if (typeof APP !== 'undefined') {
                    APP.API.notifyRecordingStatusChanged(true, mode);
                }
            } else if (updatedSessionData.status === OFF
                && (!oldSessionData || oldSessionData.status !== OFF)) {
                if (terminator) {
                    dispatch(
                        showStoppedRecordingNotification(
                            mode, getParticipantDisplayName(getState, getResourceId(terminator))));
                }
                let duration = 0, soundOff, soundOn;
                if (oldSessionData && oldSessionData.timestamp) {
                    duration
                        = (Date.now() / 1000) - oldSessionData.timestamp;
                }
                sendAnalytics(createRecordingEvent('stop', mode, duration));
                if (mode === JitsiRecordingConstants.mode.FILE) {
                    soundOff = RECORDING_OFF_SOUND_ID;
                    soundOn = RECORDING_ON_SOUND_ID;
                } else if (mode === JitsiRecordingConstants.mode.STREAM) {
                    soundOff = LIVE_STREAMING_OFF_SOUND_ID;
                    soundOn = LIVE_STREAMING_ON_SOUND_ID;
                }
                if (soundOff && soundOn) {
                    dispatch(stopSound(soundOn));
                    dispatch(playSound(soundOff));
                }
                if (typeof APP !== 'undefined') {
                    APP.API.notifyRecordingStatusChanged(false, mode);
                }
            }
        }
        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 mode = recorderSession.getMode();
    const error = recorderSession.getError();
    const isStreamMode = mode === JitsiMeetJS.constants.recording.mode.STREAM;
    switch (error) {
    case JitsiMeetJS.constants.recording.error.SERVICE_UNAVAILABLE:
        dispatch(showRecordingError({
            descriptionKey: 'recording.unavailable',
            descriptionArguments: {
                serviceName: isStreamMode
                    ? '$t(liveStreaming.serviceName)'
                    : '$t(recording.serviceName)'
            },
            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;
    }
    if (typeof APP !== 'undefined') {
        APP.API.notifyRecordingStatusChanged(false, mode, error);
    }
}
 |