Merge pull request #657 from tsareg/switch_tracks_on_device_list_change

Switch local audio and video track when list of available devices changes
This commit is contained in:
yanas 2016-05-18 15:18:31 -05:00
commit 29f06bbb77
7 changed files with 426 additions and 69 deletions

View File

@ -22,6 +22,7 @@ const TrackEvents = JitsiMeetJS.events.track;
const TrackErrors = JitsiMeetJS.errors.track; const TrackErrors = JitsiMeetJS.errors.track;
let room, connection, localAudio, localVideo, roomLocker; let room, connection, localAudio, localVideo, roomLocker;
let currentAudioInputDevices, currentVideoInputDevices;
import {VIDEO_CONTAINER_TYPE} from "./modules/UI/videolayout/LargeVideo"; import {VIDEO_CONTAINER_TYPE} from "./modules/UI/videolayout/LargeVideo";
@ -170,16 +171,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) {
@ -188,6 +198,17 @@ function createLocalTracks (...devices) {
}); });
} }
/**
* Stores lists of current 'audioinput' and 'videoinput' devices
* @param {MediaDeviceInfo[]} devices
*/
function setCurrentMediaDevices(devices) {
currentAudioInputDevices = devices.filter(
d => d.kind === 'audioinput');
currentVideoInputDevices = devices.filter(
d => d.kind === 'videoinput');
}
class ConferenceConnector { class ConferenceConnector {
constructor(resolve, reject) { constructor(resolve, reject) {
this._resolve = resolve; this._resolve = resolve;
@ -327,6 +348,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 +378,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 +392,53 @@ 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());
}
setCurrentMediaDevices(devices);
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(() => {
setCurrentMediaDevices(devices);
APP.UI.onAvailableDevicesChanged(devices);
});
}, 0);
});
} }
if (config.iAmRecorder) if (config.iAmRecorder)
this.recorder = new Recorder(); this.recorder = new Recorder();
@ -388,6 +448,180 @@ 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) {
// Event handler can be fire before direct enumerateDevices()
// call, so handle this situation here.
if (!currentAudioInputDevices && !currentVideoInputDevices) {
setCurrentMediaDevices(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) {
return Promise.all((tracks || []).map(track => {
if (track.isAudioTrack()) {
let audioWasMuted = self.audioMuted;
return self.useAudioStream(track).then(() => {
console.log('switched local audio');
// If we plugged-in new device (and switched to
// it), but video was muted before, or we
// unplugged current device and selected new
// one, then mute new video track.
if (audioWasMuted ||
currentAudioInputDevices.length >
availableAudioInputDevices.length) {
muteLocalAudio(true);
}
});
} else if (track.isVideoTrack()) {
let videoWasMuted = self.videoMuted;
return self.useVideoStream(track).then(() => {
console.log('switched local video');
// TODO: maybe make video large if we
// are not in conference yet
// If we plugged-in new device (and switched to
// it), but video was muted before, or we
// unplugged current device and selected new
// one, then mute new video track.
if (videoWasMuted ||
(currentVideoInputDevices.length >
availableVideoInputDevices.length)) {
muteLocalVideo(true);
}
});
} else {
console.error("Ignored not an audio nor a "
+ "video track: ", track);
return Promise.resolve();
}
}));
}
}
}); });
}, },
/** /**
@ -667,6 +901,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 +937,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 +956,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 +1004,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 +1355,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 +1366,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');
}); });

View File

@ -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;

View File

@ -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":
{ {

View File

@ -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;

View File

@ -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,7 +123,10 @@ export default {
// DEVICES LIST // DEVICES LIST
if (JitsiMeetJS.mediaDevices.isDeviceListAvailable() &&
JitsiMeetJS.mediaDevices.isDeviceChangeAvailable()) {
this.changeDevicesList([]); this.changeDevicesList([]);
$('#selectCamera').change(function () { $('#selectCamera').change(function () {
let cameraDeviceId = $(this).val(); let cameraDeviceId = $(this).val();
if (cameraDeviceId !== Settings.getCameraDeviceId()) { if (cameraDeviceId !== Settings.getCameraDeviceId()) {
@ -131,6 +146,7 @@ export default {
audioOutputDeviceId); 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'));
} }
}; };

View File

@ -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

View File

@ -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);
}, },