瀏覽代碼

[RN] Handle denied getUserMedia permissions

j8
Lyubo Marinov 8 年之前
父節點
當前提交
a690b9d5e1
共有 4 個文件被更改,包括 145 次插入82 次删除
  1. 1
    47
      conference.js
  2. 41
    18
      react/features/base/tracks/actions.js
  3. 85
    0
      react/features/base/tracks/functions.js
  4. 18
    17
      react/features/base/tracks/middleware.js

+ 1
- 47
conference.js 查看文件

43
     participantUpdated
43
     participantUpdated
44
 } from './react/features/base/participants';
44
 } from './react/features/base/participants';
45
 import {
45
 import {
46
+    createLocalTracks,
46
     replaceLocalTrack,
47
     replaceLocalTrack,
47
     trackAdded,
48
     trackAdded,
48
     trackRemoved
49
     trackRemoved
259
     windowLocation.pathname = pathname;
260
     windowLocation.pathname = pathname;
260
 }
261
 }
261
 
262
 
262
-/**
263
- * Create local tracks of specified types.
264
- * @param {Object} options
265
- * @param {string[]} options.devices - required track types
266
- *      ('audio', 'video' etc.)
267
- * @param {string|null} (options.cameraDeviceId) - camera device id, if
268
- *      undefined - one from settings will be used
269
- * @param {string|null} (options.micDeviceId) - microphone device id, if
270
- *      undefined - one from settings will be used
271
- * @param {boolean} (checkForPermissionPrompt) - if lib-jitsi-meet should check
272
- *      for gUM permission prompt
273
- * @returns {Promise<JitsiLocalTrack[]>}
274
- */
275
-function createLocalTracks(options, checkForPermissionPrompt) {
276
-    options || (options = {});
277
-
278
-    return JitsiMeetJS
279
-        .createLocalTracks({
280
-            // copy array to avoid mutations inside library
281
-            devices: options.devices.slice(0),
282
-            desktopSharingSources: options.desktopSharingSources,
283
-            resolution: config.resolution,
284
-            cameraDeviceId: typeof options.cameraDeviceId === 'undefined' ||
285
-                    options.cameraDeviceId === null
286
-                ? APP.settings.getCameraDeviceId()
287
-                : options.cameraDeviceId,
288
-            micDeviceId: typeof options.micDeviceId === 'undefined' ||
289
-                    options.micDeviceId === null
290
-                ? APP.settings.getMicDeviceId()
291
-                : options.micDeviceId,
292
-            // adds any ff fake device settings if any
293
-            firefox_fake_device: config.firefox_fake_device,
294
-            desktopSharingExtensionExternalInstallation:
295
-                options.desktopSharingExtensionExternalInstallation
296
-        }, checkForPermissionPrompt).then( (tracks) => {
297
-            tracks.forEach((track) => {
298
-                track.on(TrackEvents.NO_DATA_FROM_SOURCE,
299
-                    APP.UI.showTrackNotWorkingDialog.bind(null, track));
300
-            });
301
-            return tracks;
302
-        }).catch(function (err) {
303
-            logger.error(
304
-                'failed to create local tracks', options.devices, err);
305
-            return Promise.reject(err);
306
-        });
307
-}
308
-
309
 class ConferenceConnector {
263
 class ConferenceConnector {
310
     constructor(resolve, reject) {
264
     constructor(resolve, reject) {
311
         this._resolve = resolve;
265
         this._resolve = resolve;

+ 41
- 18
react/features/base/tracks/actions.js 查看文件

10
 } from '../media';
10
 } from '../media';
11
 import { getLocalParticipant } from '../participants';
11
 import { getLocalParticipant } from '../participants';
12
 
12
 
13
-import {
14
-    TRACK_ADDED,
15
-    TRACK_REMOVED,
16
-    TRACK_UPDATED
17
-} from './actionTypes';
13
+import { TRACK_ADDED, TRACK_REMOVED, TRACK_UPDATED } from './actionTypes';
14
+import { createLocalTracks } from './functions';
18
 
15
 
19
 /**
16
 /**
20
  * Request to start capturing local audio and/or video. By default, the user
17
  * Request to start capturing local audio and/or video. By default, the user
23
  * @param {Object} [options] - For info @see JitsiMeetJS.createLocalTracks.
20
  * @param {Object} [options] - For info @see JitsiMeetJS.createLocalTracks.
24
  * @returns {Function}
21
  * @returns {Function}
25
  */
22
  */
26
-export function createLocalTracks(options = {}) {
27
-    return dispatch =>
28
-        JitsiMeetJS.createLocalTracks({
29
-            cameraDeviceId: options.cameraDeviceId,
30
-            devices: options.devices || [ MEDIA_TYPE.AUDIO, MEDIA_TYPE.VIDEO ],
31
-            facingMode: options.facingMode || CAMERA_FACING_MODE.USER,
32
-            micDeviceId: options.micDeviceId
33
-        })
34
-        .then(localTracks => dispatch(_updateLocalTracks(localTracks)))
35
-        .catch(err => {
36
-            console.error(
37
-                `JitsiMeetJS.createLocalTracks.catch rejection reason: ${err}`);
38
-        });
23
+export function createInitialLocalTracks(options = {}) {
24
+    return (dispatch, getState) => {
25
+        const devices
26
+            = options.devices || [ MEDIA_TYPE.AUDIO, MEDIA_TYPE.VIDEO ];
27
+        const store = {
28
+            dispatch,
29
+            getState
30
+        };
31
+
32
+        // The following executes on React Native only at the time of this
33
+        // writing. The effort to port Web's createInitialLocalTracksAndConnect
34
+        // is significant and that's where the function createLocalTracks got
35
+        // born. I started with the idea a porting so that we could inherit the
36
+        // ability to getUserMedia for audio only or video only if getUserMedia
37
+        // for audio and video fails. Eventually though, I realized that on
38
+        // mobile we do not have combined permission prompts implemented anyway
39
+        // (either because there are no such prompts or it does not make sense
40
+        // to implement them) and the right thing to do is to ask for each
41
+        // device separately.
42
+        for (const device of devices) {
43
+            createLocalTracks(
44
+                {
45
+                    cameraDeviceId: options.cameraDeviceId,
46
+                    devices: [ device ],
47
+                    facingMode: options.facingMode || CAMERA_FACING_MODE.USER,
48
+                    micDeviceId: options.micDeviceId
49
+                },
50
+                /* firePermissionPromptIsShownEvent */ false,
51
+                store)
52
+            .then(localTracks => dispatch(_updateLocalTracks(localTracks)));
53
+
54
+            // TODO The function createLocalTracks logs the rejection reason of
55
+            // JitsiMeetJS.createLocalTracks so there is no real benefit to
56
+            // logging it here as well. Technically though,
57
+            // _updateLocalTracks may cause a rejection so it may be nice to log
58
+            // it. It's not too big of a concern at the time of this writing
59
+            // because React Native warns on unhandled Promise rejections.
60
+        }
61
+    };
39
 }
62
 }
40
 
63
 
41
 /**
64
 /**

+ 85
- 0
react/features/base/tracks/functions.js 查看文件

1
+/* global APP */
2
+
3
+import JitsiMeetJS, { JitsiTrackEvents } from '../lib-jitsi-meet';
1
 import { MEDIA_TYPE } from '../media';
4
 import { MEDIA_TYPE } from '../media';
2
 
5
 
6
+const logger = require('jitsi-meet-logger').getLogger(__filename);
7
+
8
+/**
9
+ * Create local tracks of specific types.
10
+ *
11
+ * @param {Object} options - The options with which the local tracks are to be
12
+ * created.
13
+ * @param {string|null} [options.cameraDeviceId] - Camera device id or
14
+ * {@code undefined} to use app's settings.
15
+ * @param {string[]} options.devices - Required track types such as 'audio'
16
+ * and/or 'video'.
17
+ * @param {string|null} [options.micDeviceId] - Microphone device id or
18
+ * {@code undefined} to use app's settings.
19
+ * @param {boolean} [firePermissionPromptIsShownEvent] - Whether lib-jitsi-meet
20
+ * should check for a {@code getUserMedia} permission prompt and fire a
21
+ * corresponding event.
22
+ * @param {Object} store - The redux store in the context of which the function
23
+ * is to execute and from which state such as {@code config} is to be retrieved.
24
+ * @returns {Promise<JitsiLocalTrack[]>}
25
+ */
26
+export function createLocalTracks(
27
+        options,
28
+        firePermissionPromptIsShownEvent,
29
+        store) {
30
+    options || (options = {}); // eslint-disable-line no-param-reassign
31
+
32
+    let { cameraDeviceId, micDeviceId } = options;
33
+
34
+    if (typeof APP !== 'undefined') {
35
+        // TODO The app's settings should go in the redux store and then the
36
+        // reliance on the global variable APP will go away.
37
+        if (typeof cameraDeviceId === 'undefined' || cameraDeviceId === null) {
38
+            cameraDeviceId = APP.settings.getCameraDeviceId();
39
+        }
40
+        if (typeof micDeviceId === 'undefined' || micDeviceId === null) {
41
+            micDeviceId = APP.settings.getMicDeviceId();
42
+        }
43
+
44
+        store || (store = APP.store); // eslint-disable-line no-param-reassign
45
+    }
46
+
47
+    const {
48
+        firefox_fake_device, // eslint-disable-line camelcase
49
+        resolution
50
+    } = store.getState()['features/base/config'];
51
+
52
+    return (
53
+        JitsiMeetJS.createLocalTracks(
54
+            {
55
+                cameraDeviceId,
56
+                desktopSharingExtensionExternalInstallation:
57
+                    options.desktopSharingExtensionExternalInstallation,
58
+                desktopSharingSources: options.desktopSharingSources,
59
+
60
+                // Copy array to avoid mutations inside library.
61
+                devices: options.devices.slice(0),
62
+                firefox_fake_device, // eslint-disable-line camelcase
63
+                micDeviceId,
64
+                resolution
65
+            },
66
+            firePermissionPromptIsShownEvent)
67
+        .then(tracks => {
68
+            // TODO JitsiTrackEvents.NO_DATA_FROM_SOURCE should probably be
69
+            // dispatched in the redux store here and then
70
+            // APP.UI.showTrackNotWorkingDialog should be in a middleware
71
+            // somewhere else.
72
+            if (typeof APP !== 'undefined') {
73
+                tracks.forEach(track =>
74
+                    track.on(
75
+                        JitsiTrackEvents.NO_DATA_FROM_SOURCE,
76
+                        APP.UI.showTrackNotWorkingDialog.bind(null, track)));
77
+            }
78
+
79
+            return tracks;
80
+        })
81
+        .catch(err => {
82
+            logger.error('Failed to create local tracks', options.devices, err);
83
+
84
+            return Promise.reject(err);
85
+        }));
86
+}
87
+
3
 /**
88
 /**
4
  * Returns local audio track.
89
  * Returns local audio track.
5
  *
90
  *

+ 18
- 17
react/features/base/tracks/middleware.js 查看文件

7
     SET_AUDIO_MUTED,
7
     SET_AUDIO_MUTED,
8
     SET_CAMERA_FACING_MODE,
8
     SET_CAMERA_FACING_MODE,
9
     SET_VIDEO_MUTED,
9
     SET_VIDEO_MUTED,
10
-    TOGGLE_CAMERA_FACING_MODE,
11
     setAudioMuted,
10
     setAudioMuted,
12
-    setVideoMuted
11
+    setVideoMuted,
12
+    TOGGLE_CAMERA_FACING_MODE,
13
+    toggleCameraFacingMode
13
 } from '../media';
14
 } from '../media';
14
 import { MiddlewareRegistry } from '../redux';
15
 import { MiddlewareRegistry } from '../redux';
15
 
16
 
16
 import {
17
 import {
17
-    _disposeAndRemoveTracks,
18
-    createLocalTracks,
18
+    createInitialLocalTracks,
19
     destroyLocalTracks
19
     destroyLocalTracks
20
 } from './actions';
20
 } from './actions';
21
 import { TRACK_ADDED, TRACK_REMOVED, TRACK_UPDATED } from './actionTypes';
21
 import { TRACK_ADDED, TRACK_REMOVED, TRACK_UPDATED } from './actionTypes';
38
         break;
38
         break;
39
 
39
 
40
     case LIB_DID_INIT:
40
     case LIB_DID_INIT:
41
-        store.dispatch(createLocalTracks());
41
+        store.dispatch(createInitialLocalTracks());
42
         break;
42
         break;
43
 
43
 
44
     case SET_AUDIO_MUTED:
44
     case SET_AUDIO_MUTED:
46
         break;
46
         break;
47
 
47
 
48
     case SET_CAMERA_FACING_MODE: {
48
     case SET_CAMERA_FACING_MODE: {
49
-        // XXX Destroy the local video track before creating a new one or
50
-        // react-native-webrtc may be slow or get stuck when opening a (video)
51
-        // capturer twice.
49
+        // XXX The camera facing mode of a MediaStreamTrack can be specified
50
+        // only at initialization time and then it can only be toggled. So in
51
+        // order to set the camera facing mode, one may destroy the track and
52
+        // then initialize a new instance with the new camera facing mode. But
53
+        // that is inefficient on mobile at least so the following relies on the
54
+        // fact that there are 2 camera facing modes and merely toggles between
55
+        // them to (hopefully) get the camera in the specified state.
52
         const localTrack = _getLocalTrack(store, MEDIA_TYPE.VIDEO);
56
         const localTrack = _getLocalTrack(store, MEDIA_TYPE.VIDEO);
57
+        let jitsiTrack;
53
 
58
 
54
-        if (localTrack) {
55
-            store.dispatch(_disposeAndRemoveTracks([ localTrack.jitsiTrack ]));
59
+        if (localTrack
60
+                && (jitsiTrack = localTrack.jitsiTrack)
61
+                && jitsiTrack.getCameraFacingMode()
62
+                    !== action.cameraFacingMode) {
63
+            store.dispatch(toggleCameraFacingMode());
56
         }
64
         }
57
-
58
-        store.dispatch(
59
-            createLocalTracks({
60
-                devices: [ MEDIA_TYPE.VIDEO ],
61
-                facingMode: action.cameraFacingMode
62
-            })
63
-        );
64
         break;
65
         break;
65
     }
66
     }
66
 
67
 

Loading…
取消
儲存