瀏覽代碼

Added ability to switch audio output device - changes after review

tags/v0.0.2
Kostiantyn Tsaregradskyi 9 年之前
父節點
當前提交
eddea0a02a
共有 5 個檔案被更改,包括 58 行新增67 行删除
  1. 7
    12
      JitsiMeetJS.js
  2. 1
    1
      doc/example/example.js
  3. 11
    16
      modules/RTC/JitsiTrack.js
  4. 9
    14
      modules/RTC/RTC.js
  5. 30
    24
      modules/RTC/RTCUtils.js

+ 7
- 12
JitsiMeetJS.js 查看文件

140
         return RTC.isDeviceListAvailable();
140
         return RTC.isDeviceListAvailable();
141
     },
141
     },
142
     /**
142
     /**
143
-     * Returns true if changing the camera / microphone device is supported and
144
-     * false if not.
143
+     * Returns true if changing the input (camera / microphone) or output
144
+     * (audio) device is supported and false if not.
145
+     * @params {string} [deviceType] - type of device to change. Default is
146
+     *      undefined or 'input', 'output' - for audio output device change.
145
      * @returns {boolean} true if available, false otherwise.
147
      * @returns {boolean} true if available, false otherwise.
146
      */
148
      */
147
-    isDeviceChangeAvailable: function () {
148
-        return RTC.isDeviceChangeAvailable();
149
-    },
150
-    /**
151
-     * Returns true if changing the audio output of media elements is supported
152
-     * and false if not.
153
-     */
154
-    isAudioOutputDeviceChangeAvailable: function () {
155
-        return RTC.isAudioOutputDeviceChangeAvailable();
149
+    isDeviceChangeAvailable: function (deviceType) {
150
+        return RTC.isDeviceChangeAvailable(deviceType);
156
     },
151
     },
157
     /**
152
     /**
158
      * Returns currently used audio output device id, '' stands for default
153
      * Returns currently used audio output device id, '' stands for default
165
     /**
160
     /**
166
      * Sets current audio output device.
161
      * Sets current audio output device.
167
      * @param {string} deviceId - id of 'audiooutput' device from
162
      * @param {string} deviceId - id of 'audiooutput' device from
168
-     *      navigator.mediaDevices.enumerateDevices()
163
+     *      navigator.mediaDevices.enumerateDevices(), '' is for default device
169
      * @returns {Promise} - resolves when audio output is changed, is rejected
164
      * @returns {Promise} - resolves when audio output is changed, is rejected
170
      *      otherwise
165
      *      otherwise
171
      */
166
      */

+ 1
- 1
doc/example/example.js 查看文件

241
 });
241
 });
242
 
242
 
243
 
243
 
