// @flow import moment from 'moment'; import React, { Component } from 'react'; import type { Dispatch } from 'redux'; import { Dialog } from '../../base/dialog'; import { translate } from '../../base/i18n'; import { PARTICIPANT_ROLE, getLocalParticipant } from '../../base/participants'; import { connect } from '../../base/redux'; import { statsUpdate } from '../actions'; import { recordingController } from '../controller'; /** * The type of the React {@code Component} props of * {@link LocalRecordingInfoDialog}. */ type Props = { /** * Redux store dispatch function. */ dispatch: Dispatch, /** * Current encoding format. */ encodingFormat: string, /** * Whether the local user is the moderator. */ isModerator: boolean, /** * Whether local recording is engaged. */ isEngaged: boolean, /** * The start time of the current local recording session. * Used to calculate the duration of recording. */ recordingEngagedAt: Date, /** * Stats of all the participant. */ stats: Object, /** * Invoked to obtain translated strings. */ t: Function } /** * The type of the React {@code Component} state of * {@link LocalRecordingInfoDialog}. */ type State = { /** * The recording duration string to be displayed on the UI. */ durationString: string } /** * A React Component with the contents for a dialog that shows information about * local recording. For users with moderator rights, this is also the "control * panel" for starting/stopping local recording on all clients. * * @extends Component */ class LocalRecordingInfoDialog extends Component { /** * Saves a handle to the timer for UI updates, * so that it can be cancelled when the component unmounts. */ _timer: ?IntervalID; /** * Initializes a new {@code LocalRecordingInfoDialog} instance. * * @param {Props} props - The React {@code Component} props to initialize * the new {@code LocalRecordingInfoDialog} instance with. */ constructor(props: Props) { super(props); this.state = { durationString: '' }; } /** * Implements React's {@link Component#componentDidMount()}. * * @returns {void} */ componentDidMount() { this._timer = setInterval( () => { this.setState((_prevState, props) => { const nowTime = new Date(); return { durationString: this._getDuration(nowTime, props.recordingEngagedAt) }; }); try { this.props.dispatch( statsUpdate(recordingController .getParticipantsStats())); } catch (e) { // do nothing } }, 1000 ); } /** * Implements React's {@link Component#componentWillUnmount()}. * * @returns {void} */ componentWillUnmount() { if (this._timer) { clearInterval(this._timer); this._timer = null; } } /** * Implements React's {@link Component#render()}. * * @inheritdoc * @returns {ReactElement} */ render() { const { isModerator, t } = this.props; return (
{`${t('localRecording.moderator')}:`} { isModerator ? t('localRecording.yes') : t('localRecording.no') }
{ this._renderModeratorControls() } { this._renderDurationAndFormat() }
); } /** * Renders the recording duration and encoding format. Only shown if local * recording is engaged. * * @private * @returns {ReactElement|null} */ _renderDurationAndFormat() { const { encodingFormat, isEngaged, t } = this.props; const { durationString } = this.state; if (!isEngaged) { return null; } return (
{`${t('localRecording.duration')}:`} { durationString === '' ? t('localRecording.durationNA') : durationString }
{`${t('localRecording.encoding')}:`} { encodingFormat }
); } /** * Returns React elements for displaying the local recording stats of * each participant. * * @private * @returns {ReactElement|null} */ _renderStats() { const { stats } = this.props; if (stats === undefined) { return null; } const ids = Object.keys(stats); return (
{ this._renderStatsHeader() } { ids.map((id, i) => this._renderStatsLine(i, id)) }
); } /** * Renders the stats for one participant. * * @private * @param {*} lineKey - The key required by React for elements in lists. * @param {*} id - The ID of the participant. * @returns {ReactElement} */ _renderStatsLine(lineKey, id) { const { stats } = this.props; let statusClass = 'localrec-participant-stats-item__status-dot '; statusClass += stats[id].recordingStats ? stats[id].recordingStats.isRecording ? 'status-on' : 'status-off' : 'status-unknown'; return (
{ stats[id].displayName || id }
{ stats[id].recordingStats.currentSessionToken }
); } /** * Renders the participant stats header line. * * @private * @returns {ReactElement} */ _renderStatsHeader() { const { t } = this.props; return (
{ t('localRecording.participant') }
{ t('localRecording.sessionToken') }
); } /** * Renders the moderator-only controls: The stats of all users and the * action links. * * @private * @returns {ReactElement|null} */ _renderModeratorControls() { const { isModerator, isEngaged, t } = this.props; if (!isModerator) { return null; } return (
{`${t('localRecording.participantStats')}:`}
{ this._renderStats() }
); } /** * Creates a duration string "HH:MM:SS" from two Date objects. * * @param {Date} now - Current time. * @param {Date} prev - Previous time, the time to be subtracted. * @returns {string} */ _getDuration(now, prev) { if (prev === null || prev === undefined) { return ''; } // Still a hack, as moment.js does not support formatting of duration // (i.e. TimeDelta). Only works if total duration < 24 hours. // But who is going to have a 24-hour long conference? return moment(now - prev).utc() .format('HH:mm:ss'); } /** * Callback function for the Start UI action. * * @private * @returns {void} */ _onStart() { recordingController.startRecording(); } /** * Callback function for the Stop UI action. * * @private * @returns {void} */ _onStop() { recordingController.stopRecording(); } } /** * Maps (parts of) the Redux state to the associated props for the * {@code LocalRecordingInfoDialog} component. * * @param {Object} state - The Redux state. * @private * @returns {{ * encodingFormat: string, * isModerator: boolean, * isEngaged: boolean, * recordingEngagedAt: Date, * stats: Object * }} */ function _mapStateToProps(state) { const { encodingFormat, isEngaged, recordingEngagedAt, stats } = state['features/local-recording']; const isModerator = getLocalParticipant(state).role === PARTICIPANT_ROLE.MODERATOR; return { encodingFormat, isModerator, isEngaged, recordingEngagedAt, stats }; } export default translate(connect(_mapStateToProps)(LocalRecordingInfoDialog));