allow user to select camera and microphone
This commit is contained in:
parent
b4b9160fcb
commit
f65d630ad8
147
conference.js
147
conference.js
|
@ -19,7 +19,7 @@ const ConferenceErrors = JitsiMeetJS.errors.conference;
|
|||
const TrackEvents = JitsiMeetJS.events.track;
|
||||
const TrackErrors = JitsiMeetJS.errors.track;
|
||||
|
||||
let room, connection, localTracks, localAudio, localVideo, roomLocker;
|
||||
let room, connection, localAudio, localVideo, roomLocker;
|
||||
|
||||
/**
|
||||
* Known custom conference commands.
|
||||
|
@ -120,6 +120,8 @@ function createLocalTracks (...devices) {
|
|||
// copy array to avoid mutations inside library
|
||||
devices: devices.slice(0),
|
||||
resolution: config.resolution,
|
||||
cameraDeviceId: APP.settings.getCameraDeviceId(),
|
||||
micDeviceId: APP.settings.getMicDeviceId(),
|
||||
// adds any ff fake device settings if any
|
||||
firefox_fake_device: config.firefox_fake_device
|
||||
}).catch(function (err) {
|
||||
|
@ -293,11 +295,19 @@ export default {
|
|||
]);
|
||||
}).then(([tracks, con]) => {
|
||||
console.log('initialized with %s local tracks', tracks.length);
|
||||
localTracks = tracks;
|
||||
connection = con;
|
||||
this._createRoom();
|
||||
this._createRoom(tracks);
|
||||
this.isDesktopSharingEnabled =
|
||||
JitsiMeetJS.isDesktopSharingEnabled();
|
||||
|
||||
// update list of available devices
|
||||
if (JitsiMeetJS.isDeviceListAvailable() &&
|
||||
JitsiMeetJS.isDeviceChangeAvailable()) {
|
||||
JitsiMeetJS.enumerateDevices((devices) => {
|
||||
this.availableDevices = devices;
|
||||
APP.UI.onAvailableDevicesChanged();
|
||||
});
|
||||
}
|
||||
// XXX The API will take care of disconnecting from the XMPP server
|
||||
// (and, thus, leaving the room) on unload.
|
||||
return new Promise((resolve, reject) => {
|
||||
|
@ -360,6 +370,10 @@ export default {
|
|||
listMembersIds () {
|
||||
return room.getParticipants().map(p => p.getId());
|
||||
},
|
||||
/**
|
||||
* List of available cameras and microphones.
|
||||
*/
|
||||
availableDevices: [],
|
||||
/**
|
||||
* Check if SIP is supported.
|
||||
* @returns {boolean}
|
||||
|
@ -449,32 +463,30 @@ export default {
|
|||
getLogs () {
|
||||
return room.getLogs();
|
||||
},
|
||||
_createRoom () {
|
||||
_createRoom (localTracks) {
|
||||
room = connection.initJitsiConference(APP.conference.roomName,
|
||||
this._getConferenceOptions());
|
||||
this.localId = room.myUserId();
|
||||
localTracks.forEach((track) => {
|
||||
if(track.isAudioTrack()) {
|
||||
localAudio = track;
|
||||
}
|
||||
else if (track.isVideoTrack()) {
|
||||
localVideo = track;
|
||||
}
|
||||
room.addTrack(track);
|
||||
APP.UI.addLocalStream(track);
|
||||
|
||||
if (track.isAudioTrack()) {
|
||||
this.useAudioStream(track);
|
||||
} else if (track.isVideoTrack()) {
|
||||
this.useVideoStream(track);
|
||||
}
|
||||
});
|
||||
roomLocker = createRoomLocker(room);
|
||||
this._room = room; // FIXME do not use this
|
||||
this.localId = room.myUserId();
|
||||
|
||||
let email = APP.settings.getEmail();
|
||||
email && sendEmail(email);
|
||||
|
||||
let nick = APP.settings.getDisplayName();
|
||||
(config.useNicks && !nick) && (() => {
|
||||
if (config.useNicks && !nick) {
|
||||
nick = APP.UI.askForNickname();
|
||||
APP.settings.setDisplayName(nick);
|
||||
})();
|
||||
}
|
||||
nick && room.setDisplayName(nick);
|
||||
|
||||
this._setupListeners();
|
||||
|
@ -489,6 +501,55 @@ export default {
|
|||
return options;
|
||||
},
|
||||
|
||||
/**
|
||||
* Start using provided video stream.
|
||||
* Stops previous video stream.
|
||||
* @param {JitsiLocalTrack} [stream] new stream to use or null
|
||||
*/
|
||||
useVideoStream (stream) {
|
||||
if (localVideo) {
|
||||
localVideo.stop();
|
||||
}
|
||||
localVideo = stream;
|
||||
|
||||
if (stream) {
|
||||
this.videoMuted = stream.isMuted();
|
||||
|
||||
APP.UI.addLocalStream(stream);
|
||||
|
||||
this.isSharingScreen = stream.videoType === 'desktop';
|
||||
} else {
|
||||
this.videoMuted = false;
|
||||
this.isSharingScreen = false;
|
||||
}
|
||||
|
||||
APP.UI.setVideoMuted(this.localId, this.videoMuted);
|
||||
|
||||
APP.UI.updateDesktopSharingButtons();
|
||||
},
|
||||
|
||||
/**
|
||||
* Start using provided audio stream.
|
||||
* Stops previous audio stream.
|
||||
* @param {JitsiLocalTrack} [stream] new stream to use or null
|
||||
*/
|
||||
useAudioStream (stream) {
|
||||
if (localAudio) {
|
||||
localAudio.stop();
|
||||
}
|
||||
localAudio = stream;
|
||||
|
||||
if (stream) {
|
||||
this.audioMuted = stream.isMuted();
|
||||
|
||||
APP.UI.addLocalStream(stream);
|
||||
} else {
|
||||
this.audioMuted = false;
|
||||
}
|
||||
|
||||
APP.UI.setAudioMuted(this.localId, this.audioMuted);
|
||||
},
|
||||
|
||||
videoSwitchInProgress: false,
|
||||
toggleScreenSharing () {
|
||||
if (this.videoSwitchInProgress) {
|
||||
|
@ -507,22 +568,13 @@ export default {
|
|||
createLocalTracks('video').then(function ([stream]) {
|
||||
return room.addTrack(stream);
|
||||
}).then((stream) => {
|
||||
if (localVideo) {
|
||||
localVideo.stop();
|
||||
}
|
||||
localVideo = stream;
|
||||
this.videoMuted = stream.isMuted();
|
||||
APP.UI.setVideoMuted(this.localId, this.videoMuted);
|
||||
|
||||
APP.UI.addLocalStream(stream);
|
||||
console.log('sharing local video');
|
||||
}).catch((err) => {
|
||||
localVideo = null;
|
||||
console.error('failed to share local video', err);
|
||||
}).then(() => {
|
||||
this.useVideoStream(stream);
|
||||
this.videoSwitchInProgress = false;
|
||||
this.isSharingScreen = false;
|
||||
APP.UI.updateDesktopSharingButtons();
|
||||
console.log('sharing local video');
|
||||
}).catch(function (err) {
|
||||
this.useVideoStream(null);
|
||||
this.videoSwitchInProgress = false;
|
||||
console.error('failed to share local video', err);
|
||||
});
|
||||
} else {
|
||||
// stop sharing video and share desktop
|
||||
|
@ -541,19 +593,8 @@ export default {
|
|||
);
|
||||
return room.addTrack(stream);
|
||||
}).then((stream) => {
|
||||
if (localVideo) {
|
||||
localVideo.stop();
|
||||
}
|
||||
localVideo = stream;
|
||||
|
||||
this.videoMuted = stream.isMuted();
|
||||
APP.UI.setVideoMuted(this.localId, this.videoMuted);
|
||||
|
||||
APP.UI.addLocalStream(stream);
|
||||
|
||||
this.useVideoStream(stream);
|
||||
this.videoSwitchInProgress = false;
|
||||
this.isSharingScreen = true;
|
||||
APP.UI.updateDesktopSharingButtons();
|
||||
console.log('sharing local desktop');
|
||||
}).catch((err) => {
|
||||
this.videoSwitchInProgress = false;
|
||||
|
@ -907,6 +948,30 @@ export default {
|
|||
room.pinParticipant(id);
|
||||
});
|
||||
|
||||
APP.UI.addListener(
|
||||
UIEvents.VIDEO_DEVICE_CHANGED,
|
||||
(cameraDeviceId) => {
|
||||
APP.settings.setCameraDeviceId(cameraDeviceId);
|
||||
createLocalTracks('video').then(([stream]) => {
|
||||
room.addTrack(stream);
|
||||
this.useVideoStream(stream);
|
||||
console.log('switched local video device');
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
APP.UI.addListener(
|
||||
UIEvents.AUDIO_DEVICE_CHANGED,
|
||||
(micDeviceId) => {
|
||||
APP.settings.setMicDeviceId(micDeviceId);
|
||||
createLocalTracks('audio').then(([stream]) => {
|
||||
room.addTrack(stream);
|
||||
this.useAudioStream(stream);
|
||||
console.log('switched local audio device');
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
APP.UI.addListener(
|
||||
UIEvents.TOGGLE_SCREENSHARING, this.toggleScreenSharing.bind(this)
|
||||
);
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#settingsmenu {
|
||||
background: black;
|
||||
color: #00ccff;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
#settingsmenu input, select {
|
||||
|
@ -52,6 +53,10 @@
|
|||
#startMutedOptions {
|
||||
padding-left: 10%;
|
||||
text-indent: -10%;
|
||||
|
||||
/* clearfix */
|
||||
overflow: auto;
|
||||
zoom: 1;
|
||||
}
|
||||
|
||||
#startAudioMuted {
|
||||
|
@ -66,3 +71,20 @@
|
|||
width: 94%;
|
||||
float: left;
|
||||
}
|
||||
|
||||
#devicesOptions {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#devicesOptions label {
|
||||
display: block;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
#devicesOptions span {
|
||||
padding-left: 10%;
|
||||
}
|
||||
|
||||
#devicesOptions select {
|
||||
height: 40px;
|
||||
}
|
||||
|
|
10
index.html
10
index.html
|
@ -231,6 +231,16 @@
|
|||
<span data-i18n="settings.startVideoMuted"></span>
|
||||
</label>
|
||||
</div>
|
||||
<div id="devicesOptions">
|
||||
<label className="devicesOptionsLabel">
|
||||
<span data-i18n="settings.selectCamera"></span>
|
||||
<select id="selectCamera"></select>
|
||||
</label>
|
||||
<label className="devicesOptionsLabel">
|
||||
<span data-i18n="settings.selectMic"></span>
|
||||
<select id="selectMic"></select>
|
||||
</label>
|
||||
</div>
|
||||
<button id="updateSettings" data-i18n="settings.update"></button>
|
||||
<a id="downloadlog" data-container="body" data-toggle="popover" data-placement="right" data-i18n="[data-content]downloadlogs" ><i class="fa fa-cloud-download"></i></a>
|
||||
</div>
|
||||
|
|
|
@ -84,7 +84,9 @@
|
|||
"update": "Update",
|
||||
"name": "Name",
|
||||
"startAudioMuted": "start without audio",
|
||||
"startVideoMuted": "start without video"
|
||||
"startVideoMuted": "start without video",
|
||||
"selectCamera": "select camera",
|
||||
"selectMic": "select microphone"
|
||||
},
|
||||
"videothumbnail":
|
||||
{
|
||||
|
|
|
@ -660,9 +660,9 @@ UI.askForNickname = function () {
|
|||
*/
|
||||
UI.setAudioMuted = function (id, muted) {
|
||||
VideoLayout.onAudioMute(id, muted);
|
||||
if(APP.conference.isLocalId(id))
|
||||
UIUtil.buttonClick("#toolbar_button_mute",
|
||||
"icon-microphone icon-mic-disabled");
|
||||
if (APP.conference.isLocalId(id)) {
|
||||
Toolbar.markAudioIconAsMuted(muted);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -670,8 +670,9 @@ UI.setAudioMuted = function (id, muted) {
|
|||
*/
|
||||
UI.setVideoMuted = function (id, muted) {
|
||||
VideoLayout.onVideoMute(id, muted);
|
||||
if(APP.conference.isLocalId(id))
|
||||
$('#toolbar_button_camera').toggleClass("icon-camera-disabled", muted);
|
||||
if (APP.conference.isLocalId(id)) {
|
||||
Toolbar.markVideoIconAsMuted(muted);
|
||||
}
|
||||
};
|
||||
|
||||
UI.addListener = function (type, listener) {
|
||||
|
@ -1040,6 +1041,14 @@ UI.onStartMutedChanged = function () {
|
|||
SettingsMenu.onStartMutedChanged();
|
||||
};
|
||||
|
||||
/**
|
||||
* Update list of available physical devices.
|
||||
* @param {object[]} devices new list of available devices
|
||||
*/
|
||||
UI.onAvailableDevicesChanged = function (devices) {
|
||||
SettingsMenu.onAvailableDevicesChanged(devices);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the id of the current video shown on large.
|
||||
* Currently used by tests (torture).
|
||||
|
|
|
@ -21,6 +21,21 @@ function generateLanguagesSelectBox() {
|
|||
return html + "</select>";
|
||||
}
|
||||
|
||||
function generateDevicesOptions(items, selectedId) {
|
||||
return items.map(function (item) {
|
||||
let attrs = {
|
||||
value: item.deviceId
|
||||
};
|
||||
|
||||
if (item.deviceId === selectedId) {
|
||||
attrs.selected = 'selected';
|
||||
}
|
||||
|
||||
let attrsStr = UIUtil.attrsToString(attrs);
|
||||
return `<option ${attrsStr}>${item.label}</option>`;
|
||||
}).join('\n');
|
||||
}
|
||||
|
||||
|
||||
export default {
|
||||
init (emitter) {
|
||||
|
@ -51,12 +66,23 @@ export default {
|
|||
startVideoMuted
|
||||
);
|
||||
}
|
||||
|
||||
let cameraDeviceId = $('#selectCamera').val();
|
||||
if (cameraDeviceId !== Settings.getCameraDeviceId()) {
|
||||
emitter.emit(UIEvents.VIDEO_DEVICE_CHANGED, cameraDeviceId);
|
||||
}
|
||||
|
||||
let micDeviceId = $('#selectMic').val();
|
||||
if (micDeviceId !== Settings.getMicDeviceId()) {
|
||||
emitter.emit(UIEvents.AUDIO_DEVICE_CHANGED, micDeviceId);
|
||||
}
|
||||
}
|
||||
|
||||
let startMutedBlock = $("#startMutedOptions");
|
||||
startMutedBlock.before(generateLanguagesSelectBox());
|
||||
APP.translation.translateElement($("#languages_selectbox"));
|
||||
|
||||
this.onAvailableDevicesChanged();
|
||||
this.onRoleChanged();
|
||||
this.onStartMutedChanged();
|
||||
|
||||
|
@ -94,5 +120,25 @@ export default {
|
|||
|
||||
changeAvatar (avatarUrl) {
|
||||
$('#avatar').attr('src', avatarUrl);
|
||||
},
|
||||
|
||||
onAvailableDevicesChanged () {
|
||||
let devices = APP.conference.availableDevices;
|
||||
if (!devices.length) {
|
||||
$('#devicesOptions').hide();
|
||||
return;
|
||||
}
|
||||
|
||||
let audio = devices.filter(device => device.kind === 'audioinput');
|
||||
let video = devices.filter(device => device.kind === 'videoinput');
|
||||
|
||||
$('#selectCamera').html(
|
||||
generateDevicesOptions(video, Settings.getCameraDeviceId())
|
||||
);
|
||||
$('#selectMic').html(
|
||||
generateDevicesOptions(audio, Settings.getMicDeviceId())
|
||||
);
|
||||
|
||||
$('#devicesOptions').show();
|
||||
}
|
||||
};
|
||||
|
|
|
@ -385,6 +385,22 @@ const Toolbar = {
|
|||
|
||||
updateRecordingState (state) {
|
||||
setRecordingButtonState(state);
|
||||
},
|
||||
|
||||
/**
|
||||
* Marks video icon as muted or not.
|
||||
* @param {boolean} muted if icon should look like muted or not
|
||||
*/
|
||||
markVideoIconAsMuted (muted) {
|
||||
$('#toolbar_button_camera').toggleClass("icon-camera-disabled", muted);
|
||||
},
|
||||
|
||||
/**
|
||||
* Marks audio icon as muted or not.
|
||||
* @param {boolean} muted if icon should look like muted or not
|
||||
*/
|
||||
markAudioIconAsMuted (muted) {
|
||||
$('#toolbar_button_mute').toggleClass("icon-microphone", !muted).toggleClass("icon-mic-disabled", muted);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -139,6 +139,17 @@
|
|||
return document.fullScreen
|
||||
|| document.mozFullScreen
|
||||
|| document.webkitIsFullScreen;
|
||||
},
|
||||
|
||||
/**
|
||||
* Create html attributes string out of object properties.
|
||||
* @param {Object} attrs object with properties
|
||||
* @returns {String} string of html element attributes
|
||||
*/
|
||||
attrsToString: function (attrs) {
|
||||
return Object.keys(attrs).map(
|
||||
key => ` ${key}="${attrs[key]}"`
|
||||
).join(' ');
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -17,6 +17,11 @@ function LocalVideo(VideoLayout, emitter) {
|
|||
this.flipX = true;
|
||||
this.isLocal = true;
|
||||
this.emitter = emitter;
|
||||
Object.defineProperty(this, 'id', {
|
||||
get: function () {
|
||||
return APP.conference.localId;
|
||||
}
|
||||
});
|
||||
SmallVideo.call(this);
|
||||
}
|
||||
|
||||
|
@ -195,8 +200,4 @@ LocalVideo.prototype.changeVideo = function (stream) {
|
|||
stream.on(TrackEvents.TRACK_STOPPED, endedHandler);
|
||||
};
|
||||
|
||||
LocalVideo.prototype.joined = function (id) {
|
||||
this.id = id;
|
||||
};
|
||||
|
||||
export default LocalVideo;
|
||||
|
|
|
@ -170,11 +170,8 @@ var VideoLayout = {
|
|||
* and setting them assume the id is already set.
|
||||
*/
|
||||
mucJoined () {
|
||||
let id = APP.conference.localId;
|
||||
localVideoThumbnail.joined(id);
|
||||
|
||||
if (largeVideo && !largeVideo.id) {
|
||||
this.updateLargeVideo(id, true);
|
||||
this.updateLargeVideo(APP.conference.localId, true);
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import {generateUsername} from '../util/UsernameGenerator';
|
||||
|
||||
var email = '';
|
||||
var displayName = '';
|
||||
var userId;
|
||||
var language = null;
|
||||
let email = '';
|
||||
let displayName = '';
|
||||
let userId;
|
||||
let language = null;
|
||||
let cameraDeviceId = '';
|
||||
let micDeviceId = '';
|
||||
|
||||
function supportsLocalStorage() {
|
||||
try {
|
||||
|
@ -32,6 +34,8 @@ if (supportsLocalStorage()) {
|
|||
email = window.localStorage.email || '';
|
||||
displayName = window.localStorage.displayname || '';
|
||||
language = window.localStorage.language;
|
||||
cameraDeviceId = window.localStorage.cameraDeviceId || '';
|
||||
micDeviceId = window.localStorage.micDeviceId || '';
|
||||
} else {
|
||||
console.log("local storage is not supported");
|
||||
userId = generateUniqueId();
|
||||
|
@ -86,5 +90,41 @@ export default {
|
|||
setLanguage: function (lang) {
|
||||
language = lang;
|
||||
window.localStorage.language = lang;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get device id of the camera which is currently in use.
|
||||
* Empty string stands for default device.
|
||||
* @returns {String}
|
||||
*/
|
||||
getCameraDeviceId: function () {
|
||||
return cameraDeviceId;
|
||||
},
|
||||
/**
|
||||
* Set device id of the camera which is currently in use.
|
||||
* Empty string stands for default device.
|
||||
* @param {string} newId new camera device id
|
||||
*/
|
||||
setCameraDeviceId: function (newId = '') {
|
||||
cameraDeviceId = newId;
|
||||
window.localStorage.cameraDeviceId = newId;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get device id of the microphone which is currently in use.
|
||||
* Empty string stands for default device.
|
||||
* @returns {String}
|
||||
*/
|
||||
getMicDeviceId: function () {
|
||||
return micDeviceId;
|
||||
},
|
||||
/**
|
||||
* Set device id of the microphone which is currently in use.
|
||||
* Empty string stands for default device.
|
||||
* @param {string} newId new microphone device id
|
||||
*/
|
||||
setMicDeviceId: function (newId = '') {
|
||||
micDeviceId = newId;
|
||||
window.localStorage.micDeviceId = newId;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -41,5 +41,7 @@ export default {
|
|||
LOGOUT: "UI.logout",
|
||||
RECORDING_TOGGLE: "UI.recording_toggle",
|
||||
SIP_DIAL: "UI.sip_dial",
|
||||
SUBEJCT_CHANGED: "UI.subject_changed"
|
||||
SUBEJCT_CHANGED: "UI.subject_changed",
|
||||
VIDEO_DEVICE_CHANGED: "UI.video_device_changed",
|
||||
AUDIO_DEVICE_CHANGED: "UI.audio_device_changed"
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue