Explorar el Código

Added ability to notify about available media device list changes

dev1
Kostiantyn Tsaregradskyi hace 9 años
padre
commit
0220f82e75
Se han modificado 7 ficheros con 216 adiciones y 33 borrados
  1. 74
    0
      JitsiMediaDevices.js
  2. 17
    0
      JitsiMediaDevicesEvents.js
  3. 19
    20
      JitsiMeetJS.js
  4. 18
    9
      doc/API.md
  5. 12
    3
      doc/example/example.js
  6. 74
    0
      modules/RTC/RTCUtils.js
  7. 2
    1
      service/RTC/RTCEvents.js

+ 74
- 0
JitsiMediaDevices.js Ver fichero

@@ -0,0 +1,74 @@
1
+var EventEmitter = require("events");
2
+var RTCEvents = require('./service/RTC/RTCEvents');
3
+var RTC = require("./modules/RTC/RTC");
4
+var JitsiMediaDevicesEvents = require('./JitsiMediaDevicesEvents');
5
+
6
+var eventEmitter = new EventEmitter();
7
+
8
+RTC.addListener(RTCEvents.DEVICE_LIST_CHANGED,
9
+    function (devices) {
10
+        eventEmitter.emit(JitsiMediaDevicesEvents.DEVICE_LIST_CHANGED, devices);
11
+    });
12
+
13
+var JitsiMediaDevices = {
14
+    /**
15
+     * Executes callback with list of media devices connected.
16
+     * @param {function} callback
17
+     */
18
+    enumerateDevices: function (callback) {
19
+        RTC.enumerateDevices(callback);
20
+    },
21
+    /**
22
+     * Checks if its possible to enumerate available cameras/micropones.
23
+     * @returns {boolean} true if available, false otherwise.
24
+     */
25
+    isDeviceListAvailable: function () {
26
+        return RTC.isDeviceListAvailable();
27
+    },
28
+    /**
29
+     * Returns true if changing the input (camera / microphone) or output
30
+     * (audio) device is supported and false if not.
31
+     * @params {string} [deviceType] - type of device to change. Default is
32
+     *      undefined or 'input', 'output' - for audio output device change.
33
+     * @returns {boolean} true if available, false otherwise.
34
+     */
35
+    isDeviceChangeAvailable: function (deviceType) {
36
+        return RTC.isDeviceChangeAvailable(deviceType);
37
+    },
38
+    /**
39
+     * Returns currently used audio output device id, '' stands for default
40
+     * device
41
+     * @returns {string}
42
+     */
43
+    getAudioOutputDevice: function () {
44
+        return RTC.getAudioOutputDevice();
45
+    },
46
+    /**
47
+     * Sets current audio output device.
48
+     * @param {string} deviceId - id of 'audiooutput' device from
49
+     *      navigator.mediaDevices.enumerateDevices(), '' is for default device
50
+     * @returns {Promise} - resolves when audio output is changed, is rejected
51
+     *      otherwise
52
+     */
53
+    setAudioOutputDevice: function (deviceId) {
54
+        return RTC.setAudioOutputDevice(deviceId);
55
+    },
56
+    /**
57
+     * Adds an event handler.
58
+     * @param {string} event - event name
59
+     * @param {function} handler - event handler
60
+     */
61
+    addEventListener: function (event, handler) {
62
+        eventEmitter.addListener(event, handler);
63
+    },
64
+    /**
65
+     * Removes event handler.
66
+     * @param {string} event - event name
67
+     * @param {function} handler - event handler
68
+     */
69
+    removeEventListener: function (event, handler) {
70
+        eventEmitter.removeListener(event, handler);
71
+    }
72
+};
73
+
74
+module.exports = JitsiMediaDevices;

+ 17
- 0
JitsiMediaDevicesEvents.js Ver fichero

@@ -0,0 +1,17 @@
1
+/**
2
+ * Enumeration with the events for the media devices.
3
+ * @type {{string: string}}
4
+ */
5
+var JitsiMediaDevicesEvents = {
6
+    /**
7
+     * Indicates that the list of available media devices has been changed. The
8
+     * event provides the following parameters to its listeners:
9
+     *
10
+     * @param {MediaDeviceInfo[]} devices - array of MediaDeviceInfo or
11
+     *  MediaDeviceInfo-like objects that are currently connected.
12
+     *  @see https://developer.mozilla.org/en-US/docs/Web/API/MediaDeviceInfo
13
+     */
14
+    DEVICE_LIST_CHANGED: "mediaDevices.devicechange"
15
+};
16
+
17
+module.exports = JitsiMediaDevicesEvents;

+ 19
- 20
JitsiMeetJS.js Ver fichero

@@ -1,7 +1,9 @@
1 1
 var logger = require("jitsi-meet-logger").getLogger(__filename);