244
-if (JitsiMeetJS.isAudioOutputDeviceChangeAvailable()) {
244
+if (JitsiMeetJS.isDeviceChangeAvailable('output')) {
245
     JitsiMeetJS.enumerateDevices(function(devices) {
245
     JitsiMeetJS.enumerateDevices(function(devices) {
246
         var audioOutputDevices = devices.filter(function(d) { return d.kind === 'audiooutput'; });
246
         var audioOutputDevices = devices.filter(function(d) { return d.kind === 'audiooutput'; });
247
 
247
 

+ 11
- 16
modules/RTC/JitsiTrack.js 查看文件

313
 };
313
 };
314
 
314
 
315
 /**
315
 /**
316
- * Set new audio output device for track's DOM elements.
316
+ * Sets new audio output device for track's DOM elements. Video tracks are
317
+ * ignored.
317
  * @param {string} audioOutputDeviceId - id of 'audiooutput' device from
318
  * @param {string} audioOutputDeviceId - id of 'audiooutput' device from
318
- *      navigator.mediaDevices.enumerateDevices()
319
+ *      navigator.mediaDevices.enumerateDevices(), '' for default device
319
  * @emits JitsiTrackEvents.TRACK_AUDIO_OUTPUT_CHANGED
320
  * @emits JitsiTrackEvents.TRACK_AUDIO_OUTPUT_CHANGED
320
  * @returns {Promise}
321
  * @returns {Promise}
321
  */
322
  */
322
 JitsiTrack.prototype.setAudioOutput = function (audioOutputDeviceId) {
323
 JitsiTrack.prototype.setAudioOutput = function (audioOutputDeviceId) {
323
     var self = this;
324
     var self = this;
324
 
325
 
325
-    if (!RTCUtils.isAudioOutputDeviceChangeAvailable()) {
326
+    if (!RTCUtils.isDeviceChangeAvailable('output')) {
326
         return Promise.reject(
327
         return Promise.reject(
327
             new Error('Audio output device change is not supported'));
328
             new Error('Audio output device change is not supported'));
328
     }
329
     }
329
 
330
 
331
+    // All audio communication is done through audio tracks, so ignore changing
332
+    // audio output for video tracks at all.
333
+    if (this.isVideoTrack()) {
334
+        return Promise.resolve();
335
+    }
336
+
330
     return Promise.all(this.containers.map(function(element) {
337
     return Promise.all(this.containers.map(function(element) {
331
         return element.setSinkId(audioOutputDeviceId)
338
         return element.setSinkId(audioOutputDeviceId)
332
             .catch(function (error) {
339
             .catch(function (error) {
333
-                console.error('Failed to change audio output device on element',
340
+                logger.error('Failed to change audio output device on element',
334
                     element, error);
341
                     element, error);
335
 
342
 
336
-                // TODO: for some reason 'AbortError' is raised on video
337
-                // elements with local track blobs. Maybe this is something
338
-                // similar to https://goo.gl/TKLiqx. Ignoring this error for
339
-                // now. Spec says that "If the device identified by the given
340
-                // sinkId cannot be used due to a unspecified error, throw a
341
-                // DOMException whose name is AbortError."
342
-                // In any case, all audio communication is done via separate
343
-                // audio elements, so maybe it doesn't make sense to change
344
-                // sinkId for <video> elements at all.
345
-                if (!(self.isVideoTrack() && self.isLocal && self.isLocal() &&
346
-                    error.name === 'AbortError')) {
347
                     throw error;
343
                     throw error;
348
-                }
349
             });
344
             });
350
     }))
345
     }))
351
     .then(function () {
346
     .then(function () {

+ 9
- 14
modules/RTC/RTC.js 查看文件

307
 };
307
 };
308
 
308
 
309
 /**
309
 /**
310
- * Returns true if changing the camera / microphone device is supported and
311
- * false if not.
312
- */
313
-RTC.isDeviceChangeAvailable = function () {
314
-    return RTCUtils.isDeviceChangeAvailable();
315
-};
316
-
317
-/**
318
- * Returns true if changing the audio output of media elements is supported
319
- * and false if not.
310
+ * Returns true if changing the input (camera / microphone) or output
311
+ * (audio) device is supported and false if not.
312
+ * @params {string} [deviceType] - type of device to change. Default is
313
+ *      undefined or 'input', 'output' - for audio output device change.
314
+ * @returns {boolean} true if available, false otherwise.
320
  */
315
  */
321
-RTC.isAudioOutputDeviceChangeAvailable = function () {
322
-    return RTCUtils.isAudioOutputDeviceChangeAvailable();
316
+RTC.isDeviceChangeAvailable = function (deviceType) {
317
+    return RTCUtils.isDeviceChangeAvailable(deviceType);
323
 };
318
 };
324
 
319
 
325
 /**
320
 /**
346
  * Returns <tt>true<tt/> if given WebRTC MediaStream is considered a valid
341
  * Returns <tt>true<tt/> if given WebRTC MediaStream is considered a valid
347
  * "user" stream which means that it's not a "receive only" stream nor a "mixed"
342
  * "user" stream which means that it's not a "receive only" stream nor a "mixed"
348
  * JVB stream.
343
  * JVB stream.
349
- * 
344
+ *
350
  * Clients that implement Unified Plan, such as Firefox use recvonly
345
  * Clients that implement Unified Plan, such as Firefox use recvonly
351
  * "streams/channels/tracks" for receiving remote stream/tracks, as opposed to
346
  * "streams/channels/tracks" for receiving remote stream/tracks, as opposed to
352
  * Plan B where there are only 3 channels: audio, video and data.
347
  * Plan B where there are only 3 channels: audio, video and data.
353
- * 
348
+ *
354
  * @param stream WebRTC MediaStream instance
349
  * @param stream WebRTC MediaStream instance
355
  * @returns {boolean}
350
  * @returns {boolean}
356
  */
351
  */

+ 30
- 24
modules/RTC/RTCUtils.js 查看文件

26
 
26
 
27
 var audioOuputDeviceId = ''; // default device
27
 var audioOuputDeviceId = ''; // default device
28
 
28
 
29
-var featureDetectionVideoEl = document.createElement('video');
29
+var featureDetectionAudioEl = document.createElement('audio');
30
+var isAudioOutputDeviceChangeAvailable =
31
+    typeof featureDetectionAudioEl.setSinkId !== 'undefined';
30
 
32
 
31
 var rtcReady = false;
33
 var rtcReady = false;
32
 
34
 
338
             return {
340
             return {
339
                 facing: source.facing || null,
341
                 facing: source.facing || null,
340
                 label: source.label,
342
                 label: source.label,
341
-                // theoretically deprecated MediaStreamTrack.getSources should not return 'audiooutput' devices but
342
-                // let's handle it in any case
343
-                kind: kind ? (kind === 'audiooutput' ? 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,
344
                 deviceId: source.id,
349
                 deviceId: source.id,
345
                 groupId: source.groupId || null
350
                 groupId: source.groupId || null
346
             };
351
             };
462
     return function(element, stream) {
467
     return function(element, stream) {
463
         var res = origAttachMediaStream.apply(RTCUtils, arguments);
468
         var res = origAttachMediaStream.apply(RTCUtils, arguments);
464
 
469
 
465
-        if (RTCUtils.isAudioOutputDeviceChangeAvailable()) {
470
+        if (RTCUtils.isDeviceChangeAvailable('output') &&
471
+            stream.getAudioTracks && stream.getAudioTracks().length) {
466
             element.setSinkId(RTCUtils.getAudioOutputDevice())
472
             element.setSinkId(RTCUtils.getAudioOutputDevice())
467
                 .catch(function (ex) {
473
                 .catch(function (ex) {
468
-                    console.error('Failed to set audio output on element',
474
+                    logger.error('Failed to set audio output on element',
469
                         element, ex);
475
                         element, ex);
470
                 });
476
                 });
471
         }
477
         }
847
         return (MediaStreamTrack && MediaStreamTrack.getSources)? true : false;
853
         return (MediaStreamTrack && MediaStreamTrack.getSources)? true : false;
848
     },
854
     },
849
     /**
855
     /**
850
-     * Returns true if changing the camera / microphone device is supported and
851
-     * false if not.
852
-     */
853
-    isDeviceChangeAvailable: function () {
854
-        return RTCBrowserType.isChrome() ||
855
-            RTCBrowserType.isFirefox() ||
856
-            RTCBrowserType.isOpera() ||
857
-            RTCBrowserType.isTemasysPluginUsed();
858
-    },
859
-    /**
860
-     * Returns true if changing the audio output of media elements is supported
861
-     * and 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.
862
      */
861
      */
863
-    isAudioOutputDeviceChangeAvailable: function () {
864
-        return typeof featureDetectionVideoEl.setSinkId !== 'undefined';
862
+    isDeviceChangeAvailable: function (deviceType) {
863
+        return deviceType === 'output' || deviceType === 'audiooutput'
864
+            ? isAudioOutputDeviceChangeAvailable
865
+            : RTCBrowserType.isChrome() ||
866
+                RTCBrowserType.isFirefox() ||
867
+                RTCBrowserType.isOpera() ||
868
+                RTCBrowserType.isTemasysPluginUsed();
865
     },
869
     },
866
     /**
870
     /**
867
      * A method to handle stopping of the stream.
871
      * A method to handle stopping of the stream.
896
     /**
900
     /**
897
      * Sets current audio output device.
901
      * Sets current audio output device.
898
      * @param {string} deviceId - id of 'audiooutput' device from
902
      * @param {string} deviceId - id of 'audiooutput' device from
899
-     *      navigator.mediaDevices.enumerateDevices()
903
+     *      navigator.mediaDevices.enumerateDevices(), '' for default device
900
      * @returns {Promise} - resolves when audio output is changed, is rejected
904
      * @returns {Promise} - resolves when audio output is changed, is rejected
901
      *      otherwise
905
      *      otherwise
902
      */
906
      */
903
     setAudioOutputDevice: function (deviceId) {
907
     setAudioOutputDevice: function (deviceId) {
904
-        if (!this.isAudioOutputDeviceChangeAvailable()) {
908
+        if (!this.isDeviceChangeAvailable('output')) {
905
             Promise.reject(
909
             Promise.reject(
906
                 new Error('Audio output device change is not supported'));
910
                 new Error('Audio output device change is not supported'));
907
         }
911
         }
908
-        
909
-        return featureDetectionVideoEl.setSinkId(deviceId)
912
+
913
+        return featureDetectionAudioEl.setSinkId(deviceId)
910
             .then(function() {
914
             .then(function() {
911
                 audioOuputDeviceId = deviceId;
915
                 audioOuputDeviceId = deviceId;
912
 
916
 
917
+                logger.log('Audio output device set to ' + deviceId);
918
+
913
                 eventEmitter.emit(RTCEvents.AUDIO_OUTPUT_DEVICE_CHANGED,
919
                 eventEmitter.emit(RTCEvents.AUDIO_OUTPUT_DEVICE_CHANGED,
914
                     deviceId);
920
                     deviceId);
915
             });
921
             });

Loading…
取消
儲存