Switch local audio and video track when list of available devices changes
This commit is contained in:
parent
eda11f4657
commit
b270256a7a
231
conference.js
231
conference.js
|
@ -170,16 +170,25 @@ function hangup (requestFeedback = false) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create local tracks of specified types.
|
* Create local tracks of specified types.
|
||||||
* @param {string[]} devices required track types ('audio', 'video' etc.)
|
* @param {string[]} devices - required track types ('audio', 'video' etc.)
|
||||||
|
* @param {string|null} [cameraDeviceId] - camera device id, if undefined - one
|
||||||
|
* from settings will be used
|
||||||
|
* @param {string|null} [micDeviceId] - microphone device id, if undefined - one
|
||||||
|
* from settings will be used
|
||||||
* @returns {Promise<JitsiLocalTrack[]>}
|
* @returns {Promise<JitsiLocalTrack[]>}
|
||||||
*/
|
*/
|
||||||
function createLocalTracks (...devices) {
|
function createLocalTracks (devices, cameraDeviceId, micDeviceId) {
|
||||||
return JitsiMeetJS.createLocalTracks({
|
return JitsiMeetJS.createLocalTracks({
|
||||||
// copy array to avoid mutations inside library
|
// copy array to avoid mutations inside library
|
||||||
devices: devices.slice(0),
|
devices: devices.slice(0),
|
||||||
resolution: config.resolution,
|
resolution: config.resolution,
|
||||||
cameraDeviceId: APP.settings.getCameraDeviceId(),
|
cameraDeviceId: typeof cameraDeviceId === 'undefined'
|
||||||
micDeviceId: APP.settings.getMicDeviceId(),
|
|| cameraDeviceId === null
|
||||||
|
? APP.settings.getCameraDeviceId()
|
||||||
|
: cameraDeviceId,
|
||||||
|
micDeviceId: typeof micDeviceId === 'undefined' || micDeviceId === null
|
||||||
|
? APP.settings.getMicDeviceId()
|
||||||
|
: micDeviceId,
|
||||||
// adds any ff fake device settings if any
|
// adds any ff fake device settings if any
|
||||||
firefox_fake_device: config.firefox_fake_device
|
firefox_fake_device: config.firefox_fake_device
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
||||||
|
@ -327,6 +336,7 @@ export default {
|
||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
init(options) {
|
init(options) {
|
||||||
|
let self = this;
|
||||||
this.roomName = options.roomName;
|
this.roomName = options.roomName;
|
||||||
JitsiMeetJS.setLogLevel(JitsiMeetJS.logLevels.TRACE);
|
JitsiMeetJS.setLogLevel(JitsiMeetJS.logLevels.TRACE);
|
||||||
|
|
||||||
|
@ -356,9 +366,9 @@ export default {
|
||||||
return JitsiMeetJS.init(config).then(() => {
|
return JitsiMeetJS.init(config).then(() => {
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
// try to retrieve audio and video
|
// try to retrieve audio and video
|
||||||
createLocalTracks('audio', 'video')
|
createLocalTracks(['audio', 'video'])
|
||||||
// if failed then try to retrieve only audio
|
// if failed then try to retrieve only audio
|
||||||
.catch(() => createLocalTracks('audio'))
|
.catch(() => createLocalTracks(['audio']))
|
||||||
// if audio also failed then just return empty array
|
// if audio also failed then just return empty array
|
||||||
.catch(() => []),
|
.catch(() => []),
|
||||||
connect(options.roomName)
|
connect(options.roomName)
|
||||||
|
@ -370,15 +380,49 @@ export default {
|
||||||
this.isDesktopSharingEnabled =
|
this.isDesktopSharingEnabled =
|
||||||
JitsiMeetJS.isDesktopSharingEnabled();
|
JitsiMeetJS.isDesktopSharingEnabled();
|
||||||
|
|
||||||
|
// if user didn't give access to mic or camera or doesn't have
|
||||||
|
// them at all, we disable corresponding toolbar buttons
|
||||||
|
if (!tracks.find((t) => t.isAudioTrack())) {
|
||||||
|
APP.UI.disableMicrophoneButton();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tracks.find((t) => t.isVideoTrack())) {
|
||||||
|
APP.UI.disableCameraButton();
|
||||||
|
}
|
||||||
|
|
||||||
// update list of available devices
|
// update list of available devices
|
||||||
if (JitsiMeetJS.mediaDevices.isDeviceListAvailable() &&
|
if (JitsiMeetJS.mediaDevices.isDeviceListAvailable() &&
|
||||||
JitsiMeetJS.mediaDevices.isDeviceChangeAvailable()) {
|
JitsiMeetJS.mediaDevices.isDeviceChangeAvailable()) {
|
||||||
JitsiMeetJS.mediaDevices.enumerateDevices(
|
JitsiMeetJS.mediaDevices.enumerateDevices(function(devices) {
|
||||||
APP.UI.onAvailableDevicesChanged);
|
// Ugly way to synchronize real device IDs with local
|
||||||
|
// storage and settings menu. This is a workaround until
|
||||||
|
// getConstraints() method will be implemented in browsers.
|
||||||
|
if (localAudio) {
|
||||||
|
localAudio._setRealDeviceIdFromDeviceList(devices);
|
||||||
|
APP.settings.setMicDeviceId(localAudio.getDeviceId());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (localVideo) {
|
||||||
|
localVideo._setRealDeviceIdFromDeviceList(devices);
|
||||||
|
APP.settings.setCameraDeviceId(
|
||||||
|
localVideo.getDeviceId());
|
||||||
|
}
|
||||||
|
|
||||||
|
APP.UI.onAvailableDevicesChanged(devices);
|
||||||
|
});
|
||||||
|
|
||||||
JitsiMeetJS.mediaDevices.addEventListener(
|
JitsiMeetJS.mediaDevices.addEventListener(
|
||||||
JitsiMeetJS.events.mediaDevices.DEVICE_LIST_CHANGED,
|
JitsiMeetJS.events.mediaDevices.DEVICE_LIST_CHANGED,
|
||||||
APP.UI.onAvailableDevicesChanged);
|
(devices) => {
|
||||||
|
// Just defer callback until other event callbacks are
|
||||||
|
// processed.
|
||||||
|
window.setTimeout(() => {
|
||||||
|
checkLocalDevicesAfterDeviceListChanged(devices)
|
||||||
|
.then(() => {
|
||||||
|
APP.UI.onAvailableDevicesChanged(devices);
|
||||||
|
});
|
||||||
|
}, 0);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if (config.iAmRecorder)
|
if (config.iAmRecorder)
|
||||||
this.recorder = new Recorder();
|
this.recorder = new Recorder();
|
||||||
|
@ -388,6 +432,164 @@ export default {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
(new ConferenceConnector(resolve, reject)).connect();
|
(new ConferenceConnector(resolve, reject)).connect();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function checkAudioOutputDeviceAfterDeviceListChanged(newDevices) {
|
||||||
|
if (!JitsiMeetJS.mediaDevices
|
||||||
|
.isDeviceChangeAvailable('output')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var selectedAudioOutputDeviceId =
|
||||||
|
APP.settings.getAudioOutputDeviceId(),
|
||||||
|
availableAudioOutputDevices = newDevices.filter(d => {
|
||||||
|
return d.kind === 'audiooutput';
|
||||||
|
});
|
||||||
|
|
||||||
|
if (selectedAudioOutputDeviceId !== 'default' &&
|
||||||
|
!availableAudioOutputDevices.find(d =>
|
||||||
|
d.deviceId === selectedAudioOutputDeviceId)) {
|
||||||
|
APP.settings.setAudioOutputDeviceId('default');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkLocalDevicesAfterDeviceListChanged(newDevices) {
|
||||||
|
checkAudioOutputDeviceAfterDeviceListChanged(newDevices);
|
||||||
|
|
||||||
|
let availableAudioInputDevices = newDevices.filter(
|
||||||
|
d => d.kind === 'audioinput'),
|
||||||
|
availableVideoInputDevices = newDevices.filter(
|
||||||
|
d => d.kind === 'videoinput'),
|
||||||
|
selectedAudioInputDeviceId = APP.settings.getMicDeviceId(),
|
||||||
|
selectedVideoInputDeviceId =
|
||||||
|
APP.settings.getCameraDeviceId(),
|
||||||
|
selectedAudioInputDevice = availableAudioInputDevices.find(
|
||||||
|
d => d.deviceId === selectedAudioInputDeviceId),
|
||||||
|
selectedVideoInputDevice = availableVideoInputDevices.find(
|
||||||
|
d => d.deviceId === selectedVideoInputDeviceId),
|
||||||
|
tracksToCreate = [],
|
||||||
|
micIdToUse = null,
|
||||||
|
cameraIdToUse = null;
|
||||||
|
|
||||||
|
// Here we handle case when no device was initially plugged, but
|
||||||
|
// then it's connected OR new device was connected when previous
|
||||||
|
// track has ended.
|
||||||
|
if (!localAudio || localAudio.disposed || localAudio.isEnded()){
|
||||||
|
if (availableAudioInputDevices.length
|
||||||
|
&& availableAudioInputDevices[0].label !== '') {
|
||||||
|
tracksToCreate.push('audio');
|
||||||
|
micIdToUse = availableAudioInputDevices[0].deviceId;
|
||||||
|
} else {
|
||||||
|
APP.UI.disableMicrophoneButton();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((!localVideo || localVideo.disposed || localVideo.isEnded())
|
||||||
|
&& !self.isSharingScreen){
|
||||||
|
if (availableVideoInputDevices.length
|
||||||
|
&& availableVideoInputDevices[0].label !== '') {
|
||||||
|
tracksToCreate.push('video');
|
||||||
|
cameraIdToUse = availableVideoInputDevices[0].deviceId;
|
||||||
|
} else {
|
||||||
|
APP.UI.disableCameraButton();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (localAudio && !localAudio.disposed && !localAudio.isEnded()
|
||||||
|
&& selectedAudioInputDevice
|
||||||
|
&& selectedAudioInputDeviceId !== localAudio.getDeviceId()
|
||||||
|
&& tracksToCreate.indexOf('audio') === -1) {
|
||||||
|
tracksToCreate.push('audio');
|
||||||
|
micIdToUse = selectedAudioInputDeviceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (localVideo && !localVideo.disposed && !localVideo.isEnded()
|
||||||
|
&& selectedVideoInputDevice
|
||||||
|
&& selectedVideoInputDeviceId !== localVideo.getDeviceId()
|
||||||
|
&& tracksToCreate.indexOf('video') === -1
|
||||||
|
&& !self.isSharingScreen) {
|
||||||
|
tracksToCreate.push('video');
|
||||||
|
cameraIdToUse = selectedVideoInputDeviceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tracksToCreate.length) {
|
||||||
|
return createNewTracks(
|
||||||
|
tracksToCreate, cameraIdToUse, micIdToUse);
|
||||||
|
} else {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
function createNewTracks(type, cameraDeviceId, micDeviceId) {
|
||||||
|
return createLocalTracks(type, cameraDeviceId, micDeviceId)
|
||||||
|
.then(onTracksCreated)
|
||||||
|
.catch(() => {
|
||||||
|
// if we tried to create both audio and video tracks
|
||||||
|
// at once and failed, let's try again only with
|
||||||
|
// audio. Such situation may happen in case if we
|
||||||
|
// granted access only to microphone, but not to
|
||||||
|
// camera.
|
||||||
|
if (type.indexOf('audio') !== -1
|
||||||
|
&& type.indexOf('video') !== -1) {
|
||||||
|
return createLocalTracks(['audio'], null,
|
||||||
|
micDeviceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
.then(onTracksCreated)
|
||||||
|
.catch(() => {
|
||||||
|
// if we tried to create both audio and video tracks
|
||||||
|
// at once and failed, let's try again only with
|
||||||
|
// video. Such situation may happen in case if we
|
||||||
|
// granted access only to camera, but not to
|
||||||
|
// microphone.
|
||||||
|
if (type.indexOf('audio') !== -1
|
||||||
|
&& type.indexOf('video') !== -1) {
|
||||||
|
return createLocalTracks(['video'],
|
||||||
|
cameraDeviceId,
|
||||||
|
null);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(onTracksCreated)
|
||||||
|
.catch(() => {
|
||||||
|
// can't do anything in this case, so just ignore;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function onTracksCreated(tracks) {
|
||||||
|
tracks && tracks.forEach(track => {
|
||||||
|
if (track.isAudioTrack()) {
|
||||||
|
self.useAudioStream(track).then(() => {
|
||||||
|
console.log('switched local audio');
|
||||||
|
|
||||||
|
// If we have more than 1 device - mute.
|
||||||
|
// We check with 2 for audio, because
|
||||||
|
// it always has 'default' if device is
|
||||||
|
// available at all.
|
||||||
|
// TODO: this is not 100% solution - need
|
||||||
|
// to investigate more
|
||||||
|
if (availableAudioInputDevices.length > 2) {
|
||||||
|
muteLocalAudio(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (track.isVideoTrack()) {
|
||||||
|
self.useVideoStream(track).then(() => {
|
||||||
|
console.log('switched local video');
|
||||||
|
|
||||||
|
// TODO: maybe make video large if we
|
||||||
|
// are not in conference yet
|
||||||
|
// If we have more than 1 device - mute.
|
||||||
|
// TODO: this is not 100% solution - need
|
||||||
|
// to investigate more
|
||||||
|
if (availableVideoInputDevices.length > 1) {
|
||||||
|
muteLocalVideo(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.error("Ignored not an audio nor a "
|
||||||
|
+ "video track: ", track);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
|
@ -667,6 +869,8 @@ export default {
|
||||||
this.isSharingScreen = false;
|
this.isSharingScreen = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
stream.videoType === 'camera' && APP.UI.enableCameraButton();
|
||||||
|
|
||||||
APP.UI.setVideoMuted(this.localId, this.videoMuted);
|
APP.UI.setVideoMuted(this.localId, this.videoMuted);
|
||||||
|
|
||||||
APP.UI.updateDesktopSharingButtons();
|
APP.UI.updateDesktopSharingButtons();
|
||||||
|
@ -701,6 +905,7 @@ export default {
|
||||||
this.audioMuted = false;
|
this.audioMuted = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
APP.UI.enableMicrophoneButton();
|
||||||
APP.UI.setAudioMuted(this.localId, this.audioMuted);
|
APP.UI.setAudioMuted(this.localId, this.audioMuted);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -719,7 +924,7 @@ export default {
|
||||||
this.videoSwitchInProgress = true;
|
this.videoSwitchInProgress = true;
|
||||||
|
|
||||||
if (shareScreen) {
|
if (shareScreen) {
|
||||||
createLocalTracks('desktop').then(([stream]) => {
|
createLocalTracks(['desktop']).then(([stream]) => {
|
||||||
stream.on(
|
stream.on(
|
||||||
TrackEvents.LOCAL_TRACK_STOPPED,
|
TrackEvents.LOCAL_TRACK_STOPPED,
|
||||||
() => {
|
() => {
|
||||||
|
@ -767,7 +972,7 @@ export default {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
createLocalTracks('video').then(
|
createLocalTracks(['video']).then(
|
||||||
([stream]) => this.useVideoStream(stream)
|
([stream]) => this.useVideoStream(stream)
|
||||||
).then(() => {
|
).then(() => {
|
||||||
this.videoSwitchInProgress = false;
|
this.videoSwitchInProgress = false;
|
||||||
|
@ -1118,7 +1323,7 @@ export default {
|
||||||
UIEvents.VIDEO_DEVICE_CHANGED,
|
UIEvents.VIDEO_DEVICE_CHANGED,
|
||||||
(cameraDeviceId) => {
|
(cameraDeviceId) => {
|
||||||
APP.settings.setCameraDeviceId(cameraDeviceId);
|
APP.settings.setCameraDeviceId(cameraDeviceId);
|
||||||
createLocalTracks('video').then(([stream]) => {
|
createLocalTracks(['video']).then(([stream]) => {
|
||||||
this.useVideoStream(stream);
|
this.useVideoStream(stream);
|
||||||
console.log('switched local video device');
|
console.log('switched local video device');
|
||||||
});
|
});
|
||||||
|
@ -1129,7 +1334,7 @@ export default {
|
||||||
UIEvents.AUDIO_DEVICE_CHANGED,
|
UIEvents.AUDIO_DEVICE_CHANGED,
|
||||||
(micDeviceId) => {
|
(micDeviceId) => {
|
||||||
APP.settings.setMicDeviceId(micDeviceId);
|
APP.settings.setMicDeviceId(micDeviceId);
|
||||||
createLocalTracks('audio').then(([stream]) => {
|
createLocalTracks(['audio']).then(([stream]) => {
|
||||||
this.useAudioStream(stream);
|
this.useAudioStream(stream);
|
||||||
console.log('switched local audio device');
|
console.log('switched local audio device');
|
||||||
});
|
});
|
||||||
|
|
|
@ -58,6 +58,10 @@ html, body{
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.button[disabled] {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
.toolbar_span>span {
|
.toolbar_span>span {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
|
@ -66,7 +66,9 @@
|
||||||
"dialpad": "Show dialpad",
|
"dialpad": "Show dialpad",
|
||||||
"sharedVideoMutedPopup": "Your shared video has been muted so<br/>that you can talk to the other participants.",
|
"sharedVideoMutedPopup": "Your shared video has been muted so<br/>that you can talk to the other participants.",
|
||||||
"micMutedPopup": "Your microphone has been muted so that you<br/>would fully enjoy your shared video.",
|
"micMutedPopup": "Your microphone has been muted so that you<br/>would fully enjoy your shared video.",
|
||||||
"unableToUnmutePopup": "You cannot un-mute while the shared video is on."
|
"unableToUnmutePopup": "You cannot un-mute while the shared video is on.",
|
||||||
|
"cameraDisabled": "Camera is not available",
|
||||||
|
"micDisabled": "Microphone is not available"
|
||||||
},
|
},
|
||||||
"bottomtoolbar": {
|
"bottomtoolbar": {
|
||||||
"chat": "Open / close chat",
|
"chat": "Open / close chat",
|
||||||
|
@ -90,7 +92,9 @@
|
||||||
"selectCamera": "Select camera",
|
"selectCamera": "Select camera",
|
||||||
"selectMic": "Select microphone",
|
"selectMic": "Select microphone",
|
||||||
"selectAudioOutput": "Select audio output",
|
"selectAudioOutput": "Select audio output",
|
||||||
"followMe": "Enable follow me"
|
"followMe": "Enable follow me",
|
||||||
|
"noDevice": "None",
|
||||||
|
"noPermission": "Permission to use device is not granted"
|
||||||
},
|
},
|
||||||
"videothumbnail":
|
"videothumbnail":
|
||||||
{
|
{
|
||||||
|
|
|
@ -1142,4 +1142,32 @@ UI.onSharedVideoStop = function (id, attributes) {
|
||||||
sharedVideoManager.onSharedVideoStop(id, attributes);
|
sharedVideoManager.onSharedVideoStop(id, attributes);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disables camera toolbar button.
|
||||||
|
*/
|
||||||
|
UI.disableCameraButton = function () {
|
||||||
|
Toolbar.markVideoIconAsDisabled(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables camera toolbar button.
|
||||||
|
*/
|
||||||
|
UI.enableCameraButton = function () {
|
||||||
|
Toolbar.markVideoIconAsDisabled(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disables microphone toolbar button.
|
||||||
|
*/
|
||||||
|
UI.disableMicrophoneButton = function () {
|
||||||
|
Toolbar.markAudioIconAsDisabled(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables microphone toolbar button.
|
||||||
|
*/
|
||||||
|
UI.enableMicrophoneButton = function () {
|
||||||
|
Toolbar.markAudioIconAsDisabled(false);
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = UI;
|
module.exports = UI;
|
||||||
|
|
|
@ -30,10 +30,16 @@ function generateLanguagesOptions(items, currentLang) {
|
||||||
* Generate html select options for available physical devices.
|
* Generate html select options for available physical devices.
|
||||||
* @param {{ deviceId, label }[]} items available devices
|
* @param {{ deviceId, label }[]} items available devices
|
||||||
* @param {string} [selectedId] id of selected device
|
* @param {string} [selectedId] id of selected device
|
||||||
|
* @param {boolean} permissionGranted if permission to use selected device type
|
||||||
|
* is granted
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
function generateDevicesOptions(items, selectedId) {
|
function generateDevicesOptions(items, selectedId, permissionGranted) {
|
||||||
return items.map(function (item) {
|
if (!permissionGranted && items.length) {
|
||||||
|
return '<option data-i18n="settings.noPermission"></option>';
|
||||||
|
}
|
||||||
|
|
||||||
|
var options = items.map(function (item) {
|
||||||
let attrs = {
|
let attrs = {
|
||||||
value: item.deviceId
|
value: item.deviceId
|
||||||
};
|
};
|
||||||
|
@ -44,7 +50,13 @@ function generateDevicesOptions(items, selectedId) {
|
||||||
|
|
||||||
let attrsStr = UIUtil.attrsToString(attrs);
|
let attrsStr = UIUtil.attrsToString(attrs);
|
||||||
return `<option ${attrsStr}>${item.label}</option>`;
|
return `<option ${attrsStr}>${item.label}</option>`;
|
||||||
}).join('\n');
|
});
|
||||||
|
|
||||||
|
if (!items.length) {
|
||||||
|
options.unshift('<option data-i18n="settings.noDevice"></option>');
|
||||||
|
}
|
||||||
|
|
||||||
|
return options.join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -111,26 +123,30 @@ export default {
|
||||||
|
|
||||||
|
|
||||||
// DEVICES LIST
|
// DEVICES LIST
|
||||||
this.changeDevicesList([]);
|
if (JitsiMeetJS.mediaDevices.isDeviceListAvailable() &&
|
||||||
$('#selectCamera').change(function () {
|
JitsiMeetJS.mediaDevices.isDeviceChangeAvailable()) {
|
||||||
let cameraDeviceId = $(this).val();
|
this.changeDevicesList([]);
|
||||||
if (cameraDeviceId !== Settings.getCameraDeviceId()) {
|
|
||||||
emitter.emit(UIEvents.VIDEO_DEVICE_CHANGED, cameraDeviceId);
|
$('#selectCamera').change(function () {
|
||||||
}
|
let cameraDeviceId = $(this).val();
|
||||||
});
|
if (cameraDeviceId !== Settings.getCameraDeviceId()) {
|
||||||
$('#selectMic').change(function () {
|
emitter.emit(UIEvents.VIDEO_DEVICE_CHANGED, cameraDeviceId);
|
||||||
let micDeviceId = $(this).val();
|
}
|
||||||
if (micDeviceId !== Settings.getMicDeviceId()) {
|
});
|
||||||
emitter.emit(UIEvents.AUDIO_DEVICE_CHANGED, micDeviceId);
|
$('#selectMic').change(function () {
|
||||||
}
|
let micDeviceId = $(this).val();
|
||||||
});
|
if (micDeviceId !== Settings.getMicDeviceId()) {
|
||||||
$('#selectAudioOutput').change(function () {
|
emitter.emit(UIEvents.AUDIO_DEVICE_CHANGED, micDeviceId);
|
||||||
let audioOutputDeviceId = $(this).val();
|
}
|
||||||
if (audioOutputDeviceId !== Settings.getAudioOutputDeviceId()) {
|
});
|
||||||
emitter.emit(UIEvents.AUDIO_OUTPUT_DEVICE_CHANGED,
|
$('#selectAudioOutput').change(function () {
|
||||||
audioOutputDeviceId);
|
let audioOutputDeviceId = $(this).val();
|
||||||
}
|
if (audioOutputDeviceId !== Settings.getAudioOutputDeviceId()) {
|
||||||
});
|
emitter.emit(UIEvents.AUDIO_OUTPUT_DEVICE_CHANGED,
|
||||||
|
audioOutputDeviceId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -193,41 +209,58 @@ export default {
|
||||||
* @param {{ deviceId, label, kind }[]} devices list of available devices
|
* @param {{ deviceId, label, kind }[]} devices list of available devices
|
||||||
*/
|
*/
|
||||||
changeDevicesList (devices) {
|
changeDevicesList (devices) {
|
||||||
let $devicesOptions = $('#devicesOptions');
|
|
||||||
|
|
||||||
if (!devices.length) {
|
|
||||||
$devicesOptions.hide();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let $selectCamera= $('#selectCamera'),
|
let $selectCamera= $('#selectCamera'),
|
||||||
$selectMic = $('#selectMic'),
|
$selectMic = $('#selectMic'),
|
||||||
$selectAudioOutput = $('#selectAudioOutput'),
|
$selectAudioOutput = $('#selectAudioOutput'),
|
||||||
$selectAudioOutputParent = $selectAudioOutput.parent();
|
$selectAudioOutputParent = $selectAudioOutput.parent();
|
||||||
|
|
||||||
let audio = devices.filter(device => device.kind === 'audioinput');
|
let audio = devices.filter(device => device.kind === 'audioinput'),
|
||||||
let video = devices.filter(device => device.kind === 'videoinput');
|
video = devices.filter(device => device.kind === 'videoinput'),
|
||||||
let audioOutput = devices
|
audioOutput = devices
|
||||||
.filter(device => device.kind === 'audiooutput');
|
.filter(device => device.kind === 'audiooutput'),
|
||||||
|
selectedAudioDevice = audio.find(
|
||||||
|
d => d.deviceId === Settings.getMicDeviceId()) || audio[0],
|
||||||
|
selectedVideoDevice = video.find(
|
||||||
|
d => d.deviceId === Settings.getCameraDeviceId()) || video[0],
|
||||||
|
selectedAudioOutputDevice = audioOutput.find(
|
||||||
|
d => d.deviceId === Settings.getAudioOutputDeviceId()),
|
||||||
|
videoPermissionGranted =
|
||||||
|
JitsiMeetJS.mediaDevices.isDevicePermissionGranted('video'),
|
||||||
|
audioPermissionGranted =
|
||||||
|
JitsiMeetJS.mediaDevices.isDevicePermissionGranted('audio');
|
||||||
|
|
||||||
$selectCamera.html(
|
$selectCamera
|
||||||
generateDevicesOptions(video, Settings.getCameraDeviceId())
|
.html(generateDevicesOptions(
|
||||||
);
|
video,
|
||||||
$selectMic.html(
|
selectedVideoDevice ? selectedVideoDevice.deviceId : '',
|
||||||
generateDevicesOptions(audio, Settings.getMicDeviceId())
|
videoPermissionGranted))
|
||||||
);
|
.prop('disabled', !video.length || !videoPermissionGranted);
|
||||||
|
|
||||||
if (audioOutput.length &&
|
$selectMic
|
||||||
JitsiMeetJS.mediaDevices.isDeviceChangeAvailable('output')) {
|
.html(generateDevicesOptions(
|
||||||
$selectAudioOutput.html(
|
audio,
|
||||||
generateDevicesOptions(audioOutput,
|
selectedAudioDevice ? selectedAudioDevice.deviceId : '',
|
||||||
Settings.getAudioOutputDeviceId()));
|
audioPermissionGranted))
|
||||||
|
.prop('disabled', !audio.length || !audioPermissionGranted);
|
||||||
|
|
||||||
|
if (JitsiMeetJS.mediaDevices.isDeviceChangeAvailable('output')) {
|
||||||
|
$selectAudioOutput
|
||||||
|
.html(generateDevicesOptions(
|
||||||
|
audioOutput,
|
||||||
|
selectedAudioOutputDevice
|
||||||
|
? selectedAudioOutputDevice.deviceId
|
||||||
|
: 'default',
|
||||||
|
videoPermissionGranted || audioPermissionGranted))
|
||||||
|
.prop('disabled', !audioOutput.length ||
|
||||||
|
(!videoPermissionGranted && !audioPermissionGranted));
|
||||||
|
|
||||||
$selectAudioOutputParent.show();
|
$selectAudioOutputParent.show();
|
||||||
} else {
|
} else {
|
||||||
$selectAudioOutputParent.hide();
|
$selectAudioOutputParent.hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
$devicesOptions.show();
|
$('#devicesOptions').show();
|
||||||
|
|
||||||
|
APP.translation.translateElement($('#settingsmenu option'));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -191,7 +191,9 @@ const Toolbar = {
|
||||||
UIUtil.hideDisabledButtons(defaultToolbarButtons);
|
UIUtil.hideDisabledButtons(defaultToolbarButtons);
|
||||||
|
|
||||||
Object.keys(buttonHandlers).forEach(
|
Object.keys(buttonHandlers).forEach(
|
||||||
buttonId => $(`#${buttonId}`).click(buttonHandlers[buttonId])
|
buttonId => $(`#${buttonId}`).click(function(event) {
|
||||||
|
!$(this).prop('disabled') && buttonHandlers[buttonId](event);
|
||||||
|
})
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
|
@ -361,6 +363,29 @@ const Toolbar = {
|
||||||
$('#toolbar_button_camera').toggleClass("icon-camera-disabled", muted);
|
$('#toolbar_button_camera').toggleClass("icon-camera-disabled", muted);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marks video icon as disabled or not.
|
||||||
|
* @param {boolean} disabled if icon should look like disabled or not
|
||||||
|
*/
|
||||||
|
markVideoIconAsDisabled (disabled) {
|
||||||
|
var $btn = $('#toolbar_button_camera');
|
||||||
|
|
||||||
|
$btn
|
||||||
|
.prop("disabled", disabled)
|
||||||
|
.attr("data-i18n", disabled
|
||||||
|
? "[content]toolbar.cameraDisabled"
|
||||||
|
: "[content]toolbar.videomute")
|
||||||
|
.attr("shortcut", disabled ? "" : "toggleVideoPopover");
|
||||||
|
|
||||||
|
disabled
|
||||||
|
? $btn.attr("disabled", "disabled")
|
||||||
|
: $btn.removeAttr("disabled");
|
||||||
|
|
||||||
|
APP.translation.translateElement($btn);
|
||||||
|
|
||||||
|
disabled && this.markVideoIconAsMuted(disabled);
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Marks audio icon as muted or not.
|
* Marks audio icon as muted or not.
|
||||||
* @param {boolean} muted if icon should look like muted or not
|
* @param {boolean} muted if icon should look like muted or not
|
||||||
|
@ -370,6 +395,29 @@ const Toolbar = {
|
||||||
!muted).toggleClass("icon-mic-disabled", muted);
|
!muted).toggleClass("icon-mic-disabled", muted);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marks audio icon as disabled or not.
|
||||||
|
* @param {boolean} disabled if icon should look like disabled or not
|
||||||
|
*/
|
||||||
|
markAudioIconAsDisabled (disabled) {
|
||||||
|
var $btn = $('#toolbar_button_mute');
|
||||||
|
|
||||||
|
$btn
|
||||||
|
.prop("disabled", disabled)
|
||||||
|
.attr("data-i18n", disabled
|
||||||
|
? "[content]toolbar.micDisabled"
|
||||||
|
: "[content]toolbar.mute")
|
||||||
|
.attr("shortcut", disabled ? "" : "mutePopover");
|
||||||
|
|
||||||
|
disabled
|
||||||
|
? $btn.attr("disabled", "disabled")
|
||||||
|
: $btn.removeAttr("disabled");
|
||||||
|
|
||||||
|
APP.translation.translateElement($btn);
|
||||||
|
|
||||||
|
disabled && this.markAudioIconAsMuted(disabled);
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates if the toolbar is currently hovered.
|
* Indicates if the toolbar is currently hovered.
|
||||||
* @return {true} if the toolbar is currently hovered, {false} otherwise
|
* @return {true} if the toolbar is currently hovered, {false} otherwise
|
||||||
|
|
|
@ -43,12 +43,15 @@ if (supportsLocalStorage()) {
|
||||||
window.localStorage.welcomePageDisabled || false
|
window.localStorage.welcomePageDisabled || false
|
||||||
);
|
);
|
||||||
|
|
||||||
var audioOutputDeviceId = window.localStorage.audioOutputDeviceId;
|
// Currently audio output device change is supported only in Chrome and
|
||||||
|
// default output always has 'default' device ID
|
||||||
|
var audioOutputDeviceId = window.localStorage.audioOutputDeviceId
|
||||||
|
|| 'default';
|
||||||
|
|
||||||
if (typeof audioOutputDeviceId !== 'undefined' && audioOutputDeviceId !==
|
if (audioOutputDeviceId !==
|
||||||
JitsiMeetJS.mediaDevices.getAudioOutputDevice()) {
|
JitsiMeetJS.mediaDevices.getAudioOutputDevice()) {
|
||||||
JitsiMeetJS.mediaDevices.setAudioOutputDevice(
|
JitsiMeetJS.mediaDevices.setAudioOutputDevice(audioOutputDeviceId)
|
||||||
window.localStorage.audioOutputDeviceId).catch((ex) => {
|
.catch((ex) => {
|
||||||
console.error('failed to set audio output device from local ' +
|
console.error('failed to set audio output device from local ' +
|
||||||
'storage', ex);
|
'storage', ex);
|
||||||
});
|
});
|
||||||
|
@ -166,10 +169,10 @@ export default {
|
||||||
/**
|
/**
|
||||||
* Set device id of the audio output device which is currently in use.
|
* Set device id of the audio output device which is currently in use.
|
||||||
* Empty string stands for default device.
|
* Empty string stands for default device.
|
||||||
* @param {string} newId new audio output device id
|
* @param {string} newId='default' - new audio output device id
|
||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
setAudioOutputDeviceId: function (newId = '') {
|
setAudioOutputDeviceId: function (newId = 'default') {
|
||||||
return JitsiMeetJS.mediaDevices.setAudioOutputDevice(newId)
|
return JitsiMeetJS.mediaDevices.setAudioOutputDevice(newId)
|
||||||
.then(() => window.localStorage.audioOutputDeviceId = newId);
|
.then(() => window.localStorage.audioOutputDeviceId = newId);
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in New Issue