浏览代码

Merge branch 'master' of https://github.com/tsareg/lib-jitsi-meet into tsareg-master

dev1
hristoterezov 9 年前
父节点
当前提交
0a730cb157
共有 9 个文件被更改,包括 236 次插入34 次删除
  1. 24
    4
      JitsiMeetJS.js
  2. 5
    1
      JitsiTrackEvents.js
  3. 10
    2
      doc/API.md
  4. 29
    0
      doc/example/example.js
  5. 4
    0
      doc/example/index.html
  6. 39
    0
      modules/RTC/JitsiTrack.js
  7. 29
    6
      modules/RTC/RTC.js
  8. 94
    20
      modules/RTC/RTCUtils.js
  9. 2
    1
      service/RTC/RTCEvents.js

+ 24
- 4
JitsiMeetJS.js 查看文件

@@ -151,12 +151,32 @@ var LibJitsiMeet = {
151 151
         return RTC.isDeviceListAvailable();
152 152
     },
153 153
     /**
154
-     * Returns true if changing the camera / microphone device is supported and
155
-     * false if not.
154
+     * Returns true if changing the input (camera / microphone) or output
155
+     * (audio) device is supported and false if not.
156
+     * @params {string} [deviceType] - type of device to change. Default is
157
+     *      undefined or 'input', 'output' - for audio output device change.
156 158
      * @returns {boolean} true if available, false otherwise.
157 159
      */
