Browse Source

Added ability to notify about available media device list changes

dev1
Kostiantyn Tsaregradskyi 9 years ago
parent
commit
0220f82e75
7 changed files with 216 additions and 33 deletions
  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 View File

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 View File

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 View File

1
 var logger = require("jitsi-meet-logger").getLogger(__filename);
1
 var logger = require("jitsi-meet-logger").getLogger(__filename);
2
 var JitsiConnection = require("./JitsiConnection");
2
 var JitsiConnection = require("./JitsiConnection");
3
+var JitsiMediaDevices = require("./JitsiMediaDevices");
3
 var JitsiConferenceEvents = require("./JitsiConferenceEvents");
4
 var JitsiConferenceEvents = require("./JitsiConferenceEvents");
4
 var JitsiConnectionEvents = require("./JitsiConnectionEvents");
5
 var JitsiConnectionEvents = require("./JitsiConnectionEvents");
6
+var JitsiMediaDevicesEvents = require('./JitsiMediaDevicesEvents');
5
 var JitsiConnectionErrors = require("./JitsiConnectionErrors");
7
 var JitsiConnectionErrors = require("./JitsiConnectionErrors");
6
 var JitsiConferenceErrors = require("./JitsiConferenceErrors");
8
 var JitsiConferenceErrors = require("./JitsiConferenceErrors");
7
 var JitsiTrackEvents = require("./JitsiTrackEvents");
9
 var JitsiTrackEvents = require("./JitsiTrackEvents");
40
     events: {
42
     events: {
41
         conference: JitsiConferenceEvents,
43
         conference: JitsiConferenceEvents,
42
         connection: JitsiConnectionEvents,
44
         connection: JitsiConnectionEvents,
43
-        track: JitsiTrackEvents
45
+        track: JitsiTrackEvents,
46
+        mediaDevices: JitsiMediaDevicesEvents
44
     },
47
     },
45
     errors: {
48
     errors: {
46
         conference: JitsiConferenceErrors,
49
         conference: JitsiConferenceErrors,
48
         track: JitsiTrackErrors
51
         track: JitsiTrackErrors
49
     },
52
     },
50
     logLevels: Logger.levels,
53
     logLevels: Logger.levels,
54
+    mediaDevices: JitsiMediaDevices,
51
     /**
55
     /**
52
      * Array of functions that will receive the GUM error.
56
      * Array of functions that will receive the GUM error.
53
      */
57
      */
146
     /**
150
     /**
147
      * Checks if its possible to enumerate available cameras/micropones.
151
      * Checks if its possible to enumerate available cameras/micropones.
148
      * @returns {boolean} true if available, false otherwise.
152
      * @returns {boolean} true if available, false otherwise.
153
+     * @deprecated use JitsiMeetJS.mediaDevices.isDeviceListAvailable instead
149
      */
154
      */
150
     isDeviceListAvailable: function () {
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
      * Returns true if changing the input (camera / microphone) or output
161
      * Returns true if changing the input (camera / microphone) or output
156
      * @params {string} [deviceType] - type of device to change. Default is
163
      * @params {string} [deviceType] - type of device to change. Default is
157
      *      undefined or 'input', 'output' - for audio output device change.
164
      *      undefined or 'input', 'output' - for audio output device change.
158
      * @returns {boolean} true if available, false otherwise.
165
      * @returns {boolean} true if available, false otherwise.
166
+     * @deprecated use JitsiMeetJS.mediaDevices.isDeviceChangeAvailable instead
159
      */
167
      */
160
     isDeviceChangeAvailable: function (deviceType) {
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
     enumerateDevices: function (callback) {
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
      * Array of functions that will receive the unhandled errors.
184
      * Array of functions that will receive the unhandled errors.

+ 18
- 9
doc/API.md View File

65
         5. minFps - the minimum frame rate for the video stream (passed to GUM)
65
         5. minFps - the minimum frame rate for the video stream (passed to GUM)
66
         6. maxFps - the maximum frame rate for the video stream (passed to GUM)
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
 * ```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.
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
 * ```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)).
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
 * ```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.
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
         event can be fired when ```dispose()``` method is called or for other reasons.
126
         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).
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
 * ```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
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
     We have two error types - connection and conference. You can access the events with the following code ```JitsiMeetJS.errors.<error_type>.<error_name>```.
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 View File

147
  */
147
  */
148
 function onConnectionFailed(){console.error("Connection Failed!")};
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
  * This function is called when we disconnect.
158
  * This function is called when we disconnect.
152
  */
159
  */
189
 }
