From 9803cd012e86b3468fec359de1e761114957d516 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Calvi=C3=B1o=20S=C3=A1nchez?= Date: Thu, 29 Nov 2018 04:39:59 +0100 Subject: [PATCH 1/8] Replace "videoWasEnabledAtLeastOnce" with "videoNotFound" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "videoWasEnabledAtLeastOnce" was used to allow or prevent the handling of clicks on the video button. However, it was enabled in the handler for the "localstreams" event, so it was being enabled even if there was no video (although it was disabled as needed later during the handling of the "localMediaStarted" event). In any case, its purpose can be covered by "videoNotFound", so it was removed in favour of the last. Signed-off-by: Daniel Calviño Sánchez --- js/app.js | 13 +++---------- js/webrtc.js | 3 --- 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/js/app.js b/js/app.js index 0fec59ce6..9b6eca7e5 100644 --- a/js/app.js +++ b/js/app.js @@ -73,8 +73,6 @@ _participants: null, /** @property {OCA.SpreedMe.Views.ParticipantView} _participantsView */ _participantsView: null, - /** @property {boolean} videoWasEnabledAtLeastOnce */ - videoWasEnabledAtLeastOnce: false, displayedGuestNameHint: false, audioDisabled: localStorage.getItem("audioDisabled"), audioNotFound: false, @@ -213,15 +211,10 @@ registerLocalVideoButtonHandlers: function() { $('#hideVideo').click(function() { - if(!OCA.SpreedMe.app.videoWasEnabledAtLeastOnce) { - // don't allow clicking the video toggle - // when no video ever was streamed (that - // means that permission wasn't granted - // yet or there is no video available at - // all) - console.log('video can not be enabled - there was no stream available before'); + if (OCA.SpreedMe.app.videoNotFound) { return; } + if ($(this).hasClass('video-disabled')) { OCA.SpreedMe.app.enableVideo(); localStorage.removeItem("videoDisabled"); @@ -834,8 +827,8 @@ this.disableVideo(); } } else { - this.videoWasEnabledAtLeastOnce = false; this.disableVideo(); + this.hasNoVideo(); } }, enableFullscreen: function() { diff --git a/js/webrtc.js b/js/webrtc.js index ed6c776fc..3979489be 100644 --- a/js/webrtc.js +++ b/js/webrtc.js @@ -1350,9 +1350,6 @@ var spreedPeerConnectionTable = []; OCA.SpreedMe.webrtc.on('localStream', function() { console.log('localStream'); - if (!app.videoWasEnabledAtLeastOnce) { - app.videoWasEnabledAtLeastOnce = true; - } //Reset audio and video control panel app.hasAudio(); From 8ec4b207962b4a827ef0e3fa58af51b5f84c661b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Calvi=C3=B1o=20S=C3=A1nchez?= Date: Thu, 29 Nov 2018 04:52:22 +0100 Subject: [PATCH 2/8] Do not change the stored configuration if audio is not available MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In a similar way to the video button, now the audio button does not handle the click event if audio is not available, instead of treating it as disabled and trying to enable it. Signed-off-by: Daniel Calviño Sánchez --- js/app.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/js/app.js b/js/app.js index 9b6eca7e5..ab1bfa659 100644 --- a/js/app.js +++ b/js/app.js @@ -225,7 +225,11 @@ }); $('#mute').click(function() { - if (OCA.SpreedMe.webrtc.webrtc.isAudioEnabled()) { + if (OCA.SpreedMe.app.audioNotFound) { + return; + } + + if (!OCA.SpreedMe.app.audioDisabled) { OCA.SpreedMe.app.disableAudio(); localStorage.setItem("audioDisabled", true); } else { From d79ee15710a8072450ab85abcebfae87af2abea1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Calvi=C3=B1o=20S=C3=A1nchez?= Date: Thu, 29 Nov 2018 05:17:27 +0100 Subject: [PATCH 3/8] Unify handling of "localstream" with "localMediaStarted" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SimpleWebRTC emits the "localstream" event after a successful call to "start/startLocalMedia". In turn, this is only called from "startLocalVideo", which emits "localMediaStarted" if "startLocalMedia" succeeded; "localMediaStarted" is handled by calling "startLocalMedia" in the app, so now the setup done when handling "localstream" is merged with the setup done when handling "localMediaStarted". Signed-off-by: Daniel Calviño Sánchez --- js/app.js | 15 ++++++++++++++- js/webrtc.js | 22 ---------------------- 2 files changed, 14 insertions(+), 23 deletions(-) diff --git a/js/app.js b/js/app.js index ab1bfa659..2e2ab44dc 100644 --- a/js/app.js +++ b/js/app.js @@ -822,13 +822,26 @@ uiChannel.trigger('document:click', event); }, initAudioVideoSettings: function(configuration) { - if (this.audioDisabled) { + if (configuration.audio !== false) { + this.hasAudio(); + + if (this.audioDisabled) { + this.disableAudio(); + } else { + this.enableAudio(); + } + } else { this.disableAudio(); + this.hasNoAudio(); } if (configuration.video !== false) { + this.hasVideo(); + if (this.videoDisabled) { this.disableVideo(); + } else { + this.enableVideo(); } } else { this.disableVideo(); diff --git a/js/webrtc.js b/js/webrtc.js index 3979489be..bef6bd8f3 100644 --- a/js/webrtc.js +++ b/js/webrtc.js @@ -1347,28 +1347,6 @@ var spreedPeerConnectionTable = []; OCA.SpreedMe.speakers.updateVideoContainerDummy(data.id); } }); - - OCA.SpreedMe.webrtc.on('localStream', function() { - console.log('localStream'); - - //Reset audio and video control panel - app.hasAudio(); - app.hasVideo(); - - if (!app.videoDisabled) { - app.enableVideo(); - } - - if (!OCA.SpreedMe.webrtc.webrtc.isAudioEnabled()) { - app.disableAudio(); - app.hasNoAudio(); - } - - if (!OCA.SpreedMe.webrtc.webrtc.isVideoEnabled()) { - app.disableVideo(); - app.hasNoVideo(); - } - }); } OCA.SpreedMe.initWebRTC = initWebRTC; From 98277660a8bfaef7c50b210b10ddb96a9de8b4ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Calvi=C3=B1o=20S=C3=A1nchez?= Date: Thu, 29 Nov 2018 05:58:42 +0100 Subject: [PATCH 4/8] Do not enable the video UI before enabling the video MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the video is set as available there is no need to show the video UI; it will be shown as needed when calling "enableVideo". Signed-off-by: Daniel Calviño Sánchez --- js/app.js | 1 - 1 file changed, 1 deletion(-) diff --git a/js/app.js b/js/app.js index 2e2ab44dc..52166c93d 100644 --- a/js/app.js +++ b/js/app.js @@ -985,7 +985,6 @@ }, hasVideo: function() { $('#hideVideo').removeClass('no-video-available'); - this.enableVideoUI(); this.videoNotFound = false; }, hasNoVideo: function() { From a4e0a6e2e86f407c259ddbbbc9922a29b794a81d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Calvi=C3=B1o=20S=C3=A1nchez?= Date: Fri, 30 Nov 2018 13:34:55 +0100 Subject: [PATCH 5/8] Remove inline styles for screen sharing menu items MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The items in "app-navigation-entry-menu" lists were forced to be displayed as blocks with an "!important" rule, so inline rules were used to hide some items in the screensharing menu. However, forcing the items in "app-navigation-entry-menus" lists to be displayed as blocks is no longer needed; that class is only used in the room list menus and in the screensharing menu, and both work fine without it. Moreover, the server provides rules to hide items in those menus by adding the "hidden" class, so that approach is used now instead of the inline styles. Signed-off-by: Daniel Calviño Sánchez --- css/style.scss | 4 ---- js/app.js | 18 ++++++++---------- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/css/style.scss b/css/style.scss index 9e402dee1..b7d9110a4 100644 --- a/css/style.scss +++ b/css/style.scss @@ -107,10 +107,6 @@ padding: 0; } -.app-navigation-entry-menu li { - display: block !important; -} - .participantWithList .avatar, #app-navigation .avatar, #app-navigation .icon-contacts-dark, diff --git a/js/app.js b/js/app.js index 52166c93d..38f1f3093 100644 --- a/js/app.js +++ b/js/app.js @@ -263,20 +263,18 @@ splitShare = (ffver >= 52); } - // The parent CSS of the menu list items is using "display:block !important", - // so we need to also hide with "!important". if (webrtc.getLocalScreen()) { - $('#share-screen-entry').attr('style','display:none !important'); - $('#share-window-entry').attr('style','display:none !important'); - $('#show-screen-entry').show(); - $('#stop-screen-entry').show(); + $('#share-screen-entry').addClass('hidden'); + $('#share-window-entry').addClass('hidden'); + $('#show-screen-entry').removeClass('hidden'); + $('#stop-screen-entry').removeClass('hidden'); $('#screensharing-menu').toggleClass('open'); } else { if (splitShare) { - $('#share-screen-entry').show(); - $('#share-window-entry').show(); - $('#show-screen-entry').attr('style','display:none !important'); - $('#stop-screen-entry').attr('style','display:none !important'); + $('#share-screen-entry').removeClass('hidden'); + $('#share-window-entry').removeClass('hidden'); + $('#show-screen-entry').addClass('hidden'); + $('#stop-screen-entry').addClass('hidden'); $('#screensharing-menu').toggleClass('open'); return; } From 6ed774e1f3ede23d36ce4e9ec76b29f9de6e4c76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Calvi=C3=B1o=20S=C3=A1nchez?= Date: Wed, 5 Dec 2018 16:10:24 +0100 Subject: [PATCH 6/8] Unify the initial audio and video settings with and without local media MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When "startWithoutLocalMedia" is called there is never audio nor video (otherwise "startLocalMedia" would have been called instead), so there is no need to call "isAudioEnabled" nor "isVideoEnabled". Moreover, "initAudioVideoSettings" performs the same setup as the custom handling in "startWithoutLocalMedia", so that custom handling can be replaced by a call to "initAudioVideoSettings". Signed-off-by: Daniel Calviño Sánchez --- js/app.js | 13 ++----------- js/webrtc.js | 2 +- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/js/app.js b/js/app.js index 38f1f3093..8937f1c56 100644 --- a/js/app.js +++ b/js/app.js @@ -787,23 +787,14 @@ localMediaChannel.trigger('startLocalMedia'); }, - startWithoutLocalMedia: function(isAudioEnabled, isVideoEnabled) { + startWithoutLocalMedia: function(configuration) { if (this.callbackAfterMedia) { this.callbackAfterMedia(null); this.callbackAfterMedia = null; } $('.videoView').removeClass('hidden'); - - this.disableAudio(); - if (!isAudioEnabled) { - this.hasNoAudio(); - } - - this.disableVideo(); - if (!isVideoEnabled) { - this.hasNoVideo(); - } + this.initAudioVideoSettings(configuration); if (OCA.SpreedMe.webrtc.capabilities.support) { localMediaChannel.trigger('startWithoutLocalMedia'); diff --git a/js/webrtc.js b/js/webrtc.js index bef6bd8f3..4534fe42b 100644 --- a/js/webrtc.js +++ b/js/webrtc.js @@ -1024,7 +1024,7 @@ var spreedPeerConnectionTable = []; console.log('Error while accessing microphone & camera: ', error.message || error.name); } - app.startWithoutLocalMedia(webrtc.webrtc.isAudioEnabled(), webrtc.webrtc.isVideoEnabled()); + app.startWithoutLocalMedia({audio: false, video: false}); OC.Notification.show(message, { type: 'error', timeout: 15, From c4c047a3c1a4bc5a622f49880f0b77cd00b2f55c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Calvi=C3=B1o=20S=C3=A1nchez?= Date: Thu, 29 Nov 2018 06:11:12 +0100 Subject: [PATCH 7/8] Extract view for media controls MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Daniel Calviño Sánchez --- js/app.js | 284 ++---------------- js/publicshareauth.js | 33 +-- js/views/mediacontrolsview.js | 380 +++++++++++++++++++++++++ lib/PublicShareAuth/TemplateLoader.php | 1 + templates/index-public.php | 32 +-- templates/index.php | 32 +-- 6 files changed, 416 insertions(+), 346 deletions(-) create mode 100644 js/views/mediacontrolsview.js diff --git a/js/app.js b/js/app.js index 8937f1c56..69a01fcf8 100644 --- a/js/app.js +++ b/js/app.js @@ -74,10 +74,6 @@ /** @property {OCA.SpreedMe.Views.ParticipantView} _participantsView */ _participantsView: null, displayedGuestNameHint: false, - audioDisabled: localStorage.getItem("audioDisabled"), - audioNotFound: false, - videoDisabled: localStorage.getItem("videoDisabled"), - videoNotFound: false, fullscreenDisabled: true, _searchTerm: '', guestNick: null, @@ -210,34 +206,6 @@ }, registerLocalVideoButtonHandlers: function() { - $('#hideVideo').click(function() { - if (OCA.SpreedMe.app.videoNotFound) { - return; - } - - if ($(this).hasClass('video-disabled')) { - OCA.SpreedMe.app.enableVideo(); - localStorage.removeItem("videoDisabled"); - } else { - OCA.SpreedMe.app.disableVideo(); - localStorage.setItem("videoDisabled", true); - } - }); - - $('#mute').click(function() { - if (OCA.SpreedMe.app.audioNotFound) { - return; - } - - if (!OCA.SpreedMe.app.audioDisabled) { - OCA.SpreedMe.app.disableAudio(); - localStorage.setItem("audioDisabled", true); - } else { - OCA.SpreedMe.app.enableAudio(); - localStorage.removeItem("audioDisabled"); - } - }); - $('#video-fullscreen').click(function() { if (this.fullscreenDisabled) { this.enableFullscreen(); @@ -245,119 +213,6 @@ this.disableFullscreen(); } }.bind(this)); - - $('#screensharing-button').click(function() { - var webrtc = OCA.SpreedMe.webrtc; - if (!webrtc.capabilities.supportScreenSharing) { - if (window.location.protocol === 'https:') { - OC.Notification.showTemporary(t('spreed', 'Screensharing is not supported by your browser.')); - } else { - OC.Notification.showTemporary(t('spreed', 'Screensharing requires the page to be loaded through HTTPS.')); - } - return; - } - - var splitShare = false; - if (window.navigator.userAgent.match('Firefox')) { - var ffver = parseInt(window.navigator.userAgent.match(/Firefox\/(.*)/)[1], 10); - splitShare = (ffver >= 52); - } - - if (webrtc.getLocalScreen()) { - $('#share-screen-entry').addClass('hidden'); - $('#share-window-entry').addClass('hidden'); - $('#show-screen-entry').removeClass('hidden'); - $('#stop-screen-entry').removeClass('hidden'); - $('#screensharing-menu').toggleClass('open'); - } else { - if (splitShare) { - $('#share-screen-entry').removeClass('hidden'); - $('#share-window-entry').removeClass('hidden'); - $('#show-screen-entry').addClass('hidden'); - $('#stop-screen-entry').addClass('hidden'); - $('#screensharing-menu').toggleClass('open'); - return; - } - - this.startShareScreen(); - } - }.bind(this)); - - $("#share-screen-button").on('click', function() { - var webrtc = OCA.SpreedMe.webrtc; - if (!webrtc.getLocalScreen()) { - this.startShareScreen('screen'); - } - $('#screensharing-menu').toggleClass('open', false); - }.bind(this)); - - $("#share-window-button").on('click', function() { - var webrtc = OCA.SpreedMe.webrtc; - if (!webrtc.getLocalScreen()) { - this.startShareScreen('window'); - } - $('#screensharing-menu').toggleClass('open', false); - }.bind(this)); - - $("#show-screen-button").on('click', function() { - var webrtc = OCA.SpreedMe.webrtc; - if (webrtc.getLocalScreen()) { - var currentUser = OCA.SpreedMe.webrtc.connection.getSessionid(); - OCA.SpreedMe.sharedScreens.switchScreenToId(currentUser); - } - $('#screensharing-menu').toggleClass('open', false); - }.bind(this)); - - $("#stop-screen-button").on('click', function() { - OCA.SpreedMe.webrtc.stopScreenShare(); - }); - }, - - startShareScreen: function(mode) { - var webrtc = OCA.SpreedMe.webrtc; - var screensharingButton = $('#screensharing-button'); - screensharingButton.prop('disabled', true); - webrtc.shareScreen(mode, function(err) { - screensharingButton.prop('disabled', false); - if (!err) { - $('#screensharing-button').attr('data-original-title', t('spreed', 'Screensharing options')) - .removeClass('screensharing-disabled icon-screen-off') - .addClass('icon-screen'); - return; - } - - switch (err.name) { - case "HTTPS_REQUIRED": - OC.Notification.showTemporary(t('spreed', 'Screensharing requires the page to be loaded through HTTPS.')); - break; - case "PERMISSION_DENIED": - case "NotAllowedError": - case "CEF_GETSCREENMEDIA_CANCELED": // Experimental, may go away in the future. - break; - case "FF52_REQUIRED": - OC.Notification.showTemporary(t('spreed', 'Sharing your screen only works with Firefox version 52 or newer.')); - break; - case "EXTENSION_UNAVAILABLE": - var extensionURL = null; - if (!!window.chrome && !!window.chrome.webstore) {// Chrome - extensionURL = 'https://chrome.google.com/webstore/detail/screensharing-for-nextclo/kepnpjhambipllfmgmbapncekcmabkol'; - } - - if (extensionURL) { - var text = t('spreed', 'Screensharing extension is required to share your screen.'); - var element = $('').attr('href', extensionURL).attr('target','_blank').text(text); - - OC.Notification.showTemporary(element, {isHTML: true}); - } else { - OC.Notification.showTemporary(t('spreed', 'Please use a different browser like Firefox or Chrome to share your screen.')); - } - break; - default: - OC.Notification.showTemporary(t('spreed', 'An error occurred while starting screensharing.')); - console.log("Could not start screensharing", err); - break; - } - }); }, _onKeyUp: function(event) { @@ -375,19 +230,11 @@ switch (key) { case 86: // 'v' event.preventDefault(); - if (this.videoDisabled) { - this.enableVideo(); - } else { - this.disableVideo(); - } + this._mediaControlsView.toggleVideo(); break; case 77: // 'm' event.preventDefault(); - if (this.audioDisabled) { - this.enableAudio(); - } else { - this.disableAudio(); - } + this._mediaControlsView.toggleAudio(); break; case 70: // 'f' event.preventDefault(); @@ -703,6 +550,14 @@ $('#emptycontent').show(); }); + this._mediaControlsView = new OCA.SpreedMe.Views.MediaControlsView({ + app: this, + webrtc: OCA.SpreedMe.webrtc, + sharedScreens: OCA.SpreedMe.sharedScreens, + }); + this._mediaControlsView.render(); + $('#localVideoContainer .nameIndicator').replaceWith(this._mediaControlsView.$el); + $(document).on('click', this.onDocumentClick); OC.Util.History.addOnPopStateHandler(_.bind(this._onPopState, this)); }, @@ -766,6 +621,8 @@ setupWebRTC: function() { if (!OCA.SpreedMe.webrtc) { OCA.SpreedMe.initWebRTC(this); + this._mediaControlsView.setWebRtc(OCA.SpreedMe.webrtc); + this._mediaControlsView.setSharedScreens(OCA.SpreedMe.sharedScreens); } if (!OCA.SpreedMe.webrtc.capabilities.support) { @@ -812,29 +669,29 @@ }, initAudioVideoSettings: function(configuration) { if (configuration.audio !== false) { - this.hasAudio(); + this._mediaControlsView.hasAudio(); - if (this.audioDisabled) { - this.disableAudio(); + if (this._mediaControlsView.audioDisabled) { + this._mediaControlsView.disableAudio(); } else { - this.enableAudio(); + this._mediaControlsView.enableAudio(); } } else { - this.disableAudio(); - this.hasNoAudio(); + this._mediaControlsView.disableAudio(); + this._mediaControlsView.hasNoAudio(); } if (configuration.video !== false) { - this.hasVideo(); + this._mediaControlsView.hasVideo(); - if (this.videoDisabled) { + if (this._mediaControlsView.videoDisabled) { this.disableVideo(); } else { this.enableVideo(); } } else { this.disableVideo(); - this.hasNoVideo(); + this._mediaControlsView.hasNoVideo(); } }, enableFullscreen: function() { @@ -868,83 +725,21 @@ this.fullscreenDisabled = true; }, - enableAudioButton: function() { - $('#mute').attr('data-original-title', t('spreed', 'Mute audio (m)')) - .removeClass('audio-disabled icon-audio-off') - .addClass('icon-audio'); - }, - enableAudio: function() { - if (this.audioNotFound || !OCA.SpreedMe.webrtc) { - return; - } - OCA.SpreedMe.webrtc.unmute(); - this.enableAudioButton(); - this.audioDisabled = false; - }, - disableAudioButton: function() { - $('#mute').attr('data-original-title', t('spreed', 'Unmute audio (m)')) - .addClass('audio-disabled icon-audio-off') - .removeClass('icon-audio'); - }, - disableAudio: function() { - if (this.audioNotFound || !OCA.SpreedMe.webrtc) { - return; - } - OCA.SpreedMe.webrtc.mute(); - this.disableAudioButton(); - this.audioDisabled = true; - }, - hasAudio: function() { - $('#mute').removeClass('no-audio-available'); - this.enableAudioButton(); - this.audioNotFound = false; - }, - hasNoAudio: function() { - $('#mute').removeClass('audio-disabled icon-audio') - .addClass('no-audio-available icon-audio-off') - .attr('data-original-title', t('spreed', 'No audio')); - this.audioDisabled = true; - this.audioNotFound = true; - }, enableVideoUI: function() { - var $hideVideoButton = $('#hideVideo'); - var $audioMuteButton = $('#mute'); - var $screensharingButton = $('#screensharing-button'); - var avatarContainer = $hideVideoButton.closest('.videoView').find('.avatar-container'); - var localVideo = $hideVideoButton.closest('.videoView').find('#localVideo'); - - $hideVideoButton.attr('data-original-title', t('spreed', 'Disable video (v)')) - .removeClass('local-video-disabled video-disabled icon-video-off') - .addClass('icon-video'); - $audioMuteButton.removeClass('local-video-disabled'); - $screensharingButton.removeClass('local-video-disabled'); + var avatarContainer = this._mediaControlsView.$el.closest('.videoView').find('.avatar-container'); + var localVideo = this._mediaControlsView.$el.closest('.videoView').find('#localVideo'); avatarContainer.hide(); localVideo.show(); }, enableVideo: function() { - if (this.videoNotFound || !OCA.SpreedMe.webrtc) { - return; + if (this._mediaControlsView.enableVideo()) { + this.enableVideoUI(); } - - OCA.SpreedMe.webrtc.resumeVideo(); - this.enableVideoUI(); - this.videoDisabled = false; }, hideVideo: function() { - var $hideVideoButton = $('#hideVideo'); - var $audioMuteButton = $('#mute'); - var $screensharingButton = $('#screensharing-button'); - var avatarContainer = $hideVideoButton.closest('.videoView').find('.avatar-container'); - var localVideo = $hideVideoButton.closest('.videoView').find('#localVideo'); - - if (!$hideVideoButton.hasClass('no-video-available')) { - $hideVideoButton.attr('data-original-title', t('spreed', 'Enable video (v)')) - .addClass('local-video-disabled video-disabled icon-video-off') - .removeClass('icon-video'); - $audioMuteButton.addClass('local-video-disabled'); - $screensharingButton.addClass('local-video-disabled'); - } + var avatarContainer = this._mediaControlsView.$el.closest('.videoView').find('.avatar-container'); + var localVideo = this._mediaControlsView.$el.closest('.videoView').find('#localVideo'); var avatar = avatarContainer.find('.avatar'); var guestName = localStorage.getItem("nick"); @@ -964,30 +759,13 @@ localVideo.hide(); }, disableVideo: function() { - if (this.videoNotFound || !OCA.SpreedMe.webrtc) { - return; - } - - OCA.SpreedMe.webrtc.pauseVideo(); + this._mediaControlsView.disableVideo(); + // Always hide the video, even if "disableVideo" returned "false". this.hideVideo(); - this.videoDisabled = true; - }, - hasVideo: function() { - $('#hideVideo').removeClass('no-video-available'); - this.videoNotFound = false; - }, - hasNoVideo: function() { - $('#hideVideo').removeClass('icon-video') - .addClass('no-video-available icon-video-off') - .attr('data-original-title', t('spreed', 'No Camera')); - this.videoDisabled = true; - this.videoNotFound = true; }, + // Called from webrtc.js disableScreensharingButton: function() { - $('#screensharing-button').attr('data-original-title', t('spreed', 'Enable screensharing')) - .addClass('screensharing-disabled icon-screen-off') - .removeClass('icon-screen'); - $('#screensharing-menu').toggleClass('open', false); + this._mediaControlsView.disableScreensharingButton(); }, setGuestName: function(name) { $.ajax({ diff --git a/js/publicshareauth.js b/js/publicshareauth.js index 6cf23de27..a15852fdd 100644 --- a/js/publicshareauth.js +++ b/js/publicshareauth.js @@ -74,37 +74,6 @@ '
' + '' + '
' + - ' ' + - ' ' + - ' ' + - '
' + - '
    ' + - '
  • ' + - ' ' + - '
  • ' + - '
  • ' + - ' ' + - '
  • ' + - '
  • ' + - ' ' + - '
  • ' + - '
  • ' + - ' ' + - '
  • ' + - '
' + - '
' + '
'); OCA.SpreedMe.app._emptyContentView.destroy(); @@ -112,6 +81,8 @@ el: '#talk-sidebar > #emptycontent' }); + $('#localVideoContainer .nameIndicator').replaceWith(OCA.SpreedMe.app._mediaControlsView.$el); + OCA.SpreedMe.app.registerLocalVideoButtonHandlers(); $('body').addClass('talk-sidebar-enabled'); diff --git a/js/views/mediacontrolsview.js b/js/views/mediacontrolsview.js new file mode 100644 index 000000000..97f8a53e4 --- /dev/null +++ b/js/views/mediacontrolsview.js @@ -0,0 +1,380 @@ +/* global Marionette, Handlebars, $ */ + +/** + * + * @copyright Copyright (c) 2018, Daniel Calviño Sánchez (danxuliu@gmail.com) + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +(function(OC, OCA, Marionette, Handlebars, $) { + + 'use strict'; + + OCA.SpreedMe = OCA.SpreedMe || {}; + OCA.SpreedMe.Views = OCA.SpreedMe.Views || {}; + + var TEMPLATE = + '' + + '' + + '' + + '
' + + '
    ' + + '
  • ' + + ' ' + + '
  • ' + + '
  • ' + + ' ' + + '
  • ' + + '
  • ' + + ' ' + + '
  • ' + + '
  • ' + + ' ' + + '
  • ' + + '
' + + '
'; + + var MediaControlsView = Marionette.View.extend({ + + tagName: 'div', + className: 'nameIndicator', + + template: Handlebars.compile(TEMPLATE), + + templateContext: function() { + return { + muteAudioButtonTitle: t('spreed', 'Mute audio'), + hideVideoButtonTitle: t('spreed', 'Disable video'), + screensharingButtonTitle: t('spreed', 'Share screen'), + shareScreenButtonTitle: t('spreed', 'Share whole screen'), + shareWindowButtonTitle: t('spreed', 'Share a single window'), + showScreenButtonTitle: t('spreed', 'Show your screen'), + stopScreenButtonTitle: t('spreed', 'Stop screensharing') + }; + }, + + ui: { + 'audioButton': '#mute', + 'videoButton': '#hideVideo', + 'screensharingButton': '#screensharing-button', + 'screensharingMenu': '#screensharing-menu', + 'shareScreenEntry': '#share-screen-entry', + 'shareScreenButton': '#share-screen-button', + 'shareWindowEntry': '#share-window-entry', + 'shareWindowButton': '#share-window-button', + 'showScreenEntry': '#show-screen-entry', + 'showScreenButton': '#show-screen-button', + 'stopScreenEntry': '#stop-screen-entry', + 'stopScreenButton': '#stop-screen-button', + }, + + events: { + 'click @ui.audioButton': 'toggleAudio', + 'click @ui.videoButton': 'toggleVideo', + 'click @ui.screensharingButton': 'toggleScreensharingMenu', + 'click @ui.shareScreenButton': 'shareScreen', + 'click @ui.shareWindowButton': 'shareWindow', + 'click @ui.showScreenButton': 'showScreen', + 'click @ui.stopScreenButton': 'stopScreen', + }, + + initialize: function(options) { + this._app = options.app; + this._webrtc = options.webrtc; + this._sharedScreens = options.sharedScreens; + + this._audioNotFound = false; + this._videoNotFound = false; + + if (localStorage.getItem("audioDisabled")) { + this.disableAudio(); + } + + this._videoDisabled = localStorage.getItem("videoDisabled"); + }, + + setWebRtc: function(webrtc) { + this._webrtc = webrtc; + }, + + setSharedScreens: function(sharedScreens) { + this._sharedScreens = sharedScreens; + }, + + toggleAudio: function() { + if (this.audioNotFound) { + return; + } + + if (!this.audioDisabled) { + this.disableAudio(); + localStorage.setItem("audioDisabled", true); + } else { + this.enableAudio(); + localStorage.removeItem("audioDisabled"); + } + }, + + disableAudio: function() { + if (this.audioNotFound || !this._webrtc) { + return; + } + + this._webrtc.mute(); + + this.getUI('audioButton').attr('data-original-title', t('spreed', 'Unmute audio (m)')) + .addClass('audio-disabled icon-audio-off') + .removeClass('icon-audio'); + + this.audioDisabled = true; + }, + + enableAudio: function() { + if (this.audioNotFound || !this._webrtc) { + return; + } + + this._webrtc.unmute(); + + this.getUI('audioButton').attr('data-original-title', t('spreed', 'Mute audio (m)')) + .removeClass('audio-disabled icon-audio-off') + .addClass('icon-audio'); + + this.audioDisabled = false; + }, + + hasAudio: function() { + this.getUI('audioButton').removeClass('no-audio-available'); + this.getUI('audioButton').attr('data-original-title', t('spreed', 'Mute audio (m)')) + .removeClass('audio-disabled icon-audio-off') + .addClass('icon-audio'); + + this.audioNotFound = false; + }, + + hasNoAudio: function() { + this.getUI('audioButton').removeClass('audio-disabled icon-audio') + .addClass('no-audio-available icon-audio-off') + .attr('data-original-title', t('spreed', 'No audio')); + + this.audioDisabled = true; + this.audioNotFound = true; + }, + + toggleVideo: function() { + if (this.videoNotFound) { + return; + } + + if (this.videoDisabled) { + this._app.enableVideo(); + localStorage.removeItem("videoDisabled"); + } else { + this._app.disableVideo(); + localStorage.setItem("videoDisabled", true); + } + }, + + disableVideo: function() { + if (this.videoNotFound || !this._webrtc) { + return false; + } + + this._webrtc.pauseVideo(); + + if (!this.getUI('videoButton').hasClass('no-video-available')) { + this.getUI('videoButton').attr('data-original-title', t('spreed', 'Enable video (v)')) + .addClass('local-video-disabled video-disabled icon-video-off') + .removeClass('icon-video'); + this.getUI('audioButton').addClass('local-video-disabled'); + this.getUI('screensharingButton').addClass('local-video-disabled'); + } + + this.videoDisabled = true; + + return true; + }, + + enableVideo: function() { + if (this.videoNotFound || !this._webrtc) { + return false; + } + + this._webrtc.resumeVideo(); + + this.getUI('videoButton').attr('data-original-title', t('spreed', 'Disable video (v)')) + .removeClass('local-video-disabled video-disabled icon-video-off') + .addClass('icon-video'); + this.getUI('audioButton').removeClass('local-video-disabled'); + this.getUI('screensharingButton').removeClass('local-video-disabled'); + + this.videoDisabled = false; + + return true; + }, + + hasVideo: function() { + this.getUI('videoButton').removeClass('no-video-available'); + + this.videoNotFound = false; + }, + + hasNoVideo: function() { + this.getUI('videoButton').removeClass('icon-video') + .addClass('no-video-available icon-video-off') + .attr('data-original-title', t('spreed', 'No Camera')); + + this.videoDisabled = true; + this.videoNotFound = true; + }, + + toggleScreensharingMenu: function() { + if (!this._webrtc.capabilities.supportScreenSharing) { + if (window.location.protocol === 'https:') { + OC.Notification.showTemporary(t('spreed', 'Screensharing is not supported by your browser.')); + } else { + OC.Notification.showTemporary(t('spreed', 'Screensharing requires the page to be loaded through HTTPS.')); + } + return; + } + + var splitShare = false; + if (window.navigator.userAgent.match('Firefox')) { + var ffver = parseInt(window.navigator.userAgent.match(/Firefox\/(.*)/)[1], 10); + splitShare = (ffver >= 52); + } + + if (this._webrtc.getLocalScreen()) { + this.getUI('shareScreenEntry').addClass('hidden'); + this.getUI('shareWindowEntry').addClass('hidden'); + this.getUI('showScreenEntry').removeClass('hidden'); + this.getUI('stopScreenEntry').removeClass('hidden'); + this.getUI('screensharingMenu').toggleClass('open'); + } else { + if (splitShare) { + this.getUI('shareScreenEntry').removeClass('hidden'); + this.getUI('shareWindowEntry').removeClass('hidden'); + this.getUI('showScreenEntry').addClass('hidden'); + this.getUI('stopScreenEntry').addClass('hidden'); + this.getUI('screensharingMenu').toggleClass('open'); + return; + } + + this.startShareScreen(); + } + }, + + shareScreen: function() { + if (!this._webrtc.getLocalScreen()) { + this.startShareScreen('screen'); + } + + this.getUI('screensharingMenu').toggleClass('open', false); + }, + + shareWindow: function() { + if (!this._webrtc.getLocalScreen()) { + this.startShareScreen('window'); + } + + this.getUI('screensharingMenu').toggleClass('open', false); + }, + + showScreen: function() { + if (this._webrtc.getLocalScreen()) { + var currentUser = this._webrtc.connection.getSessionid(); + this._sharedScreens.switchScreenToId(currentUser); + } + + this.getUI('screensharingMenu').toggleClass('open', false); + }, + + stopScreen: function() { + this._webrtc.stopScreenShare(); + }, + + startShareScreen: function(mode) { + this.getUI('screensharingButton').prop('disabled', true); + + this._webrtc.shareScreen(mode, function(err) { + this.getUI('screensharingButton').prop('disabled', false); + if (!err) { + this.getUI('screensharingButton').attr('data-original-title', t('spreed', 'Screensharing options')) + .removeClass('screensharing-disabled icon-screen-off') + .addClass('icon-screen'); + return; + } + + switch (err.name) { + case "HTTPS_REQUIRED": + OC.Notification.showTemporary(t('spreed', 'Screensharing requires the page to be loaded through HTTPS.')); + break; + case "PERMISSION_DENIED": + case "NotAllowedError": + case "CEF_GETSCREENMEDIA_CANCELED": // Experimental, may go away in the future. + break; + case "FF52_REQUIRED": + OC.Notification.showTemporary(t('spreed', 'Sharing your screen only works with Firefox version 52 or newer.')); + break; + case "EXTENSION_UNAVAILABLE": + var extensionURL = null; + if (!!window.chrome && !!window.chrome.webstore) {// Chrome + extensionURL = 'https://chrome.google.com/webstore/detail/screensharing-for-nextclo/kepnpjhambipllfmgmbapncekcmabkol'; + } + + if (extensionURL) { + var text = t('spreed', 'Screensharing extension is required to share your screen.'); + var element = $('
').attr('href', extensionURL).attr('target','_blank').text(text); + + OC.Notification.showTemporary(element, {isHTML: true}); + } else { + OC.Notification.showTemporary(t('spreed', 'Please use a different browser like Firefox or Chrome to share your screen.')); + } + break; + default: + OC.Notification.showTemporary(t('spreed', 'An error occurred while starting screensharing.')); + console.log("Could not start screensharing", err); + break; + } + }.bind(this)); + }, + + disableScreensharingButton: function() { + this.getUI('screensharingButton').attr('data-original-title', t('spreed', 'Enable screensharing')) + .addClass('screensharing-disabled icon-screen-off') + .removeClass('icon-screen'); + this.getUI('screensharingMenu').toggleClass('open', false); + }, + + }); + + OCA.SpreedMe.Views.MediaControlsView = MediaControlsView; + +})(OC, OCA, Marionette, Handlebars, $); diff --git a/lib/PublicShareAuth/TemplateLoader.php b/lib/PublicShareAuth/TemplateLoader.php index fb718dd25..5904f3565 100644 --- a/lib/PublicShareAuth/TemplateLoader.php +++ b/lib/PublicShareAuth/TemplateLoader.php @@ -95,6 +95,7 @@ class TemplateLoader { Util::addScript('spreed', 'views/chatview'); Util::addScript('spreed', 'views/editabletextlabel'); Util::addScript('spreed', 'views/emptycontentview'); + Util::addScript('spreed', 'views/mediacontrolsview'); Util::addScript('spreed', 'views/participantlistview'); Util::addScript('spreed', 'views/participantview'); Util::addScript('spreed', 'views/richobjectstringparser'); diff --git a/templates/index-public.php b/templates/index-public.php index 6b99937b4..699681ce2 100644 --- a/templates/index-public.php +++ b/templates/index-public.php @@ -30,6 +30,7 @@ script( 'views/chatview', 'views/editabletextlabel', 'views/emptycontentview', + 'views/mediacontrolsview', 'views/participantlistview', 'views/participantview', 'views/richobjectstringparser', @@ -69,37 +70,6 @@ script(
- - - -
-
    -
  • - -
  • -
  • - -
  • -
  • - -
  • -
  • - -
  • -
-
diff --git a/templates/index.php b/templates/index.php index 4e363e44f..3d2c1206a 100644 --- a/templates/index.php +++ b/templates/index.php @@ -29,6 +29,7 @@ script( 'views/chatview', 'views/editabletextlabel', 'views/emptycontentview', + 'views/mediacontrolsview', 'views/participantlistview', 'views/participantview', 'views/richobjectstringparser', @@ -76,37 +77,6 @@ script(
- - - -
-
    -
  • - -
  • -
  • - -
  • -
  • - -
  • -
  • - -
  • -
-
From d6a0528bbcb72b1678de66d05900db7ac1c86aeb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Calvi=C3=B1o=20S=C3=A1nchez?= Date: Thu, 29 Nov 2018 06:23:20 +0100 Subject: [PATCH 8/8] Move MediaControlsView to precompiled Handlebars templates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In Nextcloud 15 the default Content Security Policy disallows unsafe eval expressions, so Handlebars templates can no longer be compiled at runtime. For the time being that default Content Security Policy was lifted for Talk so "Handlebars.compile" could still be used. However, this only applies to Talk itself; when using Talk components in other apps they must abide to the Content Security Policy of those apps. As MediaControlsView is going to be used in the Files app it has been moved to precompiled Handlebars templates (which are still compatible with the regular Talk UI). Signed-off-by: Daniel Calviño Sánchez --- js/views/mediacontrolsview.js | 48 +++++-------------- js/views/templates.js | 19 ++++++++ .../templates/mediacontrolsview.handlebars | 31 ++++++++++++ 3 files changed, 61 insertions(+), 37 deletions(-) create mode 100644 js/views/templates/mediacontrolsview.handlebars diff --git a/js/views/mediacontrolsview.js b/js/views/mediacontrolsview.js index 97f8a53e4..f5aa9cc7d 100644 --- a/js/views/mediacontrolsview.js +++ b/js/views/mediacontrolsview.js @@ -1,4 +1,4 @@ -/* global Marionette, Handlebars, $ */ +/* global Marionette, $ */ /** * @@ -21,52 +21,26 @@ * */ -(function(OC, OCA, Marionette, Handlebars, $) { +(function(OC, OCA, Marionette, $) { 'use strict'; OCA.SpreedMe = OCA.SpreedMe || {}; + OCA.Talk = OCA.Talk || {}; OCA.SpreedMe.Views = OCA.SpreedMe.Views || {}; - - var TEMPLATE = - '' + - '' + - '' + - '
' + - '
    ' + - '
  • ' + - ' ' + - '
  • ' + - '
  • ' + - ' ' + - '
  • ' + - '
  • ' + - ' ' + - '
  • ' + - '
  • ' + - ' ' + - '
  • ' + - '
' + - '
'; + OCA.Talk.Views = OCA.Talk.Views || {}; var MediaControlsView = Marionette.View.extend({ tagName: 'div', className: 'nameIndicator', - template: Handlebars.compile(TEMPLATE), + template: function(context) { + // OCA.Talk.Views.Templates may not have been initialized when this + // view is initialized, so the template can not be directly + // assigned. + return OCA.Talk.Views.Templates['mediacontrolsview'](context); + }, templateContext: function() { return { @@ -377,4 +351,4 @@ OCA.SpreedMe.Views.MediaControlsView = MediaControlsView; -})(OC, OCA, Marionette, Handlebars, $); +})(OC, OCA, Marionette, $); diff --git a/js/views/templates.js b/js/views/templates.js index 24a4f4fee..b3158ff91 100644 --- a/js/views/templates.js +++ b/js/views/templates.js @@ -113,6 +113,25 @@ templates['chatview_comment'] = template({"1":function(container,depth0,helpers, + ((stack1 = ((helper = (helper = helpers.formattedMessage || (depth0 != null ? depth0.formattedMessage : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"formattedMessage","hash":{},"data":data}) : helper))) != null ? stack1 : "") + "\n\n"; },"useData":true}); +templates['mediacontrolsview'] = template({"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) { + var helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression; + + return "\n\n\n
\n
    \n
  • \n \n
  • \n
  • \n \n
  • \n
  • \n \n
  • \n
  • \n \n
  • \n
\n
\n"; +},"useData":true}); templates['richobjectstringparser_filepreview'] = template({"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) { var helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression; diff --git a/js/views/templates/mediacontrolsview.handlebars b/js/views/templates/mediacontrolsview.handlebars new file mode 100644 index 000000000..df108e573 --- /dev/null +++ b/js/views/templates/mediacontrolsview.handlebars @@ -0,0 +1,31 @@ + + + +
+
    +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
+