158
-    isDeviceChangeAvailable: function () {
159
-        return RTC.isDeviceChangeAvailable();
160
+    isDeviceChangeAvailable: function (deviceType) {
161
+        return RTC.isDeviceChangeAvailable(deviceType);
162
+    },
163
+    /**
164
+     * Returns currently used audio output device id, '' stands for default
165
+     * device
166
+     * @returns {string}
167
+     */
168
+    getAudioOutputDevice: function () {
169
+        return RTC.getAudioOutputDevice();
170
+    },
171
+    /**
172
+     * Sets current audio output device.
173
+     * @param {string} deviceId - id of 'audiooutput' device from
174
+     *      navigator.mediaDevices.enumerateDevices(), '' is for default device
175
+     * @returns {Promise} - resolves when audio output is changed, is rejected
176
+     *      otherwise
177
+     */
178
+    setAudioOutputDevice: function (deviceId) {
179
+        return RTC.setAudioOutputDevice(deviceId);
160 180
     },
161 181
     enumerateDevices: function (callback) {
162 182
         RTC.enumerateDevices(callback);

+ 5
- 1
JitsiTrackEvents.js 查看文件

@@ -14,7 +14,11 @@ var JitsiTrackEvents = {
14 14
     /**
15 15
      * The video type("camera" or "desktop") of the track was changed.
16 16
      */
17
-     TRACK_VIDEOTYPE_CHANGED: "track.videoTypeChanged"
17
+    TRACK_VIDEOTYPE_CHANGED: "track.videoTypeChanged",
18
+    /**
19
+     * The audio output of the track was changed.
20
+     */
21
+    TRACK_AUDIO_OUTPUT_CHANGED: "track.audioOutputChanged"
18 22
 };
19 23
 
20 24
 module.exports = JitsiTrackEvents;

+ 10
- 2
doc/API.md 查看文件

@@ -70,11 +70,15 @@ JitsiMeetJS.setLogLevel(JitsiMeetJS.logLevels.ERROR);
70 70
     - kind - "audioinput" or "videoinput"
71 71
     - deviceId - the id of the device.
72 72
 
73
-* ```JitsiMeetJS.isDeviceListAvailable()```- returns true if retrieving the device list is support and false - otherwise.
74
-
73
+* ```JitsiMeetJS.isDeviceListAvailable()``` - returns true if retrieving the device list is support and false - otherwise.
74
+* ```JitsiMeetJS.isDeviceChangeAvailable(deviceType)``` - returns true if changing the input (camera / microphone) or output (audio) device is supported and false if not. ```deviceType``` is a type of device to change. Undefined or 'input' stands for input devices, 'output' - for audio output devices.
75
+* ```JitsiMeetJS.setAudioOutputDevice(deviceId)``` - sets current audio output device. ```deviceId``` - id of 'audiooutput' device from ```JitsiMeetJS.enumerateDevices()```, '' is for default device.
76
+* ```JitsiMeetJS.getAudioOutputDevice()``` - returns currently used audio output device id, '' stands for default device.
75 77
 * ```JitsiMeetJS.isDesktopSharingEnabled()``` - returns true if desktop sharing is supported and false otherwise. NOTE: that method can be used after ```JitsiMeetJS.init(options)``` is completed otherwise the result will be always null.
76 78
 * ```JitsiMeetJS.getGlobalOnErrorHandler()``` - returns function that can be used to be attached to window.onerror and if options.enableWindowOnErrorHandler is enabled returns the function used by the lib. (function(message, source, lineno, colno, error)).
77 79
 
80
+
81
+
78 82
 * ```JitsiMeetJS.events``` - JS object that contains all events used by the API. You will need that JS object when you try to subscribe for connection or conference events.
79 83
     We have two event types - connection and conference. You can access the events with the following code ```JitsiMeetJS.events.<event_type>.<event_name>```.
80 84
     For example if you want to use the conference event that is fired when somebody leave conference you can use the following code - ```JitsiMeetJS.events.conference.USER_LEFT```.
@@ -114,6 +118,8 @@ JitsiMeetJS.setLogLevel(JitsiMeetJS.logLevels.ERROR);
114 118
     3. tracks
115 119
         - LOCAL_TRACK_STOPPED - indicates that a local track was stopped. This
116 120
         event can be fired when ```dispose()``` method is called or for other reasons.
121
+        - TRACK_AUDIO_OUTPUT_CHANGED - indicates that audio output device for track was changed (parameters - deviceId (string) - new audio output device ID).
122
+        
117 123
 
118 124
 * ```JitsiMeetJS.errors``` - JS object that contains all errors used by the API. You can use that object to check the reported errors from the API
119 125
     We have two error types - connection and conference. You can access the events with the following code ```JitsiMeetJS.errors.<error_type>.<error_name>```.
@@ -337,6 +343,8 @@ We have the following methods for controling the tracks:
337 343
 9. getParticipantId() - returns id(string) of the track owner
338 344
 
339 345
    Note: This method is implemented only for the remote tracks.
346
+   
347
+10. setAudioOutput(audioOutputDeviceId) - sets new audio output device for track's DOM elements. Video tracks are ignored.
340 348
 
341 349
 
342 350
 Getting Started

+ 29
- 0
doc/example/example.js 查看文件

@@ -36,6 +36,10 @@ function onLocalTracks(tracks)
36 36
             function () {
37 37
                 console.log("local track stoped");
38 38
             });
39
+        localTracks[i].addEventListener(JitsiMeetJS.events.track.TRACK_AUDIO_OUTPUT_CHANGED,
40
+            function (deviceId) {
41
+                console.log("track audio output device was changed to " + deviceId);
42
+            });
39 43
         if(localTracks[i].getType() == "video") {
40 44
             $("body").append("<video autoplay='1' id='localVideo" + i + "' />");
41 45
             localTracks[i].attach($("#localVideo" + i)[0]);
@@ -71,6 +75,10 @@ function onRemoteTrack(track) {
71 75
         function () {
72 76
             console.log("remote track stoped");
73 77
         });
78
+    track.addEventListener(JitsiMeetJS.events.track.TRACK_AUDIO_OUTPUT_CHANGED,
79
+        function (deviceId) {
80
+            console.log("track audio output device was changed to " + deviceId);
81
+        });
74 82
     var id = participant + track.getType() + idx;
75 83
     if(track.getType() == "video") {
76 84
         $("body").append("<video autoplay='1' id='" + participant + "video" + idx + "' />");
@@ -180,6 +188,10 @@ function switchVideo() {
180 188
         });
181 189
 }
182 190
 
191
+function changeAudioOutput(selected) {
192
+    JitsiMeetJS.setAudioOutputDevice(selected.value);
193
+}
194
+
183 195
 $(window).bind('beforeunload', unload);
184 196
 $(window).bind('unload', unload);
185 197
 
@@ -228,6 +240,23 @@ JitsiMeetJS.init(initOptions).then(function(){
228 240
     console.log(error);
229 241
 });
230 242
 
243
+
244
+if (JitsiMeetJS.isDeviceChangeAvailable('output')) {
245
+    JitsiMeetJS.enumerateDevices(function(devices) {
246
+        var audioOutputDevices = devices.filter(function(d) { return d.kind === 'audiooutput'; });
247
+
248
+        if (audioOutputDevices.length > 1) {
249
+            $('#audioOutputSelect').html(
250
+                audioOutputDevices.map(function (d) {
251
+                    return '<option value="' + d.deviceId + '">' + d.label + '</option>';
252
+                }).join('\n')
253
+            );
254
+
255
+            $('#audioOutputSelectWrapper').show();
256
+        }
257
+    })
258
+}
259
+
231 260
 var connection = null;
232 261
 var room = null;
233 262
 var localTracks = [];

+ 4
- 0
doc/example/index.html 查看文件

@@ -13,6 +13,10 @@
13 13
 <body>
14 14
     <a href="#" onclick="unload()">Unload</a>
15 15
     <a href="#" onclick="switchVideo()">switchVideo</a>
16
+    <div id="audioOutputSelectWrapper" style="display: none;">
17
+        Change audio output device
18
+        <select id="audioOutputSelect" onchange="changeAudioOutput(this)"></select>
19
+    </div>
16 20
     <!-- <video id="localVideo" autoplay="true"></video> -->
17 21
     <!--<audio id="localAudio" autoplay="true" muted="true"></audio>-->
18 22
 </body>

+ 39
- 0
modules/RTC/JitsiTrack.js 查看文件

@@ -76,6 +76,8 @@ function JitsiTrack(rtc, stream, track, streamInactiveHandler, trackMediaType,
76 76
         }
77 77
         addMediaStreamInactiveHandler(stream, streamInactiveHandler);
78 78
     }
