Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(recording) allow highlighting meeting recording moments #10981

Merged
merged 1 commit into from
Mar 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion config.js
Original file line number Diff line number Diff line change
Expand Up @@ -603,6 +603,7 @@ var config = {
// 'fullscreen',
// 'hangup',
// 'help',
// 'highlight',
// 'invite',
// 'livestreaming',
// 'microphone',
Expand Down Expand Up @@ -1116,7 +1117,8 @@ var config = {
// 'e2ee',
// 'transcribing',
// 'video-quality',
// 'insecure-room'
// 'insecure-room',
// 'highlight-moment'
// ]
// },

Expand Down
4 changes: 4 additions & 0 deletions lang/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -860,6 +860,10 @@
"expandedPending": "Recording is being started...",
"failedToStart": "Recording failed to start",
"fileSharingdescription": "Share the recording link with the meeting participants",
"highlightMoment": "Highlight moment",
"highlightMomentDisabled": "You can highlight moments when the recording starts",
"highlightMomentSuccess": "Moment highlighted",
"highlightMomentSucessDescription": "Your highlighted moment will be added to the meeting summary.",
"inProgress": "Recording or live streaming in progress",
"limitNotificationDescriptionNative": "Due to high demand your recording will be limited to {{limit}} min. For unlimited recordings try <3>{{app}}</3>.",
"limitNotificationDescriptionWeb": "Due to high demand your recording will be limited to {{limit}} min. For unlimited recordings try <a href={{url}} rel='noopener noreferrer' target='_blank'>{{app}}</a>.",
Expand Down
1 change: 1 addition & 0 deletions react/features/base/config/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export const TOOLBAR_BUTTONS = [
'fullscreen',
'hangup',
'help',
'highlight',
'invite',
'linktosalesforce',
'livestreaming',
Expand Down
3 changes: 3 additions & 0 deletions react/features/base/icons/svg/highlight.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions react/features/base/icons/svg/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export { default as IconFullScreen } from './full-screen.svg';
export { default as IconGoogle } from './google.svg';
export { default as IconHangup } from './hangup.svg';
export { default as IconHelp } from './help.svg';
export { default as IconHighlight } from './highlight.svg';
export { default as IconHome } from './home.svg';
export { default as IconHorizontalPoints } from './horizontal-points.svg';
export { default as IconInfo } from './info.svg';
Expand Down
1 change: 1 addition & 0 deletions react/features/conference/components/constants.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export const CONFERENCE_INFO = {
alwaysVisible: [ 'recording', 'local-recording', 'raised-hands-count' ],
autoHide: [
'highlight-moment',
'subject',
'conference-timer',
'participants-count',
Expand Down
5 changes: 5 additions & 0 deletions react/features/conference/components/web/ConferenceInfo.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { connect } from '../../../base/redux';
import { E2EELabel } from '../../../e2ee';
import { LocalRecordingLabel } from '../../../local-recording';
import { RecordingLabel } from '../../../recording';
import HighlightButton from '../../../recording/components/Recording/web/HighlightButton';
import { isToolboxVisible } from '../../../toolbox/functions.web';
import { TranscribingLabel } from '../../../transcribing';
import { VideoQualityLabel } from '../../../video-quality';
Expand Down Expand Up @@ -38,6 +39,10 @@ type Props = {
};

const COMPONENTS = [
{
Component: HighlightButton,
id: 'highlight-moment'
},
{
Component: SubjectText,
id: 'subject'
Expand Down
10 changes: 10 additions & 0 deletions react/features/recording/actionTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,13 @@ export const SET_SELECTED_RECORDING_SERVICE = 'SET_SELECTED_RECORDING_SERVICE';
* }
*/
export const SET_STREAM_KEY = 'SET_STREAM_KEY';

/**
* Sets the enable state of the meeting highlight button.
*
* {
* type: SET_MEETING_HIGHLIGHT_BUTTON_STATE,
* disabled: boolean
* }
*/
export const SET_MEETING_HIGHLIGHT_BUTTON_STATE = 'SET_MEETING_HIGHLIGHT_BUTTON_STATE';
43 changes: 42 additions & 1 deletion react/features/recording/actions.any.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,12 @@ import {
import {
CLEAR_RECORDING_SESSIONS,
RECORDING_SESSION_UPDATED,
SET_MEETING_HIGHLIGHT_BUTTON_STATE,
SET_PENDING_RECORDING_NOTIFICATION_UID,
SET_SELECTED_RECORDING_SERVICE,
SET_STREAM_KEY
} from './actionTypes';
import { getRecordingLink, getResourceId, isSavingRecordingOnDropbox } from './functions';
import { getRecordingLink, getResourceId, isSavingRecordingOnDropbox, sendMeetingHighlight } from './functions';
import logger from './logger';

declare var APP: Object;
Expand All @@ -38,6 +39,21 @@ export function clearRecordingSessions() {
};
}

/**
* Sets the meeting highlight button disable state.
*
* @param {boolean} disabled - The disabled state value.
* @returns {{
* type: CLEAR_RECORDING_SESSIONS
* }}
*/
export function setHighlightMomentButtonState(disabled: boolean) {
return {
type: SET_MEETING_HIGHLIGHT_BUTTON_STATE,
disabled
};
}

/**
* Signals that the pending recording notification should be removed from the
* screen.
Expand Down Expand Up @@ -105,6 +121,31 @@ export function showPendingRecordingNotification(streamType: string) {
};
}

/**
* Highlights a meeting moment.
*
* {@code stream}).
*
* @returns {Function}
*/
export function highlightMeetingMoment() {
return async (dispatch: Function, getState: Function) => {
dispatch(setHighlightMomentButtonState(true));

try {
await sendMeetingHighlight(getState());
dispatch(showNotification({
descriptionKey: 'recording.highlightMomentSucessDescription',
titleKey: 'recording.highlightMomentSuccess'
}));
} catch (err) {
logger.error('Could not highlight meeting moment', err);
}

dispatch(setHighlightMomentButtonState(false));
};
}

/**
* Signals that the recording error notification should be shown.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// @flow

import { Component } from 'react';

import { getActiveSession, isHighlightMeetingMomentDisabled } from '../..';
import { JitsiRecordingConstants } from '../../../base/lib-jitsi-meet';
import { highlightMeetingMoment } from '../../actions.any';

export type Props = {

/**
* Whether or not the conference is in audio only mode.
*/
_audioOnly: boolean,

/**
* Invoked to obtain translated strings.
*/
t: Function
};

/**
* Abstract class for the {@code AbstractHighlightButton} component.
*/
export default class AbstractHighlightButton<P: Props> extends Component<P> {
/**
* Initializes a new AbstractVideoTrack instance.
*
* @param {Object} props - The read-only properties with which the new
* instance is to be initialized.
*/
constructor(props: Props) {
super(props);

this._onClick = this._onClick.bind(this);
}

/**
* Handles clicking / pressing the button.
*
* @override
* @protected
* @returns {void}
*/
_onClick() {
const { dispatch } = this.props;

dispatch(highlightMeetingMoment());
}
}

/**
* Maps (parts of) the Redux state to the associated
* {@code AbstractVideoQualityLabel}'s props.
*
* @param {Object} state - The Redux state.
* @private
* @returns {{
* _audioOnly: boolean
* }}
*/
export function _abstractMapStateToProps(state: Object) {
const isRecordingRunning = getActiveSession(state, JitsiRecordingConstants.mode.FILE);
const isButtonDisabled = isHighlightMeetingMomentDisabled(state);
const { webhookProxyUrl } = state['features/base/config'];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, this one is not documented in config.js (not your fault)


return {
_disabled: !isRecordingRunning || isButtonDisabled,
_visible: Boolean(webhookProxyUrl)
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// @flow

import { withStyles } from '@material-ui/core';
import React from 'react';

import { translate } from '../../../../base/i18n';
import { IconHighlight } from '../../../../base/icons';
import { Label } from '../../../../base/label';
import { connect } from '../../../../base/redux';
import { Tooltip } from '../../../../base/tooltip';
import BaseTheme from '../../../../base/ui/components/BaseTheme';
import AbstractHighlightButton, {
_abstractMapStateToProps,
type Props as AbstractProps
} from '../AbstractHighlightButton';

type Props = AbstractProps & {
_disabled: boolean,

/**
* The message to show within the label's tooltip.
*/
_tooltipKey: string,

/**
* Flag controlling visibility of the component.
*/
_visible: boolean,
};

/**
* Creates the styles for the component.
*
* @param {Object} theme - The current UI theme.
*
* @returns {Object}
*/
const styles = theme => {
return {
regular: {
background: theme.palette.field02,
margin: '0 4px 4px 4px'
},
disabled: {
background: theme.palette.text02,
margin: '0 4px 4px 4px'
}
};
};

/**
* React {@code Component} responsible for displaying an action that
* allows users to highlight a meeting moment.
*/
export class HighlightButton extends AbstractHighlightButton<Props> {

/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @returns {ReactElement}
*/
render() {
const {
_disabled,
_visible,
classes,
t
} = this.props;


if (!_visible) {
return null;
}

const className = _disabled ? classes.disabled : classes.regular;
const tooltipKey = _disabled ? 'recording.highlightMomentDisabled' : 'recording.highlightMoment';

return (
<Tooltip
content = { t(tooltipKey) }
position = { 'bottom' }>
<Label
className = { className }
icon = { IconHighlight }
iconColor = { _disabled ? BaseTheme.palette.text03 : BaseTheme.palette.field01 }
id = 'highlightMeetingLabel'
onClick = { this._onClick } />
</Tooltip>
);
}
}

export default withStyles(styles)(translate(connect(_abstractMapStateToProps)(HighlightButton)));
1 change: 1 addition & 0 deletions react/features/recording/components/Recording/web/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// @flow

export { default as HighlightButton } from './HighlightButton';
export { default as RecordButton } from './RecordButton';
export { default as StartRecordingDialog } from './StartRecordingDialog';
export { default as StopRecordingDialog } from './StopRecordingDialog';
Loading