Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Select media device for video and audio input #1450

Closed
wants to merge 9 commits into from
45 changes: 45 additions & 0 deletions css/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -922,9 +922,54 @@ input[type="password"] {
}
}
}
.talk-settings-button {
position: relative;

.button {
cursor: pointer;
width: 50px;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should have the same size as all other icons

height: 50px;
display: block;
background-color: transparent;
border: none;
margin: 0;
opacity: .7;
&:hover,
&:focus,
&:active {
opacity: 1;
}
}
}
}
}

.icon-speaker {
Peterede marked this conversation as resolved.
Show resolved Hide resolved
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
Expand Down
1 change: 1 addition & 0 deletions img/speaker.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
105 changes: 105 additions & 0 deletions js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,106 @@
}.bind(this));
},

onAudioOutputChange: function() {
localStorage.setItem('audioOutput', $('#audioOutput').val());

},

onAudioSourceChange: function() {
localStorage.setItem("audioSource", $('#audioSource').val());
Peterede marked this conversation as resolved.
Show resolved Hide resolved
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();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the code is not needed remove it.

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 = $('<a>').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;
Expand Down Expand Up @@ -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) {
Expand Down
107 changes: 107 additions & 0 deletions js/views/callinfoview.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,33 @@
' <div class="clipboard-button"><span class="button icon-clippy"></span></div>' +
' </div>' +
'{{/if}}' +
'<div class="talk-settings-button">' +
' <span class="button icon-settings"></span>' +
'</div>' +
'</div>' +
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
'</div>' +

invalid HTML structure

'<div class="settings-menu hidden">' +
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure this should be in the callinfo-section, as it is not specific to the conversation, but in general?

Maybe it's time for us to start using the settings feature in the left sidebar. However I'm not sure we should go through the trouble, when we can do this easily after switching to Vue.JS ( #1347 )

Copy link
Contributor Author

@Peterede Peterede Jan 22, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is specific to the conversation just like the 'join call' button, the settings are used directly when joining the call and affect how the call appears and sounds. Putting the settings in the left sidebar might make them less obvious (and also I force the left sidebar to be hidden), the more exposed the camera, microphone and speaker settings are the better as this affects the user experience. If when the user connects their microphone is not working or their video is not working then the camera and microphone settings should be the first thing they check.

' <form class="settings-form">' +
' {{#if canPublish}}' +
' <div>' +
' <span class="menuitem icon-video settings-option">' +
' </span>' +
' <select id="videoSource" class="settings-input"></select>'+
' </div>' +
' <div>' +
' <span class="menuitem icon-audio settings-option">' +
' </span>' +
' <select id="audioSource" class="settings-input"></select>'+
' </div>' +
' {{/if}}' +
' <div>' +
' <span class="menuitem icon-speaker settings-option">' +
' </span>' +
' <select id="audioOutput" class="settings-input"></select>'+
' </div>' +
' </form>' +
'</div>';


var CallInfoView = Marionette.View.extend({

tagName: 'div',
Expand All @@ -91,6 +116,7 @@
canFullModerate: this._canFullModerate(),
isPublic: this.model.get('type') === 3,
showShareLink: !canModerate && this.model.get('type') === 3,
canPublish: OCA.SpreedMe.canPublish(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TypeError: OCA.SpreedMe.canPublish is not a function

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is from another pull request #1298

isDeletable: canModerate && (Object.keys(this.model.get('participants')).length > 2 || this.model.get('numGuests') > 0)
});
},
Expand All @@ -112,6 +138,10 @@
'passwordConfirm': '.password-confirm',

'menu': '.password-menu',

'settingsButton': '.talk-settings-button',
'settingsMenu': '.settings-menu',
'settingsInput': '.settings-input',
},

regions: {
Expand All @@ -126,6 +156,7 @@
'click @ui.passwordButton': 'showPasswordInput',
'click @ui.passwordConfirm': 'confirmPassword',
'submit @ui.passwordForm': 'confirmPassword',
'click @ui.settingsButton': 'settingsButtonClicked',
},

modelEvents: {
Expand Down Expand Up @@ -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() {
Expand Down
5 changes: 5 additions & 0 deletions js/webrtc.js
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand Down