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.

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