Przeglądaj źródła

Adds device selection support.

release-8443
hristoterezov 10 lat temu
rodzic
commit
bc1bc1d024

+ 1
- 0
JitsiConference.js Wyświetl plik

257
     });
257
     });
258
     conference.rtc.addListener(StreamEventTypes.EVENT_TYPE_LOCAL_ENDED, function (stream) {
258
     conference.rtc.addListener(StreamEventTypes.EVENT_TYPE_LOCAL_ENDED, function (stream) {
259
         conference.eventEmitter.emit(JitsiConferenceEvents.TRACK_REMOVED, stream);
259
         conference.eventEmitter.emit(JitsiConferenceEvents.TRACK_REMOVED, stream);
260
+        conference.removeTrack(stream);
260
     });
261
     });
261
     conference.rtc.addListener(StreamEventTypes.TRACK_MUTE_CHANGED, function (track) {
262
     conference.rtc.addListener(StreamEventTypes.TRACK_MUTE_CHANGED, function (track) {
262
         conference.eventEmitter.emit(JitsiConferenceEvents.TRACK_MUTE_CHANGED, track);
263
         conference.eventEmitter.emit(JitsiConferenceEvents.TRACK_MUTE_CHANGED, track);

+ 7
- 0
JitsiMeetJS.js Wyświetl plik

31
      * Creates the media tracks and returns them trough the callback.
31
      * Creates the media tracks and returns them trough the callback.
32
      * @param options Object with properties / settings specifying the tracks which should be created.
32
      * @param options Object with properties / settings specifying the tracks which should be created.
33
      * should be created or some additional configurations about resolution for example.
33
      * should be created or some additional configurations about resolution for example.
34
+     * @param {Array} options.devices the devices that will be requested
35
+     * @param {string} options.resolution resolution constraints
36
+     * @param {bool} options.dontCreateJitsiTrack if <tt>true</tt> objects with the following structure {stream: the Media Stream,
37
+     * type: "audio" or "video", videoType: "camera" or "desktop"}
38
+     * will be returned trough the Promise, otherwise JitsiTrack objects will be returned.
39
+     * @param {string} options.cameraDeviceId
40
+     * @param {string} options.micDeviceId
34
      * @returns {Promise.<{Array.<JitsiTrack>}, JitsiConferenceError>} A promise that returns an array of created JitsiTracks if resolved,
41
      * @returns {Promise.<{Array.<JitsiTrack>}, JitsiConferenceError>} A promise that returns an array of created JitsiTracks if resolved,
35
      *     or a JitsiConferenceError if rejected.
42
      *     or a JitsiConferenceError if rejected.
36
      */
43
      */

+ 19
- 12
doc/API.md Wyświetl plik

46
 JitsiMeetJS.setLogLevel(JitsiMeetJS.logLevels.ERROR);
46
 JitsiMeetJS.setLogLevel(JitsiMeetJS.logLevels.ERROR);
47
 ```
47
 ```
48
 
48
 
49
+* ```JitsiMeetJS.createLocalTracks(options)``` - Creates the media tracks and returns them trough ```Promise``` object.
50
+    - options - JS object with configuration options for the local media tracks. You can change the following properties there:
51
+        1. devices - array with the devices - "video" and "audio" that will be passed to GUM. If that property is not set GUM will try to get all available devices.
52
+        2. resolution - the prefered resolution for the local video.
53
+        3. cameraDeviceId - the deviceID for the video device that is going to be used
54
+        4. micDeviceId - the deviceID for the audio device that is going to be used
55
+
56
+* ```JitsiMeetJS.enumerateDevices(callback)``` - returns list of the available devices as a parameter to the callback function. Every device is a object with the following format:
57
+    - label - the name of the device
58
+    - kind - "audioinput" or "videoinput"
59
+    - deviceId - the id of the device.
60
+
61
+* ```JitsiMeetJS.isDeviceListAvailable()```- returns true if retrieving the device list is support and false - otherwise.
62
+
49
 * ```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.
63
 * ```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.
50
     We have two event types - connection and conference. You can access the events with the following code ```JitsiMeetJS.events.<event_type>.<event_name>```.
64
     We have two event types - connection and conference. You can access the events with the following code ```JitsiMeetJS.events.<event_type>.<event_name>```.
51
     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```.
65
     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```.
139
 
153
 
140
 2. leave() - leaves the conference
154
 2. leave() - leaves the conference
141
 
155
 
142
-3. createLocalTracks(options) - Creates the media tracks and returns them trough ```Promise``` object.
143
-    - options - JS object with configuration options for the local media tracks. You can change the following properties there:
144
-        1. devices - array with the devices - "video" and "audio" that will be passed to GUM. If that property is not set GUM will try to get all available devices.
145
-        2. resolution - the prefered resolution for the local video.
146
-
147
 4. getLocalTracks() - Returns array with JitsiTrack objects for the local streams.
156
 4. getLocalTracks() - Returns array with JitsiTrack objects for the local streams.
148
 
157
 
149
 5. addEventListener(event, listener) - Subscribes the passed listener to the event.
158
 5. addEventListener(event, listener) - Subscribes the passed listener to the event.
202
 16. removeCommandListener(command) - removes the listeners for the specified command
211
 16. removeCommandListener(command) - removes the listeners for the specified command
203
     - command - the name of the command
212
     - command - the name of the command
204
 
213
 
205
-
214
+17. addTrack(track) - Adds JitsiLocalTrack object to the conference.
215
+    - track - the JitsiLocalTrack
216
+17. removeTrack(track) - Removes JitsiLocalTrack object to the conference.
217
+    - track - the JitsiLocalTrack
206
 JitsiTrack
218
 JitsiTrack
207
 ======
219
 ======
208
 The object represents single track - video or audio. They can be remote tracks ( from the other participants in the call) or local tracks (from the devices of the local participant).
220
 The object represents single track - video or audio. They can be remote tracks ( from the other participants in the call) or local tracks (from the devices of the local participant).
229
 
241
 
230
 Note: This method is implemented only for the local tracks.
242
 Note: This method is implemented only for the local tracks.
231
 
243
 
232
-7. start() - start sending the track to the other participants in the conference.
233
-
234
-Note: This method is implemented only for the local tracks.
235
-
236
-8. getId() - returns unique string for the track.
237
-
244
+7. getId() - returns unique string for the track.
238
 
245
 
239
 
246
 
240
 Getting Started
247
 Getting Started

+ 25
- 6
doc/example/example.js Wyświetl plik

141
 // JitsiMeetJS.setLogLevel(JitsiMeetJS.logLevels.ERROR);
141
 // JitsiMeetJS.setLogLevel(JitsiMeetJS.logLevels.ERROR);
142
 
142
 
143
 JitsiMeetJS.init();
143
 JitsiMeetJS.init();
144
-JitsiMeetJS.createLocalTracks({resolution: "720"}).then(onLocalTracks);
145
-var connection = new JitsiMeetJS.JitsiConnection(null, null, options);
146
 
144
 
145
+JitsiMeetJS.enumerateDevices(function (devices) {
146
+    for(var i = 0; i < devices.length; i++)
147
+    {
148
+        var device = devices[i];
149
+        if(device.kind === "videoinput")
150
+            $("#videoDevices").append("<option value=\"" + device.deviceId +
151
+                "\">" + device.label + "</option>");
152
+    }
153
+})
154
+
155
+var connection = null;
147
 var room = null;
156
 var room = null;
148
 var localTracks = [];
157
 var localTracks = [];
149
 var remoteTracks = {};
158
 var remoteTracks = {};
150
 
159
 
151
-connection.addEventListener(JitsiMeetJS.events.connection.CONNECTION_ESTABLISHED, onConnectionSuccess);
152
-connection.addEventListener(JitsiMeetJS.events.connection.CONNECTION_FAILED, onConnectionFailed);
153
-connection.addEventListener(JitsiMeetJS.events.connection.CONNECTION_DISCONNECTED, disconnect);
154
 
160
 
155
-connection.connect();
161
+/**
162
+ * Starts the conference with the selected device
163
+ */
164
+function selectDevice() {
165
+    var videoID = $("#videoDevices").val();
166
+    JitsiMeetJS.createLocalTracks({resolution: "720", cameraDeviceId: videoID}).then(onLocalTracks);
167
+    connection = new JitsiMeetJS.JitsiConnection(null, null, options);
168
+
169
+    connection.addEventListener(JitsiMeetJS.events.connection.CONNECTION_ESTABLISHED, onConnectionSuccess);
170
+    connection.addEventListener(JitsiMeetJS.events.connection.CONNECTION_FAILED, onConnectionFailed);
171
+    connection.addEventListener(JitsiMeetJS.events.connection.CONNECTION_DISCONNECTED, disconnect);
172
+
173
+    connection.connect();
174
+}

+ 5
- 0
doc/example/index.html Wyświetl plik

12
 </head>
12
 </head>
13
 <body>
13
 <body>
14
     <a onclick="room.setDisplayName(Math.random())">Change Display Name</a>
14
     <a onclick="room.setDisplayName(Math.random())">Change Display Name</a>
15
+    <div>Select Video device:
16
+        <select id="videoDevices">
17
+        </select>
18
+        <a onclick = "selectDevice();">Select</a>
19
+    </div>
15
     <video id="localVideo" autoplay="true"></video>
20
     <video id="localVideo" autoplay="true"></video>
16
     <!--<audio id="localAudio" autoplay="true" muted="true"></audio>-->
21
     <!--<audio id="localAudio" autoplay="true" muted="true"></audio>-->
17
 </body>
22
 </body>

+ 297
- 64
lib-jitsi-meet.js Wyświetl plik

619
      * Creates the media tracks and returns them trough the callback.
619
      * Creates the media tracks and returns them trough the callback.
620
      * @param options Object with properties / settings specifying the tracks which should be created.
620
      * @param options Object with properties / settings specifying the tracks which should be created.
621
      * should be created or some additional configurations about resolution for example.
621
      * should be created or some additional configurations about resolution for example.
622
+     * @param {Array} options.devices the devices that will be requested
623
+     * @param {string} options.resolution resolution constraints
624
+     * @param {bool} options.dontCreateJitsiTrack if <tt>true</tt> objects with the following structure {stream: the Media Stream,
625
+     * type: "audio" or "video", videoType: "camera" or "desktop"}
626
+     * will be returned trough the Promise, otherwise JitsiTrack objects will be returned.
627
+     * @param {string} options.cameraDeviceId
628
+     * @param {string} options.micDeviceId
622
      * @returns {Promise.<{Array.<JitsiTrack>}, JitsiConferenceError>} A promise that returns an array of created JitsiTracks if resolved,
629
      * @returns {Promise.<{Array.<JitsiTrack>}, JitsiConferenceError>} A promise that returns an array of created JitsiTracks if resolved,
623
      *     or a JitsiConferenceError if rejected.
630
      *     or a JitsiConferenceError if rejected.
624
      */
631
      */
625
     createLocalTracks: function (options) {
632
     createLocalTracks: function (options) {
626
         return RTC.obtainAudioAndVideoPermissions(options || {});
633
         return RTC.obtainAudioAndVideoPermissions(options || {});
634
+    },
635
+    isDeviceListAvailable: function () {
636
+        return RTC.isDeviceListAvailable();
637
+    },
638
+    enumerateDevices: function (callback) {
639
+        RTC.enumerateDevices(callback);
627
     }
640
     }
628
 };
641
 };
629
 
642
 
630
-
631
 //Setups the promise object.
643
 //Setups the promise object.
632
 window.Promise = window.Promise || require("es6-promise").polyfill();
644
 window.Promise = window.Promise || require("es6-promise").polyfill();
633
 
645
 
1031
         } else {
1043
         } else {
1032
             var self = this;
1044
             var self = this;
1033
             var RTC = require("./RTCUtils");
1045
             var RTC = require("./RTCUtils");
1034
-            RTC.obtainAudioAndVideoPermissions(
1035
-                (isAudio ? ["audio"] : ["video"]),
1036
-                  self.resolution, true)
1046
+            RTC.obtainAudioAndVideoPermissions({
1047
+                devices: (isAudio ? ["audio"] : ["video"]),
1048
+                resolution: self.resolution,
1049
+                dontCreateJitsiTrack: true})
1037
                 .then(function (streams) {
1050
                 .then(function (streams) {
1038
                     var stream = null;
1051
                     var stream = null;
1039
                     for(var i = 0; i < streams.length; i++) {
1052
                     for(var i = 0; i < streams.length; i++) {
1373
 
1386
 
1374
 /**
1387
 /**
1375
  * Creates the local MediaStreams.
1388
  * Creates the local MediaStreams.
1376
- * @param options object for options (NOTE: currently only list of devices and resolution are supported)
1377
- * @param dontCreateJitsiTrack if <tt>true</tt> objects with the following structure {stream: the Media Stream,
1378
-  * type: "audio" or "video", videoType: "camera" or "desktop"}
1389
+ * @param {Object} [options] optional parameters
1390
+ * @param {Array} options.devices the devices that will be requested
1391
+ * @param {string} options.resolution resolution constraints
1392
+ * @param {bool} options.dontCreateJitsiTrack if <tt>true</tt> objects with the following structure {stream: the Media Stream,
1393
+ * type: "audio" or "video", videoType: "camera" or "desktop"}
1379
  * will be returned trough the Promise, otherwise JitsiTrack objects will be returned.
1394
  * will be returned trough the Promise, otherwise JitsiTrack objects will be returned.
1395
+ * @param {string} options.cameraDeviceId
1396
+ * @param {string} options.micDeviceId
1380
  * @returns {*} Promise object that will receive the new JitsiTracks
1397
  * @returns {*} Promise object that will receive the new JitsiTracks
1381
  */
1398
  */
1382
-RTC.obtainAudioAndVideoPermissions = function (options, dontCreateJitsiTrack) {
1383
-    return RTCUtils.obtainAudioAndVideoPermissions(
1384
-        options.devices, options.resolution, dontCreateJitsiTrack);
1399
+RTC.obtainAudioAndVideoPermissions = function (options) {
1400
+    return RTCUtils.obtainAudioAndVideoPermissions(options);
1385
 }
1401
 }
1386
 
1402
 
1387
 RTC.prototype.onIncommingCall = function(event) {
1403
 RTC.prototype.onIncommingCall = function(event) {
1472
 };
1488
 };
1473
 
1489
 
1474
 RTC.getUserMediaWithConstraints = function(um, success_callback,
1490
 RTC.getUserMediaWithConstraints = function(um, success_callback,
1475
-                                     failure_callback, resolution,
1476
-                                     bandwidth, fps, desktopStream)
1491
+                                     failure_callback, options)
1477
 {
1492
 {
1478
     return RTCUtils.getUserMediaWithConstraints(this, um, success_callback,
1493
     return RTCUtils.getUserMediaWithConstraints(this, um, success_callback,
1479
-        failure_callback, resolution, bandwidth, fps, desktopStream);
1494
+        failure_callback, options);
1480
 };
1495
 };
1481
 
1496
 
1482
 RTC.attachMediaStream =  function (elSelector, stream) {
1497
 RTC.attachMediaStream =  function (elSelector, stream) {
1491
     return RTCUtils.getVideoSrc(element);
1506
     return RTCUtils.getVideoSrc(element);
1492
 };
1507
 };
1493
 
1508
 
1509
+RTC.isDeviceListAvailable = function () {
1510
+    return RTCUtils.isDeviceListAvailable();
1511
+};
1512
+
1513
+/**
1514
+ * Allows to receive list of available cameras/microphones.
1515
+ * @param {function} callback would receive array of devices as an argument
1516
+ */
1517
+RTC.enumerateDevices = function (callback) {
1518
+    RTCUtils.enumerateDevices(callback);
1519
+};
1520
+
1494
 RTC.setVideoSrc = function (element, src) {
1521
 RTC.setVideoSrc = function (element, src) {
1495
     RTCUtils.setVideoSrc(element, src);
1522
     RTCUtils.setVideoSrc(element, src);
1496
 };
1523
 };
1808
 var devices = {
1835
 var devices = {
1809
     audio: true,
1836
     audio: true,
1810
     video: true
1837
     video: true
1811
-};
1838
+}
1812
 
1839
 
1813
 var rtcReady = false;
1840
 var rtcReady = false;
1814
 
1841
 
1858
         constraints.video.mandatory.maxHeight =
1885
         constraints.video.mandatory.maxHeight =
1859
             constraints.video.mandatory.minHeight;
1886
             constraints.video.mandatory.minHeight;
1860
 }
1887
 }
1861
-
1862
-function getConstraints(um, resolution, bandwidth, fps, desktopStream) {
1888
+/**
1889
+ * @param {string[]} um required user media types
1890
+ *
1891
+ * @param {Object} [options={}] optional parameters
1892
+ * @param {string} options.resolution
1893
+ * @param {number} options.bandwidth
1894
+ * @param {number} options.fps
1895
+ * @param {string} options.desktopStream
1896
+ * @param {string} options.cameraDeviceId
1897
+ * @param {string} options.micDeviceId
1898
+ * @param {bool} firefox_fake_device
1899
+ */
1900
+function getConstraints(um, options) {
1863
     var constraints = {audio: false, video: false};
1901
     var constraints = {audio: false, video: false};
1864
 
1902
 
1865
     if (um.indexOf('video') >= 0) {
1903
     if (um.indexOf('video') >= 0) {
1866
         // same behaviour as true
1904
         // same behaviour as true
1867
         constraints.video = { mandatory: {}, optional: [] };
1905
         constraints.video = { mandatory: {}, optional: [] };
1868
 
1906
 
1907
+        if (options.cameraDeviceId) {
1908
+            constraints.video.optional.push({
1909
+                sourceId: options.cameraDeviceId
1910
+            });
1911
+        }
1912
+
1869
         constraints.video.optional.push({ googLeakyBucket: true });
1913
         constraints.video.optional.push({ googLeakyBucket: true });
1870
 
1914
 
1871
-        setResolutionConstraints(constraints, resolution);
1915
+        setResolutionConstraints(constraints, options.resolution);
1872
     }
1916
     }
1873
     if (um.indexOf('audio') >= 0) {
1917
     if (um.indexOf('audio') >= 0) {
1874
         if (!RTCBrowserType.isFirefox()) {
1918
         if (!RTCBrowserType.isFirefox()) {
1875
             // same behaviour as true
1919
             // same behaviour as true
1876
             constraints.audio = { mandatory: {}, optional: []};
1920
             constraints.audio = { mandatory: {}, optional: []};
1921
+            if (options.micDeviceId) {
1922
+                constraints.audio.optional.push({
1923
+                    sourceId: options.micDeviceId
1924
+                });
1925
+            }
1877
             // if it is good enough for hangouts...
1926
             // if it is good enough for hangouts...
1878
             constraints.audio.optional.push(
1927
             constraints.audio.optional.push(
1879
                 {googEchoCancellation: true},
1928
                 {googEchoCancellation: true},
1885
                 {googAutoGainControl2: true}
1934
                 {googAutoGainControl2: true}
1886
             );
1935
             );
1887
         } else {
1936
         } else {
1888
-            constraints.audio = true;
1937
+            if (options.micDeviceId) {
1938
+                constraints.audio = {
1939
+                    mandatory: {},
1940
+                    optional: [{
1941
+                        sourceId: options.micDeviceId
1942
+                    }]};
1943
+            } else {
1944
+                constraints.audio = true;
1945
+            }
1889
         }
1946
         }
1890
     }
1947
     }
1891
     if (um.indexOf('screen') >= 0) {
1948
     if (um.indexOf('screen') >= 0) {
1918
         constraints.video = {
1975
         constraints.video = {
1919
             mandatory: {
1976
             mandatory: {
1920
                 chromeMediaSource: "desktop",
1977
                 chromeMediaSource: "desktop",
1921
-                chromeMediaSourceId: desktopStream,
1978
+                chromeMediaSourceId: options.desktopStream,
1922
                 googLeakyBucket: true,
1979
                 googLeakyBucket: true,
1923
                 maxWidth: window.screen.width,
1980
                 maxWidth: window.screen.width,
1924
                 maxHeight: window.screen.height,
1981
                 maxHeight: window.screen.height,
1928
         };
1985
         };
1929
     }
1986
     }
1930
 
1987
 
1931
-    if (bandwidth) {
1988
+    if (options.bandwidth) {
1932
         if (!constraints.video) {
1989
         if (!constraints.video) {
1933
             //same behaviour as true
1990
             //same behaviour as true
1934
             constraints.video = {mandatory: {}, optional: []};
1991
             constraints.video = {mandatory: {}, optional: []};
1935
         }
1992
         }
1936
-        constraints.video.optional.push({bandwidth: bandwidth});
1993
+        constraints.video.optional.push({bandwidth: options.bandwidth});
1937
     }
1994
     }
1938
-    if (fps) {
1995
+    if (options.fps) {
1939
         // for some cameras it might be necessary to request 30fps
1996
         // for some cameras it might be necessary to request 30fps
1940
         // so they choose 30fps mjpg over 10fps yuy2
1997
         // so they choose 30fps mjpg over 10fps yuy2
1941
         if (!constraints.video) {
1998
         if (!constraints.video) {
1942
             // same behaviour as true;
1999
             // same behaviour as true;
1943
             constraints.video = {mandatory: {}, optional: []};
2000
             constraints.video = {mandatory: {}, optional: []};
1944
         }
2001
         }
1945
-        constraints.video.mandatory.minFrameRate = fps;
2002
+        constraints.video.mandatory.minFrameRate = options.fps;
2003
+    }
2004
+
2005
+    // we turn audio for both audio and video tracks, the fake audio & video seems to work
2006
+    // only when enabled in one getUserMedia call, we cannot get fake audio separate by fake video
2007
+    // this later can be a problem with some of the tests
2008
+    if(RTCBrowserType.isFirefox() && options.firefox_fake_device)
2009
+    {
2010
+        constraints.audio = true;
2011
+        constraints.fake = true;
1946
     }
2012
     }
1947
 
2013
 
1948
     return constraints;
2014
     return constraints;
1949
 }
2015
 }
1950
 
2016
 
1951
 function setAvailableDevices(um, available) {
2017
 function setAvailableDevices(um, available) {
1952
-    var devices = {};
1953
     if (um.indexOf("video") != -1) {
2018
     if (um.indexOf("video") != -1) {
1954
         devices.video = available;
2019
         devices.video = available;
1955
     }
2020
     }
1968
     eventEmitter.emit(RTCEvents.RTC_READY, true);
2033
     eventEmitter.emit(RTCEvents.RTC_READY, true);
1969
 };
2034
 };
1970
 
2035
 
2036
+/**
2037
+ * Apply function with arguments if function exists.
2038
+ * Do nothing if function not provided.
2039
+ * @param {function} [fn] function to apply
2040
+ * @param {Array} [args=[]] arguments for function
2041
+ */
2042
+function maybeApply(fn, args) {
2043
+  if (fn) {
2044
+    fn.apply(null, args || []);
2045
+  }
2046
+}
2047
+
2048
+var getUserMediaStatus = {
2049
+  initialized: false,
2050
+  callbacks: []
2051
+};
2052
+
2053
+/**
2054
+ * Wrap `getUserMedia` to allow others to know if it was executed at least
2055
+ * once or not. Wrapper function uses `getUserMediaStatus` object.
2056
+ * @param {Function} getUserMedia native function
2057
+ * @returns {Function} wrapped function
2058
+ */
2059
+function wrapGetUserMedia(getUserMedia) {
2060
+  return function (constraints, successCallback, errorCallback) {
2061
+    getUserMedia(constraints, function (stream) {
2062
+      maybeApply(successCallback, [stream]);
2063
+      if (!getUserMediaStatus.initialized) {
2064
+        getUserMediaStatus.initialized = true;
2065
+        getUserMediaStatus.callbacks.forEach(function (callback) {
2066
+          callback();
2067
+        });
2068
+        getUserMediaStatus.callbacks.length = 0;
2069
+      }
2070
+    }, function (error) {
2071
+      maybeApply(errorCallback, [error]);
2072
+    });
2073
+  };
2074
+}
2075
+
2076
+/**
2077
+ * Create stub device which equals to auto selected device.
2078
+ * @param {string} kind if that should be `audio` or `video` device
2079
+ * @returns {Object} stub device description in `enumerateDevices` format
2080
+ */
2081
+function createAutoDeviceInfo(kind) {
2082
+    return {
2083
+        facing: null,
2084
+        label: 'Auto',
2085
+        kind: kind,
2086
+        deviceId: '',
2087
+        groupId: null
2088
+    };
2089
+}
2090
+
2091
+
2092
+/**
2093
+ * Execute function after getUserMedia was executed at least once.
2094
+ * @param {Function} callback function to execute after getUserMedia
2095
+ */
2096
+function afterUserMediaInitialized(callback) {
2097
+    if (getUserMediaStatus.initialized) {
2098
+        callback();
2099
+    } else {
2100
+        getUserMediaStatus.callbacks.push(callback);
2101
+    }
2102
+}
2103
+
2104
+/**
2105
+ * Wrapper function which makes enumerateDevices to wait
2106
+ * until someone executes getUserMedia first time.
2107
+ * @param {Function} enumerateDevices native function
2108
+ * @returns {Funtion} wrapped function
2109
+ */
2110
+function wrapEnumerateDevices(enumerateDevices) {
2111
+    return function (callback) {
2112
+        // enumerate devices only after initial getUserMedia
2113
+        afterUserMediaInitialized(function () {
2114
+
2115
+            enumerateDevices().then(function (devices) {
2116
+                //add auto devices
2117
+                devices.unshift(
2118
+                    createAutoDeviceInfo('audioinput'),
2119
+                    createAutoDeviceInfo('videoinput')
2120
+                );
2121
+
2122
+                callback(devices);
2123
+            }, function (err) {
2124
+                console.error('cannot enumerate devices: ', err);
2125
+
2126
+                // return only auto devices
2127
+                callback([createAutoDeviceInfo('audioInput'),
2128
+                          createAutoDeviceInfo('videoinput')]);
2129
+            });
2130
+        });
2131
+    };
2132
+}
2133
+
2134
+/**
2135
+ * Use old MediaStreamTrack to get devices list and
2136
+ * convert it to enumerateDevices format.
2137
+ * @param {Function} callback function to call when received devices list.
2138
+ */
2139
+function enumerateDevicesThroughMediaStreamTrack (callback) {
2140
+    MediaStreamTrack.getSources(function (sources) {
2141
+        var devices = sources.map(function (source) {
2142
+            var kind = (source.kind || '').toLowerCase();
2143
+            return {
2144
+                facing: source.facing || null,
2145
+                label: source.label,
2146
+                kind: kind ? kind + 'input': null,
2147
+                deviceId: source.id,
2148
+                groupId: source.groupId || null
2149
+            };
2150
+        });
2151
+
2152
+        //add auto devices
2153
+        devices.unshift(
2154
+            createAutoDeviceInfo('audioinput'),
2155
+            createAutoDeviceInfo('videoinput')
2156
+        );
2157
+        callback(devices);
2158
+    });
2159
+}
2160
+
1971
 //Options parameter is to pass config options. Currently uses only "useIPv6".
2161
 //Options parameter is to pass config options. Currently uses only "useIPv6".
1972
 var RTCUtils = {
2162
 var RTCUtils = {
1973
     init: function (options) {
2163
     init: function (options) {
1976
             var FFversion = RTCBrowserType.getFirefoxVersion();
2166
             var FFversion = RTCBrowserType.getFirefoxVersion();
1977
             if (FFversion >= 40) {
2167
             if (FFversion >= 40) {
1978
                 this.peerconnection = mozRTCPeerConnection;
2168
                 this.peerconnection = mozRTCPeerConnection;
1979
-                this.getUserMedia = navigator.mozGetUserMedia.bind(navigator);
2169
+                this.getUserMedia = wrapGetUserMedia(navigator.mozGetUserMedia.bind(navigator));
2170
+                this.enumerateDevices = wrapEnumerateDevices(
2171
+                    navigator.mediaDevices.enumerateDevices.bind(navigator.mediaDevices)
2172
+                );
1980
                 this.pc_constraints = {};
2173
                 this.pc_constraints = {};
1981
                 this.attachMediaStream = function (element, stream) {
2174
                 this.attachMediaStream = function (element, stream) {
1982
                     //  srcObject is being standardized and FF will eventually
2175
                     //  srcObject is being standardized and FF will eventually
2022
 
2215
 
2023
         } else if (RTCBrowserType.isChrome() || RTCBrowserType.isOpera()) {
2216
         } else if (RTCBrowserType.isChrome() || RTCBrowserType.isOpera()) {
2024
             this.peerconnection = webkitRTCPeerConnection;
2217
             this.peerconnection = webkitRTCPeerConnection;
2025
-            this.getUserMedia = navigator.webkitGetUserMedia.bind(navigator);
2218
+            var getUserMedia = navigator.webkitGetUserMedia.bind(navigator);
2219
+            if (navigator.mediaDevices) {
2220
+                this.getUserMedia = wrapGetUserMedia(getUserMedia);
2221
+                this.enumerateDevices = wrapEnumerateDevices(
2222
+                    navigator.mediaDevices.enumerateDevices.bind(navigator.mediaDevices)
2223
+                );
2224
+            } else {
2225
+                this.getUserMedia = getUserMedia;
2226
+                this.enumerateDevices = enumerateDevicesThroughMediaStreamTrack;
2227
+            }
2026
             this.attachMediaStream = function (element, stream) {
2228
             this.attachMediaStream = function (element, stream) {
2027
                 element.attr('src', webkitURL.createObjectURL(stream));
2229
                 element.attr('src', webkitURL.createObjectURL(stream));
2028
             };
2230
             };
2072
 
2274
 
2073
                 self.peerconnection = RTCPeerConnection;
2275
                 self.peerconnection = RTCPeerConnection;
2074
                 self.getUserMedia = getUserMedia;
2276
                 self.getUserMedia = getUserMedia;
2277
+                self.enumerateDevices = enumerateDevicesThroughMediaStreamTrack;
2075
                 self.attachMediaStream = function (elSel, stream) {
2278
                 self.attachMediaStream = function (elSel, stream) {
2076
 
2279
 
2077
                     if (stream.id === "dummyAudio" || stream.id === "dummyVideo") {
2280
                     if (stream.id === "dummyAudio" || stream.id === "dummyVideo") {
2126
         }
2329
         }
2127
 
2330
 
2128
     },
2331
     },
2129
-    getUserMediaWithConstraints: function ( um, success_callback, failure_callback, resolution, bandwidth, fps, desktopStream) {
2332
+    /**
2333
+    * @param {string[]} um required user media types
2334
+    * @param {function} success_callback
2335
+    * @param {Function} failure_callback
2336
+    * @param {Object} [options] optional parameters
2337
+    * @param {string} options.resolution
2338
+    * @param {number} options.bandwidth
2339
+    * @param {number} options.fps
2340
+    * @param {string} options.desktopStream
2341
+    * @param {string} options.cameraDeviceId
2342
+    * @param {string} options.micDeviceId
2343
+    **/
2344
+    getUserMediaWithConstraints: function ( um, success_callback, failure_callback, options) {
2345
+        options = options || {};
2346
+        resolution = options.resolution;
2130
         var constraints = getConstraints(
2347
         var constraints = getConstraints(
2131
-            um, resolution, bandwidth, fps, desktopStream);
2348
+            um, options);
2132
 
2349
 
2133
         logger.info("Get media constraints", constraints);
2350
         logger.info("Get media constraints", constraints);
2134
 
2351
 
2157
 
2374
 
2158
     /**
2375
     /**
2159
      * Creates the local MediaStreams.
2376
      * Creates the local MediaStreams.
2160
-     * @param devices the devices that will be requested
2161
-     * @param resolution resolution constraints
2162
-     * @param dontCreateJitsiTrack if <tt>true</tt> objects with the following structure {stream: the Media Stream,
2377
+     * @param {Object} [options] optional parameters
2378
+     * @param {Array} options.devices the devices that will be requested
2379
+     * @param {string} options.resolution resolution constraints
2380
+     * @param {bool} options.dontCreateJitsiTrack if <tt>true</tt> objects with the following structure {stream: the Media Stream,
2163
      * type: "audio" or "video", videoType: "camera" or "desktop"}
2381
      * type: "audio" or "video", videoType: "camera" or "desktop"}
2164
      * will be returned trough the Promise, otherwise JitsiTrack objects will be returned.
2382
      * will be returned trough the Promise, otherwise JitsiTrack objects will be returned.
2383
+     * @param {string} options.cameraDeviceId
2384
+     * @param {string} options.micDeviceId
2165
      * @returns {*} Promise object that will receive the new JitsiTracks
2385
      * @returns {*} Promise object that will receive the new JitsiTracks
2166
      */
2386
      */
2167
-    obtainAudioAndVideoPermissions: function (devices, resolution, dontCreateJitsiTracks) {
2387
+    obtainAudioAndVideoPermissions: function (options) {
2168
         var self = this;
2388
         var self = this;
2169
-        // Get AV
2170
 
2389
 
2390
+        options = options || {};
2171
         return new Promise(function (resolve, reject) {
2391
         return new Promise(function (resolve, reject) {
2172
             var successCallback = function (stream) {
2392
             var successCallback = function (stream) {
2173
-                var streams = self.successCallback(stream, resolution);
2174
-                resolve(dontCreateJitsiTracks? streams: self.createLocalTracks(streams));
2393
+                var streams = self.successCallback(stream, options.resolution);
2394
+                resolve(options.dontCreateJitsiTracks?
2395
+                    streams: self.createLocalTracks(streams));
2175
             };
2396
             };
2176
 
2397
 
2177
-            if (!devices)
2178
-                devices = ['audio', 'video'];
2179
-
2180
-            if (devices.length === 0) {
2181
-                successCallback();
2182
-                return;
2183
-            }
2398
+            options.devices = options.devices || ['audio', 'video'];
2184
 
2399
 
2185
             if (RTCBrowserType.isFirefox() || RTCBrowserType.isTemasysPluginUsed()) {
2400
             if (RTCBrowserType.isFirefox() || RTCBrowserType.isTemasysPluginUsed()) {
2186
 
2401
 
2204
                         function (error, resolution) {
2419
                         function (error, resolution) {
2205
                             logger.error(
2420
                             logger.error(
2206
                                 'failed to obtain video stream - stop', error);
2421
                                 'failed to obtain video stream - stop', error);
2207
-                            self.errorCallback(error, resolve, resolution, dontCreateJitsiTracks);
2422
+                            self.errorCallback(error, resolve, options);
2208
                         },
2423
                         },
2209
-                            resolution || '360');
2424
+                        {resolution: options.resolution || '360',
2425
+                        cameraDeviceId: options.cameraDeviceId});
2210
                 };
2426
                 };
2211
                 var obtainAudio = function () {
2427
                 var obtainAudio = function () {
2212
                     self.getUserMediaWithConstraints(
2428
                     self.getUserMediaWithConstraints(
2213
                         ['audio'],
2429
                         ['audio'],
2214
                         function (audioStream) {
2430
                         function (audioStream) {
2215
-                            if (devices.indexOf('video') !== -1)
2431
+                            (options.devices.indexOf('video') === -1) ||
2216
                                 obtainVideo(audioStream);
2432
                                 obtainVideo(audioStream);
2217
                         },
2433
                         },
2218
                         function (error) {
2434
                         function (error) {
2219
                             logger.error(
2435
                             logger.error(
2220
                                 'failed to obtain audio stream - stop', error);
2436
                                 'failed to obtain audio stream - stop', error);
2221
-                            self.errorCallback(error, resolve, null, dontCreateJitsiTracks);
2222
-                        }
2223
-                    );
2437
+                            self.errorCallback(error, resolve, options);
2438
+                        },{micDeviceId: options.micDeviceId});
2224
                 };
2439
                 };
2225
-                if (devices.indexOf('audio') !== -1) {
2440
+                if((devices.indexOf('audio') === -1))
2441
+                    obtainVideo(null)
2442
+                else
2226
                     obtainAudio();
2443
                     obtainAudio();
2227
-                } else {
2228
-                    obtainVideo(null);
2229
-                }
2230
             } else {
2444
             } else {
2231
                 this.getUserMediaWithConstraints(
2445
                 this.getUserMediaWithConstraints(
2232
-                    devices,
2446
+                    options.devices,
2233
                     function (stream) {
2447
                     function (stream) {
2234
                         successCallback(stream);
2448
                         successCallback(stream);
2235
                     },
2449
                     },
2236
                     function (error, resolution) {
2450
                     function (error, resolution) {
2237
-                        self.errorCallback(error, resolve, resolution, dontCreateJitsiTracks);
2451
+                        self.errorCallback(error, resolve, options);
2238
                     },
2452
                     },
2239
-                    resolution || '360');
2453
+                    {resolution: options.resolution || '360',
2454
+                    cameraDeviceId: options.cameraDeviceId,
2455
+                    micDeviceId: options.micDeviceId});
2240
             }
2456
             }
2241
         }.bind(this));
2457
         }.bind(this));
2242
     },
2458
     },
2260
      * Error callback called from GUM. Retries the GUM call with different resolutions.
2476
      * Error callback called from GUM. Retries the GUM call with different resolutions.
2261
      * @param error the error
2477
      * @param error the error
2262
      * @param resolve the resolve funtion that will be called on success.
2478
      * @param resolve the resolve funtion that will be called on success.
2263
-     * @param currentResolution the last resolution used for GUM.
2479
+     * @param {Object} options with the following properties:
2480
+     * @param resolution the last resolution used for GUM.
2264
      * @param dontCreateJitsiTracks if <tt>true</tt> objects with the following structure {stream: the Media Stream,
2481
      * @param dontCreateJitsiTracks if <tt>true</tt> objects with the following structure {stream: the Media Stream,
2265
      * type: "audio" or "video", videoType: "camera" or "desktop"}
2482
      * type: "audio" or "video", videoType: "camera" or "desktop"}
2266
      * will be returned trough the Promise, otherwise JitsiTrack objects will be returned.
2483
      * will be returned trough the Promise, otherwise JitsiTrack objects will be returned.
2267
      */
2484
      */
2268
-    errorCallback: function (error, resolve, currentResolution, dontCreateJitsiTracks) {
2485
+    errorCallback: function (error, resolve, options) {
2269
         var self = this;
2486
         var self = this;
2487
+        options = options || {};
2270
         logger.error('failed to obtain audio/video stream - trying audio only', error);
2488
         logger.error('failed to obtain audio/video stream - trying audio only', error);
2271
-        var resolution = getPreviousResolution(currentResolution);
2489
+        var resolution = getPreviousResolution(options.resolution);
2272
         if (typeof error == "object" && error.constraintName && error.name
2490
         if (typeof error == "object" && error.constraintName && error.name
2273
             && (error.name == "ConstraintNotSatisfiedError" ||
2491
             && (error.name == "ConstraintNotSatisfiedError" ||
2274
                 error.name == "OverconstrainedError") &&
2492
                 error.name == "OverconstrainedError") &&
2278
             self.getUserMediaWithConstraints(['audio', 'video'],
2496
             self.getUserMediaWithConstraints(['audio', 'video'],
2279
                 function (stream) {
2497
                 function (stream) {
2280
                     var streams = self.successCallback(stream, resolution);
2498
                     var streams = self.successCallback(stream, resolution);
2281
-                    resolve(dontCreateJitsiTracks? streams: self.createLocalTracks(streams));
2499
+                    resolve(options.dontCreateJitsiTracks? streams: self.createLocalTracks(streams));
2282
                 }, function (error, resolution) {
2500
                 }, function (error, resolution) {
2283
-                    return self.errorCallback(error, resolve, resolution, dontCreateJitsiTracks);
2284
-                }, resolution);
2501
+                    return self.errorCallback(error, resolve,
2502
+                        {resolution: resolution,
2503
+                        dontCreateJitsiTracks: options.dontCreateJitsiTracks});
2504
+                },
2505
+                {resolution: options.resolution});
2285
         }
2506
         }
2286
         else {
2507
         else {
2287
             self.getUserMediaWithConstraints(
2508
             self.getUserMediaWithConstraints(
2288
                 ['audio'],
2509
                 ['audio'],
2289
                 function (stream) {
2510
                 function (stream) {
2290
                     var streams = self.successCallback(stream, resolution);
2511
                     var streams = self.successCallback(stream, resolution);
2291
-                    resolve(dontCreateJitsiTracks? streams: self.createLocalTracks(streams));
2512
+                    resolve(options.dontCreateJitsiTracks? streams: self.createLocalTracks(streams));
2292
                 },
2513
                 },
2293
                 function (error) {
2514
                 function (error) {
2294
                     logger.error('failed to obtain audio/video stream - stop',
2515
                     logger.error('failed to obtain audio/video stream - stop',
2295
                         error);
2516
                         error);
2296
                     var streams = self.successCallback(null);
2517
                     var streams = self.successCallback(null);
2297
-                    resolve(dontCreateJitsiTracks? streams: self.createLocalTracks(streams));
2518
+                    resolve(options.dontCreateJitsiTracks? streams: self.createLocalTracks(streams));
2298
                 }
2519
                 }
2299
             );
2520
             );
2300
         }
2521
         }
2397
             eventEmitter.emit(eventType, localStream);
2618
             eventEmitter.emit(eventType, localStream);
2398
         }
2619
         }
2399
         return newStreams;
2620
         return newStreams;
2621
+    },
2622
+
2623
+    /**
2624
+     * Checks if its possible to enumerate available cameras/micropones.
2625
+     * @returns {boolean} true if available, false otherwise.
2626
+     */
2627
+    isDeviceListAvailable: function () {
2628
+        var isEnumerateDevicesAvailable = navigator.mediaDevices && navigator.mediaDevices.enumerateDevices;
2629
+        if (isEnumerateDevicesAvailable) {
2630
+            return true;
2631
+        }
2632
+        return (MediaStreamTrack && MediaStreamTrack.getSources)? true : false;
2400
     }
2633
     }
2401
-}
2634
+};
2402
 
2635
 
2403
 module.exports = RTCUtils;
2636
 module.exports = RTCUtils;
2404
 
2637
 

+ 4
- 3
modules/RTC/JitsiLocalTrack.js Wyświetl plik

66
         } else {
66
         } else {
67
             var self = this;
67
             var self = this;
68
             var RTC = require("./RTCUtils");
68
             var RTC = require("./RTCUtils");
69
-            RTC.obtainAudioAndVideoPermissions(
70
-                (isAudio ? ["audio"] : ["video"]),
71
-                  self.resolution, true)
69
+            RTC.obtainAudioAndVideoPermissions({
70
+                devices: (isAudio ? ["audio"] : ["video"]),
71
+                resolution: self.resolution,
72
+                dontCreateJitsiTrack: true})
72
                 .then(function (streams) {
73
                 .then(function (streams) {
73
                     var stream = null;
74
                     var stream = null;
74
                     for(var i = 0; i < streams.length; i++) {
75
                     for(var i = 0; i < streams.length; i++) {

+ 11
- 9
modules/RTC/RTC.js Wyświetl plik

38
 
38
 
39
 /**
39
 /**
40
  * Creates the local MediaStreams.
40
  * Creates the local MediaStreams.
41
- * @param options object for options (NOTE: currently only list of devices and resolution are supported)
42
- * @param dontCreateJitsiTrack if <tt>true</tt> objects with the following structure {stream: the Media Stream,
43
-  * type: "audio" or "video", videoType: "camera" or "desktop"}
41
+ * @param {Object} [options] optional parameters
42
+ * @param {Array} options.devices the devices that will be requested
43
+ * @param {string} options.resolution resolution constraints
44
+ * @param {bool} options.dontCreateJitsiTrack if <tt>true</tt> objects with the following structure {stream: the Media Stream,
45
+ * type: "audio" or "video", videoType: "camera" or "desktop"}
44
  * will be returned trough the Promise, otherwise JitsiTrack objects will be returned.
46
  * will be returned trough the Promise, otherwise JitsiTrack objects will be returned.
47
+ * @param {string} options.cameraDeviceId
48
+ * @param {string} options.micDeviceId
45
  * @returns {*} Promise object that will receive the new JitsiTracks
49
  * @returns {*} Promise object that will receive the new JitsiTracks
46
  */
50
  */
47
-RTC.obtainAudioAndVideoPermissions = function (options, dontCreateJitsiTrack) {
48
-    return RTCUtils.obtainAudioAndVideoPermissions(
49
-        options.devices, options.resolution, dontCreateJitsiTrack);
51
+RTC.obtainAudioAndVideoPermissions = function (options) {
52
+    return RTCUtils.obtainAudioAndVideoPermissions(options);
50
 }
53
 }
51
 
54
 
52
 RTC.prototype.onIncommingCall = function(event) {
55
 RTC.prototype.onIncommingCall = function(event) {
137
 };
140
 };
138
 
141
 
139
 RTC.getUserMediaWithConstraints = function(um, success_callback,
142
 RTC.getUserMediaWithConstraints = function(um, success_callback,
140
-                                     failure_callback, resolution,
141
-                                     bandwidth, fps, desktopStream)
143
+                                     failure_callback, options)
142
 {
144
 {
143
     return RTCUtils.getUserMediaWithConstraints(this, um, success_callback,
145
     return RTCUtils.getUserMediaWithConstraints(this, um, success_callback,
144
-        failure_callback, resolution, bandwidth, fps, desktopStream);
146
+        failure_callback, options);
145
 };
147
 };
146
 
148
 
147
 RTC.attachMediaStream =  function (elSelector, stream) {
149
 RTC.attachMediaStream =  function (elSelector, stream) {

+ 104
- 48
modules/RTC/RTCUtils.js Wyświetl plik

15
 var devices = {
15
 var devices = {
16
     audio: true,
16
     audio: true,
17
     video: true
17
     video: true
18
-};
18
+}
19
 
19
 
20
 var rtcReady = false;
20
 var rtcReady = false;
21
 
21
 
65
         constraints.video.mandatory.maxHeight =
65
         constraints.video.mandatory.maxHeight =
66
             constraints.video.mandatory.minHeight;
66
             constraints.video.mandatory.minHeight;
67
 }
67
 }
68
-
69
-function getConstraints(um, resolution, bandwidth, fps, desktopStream) {
68
+/**
69
+ * @param {string[]} um required user media types
70
+ *
71
+ * @param {Object} [options={}] optional parameters
72
+ * @param {string} options.resolution
73
+ * @param {number} options.bandwidth
74
+ * @param {number} options.fps
75
+ * @param {string} options.desktopStream
76
+ * @param {string} options.cameraDeviceId
77
+ * @param {string} options.micDeviceId
78
+ * @param {bool} firefox_fake_device
79
+ */
80
+function getConstraints(um, options) {
70
     var constraints = {audio: false, video: false};
81
     var constraints = {audio: false, video: false};
71
 
82
 
72
     if (um.indexOf('video') >= 0) {
83
     if (um.indexOf('video') >= 0) {
73
         // same behaviour as true
84
         // same behaviour as true
74
         constraints.video = { mandatory: {}, optional: [] };
85
         constraints.video = { mandatory: {}, optional: [] };
75
 
86
 
87
+        if (options.cameraDeviceId) {
88
+            constraints.video.optional.push({
89
+                sourceId: options.cameraDeviceId
90
+            });
91
+        }
92
+
76
         constraints.video.optional.push({ googLeakyBucket: true });
93
         constraints.video.optional.push({ googLeakyBucket: true });
77
 
94
 
78
-        setResolutionConstraints(constraints, resolution);
95
+        setResolutionConstraints(constraints, options.resolution);
79
     }
96
     }
80
     if (um.indexOf('audio') >= 0) {
97
     if (um.indexOf('audio') >= 0) {
81
         if (!RTCBrowserType.isFirefox()) {
98
         if (!RTCBrowserType.isFirefox()) {
82
             // same behaviour as true
99
             // same behaviour as true
83
             constraints.audio = { mandatory: {}, optional: []};
100
             constraints.audio = { mandatory: {}, optional: []};
101
+            if (options.micDeviceId) {
102
+                constraints.audio.optional.push({
103
+                    sourceId: options.micDeviceId
104
+                });
105
+            }
84
             // if it is good enough for hangouts...
106
             // if it is good enough for hangouts...
85
             constraints.audio.optional.push(
107
             constraints.audio.optional.push(
86
                 {googEchoCancellation: true},
108
                 {googEchoCancellation: true},
92
                 {googAutoGainControl2: true}
114
                 {googAutoGainControl2: true}
93
             );
115
             );
94
         } else {
116
         } else {
95
-            constraints.audio = true;
117
+            if (options.micDeviceId) {
118
+                constraints.audio = {
119
+                    mandatory: {},
120
+                    optional: [{
121
+                        sourceId: options.micDeviceId
122
+                    }]};
123
+            } else {
124
+                constraints.audio = true;
125
+            }
96
         }
126
         }
97
     }
127
     }
98
     if (um.indexOf('screen') >= 0) {
128
     if (um.indexOf('screen') >= 0) {
125
         constraints.video = {
155
         constraints.video = {
126
             mandatory: {
156
             mandatory: {
127
                 chromeMediaSource: "desktop",
157
                 chromeMediaSource: "desktop",
128
-                chromeMediaSourceId: desktopStream,
158
+                chromeMediaSourceId: options.desktopStream,
129
                 googLeakyBucket: true,
159
                 googLeakyBucket: true,
130
                 maxWidth: window.screen.width,
160
                 maxWidth: window.screen.width,
131
                 maxHeight: window.screen.height,
161
                 maxHeight: window.screen.height,
135
         };
165
         };
136
     }
166
     }
137
 
167
 
138
-    if (bandwidth) {
168
+    if (options.bandwidth) {
139
         if (!constraints.video) {
169
         if (!constraints.video) {
140
             //same behaviour as true
170
             //same behaviour as true
141
             constraints.video = {mandatory: {}, optional: []};
171
             constraints.video = {mandatory: {}, optional: []};
142
         }
172
         }
143
-        constraints.video.optional.push({bandwidth: bandwidth});
173
+        constraints.video.optional.push({bandwidth: options.bandwidth});
144
     }
174
     }
145
-    if (fps) {
175
+    if (options.fps) {
146
         // for some cameras it might be necessary to request 30fps
176
         // for some cameras it might be necessary to request 30fps
147
         // so they choose 30fps mjpg over 10fps yuy2
177
         // so they choose 30fps mjpg over 10fps yuy2
148
         if (!constraints.video) {
178
         if (!constraints.video) {
149
             // same behaviour as true;
179
             // same behaviour as true;
150
             constraints.video = {mandatory: {}, optional: []};
180
             constraints.video = {mandatory: {}, optional: []};
151
         }
181
         }
152
-        constraints.video.mandatory.minFrameRate = fps;
182
+        constraints.video.mandatory.minFrameRate = options.fps;
183
+    }
184
+
185
+    // we turn audio for both audio and video tracks, the fake audio & video seems to work
186
+    // only when enabled in one getUserMedia call, we cannot get fake audio separate by fake video
187
+    // this later can be a problem with some of the tests
188
+    if(RTCBrowserType.isFirefox() && options.firefox_fake_device)
189
+    {
190
+        constraints.audio = true;
191
+        constraints.fake = true;
153
     }
192
     }
154
 
193
 
155
     return constraints;
194
     return constraints;
156
 }
195
 }
157
 
196
 
158
 function setAvailableDevices(um, available) {
197
 function setAvailableDevices(um, available) {
159
-    var devices = {};
160
     if (um.indexOf("video") != -1) {
198
     if (um.indexOf("video") != -1) {
161
         devices.video = available;
199
         devices.video = available;
162
     }
200
     }
471
         }
509
         }
472
 
510
 
473
     },
511
     },
474
-    getUserMediaWithConstraints: function ( um, success_callback, failure_callback, resolution, bandwidth, fps, desktopStream) {
512
+    /**
513
+    * @param {string[]} um required user media types
514
+    * @param {function} success_callback
515
+    * @param {Function} failure_callback
516
+    * @param {Object} [options] optional parameters
517
+    * @param {string} options.resolution
518
+    * @param {number} options.bandwidth
519
+    * @param {number} options.fps
520
+    * @param {string} options.desktopStream
521
+    * @param {string} options.cameraDeviceId
522
+    * @param {string} options.micDeviceId
523
+    **/
524
+    getUserMediaWithConstraints: function ( um, success_callback, failure_callback, options) {
525
+        options = options || {};
526
+        resolution = options.resolution;
475
         var constraints = getConstraints(
527
         var constraints = getConstraints(
476
-            um, resolution, bandwidth, fps, desktopStream);
528
+            um, options);
477
 
529
 
478
         logger.info("Get media constraints", constraints);
530
         logger.info("Get media constraints", constraints);
479
 
531
 
502
 
554
 
503
     /**
555
     /**
504
      * Creates the local MediaStreams.
556
      * Creates the local MediaStreams.
505
-     * @param devices the devices that will be requested
506
-     * @param resolution resolution constraints
507
-     * @param dontCreateJitsiTrack if <tt>true</tt> objects with the following structure {stream: the Media Stream,
557
+     * @param {Object} [options] optional parameters
558
+     * @param {Array} options.devices the devices that will be requested
559
+     * @param {string} options.resolution resolution constraints
560
+     * @param {bool} options.dontCreateJitsiTrack if <tt>true</tt> objects with the following structure {stream: the Media Stream,
508
      * type: "audio" or "video", videoType: "camera" or "desktop"}
561
      * type: "audio" or "video", videoType: "camera" or "desktop"}
509
      * will be returned trough the Promise, otherwise JitsiTrack objects will be returned.
562
      * will be returned trough the Promise, otherwise JitsiTrack objects will be returned.
563
+     * @param {string} options.cameraDeviceId
564
+     * @param {string} options.micDeviceId
510
      * @returns {*} Promise object that will receive the new JitsiTracks
565
      * @returns {*} Promise object that will receive the new JitsiTracks
511
      */
566
      */
512
-    obtainAudioAndVideoPermissions: function (devices, resolution, dontCreateJitsiTracks) {
567
+    obtainAudioAndVideoPermissions: function (options) {
513
         var self = this;
568
         var self = this;
514
-        // Get AV
515
 
569
 
570
+        options = options || {};
516
         return new Promise(function (resolve, reject) {
571
         return new Promise(function (resolve, reject) {
517
             var successCallback = function (stream) {
572
             var successCallback = function (stream) {
518
-                var streams = self.successCallback(stream, resolution);
519
-                resolve(dontCreateJitsiTracks? streams: self.createLocalTracks(streams));
573
+                var streams = self.successCallback(stream, options.resolution);
574
+                resolve(options.dontCreateJitsiTracks?
575
+                    streams: self.createLocalTracks(streams));
520
             };
576
             };
521
 
577
 
522
-            if (!devices)
523
-                devices = ['audio', 'video'];
524
-
525
-            if (devices.length === 0) {
526
-                successCallback();
527
-                return;
528
-            }
578
+            options.devices = options.devices || ['audio', 'video'];
529
 
579
 
530
             if (RTCBrowserType.isFirefox() || RTCBrowserType.isTemasysPluginUsed()) {
580
             if (RTCBrowserType.isFirefox() || RTCBrowserType.isTemasysPluginUsed()) {
531
 
581
 
549
                         function (error, resolution) {
599
                         function (error, resolution) {
550
                             logger.error(
600
                             logger.error(
551
                                 'failed to obtain video stream - stop', error);
601
                                 'failed to obtain video stream - stop', error);
552
-                            self.errorCallback(error, resolve, resolution, dontCreateJitsiTracks);
602
+                            self.errorCallback(error, resolve, options);
553
                         },
603
                         },
554
-                            resolution || '360');
604
+                        {resolution: options.resolution || '360',
605
+                        cameraDeviceId: options.cameraDeviceId});
555
                 };
606
                 };
556
                 var obtainAudio = function () {
607
                 var obtainAudio = function () {
557
                     self.getUserMediaWithConstraints(
608
                     self.getUserMediaWithConstraints(
558
                         ['audio'],
609
                         ['audio'],
559
                         function (audioStream) {
610
                         function (audioStream) {
560
-                            if (devices.indexOf('video') !== -1)
611
+                            (options.devices.indexOf('video') === -1) ||
561
                                 obtainVideo(audioStream);
612
                                 obtainVideo(audioStream);
562
                         },
613
                         },
563
                         function (error) {
614
                         function (error) {
564
                             logger.error(
615
                             logger.error(
565
                                 'failed to obtain audio stream - stop', error);
616
                                 'failed to obtain audio stream - stop', error);
566
-                            self.errorCallback(error, resolve, null, dontCreateJitsiTracks);
567
-                        }
568
-                    );
617
+                            self.errorCallback(error, resolve, options);
618
+                        },{micDeviceId: options.micDeviceId});
569
                 };
619
                 };
570
-                if (devices.indexOf('audio') !== -1) {
620
+                if((devices.indexOf('audio') === -1))
621
+                    obtainVideo(null)
622
+                else
571
                     obtainAudio();
623
                     obtainAudio();
572
-                } else {
573
-                    obtainVideo(null);
574
-                }
575
             } else {
624
             } else {
576
                 this.getUserMediaWithConstraints(
625
                 this.getUserMediaWithConstraints(
577
-                    devices,
626
+                    options.devices,
578
                     function (stream) {
627
                     function (stream) {
579
                         successCallback(stream);
628
                         successCallback(stream);
580
                     },
629
                     },
581
                     function (error, resolution) {
630
                     function (error, resolution) {
582
-                        self.errorCallback(error, resolve, resolution, dontCreateJitsiTracks);
631
+                        self.errorCallback(error, resolve, options);
583
                     },
632
                     },
584
-                    resolution || '360');
633
+                    {resolution: options.resolution || '360',
634
+                    cameraDeviceId: options.cameraDeviceId,
635
+                    micDeviceId: options.micDeviceId});
585
             }
636
             }
586
         }.bind(this));
637
         }.bind(this));
587
     },
638
     },
605
      * Error callback called from GUM. Retries the GUM call with different resolutions.
656
      * Error callback called from GUM. Retries the GUM call with different resolutions.
606
      * @param error the error
657
      * @param error the error
607
      * @param resolve the resolve funtion that will be called on success.
658
      * @param resolve the resolve funtion that will be called on success.
608
-     * @param currentResolution the last resolution used for GUM.
659
+     * @param {Object} options with the following properties:
660
+     * @param resolution the last resolution used for GUM.
609
      * @param dontCreateJitsiTracks if <tt>true</tt> objects with the following structure {stream: the Media Stream,
661
      * @param dontCreateJitsiTracks if <tt>true</tt> objects with the following structure {stream: the Media Stream,
610
      * type: "audio" or "video", videoType: "camera" or "desktop"}
662
      * type: "audio" or "video", videoType: "camera" or "desktop"}
611
      * will be returned trough the Promise, otherwise JitsiTrack objects will be returned.
663
      * will be returned trough the Promise, otherwise JitsiTrack objects will be returned.
612
      */
664
      */
613
-    errorCallback: function (error, resolve, currentResolution, dontCreateJitsiTracks) {
665
+    errorCallback: function (error, resolve, options) {
614
         var self = this;
666
         var self = this;
667
+        options = options || {};
615
         logger.error('failed to obtain audio/video stream - trying audio only', error);
668
         logger.error('failed to obtain audio/video stream - trying audio only', error);
616
-        var resolution = getPreviousResolution(currentResolution);
669
+        var resolution = getPreviousResolution(options.resolution);
617
         if (typeof error == "object" && error.constraintName && error.name
670
         if (typeof error == "object" && error.constraintName && error.name
618
             && (error.name == "ConstraintNotSatisfiedError" ||
671
             && (error.name == "ConstraintNotSatisfiedError" ||
619
                 error.name == "OverconstrainedError") &&
672
                 error.name == "OverconstrainedError") &&
623
             self.getUserMediaWithConstraints(['audio', 'video'],
676
             self.getUserMediaWithConstraints(['audio', 'video'],
624
                 function (stream) {
677
                 function (stream) {
625
                     var streams = self.successCallback(stream, resolution);
678
                     var streams = self.successCallback(stream, resolution);
626
-                    resolve(dontCreateJitsiTracks? streams: self.createLocalTracks(streams));
679
+                    resolve(options.dontCreateJitsiTracks? streams: self.createLocalTracks(streams));
627
                 }, function (error, resolution) {
680
                 }, function (error, resolution) {
628
-                    return self.errorCallback(error, resolve, resolution, dontCreateJitsiTracks);
629
-                }, resolution);
681
+                    return self.errorCallback(error, resolve,
682
+                        {resolution: resolution,
683
+                        dontCreateJitsiTracks: options.dontCreateJitsiTracks});
684
+                },
685
+                {resolution: options.resolution});
630
         }
686
         }
631
         else {
687
         else {
632
             self.getUserMediaWithConstraints(
688
             self.getUserMediaWithConstraints(
633
                 ['audio'],
689
                 ['audio'],
634
                 function (stream) {
690
                 function (stream) {
635
                     var streams = self.successCallback(stream, resolution);
691
                     var streams = self.successCallback(stream, resolution);
636
-                    resolve(dontCreateJitsiTracks? streams: self.createLocalTracks(streams));
692
+                    resolve(options.dontCreateJitsiTracks? streams: self.createLocalTracks(streams));
637
                 },
693
                 },
638
                 function (error) {
694
                 function (error) {
639
                     logger.error('failed to obtain audio/video stream - stop',
695
                     logger.error('failed to obtain audio/video stream - stop',
640
                         error);
696
                         error);
641
                     var streams = self.successCallback(null);
697
                     var streams = self.successCallback(null);
642
-                    resolve(dontCreateJitsiTracks? streams: self.createLocalTracks(streams));
698
+                    resolve(options.dontCreateJitsiTracks? streams: self.createLocalTracks(streams));
643
                 }
699
                 }
644
             );
700
             );
645
         }
701
         }

Ładowanie…
Anuluj
Zapisz