196
 }
190
 
197
 
191
 function changeAudioOutput(selected) {
198
 function changeAudioOutput(selected) {
192
-    JitsiMeetJS.setAudioOutputDevice(selected.value);
199
+    JitsiMeetJS.mediaDevices.setAudioOutputDevice(selected.value);
193
 }
200
 }
194
 
201
 
195
 $(window).bind('beforeunload', unload);
202
 $(window).bind('beforeunload', unload);
231
     connection.addEventListener(JitsiMeetJS.events.connection.CONNECTION_FAILED, onConnectionFailed);
238
     connection.addEventListener(JitsiMeetJS.events.connection.CONNECTION_FAILED, onConnectionFailed);
232
     connection.addEventListener(JitsiMeetJS.events.connection.CONNECTION_DISCONNECTED, disconnect);
239
     connection.addEventListener(JitsiMeetJS.events.connection.CONNECTION_DISCONNECTED, disconnect);
233
 
240
 
241
+    JitsiMeetJS.mediaDevices.addEventListener(JitsiMeetJS.events.mediaDevices.DEVICE_LIST_CHANGED, onDeviceListChanged);
242
+
234
     connection.connect();
243
     connection.connect();
235
     JitsiMeetJS.createLocalTracks({devices: ["audio", "video"]}).
244
     JitsiMeetJS.createLocalTracks({devices: ["audio", "video"]}).
236
         then(onLocalTracks).catch(function (error) {
245
         then(onLocalTracks).catch(function (error) {
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
         var audioOutputDevices = devices.filter(function(d) { return d.kind === 'audiooutput'; });
255
         var audioOutputDevices = devices.filter(function(d) { return d.kind === 'audiooutput'; });
247
 
256
 
248
         if (audioOutputDevices.length > 1) {
257
         if (audioOutputDevices.length > 1) {

+ 74
- 0
modules/RTC/RTCUtils.js View File

19
 
19
 
20
 var eventEmitter = new EventEmitter();
20
 var eventEmitter = new EventEmitter();
21
 
21
 
22
+var AVAILABLE_DEVICES_POLL_INTERVAL_TIME = 3000; // ms
23
+
22
 var devices = {
24
 var devices = {
23
     audio: true,
25
     audio: true,
24
     video: true
26
     video: true
30
 var isAudioOutputDeviceChangeAvailable =
32
 var isAudioOutputDeviceChangeAvailable =
31
     typeof featureDetectionAudioEl.setSinkId !== 'undefined';
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
 var rtcReady = false;
48
 var rtcReady = false;
34
 
49
 
35
 function setResolutionConstraints(constraints, resolution) {
50
 function setResolutionConstraints(constraints, resolution) {
217
     eventEmitter.emit(RTCEvents.AVAILABLE_DEVICES_CHANGED, devices);
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
 // In case of IE we continue from 'onReady' callback
286
 // In case of IE we continue from 'onReady' callback
221
 // passed to RTCUtils constructor. It will be invoked by Temasys plugin
287
 // passed to RTCUtils constructor. It will be invoked by Temasys plugin
222
 // once it is initialized.
288
 // once it is initialized.
224
     rtcReady = true;
290
     rtcReady = true;
225
     eventEmitter.emit(RTCEvents.RTC_READY, true);
291
     eventEmitter.emit(RTCEvents.RTC_READY, true);
226
     screenObtainer.init(options, GUM);
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 View File

7
     AVAILABLE_DEVICES_CHANGED: "rtc.available_devices_changed",
7
     AVAILABLE_DEVICES_CHANGED: "rtc.available_devices_changed",
8
     FAKE_VIDEO_TRACK_CREATED: "rtc.fake_video_track_created",
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
+    AUDIO_OUTPUT_DEVICE_CHANGED: "rtc.audio_output_device_changed",
11
+    DEVICE_LIST_CHANGED: "rtc.device_list_changed"
11
 };
12
 };
12
 
13
 
13
 module.exports = RTCEvents;
14
 module.exports = RTCEvents;

Loading…
Cancel
Save