jiti-meet/modules/UI/recording/Recording.js

520 lines
18 KiB
JavaScript
Raw Normal View History

/* global APP, $, config, interfaceConfig, JitsiMeetJS */
2016-03-29 18:10:31 +00:00
/*
* 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';
2016-04-26 21:38:07 +00:00
import VideoLayout from '../videolayout/VideoLayout';
2016-09-14 15:11:53 +00:00
import Feedback from '../feedback/Feedback.js';
2016-04-26 21:38:07 +00:00
import Toolbar from '../toolbars/Toolbar';
/**
* The dialog for user input.
*/
let dialog = null;
2016-03-29 18:10:31 +00:00
/**
* Indicates if the recording button should be enabled.
*
* @returns {boolean} {true} if the
* @private
2016-03-29 18:10:31 +00:00
*/
function _isRecordingButtonEnabled() {
return interfaceConfig.TOOLBAR_BUTTONS.indexOf("recording") !== -1
&& config.enableRecording && APP.conference.isRecordingSupported();
2016-03-29 18:10:31 +00:00
}
/**
* 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");
2016-03-29 18:10:31 +00:00
return new Promise(function (resolve, reject) {
dialog = APP.UI.messageHandler.openDialogWithStates({
state0: {
html:
`<h2>${msg}</h2>
<input name="streamId" type="text"
2016-03-29 18:10:31 +00:00
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;
}
2016-03-29 18:10:31 +00:00
}
},
state1: {
html: `<h2>${msg}</h2> ${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;
}
});
2016-03-29 18:10:31 +00:00
});
}
/**
* Request recording token from the user.
* @returns {Promise}
*/
function _requestRecordingToken () {
let msg = APP.translation.generateTranslationHTML("dialog.recordingToken");
let token = APP.translation.translateString("dialog.token");
return new Promise(function (resolve, reject) {
dialog = APP.UI.messageHandler.openTwoButtonDialog(
2016-03-29 18:10:31 +00:00
null, null, null,
`<h2>${msg}</h2>
<input name="recordingToken" type="text"
data-i18n="[placeholder]dialog.token"
placeholder="${token}" autofocus>`,
false, "dialog.Save",
function (e, v, m, f) {
if (v && f.recordingToken) {
resolve(UIUtil.escapeHtml(f.recordingToken));
} else {
reject(APP.UI.messageHandler.CANCEL);
2016-03-29 18:10:31 +00:00
}
},
null,
function () {
dialog = null;
},
2016-03-29 18:10:31 +00:00
':input:first'
);
});
}
/**
* Shows a prompt dialog to the user when they have toggled off the recording.
*
* @param recordingType the recording type
* @returns {Promise}
* @private
*/
2016-03-29 18:10:31 +00:00
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(
2016-03-29 18:10:31 +00:00
title,
null,
message,
null,
false,
buttonKey,
function(e,v) {
2016-03-29 18:10:31 +00:00
if (v) {
resolve();
} else {
reject();
}
},
null,
function () {
dialog = null;
2016-03-29 18:10:31 +00:00
}
);
});
}
/**
* 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
*/
2016-03-29 18:10:31 +00:00
function moveToCorner(selector, move) {
let moveToCornerClass = "moveToCorner";
let containsClass = selector.hasClass(moveToCornerClass);
2016-03-29 18:10:31 +00:00
if (move && !containsClass)
2016-03-29 18:10:31 +00:00
selector.addClass(moveToCornerClass);
else if (!move && containsClass)
2016-03-29 18:10:31 +00:00
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",
2016-05-05 16:14:27 +00:00
PENDING: "pending",
RETRYING: "retrying",
2016-06-02 16:30:45 +00:00
ERROR: "error",
FAILED: "failed",
BUSY: "busy"
2016-03-29 21:30:08 +00:00
};
/**
* 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}}
*/
2016-03-29 18:10:31 +00:00
var Recording = {
/**
* Initializes the recording UI.
*/
init (emitter, recordingType) {
this.eventEmitter = emitter;
this.updateRecordingState(APP.conference.getRecordingState());
2016-03-29 18:10:31 +00:00
this.initRecordingButton(recordingType);
2016-04-26 21:38:07 +00:00
// If I am a recorder then I publish my recorder custom role to notify
// everyone.
if (config.iAmRecorder) {
VideoLayout.enableDeviceAvailabilityIcons(
2016-07-08 01:44:04 +00:00
APP.conference.getMyUserId(), false);
2016-05-01 18:35:18 +00:00
VideoLayout.setLocalVideoVisible(false);
2016-04-26 21:38:07 +00:00
Feedback.enableFeedback(false);
Toolbar.enable(false);
APP.UI.messageHandler.enableNotifications(false);
APP.UI.messageHandler.enablePopups(false);
2016-04-26 21:38:07 +00:00
}
2016-03-29 18:10:31 +00:00
},
/**
* Initialise the recording button.
*/
2016-03-29 18:10:31 +00:00
initRecordingButton(recordingType) {
let selector = $('#toolbar_button_record');
2016-09-16 03:22:56 +00:00
let button = selector.get(0);
UIUtil.setTooltip(button, 'liveStreaming.buttonTooltip', 'right');
2016-03-29 18:10:31 +00:00
if (recordingType === 'jibri') {
this.baseClass = "fa fa-play-circle";
2016-06-02 16:30:45 +00:00
this.recordingTitle = "dialog.liveStreaming";
2016-03-29 18:10:31 +00:00
this.recordingOnKey = "liveStreaming.on";
this.recordingOffKey = "liveStreaming.off";
this.recordingPendingKey = "liveStreaming.pending";
this.failedToStartKey = "liveStreaming.failedToStart";
2016-05-05 16:14:27 +00:00
this.recordingErrorKey = "liveStreaming.error";
this.recordingButtonTooltip = "liveStreaming.buttonTooltip";
2016-06-02 16:30:45 +00:00
this.recordingUnavailable = "liveStreaming.unavailable";
this.recordingBusy = "liveStreaming.busy";
2016-03-29 18:10:31 +00:00
}
else {
this.baseClass = "icon-recEnable";
2016-06-02 16:30:45 +00:00
this.recordingTitle = "dialog.recording";
2016-03-29 18:10:31 +00:00
this.recordingOnKey = "recording.on";
this.recordingOffKey = "recording.off";
this.recordingPendingKey = "recording.pending";
this.failedToStartKey = "recording.failedToStart";
2016-05-05 16:14:27 +00:00
this.recordingErrorKey = "recording.error";
this.recordingButtonTooltip = "recording.buttonTooltip";
2016-06-02 16:30:45 +00:00
this.recordingUnavailable = "recording.unavailable";
this.recordingBusy = "liveStreaming.busy";
2016-03-29 18:10:31 +00:00
}
selector.addClass(this.baseClass);
selector.attr("data-i18n", "[content]" + this.recordingButtonTooltip);
selector.attr("content",
APP.translation.translateString(this.recordingButtonTooltip));
2016-03-29 18:10:31 +00:00
var self = this;
selector.click(function () {
if (dialog)
return;
JitsiMeetJS.analytics.sendEvent('recording.clicked');
2016-03-29 18:10:31 +00:00
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');
},
() => {});
2016-03-29 18:10:31 +00:00
break;
}
case Status.AVAILABLE:
case Status.OFF: {
2016-03-29 18:10:31 +00:00
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');
}
);
2016-03-29 18:10:31 +00:00
else {
if (self.predefinedToken) {
self.eventEmitter.emit( UIEvents.RECORDING_TOGGLED,
{token: self.predefinedToken});
JitsiMeetJS.analytics.sendEvent(
'recording.started');
2016-03-29 18:10:31 +00:00
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');
}
);
2016-03-29 18:10:31 +00:00
}
break;
}
2016-06-02 16:30:45 +00:00
case Status.BUSY: {
dialog = APP.UI.messageHandler.openMessageDialog(
2016-06-02 16:30:45 +00:00
self.recordingTitle,
self.recordingBusy,
null, null,
function () {
dialog = null;
}
2016-06-02 16:30:45 +00:00
);
break;
}
2016-03-29 18:10:31 +00:00
default: {
dialog = APP.UI.messageHandler.openMessageDialog(
2016-06-02 16:30:45 +00:00
self.recordingTitle,
self.recordingUnavailable,
null, null,
function () {
dialog = null;
}
2016-03-29 18:10:31 +00:00
);
}
}
});
},
/**
* Shows or hides the 'recording' button.
* @param show {true} to show the recording button, {false} to hide it
*/
2016-03-29 18:10:31 +00:00
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);
2016-03-29 18:10:31 +00:00
},
/**
* Sets the state of the recording button.
* @param recordingState gives us the current recording state
*/
updateRecordingUI (recordingState) {
2016-03-29 18:10:31 +00:00
let buttonSelector = $('#toolbar_button_record');
2016-06-02 16:30:45 +00:00
let oldState = this.currentState;
this.currentState = recordingState;
2016-03-29 18:10:31 +00:00
// TODO: handle recording state=available
if (recordingState === Status.ON ||
recordingState === Status.RETRYING) {
2016-03-29 18:10:31 +00:00
buttonSelector.removeClass(this.baseClass);
buttonSelector.addClass(this.baseClass + " active");
2016-05-05 16:14:27 +00:00
this._updateStatusLabel(this.recordingOnKey, false);
}
else if (recordingState === Status.OFF
2016-06-02 16:30:45 +00:00
|| recordingState === Status.UNAVAILABLE
|| recordingState === Status.BUSY
|| recordingState === Status.FAILED) {
// We don't want to do any changes if this is
// an availability change.
2016-06-02 16:30:45 +00:00
if (oldState !== Status.ON
&& !isStartingStatus(oldState))
return;
2016-03-29 18:10:31 +00:00
buttonSelector.removeClass(this.baseClass + " active");
buttonSelector.addClass(this.baseClass);
let messageKey;
if (isStartingStatus(oldState))
2016-03-29 18:10:31 +00:00
messageKey = this.failedToStartKey;
else
messageKey = this.recordingOffKey;
2016-05-05 16:14:27 +00:00
this._updateStatusLabel(messageKey, true);
2016-03-29 18:10:31 +00:00
setTimeout(function(){
$('#recordingLabel').css({display: "none"});
}, 5000);
}
else if (recordingState === Status.PENDING) {
2016-03-29 18:10:31 +00:00
buttonSelector.removeClass(this.baseClass + " active");
buttonSelector.addClass(this.baseClass);
2016-05-05 16:14:27 +00:00
this._updateStatusLabel(this.recordingPendingKey, true);
}
2016-06-02 16:30:45 +00:00
else if (recordingState === Status.ERROR
|| recordingState === Status.FAILED) {
2016-05-05 16:14:27 +00:00
buttonSelector.removeClass(this.baseClass + " active");
buttonSelector.addClass(this.baseClass);
this._updateStatusLabel(this.recordingErrorKey, true);
2016-03-29 18:10:31 +00:00
}
2016-05-05 16:14:27 +00:00
let labelSelector = $('#recordingLabel');
2016-03-29 21:30:08 +00:00
// We don't show the label for available state.
if (recordingState !== Status.AVAILABLE
&& !labelSelector.is(":visible"))
2016-03-29 18:10:31 +00:00
labelSelector.css({display: "inline-block"});
// Recording spinner
if (recordingState === Status.RETRYING)
$("#recordingSpinner").show();
else
$("#recordingSpinner").hide();
2016-03-29 18:10:31 +00:00
},
// 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);
}
2016-05-05 16:14:27 +00:00
},
/**
* 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');
2016-05-05 16:14:27 +00:00
moveToCorner(labelSelector, !isCentered);
labelTextSelector.attr("data-i18n", textKey);
labelTextSelector.text(APP.translation.translateString(textKey));
2016-03-29 18:10:31 +00:00
}
};
export default Recording;