Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 64 additions & 4 deletions src/utils/webrtc/MediaDevicesManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@
*
*/

import BrowserStorage from '../../services/BrowserStorage'

/**
* Special string to set null device ids in local storage (as only strings are
* allowed).
*/
const LOCAL_STORAGE_NULL_DEVICE_ID = 'local-storage-null-device-id'

/**
* Wrapper for MediaDevices to simplify its use.
*
Expand Down Expand Up @@ -57,7 +65,9 @@
* modified.
*
* The selected devices will be automatically cleared if they are no longer
* available. When no device of certain kind is selected and there are other
* available, and they will be restored once they are again available
* (immediately if events are enabled, or otherwise the next time that devices
* are got). When no device of certain kind is selected and there are other
* devices of that kind the selected device will fall back to the first one
* found, or to the one with the "default" id (if any). It is possible to
* explicitly disable devices of certain kind by setting xxxInputId to "null"
Expand All @@ -83,6 +93,15 @@ export default function MediaDevicesManager() {
this._tracks = []

this._updateDevicesBound = this._updateDevices.bind(this)

this._pendingEnumerateDevicesPromise = null

if (BrowserStorage.getItem('audioInputId') === LOCAL_STORAGE_NULL_DEVICE_ID) {
this.attributes.audioInputId = null
}
if (BrowserStorage.getItem('videoInputId') === LOCAL_STORAGE_NULL_DEVICE_ID) {
this.attributes.videoInputId = null
}
}
MediaDevicesManager.prototype = {

Expand All @@ -94,6 +113,24 @@ MediaDevicesManager.prototype = {
this.attributes[key] = value

this._trigger('change:' + key, [value])

this._storeDeviceId(key, value)
},

_storeDeviceId: function(key, value) {
if (key !== 'audioInputId' && key !== 'videoInputId') {
return
}

if (value === null) {
value = LOCAL_STORAGE_NULL_DEVICE_ID
}

if (value) {
BrowserStorage.setItem(key, value)
} else {
BrowserStorage.removeItem(key)
}
},

on: function(event, handler) {
Expand Down Expand Up @@ -171,7 +208,7 @@ MediaDevicesManager.prototype = {
},

_updateDevices: function() {
navigator.mediaDevices.enumerateDevices().then(devices => {
this._pendingEnumerateDevicesPromise = navigator.mediaDevices.enumerateDevices().then(devices => {
const previousAudioInputId = this.attributes.audioInputId
const previousVideoInputId = this.attributes.videoInputId

Expand All @@ -197,8 +234,12 @@ MediaDevicesManager.prototype = {
if (previousVideoInputId !== this.attributes.videoInputId) {
this._trigger('change:videoInputId', [this.attributes.videoInputId])
}

this._pendingEnumerateDevicesPromise = null
}).catch(function(error) {
console.error('Could not update known media devices: ' + error.name + ': ' + error.message)

this._pendingEnumerateDevicesPromise = null
})
},

Expand Down Expand Up @@ -274,16 +315,23 @@ MediaDevicesManager.prototype = {
// Always refresh the known device with the latest values.
this._knownDevices[addedDevice.kind + '-' + addedDevice.deviceId] = addedDevice

// Set first available device as fallback, and override any
// fallback previously set if the default device is added.
// Restore previously selected device if it becomes available again.
// Additionally, set first available device as fallback, and override
// any fallback previously set if the default device is added.
if (addedDevice.kind === 'audioinput') {
if (BrowserStorage.getItem('audioInputId') === addedDevice.deviceId) {
this.attributes.audioInputId = addedDevice.deviceId
}
if (!this._fallbackAudioInputId || addedDevice.deviceId === 'default') {
this._fallbackAudioInputId = addedDevice.deviceId
}
if (this.attributes.audioInputId === undefined) {
this.attributes.audioInputId = this._fallbackAudioInputId
}
} else if (addedDevice.kind === 'videoinput') {
if (BrowserStorage.getItem('videoInputId') === addedDevice.deviceId) {
this.attributes.videoInputId = addedDevice.deviceId
}
if (!this._fallbackVideoInputId || addedDevice.deviceId === 'default') {
this._fallbackVideoInputId = addedDevice.deviceId
}
Expand Down Expand Up @@ -318,6 +366,18 @@ MediaDevicesManager.prototype = {
})
}

if (!this._pendingEnumerateDevicesPromise) {
return this._getUserMediaInternal(constraints)
}

return this._pendingEnumerateDevicesPromise.then(() => {
return this._getUserMediaInternal(constraints)
}).catch(() => {
return this._getUserMediaInternal(constraints)
})
},

_getUserMediaInternal: function(constraints) {
if (constraints.audio && !constraints.audio.deviceId) {
if (this.attributes.audioInputId) {
if (!(constraints.audio instanceof Object)) {
Expand Down
84 changes: 57 additions & 27 deletions src/utils/webrtc/simplewebrtc/localmedia.js
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,12 @@ LocalMedia.prototype.start = function(mediaConstraints, cb, context) {

this.emit('localStreamRequested', constraints, context)

if (!context) {
// Try to get the devices list before getting user media.
webrtcIndex.mediaDevicesManager.enableDeviceEvents()
webrtcIndex.mediaDevicesManager.disableDeviceEvents()
}

webrtcIndex.mediaDevicesManager.getUserMedia(constraints).then(function(stream) {
// Although the promise should be resolved only if all the constraints
// are met Edge resolves it if both audio and video are requested but
Expand Down Expand Up @@ -165,7 +171,7 @@ LocalMedia.prototype.start = function(mediaConstraints, cb, context) {
}

track.addEventListener('ended', function() {
if (isAllTracksEnded(stream)) {
if (isAllTracksEnded(stream) && !self._pendingAudioInputIdChangedCount && !self._pendingVideoInputIdChangedCount) {
self._removeStream(stream)
}
})
Expand Down Expand Up @@ -204,6 +210,26 @@ LocalMedia.prototype._handleAudioInputIdChanged = function(mediaDevicesManager,
return
}

this._pendingAudioInputIdChangedCount = 1

const resetPendingAudioInputIdChangedCount = () => {
const audioInputIdChangedAgain = this._pendingAudioInputIdChangedCount > 1

this._pendingAudioInputIdChangedCount = 0

if (audioInputIdChangedAgain) {
this._handleAudioInputIdChanged(webrtcIndex.mediaDevicesManager.get('audioInputId'))
}

if (!this._pendingAudioInputIdChangedCount && !this._pendingVideoInputIdChangedCount) {
this.localStreams.forEach(stream => {
if (isAllTracksEnded(stream)) {
this._removeStream(stream)
}
})
}
}

const localStreamsChanged = []
const localTracksReplaced = []

Expand Down Expand Up @@ -244,23 +270,15 @@ LocalMedia.prototype._handleAudioInputIdChanged = function(mediaDevicesManager,
this.emit('localTrackReplaced', null, trackStreamPair.track, trackStreamPair.stream)
})

return
}
resetPendingAudioInputIdChangedCount()

if (localTracksReplaced.length === 0) {
return
}

this._pendingAudioInputIdChangedCount = 1

const resetPendingAudioInputIdChangedCount = () => {
const audioInputIdChangedAgain = this._pendingAudioInputIdChangedCount > 1

this._pendingAudioInputIdChangedCount = 0
if (localTracksReplaced.length === 0) {
resetPendingAudioInputIdChangedCount()

if (audioInputIdChangedAgain) {
this._handleAudioInputIdChanged(webrtcIndex.mediaDevicesManager.get('audioInputId'))
}
return
}

webrtcIndex.mediaDevicesManager.getUserMedia({ audio: true }).then(stream => {
Expand Down Expand Up @@ -303,7 +321,7 @@ LocalMedia.prototype._handleAudioInputIdChanged = function(mediaDevicesManager,
}

clonedTrack.addEventListener('ended', () => {
if (isAllTracksEnded(stream)) {
if (isAllTracksEnded(stream) && !this._pendingAudioInputIdChangedCount && !this._pendingVideoInputIdChangedCount) {
this._removeStream(stream)
}
})
Expand Down Expand Up @@ -337,6 +355,26 @@ LocalMedia.prototype._handleVideoInputIdChanged = function(mediaDevicesManager,
return
}

this._pendingVideoInputIdChangedCount = 1

const resetPendingVideoInputIdChangedCount = () => {
const videoInputIdChangedAgain = this._pendingVideoInputIdChangedCount > 1

this._pendingVideoInputIdChangedCount = 0

if (videoInputIdChangedAgain) {
this._handleVideoInputIdChanged(webrtcIndex.mediaDevicesManager.get('videoInputId'))
}

if (!this._pendingAudioInputIdChangedCount && !this._pendingVideoInputIdChangedCount) {
this.localStreams.forEach(stream => {
if (isAllTracksEnded(stream)) {
this._removeStream(stream)
}
})
}
}

const localStreamsChanged = []
const localTracksReplaced = []

Expand Down Expand Up @@ -377,23 +415,15 @@ LocalMedia.prototype._handleVideoInputIdChanged = function(mediaDevicesManager,
this.emit('localTrackReplaced', null, trackStreamPair.track, trackStreamPair.stream)
})

return
}
resetPendingVideoInputIdChangedCount()

if (localTracksReplaced.length === 0) {
return
}

this._pendingVideoInputIdChangedCount = 1

const resetPendingVideoInputIdChangedCount = () => {
const videoInputIdChangedAgain = this._pendingVideoInputIdChangedCount > 1

this._pendingVideoInputIdChangedCount = 0
if (localTracksReplaced.length === 0) {
resetPendingVideoInputIdChangedCount()

if (videoInputIdChangedAgain) {
this._handleVideoInputIdChanged(webrtcIndex.mediaDevicesManager.get('videoInputId'))
}
return
}

webrtcIndex.mediaDevicesManager.getUserMedia({ video: true }).then(stream => {
Expand Down Expand Up @@ -423,7 +453,7 @@ LocalMedia.prototype._handleVideoInputIdChanged = function(mediaDevicesManager,
}

clonedTrack.addEventListener('ended', () => {
if (isAllTracksEnded(stream)) {
if (isAllTracksEnded(stream) && !this._pendingAudioInputIdChangedCount && !this._pendingVideoInputIdChangedCount) {
this._removeStream(stream)
}
})
Expand Down