[RN] Handle denied getUserMedia permissions
This commit is contained in:
parent
c97daff506
commit
a690b9d5e1
|
@ -43,6 +43,7 @@ import {
|
|||
participantUpdated
|
||||
} from './react/features/base/participants';
|
||||
import {
|
||||
createLocalTracks,
|
||||
replaceLocalTrack,
|
||||
trackAdded,
|
||||
trackRemoved
|
||||
|
@ -259,53 +260,6 @@ function assignWindowLocationPathname(pathname) {
|
|||
windowLocation.pathname = pathname;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create local tracks of specified types.
|
||||
* @param {Object} options
|
||||
* @param {string[]} options.devices - required track types
|
||||
* ('audio', 'video' etc.)
|
||||
* @param {string|null} (options.cameraDeviceId) - camera device id, if
|
||||
* undefined - one from settings will be used
|
||||
* @param {string|null} (options.micDeviceId) - microphone device id, if
|
||||
* undefined - one from settings will be used
|
||||
* @param {boolean} (checkForPermissionPrompt) - if lib-jitsi-meet should check
|
||||
* for gUM permission prompt
|
||||
* @returns {Promise<JitsiLocalTrack[]>}
|
||||
*/
|
||||
function createLocalTracks(options, checkForPermissionPrompt) {
|
||||
options || (options = {});
|
||||
|
||||
return JitsiMeetJS
|
||||
.createLocalTracks({
|
||||
// copy array to avoid mutations inside library
|
||||
devices: options.devices.slice(0),
|
||||
desktopSharingSources: options.desktopSharingSources,
|
||||
resolution: config.resolution,
|
||||
cameraDeviceId: typeof options.cameraDeviceId === 'undefined' ||
|
||||
options.cameraDeviceId === null
|
||||
? APP.settings.getCameraDeviceId()
|
||||
: options.cameraDeviceId,
|
||||
micDeviceId: typeof options.micDeviceId === 'undefined' ||
|
||||
options.micDeviceId === null
|
||||
? APP.settings.getMicDeviceId()
|
||||
: options.micDeviceId,
|
||||
// adds any ff fake device settings if any
|
||||
firefox_fake_device: config.firefox_fake_device,
|
||||
desktopSharingExtensionExternalInstallation:
|
||||
options.desktopSharingExtensionExternalInstallation
|
||||
}, checkForPermissionPrompt).then( (tracks) => {
|
||||
tracks.forEach((track) => {
|
||||
track.on(TrackEvents.NO_DATA_FROM_SOURCE,
|
||||
APP.UI.showTrackNotWorkingDialog.bind(null, track));
|
||||
});
|
||||
return tracks;
|
||||
}).catch(function (err) {
|
||||
logger.error(
|
||||
'failed to create local tracks', options.devices, err);
|
||||
return Promise.reject(err);
|
||||
});
|
||||
}
|
||||
|
||||
class ConferenceConnector {
|
||||
constructor(resolve, reject) {
|
||||
this._resolve = resolve;
|
||||
|
|
|
@ -10,11 +10,8 @@ import {
|
|||
} from '../media';
|
||||
import { getLocalParticipant } from '../participants';
|
||||
|
||||
import {
|
||||
TRACK_ADDED,
|
||||
TRACK_REMOVED,
|
||||
TRACK_UPDATED
|
||||
} from './actionTypes';
|
||||
import { TRACK_ADDED, TRACK_REMOVED, TRACK_UPDATED } from './actionTypes';
|
||||
import { createLocalTracks } from './functions';
|
||||
|
||||
/**
|
||||
* Request to start capturing local audio and/or video. By default, the user
|
||||
|
@ -23,19 +20,45 @@ import {
|
|||
* @param {Object} [options] - For info @see JitsiMeetJS.createLocalTracks.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function createLocalTracks(options = {}) {
|
||||
return dispatch =>
|
||||
JitsiMeetJS.createLocalTracks({
|
||||
export function createInitialLocalTracks(options = {}) {
|
||||
return (dispatch, getState) => {
|
||||
const devices
|
||||
= options.devices || [ MEDIA_TYPE.AUDIO, MEDIA_TYPE.VIDEO ];
|
||||
const store = {
|
||||
dispatch,
|
||||
getState
|
||||
};
|
||||
|
||||
// The following executes on React Native only at the time of this
|
||||
// writing. The effort to port Web's createInitialLocalTracksAndConnect
|
||||
// is significant and that's where the function createLocalTracks got
|
||||
// born. I started with the idea a porting so that we could inherit the
|
||||
// ability to getUserMedia for audio only or video only if getUserMedia
|
||||
// for audio and video fails. Eventually though, I realized that on
|
||||
// mobile we do not have combined permission prompts implemented anyway
|
||||
// (either because there are no such prompts or it does not make sense
|
||||
// to implement them) and the right thing to do is to ask for each
|
||||
// device separately.
|
||||
for (const device of devices) {
|
||||
createLocalTracks(
|
||||
{
|
||||
cameraDeviceId: options.cameraDeviceId,
|
||||
devices: options.devices || [ MEDIA_TYPE.AUDIO, MEDIA_TYPE.VIDEO ],
|
||||
devices: [ device ],
|
||||
facingMode: options.facingMode || CAMERA_FACING_MODE.USER,
|
||||
micDeviceId: options.micDeviceId
|
||||
})
|
||||
.then(localTracks => dispatch(_updateLocalTracks(localTracks)))
|
||||
.catch(err => {
|
||||
console.error(
|
||||
`JitsiMeetJS.createLocalTracks.catch rejection reason: ${err}`);
|
||||
});
|
||||
},
|
||||
/* firePermissionPromptIsShownEvent */ false,
|
||||
store)
|
||||
.then(localTracks => dispatch(_updateLocalTracks(localTracks)));
|
||||
|
||||
// TODO The function createLocalTracks logs the rejection reason of
|
||||
// JitsiMeetJS.createLocalTracks so there is no real benefit to
|
||||
// logging it here as well. Technically though,
|
||||
// _updateLocalTracks may cause a rejection so it may be nice to log
|
||||
// it. It's not too big of a concern at the time of this writing
|
||||
// because React Native warns on unhandled Promise rejections.
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,5 +1,90 @@
|
|||
/* global APP */
|
||||
|
||||
import JitsiMeetJS, { JitsiTrackEvents } from '../lib-jitsi-meet';
|
||||
import { MEDIA_TYPE } from '../media';
|
||||
|
||||
const logger = require('jitsi-meet-logger').getLogger(__filename);
|
||||
|
||||
/**
|
||||
* Create local tracks of specific types.
|
||||
*
|
||||
* @param {Object} options - The options with which the local tracks are to be
|
||||
* created.
|
||||
* @param {string|null} [options.cameraDeviceId] - Camera device id or
|
||||
* {@code undefined} to use app's settings.
|
||||
* @param {string[]} options.devices - Required track types such as 'audio'
|
||||
* and/or 'video'.
|
||||
* @param {string|null} [options.micDeviceId] - Microphone device id or
|
||||
* {@code undefined} to use app's settings.
|
||||
* @param {boolean} [firePermissionPromptIsShownEvent] - Whether lib-jitsi-meet
|
||||
* should check for a {@code getUserMedia} permission prompt and fire a
|
||||
* corresponding event.
|
||||
* @param {Object} store - The redux store in the context of which the function
|
||||
* is to execute and from which state such as {@code config} is to be retrieved.
|
||||
* @returns {Promise<JitsiLocalTrack[]>}
|
||||
*/
|
||||
export function createLocalTracks(
|
||||
options,
|
||||
firePermissionPromptIsShownEvent,
|
||||
store) {
|
||||
options || (options = {}); // eslint-disable-line no-param-reassign
|
||||
|
||||
let { cameraDeviceId, micDeviceId } = options;
|
||||
|
||||
if (typeof APP !== 'undefined') {
|
||||
// TODO The app's settings should go in the redux store and then the
|
||||
// reliance on the global variable APP will go away.
|
||||
if (typeof cameraDeviceId === 'undefined' || cameraDeviceId === null) {
|
||||
cameraDeviceId = APP.settings.getCameraDeviceId();
|
||||
}
|
||||
if (typeof micDeviceId === 'undefined' || micDeviceId === null) {
|
||||
micDeviceId = APP.settings.getMicDeviceId();
|
||||
}
|
||||
|
||||
store || (store = APP.store); // eslint-disable-line no-param-reassign
|
||||
}
|
||||
|
||||
const {
|
||||
firefox_fake_device, // eslint-disable-line camelcase
|
||||
resolution
|
||||
} = store.getState()['features/base/config'];
|
||||
|
||||
return (
|
||||
JitsiMeetJS.createLocalTracks(
|
||||
{
|
||||
cameraDeviceId,
|
||||
desktopSharingExtensionExternalInstallation:
|
||||
options.desktopSharingExtensionExternalInstallation,
|
||||
desktopSharingSources: options.desktopSharingSources,
|
||||
|
||||
// Copy array to avoid mutations inside library.
|
||||
devices: options.devices.slice(0),
|
||||
firefox_fake_device, // eslint-disable-line camelcase
|
||||
micDeviceId,
|
||||
resolution
|
||||
},
|
||||
firePermissionPromptIsShownEvent)
|
||||
.then(tracks => {
|
||||
// TODO JitsiTrackEvents.NO_DATA_FROM_SOURCE should probably be
|
||||
// dispatched in the redux store here and then
|
||||
// APP.UI.showTrackNotWorkingDialog should be in a middleware
|
||||
// somewhere else.
|
||||
if (typeof APP !== 'undefined') {
|
||||
tracks.forEach(track =>
|
||||
track.on(
|
||||
JitsiTrackEvents.NO_DATA_FROM_SOURCE,
|
||||
APP.UI.showTrackNotWorkingDialog.bind(null, track)));
|
||||
}
|
||||
|
||||
return tracks;
|
||||
})
|
||||
.catch(err => {
|
||||
logger.error('Failed to create local tracks', options.devices, err);
|
||||
|
||||
return Promise.reject(err);
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns local audio track.
|
||||
*
|
||||
|
|
|
@ -7,15 +7,15 @@ import {
|
|||
SET_AUDIO_MUTED,
|
||||
SET_CAMERA_FACING_MODE,
|
||||
SET_VIDEO_MUTED,
|
||||
TOGGLE_CAMERA_FACING_MODE,
|
||||
setAudioMuted,
|
||||
setVideoMuted
|
||||
setVideoMuted,
|
||||
TOGGLE_CAMERA_FACING_MODE,
|
||||
toggleCameraFacingMode
|
||||
} from '../media';
|
||||
import { MiddlewareRegistry } from '../redux';
|
||||
|
||||
import {
|
||||
_disposeAndRemoveTracks,
|
||||
createLocalTracks,
|
||||
createInitialLocalTracks,
|
||||
destroyLocalTracks
|
||||
} from './actions';
|
||||
import { TRACK_ADDED, TRACK_REMOVED, TRACK_UPDATED } from './actionTypes';
|
||||
|
@ -38,7 +38,7 @@ MiddlewareRegistry.register(store => next => action => {
|
|||
break;
|
||||
|
||||
case LIB_DID_INIT:
|
||||
store.dispatch(createLocalTracks());
|
||||
store.dispatch(createInitialLocalTracks());
|
||||
break;
|
||||
|
||||
case SET_AUDIO_MUTED:
|
||||
|
@ -46,21 +46,22 @@ MiddlewareRegistry.register(store => next => action => {
|
|||
break;
|
||||
|
||||
case SET_CAMERA_FACING_MODE: {
|
||||
// XXX Destroy the local video track before creating a new one or
|
||||
// react-native-webrtc may be slow or get stuck when opening a (video)
|
||||
// capturer twice.
|
||||
// XXX The camera facing mode of a MediaStreamTrack can be specified
|
||||
// only at initialization time and then it can only be toggled. So in
|
||||
// order to set the camera facing mode, one may destroy the track and
|
||||
// then initialize a new instance with the new camera facing mode. But
|
||||
// that is inefficient on mobile at least so the following relies on the
|
||||
// fact that there are 2 camera facing modes and merely toggles between
|
||||
// them to (hopefully) get the camera in the specified state.
|
||||
const localTrack = _getLocalTrack(store, MEDIA_TYPE.VIDEO);
|
||||
let jitsiTrack;
|
||||
|
||||
if (localTrack) {
|
||||
store.dispatch(_disposeAndRemoveTracks([ localTrack.jitsiTrack ]));
|
||||
if (localTrack
|
||||
&& (jitsiTrack = localTrack.jitsiTrack)
|
||||
&& jitsiTrack.getCameraFacingMode()
|
||||
!== action.cameraFacingMode) {
|
||||
store.dispatch(toggleCameraFacingMode());
|
||||
}
|
||||
|
||||
store.dispatch(
|
||||
createLocalTracks({
|
||||
devices: [ MEDIA_TYPE.VIDEO ],
|
||||
facingMode: action.cameraFacingMode
|
||||
})
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue