@stream-io/video-react-native-sdk 0.3.18 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/dist/commonjs/components/Call/CallControls/AcceptCallButton.js +0 -5
- package/dist/commonjs/components/Call/CallControls/AcceptCallButton.js.map +1 -1
- package/dist/commonjs/components/Call/CallControls/RejectCallButton.js +0 -5
- package/dist/commonjs/components/Call/CallControls/RejectCallButton.js.map +1 -1
- package/dist/commonjs/hooks/push/useIosVoipPushEventsSetupEffect.js +58 -7
- package/dist/commonjs/hooks/push/useIosVoipPushEventsSetupEffect.js.map +1 -1
- package/dist/commonjs/hooks/useAndroidKeepCallAliveEffect.js +15 -6
- package/dist/commonjs/hooks/useAndroidKeepCallAliveEffect.js.map +1 -1
- package/dist/commonjs/providers/MediaStreamManagement.js +25 -34
- package/dist/commonjs/providers/MediaStreamManagement.js.map +1 -1
- package/dist/commonjs/providers/StreamCall.js +22 -5
- package/dist/commonjs/providers/StreamCall.js.map +1 -1
- package/dist/commonjs/utils/push/android.js +60 -1
- package/dist/commonjs/utils/push/android.js.map +1 -1
- package/dist/commonjs/utils/push/ios.js +2 -0
- package/dist/commonjs/utils/push/ios.js.map +1 -1
- package/dist/commonjs/utils/push/rxSubjects.js +6 -1
- package/dist/commonjs/utils/push/rxSubjects.js.map +1 -1
- package/dist/commonjs/utils/push/utils.js +68 -1
- package/dist/commonjs/utils/push/utils.js.map +1 -1
- package/dist/commonjs/version.js +1 -1
- package/dist/commonjs/version.js.map +1 -1
- package/dist/module/components/Call/CallControls/AcceptCallButton.js +0 -5
- package/dist/module/components/Call/CallControls/AcceptCallButton.js.map +1 -1
- package/dist/module/components/Call/CallControls/RejectCallButton.js +0 -5
- package/dist/module/components/Call/CallControls/RejectCallButton.js.map +1 -1
- package/dist/module/hooks/push/useIosVoipPushEventsSetupEffect.js +61 -9
- package/dist/module/hooks/push/useIosVoipPushEventsSetupEffect.js.map +1 -1
- package/dist/module/hooks/useAndroidKeepCallAliveEffect.js +16 -7
- package/dist/module/hooks/useAndroidKeepCallAliveEffect.js.map +1 -1
- package/dist/module/providers/MediaStreamManagement.js +25 -34
- package/dist/module/providers/MediaStreamManagement.js.map +1 -1
- package/dist/module/providers/StreamCall.js +19 -3
- package/dist/module/providers/StreamCall.js.map +1 -1
- package/dist/module/utils/push/android.js +63 -4
- package/dist/module/utils/push/android.js.map +1 -1
- package/dist/module/utils/push/ios.js +3 -1
- package/dist/module/utils/push/ios.js.map +1 -1
- package/dist/module/utils/push/rxSubjects.js +5 -0
- package/dist/module/utils/push/rxSubjects.js.map +1 -1
- package/dist/module/utils/push/utils.js +65 -0
- package/dist/module/utils/push/utils.js.map +1 -1
- package/dist/module/version.js +1 -1
- package/dist/module/version.js.map +1 -1
- package/dist/typescript/components/Call/CallControls/AcceptCallButton.d.ts.map +1 -1
- package/dist/typescript/components/Call/CallControls/RejectCallButton.d.ts.map +1 -1
- package/dist/typescript/hooks/push/useIosVoipPushEventsSetupEffect.d.ts.map +1 -1
- package/dist/typescript/hooks/useAndroidKeepCallAliveEffect.d.ts +1 -0
- package/dist/typescript/hooks/useAndroidKeepCallAliveEffect.d.ts.map +1 -1
- package/dist/typescript/providers/MediaStreamManagement.d.ts.map +1 -1
- package/dist/typescript/providers/StreamCall.d.ts +3 -1
- package/dist/typescript/providers/StreamCall.d.ts.map +1 -1
- package/dist/typescript/utils/push/android.d.ts.map +1 -1
- package/dist/typescript/utils/push/ios.d.ts.map +1 -1
- package/dist/typescript/utils/push/rxSubjects.d.ts +6 -0
- package/dist/typescript/utils/push/rxSubjects.d.ts.map +1 -1
- package/dist/typescript/utils/push/utils.d.ts +22 -1
- package/dist/typescript/utils/push/utils.d.ts.map +1 -1
- package/dist/typescript/version.d.ts +1 -1
- package/dist/typescript/version.d.ts.map +1 -1
- package/expo-config-plugin/dist/withAndroidPermissions.js +1 -0
- package/expo-config-plugin/dist/withPushAppDelegate.js +7 -1
- package/ios/StreamVideoReactNative.h +3 -1
- package/ios/StreamVideoReactNative.m +24 -1
- package/package.json +1 -1
- package/src/components/Call/CallControls/AcceptCallButton.tsx +0 -5
- package/src/components/Call/CallControls/RejectCallButton.tsx +0 -5
- package/src/hooks/push/useIosVoipPushEventsSetupEffect.ts +76 -8
- package/src/hooks/useAndroidKeepCallAliveEffect.ts +18 -7
- package/src/providers/MediaStreamManagement.tsx +30 -39
- package/src/providers/StreamCall.tsx +25 -3
- package/src/utils/push/android.ts +82 -4
- package/src/utils/push/ios.ts +6 -1
- package/src/utils/push/rxSubjects.ts +10 -0
- package/src/utils/push/utils.ts +70 -1
- package/src/version.ts +1 -1
|
@@ -1,6 +1,17 @@
|
|
|
1
|
-
import { StreamVideoClient } from '@stream-io/video-client';
|
|
1
|
+
import { Call, StreamVideoClient } from '@stream-io/video-client';
|
|
2
2
|
import type { NonRingingPushEvent, StreamVideoConfig } from '../StreamVideoRN/types';
|
|
3
3
|
type PushConfig = NonNullable<StreamVideoConfig['push']>;
|
|
4
|
+
type CanAddPushWSSubscriptionsRef = {
|
|
5
|
+
current: boolean;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* This function is used to check if the call should be ended based on the push notification
|
|
9
|
+
* Useful for callkeep management to end the call if necessary (with reportEndCallWithUUID)
|
|
10
|
+
*/
|
|
11
|
+
export declare const shouldCallBeEnded: (callFromPush: Call, created_by_id: string | undefined, receiver_id: string | undefined) => {
|
|
12
|
+
mustEndCall: boolean;
|
|
13
|
+
callkeepReason: number;
|
|
14
|
+
};
|
|
4
15
|
export declare const processCallFromPushInBackground: (pushConfig: PushConfig, call_cid: string, action: Parameters<typeof processCallFromPush>[2]) => Promise<void>;
|
|
5
16
|
/**
|
|
6
17
|
* This function is used process the call from push notifications due to incoming call
|
|
@@ -18,5 +29,15 @@ export declare const processCallFromPush: (client: StreamVideoClient, call_cid:
|
|
|
18
29
|
* 3. Call all the callbacks to inform the app about the call
|
|
19
30
|
*/
|
|
20
31
|
export declare const processNonIncomingCallFromPush: (client: StreamVideoClient, call_cid: string, nonRingingNotificationType: NonRingingPushEvent) => Promise<void>;
|
|
32
|
+
/**
|
|
33
|
+
* This function is used to clear all the push related WS subscriptions
|
|
34
|
+
* note: events are subscribed in push for accept/decline through WS
|
|
35
|
+
*/
|
|
36
|
+
export declare const clearPushWSEventSubscriptions: () => void;
|
|
37
|
+
/**
|
|
38
|
+
* This ref is used to check if the push WS subscriptions can be added
|
|
39
|
+
* It is used to avoid adding the push WS subscriptions when the client is connected to WS in the foreground
|
|
40
|
+
*/
|
|
41
|
+
export declare const canAddPushWSSubscriptionsRef: CanAddPushWSSubscriptionsRef;
|
|
21
42
|
export {};
|
|
22
43
|
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../../src/utils/push/utils.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../../src/utils/push/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAW,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC3E,OAAO,KAAK,EACV,mBAAmB,EACnB,iBAAiB,EAClB,MAAM,wBAAwB,CAAC;AAIhC,KAAK,UAAU,GAAG,WAAW,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC,CAAC;AAEzD,KAAK,4BAA4B,GAAG;IAAE,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC;AAEzD;;;GAGG;AACH,eAAO,MAAM,iBAAiB,iBACd,IAAI,iBACH,MAAM,GAAG,SAAS,eACpB,MAAM,GAAG,SAAS;;;CAmChC,CAAC;AAMF,eAAO,MAAM,+BAA+B,eAC9B,UAAU,YACZ,MAAM,UACR,WAAW,0BAA0B,CAAC,CAAC,CAAC,CAAC,kBAclD,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,WACtB,iBAAiB,YACf,MAAM,UACR,QAAQ,GAAG,SAAS,GAAG,SAAS,kBAmBzC,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,8BAA8B,WACjC,iBAAiB,YACf,MAAM,8BACY,mBAAmB,kBAkBhD,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,6BAA6B,YAQzC,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,4BAA4B,EAAE,4BAE1C,CAAC"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const version = "0.
|
|
1
|
+
export declare const version = "0.4.0";
|
|
2
2
|
//# sourceMappingURL=version.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../../src/version.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,OAAO,
|
|
1
|
+
{"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../../src/version.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,OAAO,UAAU,CAAC"}
|
|
@@ -6,6 +6,7 @@ const withStreamVideoReactNativeSDKAndroidPermissions = (config) => {
|
|
|
6
6
|
'android.permission.POST_NOTIFICATIONS',
|
|
7
7
|
'android.permission.FOREGROUND_SERVICE',
|
|
8
8
|
'android.permission.FOREGROUND_SERVICE_MICROPHONE',
|
|
9
|
+
'android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION',
|
|
9
10
|
'android.permission.BLUETOOTH',
|
|
10
11
|
'android.permission.BLUETOOTH_CONNECT',
|
|
11
12
|
'android.permission.BLUETOOTH_ADMIN',
|
|
@@ -22,6 +22,7 @@ const withPushAppDelegate = (configuration, props) => {
|
|
|
22
22
|
'"RNCallKeep.h"',
|
|
23
23
|
'<PushKit/PushKit.h>',
|
|
24
24
|
'"RNVoipPushNotificationManager.h"',
|
|
25
|
+
'"StreamVideoReactNative.h"',
|
|
25
26
|
]);
|
|
26
27
|
config.modResults.contents = addDidFinishLaunchingWithOptions(config.modResults.contents, props.ringingPushNotifications);
|
|
27
28
|
config.modResults.contents = addDidUpdatePushCredentials(config.modResults.contents);
|
|
@@ -86,6 +87,11 @@ function addDidReceiveIncomingPushCallback(contents) {
|
|
|
86
87
|
NSDictionary *stream = payload.dictionaryPayload[@"stream"];
|
|
87
88
|
NSString *uuid = [[NSUUID UUID] UUIDString];
|
|
88
89
|
NSString *createdCallerName = stream[@"created_by_display_name"];
|
|
90
|
+
NSString *cid = stream[@"call_cid"];
|
|
91
|
+
|
|
92
|
+
[StreamVideoReactNative registerIncomingCall:cid uuid:uuid];
|
|
93
|
+
|
|
94
|
+
[RNVoipPushNotificationManager addCompletionHandler:uuid completionHandler:completion];
|
|
89
95
|
|
|
90
96
|
// display the incoming call notification
|
|
91
97
|
[RNCallKeep reportNewIncomingCall: uuid
|
|
@@ -99,7 +105,7 @@ function addDidReceiveIncomingPushCallback(contents) {
|
|
|
99
105
|
supportsUngrouping: YES
|
|
100
106
|
fromPushKit: YES
|
|
101
107
|
payload: stream
|
|
102
|
-
withCompletionHandler:
|
|
108
|
+
withCompletionHandler: nil];
|
|
103
109
|
`;
|
|
104
110
|
if (!contents.includes('[RNVoipPushNotificationManager didReceiveIncomingPushWithPayload')) {
|
|
105
111
|
const codeblock = (0, codeMod_1.findObjcFunctionCodeBlock)(contents, DID_RECEIVE_INCOMING_PUSH);
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
// Do not change these consts, it is what is used react-native-webrtc
|
|
8
8
|
NSNotificationName const kBroadcastStartedNotification = @"iOS_BroadcastStarted";
|
|
9
9
|
NSNotificationName const kBroadcastStoppedNotification = @"iOS_BroadcastStopped";
|
|
10
|
+
NSMutableDictionary *dictionary;
|
|
10
11
|
|
|
11
12
|
void broadcastNotificationCallback(CFNotificationCenterRef center,
|
|
12
13
|
void *observer,
|
|
@@ -92,9 +93,31 @@ RCT_EXPORT_MODULE();
|
|
|
92
93
|
}
|
|
93
94
|
}
|
|
94
95
|
|
|
96
|
+
+(void)registerIncomingCall:(NSString *)cid uuid:(NSString *)uuid {
|
|
97
|
+
if (dictionary == nil) {
|
|
98
|
+
dictionary = [NSMutableDictionary dictionary];
|
|
99
|
+
}
|
|
100
|
+
dictionary[cid] = uuid;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
RCT_EXPORT_METHOD(getIncomingCallUUid:(NSString *)cid
|
|
104
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
105
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
106
|
+
{
|
|
107
|
+
if (dictionary == nil) {
|
|
108
|
+
reject(@"access_failure", @"no incoming call dictionary found", nil);
|
|
109
|
+
}
|
|
110
|
+
NSString *uuid = dictionary[cid];
|
|
111
|
+
if (uuid) {
|
|
112
|
+
resolve(uuid);
|
|
113
|
+
} else {
|
|
114
|
+
reject(@"access_failure", @"requested incoming call found", nil);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
}
|
|
118
|
+
|
|
95
119
|
-(NSArray<NSString *> *)supportedEvents {
|
|
96
120
|
return @[@"StreamVideoReactNative_Ios_Screenshare_Event"];
|
|
97
121
|
}
|
|
98
122
|
|
|
99
|
-
|
|
100
123
|
@end
|
package/package.json
CHANGED
|
@@ -3,8 +3,6 @@ import React from 'react';
|
|
|
3
3
|
import { CallControlsButton } from './CallControlsButton';
|
|
4
4
|
import { Phone } from '../../../icons';
|
|
5
5
|
import { useTheme } from '../../../contexts/ThemeContext';
|
|
6
|
-
import { Platform } from 'react-native';
|
|
7
|
-
import notifee from '@notifee/react-native';
|
|
8
6
|
|
|
9
7
|
/**
|
|
10
8
|
* The props for the Accept Call button.
|
|
@@ -45,9 +43,6 @@ export const AcceptCallButton = ({
|
|
|
45
43
|
return;
|
|
46
44
|
}
|
|
47
45
|
try {
|
|
48
|
-
if (Platform.OS === 'android' && call?.cid) {
|
|
49
|
-
notifee.cancelDisplayedNotification(call?.cid);
|
|
50
|
-
}
|
|
51
46
|
await call?.join();
|
|
52
47
|
if (onAcceptCallHandler) {
|
|
53
48
|
onAcceptCallHandler();
|
|
@@ -4,8 +4,6 @@ import { CallControlsButton } from './CallControlsButton';
|
|
|
4
4
|
import { PhoneDown } from '../../../icons';
|
|
5
5
|
import { CallingState } from '@stream-io/video-client';
|
|
6
6
|
import { useTheme } from '../../../contexts/ThemeContext';
|
|
7
|
-
import { Platform } from 'react-native';
|
|
8
|
-
import notifee from '@notifee/react-native';
|
|
9
7
|
|
|
10
8
|
/**
|
|
11
9
|
* The props for the Reject Call button.
|
|
@@ -52,9 +50,6 @@ export const RejectCallButton = ({
|
|
|
52
50
|
if (callingState === CallingState.LEFT) {
|
|
53
51
|
return;
|
|
54
52
|
}
|
|
55
|
-
if (Platform.OS === 'android' && call?.cid) {
|
|
56
|
-
notifee.cancelDisplayedNotification(call?.cid);
|
|
57
|
-
}
|
|
58
53
|
await call?.leave({ reject: true });
|
|
59
54
|
if (onRejectCallHandler) {
|
|
60
55
|
onRejectCallHandler();
|
|
@@ -1,11 +1,23 @@
|
|
|
1
1
|
import { useEffect } from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
getCallKeepLib,
|
|
4
|
+
getVoipPushNotificationLib,
|
|
5
|
+
} from '../../utils/push/libs';
|
|
3
6
|
|
|
4
|
-
import { Platform } from 'react-native';
|
|
7
|
+
import { AppState, Platform } from 'react-native';
|
|
5
8
|
import { StreamVideoRN } from '../../utils';
|
|
6
9
|
import { useStreamVideoClient } from '@stream-io/video-react-bindings';
|
|
7
|
-
import { voipPushNotificationCallCId$ } from '../../utils/push/rxSubjects';
|
|
8
10
|
import { setPushLogoutCallback } from '../../utils/internal/pushLogoutCallback';
|
|
11
|
+
import { NativeModules } from 'react-native';
|
|
12
|
+
import {
|
|
13
|
+
canAddPushWSSubscriptionsRef,
|
|
14
|
+
shouldCallBeEnded,
|
|
15
|
+
} from '../../utils/push/utils';
|
|
16
|
+
import {
|
|
17
|
+
pushUnsubscriptionCallbacks$,
|
|
18
|
+
voipPushNotificationCallCId$,
|
|
19
|
+
} from '../../utils/push/rxSubjects';
|
|
20
|
+
import { RxUtils } from '@stream-io/video-client';
|
|
9
21
|
|
|
10
22
|
let lastVoipToken: string | undefined = '';
|
|
11
23
|
|
|
@@ -91,7 +103,7 @@ export const useIosVoipPushEventsSetupEffect = () => {
|
|
|
91
103
|
}, []);
|
|
92
104
|
};
|
|
93
105
|
|
|
94
|
-
const onNotificationReceived = (notification: any) => {
|
|
106
|
+
const onNotificationReceived = async (notification: any) => {
|
|
95
107
|
/* --- Example payload ---
|
|
96
108
|
{
|
|
97
109
|
"aps": {
|
|
@@ -121,9 +133,65 @@ const onNotificationReceived = (notification: any) => {
|
|
|
121
133
|
return;
|
|
122
134
|
}
|
|
123
135
|
const call_cid = notification?.stream?.call_cid;
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
136
|
+
const pushConfig = StreamVideoRN.getConfig().push;
|
|
137
|
+
if (!call_cid || Platform.OS !== 'ios' || !pushConfig) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
const client = await pushConfig.createStreamVideoClient();
|
|
141
|
+
if (!client) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
const callFromPush = await client.onRingingCall(call_cid);
|
|
145
|
+
let uuid = '';
|
|
146
|
+
try {
|
|
147
|
+
uuid = await NativeModules?.StreamVideoReactNative?.getIncomingCallUUid(
|
|
148
|
+
call_cid,
|
|
149
|
+
);
|
|
150
|
+
} catch (error) {
|
|
151
|
+
console.log('Error in getting call uuid', error);
|
|
152
|
+
}
|
|
153
|
+
if (!uuid) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
const created_by_id = notification?.stream?.created_by_id;
|
|
157
|
+
const receiver_id = notification?.stream?.receiver_id;
|
|
158
|
+
function closeCallIfNecessary() {
|
|
159
|
+
const { mustEndCall, callkeepReason } = shouldCallBeEnded(
|
|
160
|
+
callFromPush,
|
|
161
|
+
created_by_id,
|
|
162
|
+
receiver_id,
|
|
163
|
+
);
|
|
164
|
+
if (mustEndCall) {
|
|
165
|
+
const callkeep = getCallKeepLib();
|
|
166
|
+
callkeep.reportEndCallWithUUID(uuid, callkeepReason);
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
const closed = closeCallIfNecessary();
|
|
172
|
+
const canListenToWS = () =>
|
|
173
|
+
canAddPushWSSubscriptionsRef.current && AppState.currentState !== 'active';
|
|
174
|
+
if (!closed && canListenToWS()) {
|
|
175
|
+
const unsubscribe = callFromPush.on('all', () => {
|
|
176
|
+
if (!canListenToWS()) {
|
|
177
|
+
unsubscribe();
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
const _closed = closeCallIfNecessary();
|
|
181
|
+
if (_closed) {
|
|
182
|
+
unsubscribe();
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
const unsubscriptionCallbacks =
|
|
186
|
+
RxUtils.getCurrentValue(pushUnsubscriptionCallbacks$) ?? [];
|
|
187
|
+
pushUnsubscriptionCallbacks$.next([
|
|
188
|
+
...unsubscriptionCallbacks,
|
|
189
|
+
unsubscribe,
|
|
190
|
+
]);
|
|
128
191
|
}
|
|
192
|
+
// send the info to this subject, it is listened by callkeep events
|
|
193
|
+
// callkeep events will then accept/reject the call
|
|
194
|
+
voipPushNotificationCallCId$.next(call_cid);
|
|
195
|
+
const voipPushNotification = getVoipPushNotificationLib();
|
|
196
|
+
voipPushNotification.onVoipNotificationCompleted(uuid);
|
|
129
197
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useCallStateHooks } from '@stream-io/video-react-bindings';
|
|
1
|
+
import { useCall, useCallStateHooks } from '@stream-io/video-react-bindings';
|
|
2
2
|
import { useEffect, useRef } from 'react';
|
|
3
3
|
import notifee, { AuthorizationStatus } from '@notifee/react-native';
|
|
4
4
|
import { StreamVideoRN } from '../utils';
|
|
@@ -63,6 +63,7 @@ let isSetForegroundServiceRan = false;
|
|
|
63
63
|
* This hook is used to keep the call alive in the background for Android.
|
|
64
64
|
* It starts a foreground service to keep the call alive as soon as the call is joined
|
|
65
65
|
* and stops the foreground Service when the call is left.
|
|
66
|
+
* Additonally: also responsible for cancelling any notifee displayed notification when the call has transitioned out of ringing
|
|
66
67
|
*/
|
|
67
68
|
export const useAndroidKeepCallAliveEffect = () => {
|
|
68
69
|
if (!isSetForegroundServiceRan && Platform.OS === 'android') {
|
|
@@ -71,6 +72,7 @@ export const useAndroidKeepCallAliveEffect = () => {
|
|
|
71
72
|
}
|
|
72
73
|
const foregroundServiceStartedRef = useRef(false);
|
|
73
74
|
|
|
75
|
+
const activeCallCid = useCall()?.cid;
|
|
74
76
|
const { useCallCallingState } = useCallStateHooks();
|
|
75
77
|
const callingState = useCallCallingState();
|
|
76
78
|
|
|
@@ -90,16 +92,25 @@ export const useAndroidKeepCallAliveEffect = () => {
|
|
|
90
92
|
foregroundServiceStartedRef.current = true;
|
|
91
93
|
};
|
|
92
94
|
run();
|
|
95
|
+
} else if (callingState === CallingState.RINGING) {
|
|
96
|
+
// cancel any notifee displayed notification when the call has transitioned out of ringing
|
|
93
97
|
return () => {
|
|
94
|
-
if (
|
|
95
|
-
|
|
98
|
+
if (activeCallCid) {
|
|
99
|
+
notifee.cancelDisplayedNotification(activeCallCid);
|
|
96
100
|
}
|
|
97
|
-
// stop foreground service when the call is not active
|
|
98
|
-
stopForegroundService();
|
|
99
|
-
foregroundServiceStartedRef.current = false;
|
|
100
101
|
};
|
|
102
|
+
} else if (
|
|
103
|
+
callingState === CallingState.IDLE ||
|
|
104
|
+
callingState === CallingState.LEFT
|
|
105
|
+
) {
|
|
106
|
+
if (!foregroundServiceStartedRef.current) {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
// stop foreground service when the call is not active
|
|
110
|
+
stopForegroundService();
|
|
111
|
+
foregroundServiceStartedRef.current = false;
|
|
101
112
|
}
|
|
102
|
-
}, [callingState]);
|
|
113
|
+
}, [activeCallCid, callingState]);
|
|
103
114
|
|
|
104
115
|
useEffect(() => {
|
|
105
116
|
return () => {
|
|
@@ -38,17 +38,17 @@ export const MediaStreamManagement = ({
|
|
|
38
38
|
// Memoization is needed to avoid unnecessary useEffect triggers
|
|
39
39
|
const targetResolutionSetting = useMemo<TargetResolution | undefined>(() => {
|
|
40
40
|
if (
|
|
41
|
-
settings?.video.target_resolution?.width
|
|
42
|
-
settings?.video.target_resolution?.height
|
|
43
|
-
settings?.video.target_resolution?.bitrate
|
|
41
|
+
settings?.video.target_resolution?.width !== undefined ||
|
|
42
|
+
settings?.video.target_resolution?.height !== undefined ||
|
|
43
|
+
settings?.video.target_resolution?.bitrate !== undefined
|
|
44
44
|
) {
|
|
45
|
-
return
|
|
45
|
+
return {
|
|
46
|
+
width: settings?.video.target_resolution.width,
|
|
47
|
+
height: settings?.video.target_resolution.height,
|
|
48
|
+
bitrate: settings?.video.target_resolution.bitrate,
|
|
49
|
+
};
|
|
46
50
|
}
|
|
47
|
-
return
|
|
48
|
-
width: settings?.video.target_resolution.width,
|
|
49
|
-
height: settings?.video.target_resolution.height,
|
|
50
|
-
bitrate: settings?.video.target_resolution.bitrate,
|
|
51
|
-
};
|
|
51
|
+
return undefined;
|
|
52
52
|
}, [
|
|
53
53
|
settings?.video.target_resolution.width,
|
|
54
54
|
settings?.video.target_resolution.height,
|
|
@@ -82,25 +82,15 @@ export const MediaStreamManagement = ({
|
|
|
82
82
|
* This is the object is used to track the initial audio/video enablement
|
|
83
83
|
* Uses backend settings or the Prop to set initial audio/video enabled
|
|
84
84
|
* Backend settings is applied only if the prop was undefined -- meaning user did not provide any value
|
|
85
|
+
* Memoization is needed to avoid unnecessary useEffect triggers
|
|
85
86
|
*/
|
|
86
87
|
const { initialAudioEnabled, initialVideoEnabled } =
|
|
87
88
|
useMemo<MediaDevicesInitialState>(() => {
|
|
88
|
-
let _initialAudioEnabled: boolean | undefined;
|
|
89
|
-
let _initialVideoEnabled: boolean | undefined;
|
|
90
|
-
if (propInitialAudioEnabled !== undefined) {
|
|
91
|
-
_initialAudioEnabled = propInitialAudioEnabled;
|
|
92
|
-
} else if (settings?.audio.mic_default_on !== undefined) {
|
|
93
|
-
_initialAudioEnabled = settings?.audio.mic_default_on;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
if (propInitialVideoEnabled !== undefined) {
|
|
97
|
-
_initialVideoEnabled = propInitialVideoEnabled;
|
|
98
|
-
} else if (settings?.video.camera_default_on !== undefined) {
|
|
99
|
-
_initialVideoEnabled = settings?.video.camera_default_on;
|
|
100
|
-
}
|
|
101
89
|
return {
|
|
102
|
-
initialAudioEnabled:
|
|
103
|
-
|
|
90
|
+
initialAudioEnabled:
|
|
91
|
+
propInitialAudioEnabled ?? settings?.audio.mic_default_on,
|
|
92
|
+
initialVideoEnabled:
|
|
93
|
+
propInitialVideoEnabled ?? settings?.video.camera_default_on,
|
|
104
94
|
};
|
|
105
95
|
}, [
|
|
106
96
|
settings?.audio.mic_default_on,
|
|
@@ -112,28 +102,29 @@ export const MediaStreamManagement = ({
|
|
|
112
102
|
// The main logic
|
|
113
103
|
// Enable or Disable the audio/video stream based on the initial state
|
|
114
104
|
useEffect(() => {
|
|
115
|
-
if (
|
|
105
|
+
if (!call) {
|
|
116
106
|
return;
|
|
117
107
|
}
|
|
118
|
-
|
|
119
|
-
|
|
108
|
+
|
|
109
|
+
if (initialAudioEnabled !== undefined) {
|
|
110
|
+
if (initialAudioEnabled) {
|
|
111
|
+
call.microphone.enable();
|
|
112
|
+
} else {
|
|
113
|
+
call.microphone.disable();
|
|
114
|
+
}
|
|
120
115
|
}
|
|
116
|
+
|
|
121
117
|
// we wait until we receive the resolution settings from the backend
|
|
122
|
-
if (!
|
|
118
|
+
if (!targetResolutionSetting) {
|
|
123
119
|
return;
|
|
124
120
|
}
|
|
125
|
-
|
|
126
|
-
if (initialAudioEnabled) {
|
|
127
|
-
call.microphone.enable();
|
|
128
|
-
} else {
|
|
129
|
-
call.microphone.disable();
|
|
130
|
-
}
|
|
131
|
-
|
|
132
121
|
call.camera.selectTargetResolution(targetResolutionSetting);
|
|
133
|
-
if (initialVideoEnabled) {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
122
|
+
if (initialVideoEnabled !== undefined) {
|
|
123
|
+
if (initialVideoEnabled) {
|
|
124
|
+
call.camera.enable();
|
|
125
|
+
} else {
|
|
126
|
+
call.camera.disable();
|
|
127
|
+
}
|
|
137
128
|
}
|
|
138
129
|
}, [call, initialAudioEnabled, initialVideoEnabled, targetResolutionSetting]);
|
|
139
130
|
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import { StreamCallProvider } from '@stream-io/video-react-bindings';
|
|
2
|
-
import React, { PropsWithChildren } from 'react';
|
|
2
|
+
import React, { PropsWithChildren, useEffect } from 'react';
|
|
3
3
|
import { Call } from '@stream-io/video-client';
|
|
4
|
-
import { useAndroidKeepCallAliveEffect } from '../hooks';
|
|
5
4
|
import { useIosCallkeepWithCallingStateEffect } from '../hooks/push/useIosCallkeepWithCallingStateEffect';
|
|
6
5
|
import {
|
|
7
6
|
MediaDevicesInitialState,
|
|
8
7
|
MediaStreamManagement,
|
|
9
8
|
} from './MediaStreamManagement';
|
|
9
|
+
import {
|
|
10
|
+
canAddPushWSSubscriptionsRef,
|
|
11
|
+
clearPushWSEventSubscriptions,
|
|
12
|
+
} from '../utils/push/utils';
|
|
13
|
+
import { useAndroidKeepCallAliveEffect } from '../hooks/useAndroidKeepCallAliveEffect';
|
|
10
14
|
|
|
11
15
|
export type StreamCallProps = {
|
|
12
16
|
/**
|
|
@@ -15,7 +19,9 @@ export type StreamCallProps = {
|
|
|
15
19
|
*/
|
|
16
20
|
call: Call;
|
|
17
21
|
/**
|
|
18
|
-
*
|
|
22
|
+
* Optionally provide the initial status of the media devices(audio/video) to the `MediaStreamManagement`.
|
|
23
|
+
* Note: It will override the default state of the media devices set from the server side.
|
|
24
|
+
* It is used to control the initial state of the media devices(audio/video) in a custom lobby component.
|
|
19
25
|
*/
|
|
20
26
|
mediaDeviceInitialState?: MediaDevicesInitialState;
|
|
21
27
|
};
|
|
@@ -36,6 +42,7 @@ export const StreamCall = ({
|
|
|
36
42
|
<MediaStreamManagement {...mediaDeviceInitialState}>
|
|
37
43
|
<AndroidKeepCallAlive />
|
|
38
44
|
<IosInformCallkeepCallEnd />
|
|
45
|
+
<ClearPushWSSubscriptions />
|
|
39
46
|
{children}
|
|
40
47
|
</MediaStreamManagement>
|
|
41
48
|
</StreamCallProvider>
|
|
@@ -59,3 +66,18 @@ const IosInformCallkeepCallEnd = () => {
|
|
|
59
66
|
useIosCallkeepWithCallingStateEffect();
|
|
60
67
|
return null;
|
|
61
68
|
};
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* This is a renderless component to clear all push ws event subscriptions
|
|
72
|
+
* and set whether push ws subscriptions can be added or not.
|
|
73
|
+
*/
|
|
74
|
+
const ClearPushWSSubscriptions = () => {
|
|
75
|
+
useEffect(() => {
|
|
76
|
+
clearPushWSEventSubscriptions();
|
|
77
|
+
canAddPushWSSubscriptionsRef.current = false;
|
|
78
|
+
return () => {
|
|
79
|
+
canAddPushWSSubscriptionsRef.current = true;
|
|
80
|
+
};
|
|
81
|
+
}, []);
|
|
82
|
+
return null;
|
|
83
|
+
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import notifee, { EventType, Event } from '@notifee/react-native';
|
|
2
2
|
import { FirebaseMessagingTypes } from '@react-native-firebase/messaging';
|
|
3
|
-
import { StreamVideoClient } from '@stream-io/video-client';
|
|
4
|
-
import { Platform } from 'react-native';
|
|
3
|
+
import { Call, RxUtils, StreamVideoClient } from '@stream-io/video-client';
|
|
4
|
+
import { AppState, Platform } from 'react-native';
|
|
5
5
|
import type {
|
|
6
6
|
NonRingingPushEvent,
|
|
7
7
|
StreamVideoConfig,
|
|
@@ -16,8 +16,14 @@ import {
|
|
|
16
16
|
pushRejectedIncomingCallCId$,
|
|
17
17
|
pushTappedIncomingCallCId$,
|
|
18
18
|
pushNonRingingCallData$,
|
|
19
|
+
pushUnsubscriptionCallbacks$,
|
|
19
20
|
} from './rxSubjects';
|
|
20
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
canAddPushWSSubscriptionsRef,
|
|
23
|
+
clearPushWSEventSubscriptions,
|
|
24
|
+
processCallFromPushInBackground,
|
|
25
|
+
shouldCallBeEnded,
|
|
26
|
+
} from './utils';
|
|
21
27
|
import { setPushLogoutCallback } from '../internal/pushLogoutCallback';
|
|
22
28
|
import { getAndroidDefaultRingtoneUrl } from '../getAndroidDefaultRingtoneUrl';
|
|
23
29
|
|
|
@@ -153,6 +159,54 @@ const firebaseMessagingOnMessageHandler = async (
|
|
|
153
159
|
}
|
|
154
160
|
|
|
155
161
|
if (data.type === 'call.ring') {
|
|
162
|
+
const call_cid = data.call_cid;
|
|
163
|
+
const created_by_id = data.created_by_id;
|
|
164
|
+
const receiver_id = data.receiver_id;
|
|
165
|
+
|
|
166
|
+
function shouldCallBeClosed(callToCheck: Call) {
|
|
167
|
+
const { mustEndCall } = shouldCallBeEnded(
|
|
168
|
+
callToCheck,
|
|
169
|
+
created_by_id,
|
|
170
|
+
receiver_id,
|
|
171
|
+
);
|
|
172
|
+
return mustEndCall;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const canListenToWS = () =>
|
|
176
|
+
canAddPushWSSubscriptionsRef.current &&
|
|
177
|
+
AppState.currentState !== 'active';
|
|
178
|
+
const asForegroundService = canListenToWS();
|
|
179
|
+
|
|
180
|
+
if (asForegroundService) {
|
|
181
|
+
// Listen to call events from WS through fg service
|
|
182
|
+
// note: this will replace the current empty fg service runner
|
|
183
|
+
notifee.registerForegroundService(() => {
|
|
184
|
+
return new Promise(async () => {
|
|
185
|
+
const client = await pushConfig.createStreamVideoClient();
|
|
186
|
+
if (!client) {
|
|
187
|
+
notifee.stopForegroundService();
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
const callFromPush = await client.onRingingCall(call_cid);
|
|
191
|
+
if (shouldCallBeClosed(callFromPush)) {
|
|
192
|
+
notifee.stopForegroundService();
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
const unsubscribe = callFromPush.on('all', () => {
|
|
196
|
+
if (!canListenToWS() || shouldCallBeClosed(callFromPush)) {
|
|
197
|
+
unsubscribe();
|
|
198
|
+
notifee.stopForegroundService();
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
const unsubscriptionCallbacks =
|
|
202
|
+
RxUtils.getCurrentValue(pushUnsubscriptionCallbacks$) ?? [];
|
|
203
|
+
pushUnsubscriptionCallbacks$.next([
|
|
204
|
+
...unsubscriptionCallbacks,
|
|
205
|
+
unsubscribe,
|
|
206
|
+
]);
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
}
|
|
156
210
|
const incomingCallChannel = pushConfig.android.incomingCallChannel;
|
|
157
211
|
const incomingCallNotificationTextGetters =
|
|
158
212
|
pushConfig.android.incomingCallNotificationTextGetters;
|
|
@@ -172,12 +226,13 @@ const firebaseMessagingOnMessageHandler = async (
|
|
|
172
226
|
|
|
173
227
|
const channelId = incomingCallChannel.id;
|
|
174
228
|
await notifee.displayNotification({
|
|
175
|
-
id:
|
|
229
|
+
id: call_cid,
|
|
176
230
|
title: getTitle(createdUserName),
|
|
177
231
|
body: getBody(createdUserName),
|
|
178
232
|
data,
|
|
179
233
|
android: {
|
|
180
234
|
channelId,
|
|
235
|
+
asForegroundService,
|
|
181
236
|
sound: incomingCallChannel.sound,
|
|
182
237
|
vibrationPattern: incomingCallChannel.vibrationPattern,
|
|
183
238
|
pressAction: {
|
|
@@ -202,6 +257,23 @@ const firebaseMessagingOnMessageHandler = async (
|
|
|
202
257
|
timeoutAfter: 60000, // 60 seconds, after which the notification will be dismissed automatically
|
|
203
258
|
},
|
|
204
259
|
});
|
|
260
|
+
|
|
261
|
+
// check if call needs to be closed if accept/decline event was done
|
|
262
|
+
// before the notification was shown
|
|
263
|
+
const client = await pushConfig.createStreamVideoClient();
|
|
264
|
+
if (!client) {
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
const callFromPush = await client.onRingingCall(call_cid);
|
|
268
|
+
|
|
269
|
+
if (shouldCallBeClosed(callFromPush)) {
|
|
270
|
+
if (asForegroundService) {
|
|
271
|
+
notifee.stopForegroundService();
|
|
272
|
+
} else {
|
|
273
|
+
notifee.cancelDisplayedNotification(call_cid);
|
|
274
|
+
}
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
205
277
|
} else {
|
|
206
278
|
// the other types are call.live_started and call.notification
|
|
207
279
|
const callChannel = pushConfig.android.callChannel;
|
|
@@ -272,6 +344,12 @@ const onNotifeeEvent = async (event: Event, pushConfig: PushConfig) => {
|
|
|
272
344
|
const mustAccept =
|
|
273
345
|
type === EventType.ACTION_PRESS &&
|
|
274
346
|
pressAction.id === ACCEPT_CALL_ACTION_ID;
|
|
347
|
+
|
|
348
|
+
if (mustAccept || mustDecline || type === EventType.ACTION_PRESS) {
|
|
349
|
+
clearPushWSEventSubscriptions();
|
|
350
|
+
notifee.stopForegroundService();
|
|
351
|
+
}
|
|
352
|
+
|
|
275
353
|
if (mustAccept) {
|
|
276
354
|
pushAcceptedIncomingCallCId$.next(call_cid);
|
|
277
355
|
// NOTE: accept will be handled by the app with rxjs observers as the app will go to foreground always
|
package/src/utils/push/ios.ts
CHANGED
|
@@ -10,7 +10,10 @@ import {
|
|
|
10
10
|
voipCallkeepAcceptedCallOnNativeDialerMap$,
|
|
11
11
|
pushNonRingingCallData$,
|
|
12
12
|
} from './rxSubjects';
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
clearPushWSEventSubscriptions,
|
|
15
|
+
processCallFromPushInBackground,
|
|
16
|
+
} from './utils';
|
|
14
17
|
import { getExpoNotificationsLib, getPushNotificationIosLib } from './libs';
|
|
15
18
|
import { StreamVideoClient } from '@stream-io/video-client';
|
|
16
19
|
import { setPushLogoutCallback } from '../internal/pushLogoutCallback';
|
|
@@ -47,6 +50,7 @@ export const iosCallkeepAcceptCall = (
|
|
|
47
50
|
if (!shouldProcessCallFromCallkeep(call_cid, callUUIDFromCallkeep)) {
|
|
48
51
|
return;
|
|
49
52
|
}
|
|
53
|
+
clearPushWSEventSubscriptions();
|
|
50
54
|
// to call end callkeep later if ended in app and not through callkeep
|
|
51
55
|
voipCallkeepAcceptedCallOnNativeDialerMap$.next({
|
|
52
56
|
uuid: callUUIDFromCallkeep,
|
|
@@ -66,6 +70,7 @@ export const iosCallkeepRejectCall = async (
|
|
|
66
70
|
if (!shouldProcessCallFromCallkeep(call_cid, callUUIDFromCallkeep)) {
|
|
67
71
|
return;
|
|
68
72
|
}
|
|
73
|
+
clearPushWSEventSubscriptions();
|
|
69
74
|
// no need to keep these references anymore
|
|
70
75
|
voipCallkeepAcceptedCallOnNativeDialerMap$.next(undefined);
|
|
71
76
|
voipCallkeepCallOnForegroundMap$.next(undefined);
|