|
|
@@ -22,11 +22,13 @@ var eventEmitter = new EventEmitter();
|
|
22
|
22
|
var AVAILABLE_DEVICES_POLL_INTERVAL_TIME = 3000; // ms
|
|
23
|
23
|
|
|
24
|
24
|
var devices = {
|
|
25
|
|
- audio: true,
|
|
26
|
|
- video: true
|
|
|
25
|
+ audio: false,
|
|
|
26
|
+ video: false
|
|
27
|
27
|
};
|
|
28
|
28
|
|
|
29
|
|
-var audioOuputDeviceId = ''; // default device
|
|
|
29
|
+// Currently audio output device change is supported only in Chrome and
|
|
|
30
|
+// default output always has 'default' device ID
|
|
|
31
|
+var audioOutputDeviceId = 'default'; // default device
|
|
30
|
32
|
|
|
31
|
33
|
var featureDetectionAudioEl = document.createElement('audio');
|
|
32
|
34
|
var isAudioOutputDeviceChangeAvailable =
|
|
|
@@ -34,6 +36,21 @@ var isAudioOutputDeviceChangeAvailable =
|
|
34
|
36
|
|
|
35
|
37
|
var currentlyAvailableMediaDevices = [];
|
|
36
|
38
|
|
|
|
39
|
+var rawEnumerateDevicesWithCallback = navigator.mediaDevices
|
|
|
40
|
+ && navigator.mediaDevices.enumerateDevices
|
|
|
41
|
+ ? function(callback) {
|
|
|
42
|
+ navigator.mediaDevices.enumerateDevices().then(callback, function () {
|
|
|
43
|
+ callback([]);
|
|
|
44
|
+ });
|
|
|
45
|
+ }
|
|
|
46
|
+ : (MediaStreamTrack && MediaStreamTrack.getSources)
|
|
|
47
|
+ ? function (callback) {
|
|
|
48
|
+ MediaStreamTrack.getSources(function (sources) {
|
|
|
49
|
+ callback(sources.map(convertMediaStreamTrackSource));
|
|
|
50
|
+ });
|
|
|
51
|
+ }
|
|
|
52
|
+ : undefined;
|
|
|
53
|
+
|
|
37
|
54
|
// TODO: currently no browser supports 'devicechange' event even in nightly
|
|
38
|
55
|
// builds so no feature/browser detection is used at all. However in future this
|
|
39
|
56
|
// should be changed to some expression. Progress on 'devicechange' event
|
|
|
@@ -262,14 +279,20 @@ function compareAvailableMediaDevices(newDevices) {
|
|
262
|
279
|
* will be supported by browsers.
|
|
263
|
280
|
*/
|
|
264
|
281
|
function pollForAvailableMediaDevices() {
|
|
265
|
|
- RTCUtils.enumerateDevices(function (devices) {
|
|
266
|
|
- if (compareAvailableMediaDevices(devices)) {
|
|
267
|
|
- onMediaDevicesListChanged(devices);
|
|
268
|
|
- }
|
|
|
282
|
+ // Here we use plain navigator.mediaDevices.enumerateDevices instead of
|
|
|
283
|
+ // wrapped because we just need to know the fact the devices changed, labels
|
|
|
284
|
+ // do not matter. This fixes situation when we have no devices initially,
|
|
|
285
|
+ // and then plug in a new one.
|
|
|
286
|
+ if (rawEnumerateDevicesWithCallback) {
|
|
|
287
|
+ rawEnumerateDevicesWithCallback(function (devices) {
|
|
|
288
|
+ if (compareAvailableMediaDevices(devices)) {
|
|
|
289
|
+ onMediaDevicesListChanged(devices);
|
|
|
290
|
+ }
|
|
269
|
291
|
|
|
270
|
|
- window.setTimeout(pollForAvailableMediaDevices,
|
|
271
|
|
- AVAILABLE_DEVICES_POLL_INTERVAL_TIME);
|
|
272
|
|
- });
|
|
|
292
|
+ window.setTimeout(pollForAvailableMediaDevices,
|
|
|
293
|
+ AVAILABLE_DEVICES_POLL_INTERVAL_TIME);
|
|
|
294
|
+ });
|
|
|
295
|
+ }
|
|
273
|
296
|
}
|
|
274
|
297
|
|
|
275
|
298
|
/**
|
|
|
@@ -278,9 +301,35 @@ function pollForAvailableMediaDevices() {
|
|
278
|
301
|
* @emits RTCEvents.DEVICE_LIST_CHANGED
|
|
279
|
302
|
*/
|
|
280
|
303
|
function onMediaDevicesListChanged(devices) {
|
|
281
|
|
- currentlyAvailableMediaDevices = devices;
|
|
|
304
|
+ currentlyAvailableMediaDevices = devices.slice(0);
|
|
|
305
|
+ logger.info('list of media devices has changed:', currentlyAvailableMediaDevices);
|
|
|
306
|
+
|
|
|
307
|
+ var videoInputDevices = currentlyAvailableMediaDevices.filter(function (d) {
|
|
|
308
|
+ return d.kind === 'videoinput';
|
|
|
309
|
+ }),
|
|
|
310
|
+ audioInputDevices = currentlyAvailableMediaDevices.filter(function (d) {
|
|
|
311
|
+ return d.kind === 'audioinput';
|
|
|
312
|
+ }),
|
|
|
313
|
+ videoInputDevicesWithEmptyLabels = videoInputDevices.filter(
|
|
|
314
|
+ function (d) {
|
|
|
315
|
+ return d.label === '';
|
|
|
316
|
+ }),
|
|
|
317
|
+ audioInputDevicesWithEmptyLabels = audioInputDevices.filter(
|
|
|
318
|
+ function (d) {
|
|
|
319
|
+ return d.label === '';
|
|
|
320
|
+ });
|
|
|
321
|
+
|
|
|
322
|
+ if (videoInputDevices.length &&
|
|
|
323
|
+ videoInputDevices.length === videoInputDevicesWithEmptyLabels.length) {
|
|
|
324
|
+ setAvailableDevices(['video'], false);
|
|
|
325
|
+ }
|
|
|
326
|
+
|
|
|
327
|
+ if (audioInputDevices.length &&
|
|
|
328
|
+ audioInputDevices.length === audioInputDevicesWithEmptyLabels.length) {
|
|
|
329
|
+ setAvailableDevices(['audio'], false);
|
|
|
330
|
+ }
|
|
|
331
|
+
|
|
282
|
332
|
eventEmitter.emit(RTCEvents.DEVICE_LIST_CHANGED, devices);
|
|
283
|
|
- logger.info('list of media devices has changed:', devices);
|
|
284
|
333
|
}
|
|
285
|
334
|
|
|
286
|
335
|
// In case of IE we continue from 'onReady' callback
|
|
|
@@ -340,22 +389,6 @@ function wrapGetUserMedia(getUserMedia) {
|
|
340
|
389
|
};
|
|
341
|
390
|
}
|
|
342
|
391
|
|
|
343
|
|
-/**
|
|
344
|
|
- * Create stub device which equals to auto selected device.
|
|
345
|
|
- * @param {string} kind if that should be `audio` or `video` device
|
|
346
|
|
- * @returns {Object} stub device description in `enumerateDevices` format
|
|
347
|
|
- */
|
|
348
|
|
-function createAutoDeviceInfo(kind) {
|
|
349
|
|
- return {
|
|
350
|
|
- facing: null,
|
|
351
|
|
- label: 'Auto',
|
|
352
|
|
- kind: kind,
|
|
353
|
|
- deviceId: '',
|
|
354
|
|
- groupId: ''
|
|
355
|
|
- };
|
|
356
|
|
-}
|
|
357
|
|
-
|
|
358
|
|
-
|
|
359
|
392
|
/**
|
|
360
|
393
|
* Execute function after getUserMedia was executed at least once.
|
|
361
|
394
|
* @param {Function} callback function to execute after getUserMedia
|
|
|
@@ -379,24 +412,10 @@ function wrapEnumerateDevices(enumerateDevices) {
|
|
379
|
412
|
// enumerate devices only after initial getUserMedia
|
|
380
|
413
|
afterUserMediaInitialized(function () {
|
|
381
|
414
|
|
|
382
|
|
- enumerateDevices().then(function (devices) {
|
|
383
|
|
- //add auto devices
|
|
384
|
|
- devices.unshift(
|
|
385
|
|
- createAutoDeviceInfo('audioinput'),
|
|
386
|
|
- createAutoDeviceInfo('videoinput'),
|
|
387
|
|
- createAutoDeviceInfo('audiooutput')
|
|
388
|
|
- );
|
|
389
|
|
-
|
|
390
|
|
- callback(devices);
|
|
391
|
|
- }, function (err) {
|
|
|
415
|
+ enumerateDevices().then(callback, function (err) {
|
|
392
|
416
|
console.error('cannot enumerate devices: ', err);
|
|
393
|
417
|
|
|
394
|
|
- // return only auto devices
|
|
395
|
|
- callback([
|
|
396
|
|
- createAutoDeviceInfo('audioinput'),
|
|
397
|
|
- createAutoDeviceInfo('videoinput'),
|
|
398
|
|
- createAutoDeviceInfo('audiooutput')
|
|
399
|
|
- ]);
|
|
|
418
|
+ callback([]);
|
|
400
|
419
|
});
|
|
401
|
420
|
});
|
|
402
|
421
|
};
|
|
|
@@ -409,32 +428,31 @@ function wrapEnumerateDevices(enumerateDevices) {
|
|
409
|
428
|
*/
|
|
410
|
429
|
function enumerateDevicesThroughMediaStreamTrack (callback) {
|
|
411
|
430
|
MediaStreamTrack.getSources(function (sources) {
|
|
412
|
|
- var devices = sources.map(function (source) {
|
|
413
|
|
- var kind = (source.kind || '').toLowerCase();
|
|
414
|
|
- return {
|
|
415
|
|
- facing: source.facing || null,
|
|
416
|
|
- label: source.label,
|
|
417
|
|
- // theoretically deprecated MediaStreamTrack.getSources should
|
|
418
|
|
- // not return 'audiooutput' devices but let's handle it in any
|
|
419
|
|
- // case
|
|
420
|
|
- kind: kind
|
|
421
|
|
- ? (kind === 'audiooutput' ? kind : kind + 'input')
|
|
422
|
|
- : null,
|
|
423
|
|
- deviceId: source.id,
|
|
424
|
|
- groupId: source.groupId || null
|
|
425
|
|
- };
|
|
426
|
|
- });
|
|
427
|
|
-
|
|
428
|
|
- //add auto devices
|
|
429
|
|
- devices.unshift(
|
|
430
|
|
- createAutoDeviceInfo('audioinput'),
|
|
431
|
|
- createAutoDeviceInfo('videoinput'),
|
|
432
|
|
- createAutoDeviceInfo('audiooutput')
|
|
433
|
|
- );
|
|
434
|
|
- callback(devices);
|
|
|
431
|
+ callback(sources.map(convertMediaStreamTrackSource));
|
|
435
|
432
|
});
|
|
436
|
433
|
}
|
|
437
|
434
|
|
|
|
435
|
+/**
|
|
|
436
|
+ * Converts MediaStreamTrack Source to enumerateDevices format.
|
|
|
437
|
+ * @param {Object} source
|
|
|
438
|
+ */
|
|
|
439
|
+function convertMediaStreamTrackSource(source) {
|
|
|
440
|
+ var kind = (source.kind || '').toLowerCase();
|
|
|
441
|
+
|
|
|
442
|
+ return {
|
|
|
443
|
+ facing: source.facing || null,
|
|
|
444
|
+ label: source.label,
|
|
|
445
|
+ // theoretically deprecated MediaStreamTrack.getSources should
|
|
|
446
|
+ // not return 'audiooutput' devices but let's handle it in any
|
|
|
447
|
+ // case
|
|
|
448
|
+ kind: kind
|
|
|
449
|
+ ? (kind === 'audiooutput' ? kind : kind + 'input')
|
|
|
450
|
+ : null,
|
|
|
451
|
+ deviceId: source.id,
|
|
|
452
|
+ groupId: source.groupId || null
|
|
|
453
|
+ };
|
|
|
454
|
+}
|
|
|
455
|
+
|
|
438
|
456
|
function obtainDevices(options) {
|
|
439
|
457
|
if(!options.devices || options.devices.length === 0) {
|
|
440
|
458
|
return options.successCallback(options.streams || {});
|
|
|
@@ -975,7 +993,8 @@ var RTCUtils = {
|
|
975
|
993
|
/**
|
|
976
|
994
|
* Sets current audio output device.
|
|
977
|
995
|
* @param {string} deviceId - id of 'audiooutput' device from
|
|
978
|
|
- * navigator.mediaDevices.enumerateDevices(), '' for default device
|
|
|
996
|
+ * navigator.mediaDevices.enumerateDevices(), 'default' for default
|
|
|
997
|
+ * device
|
|
979
|
998
|
* @returns {Promise} - resolves when audio output is changed, is rejected
|
|
980
|
999
|
* otherwise
|
|
981
|
1000
|
*/
|
|
|
@@ -987,7 +1006,7 @@ var RTCUtils = {
|
|
987
|
1006
|
|
|
988
|
1007
|
return featureDetectionAudioEl.setSinkId(deviceId)
|
|
989
|
1008
|
.then(function() {
|
|
990
|
|
- audioOuputDeviceId = deviceId;
|
|
|
1009
|
+ audioOutputDeviceId = deviceId;
|
|
991
|
1010
|
|
|
992
|
1011
|
logger.log('Audio output device set to ' + deviceId);
|
|
993
|
1012
|
|
|
|
@@ -1001,7 +1020,7 @@ var RTCUtils = {
|
|
1001
|
1020
|
* @returns {string}
|
|
1002
|
1021
|
*/
|
|
1003
|
1022
|
getAudioOutputDevice: function () {
|
|
1004
|
|
- return audioOuputDeviceId;
|
|
|
1023
|
+ return audioOutputDeviceId;
|
|
1005
|
1024
|
}
|
|
1006
|
1025
|
};
|
|
1007
|
1026
|
|