2017-10-06 20:15:51 +00:00
|
|
|
// @flow
|
2017-09-08 09:28:44 +00:00
|
|
|
|
2019-11-27 11:08:54 +00:00
|
|
|
import { Alert, NativeModules, Platform } from 'react-native';
|
2021-10-25 10:04:51 +00:00
|
|
|
import { v4 as uuidv4 } from 'uuid';
|
2017-09-08 09:28:44 +00:00
|
|
|
|
2018-05-03 22:53:38 +00:00
|
|
|
import { createTrackMutedEvent, sendAnalytics } from '../../analytics';
|
2020-06-04 14:09:13 +00:00
|
|
|
import { appNavigate } from '../../app/actions';
|
2018-07-11 09:42:43 +00:00
|
|
|
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../../base/app';
|
2019-07-31 12:47:52 +00:00
|
|
|
import { SET_AUDIO_ONLY } from '../../base/audio-only';
|
2017-09-08 09:28:44 +00:00
|
|
|
import {
|
|
|
|
CONFERENCE_FAILED,
|
2019-02-14 21:00:51 +00:00
|
|
|
CONFERENCE_JOINED,
|
2017-09-08 09:28:44 +00:00
|
|
|
CONFERENCE_LEFT,
|
|
|
|
CONFERENCE_WILL_JOIN,
|
2019-02-14 21:00:51 +00:00
|
|
|
CONFERENCE_WILL_LEAVE,
|
2019-01-30 15:43:57 +00:00
|
|
|
getConferenceName,
|
2017-09-28 21:25:04 +00:00
|
|
|
getCurrentConference
|
2017-09-08 09:28:44 +00:00
|
|
|
} from '../../base/conference';
|
|
|
|
import { getInviteURL } from '../../base/connection';
|
|
|
|
import {
|
2018-01-24 14:41:48 +00:00
|
|
|
MEDIA_TYPE,
|
|
|
|
isVideoMutedByAudioOnly,
|
2017-09-08 09:28:44 +00:00
|
|
|
setAudioMuted
|
|
|
|
} from '../../base/media';
|
2017-09-28 21:25:04 +00:00
|
|
|
import { MiddlewareRegistry } from '../../base/redux';
|
2018-06-15 10:09:53 +00:00
|
|
|
import {
|
|
|
|
TRACK_ADDED,
|
|
|
|
TRACK_REMOVED,
|
|
|
|
TRACK_UPDATED,
|
|
|
|
isLocalTrackMuted
|
|
|
|
} from '../../base/tracks';
|
2017-09-28 21:25:04 +00:00
|
|
|
|
2017-09-08 09:28:44 +00:00
|
|
|
import CallKit from './CallKit';
|
2019-01-31 16:20:54 +00:00
|
|
|
import ConnectionService from './ConnectionService';
|
2020-05-20 10:57:03 +00:00
|
|
|
import { _SET_CALL_INTEGRATION_SUBSCRIPTIONS } from './actionTypes';
|
2019-10-18 14:30:59 +00:00
|
|
|
import { isCallIntegrationEnabled } from './functions';
|
2019-01-31 16:20:54 +00:00
|
|
|
|
2019-11-27 11:08:54 +00:00
|
|
|
const { AudioMode } = NativeModules;
|
2019-01-31 16:20:54 +00:00
|
|
|
const CallIntegration = CallKit || ConnectionService;
|
2017-09-08 09:28:44 +00:00
|
|
|
|
|
|
|
/**
|
2018-05-03 22:53:38 +00:00
|
|
|
* Middleware that captures system actions and hooks up CallKit.
|
2017-09-08 09:28:44 +00:00
|
|
|
*
|
|
|
|
* @param {Store} store - The redux store.
|
|
|
|
* @returns {Function}
|
|
|
|
*/
|
2019-01-31 16:20:54 +00:00
|
|
|
CallIntegration && MiddlewareRegistry.register(store => next => action => {
|
2017-09-08 09:28:44 +00:00
|
|
|
switch (action.type) {
|
2019-01-31 16:20:54 +00:00
|
|
|
case _SET_CALL_INTEGRATION_SUBSCRIPTIONS:
|
2017-09-28 21:25:04 +00:00
|
|
|
return _setCallKitSubscriptions(store, next, action);
|
2017-09-08 09:28:44 +00:00
|
|
|
|
2017-09-28 21:25:04 +00:00
|
|
|
case APP_WILL_MOUNT:
|
|
|
|
return _appWillMount(store, next, action);
|
2017-09-08 09:28:44 +00:00
|
|
|
|
|
|
|
case APP_WILL_UNMOUNT:
|
2017-09-28 21:25:04 +00:00
|
|
|
store.dispatch({
|
2019-01-31 16:20:54 +00:00
|
|
|
type: _SET_CALL_INTEGRATION_SUBSCRIPTIONS,
|
2017-09-28 21:25:04 +00:00
|
|
|
subscriptions: undefined
|
2017-09-08 09:28:44 +00:00
|
|
|
});
|
|
|
|
break;
|
|
|
|
|
2017-09-28 21:25:04 +00:00
|
|
|
case CONFERENCE_FAILED:
|
|
|
|
return _conferenceFailed(store, next, action);
|
2017-09-08 09:28:44 +00:00
|
|
|
|
2017-09-28 21:25:04 +00:00
|
|
|
case CONFERENCE_JOINED:
|
|
|
|
return _conferenceJoined(store, next, action);
|
2017-09-08 09:28:44 +00:00
|
|
|
|
2019-02-14 21:00:51 +00:00
|
|
|
// If a conference is being left in a graceful manner then
|
|
|
|
// the CONFERENCE_WILL_LEAVE fires as soon as the conference starts
|
|
|
|
// disconnecting. We need to destroy the call on the native side as soon
|
|
|
|
// as possible, because the disconnection process is asynchronous and
|
|
|
|
// Android not always supports two simultaneous calls at the same time
|
|
|
|
// (even though it should according to the spec).
|
2017-09-28 21:25:04 +00:00
|
|
|
case CONFERENCE_LEFT:
|
2019-02-14 21:00:51 +00:00
|
|
|
case CONFERENCE_WILL_LEAVE:
|
2017-09-28 21:25:04 +00:00
|
|
|
return _conferenceLeft(store, next, action);
|
2017-09-08 09:28:44 +00:00
|
|
|
|
2017-09-28 21:25:04 +00:00
|
|
|
case CONFERENCE_WILL_JOIN:
|
|
|
|
return _conferenceWillJoin(store, next, action);
|
2017-09-08 09:28:44 +00:00
|
|
|
|
2018-06-20 10:22:26 +00:00
|
|
|
case SET_AUDIO_ONLY:
|
|
|
|
return _setAudioOnly(store, next, action);
|
|
|
|
|
2018-06-15 10:09:53 +00:00
|
|
|
case TRACK_ADDED:
|
|
|
|
case TRACK_REMOVED:
|
|
|
|
case TRACK_UPDATED:
|
|
|
|
return _syncTrackState(store, next, action);
|
2017-09-08 09:28:44 +00:00
|
|
|
}
|
|
|
|
|
2017-09-28 21:25:04 +00:00
|
|
|
return next(action);
|
|
|
|
});
|
2017-09-08 09:28:44 +00:00
|
|
|
|
2017-09-28 21:25:04 +00:00
|
|
|
/**
|
2018-05-03 22:53:38 +00:00
|
|
|
* Notifies the feature callkit that the action {@link APP_WILL_MOUNT} is being
|
2017-10-01 06:35:19 +00:00
|
|
|
* dispatched within a specific redux {@code store}.
|
2017-09-28 21:25:04 +00:00
|
|
|
*
|
2017-10-01 06:35:19 +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.
|
2018-05-03 22:53:38 +00:00
|
|
|
* @param {Dispatch} next - The redux {@code dispatch} function to dispatch the
|
|
|
|
* specified {@code action} in the specified {@code store}.
|
2017-10-01 06:35:19 +00:00
|
|
|
* @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
|
2018-05-03 22:53:38 +00:00
|
|
|
* @returns {*} The value returned by {@code next(action)}.
|
2017-09-28 21:25:04 +00:00
|
|
|
*/
|
|
|
|
function _appWillMount({ dispatch, getState }, next, action) {
|
|
|
|
const result = next(action);
|
2017-09-08 09:28:44 +00:00
|
|
|
|
2017-09-28 21:25:04 +00:00
|
|
|
const context = {
|
|
|
|
dispatch,
|
|
|
|
getState
|
|
|
|
};
|
2019-01-31 16:20:54 +00:00
|
|
|
|
|
|
|
const delegate = {
|
|
|
|
_onPerformSetMutedCallAction,
|
|
|
|
_onPerformEndCallAction
|
|
|
|
};
|
|
|
|
|
2021-11-25 16:24:13 +00:00
|
|
|
if (isCallIntegrationEnabled(getState)) {
|
|
|
|
const subscriptions = CallIntegration.registerSubscriptions(context, delegate);
|
2019-01-31 16:20:54 +00:00
|
|
|
|
2021-11-25 16:24:13 +00:00
|
|
|
subscriptions && dispatch({
|
|
|
|
type: _SET_CALL_INTEGRATION_SUBSCRIPTIONS,
|
|
|
|
subscriptions
|
|
|
|
});
|
|
|
|
}
|
2017-09-28 21:25:04 +00:00
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2018-05-03 22:53:38 +00:00
|
|
|
* Notifies the feature callkit that the action {@link CONFERENCE_FAILED} is
|
|
|
|
* being dispatched within a specific redux {@code store}.
|
2017-09-28 21:25:04 +00:00
|
|
|
*
|
2017-10-01 06:35:19 +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.
|
2018-05-03 22:53:38 +00:00
|
|
|
* @param {Dispatch} next - The redux {@code dispatch} function to dispatch the
|
|
|
|
* specified {@code action} in the specified {@code store}.
|
2017-10-01 06:35:19 +00:00
|
|
|
* @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
|
2018-05-03 22:53:38 +00:00
|
|
|
* @returns {*} The value returned by {@code next(action)}.
|
2017-09-28 21:25:04 +00:00
|
|
|
*/
|
2019-10-18 14:30:59 +00:00
|
|
|
function _conferenceFailed({ getState }, next, action) {
|
2017-09-28 21:25:04 +00:00
|
|
|
const result = next(action);
|
|
|
|
|
2019-10-18 14:30:59 +00:00
|
|
|
if (!isCallIntegrationEnabled(getState)) {
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2017-10-19 18:29:25 +00:00
|
|
|
// 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) {
|
2019-02-14 21:00:51 +00:00
|
|
|
delete action.conference.callUUID;
|
2019-01-31 16:20:54 +00:00
|
|
|
CallIntegration.reportCallFailed(callUUID);
|
2017-10-19 18:29:25 +00:00
|
|
|
}
|
2017-09-08 09:28:44 +00:00
|
|
|
}
|
|
|
|
|
2017-09-28 21:25:04 +00:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2018-05-03 22:53:38 +00:00
|
|
|
* Notifies the feature callkit that the action {@link CONFERENCE_JOINED} is
|
|
|
|
* being dispatched within a specific redux {@code store}.
|
2017-09-28 21:25:04 +00:00
|
|
|
*
|
2017-10-01 06:35:19 +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.
|
2018-05-03 22:53:38 +00:00
|
|
|
* @param {Dispatch} next - The redux {@code dispatch} function to dispatch the
|
|
|
|
* specified {@code action} in the specified {@code store}.
|
2017-10-01 06:35:19 +00:00
|
|
|
* @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
|
2018-05-03 22:53:38 +00:00
|
|
|
* @returns {*} The value returned by {@code next(action)}.
|
2017-09-28 21:25:04 +00:00
|
|
|
*/
|
2019-09-12 09:59:15 +00:00
|
|
|
function _conferenceJoined({ getState }, next, action) {
|
2017-09-28 21:25:04 +00:00
|
|
|
const result = next(action);
|
2017-09-08 09:28:44 +00:00
|
|
|
|
2019-10-18 14:30:59 +00:00
|
|
|
if (!isCallIntegrationEnabled(getState)) {
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2017-09-28 21:25:04 +00:00
|
|
|
const { callUUID } = action.conference;
|
2017-09-08 09:28:44 +00:00
|
|
|
|
2017-09-28 21:25:04 +00:00
|
|
|
if (callUUID) {
|
2019-12-03 09:02:56 +00:00
|
|
|
CallIntegration.reportConnectedOutgoingCall(callUUID)
|
|
|
|
.then(() => {
|
|
|
|
// iOS 13 doesn't like the mute state to be false before the call is started
|
|
|
|
// so we update it here in case the user selected startWithAudioMuted.
|
|
|
|
if (Platform.OS === 'ios') {
|
|
|
|
_updateCallIntegrationMuted(action.conference, getState());
|
|
|
|
}
|
|
|
|
})
|
2020-01-08 15:48:02 +00:00
|
|
|
.catch(() => {
|
|
|
|
// Currently errors here are only emitted by Android.
|
2019-12-03 09:02:56 +00:00
|
|
|
//
|
2020-01-08 15:48:02 +00:00
|
|
|
// Some Samsung devices will fail to fully engage ConnectionService if no SIM card
|
|
|
|
// was ever installed on the device. We could check for it, but it would require
|
|
|
|
// the CALL_PHONE permission, which is not something we want to do, so fallback to
|
|
|
|
// not using ConnectionService.
|
|
|
|
_handleConnectionServiceFailure(getState());
|
2019-12-03 09:02:56 +00:00
|
|
|
});
|
2017-09-08 09:28:44 +00:00
|
|
|
}
|
|
|
|
|
2017-09-28 21:25:04 +00:00
|
|
|
return result;
|
|
|
|
}
|
2017-09-08 09:28:44 +00:00
|
|
|
|
2017-09-28 21:25:04 +00:00
|
|
|
/**
|
2018-05-03 22:53:38 +00:00
|
|
|
* Notifies the feature callkit that the action {@link CONFERENCE_LEFT} is being
|
2017-10-01 06:35:19 +00:00
|
|
|
* dispatched within a specific redux {@code store}.
|
2017-09-28 21:25:04 +00:00
|
|
|
*
|
2017-10-01 06:35:19 +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.
|
2018-05-03 22:53:38 +00:00
|
|
|
* @param {Dispatch} next - The redux {@code dispatch} function to dispatch the
|
|
|
|
* specified {@code action} in the specified {@code store}.
|
2017-10-01 06:35:19 +00:00
|
|
|
* @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
|
2018-05-03 22:53:38 +00:00
|
|
|
* @returns {*} The value returned by {@code next(action)}.
|
2017-09-28 21:25:04 +00:00
|
|
|
*/
|
2019-10-18 14:30:59 +00:00
|
|
|
function _conferenceLeft({ getState }, next, action) {
|
2017-09-28 21:25:04 +00:00
|
|
|
const result = next(action);
|
2017-09-08 09:28:44 +00:00
|
|
|
|
2019-10-18 14:30:59 +00:00
|
|
|
if (!isCallIntegrationEnabled(getState)) {
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2017-09-28 21:25:04 +00:00
|
|
|
const { callUUID } = action.conference;
|
|
|
|
|
|
|
|
if (callUUID) {
|
2019-02-14 21:00:51 +00:00
|
|
|
delete action.conference.callUUID;
|
2019-01-31 16:20:54 +00:00
|
|
|
CallIntegration.endCall(callUUID);
|
2017-09-08 09:28:44 +00:00
|
|
|
}
|
|
|
|
|
2017-09-28 21:25:04 +00:00
|
|
|
return result;
|
|
|
|
}
|
2017-09-08 09:28:44 +00:00
|
|
|
|
2017-09-28 21:25:04 +00:00
|
|
|
/**
|
2018-05-03 22:53:38 +00:00
|
|
|
* Notifies the feature callkit that the action {@link CONFERENCE_WILL_JOIN} is
|
2017-10-01 06:35:19 +00:00
|
|
|
* being dispatched within a specific redux {@code store}.
|
2017-09-28 21:25:04 +00:00
|
|
|
*
|
2017-10-01 06:35:19 +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.
|
2018-05-03 22:53:38 +00:00
|
|
|
* @param {Dispatch} next - The redux {@code dispatch} function to dispatch the
|
|
|
|
* specified {@code action} in the specified {@code store}.
|
2017-10-01 06:35:19 +00:00
|
|
|
* @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
|
2018-05-03 22:53:38 +00:00
|
|
|
* @returns {*} The value returned by {@code next(action)}.
|
2017-09-28 21:25:04 +00:00
|
|
|
*/
|
2019-01-31 16:20:54 +00:00
|
|
|
function _conferenceWillJoin({ dispatch, getState }, next, action) {
|
2017-09-28 21:25:04 +00:00
|
|
|
const result = next(action);
|
2017-09-08 09:28:44 +00:00
|
|
|
|
2019-10-18 14:30:59 +00:00
|
|
|
if (!isCallIntegrationEnabled(getState)) {
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2017-10-10 20:07:40 +00:00
|
|
|
const { conference } = action;
|
|
|
|
const state = getState();
|
2018-06-08 18:08:45 +00:00
|
|
|
const { callHandle, callUUID } = state['features/base/config'];
|
2017-10-10 20:07:40 +00:00
|
|
|
const url = getInviteURL(state);
|
2018-06-08 18:08:45 +00:00
|
|
|
const handle = callHandle || url.toString();
|
2017-10-10 20:07:40 +00:00
|
|
|
const hasVideo = !isVideoMutedByAudioOnly(state);
|
2017-09-08 09:28:44 +00:00
|
|
|
|
2020-09-09 07:22:34 +00:00
|
|
|
// If we already have a callUUID set, don't start a new call.
|
|
|
|
if (conference.callUUID) {
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2018-05-03 22:53:38 +00:00
|
|
|
// When assigning the call UUID, do so in upper case, since iOS will return
|
|
|
|
// it upper cased.
|
2021-10-25 10:04:51 +00:00
|
|
|
conference.callUUID = (callUUID || uuidv4()).toUpperCase();
|
2018-04-18 15:11:56 +00:00
|
|
|
|
2019-01-31 16:20:54 +00:00
|
|
|
CallIntegration.startCall(conference.callUUID, handle, hasVideo)
|
2017-09-28 21:25:04 +00:00
|
|
|
.then(() => {
|
2019-01-30 15:43:57 +00:00
|
|
|
const displayName = getConferenceName(state);
|
2018-01-24 14:41:48 +00:00
|
|
|
|
2019-01-31 16:20:54 +00:00
|
|
|
CallIntegration.updateCall(
|
|
|
|
conference.callUUID,
|
|
|
|
{
|
|
|
|
displayName,
|
|
|
|
hasVideo
|
|
|
|
});
|
2019-09-12 09:59:15 +00:00
|
|
|
|
|
|
|
// iOS 13 doesn't like the mute state to be false before the call is started
|
|
|
|
// so delay it until the conference was joined.
|
|
|
|
if (Platform.OS !== 'ios') {
|
|
|
|
_updateCallIntegrationMuted(conference, state);
|
|
|
|
}
|
2019-01-31 16:20:54 +00:00
|
|
|
})
|
|
|
|
.catch(error => {
|
2019-11-27 11:08:54 +00:00
|
|
|
// Currently this error codes are emitted only by Android.
|
|
|
|
//
|
2019-01-31 16:20:54 +00:00
|
|
|
if (error.code === 'CREATE_OUTGOING_CALL_FAILED') {
|
|
|
|
// We're not tracking the call anymore - it doesn't exist on
|
|
|
|
// the native side.
|
|
|
|
delete conference.callUUID;
|
|
|
|
dispatch(appNavigate(undefined));
|
|
|
|
Alert.alert(
|
|
|
|
'Call aborted',
|
|
|
|
'There\'s already another call in progress.'
|
|
|
|
+ ' Please end it first and try again.',
|
|
|
|
[
|
|
|
|
{ text: 'OK' }
|
|
|
|
],
|
|
|
|
{ cancelable: false });
|
2020-01-08 15:48:02 +00:00
|
|
|
} else {
|
2019-11-27 11:08:54 +00:00
|
|
|
// Some devices fail because the CALL_PHONE permission is not granted, which is
|
2019-12-03 09:02:56 +00:00
|
|
|
// nonsense, because it's not needed for self-managed connections.
|
2020-01-08 15:48:02 +00:00
|
|
|
// Some other devices fail because ConnectionService is not supported.
|
|
|
|
// Be that as it may, fallback to non-ConnectionService audio device handling.
|
2019-12-03 09:02:56 +00:00
|
|
|
|
|
|
|
_handleConnectionServiceFailure(state);
|
2019-01-31 16:20:54 +00:00
|
|
|
}
|
2017-09-28 21:25:04 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2019-12-03 09:02:56 +00:00
|
|
|
/**
|
|
|
|
* Handles a ConnectionService fatal error by falling back to non-ConnectionService device management.
|
|
|
|
*
|
|
|
|
* @param {Object} state - Redux store.
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
|
|
|
function _handleConnectionServiceFailure(state: Object) {
|
|
|
|
const conference = getCurrentConference(state);
|
|
|
|
|
|
|
|
if (conference) {
|
|
|
|
// We're not tracking the call anymore.
|
|
|
|
delete conference.callUUID;
|
|
|
|
|
|
|
|
// ConnectionService has fatally failed. Alas, this also means audio device management would be broken, so
|
|
|
|
// fallback to not using ConnectionService.
|
|
|
|
// NOTE: We are not storing this in Settings, in case it's a transient issue, as far fetched as
|
|
|
|
// that may be.
|
|
|
|
if (AudioMode.setUseConnectionService) {
|
|
|
|
AudioMode.setUseConnectionService(false);
|
|
|
|
|
|
|
|
const hasVideo = !isVideoMutedByAudioOnly(state);
|
|
|
|
|
|
|
|
// Set the desired audio mode, since we just reset the whole thing.
|
|
|
|
AudioMode.setMode(hasVideo ? AudioMode.VIDEO_CALL : AudioMode.AUDIO_CALL);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-09-28 21:25:04 +00:00
|
|
|
/**
|
2017-10-01 06:35:19 +00:00
|
|
|
* Handles CallKit's event {@code performEndCallAction}.
|
2017-09-28 21:25:04 +00:00
|
|
|
*
|
|
|
|
* @param {Object} event - The details of the CallKit event
|
2017-10-01 06:35:19 +00:00
|
|
|
* {@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-08 09:28:44 +00:00
|
|
|
}
|
2017-09-28 21:25:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2017-10-01 06:35:19 +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
|
2017-10-01 06:35:19 +00:00
|
|
|
* {@code performSetMutedCallAction}.
|
2017-09-28 21:25:04 +00:00
|
|
|
* @returns {void}
|
|
|
|
*/
|
2018-06-21 13:43:21 +00:00
|
|
|
function _onPerformSetMutedCallAction({ callUUID, muted }) {
|
2017-09-28 21:25:04 +00:00
|
|
|
const { dispatch, getState } = this; // eslint-disable-line no-invalid-this
|
|
|
|
const conference = getCurrentConference(getState);
|
|
|
|
|
|
|
|
if (conference && conference.callUUID === callUUID) {
|
2018-06-21 13:43:21 +00:00
|
|
|
muted = Boolean(muted); // eslint-disable-line no-param-reassign
|
2019-01-31 16:20:54 +00:00
|
|
|
sendAnalytics(
|
|
|
|
createTrackMutedEvent('audio', 'call-integration', muted));
|
2018-06-21 13:43:21 +00:00
|
|
|
dispatch(setAudioMuted(muted, /* ensureTrack */ true));
|
2017-09-28 21:25:04 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-06-20 10:22:26 +00:00
|
|
|
/**
|
|
|
|
* Update CallKit with the audio only state of the conference. When a conference
|
|
|
|
* is in audio only mode we will tell CallKit the call has no video. This
|
|
|
|
* affects how the call is saved in the recent calls list.
|
|
|
|
*
|
|
|
|
* XXX: Note that here we are taking the `audioOnly` value straight from the
|
|
|
|
* action, instead of examining the state. This is intentional, as setting the
|
|
|
|
* audio only involves multiple actions which will be reflected in the state
|
|
|
|
* later, but we are just interested in knowing if the mode is going to be
|
|
|
|
* set or not.
|
|
|
|
*
|
|
|
|
* @param {Store} store - The redux store in which the specified {@code action}
|
|
|
|
* is being dispatched.
|
|
|
|
* @param {Dispatch} next - The redux {@code dispatch} function to dispatch the
|
|
|
|
* specified {@code action} in the specified {@code store}.
|
|
|
|
* @param {Action} action - The redux action which is being dispatched in the
|
|
|
|
* specified {@code store}.
|
|
|
|
* @private
|
|
|
|
* @returns {*} The value returned by {@code next(action)}.
|
|
|
|
*/
|
|
|
|
function _setAudioOnly({ getState }, next, action) {
|
|
|
|
const result = next(action);
|
|
|
|
const state = getState();
|
2019-10-18 14:30:59 +00:00
|
|
|
|
|
|
|
if (!isCallIntegrationEnabled(state)) {
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2018-06-20 10:22:26 +00:00
|
|
|
const conference = getCurrentConference(state);
|
|
|
|
|
|
|
|
if (conference && conference.callUUID) {
|
2019-01-31 16:20:54 +00:00
|
|
|
CallIntegration.updateCall(
|
2018-06-20 10:22:26 +00:00
|
|
|
conference.callUUID,
|
|
|
|
{ hasVideo: !action.audioOnly });
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2017-09-08 09:28:44 +00:00
|
|
|
/**
|
2018-05-03 22:53:38 +00:00
|
|
|
* Notifies the feature callkit that the action
|
2019-01-31 16:20:54 +00:00
|
|
|
* {@link _SET_CALL_INTEGRATION_SUBSCRIPTIONS} is being dispatched within
|
|
|
|
* a specific redux {@code store}.
|
2017-09-08 09:28:44 +00:00
|
|
|
*
|
2017-10-01 06:35:19 +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.
|
2018-05-03 22:53:38 +00:00
|
|
|
* @param {Dispatch} next - The redux {@code dispatch} function to dispatch the
|
|
|
|
* specified {@code action} in the specified {@code store}.
|
2019-01-31 16:20:54 +00:00
|
|
|
* @param {Action} action - The redux action
|
|
|
|
* {@code _SET_CALL_INTEGRATION_SUBSCRIPTIONS} which is being dispatched in
|
|
|
|
* the specified {@code store}.
|
2017-09-28 21:25:04 +00:00
|
|
|
* @private
|
2018-05-03 22:53:38 +00:00
|
|
|
* @returns {*} The value returned by {@code next(action)}.
|
2017-09-08 09:28:44 +00:00
|
|
|
*/
|
2017-09-28 21:25:04 +00:00
|
|
|
function _setCallKitSubscriptions({ getState }, next, action) {
|
2019-01-31 16:20:54 +00:00
|
|
|
const { subscriptions } = getState()['features/call-integration'];
|
2017-09-08 09:28:44 +00:00
|
|
|
|
2017-09-28 21:25:04 +00:00
|
|
|
if (subscriptions) {
|
|
|
|
for (const subscription of subscriptions) {
|
|
|
|
subscription.remove();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return next(action);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2018-06-15 10:09:53 +00:00
|
|
|
* Synchronize the muted state of tracks with CallKit.
|
2017-09-28 21:25:04 +00:00
|
|
|
*
|
2017-10-01 06:35:19 +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.
|
2018-05-03 22:53:38 +00:00
|
|
|
* @param {Dispatch} next - The redux {@code dispatch} function to dispatch the
|
|
|
|
* specified {@code action} in the specified {@code store}.
|
2018-06-15 10:09:53 +00:00
|
|
|
* @param {Action} action - The redux action which is being dispatched in the
|
|
|
|
* specified {@code store}.
|
2017-09-28 21:25:04 +00:00
|
|
|
* @private
|
2018-05-03 22:53:38 +00:00
|
|
|
* @returns {*} The value returned by {@code next(action)}.
|
2017-09-28 21:25:04 +00:00
|
|
|
*/
|
2018-06-15 10:09:53 +00:00
|
|
|
function _syncTrackState({ getState }, next, action) {
|
2018-01-24 14:41:48 +00:00
|
|
|
const result = next(action);
|
2019-10-18 14:30:59 +00:00
|
|
|
|
|
|
|
if (!isCallIntegrationEnabled(getState)) {
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2018-06-21 13:43:51 +00:00
|
|
|
const { jitsiTrack } = action.track;
|
2018-01-24 14:41:48 +00:00
|
|
|
const state = getState();
|
|
|
|
const conference = getCurrentConference(state);
|
|
|
|
|
2018-06-21 13:43:51 +00:00
|
|
|
if (jitsiTrack.isLocal() && conference && conference.callUUID) {
|
2018-06-21 14:07:56 +00:00
|
|
|
switch (jitsiTrack.getType()) {
|
|
|
|
case 'audio': {
|
2019-09-12 09:59:15 +00:00
|
|
|
_updateCallIntegrationMuted(conference, state);
|
2018-06-21 14:07:56 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 'video': {
|
2019-01-31 16:20:54 +00:00
|
|
|
CallIntegration.updateCall(
|
2018-06-21 14:07:56 +00:00
|
|
|
conference.callUUID,
|
|
|
|
{ hasVideo: !isVideoMutedByAudioOnly(state) });
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
2018-01-24 14:41:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
2019-09-12 09:59:15 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Update the muted state in the native side.
|
|
|
|
*
|
|
|
|
* @param {Object} conference - The current active conference.
|
|
|
|
* @param {Object} state - The redux store state.
|
|
|
|
* @private
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
|
|
|
function _updateCallIntegrationMuted(conference, state) {
|
|
|
|
const muted = isLocalTrackMuted(state['features/base/tracks'], MEDIA_TYPE.AUDIO);
|
|
|
|
|
|
|
|
CallIntegration.setMuted(conference.callUUID, muted);
|
|
|
|
}
|