feat(recording):Recording popup menu
This commit is contained in:
parent
0c08f96755
commit
62a8ceecce
|
@ -152,7 +152,7 @@ var config = { // eslint-disable-line no-unused-vars
|
|||
// Whether to enable recording or not.
|
||||
//enableRecording: false,
|
||||
|
||||
// Type for recording: one of jibri or jirecon.
|
||||
// Type for recording: one of jibri, jibri_file or jirecon.
|
||||
//recordingType: 'jibri',
|
||||
|
||||
// Misc
|
||||
|
|
|
@ -1,3 +1,41 @@
|
|||
/**
|
||||
* The recording spinner style.
|
||||
*/
|
||||
.recordingSpinner {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
/**
|
||||
* The recording button popup style.
|
||||
*/
|
||||
.recording-popup-dialog {
|
||||
width: 140px;
|
||||
|
||||
/**
|
||||
* The popup item style.
|
||||
*/
|
||||
.recording-popup-item {
|
||||
font-size: 12px;
|
||||
position: relative;
|
||||
top: -5px;
|
||||
}
|
||||
|
||||
/**
|
||||
* The popup title style.
|
||||
*/
|
||||
.recording-popup-title {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The transcription label style.
|
||||
*/
|
||||
#transcriptionLabel {
|
||||
border-radius: 3px;
|
||||
|
||||
> span {
|
||||
line-height: 40px;
|
||||
padding-left: 12px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -95,8 +95,8 @@
|
|||
}
|
||||
|
||||
.connection-indicator,
|
||||
div.indicator-container,
|
||||
{
|
||||
div.indicator-container
|
||||
{
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ var interfaceConfig = { // eslint-disable-line no-unused-vars
|
|||
//main toolbar
|
||||
'microphone', 'camera', 'desktop', 'invite', 'fullscreen', 'fodeviceselection', 'hangup', // jshint ignore:line
|
||||
//extended toolbar
|
||||
'profile', 'addtocall', 'contacts', 'chat', 'recording', 'etherpad', 'sharedvideo', 'dialout', 'settings', 'raisehand', 'videoquality', 'filmstrip'], // jshint ignore:line
|
||||
'profile', 'addtocall', 'contacts', 'chat', 'recording', 'transcription', 'etherpad', 'sharedvideo', 'dialout', 'settings', 'raisehand', 'videoquality', 'filmstrip'], // jshint ignore:line
|
||||
/**
|
||||
* Main Toolbar Buttons
|
||||
* All of them should be in TOOLBAR_BUTTONS
|
||||
|
|
|
@ -401,7 +401,10 @@
|
|||
"failedToStart": "Recording failed to start",
|
||||
"buttonTooltip": "Start / Stop recording",
|
||||
"error": "Recording failed. Please try again.",
|
||||
"unavailable": "The recording service is currently unavailable. Please try again later."
|
||||
"unavailable": "The recording service is currently unavailable. Please try again later.",
|
||||
"recordingPopupTitle": "Recording options",
|
||||
"videoRecordingLabel": "Video",
|
||||
"transcriptionLabel": "Transcription"
|
||||
},
|
||||
"liveStreaming":
|
||||
{
|
||||
|
|
|
@ -477,7 +477,6 @@ UI.updateLocalRole = isModerator => {
|
|||
APP.store.dispatch(showDialOutButton(isModerator));
|
||||
APP.store.dispatch(showSharedVideoButton());
|
||||
|
||||
Recording.showRecordingButton(isModerator);
|
||||
SettingsMenu.showStartMutedOptions(isModerator);
|
||||
SettingsMenu.showFollowMeOptions(isModerator);
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* global APP, $, config, interfaceConfig, JitsiMeetJS */
|
||||
/* global APP, config, interfaceConfig, JitsiMeetJS */
|
||||
/*
|
||||
* Copyright @ 2015 Atlassian Pty Ltd
|
||||
*
|
||||
|
@ -249,8 +249,7 @@ function isStartingStatus(status) {
|
|||
|
||||
/**
|
||||
* Manages the recording user interface and user experience.
|
||||
* @type {{init, initRecordingButton, showRecordingButton, updateRecordingState,
|
||||
* updateRecordingUI, checkAutoRecord}}
|
||||
* @type {{init, updateRecordingState, updateRecordingUI, checkAutoRecord}}
|
||||
*/
|
||||
var Recording = {
|
||||
/**
|
||||
|
@ -271,13 +270,6 @@ var Recording = {
|
|||
Object.assign(this, RECORDING_TRANSLATION_KEYS);
|
||||
}
|
||||
|
||||
// XXX Due to the React-ification of Toolbox, the HTMLElement with id
|
||||
// toolbar_button_record may not exist yet.
|
||||
$(document).on(
|
||||
'click',
|
||||
'#toolbar_button_record',
|
||||
ev => this._onToolbarButtonClick(ev));
|
||||
|
||||
// If I am a recorder then I publish my recorder custom role to notify
|
||||
// everyone.
|
||||
if (config.iAmRecorder) {
|
||||
|
@ -290,28 +282,6 @@ var Recording = {
|
|||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Initialise the recording button.
|
||||
*/
|
||||
initRecordingButton() {
|
||||
const selector = $('#toolbar_button_record');
|
||||
|
||||
selector.addClass(this.baseClass);
|
||||
selector.attr("data-i18n", "[content]" + this.recordingButtonTooltip);
|
||||
APP.translation.translateElement(selector);
|
||||
},
|
||||
|
||||
/**
|
||||
* Shows or hides the 'recording' button.
|
||||
* @param show {true} to show the recording button, {false} to hide it
|
||||
*/
|
||||
showRecordingButton(show) {
|
||||
let shouldShow = show && _isRecordingButtonEnabled();
|
||||
let id = 'toolbar_button_record';
|
||||
|
||||
UIUtil.setVisible(id, shouldShow);
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the recording state UI.
|
||||
* @param recordingState gives us the current recording state
|
||||
|
@ -338,6 +308,7 @@ var Recording = {
|
|||
this.currentState = recordingState;
|
||||
|
||||
let labelDisplayConfiguration;
|
||||
let recordingButtonToggled;
|
||||
|
||||
switch (recordingState) {
|
||||
case Status.ON:
|
||||
|
@ -348,7 +319,7 @@ var Recording = {
|
|||
showSpinner: recordingState === Status.RETRYING
|
||||
};
|
||||
|
||||
this._setToolbarButtonToggled(true);
|
||||
recordingButtonToggled = true;
|
||||
|
||||
break;
|
||||
}
|
||||
|
@ -372,7 +343,7 @@ var Recording = {
|
|||
: this.recordingOffKey
|
||||
};
|
||||
|
||||
this._setToolbarButtonToggled(false);
|
||||
recordingButtonToggled = false;
|
||||
|
||||
setTimeout(function(){
|
||||
APP.store.dispatch(hideRecordingLabel());
|
||||
|
@ -387,8 +358,6 @@ var Recording = {
|
|||
key: this.recordingPendingKey
|
||||
};
|
||||
|
||||
this._setToolbarButtonToggled(false);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -398,7 +367,7 @@ var Recording = {
|
|||
key: this.recordingErrorKey
|
||||
};
|
||||
|
||||
this._setToolbarButtonToggled(false);
|
||||
recordingButtonToggled = false;
|
||||
|
||||
break;
|
||||
}
|
||||
|
@ -412,6 +381,7 @@ var Recording = {
|
|||
|
||||
APP.store.dispatch(updateRecordingState({
|
||||
labelDisplayConfiguration,
|
||||
recordingButtonToggled,
|
||||
recordingState
|
||||
}));
|
||||
},
|
||||
|
@ -431,7 +401,7 @@ var Recording = {
|
|||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_onToolbarButtonClick() {
|
||||
toggleRecording() {
|
||||
if (dialog) {
|
||||
return;
|
||||
}
|
||||
|
@ -451,7 +421,12 @@ var Recording = {
|
|||
}
|
||||
case Status.AVAILABLE:
|
||||
case Status.OFF: {
|
||||
if (this.recordingType === 'jibri')
|
||||
if (this.recordingType === 'jibri_file') {
|
||||
this.eventEmitter.emit(
|
||||
UIEvents.RECORDING_TOGGLED);
|
||||
JitsiMeetJS.analytics.sendEvent('recording.started');
|
||||
}
|
||||
else if (this.recordingType === 'jibri')
|
||||
_requestLiveStreamId().then(streamId => {
|
||||
this.eventEmitter.emit(
|
||||
UIEvents.RECORDING_TOGGLED,
|
||||
|
@ -508,16 +483,6 @@ var Recording = {
|
|||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the toggled state of the recording toolbar button.
|
||||
*
|
||||
* @param {boolean} isToggled indicates if the button should be toggled
|
||||
* or not
|
||||
*/
|
||||
_setToolbarButtonToggled(isToggled) {
|
||||
$("#toolbar_button_record").toggleClass("toggled", isToggled);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,307 @@
|
|||
import AKInlineDialog from '@atlaskit/inline-dialog';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { ToggleStateless } from '@atlaskit/toggle';
|
||||
|
||||
import { translate } from '../../base/i18n';
|
||||
import { isButtonEnabled, ToolbarButton } from '../../toolbox';
|
||||
import Recording from '../../../../modules/UI/recording/Recording';
|
||||
|
||||
const DEFAULT_BUTTON_CONFIGURATION = {
|
||||
buttonName: 'recording',
|
||||
classNames: [ 'button', 'icon-recEnable' ],
|
||||
enabled: true,
|
||||
id: 'toolbar_button_record',
|
||||
tooltipKey: 'recording.buttonTooltip'
|
||||
};
|
||||
|
||||
/**
|
||||
* TOFIX: Copy paste from VideoQualityButton, we need a base class that supports
|
||||
* inline dialogs and does that position thing.
|
||||
* @type {{bottom: string, left: string, right: string, top: string}}
|
||||
*/
|
||||
const TOOLTIP_TO_DIALOG_POSITION = {
|
||||
bottom: 'bottom center',
|
||||
left: 'left middle',
|
||||
right: 'right middle',
|
||||
top: 'top center'
|
||||
};
|
||||
|
||||
/**
|
||||
* React {@code Component} for displaying an inline dialog for changing receive
|
||||
* video settings.
|
||||
*
|
||||
* @extends Component
|
||||
*/
|
||||
class RecordingButton extends Component {
|
||||
/**
|
||||
* {@code RecordingButton}'s property types.
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
static propTypes = {
|
||||
/**
|
||||
* The redux store representation of the JitsiConference.
|
||||
*/
|
||||
_conference: React.PropTypes.object,
|
||||
|
||||
/**
|
||||
* Indicates if the recording button has been toggled.
|
||||
*/
|
||||
_recordingButtonToggled: React.PropTypes.bool,
|
||||
|
||||
/**
|
||||
* Indicates if the audio recording option should be enabled.
|
||||
*/
|
||||
_recordingEnabled: React.PropTypes.bool,
|
||||
|
||||
/**
|
||||
* Indicates if the transcription option should be enabled.
|
||||
*/
|
||||
_transcriptionEnabled: React.PropTypes.bool,
|
||||
|
||||
/**
|
||||
* Whether or not the button is visible, based on the visibility of the
|
||||
* toolbar. Used to automatically hide the inline dialog if not visible.
|
||||
*/
|
||||
_visible: React.PropTypes.bool,
|
||||
|
||||
/**
|
||||
* Invoked to obtain translated string.
|
||||
*/
|
||||
t: React.PropTypes.func,
|
||||
|
||||
/**
|
||||
* From which side tooltips should display. Will be re-used for
|
||||
* displaying the inline dialog for video quality adjustment.
|
||||
*/
|
||||
tooltipPosition: React.PropTypes.string
|
||||
};
|
||||
|
||||
/**
|
||||
* Initializes a new {@code RecordingButton} instance.
|
||||
*
|
||||
* @param {Object} props - The read-only properties with which the new
|
||||
* instance is to be initialized.
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
/**
|
||||
* Whether or not the inline dialog for adjusting received video
|
||||
* quality is displayed.
|
||||
*/
|
||||
showRecordingDialog: false,
|
||||
|
||||
isToggleRecordingChecked: false,
|
||||
|
||||
isToggleTranscriptionChecked: false
|
||||
};
|
||||
|
||||
// Bind event handlers so they are only bound once for every instance.
|
||||
this._onDialogClose = this._onDialogClose.bind(this);
|
||||
this._onDialogToggle = this._onDialogToggle.bind(this);
|
||||
this._onToggleRecordingChange
|
||||
= this._onToggleRecordingChange.bind(this);
|
||||
this._onToggleTranscriptionChange
|
||||
= this._onToggleTranscriptionChange.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the toggled state depending on the _recordingButtonToggled
|
||||
* prop.
|
||||
*
|
||||
* @param {Object} nextProps - The props that will be applied after the
|
||||
* update.
|
||||
* @inheritdoc
|
||||
* @returns {void}
|
||||
*/
|
||||
componentWillUpdate(nextProps) {
|
||||
if (nextProps._recordingButtonToggled
|
||||
!== this.props._recordingButtonToggled
|
||||
&& nextProps._recordingButtonToggled
|
||||
!== this.state.isToggleRecordingChecked) {
|
||||
this.setState({
|
||||
isToggleRecordingChecked: nextProps._recordingButtonToggled
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const { _visible, tooltipPosition } = this.props;
|
||||
const buttonConfiguration = {
|
||||
...DEFAULT_BUTTON_CONFIGURATION,
|
||||
classNames: [
|
||||
...DEFAULT_BUTTON_CONFIGURATION.classNames,
|
||||
this.state.showRecordingDialog ? 'toggled button-active' : ''
|
||||
]
|
||||
};
|
||||
|
||||
const content = this._renderRecordingMenu();
|
||||
|
||||
return (
|
||||
<AKInlineDialog
|
||||
content = { content }
|
||||
isOpen = { _visible && this.state.showRecordingDialog }
|
||||
onClose = { this._onDialogClose }
|
||||
position = { TOOLTIP_TO_DIALOG_POSITION[tooltipPosition] }>
|
||||
<ToolbarButton
|
||||
button = { buttonConfiguration }
|
||||
onClick = { this._onDialogToggle }
|
||||
tooltipPosition = { tooltipPosition } />
|
||||
</AKInlineDialog>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides the attached inline dialog.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onDialogClose() {
|
||||
this.setState({ showRecordingDialog: false });
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the display of the dialog.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onDialogToggle() {
|
||||
this.setState({
|
||||
showRecordingDialog: !this.state.showRecordingDialog
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the current known state of the toggle selection.
|
||||
*
|
||||
* @param {Object} event - The DOM event from changing the toggle selection.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onToggleRecordingChange(event) {
|
||||
this.setState({
|
||||
isToggleRecordingChecked: event.target.checked
|
||||
});
|
||||
Recording.toggleRecording();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the current known state of the toggle selection.
|
||||
*
|
||||
* @param {Object} event - The DOM event from changing the toggle selection.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onToggleTranscriptionChange(event) {
|
||||
const checked = event.target.checked;
|
||||
|
||||
this.setState({
|
||||
isToggleTranscriptionChecked: checked
|
||||
});
|
||||
|
||||
checked
|
||||
? this.props._conference.startTranscriber()
|
||||
: this.props._conference.stopTranscriber();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@code RemoteVideoMenu} with buttons for interacting with
|
||||
* the remote participant.
|
||||
*
|
||||
* @private
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
_renderRecordingMenu() {
|
||||
const {
|
||||
_recordingEnabled,
|
||||
_transcriptionEnabled
|
||||
} = this.props;
|
||||
|
||||
const buttons = [];
|
||||
|
||||
if (_recordingEnabled) {
|
||||
buttons.push(
|
||||
<div key = 'recordingKey'>
|
||||
<ToggleStateless
|
||||
isChecked
|
||||
= { this.state.isToggleRecordingChecked }
|
||||
label = 'Recording'
|
||||
onChange = { this._onToggleRecordingChange } />
|
||||
<span className = 'recording-popup-item'>
|
||||
{ this.props.t('recording.videoRecordingLabel') }
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (_transcriptionEnabled) {
|
||||
buttons.push(
|
||||
<div key = 'transcriptionKey'>
|
||||
<ToggleStateless
|
||||
isChecked
|
||||
= { this.state.isToggleTranscriptionChecked }
|
||||
label = 'Transcription'
|
||||
onChange = { this._onToggleTranscriptionChange } />
|
||||
<span className = 'recording-popup-item'>
|
||||
{ this.props.t('recording.transcriptionLabel') }
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (buttons.length > 0) {
|
||||
return (
|
||||
<div className = 'recording-popup-dialog'>
|
||||
<h4 className = 'recording-popup-title'>
|
||||
{ this.props.t('recording.recordingPopupTitle') }
|
||||
</h4>
|
||||
{ buttons }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps (parts of) the Redux state to the associated {@code VideoQualityButton}
|
||||
* component's props.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @private
|
||||
* @returns {{
|
||||
* _visible: boolean
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
const { conference } = state['features/base/conference'];
|
||||
const { enableRecording, enableUserRolesBasedOnToken }
|
||||
= state['features/base/config'];
|
||||
const { isGuest } = state['features/jwt'];
|
||||
const { recordingButtonToggled } = state['features/recording'];
|
||||
|
||||
return {
|
||||
_conference: conference,
|
||||
_visible: state['features/toolbox'].visible,
|
||||
_recordingButtonToggled: recordingButtonToggled,
|
||||
_recordingEnabled: isButtonEnabled('recording')
|
||||
&& enableRecording
|
||||
&& conference && conference.isRecordingSupported(),
|
||||
_transcriptionEnabled: isButtonEnabled('transcription')
|
||||
&& (!enableUserRolesBasedOnToken || !isGuest)
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)(RecordingButton));
|
|
@ -1 +1,2 @@
|
|||
export { default as RecordingLabel } from './RecordingLabel';
|
||||
export { default as RecordingButton } from './RecordingButton';
|
||||
|
|
|
@ -6,6 +6,7 @@ import { DEFAULT_AVATAR_RELATIVE_PATH } from '../base/participants';
|
|||
import { openDeviceSelectionDialog } from '../device-selection';
|
||||
import { openDialOutDialog } from '../dial-out';
|
||||
import { openAddPeopleDialog, openInviteDialog } from '../invite';
|
||||
import { RecordingButton } from '../recording';
|
||||
import { VideoQualityButton } from '../video-quality';
|
||||
|
||||
import UIEvents from '../../../service/UI/UIEvents';
|
||||
|
@ -368,13 +369,7 @@ const buttons: Object = {
|
|||
* initialization in the recording module.
|
||||
*/
|
||||
recording: {
|
||||
classNames: [ 'button' ],
|
||||
enabled: true,
|
||||
|
||||
// will be displayed once the recording functionality is detected
|
||||
hidden: true,
|
||||
id: 'toolbar_button_record',
|
||||
tooltipKey: 'liveStreaming.buttonTooltip'
|
||||
component: RecordingButton
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in New Issue