Refactored conference.js code. Moved almost all code that relates to handling change of media devices to separate module. Fixed couple of bugs.

This commit is contained in:
tsareg 2016-06-13 14:49:00 +03:00 committed by yanas
parent 97237470af
commit 897a6bfbe6
2 changed files with 366 additions and 281 deletions

View File

@ -12,6 +12,8 @@ import Recorder from './modules/recorder/Recorder';
import CQEvents from './service/connectionquality/CQEvents';
import UIEvents from './service/UI/UIEvents';
import mediaDeviceHelper from './modules/devices/mediaDeviceHelper';
const ConnectionEvents = JitsiMeetJS.events.connection;
const ConnectionErrors = JitsiMeetJS.errors.connection;
@ -22,7 +24,6 @@ const TrackEvents = JitsiMeetJS.events.track;
const TrackErrors = JitsiMeetJS.errors.track;
let room, connection, localAudio, localVideo, roomLocker;
let currentAudioInputDevices, currentVideoInputDevices;
import {VIDEO_CONTAINER_TYPE} from "./modules/UI/videolayout/LargeVideo";
@ -211,17 +212,6 @@ function createLocalTracks (devices, cameraDeviceId, micDeviceId) {
});
}
/**
* 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');
}
/**
* Changes the email for the local user
* @param email {string} the new email
@ -489,44 +479,8 @@ export default {
APP.UI.disableCameraButton();
}
// update list of available devices
if (JitsiMeetJS.mediaDevices.isDeviceListAvailable() &&
JitsiMeetJS.mediaDevices.isDeviceChangeAvailable()) {
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());
}
this._initDeviceList();
if (localVideo) {
localVideo._setRealDeviceIdFromDeviceList(devices);
APP.settings.setCameraDeviceId(
localVideo.getDeviceId());
}
setCurrentMediaDevices(devices);
APP.UI.onAvailableDevicesChanged(devices);
});
JitsiMeetJS.mediaDevices.addEventListener(
JitsiMeetJS.events.mediaDevices.DEVICE_LIST_CHANGED,
(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)
this.recorder = new Recorder();
@ -535,220 +489,6 @@ 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) {
// 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) {
let audioTrackCreationError;
let videoTrackCreationError;
let audioRequested = type.indexOf('audio') !== -1;
let videoRequested = type.indexOf('video') !== -1;
let promise;
if (audioRequested && micDeviceId !== null) {
if (videoRequested && cameraDeviceId !== null) {
promise = createLocalTracks(
type, cameraDeviceId, micDeviceId)
.catch(() => {
return Promise.all([
createAudioTrack(false),
createVideoTrack(false)]);
})
.then((audioTracks, videoTracks) => {
if (audioTrackCreationError) {
if (videoTrackCreationError) {
APP.UI.showDeviceErrorDialog(
audioTrackCreationError,
videoTrackCreationError);
} else {
APP.UI.showDeviceErrorDialog(
audioTrackCreationError,
null);
}
} else if (videoTrackCreationError) {
APP.UI.showDeviceErrorDialog(
null,
videoTrackCreationError);
}
return (audioTracks || [])
.concat(videoTracks || []);
});
} else {
promise = createAudioTrack();
}
} else if (videoRequested && cameraDeviceId !== null) {
promise = createVideoTrack();
} else {
promise = Promise.resolve([]);
}
return promise
.then(onTracksCreated);
function createAudioTrack(showError) {
return createLocalTracks(['audio'], null, micDeviceId)
.catch(err => {
audioTrackCreationError = err;
if (showError) {
APP.UI.showDeviceErrorDialog(err, null);
}
return [];
});
}
function createVideoTrack(showError) {
return createLocalTracks(
['video'], cameraDeviceId, null)
.catch(err => {
videoTrackCreationError = err;
if (showError) {
APP.UI.showDeviceErrorDialog(null, err);
}
return [];
});
}
}
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();
}
}));
}
}
});
},
/**
@ -956,16 +696,7 @@ export default {
room = connection.initJitsiConference(APP.conference.roomName,
this._getConferenceOptions());
this.localId = room.myUserId();
localTracks.forEach((track) => {
if (track.isAudioTrack()) {
this.useAudioStream(track);
} else if (track.isVideoTrack()) {
this.useVideoStream(track);
} else {
console.error(
"Ignored not an audio nor a video track: ", track);
}
});
this._setLocalAudioVideoStreams(localTracks);
roomLocker = createRoomLocker(room);
this._room = room; // FIXME do not use this
@ -986,6 +717,26 @@ export default {
this._setupListeners();
},
/**
* Sets local video and audio streams.
* @param {JitsiLocalTrack[]} tracks=[]
* @returns {Promise[]}
* @private
*/
_setLocalAudioVideoStreams(tracks = []) {
return tracks.map(track => {
if (track.isAudioTrack()) {
return this.useAudioStream(track);
} else if (track.isVideoTrack()) {
return this.useVideoStream(track);
} else {
console.error(
"Ignored not an audio nor a video track: ", track);
return Promise.resolve();
}
});
},
_getConferenceOptions() {
let options = config;
if(config.enableRecording && !config.recordingType) {
@ -1480,7 +1231,7 @@ export default {
APP.UI.addListener(
UIEvents.VIDEO_DEVICE_CHANGED,
(cameraDeviceId) => {
createLocalTracks(['video'])
createLocalTracks(['video'], cameraDeviceId, null)
.then(([stream]) => {
this.useVideoStream(stream);
console.log('switched local video device');
@ -1496,7 +1247,7 @@ export default {
APP.UI.addListener(
UIEvents.AUDIO_DEVICE_CHANGED,
(micDeviceId) => {
createLocalTracks(['audio'])
createLocalTracks(['audio'], null, micDeviceId)
.then(([stream]) => {
this.useAudioStream(stream);
console.log('switched local audio device');
@ -1574,11 +1325,112 @@ export default {
});
},
/**
* Adss any room listener.
* @param eventName one of the ConferenceEvents
* @param callBack the function to be called when the event occurs
*/
addConferenceListener(eventName, callBack) {
* Adds any room listener.
* @param eventName one of the ConferenceEvents
* @param callBack the function to be called when the event occurs
*/
addConferenceListener(eventName, callBack) {
room.on(eventName, callBack);
},
/**
* Inits list of current devices and event listener for device change.
* @private
*/
_initDeviceList() {
if (JitsiMeetJS.mediaDevices.isDeviceListAvailable() &&
JitsiMeetJS.mediaDevices.isDeviceChangeAvailable()) {
JitsiMeetJS.mediaDevices.enumerateDevices(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());
}
mediaDeviceHelper.setCurrentMediaDevices(devices);
APP.UI.onAvailableDevicesChanged(devices);
});
JitsiMeetJS.mediaDevices.addEventListener(
JitsiMeetJS.events.mediaDevices.DEVICE_LIST_CHANGED,
(devices) =>
window.setTimeout(
() => this._onDeviceListChanged(devices), 0));
}
},
/**
* Event listener for JitsiMediaDevicesEvents.DEVICE_LIST_CHANGED to
* handle change of available media devices.
* @private
* @param {MediaDeviceInfo[]} devices
* @returns {Promise}
*/
_onDeviceListChanged(devices) {
let currentDevices = mediaDeviceHelper.getCurrentMediaDevices();
// Event handler can be fired before direct
// enumerateDevices() call, so handle this situation here.
if (!currentDevices.audioinput &&
!currentDevices.videoinput &&
!currentDevices.audiooutput) {
mediaDeviceHelper.setCurrentMediaDevices(devices);
currentDevices = mediaDeviceHelper.getCurrentMediaDevices();
}
let newDevices =
mediaDeviceHelper.getNewMediaDevicesAfterDeviceListChanged(
devices, this.isSharingScreen, localVideo, localAudio);
let promises = [];
let audioWasMuted = this.audioMuted;
let videoWasMuted = this.videoMuted;
let availableAudioInputDevices =
mediaDeviceHelper.getDevicesFromListByKind(devices, 'audioinput');
let availableVideoInputDevices =
mediaDeviceHelper.getDevicesFromListByKind(devices, 'videoinput');
if (typeof newDevices.audiooutput !== 'undefined') {
// Just ignore any errors in catch block.
promises.push(APP.settings
.setAudioOutputDeviceId(newDevices.audiooutput)
.catch());
}
promises.push(
mediaDeviceHelper.createLocalTracksAfterDeviceListChanged(
createLocalTracks,
newDevices.videoinput,
newDevices.audioinput)
.then(tracks =>
Promise.all(this._setLocalAudioVideoStreams(tracks)))
.then(() => {
// If audio was muted before, or we unplugged current device
// and selected new one, then mute new audio track.
if (audioWasMuted ||
currentDevices.audioinput.length >
availableAudioInputDevices.length) {
muteLocalAudio(true);
}
// If video was muted before, or we unplugged current device
// and selected new one, then mute new video track.
if (videoWasMuted ||
currentDevices.videoinput.length >
availableVideoInputDevices.length) {
muteLocalVideo(true);
}
}));
return Promise.all(promises)
.then(() => {
mediaDeviceHelper.setCurrentMediaDevices(devices);
APP.UI.onAvailableDevicesChanged(devices);
});
}
};
};

View File

@ -0,0 +1,233 @@
/* global $, APP, JitsiMeetJS, config, interfaceConfig */
let currentAudioInputDevices,
currentVideoInputDevices,
currentAudioOutputDevices;
/**
* Determines if currently selected audio output device should be changed after
* list of available devices has been changed.
* @param {MediaDeviceInfo[]} newDevices
* @returns {string|undefined} - ID of new audio output device to use, undefined
* if audio output device should not be changed.
*/
function getNewAudioOutputDevice(newDevices) {
if (!JitsiMeetJS.mediaDevices.isDeviceChangeAvailable('output')) {
return;
}
let selectedAudioOutputDeviceId = APP.settings.getAudioOutputDeviceId();
let availableAudioOutputDevices = newDevices.filter(
d => d.kind === 'audiooutput');
// Switch to 'default' audio output device if we don't have the selected one
// available anymore.
if (selectedAudioOutputDeviceId !== 'default' &&
!availableAudioOutputDevices.find(d =>
d.deviceId === selectedAudioOutputDeviceId)) {
return 'default';
}
}
/**
* Determines if currently selected audio input device should be changed after
* list of available devices has been changed.
* @param {MediaDeviceInfo[]} newDevices
* @param {JitsiLocalTrack} localAudio
* @returns {string|undefined} - ID of new microphone device to use, undefined
* if audio input device should not be changed.
*/
function getNewAudioInputDevice(newDevices, localAudio) {
let availableAudioInputDevices = newDevices.filter(
d => d.kind === 'audioinput');
let selectedAudioInputDeviceId = APP.settings.getMicDeviceId();
let selectedAudioInputDevice = availableAudioInputDevices.find(
d => d.deviceId === selectedAudioInputDeviceId);
// 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 we have new audio device and permission to use it was granted
// (label is not an empty string), then we will try to use the first
// available device.
if (availableAudioInputDevices.length &&
availableAudioInputDevices[0].label !== '') {
return availableAudioInputDevices[0].deviceId;
}
// Otherwise we assume that we don't have any audio input devices
// to use and that's why disable microphone button on UI.
else {
APP.UI.disableMicrophoneButton();
}
} else {
// And here we handle case when we already have some device working,
// but we plug-in a "preferred" (previously selected in settings, stored
// in local storage) device.
if (selectedAudioInputDevice &&
selectedAudioInputDeviceId !== localAudio.getDeviceId()) {
return selectedAudioInputDeviceId;
}
}
}
/**
* Determines if currently selected video input device should be changed after
* list of available devices has been changed.
* @param {MediaDeviceInfo[]} newDevices
* @param {JitsiLocalTrack} localVideo
* @returns {string|undefined} - ID of new camera device to use, undefined
* if video input device should not be changed.
*/
function getNewVideoInputDevice(newDevices, localVideo) {
let availableVideoInputDevices = newDevices.filter(
d => d.kind === 'videoinput');
let selectedVideoInputDeviceId = APP.settings.getCameraDeviceId();
let selectedVideoInputDevice = availableVideoInputDevices.find(
d => d.deviceId === selectedVideoInputDeviceId);
// Here we handle case when no video input device was initially plugged,
// but then device is connected OR new device was connected when
// previous track has ended.
if (!localVideo || localVideo.disposed || localVideo.isEnded()) {
// If we have new video device and permission to use it was granted
// (label is not an empty string), then we will try to use the first
// available device.
if (availableVideoInputDevices.length &&
availableVideoInputDevices[0].label !== '') {
return availableVideoInputDevices[0].deviceId;
}
// Otherwise we assume that we don't have any video input devices
// to use and that's why disable microphone button on UI.
else {
APP.UI.disableCameraButton();
}
} else {
// And here we handle case when we already have some device working,
// but we plug-in a "preferred" (previously selected in settings, stored
// in local storage) device.
if (selectedVideoInputDevice &&
selectedVideoInputDeviceId !== localVideo.getDeviceId()) {
return selectedVideoInputDeviceId;
}
}
}
export default {
/**
* Returns list of devices of single kind.
* @param {MediaDeviceInfo[]} devices
* @param {'audioinput'|'audiooutput'|'videoinput'} kind
* @returns {MediaDeviceInfo[]}
*/
getDevicesFromListByKind(devices, kind) {
return devices.filter(d => d.kind === kind);
},
/**
* Stores lists of current 'audioinput', 'videoinput' and 'audiooutput'
* devices.
* @param {MediaDeviceInfo[]} devices
*/
setCurrentMediaDevices(devices) {
currentAudioInputDevices =
this.getDevicesFromListByKind(devices, 'audioinput');
currentVideoInputDevices =
this.getDevicesFromListByKind(devices, 'videoinput');
currentAudioOutputDevices =
this.getDevicesFromListByKind(devices, 'audiooutput');
},
/**
* Returns lists of current 'audioinput', 'videoinput' and 'audiooutput'
* devices.
* @returns {{
* audioinput: (MediaDeviceInfo[]|undefined),
* videoinput: (MediaDeviceInfo[]|undefined),
* audiooutput: (MediaDeviceInfo[]|undefined),
* }}
*/
getCurrentMediaDevices() {
return {
audioinput: currentAudioInputDevices,
videoinput: currentVideoInputDevices,
audiooutput: currentAudioOutputDevices
};
},
/**
* Determines if currently selected media devices should be changed after
* list of available devices has been changed.
* @param {MediaDeviceInfo[]} newDevices
* @param {boolean} isSharingScreen
* @param {JitsiLocalTrack} localVideo
* @param {JitsiLocalTrack} localAudio
* @returns {{
* audioinput: (string|undefined),
* videoinput: (string|undefined),
* audiooutput: (string|undefined)
* }}
*/
getNewMediaDevicesAfterDeviceListChanged(
newDevices, isSharingScreen, localVideo, localAudio) {
return {
audioinput: getNewAudioInputDevice(newDevices, localAudio),
videoinput: !isSharingScreen &&
getNewVideoInputDevice(newDevices, localVideo),
audiooutput: getNewAudioOutputDevice(newDevices)
};
},
/**
* Tries to create new local tracks for new devices obtained after device
* list changed. Shows error dialog in case of failures.
* @param {function} createLocalTracks
* @param {string} (cameraDeviceId)
* @param {string} (micDeviceId)
* @returns {Promise.<JitsiLocalTrack[]>}
*/
createLocalTracksAfterDeviceListChanged(
createLocalTracks, cameraDeviceId, micDeviceId) {
let audioTrackError;
let videoTrackError;
let audioRequested = !!micDeviceId;
let videoRequested = !!cameraDeviceId;
if (audioRequested && videoRequested) {
// First we try to create both audio and video tracks together.
return createLocalTracks(
['audio', 'video'], cameraDeviceId, micDeviceId)
// If we fail to do this, try to create them separately.
.catch(() => Promise.all(
[createAudioTrack(false), createVideoTrack(false)]))
.then((audioTracks, videoTracks) => {
if (audioTrackError || videoTrackError) {
APP.UI.showDeviceErrorDialog(
audioTrackError, videoTrackError);
}
return (audioTracks || []).concat(videoTracks || []);
});
} else if (videoRequested && !audioRequested) {
return createVideoTrack();
} else if (audioRequested && !videoRequested) {
return createAudioTrack();
} else {
return Promise.resolve([]);
}
function createAudioTrack(showError) {
return createLocalTracks(['audio'], null, micDeviceId)
.catch(err => {
audioTrackError = err;
showError && APP.UI.showDeviceErrorDialog(err, null);
return [];
});
}
function createVideoTrack(showError) {
return createLocalTracks(['video'], cameraDeviceId, null)
.catch(err => {
videoTrackError = err;
showError && APP.UI.showDeviceErrorDialog(null, err);
return [];
});
}
}
};