[RN] Handle denied getUserMedia permissions
This commit is contained in:
parent
c97daff506
commit
a690b9d5e1
|
@ -43,6 +43,7 @@ import {
|
||||||
participantUpdated
|
participantUpdated
|
||||||
} from './react/features/base/participants';
|
} from './react/features/base/participants';
|
||||||
import {
|
import {
|
||||||
|
createLocalTracks,
|
||||||
replaceLocalTrack,
|
replaceLocalTrack,
|
||||||
trackAdded,
|
trackAdded,
|
||||||
trackRemoved
|
trackRemoved
|
||||||
|
@ -259,53 +260,6 @@ function assignWindowLocationPathname(pathname) {
|
||||||
windowLocation.pathname = 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 {
|
class ConferenceConnector {
|
||||||
constructor(resolve, reject) {
|
constructor(resolve, reject) {
|
||||||
this._resolve = resolve;
|
this._resolve = resolve;
|
||||||
|
|
|
@ -10,11 +10,8 @@ import {
|
||||||
} from '../media';
|
} from '../media';
|
||||||
import { getLocalParticipant } from '../participants';
|
import { getLocalParticipant } from '../participants';
|
||||||
|
|
||||||
import {
|
import { TRACK_ADDED, TRACK_REMOVED, TRACK_UPDATED } from './actionTypes';
|
||||||
TRACK_ADDED,
|
import { createLocalTracks } from './functions';
|
||||||
TRACK_REMOVED,
|
|
||||||
TRACK_UPDATED
|
|
||||||
} from './actionTypes';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Request to start capturing local audio and/or video. By default, the user
|
* 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.
|
* @param {Object} [options] - For info @see JitsiMeetJS.createLocalTracks.
|
||||||
* @returns {Function}
|
* @returns {Function}
|
||||||
*/
|
*/
|
||||||
export function createLocalTracks(options = {}) {
|
export function createInitialLocalTracks(options = {}) {
|
||||||
return dispatch =>
|
return (dispatch, getState) => {
|
||||||
JitsiMeetJS.createLocalTracks({
|
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,
|
cameraDeviceId: options.cameraDeviceId,
|
||||||
devices: options.devices || [ MEDIA_TYPE.AUDIO, MEDIA_TYPE.VIDEO ],
|
devices: [ device ],
|
||||||
facingMode: options.facingMode || CAMERA_FACING_MODE.USER,
|
facingMode: options.facingMode || CAMERA_FACING_MODE.USER,
|
||||||
micDeviceId: options.micDeviceId
|
micDeviceId: options.micDeviceId
|
||||||
})
|
},
|
||||||
.then(localTracks => dispatch(_updateLocalTracks(localTracks)))
|
/* firePermissionPromptIsShownEvent */ false,
|
||||||
.catch(err => {
|
store)
|
||||||
console.error(
|
.then(localTracks => dispatch(_updateLocalTracks(localTracks)));
|
||||||
`JitsiMeetJS.createLocalTracks.catch rejection reason: ${err}`);
|
|
||||||
});
|
// 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';
|
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.
|
* Returns local audio track.
|
||||||
*
|
*
|
||||||
|
|
|
@ -7,15 +7,15 @@ import {
|
||||||
SET_AUDIO_MUTED,
|
SET_AUDIO_MUTED,
|
||||||
SET_CAMERA_FACING_MODE,
|
SET_CAMERA_FACING_MODE,
|
||||||
SET_VIDEO_MUTED,
|
SET_VIDEO_MUTED,
|
||||||
TOGGLE_CAMERA_FACING_MODE,
|
|
||||||
setAudioMuted,
|
setAudioMuted,
|
||||||
setVideoMuted
|
setVideoMuted,
|
||||||
|
TOGGLE_CAMERA_FACING_MODE,
|
||||||
|
toggleCameraFacingMode
|
||||||
} from '../media';
|
} from '../media';
|
||||||
import { MiddlewareRegistry } from '../redux';
|
import { MiddlewareRegistry } from '../redux';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
_disposeAndRemoveTracks,
|
createInitialLocalTracks,
|
||||||
createLocalTracks,
|
|
||||||
destroyLocalTracks
|
destroyLocalTracks
|
||||||
} from './actions';
|
} from './actions';
|
||||||
import { TRACK_ADDED, TRACK_REMOVED, TRACK_UPDATED } from './actionTypes';
|
import { TRACK_ADDED, TRACK_REMOVED, TRACK_UPDATED } from './actionTypes';
|
||||||
|
@ -38,7 +38,7 @@ MiddlewareRegistry.register(store => next => action => {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case LIB_DID_INIT:
|
case LIB_DID_INIT:
|
||||||
store.dispatch(createLocalTracks());
|
store.dispatch(createInitialLocalTracks());
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SET_AUDIO_MUTED:
|
case SET_AUDIO_MUTED:
|
||||||
|
@ -46,21 +46,22 @@ MiddlewareRegistry.register(store => next => action => {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SET_CAMERA_FACING_MODE: {
|
case SET_CAMERA_FACING_MODE: {
|
||||||
// XXX Destroy the local video track before creating a new one or
|
// XXX The camera facing mode of a MediaStreamTrack can be specified
|
||||||
// react-native-webrtc may be slow or get stuck when opening a (video)
|
// only at initialization time and then it can only be toggled. So in
|
||||||
// capturer twice.
|
// 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);
|
const localTrack = _getLocalTrack(store, MEDIA_TYPE.VIDEO);
|
||||||
|
let jitsiTrack;
|
||||||
|
|
||||||
if (localTrack) {
|
if (localTrack
|
||||||
store.dispatch(_disposeAndRemoveTracks([ localTrack.jitsiTrack ]));
|
&& (jitsiTrack = localTrack.jitsiTrack)
|
||||||
|
&& jitsiTrack.getCameraFacingMode()
|
||||||
|
!== action.cameraFacingMode) {
|
||||||
|
store.dispatch(toggleCameraFacingMode());
|
||||||
}
|
}
|
||||||
|
|
||||||
store.dispatch(
|
|
||||||
createLocalTracks({
|
|
||||||
devices: [ MEDIA_TYPE.VIDEO ],
|
|
||||||
facingMode: action.cameraFacingMode
|
|
||||||
})
|
|
||||||
);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue