|
@@ -24,6 +24,12 @@ var devices = {
|
24
|
24
|
video: true
|
25
|
25
|
};
|
26
|
26
|
|
|
27
|
+var audioOuputDeviceId = ''; // default device
|
|
28
|
+
|
|
29
|
+var featureDetectionAudioEl = document.createElement('audio');
|
|
30
|
+var isAudioOutputDeviceChangeAvailable =
|
|
31
|
+ typeof featureDetectionAudioEl.setSinkId !== 'undefined';
|
|
32
|
+
|
27
|
33
|
var rtcReady = false;
|
28
|
34
|
|
29
|
35
|
function setResolutionConstraints(constraints, resolution) {
|
|
@@ -303,7 +309,8 @@ function wrapEnumerateDevices(enumerateDevices) {
|
303
|
309
|
//add auto devices
|
304
|
310
|
devices.unshift(
|
305
|
311
|
createAutoDeviceInfo('audioinput'),
|
306
|
|
- createAutoDeviceInfo('videoinput')
|
|
312
|
+ createAutoDeviceInfo('videoinput'),
|
|
313
|
+ createAutoDeviceInfo('audiooutput')
|
307
|
314
|
);
|
308
|
315
|
|
309
|
316
|
callback(devices);
|
|
@@ -311,8 +318,11 @@ function wrapEnumerateDevices(enumerateDevices) {
|
311
|
318
|
console.error('cannot enumerate devices: ', err);
|
312
|
319
|
|
313
|
320
|
// return only auto devices
|
314
|
|
- callback([createAutoDeviceInfo('audioinput'),
|
315
|
|
- createAutoDeviceInfo('videoinput')]);
|
|
321
|
+ callback([
|
|
322
|
+ createAutoDeviceInfo('audioinput'),
|
|
323
|
+ createAutoDeviceInfo('videoinput'),
|
|
324
|
+ createAutoDeviceInfo('audiooutput')
|
|
325
|
+ ]);
|
316
|
326
|
});
|
317
|
327
|
});
|
318
|
328
|
};
|
|
@@ -330,7 +340,12 @@ function enumerateDevicesThroughMediaStreamTrack (callback) {
|
330
|
340
|
return {
|
331
|
341
|
facing: source.facing || null,
|
332
|
342
|
label: source.label,
|
333
|
|
- kind: kind ? kind + 'input': null,
|
|
343
|
+ // theoretically deprecated MediaStreamTrack.getSources should
|
|
344
|
+ // not return 'audiooutput' devices but let's handle it in any
|
|
345
|
+ // case
|
|
346
|
+ kind: kind
|
|
347
|
+ ? (kind === 'audiooutput' ? kind : kind + 'input')
|
|
348
|
+ : null,
|
334
|
349
|
deviceId: source.id,
|
335
|
350
|
groupId: source.groupId || null
|
336
|
351
|
};
|
|
@@ -339,7 +354,8 @@ function enumerateDevicesThroughMediaStreamTrack (callback) {
|
339
|
354
|
//add auto devices
|
340
|
355
|
devices.unshift(
|
341
|
356
|
createAutoDeviceInfo('audioinput'),
|
342
|
|
- createAutoDeviceInfo('videoinput')
|
|
357
|
+ createAutoDeviceInfo('videoinput'),
|
|
358
|
+ createAutoDeviceInfo('audiooutput')
|
343
|
359
|
);
|
344
|
360
|
callback(devices);
|
345
|
361
|
});
|
|
@@ -441,6 +457,29 @@ function handleLocalStream(streams, resolution) {
|
441
|
457
|
return res;
|
442
|
458
|
}
|
443
|
459
|
|
|
460
|
+/**
|
|
461
|
+ * Wraps original attachMediaStream function to set current audio output device
|
|
462
|
+ * if this is supported.
|
|
463
|
+ * @param {Function} origAttachMediaStream
|
|
464
|
+ * @returns {Function}
|
|
465
|
+ */
|
|
466
|
+function wrapAttachMediaStream(origAttachMediaStream) {
|
|
467
|
+ return function(element, stream) {
|
|
468
|
+ var res = origAttachMediaStream.apply(RTCUtils, arguments);
|
|
469
|
+
|
|
470
|
+ if (RTCUtils.isDeviceChangeAvailable('output') &&
|
|
471
|
+ stream.getAudioTracks && stream.getAudioTracks().length) {
|
|
472
|
+ element.setSinkId(RTCUtils.getAudioOutputDevice())
|
|
473
|
+ .catch(function (ex) {
|
|
474
|
+ logger.error('Failed to set audio output on element',
|
|
475
|
+ element, ex);
|
|
476
|
+ });
|
|
477
|
+ }
|
|
478
|
+
|
|
479
|
+ return res;
|
|
480
|
+ }
|
|
481
|
+}
|
|
482
|
+
|
444
|
483
|
//Options parameter is to pass config options. Currently uses only "useIPv6".
|
445
|
484
|
var RTCUtils = {
|
446
|
485
|
init: function (options) {
|
|
@@ -461,7 +500,7 @@ var RTCUtils = {
|
461
|
500
|
navigator.mediaDevices.enumerateDevices.bind(navigator.mediaDevices)
|
462
|
501
|
);
|
463
|
502
|
this.pc_constraints = {};
|
464
|
|
- this.attachMediaStream = function (element, stream) {
|
|
503
|
+ this.attachMediaStream = wrapAttachMediaStream(function (element, stream) {
|
465
|
504
|
// srcObject is being standardized and FF will eventually
|
466
|
505
|
// support that unprefixed. FF also supports the
|
467
|
506
|
// "element.src = URL.createObjectURL(...)" combo, but that
|
|
@@ -475,7 +514,7 @@ var RTCUtils = {
|
475
|
514
|
element.play();
|
476
|
515
|
|
477
|
516
|
return element;
|
478
|
|
- };
|
|
517
|
+ });
|
479
|
518
|
this.getStreamID = function (stream) {
|
480
|
519
|
var id = stream.id;
|
481
|
520
|
if (!id) {
|
|
@@ -510,7 +549,7 @@ var RTCUtils = {
|
510
|
549
|
this.getUserMedia = getUserMedia;
|
511
|
550
|
this.enumerateDevices = enumerateDevicesThroughMediaStreamTrack;
|
512
|
551
|
}
|
513
|
|
- this.attachMediaStream = function (element, stream) {
|
|
552
|
+ this.attachMediaStream = wrapAttachMediaStream(function (element, stream) {
|
514
|
553
|
|
515
|
554
|
// saves the created url for the stream, so we can reuse it
|
516
|
555
|
// and not keep creating urls
|
|
@@ -522,7 +561,7 @@ var RTCUtils = {
|
522
|
561
|
element.src = stream.jitsiObjectURL;
|
523
|
562
|
|
524
|
563
|
return element;
|
525
|
|
- };
|
|
564
|
+ });
|
526
|
565
|
this.getStreamID = function (stream) {
|
527
|
566
|
// streams from FF endpoints have the characters '{' and '}'
|
528
|
567
|
// that make jQuery choke.
|
|
@@ -573,7 +612,7 @@ var RTCUtils = {
|
573
|
612
|
self.peerconnection = RTCPeerConnection;
|
574
|
613
|
self.getUserMedia = window.getUserMedia;
|
575
|
614
|
self.enumerateDevices = enumerateDevicesThroughMediaStreamTrack;
|
576
|
|
- self.attachMediaStream = function (element, stream) {
|
|
615
|
+ self.attachMediaStream = wrapAttachMediaStream(function (element, stream) {
|
577
|
616
|
|
578
|
617
|
if (stream.id === "dummyAudio" || stream.id === "dummyVideo") {
|
579
|
618
|
return;
|
|
@@ -585,7 +624,7 @@ var RTCUtils = {
|
585
|
624
|
}
|
586
|
625
|
|
587
|
626
|
return attachMediaStream(element, stream);
|
588
|
|
- };
|
|
627
|
+ });
|
589
|
628
|
self.getStreamID = function (stream) {
|
590
|
629
|
return SDPUtil.filter_special_chars(stream.label);
|
591
|
630
|
};
|
|
@@ -814,15 +853,20 @@ var RTCUtils = {
|
814
|
853
|
return (MediaStreamTrack && MediaStreamTrack.getSources)? true : false;
|
815
|
854
|
},
|
816
|
855
|
/**
|
817
|
|
- * Returns true if changing the camera / microphone device is supported and
|
818
|
|
- * false if not.
|
|
856
|
+ * Returns true if changing the input (camera / microphone) or output
|
|
857
|
+ * (audio) device is supported and false if not.
|
|
858
|
+ * @params {string} [deviceType] - type of device to change. Default is
|
|
859
|
+ * undefined or 'input', 'output' - for audio output device change.
|
|
860
|
+ * @returns {boolean} true if available, false otherwise.
|
819
|
861
|
*/
|
820
|
|
- isDeviceChangeAvailable: function () {
|
821
|
|
- return RTCBrowserType.isChrome() ||
|
822
|
|
- RTCBrowserType.isFirefox() ||
|
823
|
|
- RTCBrowserType.isOpera() ||
|
824
|
|
- RTCBrowserType.isTemasysPluginUsed() ||
|
825
|
|
- RTCBrowserType.isNWJS();
|
|
862
|
+ isDeviceChangeAvailable: function (deviceType) {
|
|
863
|
+ return deviceType === 'output' || deviceType === 'audiooutput'
|
|
864
|
+ ? isAudioOutputDeviceChangeAvailable
|
|
865
|
+ : RTCBrowserType.isChrome() ||
|
|
866
|
+ RTCBrowserType.isFirefox() ||
|
|
867
|
+ RTCBrowserType.isOpera() ||
|
|
868
|
+ RTCBrowserType.isTemasysPluginUsed()||
|
|
869
|
+ RTCBrowserType.isNWJS();
|
826
|
870
|
},
|
827
|
871
|
/**
|
828
|
872
|
* A method to handle stopping of the stream.
|
|
@@ -853,8 +897,38 @@ var RTCUtils = {
|
853
|
897
|
*/
|
854
|
898
|
isDesktopSharingEnabled: function () {
|
855
|
899
|
return screenObtainer.isSupported();
|
856
|
|
- }
|
|
900
|
+ },
|
|
901
|
+ /**
|
|
902
|
+ * Sets current audio output device.
|
|
903
|
+ * @param {string} deviceId - id of 'audiooutput' device from
|
|
904
|
+ * navigator.mediaDevices.enumerateDevices(), '' for default device
|
|
905
|
+ * @returns {Promise} - resolves when audio output is changed, is rejected
|
|
906
|
+ * otherwise
|
|
907
|
+ */
|
|
908
|
+ setAudioOutputDevice: function (deviceId) {
|
|
909
|
+ if (!this.isDeviceChangeAvailable('output')) {
|
|
910
|
+ Promise.reject(
|
|
911
|
+ new Error('Audio output device change is not supported'));
|
|
912
|
+ }
|
857
|
913
|
|
|
914
|
+ return featureDetectionAudioEl.setSinkId(deviceId)
|
|
915
|
+ .then(function() {
|
|
916
|
+ audioOuputDeviceId = deviceId;
|
|
917
|
+
|
|
918
|
+ logger.log('Audio output device set to ' + deviceId);
|
|
919
|
+
|
|
920
|
+ eventEmitter.emit(RTCEvents.AUDIO_OUTPUT_DEVICE_CHANGED,
|
|
921
|
+ deviceId);
|
|
922
|
+ });
|
|
923
|
+ },
|
|
924
|
+ /**
|
|
925
|
+ * Returns currently used audio output device id, '' stands for default
|
|
926
|
+ * device
|
|
927
|
+ * @returns {string}
|
|
928
|
+ */
|
|
929
|
+ getAudioOutputDevice: function () {
|
|
930
|
+ return audioOuputDeviceId;
|
|
931
|
+ }
|
858
|
932
|
};
|
859
|
933
|
|
860
|
934
|
module.exports = RTCUtils;
|