From 6773aed67f08879333f16118c1953c485b443094 Mon Sep 17 00:00:00 2001 From: Hristo Terezov Date: Thu, 28 May 2020 17:42:02 -0500 Subject: [PATCH] feat(recording): Limit notification --- config.js | 15 ++++ lang/main.json | 4 + .../components/AbstractNotification.js | 5 ++ .../components/native/Notification.js | 17 ++-- .../notifications/components/native/styles.js | 2 +- .../recording/{actions.js => actions.any.js} | 0 react/features/recording/actions.native.js | 42 ++++++++++ react/features/recording/actions.web.js | 27 +++++++ .../RecordingLimitNotificationDescription.js | 81 +++++++++++++++++++ .../recording/components/web/index.js | 1 + react/features/recording/middleware.js | 17 +++- 11 files changed, 201 insertions(+), 10 deletions(-) rename react/features/recording/{actions.js => actions.any.js} (100%) create mode 100644 react/features/recording/actions.native.js create mode 100644 react/features/recording/actions.web.js create mode 100644 react/features/recording/components/web/RecordingLimitNotificationDescription.js diff --git a/config.js b/config.js index a0656bd87..2bfb82ac4 100644 --- a/config.js +++ b/config.js @@ -217,6 +217,21 @@ var config = { // Default value for the channel "last N" attribute. -1 for unlimited. channelLastN: -1, + // // Options for the recording limit notification. + // recordingLimit: { + // + // // The recording limit in minutes. Note: This number appears in the notification text + // // but doesn't enforce the actual recording time limit. This should be configured in + // // jibri! + // limit: 60, + // + // // The name of the app with unlimited recordings. + // appName: 'Unlimited recordings APP', + // + // // The URL of the app with unlimited recordings. + // appURL: 'https://unlimited.recordings.app.com/' + // }, + // Disables or enables RTX (RFC 4588) (defaults to false). // disableRtx: false, diff --git a/lang/main.json b/lang/main.json index 515c4a2f4..076ee147c 100644 --- a/lang/main.json +++ b/lang/main.json @@ -395,6 +395,8 @@ "videoQuality": "Manage call quality" }, "liveStreaming": { + "limitNotificationDescriptionWeb": "Due to high demand your streaming will be limited to {{limit}} min. For unlimited streaming try {{app}}.", + "limitNotificationDescriptionNative": "Your streaming will be limited to {{limit}} min. For unlimited streaming try {{app}}.", "busy": "We're working on freeing streaming resources. Please try again in a few minutes.", "busyTitle": "All streamers are currently busy", "changeSignIn": "Switch accounts.", @@ -552,6 +554,8 @@ }, "raisedHand": "Would like to speak", "recording": { + "limitNotificationDescriptionWeb": "Due to high demand your recording will be limited to {{limit}} min. For unlimited recordings try {{app}}.", + "limitNotificationDescriptionNative": "Due to high demand your recording will be limited to {{limit}} min. For unlimited recordings try <3>{{app}}.", "authDropboxText": "Upload to Dropbox", "availableSpace": "Available space: {{spaceLeft}} MB (approximately {{duration}} minutes of recording)", "beta": "BETA", diff --git a/react/features/notifications/components/AbstractNotification.js b/react/features/notifications/components/AbstractNotification.js index ae25749b2..8b0709295 100644 --- a/react/features/notifications/components/AbstractNotification.js +++ b/react/features/notifications/components/AbstractNotification.js @@ -55,6 +55,11 @@ export type Props = { */ isDismissAllowed: boolean, + /** + * Maximum lines of the description. + */ + maxLines: ?number, + /** * Callback invoked when the user clicks to dismiss the notification. */ diff --git a/react/features/notifications/components/native/Notification.js b/react/features/notifications/components/native/Notification.js index 9f09be3ce..4e12cab5c 100644 --- a/react/features/notifications/components/native/Notification.js +++ b/react/features/notifications/components/native/Notification.js @@ -11,6 +11,13 @@ import AbstractNotification, { import styles from './styles'; +/** + * Default value for the maxLines prop. + * + * @type {number} + */ +const DEFAULT_MAX_LINES = 1; + /** * Implements a React {@link Component} to display a notification. * @@ -24,9 +31,7 @@ class Notification extends AbstractNotification { * @returns {ReactElement} */ render() { - const { - isDismissAllowed - } = this.props; + const { isDismissAllowed } = this.props; return ( { * @private */ _renderContent() { - const { t, title, titleArguments, titleKey } = this.props; + const { maxLines = DEFAULT_MAX_LINES, t, title, titleArguments, titleKey } = this.props; const titleText = title || (titleKey && t(titleKey, titleArguments)); const description = this._getDescription(); @@ -69,7 +74,7 @@ class Notification extends AbstractNotification { return description.map((line, index) => ( { line } @@ -78,7 +83,7 @@ class Notification extends AbstractNotification { return ( { titleText } diff --git a/react/features/notifications/components/native/styles.js b/react/features/notifications/components/native/styles.js index 07101a821..3719c0a3f 100644 --- a/react/features/notifications/components/native/styles.js +++ b/react/features/notifications/components/native/styles.js @@ -40,7 +40,7 @@ export default { notification: { backgroundColor: '#768898', flexDirection: 'row', - height: 48, + minHeight: 48, marginTop: 0.5 * BoxModel.margin }, diff --git a/react/features/recording/actions.js b/react/features/recording/actions.any.js similarity index 100% rename from react/features/recording/actions.js rename to react/features/recording/actions.any.js diff --git a/react/features/recording/actions.native.js b/react/features/recording/actions.native.js new file mode 100644 index 000000000..d8de2e328 --- /dev/null +++ b/react/features/recording/actions.native.js @@ -0,0 +1,42 @@ +// @flow + +import JitsiMeetJS from '../base/lib-jitsi-meet'; +import { showNotification } from '../notifications'; + +export * from './actions.any'; + +/** + * Signals that a started recording notification should be shown on the + * screen for a given period. + * + * @param {string} streamType - The type of the stream ({@code file} or + * {@code stream}). + * @returns {showNotification} + */ +export function showRecordingLimitNotification(streamType: string) { + return (dispatch: Function, getState: Function) => { + const isLiveStreaming = streamType === JitsiMeetJS.constants.recording.mode.STREAM; + let descriptionKey, titleKey; + + if (isLiveStreaming) { + descriptionKey = 'liveStreaming.limitNotificationDescriptionNative'; + titleKey = 'dialog.liveStreaming'; + } else { + descriptionKey = 'recording.limitNotificationDescriptionNative'; + titleKey = 'dialog.recording'; + } + + const { recordingLimit = {} } = getState()['features/base/config']; + const { limit, appName } = recordingLimit; + + return dispatch(showNotification({ + descriptionArguments: { + limit, + app: appName + }, + descriptionKey, + titleKey, + maxLines: 2 + }, 10000)); + }; +} diff --git a/react/features/recording/actions.web.js b/react/features/recording/actions.web.js new file mode 100644 index 000000000..3baef9d12 --- /dev/null +++ b/react/features/recording/actions.web.js @@ -0,0 +1,27 @@ +// @flow + +import React from 'react'; + +import JitsiMeetJS from '../base/lib-jitsi-meet'; +import { showNotification } from '../notifications'; + +import { RecordingLimitNotificationDescription } from './components'; + +export * from './actions.any'; + +/** + * Signals that a started recording notification should be shown on the + * screen for a given period. + * + * @param {string} streamType - The type of the stream ({@code file} or + * {@code stream}). + * @returns {showNotification} + */ +export function showRecordingLimitNotification(streamType: string) { + const isLiveStreaming = streamType === JitsiMeetJS.constants.recording.mode.STREAM; + + return showNotification({ + description: , + titleKey: isLiveStreaming ? 'dialog.liveStreaming' : 'dialog.recording' + }, 10000); +} diff --git a/react/features/recording/components/web/RecordingLimitNotificationDescription.js b/react/features/recording/components/web/RecordingLimitNotificationDescription.js new file mode 100644 index 000000000..1c37da42e --- /dev/null +++ b/react/features/recording/components/web/RecordingLimitNotificationDescription.js @@ -0,0 +1,81 @@ +// @flow + +import React from 'react'; + +import { translate, translateToHTML } from '../../../base/i18n'; +import { connect } from '../../../base/redux'; + +/** + * The type of the React {@code Component} props of {@link RecordingLimitNotificationDescription}. + */ +type Props = { + + /** + * The limit of time in minutes for the recording. + */ + _limit: number, + + /** + * The name of the app with unlimited recordings. + */ + _appName: string, + + /** + * The URL to the app with unlimited recordings. + */ + _appURL: string, + + /** + * True if the notification is related to the livestreaming and false if not. + */ + isLiveStreaming: Boolean, + + /** + * Invoked to obtain translated strings. + */ + t: Function +}; + +/** + * A component that renders the description of the notification for the recording initiator. + * + * @param {Props} props - The props of the component. + * @returns {Component} + */ +function RecordingLimitNotificationDescription(props: Props) { + const { _limit, _appName, _appURL, isLiveStreaming, t } = props; + + return ( + + { + translateToHTML( + t, + `${isLiveStreaming ? 'liveStreaming' : 'recording'}.limitNotificationDescriptionWeb`, { + limit: _limit, + app: _appName, + url: _appURL + }) + } + + ); +} + + +/** + * Maps part of the Redix state to the props of this component. + * + * @param {Object} state - The Redux state. + * @returns {Props} + */ +function _mapStateToProps(state): $Shape { + const { recordingLimit = {} } = state['features/base/config']; + const { limit: _limit, appName: _appName, appURL: _appURL } = recordingLimit; + + return { + _limit, + _appName, + _appURL + }; +} + +export default translate(connect(_mapStateToProps)(RecordingLimitNotificationDescription)); diff --git a/react/features/recording/components/web/index.js b/react/features/recording/components/web/index.js index 2004cf90b..df93a236a 100644 --- a/react/features/recording/components/web/index.js +++ b/react/features/recording/components/web/index.js @@ -1,3 +1,4 @@ // @flow export { default as RecordingLabel } from './RecordingLabel'; +export { default as RecordingLimitNotificationDescription } from './RecordingLimitNotificationDescription'; diff --git a/react/features/recording/middleware.js b/react/features/recording/middleware.js index c680c3364..9c6a41534 100644 --- a/react/features/recording/middleware.js +++ b/react/features/recording/middleware.js @@ -26,6 +26,7 @@ import { hidePendingRecordingNotification, showPendingRecordingNotification, showRecordingError, + showRecordingLimitNotification, showStartedRecordingNotification, showStoppedRecordingNotification, updateRecordingSessionData @@ -44,6 +45,8 @@ import { RECORDING_ON_SOUND_FILE } from './sounds'; +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. @@ -131,7 +134,8 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => { const { iAmRecorder, iAmSipGateway, - disableRecordAudioNotification + disableRecordAudioNotification, + recordingLimit } = getState()['features/base/config']; if (iAmRecorder && !iAmSipGateway) { @@ -151,9 +155,16 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => { if (updatedSessionData.status === ON && (!oldSessionData || oldSessionData.status !== ON)) { - const initiatorName = initiator && getParticipantDisplayName(getState, initiator.getId()); + if (initiator) { + const initiatorName = initiator && getParticipantDisplayName(getState, initiator.getId()); + + initiatorName && dispatch(showStartedRecordingNotification(mode, initiatorName)); + } else if (typeof recordingLimit === 'object') { + // Show notification with additional information to the initiator. + dispatch(showRecordingLimitNotification(mode)); + } + - initiatorName && dispatch(showStartedRecordingNotification(mode, initiatorName)); sendAnalytics(createRecordingEvent('start', mode)); if (disableRecordAudioNotification) {