79
+
80
+    RTCUtils.addListener(RTCEvents.AUDIO_OUTPUT_DEVICE_CHANGED, this.setAudioOutput.bind(this));
79 81
 }
80 82
 
81 83
 /**
@@ -310,4 +312,41 @@ JitsiTrack.prototype.getMSID = function () {
310 312
     return (streamId && trackId) ? (streamId + " " + trackId) : null;
311 313
 };
312 314
 
315
+/**
316
+ * Sets new audio output device for track's DOM elements. Video tracks are
317
+ * ignored.
318
+ * @param {string} audioOutputDeviceId - id of 'audiooutput' device from
319
+ *      navigator.mediaDevices.enumerateDevices(), '' for default device
320
+ * @emits JitsiTrackEvents.TRACK_AUDIO_OUTPUT_CHANGED
321
+ * @returns {Promise}
322
+ */
323
+JitsiTrack.prototype.setAudioOutput = function (audioOutputDeviceId) {
324
+    var self = this;
325
+
326
+    if (!RTCUtils.isDeviceChangeAvailable('output')) {
327
+        return Promise.reject(
328
+            new Error('Audio output device change is not supported'));
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
+
337
+    return Promise.all(this.containers.map(function(element) {
338
+        return element.setSinkId(audioOutputDeviceId)
339
+            .catch(function (error) {
340
+                logger.error('Failed to change audio output device on element',
341
+                    element, error);
342
+
343
+                    throw error;
344
+            });
345
+    }))
346
+    .then(function () {
347
+        self.eventEmitter.emit(JitsiTrackEvents.TRACK_AUDIO_OUTPUT_CHANGED,
348
+            audioOutputDeviceId);
349
+    });
350
+};
351
+
313 352
 module.exports = JitsiTrack;

+ 29
- 6
modules/RTC/RTC.js 查看文件

@@ -307,22 +307,45 @@ RTC.isDeviceListAvailable = function () {
307 307
 };
308 308
 
309 309
 /**
310
- * Returns true if changing the camera / microphone device is supported and
311
- * 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.
315
+ */
316
+RTC.isDeviceChangeAvailable = function (deviceType) {
317
+    return RTCUtils.isDeviceChangeAvailable(deviceType);
318
+};
319
+
320
+/**
321
+ * Returns currently used audio output device id, '' stands for default
322
+ * device
323
+ * @returns {string}
324
+ */
325
+RTC.getAudioOutputDevice = function () {
326
+    return RTCUtils.getAudioOutputDevice();
327
+};
328
+
329
+/**
330
+ * Sets current audio output device.
331
+ * @param {string} deviceId - id of 'audiooutput' device from
332
+ *      navigator.mediaDevices.enumerateDevices()
333
+ * @returns {Promise} - resolves when audio output is changed, is rejected
334
+ *      otherwise
312 335
  */
313
-RTC.isDeviceChangeAvailable = function () {
314
-    return RTCUtils.isDeviceChangeAvailable();
336
+RTC.setAudioOutputDevice = function (deviceId) {
337
+    return RTCUtils.setAudioOutputDevice(deviceId);
315 338
 };
316 339
 
317 340
 /**
318 341
  * Returns <tt>true<tt/> if given WebRTC MediaStream is considered a valid
319 342
  * "user" stream which means that it's not a "receive only" stream nor a "mixed"
320 343
  * JVB stream.
321
- * 
344
+ *
322 345
  * Clients that implement Unified Plan, such as Firefox use recvonly
323 346
  * "streams/channels/tracks" for receiving remote stream/tracks, as opposed to
324 347
  * Plan B where there are only 3 channels: audio, video and data.
325
- * 
348
+ *
326 349
  * @param stream WebRTC MediaStream instance
327 350
  * @returns {boolean}
328 351
  */

+ 94
- 20
modules/RTC/RTCUtils.js 查看文件

@@ -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;

+ 2
- 1
service/RTC/RTCEvents.js 查看文件

@@ -6,7 +6,8 @@ var RTCEvents = {
6 6
     LASTN_ENDPOINT_CHANGED: "rtc.lastn_endpoint_changed",
7 7
     AVAILABLE_DEVICES_CHANGED: "rtc.available_devices_changed",
8 8
     FAKE_VIDEO_TRACK_CREATED: "rtc.fake_video_track_created",
9
-    TRACK_ATTACHED: "rtc.track_attached"
9
+    TRACK_ATTACHED: "rtc.track_attached",
10
+    AUDIO_OUTPUT_DEVICE_CHANGED: "rtc.audio_output_device_changed"
10 11
 };
11 12
 
12 13
 module.exports = RTCEvents;

正在加载...
取消
保存