123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522 |
- /* global APP, $, config, interfaceConfig, JitsiMeetJS */
- /*
- * 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.
- */
- import UIEvents from "../../../service/UI/UIEvents";
- import UIUtil from '../util/UIUtil';
- import VideoLayout from '../videolayout/VideoLayout';
- import Feedback from '../feedback/Feedback.js';
- import Toolbar from '../toolbars/Toolbar';
-
- /**
- * 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 msg = APP.translation.generateTranslationHTML("dialog.liveStreaming");
- const token = APP.translation.translateString("dialog.streamKey");
- 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");
-
- return new Promise(function (resolve, reject) {
- dialog = APP.UI.messageHandler.openDialogWithStates({
- state0: {
- title: msg,
- html:
- `<input name="streamId" type="text"
- data-i18n="[placeholder]dialog.streamKey"
- placeholder="${token}" autofocus>`,
- persistent: false,
- buttons: [
- {title: cancelButton, value: false},
- {title: startStreamingButton, value: true}
- ],
- focus: ':input:first',
- defaultButton: 1,
- submit: function (e, v, m, f) {
- e.preventDefault();
-
- if (v) {
- if (f.streamId && f.streamId.length > 0) {
- resolve(UIUtil.escapeHtml(f.streamId));
- dialog.close();
- return;
- }
- else {
- dialog.goToState('state1');
- return false;
- }
- } else {
- reject(APP.UI.messageHandler.CANCEL);
- dialog.close();
- return false;
- }
- }
- },
-
- state1: {
- title: msg,
- html: streamIdRequired,
- persistent: false,
- buttons: [
- {title: cancelButton, value: false},
- {title: backButton, value: true}
- ],
- focus: ':input:first',
- defaultButton: 1,
- submit: function (e, v) {
- e.preventDefault();
- if (v === 0) {
- reject(APP.UI.messageHandler.CANCEL);
- dialog.close();
- } else {
- dialog.goToState('state0');
- }
- }
- }
- }, {
- close: function () {
- dialog = null;
- }
- });
- });
- }
-
- /**
- * Request recording token from the user.
- * @returns {Promise}
- */
- function _requestRecordingToken () {
- let titleKey = "dialog.recordingToken";
- let token = APP.translation.translateString("dialog.token");
- let messageString = (
- `<input name="recordingToken" type="text"
- data-i18n="[placeholder]dialog.token"
- placeholder="${token}" autofocus>`
- );
- return new Promise(function (resolve, reject) {
- dialog = APP.UI.messageHandler.openTwoButtonDialog({
- titleKey,
- messageString,
- leftButtonKey: 'dialog.Save',
- submitFunction: function (e, v, m, f) {
- if (v && f.recordingToken) {
- resolve(UIUtil.escapeHtml(f.recordingToken));
- } else {
- reject(APP.UI.messageHandler.CANCEL);
- }
- },
- closeFunction: function () {
- 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) {
- var title;
- var message;
- var 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(function (resolve, reject) {
- dialog = APP.UI.messageHandler.openTwoButtonDialog({
- titleKey: title,
- messageKey: message,
- leftButtonKey: buttonKey,
- submitFunction: function(e,v) {
- if (v) {
- resolve();
- } else {
- reject();
- }
- },
- closeFunction: function () {
- dialog = null;
- }
- });
- });
- }
-
- /**
- * Moves the element given by {selector} to the top right corner of the screen.
- * @param selector the selector for the element to move
- * @param move {true} to move the element, {false} to move it back to its intial
- * position
- */
- function moveToCorner(selector, move) {
- let moveToCornerClass = "moveToCorner";
- let containsClass = selector.hasClass(moveToCornerClass);
-
- if (move && !containsClass)
- selector.addClass(moveToCornerClass);
- else if (!move && containsClass)
- selector.removeClass(moveToCornerClass);
- }
-
- /**
- * The status of the recorder.
- * FIXME: Those constants should come from the library.
- * @type {{ON: string, OFF: string, AVAILABLE: string,
- * UNAVAILABLE: string, PENDING: string}}
- */
- var Status = {
- ON: "on",
- OFF: "off",
- AVAILABLE: "available",
- UNAVAILABLE: "unavailable",
- PENDING: "pending",
- RETRYING: "retrying",
- ERROR: "error",
- FAILED: "failed",
- BUSY: "busy"
- };
-
- /**
- * Checks whether if the given status is either PENDING or RETRYING
- * @param status {Status} Jibri status to be checked
- * @returns {boolean} true if the condition is met or false otherwise.
- */
- function isStartingStatus(status) {
- return status === Status.PENDING || status === Status.RETRYING;
- }
-
- /**
- * Manages the recording user interface and user experience.
- * @type {{init, initRecordingButton, showRecordingButton, updateRecordingState,
- * updateRecordingUI, checkAutoRecord}}
- */
- var Recording = {
- /**
- * Initializes the recording UI.
- */
- init (emitter, recordingType) {
- this.eventEmitter = emitter;
-
- this.updateRecordingState(APP.conference.getRecordingState());
-
- this.initRecordingButton(recordingType);
-
- // If I am a recorder then I publish my recorder custom role to notify
- // everyone.
- if (config.iAmRecorder) {
- VideoLayout.enableDeviceAvailabilityIcons(
- APP.conference.getMyUserId(), false);
- VideoLayout.setLocalVideoVisible(false);
- Feedback.enableFeedback(false);
- Toolbar.enable(false);
- APP.UI.messageHandler.enableNotifications(false);
- APP.UI.messageHandler.enablePopups(false);
- }
- },
-
- /**
- * Initialise the recording button.
- */
- initRecordingButton(recordingType) {
- let selector = $('#toolbar_button_record');
-
- let button = selector.get(0);
- UIUtil.setTooltip(button, 'liveStreaming.buttonTooltip', 'right');
-
- if (recordingType === 'jibri') {
- this.baseClass = "fa fa-play-circle";
- this.recordingTitle = "dialog.liveStreaming";
- this.recordingOnKey = "liveStreaming.on";
- this.recordingOffKey = "liveStreaming.off";
- this.recordingPendingKey = "liveStreaming.pending";
- this.failedToStartKey = "liveStreaming.failedToStart";
- this.recordingErrorKey = "liveStreaming.error";
- this.recordingButtonTooltip = "liveStreaming.buttonTooltip";
- this.recordingUnavailable = "liveStreaming.unavailable";
- this.recordingBusy = "liveStreaming.busy";
- }
- else {
- this.baseClass = "icon-recEnable";
- this.recordingTitle = "dialog.recording";
- this.recordingOnKey = "recording.on";
- this.recordingOffKey = "recording.off";
- this.recordingPendingKey = "recording.pending";
- this.failedToStartKey = "recording.failedToStart";
- this.recordingErrorKey = "recording.error";
- this.recordingButtonTooltip = "recording.buttonTooltip";
- this.recordingUnavailable = "recording.unavailable";
- this.recordingBusy = "liveStreaming.busy";
- }
-
- selector.addClass(this.baseClass);
- selector.attr("data-i18n", "[content]" + this.recordingButtonTooltip);
- selector.attr("content",
- APP.translation.translateString(this.recordingButtonTooltip));
-
- var self = this;
- selector.click(function () {
- if (dialog)
- return;
- JitsiMeetJS.analytics.sendEvent('recording.clicked');
- switch (self.currentState) {
- case Status.ON:
- case Status.RETRYING:
- case Status.PENDING: {
- _showStopRecordingPrompt(recordingType).then(
- () => {
- self.eventEmitter.emit(UIEvents.RECORDING_TOGGLED);
- JitsiMeetJS.analytics.sendEvent(
- 'recording.stopped');
- },
- () => {});
- break;
- }
- case Status.AVAILABLE:
- case Status.OFF: {
- if (recordingType === 'jibri')
- _requestLiveStreamId().then((streamId) => {
- self.eventEmitter.emit( UIEvents.RECORDING_TOGGLED,
- {streamId: streamId});
- JitsiMeetJS.analytics.sendEvent(
- 'recording.started');
- }).catch(
- reason => {
- if (reason !== APP.UI.messageHandler.CANCEL)
- console.error(reason);
- else
- JitsiMeetJS.analytics.sendEvent(
- 'recording.canceled');
- }
- );
- else {
- if (self.predefinedToken) {
- self.eventEmitter.emit( UIEvents.RECORDING_TOGGLED,
- {token: self.predefinedToken});
- JitsiMeetJS.analytics.sendEvent(
- 'recording.started');
- return;
- }
-
- _requestRecordingToken().then((token) => {
- self.eventEmitter.emit( UIEvents.RECORDING_TOGGLED,
- {token: token});
- JitsiMeetJS.analytics.sendEvent(
- 'recording.started');
- }).catch(
- reason => {
- if (reason !== APP.UI.messageHandler.CANCEL)
- console.error(reason);
- else
- JitsiMeetJS.analytics.sendEvent(
- 'recording.canceled');
- }
- );
- }
- break;
- }
- case Status.BUSY: {
- dialog = APP.UI.messageHandler.openMessageDialog(
- self.recordingTitle,
- self.recordingBusy,
- null, null,
- function () {
- dialog = null;
- }
- );
- break;
- }
- default: {
- dialog = APP.UI.messageHandler.openMessageDialog(
- self.recordingTitle,
- self.recordingUnavailable,
- null, null,
- function () {
- dialog = null;
- }
- );
- }
- }
- });
- },
-
- /**
- * Shows or hides the 'recording' button.
- * @param show {true} to show the recording button, {false} to hide it
- */
- showRecordingButton (show) {
- if (_isRecordingButtonEnabled() && show) {
- $('#toolbar_button_record').css({display: "inline-block"});
- } else {
- $('#toolbar_button_record').css({display: "none"});
- }
- },
-
- /**
- * 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) {
-
- let oldState = this.currentState;
- this.currentState = recordingState;
-
- // TODO: handle recording state=available
- if (recordingState === Status.ON ||
- recordingState === Status.RETRYING) {
-
- this._setToolbarButtonToggled(true);
-
- this._updateStatusLabel(this.recordingOnKey, false);
- }
- else if (recordingState === Status.OFF
- || recordingState === Status.UNAVAILABLE
- || recordingState === Status.BUSY
- || recordingState === Status.FAILED) {
-
- // We don't want to do any changes if this is
- // an availability change.
- if (oldState !== Status.ON
- && !isStartingStatus(oldState))
- return;
-
- this._setToolbarButtonToggled(false);
-
- let messageKey;
- if (isStartingStatus(oldState))
- messageKey = this.failedToStartKey;
- else
- messageKey = this.recordingOffKey;
-
- this._updateStatusLabel(messageKey, true);
-
- setTimeout(function(){
- $('#recordingLabel').css({display: "none"});
- }, 5000);
- }
- else if (recordingState === Status.PENDING) {
-
- this._setToolbarButtonToggled(false);
-
- this._updateStatusLabel(this.recordingPendingKey, true);
- }
- else if (recordingState === Status.ERROR
- || recordingState === Status.FAILED) {
-
- this._setToolbarButtonToggled(false);
-
- this._updateStatusLabel(this.recordingErrorKey, true);
- }
-
- let labelSelector = $('#recordingLabel');
-
- // We don't show the label for available state.
- if (recordingState !== Status.AVAILABLE
- && !labelSelector.is(":visible"))
- labelSelector.css({display: "inline-block"});
-
- // Recording spinner
- if (recordingState === Status.RETRYING)
- $("#recordingSpinner").show();
- else
- $("#recordingSpinner").hide();
- },
- // checks whether recording is enabled and whether we have params
- // to start automatically recording
- checkAutoRecord () {
- if (_isRecordingButtonEnabled && config.autoRecord) {
- this.predefinedToken = UIUtil.escapeHtml(config.autoRecordToken);
- this.eventEmitter.emit(UIEvents.RECORDING_TOGGLED,
- this.predefinedToken);
- }
- },
- /**
- * Updates the status label.
- * @param textKey the text to show
- * @param isCentered indicates if the label should be centered on the window
- * or moved to the top right corner.
- */
- _updateStatusLabel(textKey, isCentered) {
- let labelSelector = $('#recordingLabel');
- let labelTextSelector = $('#recordingLabelText');
-
- moveToCorner(labelSelector, !isCentered);
-
- labelTextSelector.attr("data-i18n", textKey);
- labelTextSelector.text(APP.translation.translateString(textKey));
- },
-
- /**
- * 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;
|