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.

897 lines
29 KiB

  1. /* global Marionette, Backbone, OCA */
  2. /**
  3. * @author Christoph Wurst <christoph@winzerhof-wurst.at>
  4. *
  5. * @license GNU AGPL version 3 or any later version
  6. *
  7. * This program is free software: you can redistribute it and/or modify
  8. * it under the terms of the GNU Affero General Public License as
  9. * published by the Free Software Foundation, either version 3 of the
  10. * License, or (at your option) any later version.
  11. *
  12. * This program is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU Affero General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU Affero General Public License
  18. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  19. *
  20. */
  21. (function(OC, OCA, Marionette, Backbone, _, $) {
  22. 'use strict';
  23. OCA.Talk = OCA.Talk || {};
  24. var roomChannel = Backbone.Radio.channel('rooms');
  25. OCA.Talk.Application = Marionette.Application.extend({
  26. OWNER: 1,
  27. MODERATOR: 2,
  28. USER: 3,
  29. GUEST: 4,
  30. USERSELFJOINED: 5,
  31. /** @property {OCA.SpreedMe.Models.Room} activeRoom */
  32. activeRoom: null,
  33. /** @property {String} token */
  34. token: null,
  35. /** @property {OCA.Talk.Connection} connection */
  36. connection: null,
  37. /** @property {OCA.Talk.Signaling.base} signaling */
  38. signaling: null,
  39. /** @property {OCA.SpreedMe.Models.RoomCollection} _rooms */
  40. _rooms: null,
  41. /** @property {OCA.SpreedMe.Views.RoomListView} _roomsView */
  42. _roomsView: null,
  43. /** @property {OCA.SpreedMe.Models.ParticipantCollection} _participants */
  44. _participants: null,
  45. /** @property {OCA.SpreedMe.Views.ParticipantView} _participantsView */
  46. _participantsView: null,
  47. /** @property {boolean} videoWasEnabledAtLeastOnce */
  48. videoWasEnabledAtLeastOnce: false,
  49. displayedGuestNameHint: false,
  50. audioDisabled: localStorage.getItem("audioDisabled"),
  51. audioNotFound: false,
  52. videoDisabled: localStorage.getItem("videoDisabled"),
  53. videoNotFound: false,
  54. fullscreenDisabled: true,
  55. _searchTerm: '',
  56. guestNick: null,
  57. _currentEmptyContent: null,
  58. _lastEmptyContent: null,
  59. _registerPageEvents: function() {
  60. $('#select-participants').select2({
  61. ajax: {
  62. url: OC.linkToOCS('apps/files_sharing/api/v1') + 'sharees',
  63. dataType: 'json',
  64. quietMillis: 100,
  65. data: function (term) {
  66. this._searchTerm = term;
  67. return {
  68. format: 'json',
  69. search: term,
  70. perPage: 20,
  71. itemType: 'call'
  72. };
  73. },
  74. results: function (response) {
  75. // TODO improve error case
  76. if (response.ocs.data === undefined) {
  77. console.error('Failure happened', response);
  78. return;
  79. }
  80. var results = [];
  81. $.each(response.ocs.data.exact.users, function(id, user) {
  82. if (OC.getCurrentUser().uid === user.value.shareWith) {
  83. return;
  84. }
  85. results.push({ id: user.value.shareWith, displayName: user.label, type: "user"});
  86. });
  87. $.each(response.ocs.data.exact.groups, function(id, group) {
  88. results.push({ id: group.value.shareWith, displayName: group.label + ' ' + t('spreed', '(group)'), type: "group"});
  89. });
  90. $.each(response.ocs.data.users, function(id, user) {
  91. if (OC.getCurrentUser().uid === user.value.shareWith) {
  92. return;
  93. }
  94. results.push({ id: user.value.shareWith, displayName: user.label, type: "user"});
  95. });
  96. $.each(response.ocs.data.groups, function(id, group) {
  97. results.push({ id: group.value.shareWith, displayName: group.label + ' ' + t('spreed', '(group)'), type: "group"});
  98. });
  99. //Add custom entry to create a new empty group or public room
  100. if (OCA.SpreedMe.app._searchTerm === '') {
  101. results.unshift({ id: "create-public-room", displayName: t('spreed', 'New public conversation'), type: "createPublicRoom"});
  102. results.unshift({ id: "create-group-room", displayName: t('spreed', 'New group conversation'), type: "createGroupRoom"});
  103. } else {
  104. results.push({ id: "create-group-room", displayName: t('spreed', 'New group conversation'), type: "createGroupRoom"});
  105. results.push({ id: "create-public-room", displayName: t('spreed', 'New public conversation'), type: "createPublicRoom"});
  106. }
  107. return {
  108. results: results,
  109. more: false
  110. };
  111. }
  112. },
  113. initSelection: function (element, callback) {
  114. callback({id: element.val()});
  115. },
  116. formatResult: function (element) {
  117. if ((element.type === "createGroupRoom") || (element.type === "createPublicRoom")) {
  118. return '<span><div class="avatar icon-add"></div>' + escapeHTML(element.displayName) + '</span>';
  119. }
  120. return '<span><div class="avatar" data-user="' + escapeHTML(element.id) + '" data-user-display-name="' + escapeHTML(element.displayName) + '"></div>' + escapeHTML(element.displayName) + '</span>';
  121. },
  122. formatSelection: function () {
  123. return '<span class="select2-default" style="padding-left: 0;">' + t('spreed', 'New conversation …') + '</span>';
  124. }
  125. });
  126. $('#select-participants').on("click", function() {
  127. $('.select2-drop').find('.avatar').each(function () {
  128. var element = $(this);
  129. if (element.data('user-display-name')) {
  130. element.avatar(element.data('user'), 32, undefined, false, undefined, element.data('user-display-name'));
  131. } else {
  132. element.avatar(element.data('user'), 32);
  133. }
  134. });
  135. });
  136. $('#select-participants').on("select2-selecting", function(e) {
  137. switch (e.object.type) {
  138. case "user":
  139. this.connection.createOneToOneVideoCall(e.val);
  140. break;
  141. case "group":
  142. this.connection.createGroupVideoCall(e.val, "");
  143. break;
  144. case "createPublicRoom":
  145. this.connection.createPublicVideoCall(OCA.SpreedMe.app._searchTerm);
  146. break;
  147. case "createGroupRoom":
  148. this.connection.createGroupVideoCall("", OCA.SpreedMe.app._searchTerm);
  149. break;
  150. default:
  151. console.log("Unknown type", e.object.type);
  152. break;
  153. }
  154. }.bind(this));
  155. $('#select-participants').on("select2-loaded", function() {
  156. $('.select2-drop').find('.avatar').each(function () {
  157. var element = $(this);
  158. if (element.data('user-display-name')) {
  159. element.avatar(element.data('user'), 32, undefined, false, undefined, element.data('user-display-name'));
  160. } else {
  161. element.avatar(element.data('user'), 32);
  162. }
  163. });
  164. });
  165. // Initialize button tooltips
  166. $('[data-toggle="tooltip"]').tooltip({trigger: 'hover'}).click(function() {
  167. $(this).tooltip('hide');
  168. });
  169. $('#hideVideo').click(function() {
  170. if(!OCA.SpreedMe.app.videoWasEnabledAtLeastOnce) {
  171. // don't allow clicking the video toggle
  172. // when no video ever was streamed (that
  173. // means that permission wasn't granted
  174. // yet or there is no video available at
  175. // all)
  176. console.log('video can not be enabled - there was no stream available before');
  177. return;
  178. }
  179. if ($(this).hasClass('video-disabled')) {
  180. OCA.SpreedMe.app.enableVideo();
  181. localStorage.removeItem("videoDisabled");
  182. } else {
  183. OCA.SpreedMe.app.disableVideo();
  184. localStorage.setItem("videoDisabled", true);
  185. }
  186. });
  187. $('#mute').click(function() {
  188. if (OCA.SpreedMe.webrtc.webrtc.isAudioEnabled()) {
  189. OCA.SpreedMe.app.disableAudio();
  190. localStorage.setItem("audioDisabled", true);
  191. } else {
  192. OCA.SpreedMe.app.enableAudio();
  193. localStorage.removeItem("audioDisabled");
  194. }
  195. });
  196. $('#video-fullscreen').click(function() {
  197. if (this.fullscreenDisabled) {
  198. this.enableFullscreen();
  199. } else {
  200. this.disableFullscreen();
  201. }
  202. }.bind(this));
  203. $('#screensharing-button').click(function() {
  204. var webrtc = OCA.SpreedMe.webrtc;
  205. if (!webrtc.capabilities.supportScreenSharing) {
  206. if (window.location.protocol === 'https:') {
  207. OC.Notification.showTemporary(t('spreed', 'Screensharing is not supported by your browser.'));
  208. } else {
  209. OC.Notification.showTemporary(t('spreed', 'Screensharing requires the page to be loaded through HTTPS.'));
  210. }
  211. return;
  212. }
  213. if (webrtc.getLocalScreen()) {
  214. $('#screensharing-menu').toggleClass('open');
  215. } else {
  216. var screensharingButton = $(this);
  217. screensharingButton.prop('disabled', true);
  218. webrtc.shareScreen(function(err) {
  219. screensharingButton.prop('disabled', false);
  220. if (!err) {
  221. $('#screensharing-button').attr('data-original-title', t('spreed', 'Screensharing options'))
  222. .removeClass('screensharing-disabled icon-screen-off')
  223. .addClass('icon-screen');
  224. return;
  225. }
  226. switch (err.name) {
  227. case "HTTPS_REQUIRED":
  228. OC.Notification.showTemporary(t('spreed', 'Screensharing requires the page to be loaded through HTTPS.'));
  229. break;
  230. case "PERMISSION_DENIED":
  231. case "NotAllowedError":
  232. case "CEF_GETSCREENMEDIA_CANCELED": // Experimental, may go away in the future.
  233. break;
  234. case "FF52_REQUIRED":
  235. OC.Notification.showTemporary(t('spreed', 'Sharing your screen only works with Firefox version 52 or newer.'));
  236. break;
  237. case "EXTENSION_UNAVAILABLE":
  238. var extensionURL = null;
  239. if (!!window.chrome && !!window.chrome.webstore) {// Chrome
  240. extensionURL = 'https://chrome.google.com/webstore/detail/screensharing-for-nextclo/kepnpjhambipllfmgmbapncekcmabkol';
  241. }
  242. if (extensionURL) {
  243. var text = t('spreed', 'Screensharing extension is required to share your screen.');
  244. var element = $('<a>').attr('href', extensionURL).attr('target','_blank').text(text);
  245. OC.Notification.showTemporary(element, {isHTML: true});
  246. } else {
  247. OC.Notification.showTemporary(t('spreed', 'Please use a different browser like Firefox or Chrome to share your screen.'));
  248. }
  249. break;
  250. default:
  251. OC.Notification.showTemporary(t('spreed', 'An error occurred while starting screensharing.'));
  252. console.log("Could not start screensharing", err);
  253. break;
  254. }
  255. });
  256. }
  257. });
  258. $("#show-screen-button").on('click', function() {
  259. var currentUser = OCA.SpreedMe.webrtc.connection.getSessionid();
  260. OCA.SpreedMe.sharedScreens.switchScreenToId(currentUser);
  261. $('#screensharing-menu').toggleClass('open', false);
  262. });
  263. $("#stop-screen-button").on('click', function() {
  264. OCA.SpreedMe.webrtc.stopScreenShare();
  265. });
  266. $(document).keyup(this._onKeyUp.bind(this));
  267. },
  268. _onKeyUp: function(event) {
  269. // Define which objects to check for the event properties.
  270. var key = event.which;
  271. // Trigger the event only if no input or textarea is focused
  272. // and the CTRL key is not pressed
  273. if ($('input:focus').length === 0 &&
  274. $('textarea:focus').length === 0 &&
  275. $('div[contenteditable=true]:focus').length === 0 &&
  276. !event.ctrlKey) {
  277. // Actual shortcut handling
  278. switch (key) {
  279. case 86: // 'v'
  280. event.preventDefault();
  281. if (this.videoDisabled) {
  282. this.enableVideo();
  283. } else {
  284. this.disableVideo();
  285. }
  286. break;
  287. case 77: // 'm'
  288. event.preventDefault();
  289. if (this.audioDisabled) {
  290. this.enableAudio();
  291. } else {
  292. this.disableAudio();
  293. }
  294. break;
  295. case 70: // 'f'
  296. event.preventDefault();
  297. if (this.fullscreenDisabled) {
  298. this.enableFullscreen();
  299. } else {
  300. this.disableFullscreen();
  301. }
  302. break;
  303. case 67: // 'c'
  304. event.preventDefault();
  305. this._sidebarView.selectTab('chat');
  306. break;
  307. case 80: // 'p'
  308. event.preventDefault();
  309. this._sidebarView.selectTab('participants');
  310. break;
  311. }
  312. }
  313. },
  314. _showRoomList: function() {
  315. this._roomsView = new OCA.SpreedMe.Views.RoomListView({
  316. el: '#app-navigation ul',
  317. collection: this._rooms
  318. });
  319. },
  320. _showParticipantList: function() {
  321. this._participants = new OCA.SpreedMe.Models.ParticipantCollection();
  322. this._participantsView = new OCA.SpreedMe.Views.ParticipantView({
  323. room: this.activeRoom,
  324. collection: this._participants,
  325. id: 'participantsTabView'
  326. });
  327. this.signaling.on('usersChanged', function() {
  328. // Also refresh the participant list when the users change
  329. this._participants.fetch();
  330. }.bind(this));
  331. this._participantsView.listenTo(this._rooms, 'change:active', function(model, active) {
  332. if (active) {
  333. this.setRoom(model);
  334. }
  335. });
  336. this._sidebarView.addTab('participants', { label: t('spreed', 'Participants'), icon: 'icon-contacts-dark' }, this._participantsView);
  337. },
  338. /**
  339. * @param {string} token
  340. */
  341. _setRoomActive: function(token) {
  342. if (OC.getCurrentUser().uid) {
  343. this._rooms.forEach(function(room) {
  344. room.set('active', room.get('token') === token);
  345. });
  346. }
  347. },
  348. addParticipantToRoom: function(token, participant) {
  349. $.post(
  350. OC.linkToOCS('apps/spreed/api/v1/room', 2) + token + '/participants',
  351. {
  352. newParticipant: participant
  353. }
  354. ).done(function() {
  355. this.signaling.syncRooms();
  356. }.bind(this));
  357. },
  358. syncAndSetActiveRoom: function(token) {
  359. var self = this;
  360. this.signaling.syncRooms()
  361. .then(function() {
  362. self.stopListening(self.activeRoom, 'change:participantInCall');
  363. if (OC.getCurrentUser().uid) {
  364. roomChannel.trigger('active', token);
  365. self._rooms.forEach(function(room) {
  366. if (room.get('token') === token) {
  367. self.activeRoom = room;
  368. }
  369. });
  370. } else {
  371. // The public page supports only a single room, so the
  372. // active room is already the room for the given token.
  373. self.setRoomMessageForGuest(self.activeRoom.get('participants'));
  374. }
  375. // Disable video when entering a room with more than 5 participants.
  376. if (Object.keys(self.activeRoom.get('participants')).length > 5) {
  377. self.disableVideo();
  378. }
  379. self.setPageTitle(self.activeRoom.get('displayName'));
  380. self.updateChatViewPlacement();
  381. self.listenTo(self.activeRoom, 'change:participantInCall', self.updateChatViewPlacement);
  382. self.updateSidebarWithActiveRoom();
  383. });
  384. },
  385. updateChatViewPlacement: function() {
  386. if (!this.activeRoom) {
  387. // This should never happen, but just in case
  388. return;
  389. }
  390. if (this.activeRoom.get('participantInCall') && this._chatViewInMainView === true) {
  391. this._chatView.saveScrollPosition();
  392. this._chatView.$el.detach();
  393. this._sidebarView.addTab('chat', { label: t('spreed', 'Chat'), icon: 'icon-comment', priority: 100 }, this._chatView);
  394. this._sidebarView.selectTab('chat');
  395. this._chatView.restoreScrollPosition();
  396. this._chatView.setTooltipContainer(this._chatView.$el);
  397. this._chatViewInMainView = false;
  398. } else if (!this.activeRoom.get('participantInCall') && !this._chatViewInMainView) {
  399. this._chatView.saveScrollPosition();
  400. this._sidebarView.removeTab('chat');
  401. this._chatView.$el.prependTo('#app-content-wrapper');
  402. this._chatView.restoreScrollPosition();
  403. this._chatView.setTooltipContainer($('#app'));
  404. this._chatView.focusChatInput();
  405. this._chatViewInMainView = true;
  406. }
  407. },
  408. updateSidebarWithActiveRoom: function() {
  409. this._sidebarView.enable();
  410. // The sidebar has a width of 27% of the window width and a minimum
  411. // width of 300px. Therefore, when the window is 1111px wide or
  412. // narrower the sidebar will always be 300px wide, and when that
  413. // happens it will overlap with the content area (the narrower the
  414. // window the larger the overlap). Due to this the sidebar is opened
  415. // automatically only if it will not overlap with the content area.
  416. if ($(window).width() > 1111) {
  417. this._sidebarView.open();
  418. }
  419. var callInfoView = new OCA.SpreedMe.Views.CallInfoView({
  420. model: this.activeRoom,
  421. guestNameModel: this._localStorageModel
  422. });
  423. this._sidebarView.setCallInfoView(callInfoView);
  424. this._messageCollection.setRoomToken(this.activeRoom.get('token'));
  425. this._messageCollection.receiveMessages();
  426. },
  427. setPageTitle: function(title){
  428. if (title) {
  429. title += ' - ';
  430. } else {
  431. title = '';
  432. }
  433. title += t('spreed', 'Talk');
  434. title += ' - ' + oc_defaults.title;
  435. window.document.title = title;
  436. },
  437. /**
  438. *
  439. * @param {string|Object} icon
  440. * @param {string} icon.userId
  441. * @param {string} icon.displayName
  442. * @param {string} message
  443. * @param {string} [messageAdditional]
  444. * @param {string} [url]
  445. */
  446. setEmptyContentMessage: function(icon, message, messageAdditional, url) {
  447. var $icon = $('#emptycontent-icon'),
  448. $emptyContent = $('#emptycontent');
  449. //Remove previous icon and avatar from emptycontent
  450. $icon.removeAttr('class').attr('class', '');
  451. $icon.html('');
  452. if (url) {
  453. $('#shareRoomInput').removeClass('hidden').val(url);
  454. $('#shareRoomClipboardButton').removeClass('hidden');
  455. } else {
  456. $('#shareRoomInput').addClass('hidden');
  457. $('#shareRoomClipboardButton').addClass('hidden');
  458. }
  459. if (typeof icon === 'string') {
  460. $icon.addClass(icon);
  461. } else {
  462. var $avatar = $('<div>');
  463. $avatar.addClass('avatar room-avatar');
  464. if (icon.userId !== icon.displayName) {
  465. $avatar.avatar(icon.userId, 128, undefined, false, undefined, icon.displayName);
  466. } else {
  467. $avatar.avatar(icon.userId, 128);
  468. }
  469. $icon.append($avatar);
  470. }
  471. $emptyContent.find('h2').html(message);
  472. $emptyContent.find('p').text(messageAdditional ? messageAdditional : '');
  473. this._lastEmptyContent = this._currentEmptyContent;
  474. this._currentEmptyContent = arguments;
  475. },
  476. restoreEmptyContent: function() {
  477. this.setEmptyContentMessage.apply(this, this._lastEmptyContent);
  478. },
  479. setRoomMessageForGuest: function(participants) {
  480. if (Object.keys(participants).length === 1) {
  481. var participantId = '',
  482. participantName = '';
  483. _.each(participants, function(data, userId) {
  484. if (OC.getCurrentUser().uid !== userId) {
  485. participantId = userId;
  486. participantName = data.name;
  487. }
  488. });
  489. OCA.SpreedMe.app.setEmptyContentMessage(
  490. { userId: participantId, displayName: participantName},
  491. t('spreed', 'Waiting for {participantName} to join the call …', {participantName: participantName})
  492. );
  493. } else {
  494. OCA.SpreedMe.app.setEmptyContentMessage('icon-contacts-dark', t('spreed', 'Waiting for others to join the call …'));
  495. }
  496. },
  497. initialize: function() {
  498. this._sidebarView = new OCA.SpreedMe.Views.SidebarView();
  499. $('#app-content').append(this._sidebarView.$el);
  500. if (OC.getCurrentUser().uid) {
  501. this._rooms = new OCA.SpreedMe.Models.RoomCollection();
  502. this.listenTo(roomChannel, 'active', this._setRoomActive);
  503. } else {
  504. this.initGuestName();
  505. }
  506. this._sidebarView.listenTo(roomChannel, 'leaveCurrentRoom', function() {
  507. this.disable();
  508. });
  509. this._messageCollection = new OCA.SpreedMe.Models.ChatMessageCollection(null, {token: null});
  510. this._chatView = new OCA.SpreedMe.Views.ChatView({
  511. collection: this._messageCollection,
  512. id: 'commentsTabView',
  513. guestNameModel: this._localStorageModel
  514. });
  515. this._messageCollection.listenTo(roomChannel, 'leaveCurrentRoom', function() {
  516. this.stopReceivingMessages();
  517. });
  518. this.listenTo(roomChannel, 'leaveCurrentRoom', function() {
  519. this._chatView.$el.detach();
  520. this._chatViewInMainView = false;
  521. });
  522. $(document).on('click', this.onDocumentClick);
  523. OC.Util.History.addOnPopStateHandler(_.bind(this._onPopState, this));
  524. },
  525. onStart: function() {
  526. this.signaling = OCA.Talk.Signaling.createConnection();
  527. this.connection = new OCA.Talk.Connection(this);
  528. this.token = $('#app').attr('data-token');
  529. $(window).unload(function () {
  530. this.connection.leaveCurrentRoom(false);
  531. this.signaling.disconnect();
  532. }.bind(this));
  533. if (OC.getCurrentUser().uid) {
  534. this._showRoomList();
  535. this.signaling.setRoomCollection(this._rooms)
  536. .then(function(data) {
  537. $('#app-navigation').removeClass('icon-loading');
  538. this._roomsView.render();
  539. if (data.length === 0) {
  540. $('#select-participants').select2('open');
  541. }
  542. }.bind(this));
  543. this._showParticipantList();
  544. } else {
  545. // The token is always defined in the public page.
  546. this.activeRoom = new OCA.SpreedMe.Models.Room({ token: this.token });
  547. this.signaling.setRoom(this.activeRoom);
  548. }
  549. this._registerPageEvents();
  550. this.initShareRoomClipboard();
  551. if (this.token) {
  552. this.connection.joinRoom(this.token);
  553. }
  554. },
  555. setupWebRTC: function() {
  556. if (!OCA.SpreedMe.webrtc) {
  557. OCA.SpreedMe.initWebRTC(this);
  558. }
  559. OCA.SpreedMe.webrtc.startMedia(this.token);
  560. },
  561. startLocalMedia: function(configuration) {
  562. if (this.callbackAfterMedia) {
  563. this.callbackAfterMedia();
  564. this.callbackAfterMedia = null;
  565. }
  566. $('.videoView').removeClass('hidden');
  567. this.initAudioVideoSettings(configuration);
  568. this.restoreEmptyContent();
  569. },
  570. startWithoutLocalMedia: function(isAudioEnabled, isVideoEnabled) {
  571. if (this.callbackAfterMedia) {
  572. this.callbackAfterMedia();
  573. this.callbackAfterMedia = null;
  574. }
  575. $('.videoView').removeClass('hidden');
  576. this.disableAudio();
  577. if (!isAudioEnabled) {
  578. this.hasNoAudio();
  579. }
  580. this.disableVideo();
  581. if (!isVideoEnabled) {
  582. this.hasNoVideo();
  583. }
  584. },
  585. _onPopState: function(params) {
  586. if (!_.isUndefined(params.token)) {
  587. this.connection.joinRoom(params.token);
  588. }
  589. },
  590. onDocumentClick: function(event) {
  591. var uiChannel = Backbone.Radio.channel('ui');
  592. uiChannel.trigger('document:click', event);
  593. },
  594. initAudioVideoSettings: function(configuration) {
  595. if (this.audioDisabled) {
  596. this.disableAudio();
  597. }
  598. if (configuration.video !== false) {
  599. if (this.videoDisabled) {
  600. this.disableVideo();
  601. }
  602. } else {
  603. this.videoWasEnabledAtLeastOnce = false;
  604. this.disableVideo();
  605. }
  606. },
  607. enableFullscreen: function() {
  608. var fullscreenElem = document.getElementById('app-content');
  609. if (fullscreenElem.requestFullscreen) {
  610. fullscreenElem.requestFullscreen();
  611. } else if (fullscreenElem.webkitRequestFullscreen) {
  612. fullscreenElem.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
  613. } else if (fullscreenElem.mozRequestFullScreen) {
  614. fullscreenElem.mozRequestFullScreen();
  615. } else if (fullscreenElem.msRequestFullscreen) {
  616. fullscreenElem.msRequestFullscreen();
  617. }
  618. $('#video-fullscreen').attr('data-original-title', t('spreed', 'Exit fullscreen (f)'));
  619. this.fullscreenDisabled = false;
  620. },
  621. disableFullscreen: function() {
  622. if (document.exitFullscreen) {
  623. document.exitFullscreen();
  624. } else if (document.webkitExitFullscreen) {
  625. document.webkitExitFullscreen();
  626. } else if (document.mozCancelFullScreen) {
  627. document.mozCancelFullScreen();
  628. } else if (document.msExitFullscreen) {
  629. document.msExitFullscreen();
  630. }
  631. $('#video-fullscreen').attr('data-original-title', t('spreed', 'Fullscreen (f)'));
  632. this.fullscreenDisabled = true;
  633. },
  634. enableAudio: function() {
  635. if (this.audioNotFound || !OCA.SpreedMe.webrtc) {
  636. return;
  637. }
  638. OCA.SpreedMe.webrtc.unmute();
  639. $('#mute').attr('data-original-title', t('spreed', 'Mute audio (m)'))
  640. .removeClass('audio-disabled icon-audio-off')
  641. .addClass('icon-audio');
  642. this.audioDisabled = false;
  643. },
  644. disableAudio: function() {
  645. if (this.audioNotFound || !OCA.SpreedMe.webrtc) {
  646. return;
  647. }
  648. OCA.SpreedMe.webrtc.mute();
  649. $('#mute').attr('data-original-title', t('spreed', 'Unmute audio (m)'))
  650. .addClass('audio-disabled icon-audio-off')
  651. .removeClass('icon-audio');
  652. this.audioDisabled = true;
  653. },
  654. hasNoAudio: function() {
  655. $('#mute').removeClass('audio-disabled icon-audio')
  656. .addClass('no-audio-available icon-audio-off')
  657. .attr('data-original-title', t('spreed', 'No audio'));
  658. this.audioDisabled = true;
  659. this.audioNotFound = true;
  660. },
  661. enableVideo: function() {
  662. if (this.videoNotFound || !OCA.SpreedMe.webrtc) {
  663. return;
  664. }
  665. var $hideVideoButton = $('#hideVideo');
  666. var $audioMuteButton = $('#mute');
  667. var $screensharingButton = $('#screensharing-button');
  668. var avatarContainer = $hideVideoButton.closest('.videoView').find('.avatar-container');
  669. var localVideo = $hideVideoButton.closest('.videoView').find('#localVideo');
  670. OCA.SpreedMe.webrtc.resumeVideo();
  671. $hideVideoButton.attr('data-original-title', t('spreed', 'Disable video (v)'))
  672. .removeClass('video-disabled icon-video-off')
  673. .addClass('icon-video');
  674. $audioMuteButton.removeClass('video-disabled');
  675. $screensharingButton.removeClass('video-disabled');
  676. avatarContainer.hide();
  677. localVideo.show();
  678. this.videoDisabled = false;
  679. },
  680. hideVideo: function() {
  681. var $hideVideoButton = $('#hideVideo');
  682. var $audioMuteButton = $('#mute');
  683. var $screensharingButton = $('#screensharing-button');
  684. var avatarContainer = $hideVideoButton.closest('.videoView').find('.avatar-container');
  685. var localVideo = $hideVideoButton.closest('.videoView').find('#localVideo');
  686. if (!$hideVideoButton.hasClass('no-video-available')) {
  687. $hideVideoButton.attr('data-original-title', t('spreed', 'Enable video (v)'))
  688. .addClass('video-disabled icon-video-off')
  689. .removeClass('icon-video');
  690. $audioMuteButton.addClass('video-disabled');
  691. $screensharingButton.addClass('video-disabled');
  692. }
  693. var avatar = avatarContainer.find('.avatar');
  694. var guestName = localStorage.getItem("nick");
  695. if (OC.getCurrentUser().uid) {
  696. avatar.avatar(OC.getCurrentUser().uid, 128);
  697. } else {
  698. avatar.imageplaceholder('?', guestName, 128);
  699. avatar.css('background-color', '#b9b9b9');
  700. if (this.displayedGuestNameHint === false) {
  701. OC.Notification.showTemporary(t('spreed', 'You can set your name on the right sidebar so other participants can identify you better.'));
  702. this.displayedGuestNameHint = true;
  703. }
  704. }
  705. avatarContainer.removeClass('hidden');
  706. avatarContainer.show();
  707. localVideo.hide();
  708. },
  709. disableVideo: function() {
  710. if (this.videoNotFound || !OCA.SpreedMe.webrtc) {
  711. return;
  712. }
  713. OCA.SpreedMe.webrtc.pauseVideo();
  714. this.hideVideo();
  715. this.videoDisabled = true;
  716. },
  717. hasNoVideo: function() {
  718. $('#hideVideo').removeClass('icon-video')
  719. .addClass('no-video-available icon-video-off')
  720. .attr('data-original-title', t('spreed', 'No Camera'));
  721. this.videoDisabled = true;
  722. this.videoNotFound = true;
  723. },
  724. disableScreensharingButton: function() {
  725. $('#screensharing-button').attr('data-original-title', t('spreed', 'Enable screensharing'))
  726. .addClass('screensharing-disabled icon-screen-off')
  727. .removeClass('icon-screen');
  728. $('#screensharing-menu').toggleClass('open', false);
  729. },
  730. initGuestName: function() {
  731. var self = this;
  732. this._localStorageModel = new OCA.SpreedMe.Models.LocalStorageModel({ nick: '' });
  733. this._localStorageModel.on('change:nick', function(model, newDisplayName) {
  734. $.ajax({
  735. url: OC.linkToOCS('apps/spreed/api/v1/guest', 2) + this.token + '/name',
  736. type: 'POST',
  737. data: {
  738. displayName: newDisplayName
  739. },
  740. beforeSend: function (request) {
  741. request.setRequestHeader('Accept', 'application/json');
  742. },
  743. success: function() {
  744. self._onChangeGuestName(newDisplayName);
  745. }
  746. });
  747. }.bind(this));
  748. this._localStorageModel.fetch();
  749. },
  750. _onChangeGuestName: function(newDisplayName) {
  751. var avatar = $('#localVideoContainer').find('.avatar');
  752. avatar.imageplaceholder('?', newDisplayName, 128);
  753. avatar.css('background-color', '#b9b9b9');
  754. if (OCA.SpreedMe.webrtc) {
  755. console.log('_onChangeGuestName.webrtc');
  756. OCA.SpreedMe.webrtc.sendDirectlyToAll('status', 'nickChanged', newDisplayName);
  757. }
  758. },
  759. initShareRoomClipboard: function () {
  760. $('body').find('.shareRoomClipboard').tooltip({
  761. placement: 'bottom',
  762. trigger: 'hover',
  763. title: t('core', 'Copy')
  764. });
  765. var clipboard = new Clipboard('.shareRoomClipboard');
  766. clipboard.on('success', function(e) {
  767. var $input = $(e.trigger);
  768. $input.tooltip('hide')
  769. .attr('data-original-title', t('core', 'Copied!'))
  770. .tooltip('fixTitle')
  771. .tooltip({placement: 'bottom', trigger: 'manual'})
  772. .tooltip('show');
  773. _.delay(function() {
  774. $input.tooltip('hide')
  775. .attr('data-original-title', t('core', 'Copy'))
  776. .tooltip('fixTitle');
  777. }, 3000);
  778. });
  779. clipboard.on('error', function (e) {
  780. var $input = $(e.trigger);
  781. var actionMsg = '';
  782. if (/iPhone|iPad/i.test(navigator.userAgent)) {
  783. actionMsg = t('core', 'Not supported!');
  784. } else if (/Mac/i.test(navigator.userAgent)) {
  785. actionMsg = t('core', 'Press ⌘-C to copy.');
  786. } else {
  787. actionMsg = t('core', 'Press Ctrl-C to copy.');
  788. }
  789. $input.tooltip('hide')
  790. .attr('data-original-title', actionMsg)
  791. .tooltip('fixTitle')
  792. .tooltip({placement: 'bottom', trigger: 'manual'})
  793. .tooltip('show');
  794. _.delay(function () {
  795. $input.tooltip('hide')
  796. .attr('data-original-title', t('spreed', 'Copy'))
  797. .tooltip('fixTitle');
  798. }, 3000);
  799. });
  800. }
  801. });
  802. })(OC, OCA, Marionette, Backbone, _, $);