2 2
 var JitsiConnection = require("./JitsiConnection");
3
+var JitsiMediaDevices = require("./JitsiMediaDevices");
3 4
 var JitsiConferenceEvents = require("./JitsiConferenceEvents");
4 5
 var JitsiConnectionEvents = require("./JitsiConnectionEvents");
6
+var JitsiMediaDevicesEvents = require('./JitsiMediaDevicesEvents');
5 7
 var JitsiConnectionErrors = require("./JitsiConnectionErrors");
6 8
 var JitsiConferenceErrors = require("./JitsiConferenceErrors");
7 9
 var JitsiTrackEvents = require("./JitsiTrackEvents");
@@ -40,7 +42,8 @@ var LibJitsiMeet = {
40 42
     events: {
41 43
         conference: JitsiConferenceEvents,
42 44
         connection: JitsiConnectionEvents,
43
-        track: JitsiTrackEvents
45
+        track: JitsiTrackEvents,
46
+        mediaDevices: JitsiMediaDevicesEvents
44 47
     },
45 48
     errors: {
46 49
         conference: JitsiConferenceErrors,
@@ -48,6 +51,7 @@ var LibJitsiMeet = {
48 51
         track: JitsiTrackErrors
49 52
     },
50 53
     logLevels: Logger.levels,
54
+    mediaDevices: JitsiMediaDevices,
51 55
     /**
52 56
      * Array of functions that will receive the GUM error.
53 57
      */
@@ -146,9 +150,12 @@ var LibJitsiMeet = {
146 150
     /**
147 151
      * Checks if its possible to enumerate available cameras/micropones.
148 152
      * @returns {boolean} true if available, false otherwise.
153
+     * @deprecated use JitsiMeetJS.mediaDevices.isDeviceListAvailable instead
149 154
      */
150 155
     isDeviceListAvailable: function () {
151
-        return RTC.isDeviceListAvailable();
156
+        logger.warn('This method is deprecated, use ' +
157
+            'JitsiMeetJS.mediaDevices.isDeviceListAvailable instead');
158
+        return this.mediaDevices.isDeviceListAvailable();
152 159
     },
153 160
     /**
154 161
      * Returns true if changing the input (camera / microphone) or output
@@ -156,30 +163,22 @@ var LibJitsiMeet = {
156 163
      * @params {string} [deviceType] - type of device to change. Default is
157 164
      *      undefined or 'input', 'output' - for audio output device change.
158 165
      * @returns {boolean} true if available, false otherwise.
166
+     * @deprecated use JitsiMeetJS.mediaDevices.isDeviceChangeAvailable instead
159 167
      */
160 168
     isDeviceChangeAvailable: function (deviceType) {
161
-        return RTC.isDeviceChangeAvailable(deviceType);
169
+        logger.warn('This method is deprecated, use ' +
170
+            'JitsiMeetJS.mediaDevices.isDeviceChangeAvailable instead');
171
+        return this.mediaDevices.isDeviceChangeAvailable(deviceType);
162 172
     },
163 173
     /**
164
-     * Returns currently used audio output device id, '' stands for default
165
-     * device
166
-     * @returns {string}
174
+     * Executes callback with list of media devices connected.
175
+     * @param {function} callback
176
+     * @deprecated use JitsiMeetJS.mediaDevices.enumerateDevices instead
167 177
      */
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);
180
-    },
181 178
     enumerateDevices: function (callback) {
182
-        RTC.enumerateDevices(callback);
179
+        logger.warn('This method is deprecated, use ' +
180
+            'JitsiMeetJS.mediaDevices.enumerateDevices instead');
181
+        this.mediaDevices.enumerateDevices(callback);
183 182
     },
184 183
     /**
185 184
      * Array of functions that will receive the unhandled errors.

+ 18
- 9
doc/API.md Ver fichero

@@ -65,18 +65,24 @@ JitsiMeetJS.setLogLevel(JitsiMeetJS.logLevels.ERROR);
65 65
         5. minFps - the minimum frame rate for the video stream (passed to GUM)
66 66
         6. maxFps - the maximum frame rate for the video stream (passed to GUM)
67 67
 
68
-* ```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:
69
-    - label - the name of the device
70
-    - kind - "audioinput" or "videoinput"
71
-    - deviceId - the id of the device.
72
-
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.
68
+* ```JitsiMeetJS.enumerateDevices(callback)``` - __DEPRECATED__. Use ```JitsiMeetJS.mediaDevices.enumerateDevices(callback)``` instead.
69
+* ```JitsiMeetJS.isDeviceListAvailable()``` - __DEPRECATED__. Use ```JitsiMeetJS.mediaDevices.isDeviceListAvailable()``` instead.
70
+* ```JitsiMeetJS.isDeviceChangeAvailable(deviceType)``` - __DEPRECATED__. Use ```JitsiMeetJS.mediaDevices.isDeviceChangeAvailable(deviceType)``` instead.
77 71
 * ```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.
78 72
 * ```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)).
79 73
 
74
+* ```JitsiMeetJS.mediaDevices``` - JS object that contains methods for interaction with media devices. Following methods are available:
75
+    - ```isDeviceListAvailable()``` - returns true if retrieving the device list is supported and false - otherwise
76
+    - ```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.
77
+    - ```enumerateDevices(callback)``` - returns list of the available devices as a parameter to the callback function. Every device is a MediaDeviceInfo object with the following properties:
78
+        - label - the name of the device
79
+        - kind - "audioinput", "videoinput" or "audiooutput"
80
+        - deviceId - the id of the device
81
+        - groupId - group identifier, two devices have the same group identifier if they belong to the same physical device; for example a monitor with both a built-in camera and microphone
82
+    - ```setAudioOutputDevice(deviceId)``` - sets current audio output device. ```deviceId``` - id of 'audiooutput' device from ```JitsiMeetJS.enumerateDevices()```, '' is for default device.
83
+    - ```getAudioOutputDevice()``` - returns currently used audio output device id, '' stands for default device.
84
+    - ```addEventListener(event, handler)``` - attaches an event handler.
85
+    - ```removeEventListener(event, handler)``` - removes an event handler.
80 86
 
81 87
 
82 88
 * ```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.
@@ -120,6 +126,9 @@ JitsiMeetJS.setLogLevel(JitsiMeetJS.logLevels.ERROR);
120 126
         event can be fired when ```dispose()``` method is called or for other reasons.
121 127
         - TRACK_AUDIO_OUTPUT_CHANGED - indicates that audio output device for track was changed (parameters - deviceId (string) - new audio output device ID).
122 128
         
129
+    4. mediaDevices
130
+        - DEVICE_LIST_CHANGED - indicates that list of currently connected devices has changed (parameters - devices(MediaDeviceInfo[])).
131
+        
123 132
 
124 133
 * ```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
125 134
     We have two error types - connection and conference. You can access the events with the following code ```JitsiMeetJS.errors.<error_type>.<error_name>```.

+ 12
- 3
doc/example/example.js Ver fichero

@@ -147,6 +147,13 @@ function onConnectionSuccess(){
147 147
  */
148 148
 function onConnectionFailed(){console.error("Connection Failed!")};
149 149
 
150
+/**
151
+ * This function is called when the connection fail.
152
+ */
153
+function onDeviceListChanged(devices) {
154
+    console.info('current devices', devices);
155
+}
156
+
150 157
 /**
151 158
  * This function is called when we disconnect.
152 159
  */
@@ -189,7 +196,7 @@ function switchVideo() {
189 196
 }
190 197
 
191 198
 function changeAudioOutput(selected) {
192
-    JitsiMeetJS.setAudioOutputDevice(selected.value);
199
+    JitsiMeetJS.mediaDevices.setAudioOutputDevice(selected.value);
193 200
 }
194 201
 
195 202
 $(window).bind('beforeunload', unload);
@@ -231,6 +238,8 @@ JitsiMeetJS.init(initOptions).then(function(){
231 238
     connection.addEventListener(JitsiMeetJS.events.connection.CONNECTION_FAILED, onConnectionFailed);
232 239
     connection.addEventListener(JitsiMeetJS.events.connection.CONNECTION_DISCONNECTED, disconnect);
233 240
 
241
+    JitsiMeetJS.mediaDevices.addEventListener(JitsiMeetJS.events.mediaDevices.DEVICE_LIST_CHANGED, onDeviceListChanged);
242
+
234 243
     connection.connect();
235 244
     JitsiMeetJS.createLocalTracks({devices: ["audio", "video"]}).
236 245
         then(onLocalTracks).catch(function (error) {
@@ -241,8 +250,8 @@ JitsiMeetJS.init(initOptions).then(function(){
241 250
 });
242 251
 
243 252
 
244
-if (JitsiMeetJS.isDeviceChangeAvailable('output')) {
245
-    JitsiMeetJS.enumerateDevices(function(devices) {
253
+if (JitsiMeetJS.mediaDevices.isDeviceChangeAvailable('output')) {
254
+    JitsiMeetJS.mediaDevices.enumerateDevices(function(devices) {
246 255
         var audioOutputDevices = devices.filter(function(d) { return d.kind === 'audiooutput'; });
247 256
 
248 257
         if (audioOutputDevices.length > 1) {

+ 74
- 0
modules/RTC/RTCUtils.js Ver fichero

@@ -19,6 +19,8 @@ var VideoType = require("../../service/RTC/VideoType");
19 19
 
20 20
 var eventEmitter = new EventEmitter();
21 21
 
22
+var AVAILABLE_DEVICES_POLL_INTERVAL_TIME = 3000; // ms
23
+
22 24
 var devices = {
23 25
     audio: true,
24 26
     video: true
@@ -30,6 +32,19 @@ var featureDetectionAudioEl = document.createElement('audio');
30 32
 var isAudioOutputDeviceChangeAvailable =
31 33
     typeof featureDetectionAudioEl.setSinkId !== 'undefined';
32 34
 
35
+var currentlyAvailableMediaDevices = [];
36
+
37
+// TODO: currently no browser supports 'devicechange' event even in nightly
38
+// builds so no feature/browser detection is used at all. However in future this
39
+// should be changed to some expression. Progress on 'devicechange' event
40
+// implementation for Chrome/Opera/NWJS can be tracked at
41
+// https://bugs.chromium.org/p/chromium/issues/detail?id=388648, for Firefox -
42
+// at https://bugzilla.mozilla.org/show_bug.cgi?id=1152383. More information on
43
+// 'devicechange' event can be found in spec -
44
+// http://w3c.github.io/mediacapture-main/#event-mediadevices-devicechange
45
+// TODO: check MS Edge
46
+var isDeviceChangeEventSupported = false;
47
+
33 48
 var rtcReady = false;
34 49
 
35 50
 function setResolutionConstraints(constraints, resolution) {
@@ -217,6 +232,57 @@ function setAvailableDevices(um, available) {
217 232
     eventEmitter.emit(RTCEvents.AVAILABLE_DEVICES_CHANGED, devices);
218 233
 }
219 234
 
235
+/**
236
+ * Checks if new list of available media devices differs from previous one.
237
+ * @param {MediaDeviceInfo[]} newDevices - list of new devices.
238
+ * @returns {boolean} - true if list is different, false otherwise.
239
+ */
240
+function compareAvailableMediaDevices(newDevices) {
241
+    if (newDevices.length !== currentlyAvailableMediaDevices.length) {
242
+        return true;
243
+    }
244
+
245
+    return newDevices.map(mediaDeviceInfoToJSON).sort().join('') !==
246
+        currentlyAvailableMediaDevices.map(mediaDeviceInfoToJSON).sort().join('');
247
+
248
+    function mediaDeviceInfoToJSON(info) {
249
+        return JSON.stringify({
250
+            kind: info.kind,
251
+            deviceId: info.deviceId,
252
+            groupId: info.groupId,
253
+            label: info.label,
254
+            facing: info.facing
255
+        });
256
+    }
257
+}
258
+
259
+/**
260
+ * Periodically polls enumerateDevices() method to check if list of media
261
+ * devices has changed. This is temporary workaround until 'devicechange' event
262
+ * will be supported by browsers.
263
+ */
264
+function pollForAvailableMediaDevices() {
265
+    RTCUtils.enumerateDevices(function (devices) {
266
+        if (compareAvailableMediaDevices(devices)) {
267
+            onMediaDevicesListChanged(devices);
268
+        }
269
+
270
+        window.setTimeout(pollForAvailableMediaDevices,
271
+            AVAILABLE_DEVICES_POLL_INTERVAL_TIME);
272
+    });
273
+}
274
+
275
+/**
276
+ * Event handler for the 'devicechange' event.
277
+ * @param {MediaDeviceInfo[]} devices - list of media devices.
278
+ * @emits RTCEvents.DEVICE_LIST_CHANGED
279
+ */
280
+function onMediaDevicesListChanged(devices) {
281
+    currentlyAvailableMediaDevices = devices;
282
+    eventEmitter.emit(RTCEvents.DEVICE_LIST_CHANGED, devices);
283
+    logger.info('list of media devices has changed:', devices);
284
+}
285
+
220 286
 // In case of IE we continue from 'onReady' callback
221 287
 // passed to RTCUtils constructor. It will be invoked by Temasys plugin
222 288
 // once it is initialized.
@@ -224,6 +290,14 @@ function onReady (options, GUM) {
224 290
     rtcReady = true;
225 291
     eventEmitter.emit(RTCEvents.RTC_READY, true);
226 292
     screenObtainer.init(options, GUM);
293
+
294
+    if (isDeviceChangeEventSupported && RTCUtils.isDeviceListAvailable()) {
295
+        navigator.mediaDevices.addEventListener('devicechange', function () {
296
+            RTCUtils.enumerateDevices(onMediaDevicesListChanged);
297
+        });
298
+    } else if (RTCUtils.isDeviceListAvailable()) {
299
+        pollForAvailableMediaDevices();
300
+    }
227 301
 }
228 302
 
229 303
 /**

+ 2
- 1
service/RTC/RTCEvents.js Ver fichero

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

Loading…
Cancelar
Guardar