From 3d83847e4be8cf3ff1ac27ae879288d4226cc947 Mon Sep 17 00:00:00 2001 From: vp8x8 <37841821+vp8x8@users.noreply.github.com> Date: Mon, 21 Jun 2021 11:36:18 +0300 Subject: [PATCH] feat(vpaas, recording): Show recording link to recording initiator (#9362) * feat(vpaas, recording): Show recording link to recording initiator This applies only for jaas users for now but is easily extensible. Changed the recording sharing icon according to ui design. * fix(vpaas, recording): Guard for deployment info --- images/icon-cloud.png | Bin 0 -> 349 bytes lang/main.json | 6 +- react/features/base/config/functions.any.js | 20 +++++ react/features/billing-counter/functions.js | 10 +++ react/features/recording/actions.any.js | 84 ++++++++++++++---- .../Recording/StartRecordingDialogContent.js | 32 +++---- .../components/Recording/styles.native.js | 2 +- .../components/Recording/styles.web.js | 2 +- react/features/recording/functions.js | 36 ++++++++ react/features/recording/middleware.js | 14 +-- 10 files changed, 159 insertions(+), 47 deletions(-) create mode 100644 images/icon-cloud.png diff --git a/images/icon-cloud.png b/images/icon-cloud.png new file mode 100644 index 0000000000000000000000000000000000000000..6ea1b957220c1e301d696adca9a18b7d99f78fec GIT binary patch literal 349 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjoCO|{#S9GG!XV7ZFl!D-1!HlL zyA#8@b22Z19F}xPUq=Rpjs4tz5?O)#Po6H0Ar*{or<@gRG7xCJ|Lnj`z81bW4sQ;0 z8_T_A@ZQL}e%=d*wGLJass+yy&hSeG8m9CtX5S~7rV#mUWBz6a=KXC}<V@>HC6fN zT#jq&7Ho69Rc^I#e#4?k(^oE9Y#(;uZFPLq-;H9eo_sc6j&pqZe%JZPm*p!Pj9T0- zT;UQ> { + const state = getState(); + const initiatorId = getResourceId(initiator); + const participantName = getParticipantDisplayName(state, initiatorId); + let dialogProps = { + customActionNameKey: undefined, + descriptionKey: participantName ? 'liveStreaming.onBy' : 'liveStreaming.on', + descriptionArguments: { name: participantName }, + isDismissAllowed: true, + titleKey: 'dialog.liveStreaming' + }; - return showNotification(dialogProps, NOTIFICATION_TIMEOUT); + if (mode !== JitsiMeetJS.constants.recording.mode.STREAM) { + const recordingSharingUrl = getRecordingSharingUrl(state); + const iAmRecordingInitiator = getLocalParticipant(state).id === initiatorId; + + dialogProps = { + customActionHandler: undefined, + customActionNameKey: undefined, + descriptionKey: participantName ? 'recording.onBy' : 'recording.on', + descriptionArguments: { name: participantName }, + isDismissAllowed: true, + titleKey: 'dialog.recording' + }; + + // fetch the recording link from the server for recording initiators in jaas meetings + if (recordingSharingUrl + && isVpaasMeeting(state) + && iAmRecordingInitiator) { + const region = getMeetingRegion(state); + const tenant = getVpaasTenant(state); + + try { + const link = await getRecordingLink(recordingSharingUrl, sessionId, region, tenant); + + // add the option to copy recording link + dialogProps.customActionNameKey = 'recording.copyLink'; + dialogProps.customActionHandler = () => copyText(link); + dialogProps.titleKey = 'recording.on'; + dialogProps.descriptionKey = 'recording.linkGenerated'; + dialogProps.isDismissAllowed = false; + } catch (err) { + dispatch(showErrorNotification({ + titleKey: 'recording.errorFetchingLink' + })); + + return logger.error('Could not fetch recording link', err); + } + } + } + + dispatch(showNotification(dialogProps)); + }; } /** diff --git a/react/features/recording/components/Recording/StartRecordingDialogContent.js b/react/features/recording/components/Recording/StartRecordingDialogContent.js index dc1fe623b..1e5fef9bb 100644 --- a/react/features/recording/components/Recording/StartRecordingDialogContent.js +++ b/react/features/recording/components/Recording/StartRecordingDialogContent.js @@ -26,8 +26,7 @@ import { authorizeDropbox, updateDropboxToken } from '../../../dropbox'; import { RECORDING_TYPES } from '../../constants'; import { getRecordingDurationEstimation } from '../../functions'; -import { DROPBOX_LOGO, ICON_SHARE, JITSI_LOGO } from './styles'; - +import { DROPBOX_LOGO, ICON_CLOUD, JITSI_LOGO } from './styles'; type Props = { @@ -162,9 +161,11 @@ class StartRecordingDialogContent extends Component { * @returns {React$Component} */ _renderFileSharingContent() { - const { fileRecordingsServiceSharingEnabled, isVpaas } = this.props; + const { fileRecordingsServiceSharingEnabled, isVpaas, selectedRecordingService } = this.props; - if (!fileRecordingsServiceSharingEnabled || isVpaas) { + if (!fileRecordingsServiceSharingEnabled + || isVpaas + || selectedRecordingService !== RECORDING_TYPES.JITSI_REC_SERVICE) { return null; } @@ -173,31 +174,22 @@ class StartRecordingDialogContent extends Component { _styles: styles, isValidating, onSharingSettingChanged, - selectedRecordingService, sharingSetting, t } = this.props; - const controlDisabled = selectedRecordingService !== RECORDING_TYPES.JITSI_REC_SERVICE; - let mainContainerClasses = 'recording-header recording-header-line'; - - if (controlDisabled) { - mainContainerClasses += ' recording-switch-disabled'; - } - return ( { + value = { sharingSetting } /> ); } @@ -248,7 +240,7 @@ class StartRecordingDialogContent extends Component { value = { this.props.selectedRecordingService === RECORDING_TYPES.JITSI_REC_SERVICE } /> ) : null; - const icon = isVpaas ? ICON_SHARE : JITSI_LOGO; + const icon = isVpaas ? ICON_CLOUD : JITSI_LOGO; const label = isVpaas ? t('recording.serviceDescriptionCloud') : t('recording.serviceDescription'); return ( @@ -334,7 +326,7 @@ class StartRecordingDialogContent extends Component { return ( diff --git a/react/features/recording/components/Recording/styles.native.js b/react/features/recording/components/Recording/styles.native.js index 442f8cf20..3d49191b7 100644 --- a/react/features/recording/components/Recording/styles.native.js +++ b/react/features/recording/components/Recording/styles.native.js @@ -4,7 +4,7 @@ import { ColorSchemeRegistry, schemeColor } from '../../../base/color-scheme'; import { BoxModel, ColorPalette } from '../../../base/styles'; export const DROPBOX_LOGO = require('../../../../../images/dropboxLogo_square.png'); -export const ICON_SHARE = require('../../../../../images/icon-users.png'); +export const ICON_CLOUD = require('../../../../../images/icon-cloud.png'); export const JITSI_LOGO = require('../../../../../images/jitsiLogo_square.png'); // XXX The "standard" {@code BoxModel.padding} has been deemed insufficient in diff --git a/react/features/recording/components/Recording/styles.web.js b/react/features/recording/components/Recording/styles.web.js index 1b8318589..4a0d76ae1 100644 --- a/react/features/recording/components/Recording/styles.web.js +++ b/react/features/recording/components/Recording/styles.web.js @@ -4,6 +4,6 @@ export default {}; export const DROPBOX_LOGO = 'images/dropboxLogo_square.png'; -export const ICON_SHARE = 'images/icon-users.png'; +export const ICON_CLOUD = 'images/icon-cloud.png'; export const JITSI_LOGO = 'images/jitsiLogo_square.png'; diff --git a/react/features/recording/functions.js b/react/features/recording/functions.js index 363132b88..678d254ab 100644 --- a/react/features/recording/functions.js +++ b/react/features/recording/functions.js @@ -46,6 +46,27 @@ export function getSessionById(state: Object, id: string) { sessionData => sessionData.id === id); } +/** + * Fetches the recording link from the server. + * + * @param {string} url - The base url. + * @param {string} recordingSessionId - The ID of the recording session to find. + * @param {string} region - The meeting region. + * @param {string} tenant - The meeting tenant. + * @returns {Promise} + */ +export async function getRecordingLink(url: string, recordingSessionId: string, region: string, tenant: string) { + const fullUrl = `${url}?recordingSessionId=${recordingSessionId}®ion=${region}&tenant=${tenant}`; + const res = await fetch(fullUrl, { + headers: { + 'Content-Type': 'application/json' + } + }); + const json = await res.json(); + + return res.ok ? json.url : Promise.reject(json); +} + /** * Returns the recording session status that is to be shown in a label. E.g. If * there is a session with the status OFF and one with PENDING, then the PENDING @@ -72,3 +93,18 @@ export function getSessionStatusToShow(state: Object, mode: string): ?string { return status; } + + +/** + * Returns the resource id. + * + * @param {Object | string} recorder - A participant or it's resource. + * @returns {string|undefined} + */ +export function getResourceId(recorder: string | Object) { + if (recorder) { + return typeof recorder === 'string' + ? recorder + : recorder.getId(); + } +} diff --git a/react/features/recording/middleware.js b/react/features/recording/middleware.js index bccc491bd..9ec0a6fb4 100644 --- a/react/features/recording/middleware.js +++ b/react/features/recording/middleware.js @@ -37,7 +37,7 @@ import { RECORDING_OFF_SOUND_ID, RECORDING_ON_SOUND_ID } from './constants'; -import { getSessionById } from './functions'; +import { getSessionById, getResourceId } from './functions'; import { LIVE_STREAMING_OFF_SOUND_FILE, LIVE_STREAMING_ON_SOUND_FILE, @@ -160,11 +160,9 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => { // Show notification with additional information to the initiator. dispatch(showRecordingLimitNotification(mode)); } else { - dispatch(showStartedRecordingNotification( - mode, initiator && getParticipantDisplayName(getState, initiator.getId()))); + dispatch(showStartedRecordingNotification(mode, initiator, action.sessionData.id)); } - sendAnalytics(createRecordingEvent('start', mode)); if (disableRecordAudioNotification) { @@ -188,8 +186,12 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => { } } else if (updatedSessionData.status === OFF && (!oldSessionData || oldSessionData.status !== OFF)) { - dispatch(showStoppedRecordingNotification( - mode, terminator && getParticipantDisplayName(getState, terminator.getId()))); + if (terminator) { + dispatch( + showStoppedRecordingNotification( + mode, getParticipantDisplayName(getState, getResourceId(terminator)))); + } + let duration = 0, soundOff, soundOn; if (oldSessionData && oldSessionData.timestamp) {