jiti-meet/react/features/mobile/callkit/middleware.js

408 lines
13 KiB
JavaScript
Raw Normal View History

2017-10-06 20:15:51 +00:00
// @flow
2017-09-28 21:25:04 +00:00
import { NativeModules } from 'react-native';
import uuid from 'uuid';
import {
Restructures the analytics events (#2333) * ref: Restructures the pinned/unpinned events. * ref: Refactors the "audio only disabled" event. * ref: Refactors the "stream switch delay" event. * ref: Refactors the "select participant failed" event. * ref: Refactors the "initially muted" events. * ref: Refactors the screen sharing started/stopped events. * ref: Restructures the "device list changed" events. * ref: Restructures the "shared video" events. * ref: Restructures the "start muted" events. * ref: Restructures the "start audio only" event. * ref: Restructures the "sync track state" event. * ref: Restructures the "callkit" events. * ref: Restructures the "replace track". * ref: Restructures keyboard shortcuts events. * ref: Restructures most of the toolbar events. * ref: Refactors the API events. * ref: Restructures the video quality, profile button and invite dialog events. * ref: Refactors the "device changed" events. * ref: Refactors the page reload event. * ref: Removes an unused function. * ref: Removes a method which is needlessly exposed under a different name. * ref: Refactors the events from the remote video menu. * ref: Refactors the events from the profile pane. * ref: Restructures the recording-related events. Removes events fired when recording with something other than jibri (which isn't currently supported anyway). * ref: Cleans up AnalyticsEvents.js. * ref: Removes an unused function and adds documentation. * feat: Adds events for all API calls. * fix: Addresses feedback. * fix: Brings back mistakenly removed code. * fix: Simplifies code and fixes a bug in toggleFilmstrip when the 'visible' parameter is defined. * feat: Removes the resolution change application log. * ref: Uses consistent naming for events' attributes. Uses "_" as a separator instead of camel case or ".". * ref: Don't add the user agent and conference name as permanent properties. The library does this on its own now. * ref: Adapts the GA handler to changes in lib-jitsi-meet. * ref: Removes unused fields from the analytics handler initializaiton. * ref: Renames the google analytics file and add docs. * fix: Fixes the push-to-talk events and logs. * npm: Updates lib-jitsi-meet to 515374c8d383cb17df8ed76427e6f0fb5ea6ff1e. * fix: Fixes a recently introduced bug in the google analytics handler. * ref: Uses "value" instead of "delay" since this is friendlier to GA.
2018-01-03 21:24:07 +00:00
createTrackMutedEvent,
sendAnalytics
} from '../../analytics';
2017-09-28 21:25:04 +00:00
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT, appNavigate } from '../../app';
import {
CONFERENCE_FAILED,
CONFERENCE_LEFT,
CONFERENCE_WILL_JOIN,
2017-09-28 21:25:04 +00:00
CONFERENCE_JOINED,
getCurrentConference
} from '../../base/conference';
import { getInviteURL } from '../../base/connection';
import {
MEDIA_TYPE,
SET_AUDIO_MUTED,
SET_VIDEO_MUTED,
VIDEO_MUTISM_AUTHORITY,
isVideoMutedByAudioOnly,
setAudioMuted
} from '../../base/media';
2017-09-28 21:25:04 +00:00
import { MiddlewareRegistry } from '../../base/redux';
import {
TRACK_CREATE_ERROR,
isLocalTrackMuted
} from '../../base/tracks';
2017-09-28 21:25:04 +00:00
import { _SET_CALLKIT_SUBSCRIPTIONS } from './actionTypes';
import CallKit from './CallKit';
/**
* Middleware that captures several system actions and hooks up CallKit.
*
* @param {Store} store - The redux store.
* @returns {Function}
*/
2017-09-28 21:25:04 +00:00
CallKit && MiddlewareRegistry.register(store => next => action => {
switch (action.type) {
2017-09-28 21:25:04 +00:00
case _SET_CALLKIT_SUBSCRIPTIONS:
return _setCallKitSubscriptions(store, next, action);
2017-09-28 21:25:04 +00:00
case APP_WILL_MOUNT:
return _appWillMount(store, next, action);
case APP_WILL_UNMOUNT:
2017-09-28 21:25:04 +00:00
store.dispatch({
type: _SET_CALLKIT_SUBSCRIPTIONS,
subscriptions: undefined
});
break;
2017-09-28 21:25:04 +00:00
case CONFERENCE_FAILED:
return _conferenceFailed(store, next, action);
2017-09-28 21:25:04 +00:00
case CONFERENCE_JOINED:
return _conferenceJoined(store, next, action);
2017-09-28 21:25:04 +00:00
case CONFERENCE_LEFT:
return _conferenceLeft(store, next, action);
2017-09-28 21:25:04 +00:00
case CONFERENCE_WILL_JOIN:
return _conferenceWillJoin(store, next, action);
2017-09-28 21:25:04 +00:00
case SET_AUDIO_MUTED:
return _setAudioMuted(store, next, action);
2017-09-28 21:25:04 +00:00
case SET_VIDEO_MUTED:
return _setVideoMuted(store, next, action);
case TRACK_CREATE_ERROR:
return _trackCreateError(store, next, action);
}
2017-09-28 21:25:04 +00:00
return next(action);
});
2017-09-28 21:25:04 +00:00
/**
* Notifies the feature jwt that the action {@link APP_WILL_MOUNT} is being
* dispatched within a specific redux {@code store}.
2017-09-28 21:25:04 +00:00
*
* @param {Store} store - The redux store in which the specified {@code action}
2017-09-28 21:25:04 +00:00
* is being dispatched.
* @param {Dispatch} next - The redux dispatch function to dispatch the
* specified {@code action} to the specified {@code store}.
* @param {Action} action - The redux action {@code APP_WILL_MOUNT} which is
* being dispatched in the specified {@code store}.
2017-09-28 21:25:04 +00:00
* @private
* @returns {*}
*/
function _appWillMount({ dispatch, getState }, next, action) {
const result = next(action);
2017-09-28 21:25:04 +00:00
CallKit.setProviderConfiguration({
2017-10-09 20:23:25 +00:00
iconTemplateImageName: 'CallKitIcon',
2017-09-28 21:25:04 +00:00
localizedName: NativeModules.AppInfo.name
});
const context = {
dispatch,
getState
};
const subscriptions = [
CallKit.addListener(
'performEndCallAction',
_onPerformEndCallAction,
context),
CallKit.addListener(
'performSetMutedCallAction',
_onPerformSetMutedCallAction,
context),
// According to CallKit's documentation, when the system resets we
// should terminate all calls. Hence, providerDidReset is the same
// to us as performEndCallAction.
CallKit.addListener(
'providerDidReset',
_onPerformEndCallAction,
context)
];
dispatch({
type: _SET_CALLKIT_SUBSCRIPTIONS,
subscriptions
});
return result;
}
/**
* Notifies the feature jwt that the action {@link CONFERENCE_FAILED} is being
* dispatched within a specific redux {@code store}.
2017-09-28 21:25:04 +00:00
*
* @param {Store} store - The redux store in which the specified {@code action}
2017-09-28 21:25:04 +00:00
* is being dispatched.
* @param {Dispatch} next - The redux dispatch function to dispatch the
* specified {@code action} to the specified {@code store}.
* @param {Action} action - The redux action {@code CONFERENCE_FAILED} which is
* being dispatched in the specified {@code store}.
2017-09-28 21:25:04 +00:00
* @private
* @returns {*}
*/
function _conferenceFailed(store, next, action) {
const result = next(action);
// XXX Certain CONFERENCE_FAILED errors are recoverable i.e. they have
// prevented the user from joining a specific conference but the app may be
// able to eventually join the conference.
if (!action.error.recoverable) {
const { callUUID } = action.conference;
if (callUUID) {
CallKit.reportCallFailed(callUUID);
}
}
2017-09-28 21:25:04 +00:00
return result;
}
/**
* Notifies the feature jwt that the action {@link CONFERENCE_JOINED} is being
* dispatched within a specific redux {@code store}.
2017-09-28 21:25:04 +00:00
*
* @param {Store} store - The redux store in which the specified {@code action}
2017-09-28 21:25:04 +00:00
* is being dispatched.
* @param {Dispatch} next - The redux dispatch function to dispatch the
* specified {@code action} to the specified {@code store}.
* @param {Action} action - The redux action {@code CONFERENCE_JOINED} which is
* being dispatched in the specified {@code store}.
2017-09-28 21:25:04 +00:00
* @private
* @returns {*}
*/
function _conferenceJoined(store, next, action) {
const result = next(action);
2017-09-28 21:25:04 +00:00
const { callUUID } = action.conference;
2017-09-28 21:25:04 +00:00
if (callUUID) {
CallKit.reportConnectedOutgoingCall(callUUID);
}
2017-09-28 21:25:04 +00:00
return result;
}
2017-09-28 21:25:04 +00:00
/**
* Notifies the feature jwt that the action {@link CONFERENCE_LEFT} is being
* dispatched within a specific redux {@code store}.
2017-09-28 21:25:04 +00:00
*
* @param {Store} store - The redux store in which the specified {@code action}
2017-09-28 21:25:04 +00:00
* is being dispatched.
* @param {Dispatch} next - The redux dispatch function to dispatch the
* specified {@code action} to the specified {@code store}.
* @param {Action} action - The redux action {@code CONFERENCE_LEFT} which is
* being dispatched in the specified {@code store}.
2017-09-28 21:25:04 +00:00
* @private
* @returns {*}
*/
function _conferenceLeft(store, next, action) {
const result = next(action);
2017-09-28 21:25:04 +00:00
const { callUUID } = action.conference;
if (callUUID) {
CallKit.endCall(callUUID);
}
2017-09-28 21:25:04 +00:00
return result;
}
2017-09-28 21:25:04 +00:00
/**
* Notifies the feature jwt that the action {@link CONFERENCE_WILL_JOIN} is
* being dispatched within a specific redux {@code store}.
2017-09-28 21:25:04 +00:00
*
* @param {Store} store - The redux store in which the specified {@code action}
2017-09-28 21:25:04 +00:00
* is being dispatched.
* @param {Dispatch} next - The redux dispatch function to dispatch the
* specified {@code action} to the specified {@code store}.
* @param {Action} action - The redux action {@code CONFERENCE_WILL_JOIN} which
* is being dispatched in the specified {@code store}.
2017-09-28 21:25:04 +00:00
* @private
* @returns {*}
*/
function _conferenceWillJoin({ getState }, next, action) {
const result = next(action);
const { conference } = action;
const state = getState();
const url = getInviteURL(state);
const hasVideo = !isVideoMutedByAudioOnly(state);
2017-09-28 21:25:04 +00:00
// When assigning the call UUID, do so in upper case, since iOS will
// return it upper cased.
conference.callUUID = uuid.v4().toUpperCase();
CallKit.startCall(conference.callUUID, url.toString(), hasVideo)
.then(() => {
const { room } = state['features/base/conference'];
2017-10-05 22:54:13 +00:00
const { callee } = state['features/base/jwt'];
const tracks = state['features/base/tracks'];
const muted = isLocalTrackMuted(tracks, MEDIA_TYPE.AUDIO);
2017-09-28 21:25:04 +00:00
CallKit.updateCall(
conference.callUUID,
{ displayName: (callee && callee.name) || room });
CallKit.setMuted(conference.callUUID, muted);
2017-09-28 21:25:04 +00:00
});
return result;
}
/**
* Handles CallKit's event {@code performEndCallAction}.
2017-09-28 21:25:04 +00:00
*
* @param {Object} event - The details of the CallKit event
* {@code performEndCallAction}.
2017-09-28 21:25:04 +00:00
* @returns {void}
*/
function _onPerformEndCallAction({ callUUID }) {
const { dispatch, getState } = this; // eslint-disable-line no-invalid-this
const conference = getCurrentConference(getState);
if (conference && conference.callUUID === callUUID) {
2017-10-06 20:15:51 +00:00
// We arrive here when a call is ended by the system, for example, when
// another incoming call is received and the user selects "End &
// Accept".
2017-09-28 21:25:04 +00:00
delete conference.callUUID;
dispatch(appNavigate(undefined));
}
2017-09-28 21:25:04 +00:00
}
/**
* Handles CallKit's event {@code performSetMutedCallAction}.
2017-09-28 21:25:04 +00:00
*
* @param {Object} event - The details of the CallKit event
* {@code performSetMutedCallAction}.
2017-09-28 21:25:04 +00:00
* @returns {void}
*/
function _onPerformSetMutedCallAction({ callUUID, muted: newValue }) {
const { dispatch, getState } = this; // eslint-disable-line no-invalid-this
const conference = getCurrentConference(getState);
if (conference && conference.callUUID === callUUID) {
// Break the loop. Audio can be muted from both CallKit and Jitsi Meet.
// We must keep them in sync, but at some point the loop needs to be
// broken. We are doing it here, on the CallKit handler.
const { muted: oldValue } = getState()['features/base/media'].audio;
if (oldValue !== newValue) {
const value = Boolean(newValue);
Restructures the analytics events (#2333) * ref: Restructures the pinned/unpinned events. * ref: Refactors the "audio only disabled" event. * ref: Refactors the "stream switch delay" event. * ref: Refactors the "select participant failed" event. * ref: Refactors the "initially muted" events. * ref: Refactors the screen sharing started/stopped events. * ref: Restructures the "device list changed" events. * ref: Restructures the "shared video" events. * ref: Restructures the "start muted" events. * ref: Restructures the "start audio only" event. * ref: Restructures the "sync track state" event. * ref: Restructures the "callkit" events. * ref: Restructures the "replace track". * ref: Restructures keyboard shortcuts events. * ref: Restructures most of the toolbar events. * ref: Refactors the API events. * ref: Restructures the video quality, profile button and invite dialog events. * ref: Refactors the "device changed" events. * ref: Refactors the page reload event. * ref: Removes an unused function. * ref: Removes a method which is needlessly exposed under a different name. * ref: Refactors the events from the remote video menu. * ref: Refactors the events from the profile pane. * ref: Restructures the recording-related events. Removes events fired when recording with something other than jibri (which isn't currently supported anyway). * ref: Cleans up AnalyticsEvents.js. * ref: Removes an unused function and adds documentation. * feat: Adds events for all API calls. * fix: Addresses feedback. * fix: Brings back mistakenly removed code. * fix: Simplifies code and fixes a bug in toggleFilmstrip when the 'visible' parameter is defined. * feat: Removes the resolution change application log. * ref: Uses consistent naming for events' attributes. Uses "_" as a separator instead of camel case or ".". * ref: Don't add the user agent and conference name as permanent properties. The library does this on its own now. * ref: Adapts the GA handler to changes in lib-jitsi-meet. * ref: Removes unused fields from the analytics handler initializaiton. * ref: Renames the google analytics file and add docs. * fix: Fixes the push-to-talk events and logs. * npm: Updates lib-jitsi-meet to 515374c8d383cb17df8ed76427e6f0fb5ea6ff1e. * fix: Fixes a recently introduced bug in the google analytics handler. * ref: Uses "value" instead of "delay" since this is friendlier to GA.
2018-01-03 21:24:07 +00:00
sendAnalytics(createTrackMutedEvent('audio', 'callkit', value));
dispatch(setAudioMuted(
value, VIDEO_MUTISM_AUTHORITY.USER, /* ensureTrack */ true));
2017-09-28 21:25:04 +00:00
}
}
}
/**
* Notifies the feature jwt that the action {@link SET_AUDIO_MUTED} is being
* dispatched within a specific redux {@code store}.
2017-09-28 21:25:04 +00:00
*
* @param {Store} store - The redux store in which the specified {@code action}
2017-09-28 21:25:04 +00:00
* is being dispatched.
* @param {Dispatch} next - The redux dispatch function to dispatch the
* specified {@code action} to the specified {@code store}.
* @param {Action} action - The redux action {@code SET_AUDIO_MUTED} which is
* being dispatched in the specified {@code store}.
2017-09-28 21:25:04 +00:00
* @private
* @returns {*}
*/
function _setAudioMuted({ getState }, next, action) {
const result = next(action);
const conference = getCurrentConference(getState);
if (conference && conference.callUUID) {
CallKit.setMuted(conference.callUUID, action.muted);
}
return result;
2017-09-28 21:25:04 +00:00
}
/**
2017-09-28 21:25:04 +00:00
* Notifies the feature jwt that the action {@link _SET_CALLKIT_SUBSCRIPTIONS}
* is being dispatched within a specific redux {@code store}.
*
* @param {Store} store - The redux store in which the specified {@code action}
2017-09-28 21:25:04 +00:00
* is being dispatched.
* @param {Dispatch} next - The redux dispatch function to dispatch the
* specified {@code action} to the specified {@code store}.
* @param {Action} action - The redux action {@code _SET_CALLKIT_SUBSCRIPTIONS}
* which is being dispatched in the specified {@code store}.
2017-09-28 21:25:04 +00:00
* @private
* @returns {*}
*/
2017-09-28 21:25:04 +00:00
function _setCallKitSubscriptions({ getState }, next, action) {
const { subscriptions } = getState()['features/callkit'];
2017-09-28 21:25:04 +00:00
if (subscriptions) {
for (const subscription of subscriptions) {
subscription.remove();
}
}
return next(action);
}
/**
* Notifies the feature jwt that the action {@link SET_VIDEO_MUTED} is being
* dispatched within a specific redux {@code store}.
2017-09-28 21:25:04 +00:00
*
* @param {Store} store - The redux store in which the specified {@code action}
2017-09-28 21:25:04 +00:00
* is being dispatched.
* @param {Dispatch} next - The redux dispatch function to dispatch the
* specified {@code action} to the specified {@code store}.
* @param {Action} action - The redux action {@code SET_VIDEO_MUTED} which is
* being dispatched in the specified {@code store}.
2017-09-28 21:25:04 +00:00
* @private
* @returns {*}
*/
function _setVideoMuted({ getState }, next, action) {
const result = next(action);
const conference = getCurrentConference(getState);
if (conference && conference.callUUID) {
CallKit.updateCall(
conference.callUUID,
{ hasVideo: !isVideoMutedByAudioOnly(getState) });
}
return result;
}
/**
* Handles a track creation failure. This is relevant to us in the following
* (corner) case: if the user never gave their permission to use the microphone
* and try to unmute from the CallKit interface, this will fail, and we need
* to sync back the CallKit button state.
*
* @param {Store} store - The redux store in which the specified {@code action}
* is being dispatched.
* @param {Dispatch} next - The redux dispatch function to dispatch the
* specified {@code action} to the specified {@code store}.
* @param {Action} action - The redux action {@code TRACK_CREARE_ERROR} which is
* being dispatched in the specified {@code store}.
* @private
* @returns {*}
*/
function _trackCreateError({ getState }, next, action) {
const result = next(action);
const state = getState();
const conference = getCurrentConference(state);
if (conference && conference.callUUID) {
const tracks = state['features/base/tracks'];
const muted = isLocalTrackMuted(tracks, MEDIA_TYPE.AUDIO);
CallKit.setMuted(conference.callUUID, muted);
}
return result;
}