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.
|
||||
* @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[]>}
|
||||
*/
|
||||
function createLocalTracks (...devices) {
|
||||
function createLocalTracks (devices, cameraDeviceId, micDeviceId) {
|
||||
return JitsiMeetJS.createLocalTracks({
|
||||
// copy array to avoid mutations inside library
|
||||
devices: devices.slice(0),
|
||||
resolution: config.resolution,
|
||||
cameraDeviceId: APP.settings.getCameraDeviceId(),
|
||||
micDeviceId: APP.settings.getMicDeviceId(),
|
||||
cameraDeviceId: typeof cameraDeviceId === 'undefined'
|
||||
|| cameraDeviceId === null
|
||||
? APP.settings.getCameraDeviceId()
|
||||
: cameraDeviceId,
|
||||
micDeviceId: typeof micDeviceId === 'undefined' || micDeviceId === null
|
||||
? APP.settings.getMicDeviceId()
|
||||
: micDeviceId,
|
||||
// adds any ff fake device settings if any
|
||||
firefox_fake_device: config.firefox_fake_device
|
||||
}).catch(function (err) {
|
||||
|
@ -327,6 +336,7 @@ export default {
|
|||
* @returns {Promise}
|
||||
*/
|
||||
init(options) {
|
||||
let self = this;
|
||||
this.roomName = options.roomName;
|
||||
JitsiMeetJS.setLogLevel(JitsiMeetJS.logLevels.TRACE);
|
||||
|
||||
|
@ -356,9 +366,9 @@ export default {
|
|||
return JitsiMeetJS.init(config).then(() => {
|
||||
return Promise.all([
|
||||
// try to retrieve audio and video
|
||||
createLocalTracks('audio', 'video')
|
||||
createLocalTracks(['audio', 'video'])
|
||||
// if failed then try to retrieve only audio
|
||||
.catch(() => createLocalTracks('audio'))
|
||||
.catch(() => createLocalTracks(['audio']))
|
||||
// if audio also failed then just return empty array
|
||||
.catch(() => []),
|
||||
connect(options.roomName)
|
||||
|
@ -370,15 +380,49 @@ export default {
|
|||
this.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
|
||||
if (JitsiMeetJS.mediaDevices.isDeviceListAvailable() &&
|
||||
JitsiMeetJS.mediaDevices.isDeviceChangeAvailable()) {
|
||||
JitsiMeetJS.mediaDevices.enumerateDevices(
|
||||
APP.UI.onAvailableDevicesChanged);
|
||||
JitsiMeetJS.mediaDevices.enumerateDevices(function(devices) {
|
||||
// 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.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)
|
||||
this.recorder = new Recorder();
|
||||
|
@ -388,6 +432,164 @@ export default {
|
|||
return new Promise((resolve, reject) => {
|
||||
(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;
|
||||
}
|
||||
|
||||
stream.videoType === 'camera' && APP.UI.enableCameraButton();
|
||||
|
||||
APP.UI.setVideoMuted(this.localId, this.videoMuted);
|
||||
|
||||
APP.UI.updateDesktopSharingButtons();
|
||||
|
@ -701,6 +905,7 @@ export default {
|
|||
this.audioMuted = false;
|
||||
}
|
||||
|
||||
APP.UI.enableMicrophoneButton();
|
||||
APP.UI.setAudioMuted(this.localId, this.audioMuted);
|
||||
});
|
||||
},
|
||||
|
@ -719,7 +924,7 @@ export default {
|
|||
this.videoSwitchInProgress = true;
|
||||
|
||||
if (shareScreen) {
|
||||
createLocalTracks('desktop').then(([stream]) => {
|
||||
createLocalTracks(['desktop']).then(([stream]) => {
|
||||
stream.on(
|
||||
TrackEvents.LOCAL_TRACK_STOPPED,
|
||||
() => {
|
||||
|
@ -767,7 +972,7 @@ export default {
|
|||
);
|
||||
});
|
||||
} else {
|
||||
createLocalTracks('video').then(
|
||||
createLocalTracks(['video']).then(
|
||||
([stream]) => this.useVideoStream(stream)
|
||||
).then(() => {
|
||||
this.videoSwitchInProgress = false;
|
||||
|
@ -1118,7 +1323,7 @@ export default {
|
|||
UIEvents.VIDEO_DEVICE_CHANGED,
|
||||
(cameraDeviceId) => {
|
||||
APP.settings.setCameraDeviceId(cameraDeviceId);
|
||||
createLocalTracks('video').then(([stream]) => {
|
||||
createLocalTracks(['video']).then(([stream]) => {
|
||||
this.useVideoStream(stream);
|
||||
console.log('switched local video device');
|
||||
});
|
||||
|
@ -1129,7 +1334,7 @@ export default {
|
|||
UIEvents.AUDIO_DEVICE_CHANGED,
|
||||
(micDeviceId) => {
|
||||
APP.settings.setMicDeviceId(micDeviceId);
|
||||
createLocalTracks('audio').then(([stream]) => {
|
||||
createLocalTracks(['audio']).then(([stream]) => {
|
||||
this.useAudioStream(stream);
|
||||
console.log('switched local audio device');
|
||||
});
|
||||
|
|
|
@ -58,6 +58,10 @@ html, body{
|
|||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.button[disabled] {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.toolbar_span>span {
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
|
|
|
@ -66,7 +66,9 @@
|
|||
"dialpad": "Show dialpad",
|
||||
"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.",
|
||||
"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": {
|
||||
"chat": "Open / close chat",
|
||||
|
@ -90,7 +92,9 @@
|
|||
"selectCamera": "Select camera",
|
||||
"selectMic": "Select microphone",
|
||||
"selectAudioOutput": "Select audio output",
|
||||
"followMe": "Enable follow me"
|
||||
"followMe": "Enable follow me",
|
||||
"noDevice": "None",
|
||||
"noPermission": "Permission to use device is not granted"
|
||||
},
|
||||
"videothumbnail":
|
||||
{
|
||||
|
|
|
@ -1142,4 +1142,32 @@ UI.onSharedVideoStop = function (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;
|
||||
|
|
|
@ -30,10 +30,16 @@ function generateLanguagesOptions(items, currentLang) {
|
|||
* Generate html select options for available physical devices.
|
||||
* @param {{ deviceId, label }[]} items available devices
|
||||
* @param {string} [selectedId] id of selected device
|
||||
* @param {boolean} permissionGranted if permission to use selected device type
|
||||
* is granted
|
||||
* @returns {string}
|
||||
*/
|
||||
function generateDevicesOptions(items, selectedId) {
|
||||
return items.map(function (item) {
|
||||
function generateDevicesOptions(items, selectedId, permissionGranted) {
|
||||
if (!permissionGranted && items.length) {
|
||||
return '<option data-i18n="settings.noPermission"></option>';
|
||||
}
|
||||
|
||||
var options = items.map(function (item) {
|
||||
let attrs = {
|
||||
value: item.deviceId
|
||||
};
|
||||
|
@ -44,7 +50,13 @@ function generateDevicesOptions(items, selectedId) {
|
|||
|
||||
let attrsStr = UIUtil.attrsToString(attrs);
|
||||
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
|
||||
this.changeDevicesList([]);
|
||||
$('#selectCamera').change(function () {
|
||||
let cameraDeviceId = $(this).val();
|
||||
if (cameraDeviceId !== Settings.getCameraDeviceId()) {
|
||||
emitter.emit(UIEvents.VIDEO_DEVICE_CHANGED, cameraDeviceId);
|
||||
}
|
||||
});
|
||||
$('#selectMic').change(function () {
|
||||
let micDeviceId = $(this).val();
|
||||
if (micDeviceId !== Settings.getMicDeviceId()) {
|
||||
emitter.emit(UIEvents.AUDIO_DEVICE_CHANGED, micDeviceId);
|
||||
}
|
||||
});
|
||||
$('#selectAudioOutput').change(function () {
|
||||
let audioOutputDeviceId = $(this).val();
|
||||
if (audioOutputDeviceId !== Settings.getAudioOutputDeviceId()) {
|
||||
emitter.emit(UIEvents.AUDIO_OUTPUT_DEVICE_CHANGED,
|
||||
audioOutputDeviceId);
|
||||
}
|
||||
});
|
||||
if (JitsiMeetJS.mediaDevices.isDeviceListAvailable() &&
|
||||
JitsiMeetJS.mediaDevices.isDeviceChangeAvailable()) {
|
||||
this.changeDevicesList([]);
|
||||
|
||||
$('#selectCamera').change(function () {
|
||||
let cameraDeviceId = $(this).val();
|
||||
if (cameraDeviceId !== Settings.getCameraDeviceId()) {
|
||||
emitter.emit(UIEvents.VIDEO_DEVICE_CHANGED, cameraDeviceId);
|
||||
}
|
||||
});
|
||||
$('#selectMic').change(function () {
|
||||
let micDeviceId = $(this).val();
|
||||
if (micDeviceId !== Settings.getMicDeviceId()) {
|
||||
emitter.emit(UIEvents.AUDIO_DEVICE_CHANGED, micDeviceId);
|
||||
}
|
||||
});
|
||||
$('#selectAudioOutput').change(function () {
|
||||
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
|
||||
*/
|
||||
changeDevicesList (devices) {
|
||||
let $devicesOptions = $('#devicesOptions');
|
||||
|
||||
if (!devices.length) {
|
||||
$devicesOptions.hide();
|
||||
return;
|
||||
}
|
||||
|
||||
let $selectCamera= $('#selectCamera'),
|
||||
$selectMic = $('#selectMic'),
|
||||
$selectAudioOutput = $('#selectAudioOutput'),
|
||||
$selectAudioOutputParent = $selectAudioOutput.parent();
|
||||
|
||||
let audio = devices.filter(device => device.kind === 'audioinput');
|
||||
let video = devices.filter(device => device.kind === 'videoinput');
|
||||
let audioOutput = devices
|
||||
.filter(device => device.kind === 'audiooutput');
|
||||
let audio = devices.filter(device => device.kind === 'audioinput'),
|
||||
video = devices.filter(device => device.kind === 'videoinput'),
|
||||
audioOutput = devices
|
||||
.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(
|
||||
generateDevicesOptions(video, Settings.getCameraDeviceId())
|
||||
);
|
||||
$selectMic.html(
|
||||
generateDevicesOptions(audio, Settings.getMicDeviceId())
|
||||
);
|
||||
$selectCamera
|
||||
.html(generateDevicesOptions(
|
||||
video,
|
||||
selectedVideoDevice ? selectedVideoDevice.deviceId : '',
|
||||
videoPermissionGranted))
|
||||
.prop('disabled', !video.length || !videoPermissionGranted);
|
||||
|
||||
if (audioOutput.length &&
|
||||
JitsiMeetJS.mediaDevices.isDeviceChangeAvailable('output')) {
|
||||
$selectAudioOutput.html(
|
||||
generateDevicesOptions(audioOutput,
|
||||
Settings.getAudioOutputDeviceId()));
|
||||
$selectMic
|
||||
.html(generateDevicesOptions(
|
||||
audio,
|
||||
selectedAudioDevice ? selectedAudioDevice.deviceId : '',
|
||||
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();
|
||||
} else {
|
||||
$selectAudioOutputParent.hide();
|
||||
}
|
||||
|
||||
$devicesOptions.show();
|
||||
$('#devicesOptions').show();
|
||||
|
||||
APP.translation.translateElement($('#settingsmenu option'));
|
||||
}
|
||||
};
|
||||
|
|
|
@ -191,7 +191,9 @@ const Toolbar = {
|
|||
UIUtil.hideDisabledButtons(defaultToolbarButtons);
|
||||
|
||||
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);
|
||||
},
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @param {boolean} muted if icon should look like muted or not
|
||||
|
@ -370,6 +395,29 @@ const Toolbar = {
|
|||
!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.
|
||||
* @return {true} if the toolbar is currently hovered, {false} otherwise
|
||||
|
|
|
@ -43,12 +43,15 @@ if (supportsLocalStorage()) {
|
|||
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 !==
|
||||
JitsiMeetJS.mediaDevices.getAudioOutputDevice()) {
|
||||
JitsiMeetJS.mediaDevices.setAudioOutputDevice(
|
||||
window.localStorage.audioOutputDeviceId).catch((ex) => {
|
||||
if (audioOutputDeviceId !==
|
||||
JitsiMeetJS.mediaDevices.getAudioOutputDevice()) {
|
||||
JitsiMeetJS.mediaDevices.setAudioOutputDevice(audioOutputDeviceId)
|
||||
.catch((ex) => {
|
||||
console.error('failed to set audio output device from local ' +
|
||||
'storage', ex);
|
||||
});
|
||||
|
@ -166,10 +169,10 @@ export default {
|
|||
/**
|
||||
* Set device id of the audio output device which is currently in use.
|
||||
* 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}
|
||||
*/
|
||||
setAudioOutputDeviceId: function (newId = '') {
|
||||
setAudioOutputDeviceId: function (newId = 'default') {
|
||||
return JitsiMeetJS.mediaDevices.setAudioOutputDevice(newId)
|
||||
.then(() => window.localStorage.audioOutputDeviceId = newId);
|
||||
},
|
||||
|
|
Loading…
Reference in New Issue