You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Recording.js 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394
  1. /* global APP, $, config, interfaceConfig */
  2. /*
  3. * Copyright @ 2015 Atlassian Pty Ltd
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. import UIEvents from "../../../service/UI/UIEvents";
  18. import UIUtil from '../util/UIUtil';
  19. /**
  20. * Indicates if the recording button should be enabled.
  21. *
  22. * @returns {boolean} {true} if the
  23. * @private
  24. */
  25. function _isRecordingButtonEnabled() {
  26. return interfaceConfig.TOOLBAR_BUTTONS.indexOf("recording") !== -1
  27. && config.enableRecording;
  28. }
  29. /**
  30. * Request live stream token from the user.
  31. * @returns {Promise}
  32. */
  33. function _requestLiveStreamId() {
  34. const msg = APP.translation.generateTranslationHTML("dialog.liveStreaming");
  35. const token = APP.translation.translateString("dialog.streamKey");
  36. const cancelButton
  37. = APP.translation.generateTranslationHTML("dialog.Cancel");
  38. const backButton = APP.translation.generateTranslationHTML("dialog.Back");
  39. const startStreamingButton
  40. = APP.translation.generateTranslationHTML("dialog.startLiveStreaming");
  41. const streamIdRequired
  42. = APP.translation.generateTranslationHTML(
  43. "liveStreaming.streamIdRequired");
  44. return new Promise(function (resolve, reject) {
  45. let dialog = APP.UI.messageHandler.openDialogWithStates({
  46. state0: {
  47. html:
  48. `<h2>${msg}</h2>
  49. <input name="streamId" type="text"
  50. data-i18n="[placeholder]dialog.streamKey"
  51. placeholder="${token}" autofocus>`,
  52. persistent: false,
  53. buttons: [
  54. {title: cancelButton, value: false},
  55. {title: startStreamingButton, value: true}
  56. ],
  57. focus: ':input:first',
  58. defaultButton: 1,
  59. submit: function (e, v, m, f) {
  60. e.preventDefault();
  61. if (v) {
  62. if (f.streamId && f.streamId.length > 0) {
  63. resolve(UIUtil.escapeHtml(f.streamId));
  64. dialog.close();
  65. return;
  66. }
  67. else {
  68. dialog.goToState('state1');
  69. return false;
  70. }
  71. } else {
  72. reject();
  73. dialog.close();
  74. return false;
  75. }
  76. }
  77. },
  78. state1: {
  79. html: `<h2>${msg}</h2> ${streamIdRequired}`,
  80. persistent: false,
  81. buttons: [
  82. {title: cancelButton, value: false},
  83. {title: backButton, value: true}
  84. ],
  85. focus: ':input:first',
  86. defaultButton: 1,
  87. submit: function (e, v, m, f) {
  88. e.preventDefault();
  89. if (v === 0) {
  90. reject();
  91. dialog.close();
  92. } else {
  93. dialog.goToState('state0');
  94. }
  95. }
  96. }
  97. });
  98. });
  99. }
  100. /**
  101. * Request recording token from the user.
  102. * @returns {Promise}
  103. */
  104. function _requestRecordingToken () {
  105. let msg = APP.translation.generateTranslationHTML("dialog.recordingToken");
  106. let token = APP.translation.translateString("dialog.token");
  107. return new Promise(function (resolve, reject) {
  108. APP.UI.messageHandler.openTwoButtonDialog(
  109. null, null, null,
  110. `<h2>${msg}</h2>
  111. <input name="recordingToken" type="text"
  112. data-i18n="[placeholder]dialog.token"
  113. placeholder="${token}" autofocus>`,
  114. false, "dialog.Save",
  115. function (e, v, m, f) {
  116. if (v && f.recordingToken) {
  117. resolve(UIUtil.escapeHtml(f.recordingToken));
  118. } else {
  119. reject();
  120. }
  121. },
  122. null,
  123. function () { },
  124. ':input:first'
  125. );
  126. });
  127. }
  128. /**
  129. * Shows a prompt dialog to the user when they have toggled off the recording.
  130. *
  131. * @param recordingType the recording type
  132. * @returns {Promise}
  133. * @private
  134. */
  135. function _showStopRecordingPrompt (recordingType) {
  136. var title;
  137. var message;
  138. var buttonKey;
  139. if (recordingType === "jibri") {
  140. title = "dialog.liveStreaming";
  141. message = "dialog.stopStreamingWarning";
  142. buttonKey = "dialog.stopLiveStreaming";
  143. }
  144. else {
  145. title = "dialog.recording";
  146. message = "dialog.stopRecordingWarning";
  147. buttonKey = "dialog.stopRecording";
  148. }
  149. return new Promise(function (resolve, reject) {
  150. APP.UI.messageHandler.openTwoButtonDialog(
  151. title,
  152. null,
  153. message,
  154. null,
  155. false,
  156. buttonKey,
  157. function(e,v,m,f) {
  158. if (v) {
  159. resolve();
  160. } else {
  161. reject();
  162. }
  163. }
  164. );
  165. });
  166. }
  167. /**
  168. * Moves the element given by {selector} to the top right corner of the screen.
  169. * @param selector the selector for the element to move
  170. * @param move {true} to move the element, {false} to move it back to its intial
  171. * position
  172. */
  173. function moveToCorner(selector, move) {
  174. let moveToCornerClass = "moveToCorner";
  175. if (move && !selector.hasClass(moveToCornerClass))
  176. selector.addClass(moveToCornerClass);
  177. else
  178. selector.removeClass(moveToCornerClass);
  179. }
  180. /**
  181. * The status of the recorder.
  182. * FIXME: Those constants should come from the library.
  183. * @type {{ON: string, OFF: string, AVAILABLE: string,
  184. * UNAVAILABLE: string, PENDING: string}}
  185. */
  186. var Status = {
  187. ON: "on",
  188. OFF: "off",
  189. AVAILABLE: "available",
  190. UNAVAILABLE: "unavailable",
  191. PENDING: "pending"
  192. };
  193. /**
  194. * Manages the recording user interface and user experience.
  195. * @type {{init, initRecordingButton, showRecordingButton, updateRecordingState,
  196. * setRecordingButtonState, checkAutoRecord}}
  197. */
  198. var Recording = {
  199. /**
  200. * Initializes the recording UI.
  201. */
  202. init (emitter, recordingType) {
  203. this.eventEmitter = emitter;
  204. // Use recorder states directly from the library.
  205. this.currentState = Status.UNAVAILABLE;
  206. this.initRecordingButton(recordingType);
  207. },
  208. /**
  209. * Initialise the recording button.
  210. */
  211. initRecordingButton(recordingType) {
  212. let selector = $('#toolbar_button_record');
  213. if (recordingType === 'jibri') {
  214. this.baseClass = "fa fa-play-circle";
  215. this.recordingOnKey = "liveStreaming.on";
  216. this.recordingOffKey = "liveStreaming.off";
  217. this.recordingPendingKey = "liveStreaming.pending";
  218. this.failedToStartKey = "liveStreaming.failedToStart";
  219. this.recordingButtonTooltip = "liveStreaming.buttonTooltip";
  220. }
  221. else {
  222. this.baseClass = "icon-recEnable";
  223. this.recordingOnKey = "recording.on";
  224. this.recordingOffKey = "recording.off";
  225. this.recordingPendingKey = "recording.pending";
  226. this.failedToStartKey = "recording.failedToStart";
  227. this.recordingButtonTooltip = "recording.buttonTooltip";
  228. }
  229. selector.addClass(this.baseClass);
  230. selector.attr("data-i18n", "[content]" + this.recordingButtonTooltip);
  231. selector.attr("content",
  232. APP.translation.translateString(this.recordingButtonTooltip));
  233. var self = this;
  234. selector.click(function () {
  235. switch (self.currentState) {
  236. case Status.ON:
  237. case Status.PENDING: {
  238. _showStopRecordingPrompt(recordingType).then(() =>
  239. self.eventEmitter.emit(UIEvents.RECORDING_TOGGLED));
  240. break;
  241. }
  242. case Status.AVAILABLE:
  243. case Status.OFF: {
  244. if (recordingType === 'jibri')
  245. _requestLiveStreamId().then((streamId) => {
  246. self.eventEmitter.emit( UIEvents.RECORDING_TOGGLED,
  247. {streamId: streamId});
  248. });
  249. else {
  250. if (self.predefinedToken) {
  251. self.eventEmitter.emit( UIEvents.RECORDING_TOGGLED,
  252. {token: self.predefinedToken});
  253. return;
  254. }
  255. _requestRecordingToken().then((token) => {
  256. self.eventEmitter.emit( UIEvents.RECORDING_TOGGLED,
  257. {token: token});
  258. });
  259. }
  260. break;
  261. }
  262. default: {
  263. APP.UI.messageHandler.openMessageDialog(
  264. "dialog.liveStreaming",
  265. "liveStreaming.unavailable"
  266. );
  267. }
  268. }
  269. });
  270. },
  271. /**
  272. * Shows or hides the 'recording' button.
  273. * @param show {true} to show the recording button, {false} to hide it
  274. */
  275. showRecordingButton (show) {
  276. if (_isRecordingButtonEnabled() && show) {
  277. $('#toolbar_button_record').css({display: "inline-block"});
  278. } else {
  279. $('#toolbar_button_record').css({display: "none"});
  280. }
  281. },
  282. /**
  283. * Updates the recording state UI.
  284. * @param recordingState gives us the current recording state
  285. */
  286. updateRecordingState(recordingState) {
  287. // I'm the recorder, so I don't want to see any UI related to states.
  288. if (config.iAmRecorder)
  289. return;
  290. // If there's no state change, we ignore the update.
  291. if (this.currentState === recordingState)
  292. return;
  293. this.setRecordingButtonState(recordingState);
  294. },
  295. /**
  296. * Sets the state of the recording button.
  297. * @param recordingState gives us the current recording state
  298. */
  299. setRecordingButtonState (recordingState) {
  300. let buttonSelector = $('#toolbar_button_record');
  301. let labelSelector = $('#recordingLabel');
  302. // TODO: handle recording state=available
  303. if (recordingState === Status.ON) {
  304. buttonSelector.removeClass(this.baseClass);
  305. buttonSelector.addClass(this.baseClass + " active");
  306. labelSelector.attr("data-i18n", this.recordingOnKey);
  307. moveToCorner(labelSelector, true, 3000);
  308. labelSelector
  309. .text(APP.translation.translateString(this.recordingOnKey));
  310. } else if (recordingState === Status.OFF
  311. || recordingState === Status.UNAVAILABLE) {
  312. // We don't want to do any changes if this is
  313. // an availability change.
  314. if (this.currentState !== Status.ON
  315. && this.currentState !== Status.PENDING)
  316. return;
  317. buttonSelector.removeClass(this.baseClass + " active");
  318. buttonSelector.addClass(this.baseClass);
  319. moveToCorner(labelSelector, false);
  320. let messageKey;
  321. if (this.currentState === Status.PENDING)
  322. messageKey = this.failedToStartKey;
  323. else
  324. messageKey = this.recordingOffKey;
  325. labelSelector.attr("data-i18n", messageKey);
  326. labelSelector.text(APP.translation.translateString(messageKey));
  327. setTimeout(function(){
  328. $('#recordingLabel').css({display: "none"});
  329. }, 5000);
  330. }
  331. else if (recordingState === Status.PENDING) {
  332. buttonSelector.removeClass(this.baseClass + " active");
  333. buttonSelector.addClass(this.baseClass);
  334. moveToCorner(labelSelector, false);
  335. labelSelector
  336. .attr("data-i18n", this.recordingPendingKey);
  337. labelSelector
  338. .text(APP.translation.translateString(
  339. this.recordingPendingKey));
  340. }
  341. this.currentState = recordingState;
  342. // We don't show the label for available state.
  343. if (recordingState !== Status.AVAILABLE
  344. && !labelSelector.is(":visible"))
  345. labelSelector.css({display: "inline-block"});
  346. },
  347. // checks whether recording is enabled and whether we have params
  348. // to start automatically recording
  349. checkAutoRecord () {
  350. if (_isRecordingButtonEnabled && config.autoRecord) {
  351. this.predefinedToken = UIUtil.escapeHtml(config.autoRecordToken);
  352. this.eventEmitter.emit(UIEvents.RECORDING_TOGGLED,
  353. this.predefinedToken);
  354. }
  355. }
  356. };
  357. export default Recording;