diff --git a/css/style.scss b/css/style.scss index 57a0447f9a0..aa1bc561e9e 100644 --- a/css/style.scss +++ b/css/style.scss @@ -922,9 +922,54 @@ input[type="password"] { } } } + .talk-settings-button { + position: relative; + + .button { + cursor: pointer; + width: 50px; + height: 50px; + display: block; + background-color: transparent; + border: none; + margin: 0; + opacity: .7; + &:hover, + &:focus, + &:active { + opacity: 1; + } + } + } } } +.icon-speaker { + background-image: url('../img/speaker.svg?v=1'); + background-size: contain; +} + +.settings-option { + background-position: left; + display: inline-block; + position: relative; + top: 3px; + margin-right: 5px; +} + +.settings-input { + width: 90%; + margin-bottom: 10px; +} + +.settings-menu { + padding: 10px; +} + +.settings-confirm { + float: right; +} + /** * Cascade parent element height to the chat view in the sidebar to limit the * vertical scroll bar only to the list of messages. Otherwise, the vertical diff --git a/img/speaker.svg b/img/speaker.svg new file mode 100644 index 00000000000..277120ed9ec --- /dev/null +++ b/img/speaker.svg @@ -0,0 +1 @@ + diff --git a/js/app.js b/js/app.js index 48231662325..8556ff05c09 100644 --- a/js/app.js +++ b/js/app.js @@ -218,6 +218,106 @@ }.bind(this)); }, + onAudioOutputChange: function() { + localStorage.setItem('audioOutput', $('#audioOutput').val()); + + }, + + onAudioSourceChange: function() { + localStorage.setItem("audioSource", $('#audioSource').val()); + OCA.SpreedMe.app.setMediaSource(localStorage.getItem("audioSource"), localStorage.getItem("videoSource")); + }, + + onVideoSourceChange: function() { + localStorage.setItem("videoSource", $('#videoSource').val()); + OCA.SpreedMe.app.setMediaSource(localStorage.getItem("audioSource"), localStorage.getItem("videoSource")); + }, + + initMediaSources: function() { + OCA.SpreedMe.app.setMediaSource(localStorage.getItem("audioSource"), localStorage.getItem("videoSource")); + }, + + setMediaSource: function(audioSource, videoSource) { + + if (typeof OCA.SpreedMe.webrtc !== 'undefined') { + OCA.SpreedMe.webrtc.config.media = { + audio: { + optional: [{sourceId: audioSource}] + }, + video: { + optional: [{sourceId: videoSource}] + } + }; + } + + if (OCA.SpreedMe.app.signaling.currentCallToken !== null) { + this.changingMedia = true; + OCA.SpreedMe.app.signaling.leaveCurrentCall(); + OCA.SpreedMe.webrtc.stopLocalVideo(); + + OCA.SpreedMe.webrtc.startLocalVideo(OCA.SpreedMe.webrtc.config.media); + OCA.SpreedMe.app.connection.joinCall(this.activeRoom.get('token')); + + /*var senders = existingPeer.pc.getLocalStreams(); + for (var i = 0; i < senders.length; i++) { + existingPeer.pc.removeStream(senders[i]); + } + OCA.SpreedMe.webrtc.on('localStream', function() { + senders = this.webrtc.localStreams; + var localStreams = existingPeer.pc.getLocalStreams(); + for (var i = 0; i < senders.length; i++) { + if (!localStreams.includes(senders[i])) { + existingPeer.pc.addStream(senders[i]); + } + } + });*/ + } + }, + + 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) { // Define which objects to check for the event properties. var key = event.which; @@ -363,6 +463,11 @@ return; } + if (this.changingMedia) { + this.changingMedia = false; + return; + } + var flags = this.activeRoom.get('participantFlags') || 0; var inCall = flags & OCA.SpreedMe.app.FLAG_IN_CALL !== 0; if (inCall && this._chatViewInMainView === true) { diff --git a/js/views/callinfoview.js b/js/views/callinfoview.js index 10cc8de23f7..9edea4b61b3 100644 --- a/js/views/callinfoview.js +++ b/js/views/callinfoview.js @@ -71,8 +71,33 @@ '
' + ' ' + '{{/if}}' + + '
' + + ' ' + + '
' + + '' + + ''; + var CallInfoView = Marionette.View.extend({ tagName: 'div', @@ -91,6 +116,7 @@ canFullModerate: this._canFullModerate(), isPublic: this.model.get('type') === 3, showShareLink: !canModerate && this.model.get('type') === 3, + canPublish: OCA.SpreedMe.canPublish(), isDeletable: canModerate && (Object.keys(this.model.get('participants')).length > 2 || this.model.get('numGuests') > 0) }); }, @@ -112,6 +138,10 @@ 'passwordConfirm': '.password-confirm', 'menu': '.password-menu', + + 'settingsButton': '.talk-settings-button', + 'settingsMenu': '.settings-menu', + 'settingsInput': '.settings-input', }, regions: { @@ -126,6 +156,7 @@ 'click @ui.passwordButton': 'showPasswordInput', 'click @ui.passwordConfirm': 'confirmPassword', 'submit @ui.passwordForm': 'confirmPassword', + 'click @ui.settingsButton': 'settingsButtonClicked', }, modelEvents: { @@ -242,6 +273,82 @@ $(self.ui.passwordInput).focus(); }); + this.initSettings(); + + }, + + gotSources: function(sourceInfos) { + var audioSelect = document.querySelector("select#audioSource"); + var videoSelect = document.querySelector("select#videoSource"); + var outputSelect = document.querySelector("select#audioOutput"); + + // clear the lists + $('#audioOutput').empty(); + $('#audioSource').empty(); + $('#videoSource').empty(); + + for (var i = 0; i != sourceInfos.length; ++i) { + var sourceInfo = sourceInfos[i]; + var option = document.createElement("option"); + option.value = sourceInfo.deviceId; + if (sourceInfo.kind === 'audioinput' && audioSelect !== null) { + option.text = sourceInfo.label || 'microphone ' + (audioSelect.length + 1); + audioSelect.appendChild(option); + } else if (sourceInfo.kind === 'videoinput' && videoSelect !== null) { + option.text = sourceInfo.label || 'camera ' + (videoSelect.length + 1); + videoSelect.appendChild(option); + } else if (sourceInfo.kind === 'audiooutput') { + option.text = sourceInfo.label || 'speaker ' + (outputSelect.length + 1); + outputSelect.appendChild(option); + } + } + + // hide empty options + if ($('#audioOutput option').length === 0) { + var option = document.createElement("option"); + option.value = sourceInfo.deviceId; + option.text = 'Default'; + outputSelect.appendChild(option); + } + else { + var val = localStorage.getItem("audioOutput"); + if (val !== null) { + $('#audioOutput').val(val); + } + outputSelect.onchange = OCA.SpreedMe.app.onAudioOutputChange; + } + + if ($('#videoSource option').length === 0) { + $('#videoSource').hide(); + } + else { + var val = localStorage.getItem("videoSource"); + if (val !== null) { + $('#videoSource').val(val); + } + videoSelect.onchange = OCA.SpreedMe.app.onVideoSourceChange; + } + + if ($('#audioSource option').length === 0) { + $('#audioSource').hide(); + } + else { + var val = localStorage.getItem("audioSource"); + if (val !== null) { + $('#audioSource').val(val); + } + audioSelect.onchange = OCA.SpreedMe.app.onAudioSourceChange; + } + + }, + + initSettings: function() { + navigator.mediaDevices.enumerateDevices().then(this.gotSources); + }, + + settingsButtonClicked: function(e) { + e.preventDefault(); + $('.settings-menu').toggle(); }, _canModerate: function() { diff --git a/js/webrtc.js b/js/webrtc.js index 8a8cf16b82a..1bd7b1a8f63 100644 --- a/js/webrtc.js +++ b/js/webrtc.js @@ -1072,6 +1072,11 @@ var spreedPeerConnectionTable = []; return; } + // set the output device + if (localStorage.getItem("audioOutput") !== null) { + video.setSinkId(localStorage.getItem("audioOutput")); + } + var videoContainer = $(OCA.SpreedMe.videos.getContainerId(peer.id)); if (videoContainer.length) { var userId = spreedMappingTable[peer.id];