feat(recording):Recording popup menu

This commit is contained in:
yanas 2017-09-19 13:41:04 -05:00
parent 0c08f96755
commit 62a8ceecce
10 changed files with 370 additions and 62 deletions

View File

@ -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

View File

@ -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;
}
}

View File

@ -95,8 +95,8 @@
}
.connection-indicator,
div.indicator-container,
{
div.indicator-container
{
margin-right: 4px;
}

View File

@ -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

View File

@ -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":
{

View File

@ -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);

View File

@ -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);
}
};

View File

@ -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));

View File

@ -1 +1,2 @@
export { default as RecordingLabel } from './RecordingLabel';
export { default as RecordingButton } from './RecordingButton';

View File

@ -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
},
/**