| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577 |
- /* global APP, $, config, interfaceConfig */
- /*
- * Copyright @ 2015 Atlassian Pty Ltd
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- const logger = require('jitsi-meet-logger').getLogger(__filename);
-
- import UIEvents from '../../../service/UI/UIEvents';
- import UIUtil from '../util/UIUtil';
- import VideoLayout from '../videolayout/VideoLayout';
-
- import {
- JitsiRecordingStatus
- } from '../../../react/features/base/lib-jitsi-meet';
- import {
- createToolbarEvent,
- createRecordingDialogEvent,
- sendAnalytics
- } from '../../../react/features/analytics';
- import { setToolboxEnabled } from '../../../react/features/toolbox';
- import { setNotificationsEnabled } from '../../../react/features/notifications';
- import {
- hideRecordingLabel,
- updateRecordingState
- } from '../../../react/features/recording';
-
- /**
- * Translation keys to use for display in the UI when recording the conference
- * but not streaming live.
- *
- * @private
- * @type {Object}
- */
- export const RECORDING_TRANSLATION_KEYS = {
- failedToStartKey: 'recording.failedToStart',
- recordingBusy: 'recording.busy',
- recordingBusyTitle: 'recording.busyTitle',
- recordingButtonTooltip: 'recording.buttonTooltip',
- recordingErrorKey: 'recording.error',
- recordingOffKey: 'recording.off',
- recordingOnKey: 'recording.on',
- recordingPendingKey: 'recording.pending',
- recordingTitle: 'dialog.recording',
- recordingUnavailable: 'recording.unavailable',
- recordingUnavailableParams: '$t(recording.serviceName)',
- recordingUnavailableTitle: 'recording.unavailableTitle'
- };
-
- /**
- * Translation keys to use for display in the UI when the recording mode is
- * currently streaming live.
- *
- * @private
- * @type {Object}
- */
- export const STREAMING_TRANSLATION_KEYS = {
- failedToStartKey: 'liveStreaming.failedToStart',
- recordingBusy: 'liveStreaming.busy',
- recordingBusyTitle: 'liveStreaming.busyTitle',
- recordingButtonTooltip: 'liveStreaming.buttonTooltip',
- recordingErrorKey: 'liveStreaming.error',
- recordingOffKey: 'liveStreaming.off',
- recordingOnKey: 'liveStreaming.on',
- recordingPendingKey: 'liveStreaming.pending',
- recordingTitle: 'dialog.liveStreaming',
- recordingUnavailable: 'recording.unavailable',
- recordingUnavailableParams: '$t(liveStreaming.serviceName)',
- recordingUnavailableTitle: 'liveStreaming.unavailableTitle'
- };
-
- /**
- * The dialog for user input.
- */
- let dialog = null;
-
- /**
- * Indicates if the recording button should be enabled.
- *
- * @returns {boolean} {true} if the
- * @private
- */
- function _isRecordingButtonEnabled() {
- return (
- interfaceConfig.TOOLBAR_BUTTONS.indexOf('recording') !== -1
- && config.enableRecording
- && APP.conference.isRecordingSupported());
- }
-
- /**
- * Request live stream token from the user.
- * @returns {Promise}
- */
- function _requestLiveStreamId() {
- const cancelButton
- = APP.translation.generateTranslationHTML('dialog.Cancel');
- const backButton = APP.translation.generateTranslationHTML('dialog.Back');
- const startStreamingButton
- = APP.translation.generateTranslationHTML('dialog.startLiveStreaming');
- const streamIdRequired
- = APP.translation.generateTranslationHTML(
- 'liveStreaming.streamIdRequired');
- const streamIdHelp
- = APP.translation.generateTranslationHTML(
- 'liveStreaming.streamIdHelp');
-
- return new Promise((resolve, reject) => {
- dialog = APP.UI.messageHandler.openDialogWithStates({
- state0: {
- titleKey: 'dialog.liveStreaming',
- html:
- `<input class="input-control"
- name="streamId" type="text"
- data-i18n="[placeholder]dialog.streamKey"
- autofocus><div style="text-align: right">
- <a class="helper-link" target="_new"
- href="${interfaceConfig.LIVE_STREAMING_HELP_LINK}">${
- streamIdHelp
- }</a></div>`,
- persistent: false,
- buttons: [
- { title: cancelButton,
- value: false },
- { title: startStreamingButton,
- value: true }
- ],
- focus: ':input:first',
- defaultButton: 1,
- submit(e, v, m, f) { // eslint-disable-line max-params
- e.preventDefault();
-
- if (v) {
- if (f.streamId && f.streamId.length > 0) {
- resolve(UIUtil.escapeHtml(f.streamId));
- dialog.close();
-
- return;
- }
- dialog.goToState('state1');
-
- return false;
-
- }
- reject(APP.UI.messageHandler.CANCEL);
- dialog.close();
-
- return false;
-
- }
- },
-
- state1: {
- titleKey: 'dialog.liveStreaming',
- html: streamIdRequired,
- persistent: false,
- buttons: [
- { title: cancelButton,
- value: false },
- { title: backButton,
- value: true }
- ],
- focus: ':input:first',
- defaultButton: 1,
- submit(e, v) {
- e.preventDefault();
- if (v === 0) {
- reject(APP.UI.messageHandler.CANCEL);
- dialog.close();
- } else {
- dialog.goToState('state0');
- }
- }
- }
- }, {
- close() {
- dialog = null;
- }
- });
- });
- }
-
- /**
- * Request recording token from the user.
- * @returns {Promise}
- */
- function _requestRecordingToken() {
- const titleKey = 'dialog.recordingToken';
- const msgString
- = `<input name="recordingToken" type="text"
- data-i18n="[placeholder]dialog.token"
- class="input-control"
- autofocus>`
-
- ;
-
-
- return new Promise((resolve, reject) => {
- dialog = APP.UI.messageHandler.openTwoButtonDialog({
- titleKey,
- msgString,
- leftButtonKey: 'dialog.Save',
- submitFunction(e, v, m, f) { // eslint-disable-line max-params
- if (v && f.recordingToken) {
- resolve(UIUtil.escapeHtml(f.recordingToken));
- } else {
- reject(APP.UI.messageHandler.CANCEL);
- }
- },
- closeFunction() {
- dialog = null;
- },
- focus: ':input:first'
- });
- });
- }
-
- /**
- * Shows a prompt dialog to the user when they have toggled off the recording.
- *
- * @param recordingType the recording type
- * @returns {Promise}
- * @private
- */
- function _showStopRecordingPrompt(recordingType) {
- let title;
- let message;
- let buttonKey;
-
- if (recordingType === 'jibri') {
- title = 'dialog.liveStreaming';
- message = 'dialog.stopStreamingWarning';
- buttonKey = 'dialog.stopLiveStreaming';
- } else {
- title = 'dialog.recording';
- message = 'dialog.stopRecordingWarning';
- buttonKey = 'dialog.stopRecording';
- }
-
- return new Promise((resolve, reject) => {
- dialog = APP.UI.messageHandler.openTwoButtonDialog({
- titleKey: title,
- msgKey: message,
- leftButtonKey: buttonKey,
- submitFunction: (e, v) => (v ? resolve : reject)(),
- closeFunction: () => {
- dialog = null;
- }
- });
- });
- }
-
- /**
- * Checks whether if the given status is either PENDING or RETRYING
- * @param status {JitsiRecordingStatus} Jibri status to be checked
- * @returns {boolean} true if the condition is met or false otherwise.
- */
- function isStartingStatus(status) {
- return (
- status === JitsiRecordingStatus.PENDING
- || status === JitsiRecordingStatus.RETRYING
- );
- }
-
- /**
- * Manages the recording user interface and user experience.
- * @type {{init, initRecordingButton, showRecordingButton, updateRecordingState,
- * updateRecordingUI, checkAutoRecord}}
- */
- const Recording = {
- /**
- * Initializes the recording UI.
- */
- init(eventEmitter, recordingType) {
- this.eventEmitter = eventEmitter;
- this.recordingType = recordingType;
-
- this.updateRecordingState(APP.conference.getRecordingState());
-
- if (recordingType === 'jibri') {
- this.baseClass = 'fa fa-play-circle';
- Object.assign(this, STREAMING_TRANSLATION_KEYS);
- } else {
- this.baseClass = 'icon-recEnable';
- 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) {
- VideoLayout.enableDeviceAvailabilityIcons(
- APP.conference.getMyUserId(), false);
-
- // in case of iAmSipGateway keep local video visible
- if (!config.iAmSipGateway) {
- VideoLayout.setLocalVideoVisible(false);
- }
-
- APP.store.dispatch(setToolboxEnabled(false));
- APP.store.dispatch(setNotificationsEnabled(false));
- APP.UI.messageHandler.enablePopups(false);
- }
- },
-
- /**
- * 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) {
- const shouldShow = show && _isRecordingButtonEnabled();
- const id = 'toolbar_button_record';
-
- UIUtil.setVisible(id, shouldShow);
- },
-
- /**
- * Updates the recording state UI.
- * @param recordingState gives us the current recording state
- */
- updateRecordingState(recordingState) {
- // I'm the recorder, so I don't want to see any UI related to states.
- if (config.iAmRecorder) {
- return;
- }
-
- // If there's no state change, we ignore the update.
- if (!recordingState || this.currentState === recordingState) {
- return;
- }
-
- this.updateRecordingUI(recordingState);
- },
-
- /**
- * Sets the state of the recording button.
- * @param recordingState gives us the current recording state
- */
- updateRecordingUI(recordingState) {
-
- const oldState = this.currentState;
-
- this.currentState = recordingState;
-
- let labelDisplayConfiguration;
-
- switch (recordingState) {
- case JitsiRecordingStatus.ON:
- case JitsiRecordingStatus.RETRYING: {
- labelDisplayConfiguration = {
- centered: false,
- key: this.recordingOnKey,
- showSpinner: recordingState === JitsiRecordingStatus.RETRYING
- };
-
- this._setToolbarButtonToggled(true);
-
- break;
- }
-
- case JitsiRecordingStatus.OFF:
- case JitsiRecordingStatus.BUSY:
- case JitsiRecordingStatus.FAILED:
- case JitsiRecordingStatus.UNAVAILABLE: {
- const wasInStartingStatus = isStartingStatus(oldState);
-
- // We don't want UI changes if this is an availability change.
- if (oldState !== JitsiRecordingStatus.ON && !wasInStartingStatus) {
- APP.store.dispatch(updateRecordingState({ recordingState }));
-
- return;
- }
-
- labelDisplayConfiguration = {
- centered: true,
- key: wasInStartingStatus
- ? this.failedToStartKey
- : this.recordingOffKey
- };
-
- this._setToolbarButtonToggled(false);
-
- setTimeout(() => {
- APP.store.dispatch(hideRecordingLabel());
- }, 5000);
-
- break;
- }
-
- case JitsiRecordingStatus.PENDING: {
- labelDisplayConfiguration = {
- centered: true,
- key: this.recordingPendingKey
- };
-
- this._setToolbarButtonToggled(false);
-
- break;
- }
-
- case JitsiRecordingStatus.ERROR: {
- labelDisplayConfiguration = {
- centered: true,
- key: this.recordingErrorKey
- };
-
- this._setToolbarButtonToggled(false);
-
- break;
- }
-
- // Return an empty label display configuration to indicate no label
- // should be displayed. The JitsiRecordingStatus.AVAIABLE case is
- // handled here.
- default: {
- labelDisplayConfiguration = null;
- }
- }
-
- APP.store.dispatch(updateRecordingState({
- labelDisplayConfiguration,
- recordingState
- }));
- },
-
- // checks whether recording is enabled and whether we have params
- // to start automatically recording (XXX: No, it doesn't do that).
- checkAutoRecord() {
- if (_isRecordingButtonEnabled && config.autoRecord) {
- this.predefinedToken = UIUtil.escapeHtml(config.autoRecordToken);
- this.eventEmitter.emit(
- UIEvents.RECORDING_TOGGLED,
- { token: this.predefinedToken });
- }
- },
-
- /**
- * Handles {@code click} on {@code toolbar_button_record}.
- *
- * @returns {void}
- */
- _onToolbarButtonClick() {
- sendAnalytics(createToolbarEvent(
- 'recording.button',
- {
- 'dialog_present': Boolean(dialog)
- }));
-
- if (dialog) {
- return;
- }
-
- switch (this.currentState) {
- case JitsiRecordingStatus.ON:
- case JitsiRecordingStatus.RETRYING:
- case JitsiRecordingStatus.PENDING: {
- _showStopRecordingPrompt(this.recordingType).then(
- () => {
- this.eventEmitter.emit(UIEvents.RECORDING_TOGGLED);
-
- // The confirm button on the stop recording dialog was
- // clicked
- sendAnalytics(
- createRecordingDialogEvent(
- 'stop',
- 'confirm.button'));
- },
- () => {}); // eslint-disable-line no-empty-function
- break;
- }
- case JitsiRecordingStatus.AVAILABLE:
- case JitsiRecordingStatus.OFF: {
- if (this.recordingType === 'jibri') {
- _requestLiveStreamId()
- .then(streamId => {
- this.eventEmitter.emit(
- UIEvents.RECORDING_TOGGLED,
- { streamId });
-
- // The confirm button on the start recording dialog was
- // clicked
- sendAnalytics(
- createRecordingDialogEvent(
- 'start',
- 'confirm.button'));
- })
- .catch(reason => {
- if (reason === APP.UI.messageHandler.CANCEL) {
- // The cancel button on the start recording dialog was
- // clicked
- sendAnalytics(
- createRecordingDialogEvent(
- 'start',
- 'cancel.button'));
- } else {
- logger.error(reason);
- }
- });
- } else {
- // Note that we only fire analytics events for Jibri.
- if (this.predefinedToken) {
- this.eventEmitter.emit(
- UIEvents.RECORDING_TOGGLED,
- { token: this.predefinedToken });
-
- return;
- }
-
- _requestRecordingToken().then(token => {
- this.eventEmitter.emit(
- UIEvents.RECORDING_TOGGLED,
- { token });
- })
- .catch(reason => {
- if (reason !== APP.UI.messageHandler.CANCEL) {
- logger.error(reason);
- }
- });
- }
- break;
- }
- case JitsiRecordingStatus.BUSY: {
- APP.UI.messageHandler.showWarning({
- descriptionKey: this.recordingBusy,
- titleKey: this.recordingBusyTitle
- });
- break;
- }
- default: {
- APP.UI.messageHandler.showError({
- descriptionKey: this.recordingUnavailable,
- descriptionArguments: {
- serviceName: this.recordingUnavailableParams },
- titleKey: this.recordingUnavailableTitle
- });
- }
- }
- },
-
- /**
- * 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);
- }
- };
-
- export default Recording;
|