@stream-io/video-react-native-sdk 1.30.5 → 1.31.1-beta.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/android/src/main/AndroidManifest.xml +8 -1
- package/android/src/main/AndroidManifestNew.xml +11 -0
- package/android/src/main/java/com/streamvideo/reactnative/StreamVideoReactNativeModule.kt +127 -5
- package/android/src/main/java/com/streamvideo/reactnative/audio/utils/WebRtcAudioUtils.kt +70 -6
- package/android/src/main/java/com/streamvideo/reactnative/callmanager/StreamInCallManagerModule.kt +6 -4
- package/android/src/main/java/com/streamvideo/reactnative/keepalive/KeepAliveNotification.kt +83 -0
- package/android/src/main/java/com/streamvideo/reactnative/keepalive/StreamCallKeepAliveHeadlessService.kt +149 -0
- package/android/src/main/java/com/streamvideo/reactnative/screenshare/ScreenAudioCapture.kt +111 -0
- package/dist/commonjs/components/Call/CallControls/ScreenShareToggleButton.js +3 -2
- package/dist/commonjs/components/Call/CallControls/ScreenShareToggleButton.js.map +1 -1
- package/dist/commonjs/hooks/index.js +11 -0
- package/dist/commonjs/hooks/index.js.map +1 -1
- package/dist/commonjs/hooks/push/index.js +0 -2
- package/dist/commonjs/hooks/push/index.js.map +1 -1
- package/dist/commonjs/hooks/push/useCallingExpWithCallingStateEffect.js +144 -0
- package/dist/commonjs/hooks/push/useCallingExpWithCallingStateEffect.js.map +1 -0
- package/dist/commonjs/hooks/push/useIosVoipPushEventsSetupEffect.js +18 -31
- package/dist/commonjs/hooks/push/useIosVoipPushEventsSetupEffect.js.map +1 -1
- package/dist/commonjs/hooks/useAndroidKeepCallAliveEffect.js +64 -97
- package/dist/commonjs/hooks/useAndroidKeepCallAliveEffect.js.map +1 -1
- package/dist/commonjs/hooks/useScreenShareAudioMixing.js +126 -0
- package/dist/commonjs/hooks/useScreenShareAudioMixing.js.map +1 -0
- package/dist/commonjs/hooks/useScreenShareButton.js +57 -3
- package/dist/commonjs/hooks/useScreenShareButton.js.map +1 -1
- package/dist/commonjs/index.js +1 -0
- package/dist/commonjs/index.js.map +1 -1
- package/dist/commonjs/modules/ScreenShareAudioManager.js +54 -0
- package/dist/commonjs/modules/ScreenShareAudioManager.js.map +1 -0
- package/dist/commonjs/modules/call-manager/CallManager.js +26 -0
- package/dist/commonjs/modules/call-manager/CallManager.js.map +1 -1
- package/dist/commonjs/providers/StreamCall/index.js +16 -6
- package/dist/commonjs/providers/StreamCall/index.js.map +1 -1
- package/dist/commonjs/utils/StreamVideoRN/index.js +35 -21
- package/dist/commonjs/utils/StreamVideoRN/index.js.map +1 -1
- package/dist/commonjs/utils/internal/callingx/audioSessionPromise.js +68 -0
- package/dist/commonjs/utils/internal/callingx/audioSessionPromise.js.map +1 -0
- package/dist/commonjs/utils/internal/callingx/callingx.js +150 -0
- package/dist/commonjs/utils/internal/callingx/callingx.js.map +1 -0
- package/dist/commonjs/utils/internal/registerSDKGlobals.js +53 -3
- package/dist/commonjs/utils/internal/registerSDKGlobals.js.map +1 -1
- package/dist/commonjs/utils/keepCallAliveHeadlessTask.js +48 -0
- package/dist/commonjs/utils/keepCallAliveHeadlessTask.js.map +1 -0
- package/dist/commonjs/utils/push/android.js +135 -202
- package/dist/commonjs/utils/push/android.js.map +1 -1
- package/dist/commonjs/utils/push/internal/ios.js +17 -34
- package/dist/commonjs/utils/push/internal/ios.js.map +1 -1
- package/dist/commonjs/utils/push/internal/rxSubjects.js +1 -45
- package/dist/commonjs/utils/push/internal/rxSubjects.js.map +1 -1
- package/dist/commonjs/utils/push/internal/utils.js +71 -53
- package/dist/commonjs/utils/push/internal/utils.js.map +1 -1
- package/dist/commonjs/utils/push/ios.js.map +1 -1
- package/dist/commonjs/utils/push/libs/callingx.js +75 -0
- package/dist/commonjs/utils/push/libs/callingx.js.map +1 -0
- package/dist/commonjs/utils/push/libs/index.js +8 -19
- package/dist/commonjs/utils/push/libs/index.js.map +1 -1
- package/dist/commonjs/utils/push/libs/notifee/index.js +0 -19
- package/dist/commonjs/utils/push/libs/notifee/index.js.map +1 -1
- package/dist/commonjs/utils/push/setupCallingExpEvents.js +105 -0
- package/dist/commonjs/utils/push/setupCallingExpEvents.js.map +1 -0
- package/dist/commonjs/utils/push/setupIosVoipPushEvents.js +7 -6
- package/dist/commonjs/utils/push/setupIosVoipPushEvents.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/ScreenShareToggleButton.js +3 -2
- package/dist/module/components/Call/CallControls/ScreenShareToggleButton.js.map +1 -1
- package/dist/module/hooks/index.js +1 -0
- package/dist/module/hooks/index.js.map +1 -1
- package/dist/module/hooks/push/index.js +0 -2
- package/dist/module/hooks/push/index.js.map +1 -1
- package/dist/module/hooks/push/useCallingExpWithCallingStateEffect.js +137 -0
- package/dist/module/hooks/push/useCallingExpWithCallingStateEffect.js.map +1 -0
- package/dist/module/hooks/push/useIosVoipPushEventsSetupEffect.js +18 -31
- package/dist/module/hooks/push/useIosVoipPushEventsSetupEffect.js.map +1 -1
- package/dist/module/hooks/useAndroidKeepCallAliveEffect.js +66 -99
- package/dist/module/hooks/useAndroidKeepCallAliveEffect.js.map +1 -1
- package/dist/module/hooks/useScreenShareAudioMixing.js +119 -0
- package/dist/module/hooks/useScreenShareAudioMixing.js.map +1 -0
- package/dist/module/hooks/useScreenShareButton.js +57 -3
- package/dist/module/hooks/useScreenShareButton.js.map +1 -1
- package/dist/module/index.js +1 -0
- package/dist/module/index.js.map +1 -1
- package/dist/module/modules/ScreenShareAudioManager.js +47 -0
- package/dist/module/modules/ScreenShareAudioManager.js.map +1 -0
- package/dist/module/modules/call-manager/CallManager.js +26 -0
- package/dist/module/modules/call-manager/CallManager.js.map +1 -1
- package/dist/module/providers/StreamCall/index.js +16 -6
- package/dist/module/providers/StreamCall/index.js.map +1 -1
- package/dist/module/utils/StreamVideoRN/index.js +35 -21
- package/dist/module/utils/StreamVideoRN/index.js.map +1 -1
- package/dist/module/utils/internal/callingx/audioSessionPromise.js +61 -0
- package/dist/module/utils/internal/callingx/audioSessionPromise.js.map +1 -0
- package/dist/module/utils/internal/callingx/callingx.js +140 -0
- package/dist/module/utils/internal/callingx/callingx.js.map +1 -0
- package/dist/module/utils/internal/registerSDKGlobals.js +53 -3
- package/dist/module/utils/internal/registerSDKGlobals.js.map +1 -1
- package/dist/module/utils/keepCallAliveHeadlessTask.js +42 -0
- package/dist/module/utils/keepCallAliveHeadlessTask.js.map +1 -0
- package/dist/module/utils/push/android.js +137 -204
- package/dist/module/utils/push/android.js.map +1 -1
- package/dist/module/utils/push/internal/ios.js +17 -34
- package/dist/module/utils/push/internal/ios.js.map +1 -1
- package/dist/module/utils/push/internal/rxSubjects.js +0 -44
- package/dist/module/utils/push/internal/rxSubjects.js.map +1 -1
- package/dist/module/utils/push/internal/utils.js +67 -50
- package/dist/module/utils/push/internal/utils.js.map +1 -1
- package/dist/module/utils/push/ios.js.map +1 -1
- package/dist/module/utils/push/libs/callingx.js +67 -0
- package/dist/module/utils/push/libs/callingx.js.map +1 -0
- package/dist/module/utils/push/libs/index.js +1 -2
- package/dist/module/utils/push/libs/index.js.map +1 -1
- package/dist/module/utils/push/libs/notifee/index.js +0 -18
- package/dist/module/utils/push/libs/notifee/index.js.map +1 -1
- package/dist/module/utils/push/setupCallingExpEvents.js +99 -0
- package/dist/module/utils/push/setupCallingExpEvents.js.map +1 -0
- package/dist/module/utils/push/setupIosVoipPushEvents.js +7 -6
- package/dist/module/utils/push/setupIosVoipPushEvents.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/ScreenShareToggleButton.d.ts +6 -1
- package/dist/typescript/components/Call/CallControls/ScreenShareToggleButton.d.ts.map +1 -1
- package/dist/typescript/hooks/index.d.ts +1 -0
- package/dist/typescript/hooks/index.d.ts.map +1 -1
- package/dist/typescript/hooks/push/index.d.ts.map +1 -1
- package/dist/typescript/hooks/push/useCallingExpWithCallingStateEffect.d.ts +5 -0
- package/dist/typescript/hooks/push/useCallingExpWithCallingStateEffect.d.ts.map +1 -0
- package/dist/typescript/hooks/push/useIosVoipPushEventsSetupEffect.d.ts.map +1 -1
- package/dist/typescript/hooks/useAndroidKeepCallAliveEffect.d.ts.map +1 -1
- package/dist/typescript/hooks/useScreenShareAudioMixing.d.ts +14 -0
- package/dist/typescript/hooks/useScreenShareAudioMixing.d.ts.map +1 -0
- package/dist/typescript/hooks/useScreenShareButton.d.ts +39 -2
- package/dist/typescript/hooks/useScreenShareButton.d.ts.map +1 -1
- package/dist/typescript/index.d.ts +1 -0
- package/dist/typescript/index.d.ts.map +1 -1
- package/dist/typescript/modules/ScreenShareAudioManager.d.ts +28 -0
- package/dist/typescript/modules/ScreenShareAudioManager.d.ts.map +1 -0
- package/dist/typescript/modules/call-manager/CallManager.d.ts +5 -0
- package/dist/typescript/modules/call-manager/CallManager.d.ts.map +1 -1
- package/dist/typescript/providers/StreamCall/index.d.ts.map +1 -1
- package/dist/typescript/utils/StreamVideoRN/index.d.ts +22 -2
- package/dist/typescript/utils/StreamVideoRN/index.d.ts.map +1 -1
- package/dist/typescript/utils/StreamVideoRN/types.d.ts +59 -25
- package/dist/typescript/utils/StreamVideoRN/types.d.ts.map +1 -1
- package/dist/typescript/utils/internal/callingx/audioSessionPromise.d.ts +16 -0
- package/dist/typescript/utils/internal/callingx/audioSessionPromise.d.ts.map +1 -0
- package/dist/typescript/utils/internal/callingx/callingx.d.ts +18 -0
- package/dist/typescript/utils/internal/callingx/callingx.d.ts.map +1 -0
- package/dist/typescript/utils/internal/registerSDKGlobals.d.ts.map +1 -1
- package/dist/typescript/utils/keepCallAliveHeadlessTask.d.ts +10 -0
- package/dist/typescript/utils/keepCallAliveHeadlessTask.d.ts.map +1 -0
- package/dist/typescript/utils/push/android.d.ts +1 -2
- package/dist/typescript/utils/push/android.d.ts.map +1 -1
- package/dist/typescript/utils/push/internal/ios.d.ts.map +1 -1
- package/dist/typescript/utils/push/internal/rxSubjects.d.ts +0 -33
- package/dist/typescript/utils/push/internal/rxSubjects.d.ts.map +1 -1
- package/dist/typescript/utils/push/internal/utils.d.ts +14 -8
- package/dist/typescript/utils/push/internal/utils.d.ts.map +1 -1
- package/dist/typescript/utils/push/ios.d.ts +1 -2
- package/dist/typescript/utils/push/ios.d.ts.map +1 -1
- package/dist/typescript/utils/push/libs/callingx.d.ts +9 -0
- package/dist/typescript/utils/push/libs/callingx.d.ts.map +1 -0
- package/dist/typescript/utils/push/libs/firebaseMessaging/index.d.ts +16 -2
- package/dist/typescript/utils/push/libs/firebaseMessaging/index.d.ts.map +1 -1
- package/dist/typescript/utils/push/libs/index.d.ts +1 -2
- package/dist/typescript/utils/push/libs/index.d.ts.map +1 -1
- package/dist/typescript/utils/push/libs/notifee/index.d.ts +0 -1
- package/dist/typescript/utils/push/libs/notifee/index.d.ts.map +1 -1
- package/dist/typescript/utils/push/setupCallingExpEvents.d.ts +8 -0
- package/dist/typescript/utils/push/setupCallingExpEvents.d.ts.map +1 -0
- package/dist/typescript/utils/push/setupIosVoipPushEvents.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/withAndroidManifest.js +1 -33
- package/expo-config-plugin/dist/withAndroidPermissions.js +2 -7
- package/expo-config-plugin/dist/withAppDelegate.js +19 -197
- package/expo-config-plugin/dist/withMainActivity.js +1 -1
- package/expo-config-plugin/dist/withiOSInfoPlist.js +2 -3
- package/ios/StreamInCallManager.m +2 -0
- package/ios/StreamInCallManager.swift +19 -7
- package/ios/StreamVideoReactNative.h +7 -4
- package/ios/StreamVideoReactNative.m +282 -86
- package/package.json +13 -18
- package/src/components/Call/CallControls/ScreenShareToggleButton.tsx +11 -1
- package/src/hooks/index.ts +1 -0
- package/src/hooks/push/index.ts +0 -2
- package/src/hooks/push/useCallingExpWithCallingStateEffect.ts +189 -0
- package/src/hooks/push/useIosVoipPushEventsSetupEffect.ts +21 -34
- package/src/hooks/useAndroidKeepCallAliveEffect.ts +94 -120
- package/src/hooks/useScreenShareAudioMixing.ts +130 -0
- package/src/hooks/useScreenShareButton.ts +87 -2
- package/src/index.ts +1 -0
- package/src/modules/ScreenShareAudioManager.ts +49 -0
- package/src/modules/call-manager/CallManager.ts +36 -0
- package/src/modules/call-manager/native-module.d.ts +7 -0
- package/src/providers/StreamCall/index.tsx +17 -6
- package/src/utils/StreamVideoRN/index.ts +42 -30
- package/src/utils/StreamVideoRN/types.ts +61 -25
- package/src/utils/internal/callingx/audioSessionPromise.ts +65 -0
- package/src/utils/internal/callingx/callingx.ts +194 -0
- package/src/utils/internal/registerSDKGlobals.ts +52 -4
- package/src/utils/keepCallAliveHeadlessTask.ts +54 -0
- package/src/utils/push/android.ts +198 -311
- package/src/utils/push/internal/ios.ts +28 -44
- package/src/utils/push/internal/rxSubjects.ts +0 -61
- package/src/utils/push/internal/utils.ts +108 -64
- package/src/utils/push/ios.ts +1 -6
- package/src/utils/push/libs/callingx.ts +89 -0
- package/src/utils/push/libs/index.ts +1 -2
- package/src/utils/push/libs/notifee/index.ts +0 -27
- package/src/utils/push/setupCallingExpEvents.ts +135 -0
- package/src/utils/push/setupIosVoipPushEvents.ts +11 -7
- package/src/version.ts +1 -1
- package/android/src/main/java/com/streamvideo/reactnative/util/CallAliveServiceChecker.kt +0 -95
- package/dist/commonjs/hooks/push/useIosCallkeepWithCallingStateEffect.js +0 -160
- package/dist/commonjs/hooks/push/useIosCallkeepWithCallingStateEffect.js.map +0 -1
- package/dist/commonjs/hooks/push/useProcessPushCallEffect.js +0 -67
- package/dist/commonjs/hooks/push/useProcessPushCallEffect.js.map +0 -1
- package/dist/commonjs/utils/push/libs/callkeep.js +0 -17
- package/dist/commonjs/utils/push/libs/callkeep.js.map +0 -1
- package/dist/commonjs/utils/push/libs/voipPushNotification.js +0 -17
- package/dist/commonjs/utils/push/libs/voipPushNotification.js.map +0 -1
- package/dist/commonjs/utils/push/setupIosCallKeepEvents.js +0 -205
- package/dist/commonjs/utils/push/setupIosCallKeepEvents.js.map +0 -1
- package/dist/module/hooks/push/useIosCallkeepWithCallingStateEffect.js +0 -153
- package/dist/module/hooks/push/useIosCallkeepWithCallingStateEffect.js.map +0 -1
- package/dist/module/hooks/push/useProcessPushCallEffect.js +0 -60
- package/dist/module/hooks/push/useProcessPushCallEffect.js.map +0 -1
- package/dist/module/utils/push/libs/callkeep.js +0 -11
- package/dist/module/utils/push/libs/callkeep.js.map +0 -1
- package/dist/module/utils/push/libs/voipPushNotification.js +0 -11
- package/dist/module/utils/push/libs/voipPushNotification.js.map +0 -1
- package/dist/module/utils/push/setupIosCallKeepEvents.js +0 -199
- package/dist/module/utils/push/setupIosCallKeepEvents.js.map +0 -1
- package/dist/typescript/hooks/push/useIosCallkeepWithCallingStateEffect.d.ts +0 -5
- package/dist/typescript/hooks/push/useIosCallkeepWithCallingStateEffect.d.ts.map +0 -1
- package/dist/typescript/hooks/push/useProcessPushCallEffect.d.ts +0 -8
- package/dist/typescript/hooks/push/useProcessPushCallEffect.d.ts.map +0 -1
- package/dist/typescript/utils/push/libs/callkeep.d.ts +0 -3
- package/dist/typescript/utils/push/libs/callkeep.d.ts.map +0 -1
- package/dist/typescript/utils/push/libs/voipPushNotification.d.ts +0 -3
- package/dist/typescript/utils/push/libs/voipPushNotification.d.ts.map +0 -1
- package/dist/typescript/utils/push/setupIosCallKeepEvents.d.ts +0 -6
- package/dist/typescript/utils/push/setupIosCallKeepEvents.d.ts.map +0 -1
- package/src/hooks/push/useIosCallkeepWithCallingStateEffect.ts +0 -235
- package/src/hooks/push/useProcessPushCallEffect.ts +0 -108
- package/src/utils/push/libs/callkeep.ts +0 -16
- package/src/utils/push/libs/voipPushNotification.ts +0 -17
- package/src/utils/push/setupIosCallKeepEvents.ts +0 -252
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver).
|
|
4
4
|
|
|
5
|
+
## [1.31.0](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-native-sdk-1.30.5...@stream-io/video-react-native-sdk-1.31.0) (2026-03-31)
|
|
6
|
+
|
|
7
|
+
### Dependency Updates
|
|
8
|
+
|
|
9
|
+
- `@stream-io/noise-cancellation-react-native` updated to version `0.6.0`
|
|
10
|
+
- `@stream-io/video-filters-react-native` updated to version `0.11.0`
|
|
11
|
+
|
|
12
|
+
### Features
|
|
13
|
+
|
|
14
|
+
- screen share audio ([#2157](https://github.com/GetStream/stream-video-js/issues/2157)) ([ba3b9d8](https://github.com/GetStream/stream-video-js/commit/ba3b9d8c2168d7c1cd66050524a5dc0a0f7e3e6e))
|
|
15
|
+
|
|
5
16
|
## [1.30.5](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-native-sdk-1.30.4...@stream-io/video-react-native-sdk-1.30.5) (2026-03-27)
|
|
6
17
|
|
|
7
18
|
### Dependency Updates
|
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
package="com.streamvideo.reactnative">
|
|
3
3
|
|
|
4
4
|
<uses-permission android:name="android.permission.INTERNET" />
|
|
5
|
-
<uses-permission android:name="android.permission.DEVICE_POWER" />
|
|
6
5
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
|
6
|
+
|
|
7
|
+
<application>
|
|
8
|
+
<service
|
|
9
|
+
android:name="com.streamvideo.reactnative.keepalive.StreamCallKeepAliveHeadlessService"
|
|
10
|
+
android:exported="false"
|
|
11
|
+
android:stopWithTask="true"
|
|
12
|
+
android:foregroundServiceType="mediaPlayback|camera|microphone" />
|
|
13
|
+
</application>
|
|
7
14
|
</manifest>
|
|
@@ -1,2 +1,13 @@
|
|
|
1
1
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
|
2
|
+
|
|
3
|
+
<uses-permission android:name="android.permission.INTERNET" />
|
|
4
|
+
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
|
5
|
+
|
|
6
|
+
<application>
|
|
7
|
+
<service
|
|
8
|
+
android:name="com.streamvideo.reactnative.keepalive.StreamCallKeepAliveHeadlessService"
|
|
9
|
+
android:exported="false"
|
|
10
|
+
android:stopWithTask="true"
|
|
11
|
+
android:foregroundServiceType="mediaPlayback|camera|microphone" />
|
|
12
|
+
</application>
|
|
2
13
|
</manifest>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
package com.streamvideo.reactnative
|
|
2
2
|
|
|
3
|
+
import android.app.Activity
|
|
3
4
|
import android.content.BroadcastReceiver
|
|
4
5
|
import android.content.Context
|
|
5
6
|
import android.content.Intent
|
|
@@ -9,12 +10,14 @@ import android.graphics.Bitmap
|
|
|
9
10
|
import android.media.AudioAttributes
|
|
10
11
|
import android.media.AudioFormat
|
|
11
12
|
import android.media.AudioTrack
|
|
13
|
+
import android.media.projection.MediaProjectionManager
|
|
12
14
|
import android.net.Uri
|
|
13
15
|
import android.os.BatteryManager
|
|
14
16
|
import android.os.Build
|
|
15
17
|
import android.os.PowerManager
|
|
16
18
|
import android.util.Base64
|
|
17
19
|
import android.util.Log
|
|
20
|
+
import androidx.core.content.ContextCompat
|
|
18
21
|
import com.facebook.react.bridge.Arguments
|
|
19
22
|
import com.facebook.react.bridge.Promise
|
|
20
23
|
import com.facebook.react.bridge.ReactApplicationContext
|
|
@@ -23,8 +26,10 @@ import com.facebook.react.bridge.ReactMethod
|
|
|
23
26
|
import com.facebook.react.bridge.WritableMap
|
|
24
27
|
import com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter
|
|
25
28
|
import com.oney.WebRTCModule.WebRTCModule
|
|
29
|
+
import com.oney.WebRTCModule.WebRTCModuleOptions
|
|
30
|
+
import com.streamvideo.reactnative.screenshare.ScreenAudioCapture
|
|
31
|
+
import com.streamvideo.reactnative.keepalive.StreamCallKeepAliveHeadlessService
|
|
26
32
|
import com.streamvideo.reactnative.util.CallAlivePermissionsHelper
|
|
27
|
-
import com.streamvideo.reactnative.util.CallAliveServiceChecker
|
|
28
33
|
import com.streamvideo.reactnative.util.PiPHelper
|
|
29
34
|
import com.streamvideo.reactnative.util.RingtoneUtil
|
|
30
35
|
import com.streamvideo.reactnative.util.YuvFrame
|
|
@@ -52,6 +57,9 @@ class StreamVideoReactNativeModule(reactContext: ReactApplicationContext) :
|
|
|
52
57
|
private var busyToneAudioTrack: AudioTrack? = null
|
|
53
58
|
private var busyToneJob: Job? = null
|
|
54
59
|
|
|
60
|
+
// Screen share audio mixing
|
|
61
|
+
private var screenAudioCapture: ScreenAudioCapture? = null
|
|
62
|
+
|
|
55
63
|
private var thermalStatusListener: PowerManager.OnThermalStatusChangedListener? = null
|
|
56
64
|
|
|
57
65
|
private var batteryChargingStateReceiver = object : BroadcastReceiver() {
|
|
@@ -115,11 +123,47 @@ class StreamVideoReactNativeModule(reactContext: ReactApplicationContext) :
|
|
|
115
123
|
promise.resolve(false)
|
|
116
124
|
return
|
|
117
125
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
126
|
+
// Service is declared in the SDK's own AndroidManifest and merged by default.
|
|
127
|
+
// Permissions are expected to be provided by the app (or via Expo config plugin).
|
|
128
|
+
promise.resolve(true)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
@ReactMethod
|
|
133
|
+
fun startKeepCallAliveService(
|
|
134
|
+
callCid: String,
|
|
135
|
+
channelId: String,
|
|
136
|
+
channelName: String,
|
|
137
|
+
title: String,
|
|
138
|
+
body: String,
|
|
139
|
+
smallIconName: String?,
|
|
140
|
+
promise: Promise
|
|
141
|
+
) {
|
|
142
|
+
try {
|
|
143
|
+
val intent = StreamCallKeepAliveHeadlessService.buildStartIntent(
|
|
144
|
+
reactApplicationContext,
|
|
145
|
+
callCid,
|
|
146
|
+
channelId,
|
|
147
|
+
channelName,
|
|
148
|
+
title,
|
|
149
|
+
body,
|
|
150
|
+
smallIconName
|
|
151
|
+
)
|
|
152
|
+
ContextCompat.startForegroundService(reactApplicationContext, intent)
|
|
122
153
|
promise.resolve(true)
|
|
154
|
+
} catch (e: Exception) {
|
|
155
|
+
promise.reject(NAME, "Failed to start keep call alive foreground service", e)
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
@ReactMethod
|
|
160
|
+
fun stopKeepCallAliveService(promise: Promise) {
|
|
161
|
+
try {
|
|
162
|
+
val intent = StreamCallKeepAliveHeadlessService.buildStopIntent(reactApplicationContext)
|
|
163
|
+
val stopped = reactApplicationContext.stopService(intent)
|
|
164
|
+
promise.resolve(stopped)
|
|
165
|
+
} catch (e: Exception) {
|
|
166
|
+
promise.reject(NAME, "Failed to stop keep call alive foreground service", e)
|
|
123
167
|
}
|
|
124
168
|
}
|
|
125
169
|
|
|
@@ -148,6 +192,7 @@ class StreamVideoReactNativeModule(reactContext: ReactApplicationContext) :
|
|
|
148
192
|
reactApplicationContext.unregisterReceiver(batteryChargingStateReceiver)
|
|
149
193
|
stopThermalStatusUpdates()
|
|
150
194
|
stopBusyToneInternal() // Clean up busy tone on invalidate
|
|
195
|
+
stopScreenShareAudioMixingInternal() // Clean up screen share audio on invalidate
|
|
151
196
|
super.invalidate()
|
|
152
197
|
}
|
|
153
198
|
|
|
@@ -484,6 +529,83 @@ class StreamVideoReactNativeModule(reactContext: ReactApplicationContext) :
|
|
|
484
529
|
return ShortArray(totalSamples)
|
|
485
530
|
}
|
|
486
531
|
|
|
532
|
+
@ReactMethod
|
|
533
|
+
fun startScreenShareAudioMixing(promise: Promise) {
|
|
534
|
+
try {
|
|
535
|
+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
|
536
|
+
promise.reject("API_LEVEL", "Screen audio capture requires Android 10 (API 29)+")
|
|
537
|
+
return
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
if (screenAudioCapture != null) {
|
|
541
|
+
Log.w(NAME, "Screen share audio mixing is already active")
|
|
542
|
+
promise.resolve(null)
|
|
543
|
+
return
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
val module = reactApplicationContext.getNativeModule(WebRTCModule::class.java)!!
|
|
547
|
+
|
|
548
|
+
// Get the MediaProjection permission result Intent from WebRTC
|
|
549
|
+
val permissionIntent = module.userMediaImpl?.mediaProjectionPermissionResultData
|
|
550
|
+
if (permissionIntent == null) {
|
|
551
|
+
promise.reject("NO_PROJECTION", "No MediaProjection permission available. Start screen sharing first.")
|
|
552
|
+
return
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// Create a MediaProjection for audio capture
|
|
556
|
+
val mediaProjectionManager = reactApplicationContext.getSystemService(
|
|
557
|
+
Context.MEDIA_PROJECTION_SERVICE
|
|
558
|
+
) as MediaProjectionManager
|
|
559
|
+
val mediaProjection = mediaProjectionManager.getMediaProjection(
|
|
560
|
+
Activity.RESULT_OK, permissionIntent
|
|
561
|
+
)
|
|
562
|
+
if (mediaProjection == null) {
|
|
563
|
+
promise.reject("PROJECTION_ERROR", "Failed to create MediaProjection for audio capture")
|
|
564
|
+
return
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
screenAudioCapture = ScreenAudioCapture(mediaProjection).also { it.start() }
|
|
568
|
+
|
|
569
|
+
// Register the screen audio bytes provider so the AudioBufferCallback
|
|
570
|
+
// in WebRTCModule mixes screen audio into the mic buffer.
|
|
571
|
+
WebRTCModuleOptions.getInstance().screenAudioBytesProvider =
|
|
572
|
+
WebRTCModuleOptions.ScreenAudioBytesProvider { bytesRequested ->
|
|
573
|
+
screenAudioCapture?.getScreenAudioBytes(bytesRequested)
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
Log.d(NAME, "Screen share audio mixing started")
|
|
577
|
+
promise.resolve(null)
|
|
578
|
+
} catch (e: Exception) {
|
|
579
|
+
Log.e(NAME, "Error starting screen share audio mixing: ${e.message}")
|
|
580
|
+
promise.reject("ERROR", e.message, e)
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
@ReactMethod
|
|
585
|
+
fun stopScreenShareAudioMixing(promise: Promise) {
|
|
586
|
+
try {
|
|
587
|
+
stopScreenShareAudioMixingInternal()
|
|
588
|
+
promise.resolve(null)
|
|
589
|
+
} catch (e: Exception) {
|
|
590
|
+
Log.e(NAME, "Error stopping screen share audio mixing: ${e.message}")
|
|
591
|
+
promise.reject("ERROR", e.message, e)
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
private fun stopScreenShareAudioMixingInternal() {
|
|
596
|
+
try {
|
|
597
|
+
// Clear the provider so the AudioBufferCallback stops mixing
|
|
598
|
+
WebRTCModuleOptions.getInstance().screenAudioBytesProvider = null
|
|
599
|
+
|
|
600
|
+
screenAudioCapture?.stop()
|
|
601
|
+
screenAudioCapture = null
|
|
602
|
+
|
|
603
|
+
Log.d(NAME, "Screen share audio mixing stopped")
|
|
604
|
+
} catch (e: Exception) {
|
|
605
|
+
Log.e(NAME, "Error in stopScreenShareAudioMixingInternal: ${e.message}")
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
|
|
487
609
|
companion object {
|
|
488
610
|
private const val NAME = "StreamVideoReactNative"
|
|
489
611
|
private const val SAMPLE_RATE = 22050
|
|
@@ -55,14 +55,78 @@ object WebRtcAudioUtils {
|
|
|
55
55
|
* what might be the root cause.
|
|
56
56
|
*/
|
|
57
57
|
fun logAudioState(tag: String, reactContext: ReactContext) {
|
|
58
|
+
Log.d(tag, getAudioStateLog(reactContext))
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Returns a string containing information about the current audio state.
|
|
63
|
+
* Similar to logAudioState but returns the information instead of logging it.
|
|
64
|
+
*/
|
|
65
|
+
fun getAudioStateLog(reactContext: ReactContext): String {
|
|
66
|
+
val audioManager = reactContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
|
67
|
+
val sb = StringBuilder()
|
|
68
|
+
|
|
69
|
+
// Volume control stream
|
|
58
70
|
reactContext.currentActivity?.let {
|
|
59
|
-
|
|
71
|
+
sb.appendLine("volumeControlStream: ${streamTypeToString(it.volumeControlStream)}")
|
|
60
72
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
73
|
+
|
|
74
|
+
// Device info
|
|
75
|
+
sb.appendLine("Android SDK: ${Build.VERSION.SDK_INT}, Release: ${Build.VERSION.RELEASE}, Brand: ${Build.BRAND}, Device: ${Build.DEVICE}, Id: ${Build.ID}, Hardware: ${Build.HARDWARE}, Manufacturer: ${Build.MANUFACTURER}, Model: ${Build.MODEL}, Product: ${Build.PRODUCT}")
|
|
76
|
+
|
|
77
|
+
// Basic audio state
|
|
78
|
+
sb.appendLine("Audio State: audio mode: ${modeToString(audioManager.mode)}, has mic: ${hasMicrophone(reactContext)}, mic muted: ${audioManager.isMicrophoneMute}, music active: ${audioManager.isMusicActive}, speakerphone: ${audioManager.isSpeakerphoneOn}, BT SCO: ${audioManager.isBluetoothScoOn}")
|
|
79
|
+
|
|
80
|
+
// Volume info
|
|
81
|
+
val fixedVolume = audioManager.isVolumeFixed
|
|
82
|
+
sb.appendLine(" fixed volume=$fixedVolume")
|
|
83
|
+
if (!fixedVolume) {
|
|
84
|
+
val streams = intArrayOf(
|
|
85
|
+
AudioManager.STREAM_VOICE_CALL,
|
|
86
|
+
AudioManager.STREAM_MUSIC,
|
|
87
|
+
AudioManager.STREAM_RING,
|
|
88
|
+
AudioManager.STREAM_ALARM,
|
|
89
|
+
AudioManager.STREAM_NOTIFICATION,
|
|
90
|
+
AudioManager.STREAM_SYSTEM
|
|
91
|
+
)
|
|
92
|
+
for (stream in streams) {
|
|
93
|
+
val info = StringBuilder()
|
|
94
|
+
info.append(" ${streamTypeToString(stream)}: ")
|
|
95
|
+
info.append("volume=${audioManager.getStreamVolume(stream)}")
|
|
96
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
|
97
|
+
info.append(", min=${audioManager.getStreamMinVolume(stream)}")
|
|
98
|
+
}
|
|
99
|
+
info.append(", max=${audioManager.getStreamMaxVolume(stream)}")
|
|
100
|
+
info.append(", muted=${audioManager.isStreamMute(stream)}")
|
|
101
|
+
sb.appendLine(info.toString())
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Audio devices
|
|
106
|
+
val inputDevices = audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)
|
|
107
|
+
val outputDevices = audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS)
|
|
108
|
+
val devices = inputDevices + outputDevices
|
|
109
|
+
if (devices.isNotEmpty()) {
|
|
110
|
+
sb.appendLine("Audio Devices:")
|
|
111
|
+
for (device in devices) {
|
|
112
|
+
val info = StringBuilder()
|
|
113
|
+
info.append(" ${deviceTypeToString(device.type)}")
|
|
114
|
+
info.append(if (device.isSource) "(in): " else "(out): ")
|
|
115
|
+
if (device.channelCounts.isNotEmpty()) {
|
|
116
|
+
info.append("channels=${device.channelCounts.contentToString()}, ")
|
|
117
|
+
}
|
|
118
|
+
if (device.encodings.isNotEmpty()) {
|
|
119
|
+
info.append("encodings=${device.encodings.contentToString()}, ")
|
|
120
|
+
}
|
|
121
|
+
if (device.sampleRates.isNotEmpty()) {
|
|
122
|
+
info.append("sample rates=${device.sampleRates.contentToString()}, ")
|
|
123
|
+
}
|
|
124
|
+
info.append("id=${device.id}")
|
|
125
|
+
sb.appendLine(info.toString())
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return sb.toString()
|
|
66
130
|
}
|
|
67
131
|
|
|
68
132
|
/** Converts AudioDeviceInfo types to local string representation. */
|
package/android/src/main/java/com/streamvideo/reactnative/callmanager/StreamInCallManagerModule.kt
CHANGED
|
@@ -168,10 +168,12 @@ class StreamInCallManagerModule(reactContext: ReactApplicationContext) :
|
|
|
168
168
|
|
|
169
169
|
@ReactMethod
|
|
170
170
|
fun logAudioState() {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
171
|
+
Log.d(TAG, getAudioStateLog())
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
@ReactMethod(isBlockingSynchronousMethod = true)
|
|
175
|
+
fun getAudioStateLog(): String {
|
|
176
|
+
return WebRtcAudioUtils.getAudioStateLog(reactApplicationContext)
|
|
175
177
|
}
|
|
176
178
|
|
|
177
179
|
@Suppress("unused")
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
package com.streamvideo.reactnative.keepalive
|
|
2
|
+
|
|
3
|
+
import android.app.Notification
|
|
4
|
+
import android.app.NotificationChannel
|
|
5
|
+
import android.app.NotificationManager
|
|
6
|
+
import android.app.PendingIntent
|
|
7
|
+
import android.content.Context
|
|
8
|
+
import android.content.Intent
|
|
9
|
+
import android.content.pm.PackageManager
|
|
10
|
+
import android.os.Build
|
|
11
|
+
import androidx.core.app.NotificationCompat
|
|
12
|
+
|
|
13
|
+
internal object KeepAliveNotification {
|
|
14
|
+
private const val DEFAULT_CHANNEL_DESCRIPTION = "Stream call keep-alive"
|
|
15
|
+
|
|
16
|
+
fun ensureChannel(
|
|
17
|
+
context: Context,
|
|
18
|
+
channelId: String,
|
|
19
|
+
channelName: String
|
|
20
|
+
) {
|
|
21
|
+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return
|
|
22
|
+
val manager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
|
23
|
+
val existing = manager.getNotificationChannel(channelId)
|
|
24
|
+
if (existing != null) return
|
|
25
|
+
|
|
26
|
+
val channel = NotificationChannel(
|
|
27
|
+
channelId,
|
|
28
|
+
channelName,
|
|
29
|
+
NotificationManager.IMPORTANCE_LOW
|
|
30
|
+
).apply {
|
|
31
|
+
description = DEFAULT_CHANNEL_DESCRIPTION
|
|
32
|
+
setShowBadge(false)
|
|
33
|
+
}
|
|
34
|
+
manager.createNotificationChannel(channel)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
fun buildOngoingNotification(
|
|
38
|
+
context: Context,
|
|
39
|
+
channelId: String,
|
|
40
|
+
title: String,
|
|
41
|
+
body: String,
|
|
42
|
+
smallIconName: String?
|
|
43
|
+
): Notification {
|
|
44
|
+
val launchIntent = context.packageManager.getLaunchIntentForPackage(context.packageName)
|
|
45
|
+
val pendingIntentFlags =
|
|
46
|
+
PendingIntent.FLAG_UPDATE_CURRENT or
|
|
47
|
+
PendingIntent.FLAG_IMMUTABLE
|
|
48
|
+
val contentIntent = if (launchIntent != null) {
|
|
49
|
+
PendingIntent.getActivity(context, 0, launchIntent, pendingIntentFlags)
|
|
50
|
+
} else {
|
|
51
|
+
// Fallback: empty intent to avoid crash if launch activity is missing for some reason
|
|
52
|
+
PendingIntent.getActivity(context, 0, Intent(), pendingIntentFlags)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
val iconResId = resolveSmallIconResId(context, smallIconName)
|
|
56
|
+
return NotificationCompat.Builder(context, channelId)
|
|
57
|
+
.setContentTitle(title)
|
|
58
|
+
.setContentText(body)
|
|
59
|
+
.setOngoing(true)
|
|
60
|
+
.setOnlyAlertOnce(true)
|
|
61
|
+
.setCategory(NotificationCompat.CATEGORY_CALL)
|
|
62
|
+
.setContentIntent(contentIntent)
|
|
63
|
+
.setSmallIcon(iconResId)
|
|
64
|
+
.build()
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
private fun resolveSmallIconResId(context: Context, smallIconName: String?): Int {
|
|
68
|
+
val resources = context.resources
|
|
69
|
+
val packageName = context.packageName
|
|
70
|
+
if (!smallIconName.isNullOrBlank()) {
|
|
71
|
+
val id = resources.getIdentifier(smallIconName, "drawable", packageName)
|
|
72
|
+
if (id != 0) return id
|
|
73
|
+
}
|
|
74
|
+
// Default to the app icon
|
|
75
|
+
return try {
|
|
76
|
+
val appInfo = context.packageManager.getApplicationInfo(packageName, 0)
|
|
77
|
+
appInfo.icon
|
|
78
|
+
} catch (_: PackageManager.NameNotFoundException) {
|
|
79
|
+
android.R.drawable.ic_dialog_info
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
package com.streamvideo.reactnative.keepalive
|
|
2
|
+
|
|
3
|
+
import android.Manifest
|
|
4
|
+
import android.content.Intent
|
|
5
|
+
import android.content.pm.PackageManager
|
|
6
|
+
import android.content.pm.ServiceInfo
|
|
7
|
+
import android.os.Build
|
|
8
|
+
import android.util.Log
|
|
9
|
+
import androidx.annotation.RequiresApi
|
|
10
|
+
import androidx.core.app.ServiceCompat
|
|
11
|
+
import androidx.core.content.ContextCompat
|
|
12
|
+
import com.facebook.react.HeadlessJsTaskService
|
|
13
|
+
import com.facebook.react.bridge.Arguments
|
|
14
|
+
import com.facebook.react.jstasks.HeadlessJsTaskConfig
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Foreground service that runs a React Native HeadlessJS task to keep a call alive.
|
|
18
|
+
*
|
|
19
|
+
*/
|
|
20
|
+
class StreamCallKeepAliveHeadlessService : HeadlessJsTaskService() {
|
|
21
|
+
|
|
22
|
+
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
|
23
|
+
val safeIntent = intent ?: Intent()
|
|
24
|
+
val channelId = safeIntent.getStringExtra(EXTRA_CHANNEL_ID) ?: DEFAULT_CHANNEL_ID
|
|
25
|
+
val channelName = safeIntent.getStringExtra(EXTRA_CHANNEL_NAME) ?: DEFAULT_CHANNEL_NAME
|
|
26
|
+
val title = safeIntent.getStringExtra(EXTRA_TITLE) ?: DEFAULT_TITLE
|
|
27
|
+
val body = safeIntent.getStringExtra(EXTRA_BODY) ?: DEFAULT_BODY
|
|
28
|
+
val smallIconName = safeIntent.getStringExtra(EXTRA_SMALL_ICON_NAME)
|
|
29
|
+
|
|
30
|
+
KeepAliveNotification.ensureChannel(this, channelId, channelName)
|
|
31
|
+
val notification = KeepAliveNotification.buildOngoingNotification(
|
|
32
|
+
context = this,
|
|
33
|
+
channelId = channelId,
|
|
34
|
+
title = title,
|
|
35
|
+
body = body,
|
|
36
|
+
smallIconName = smallIconName
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
startForegroundCompat(notification)
|
|
40
|
+
|
|
41
|
+
// Ensure HeadlessJS task is started
|
|
42
|
+
return super.onStartCommand(safeIntent, flags, startId)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
override fun getTaskConfig(intent: Intent?): HeadlessJsTaskConfig? {
|
|
46
|
+
val callCid = intent?.getStringExtra(EXTRA_CALL_CID) ?: return null
|
|
47
|
+
val data = Arguments.createMap().apply {
|
|
48
|
+
putString("callCid", callCid)
|
|
49
|
+
}
|
|
50
|
+
// We intentionally allow long-running work (the JS task can return a never-resolving Promise).
|
|
51
|
+
return HeadlessJsTaskConfig(
|
|
52
|
+
TASK_NAME,
|
|
53
|
+
data,
|
|
54
|
+
0, // timeout (0 = no timeout)
|
|
55
|
+
true // allowedInForeground
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
override fun onDestroy() {
|
|
60
|
+
super.onDestroy()
|
|
61
|
+
stopForeground(STOP_FOREGROUND_REMOVE)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
@RequiresApi(Build.VERSION_CODES.R)
|
|
65
|
+
private fun computeForegroundServiceTypes(): Int {
|
|
66
|
+
var types = ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK
|
|
67
|
+
|
|
68
|
+
val hasCameraPermission =
|
|
69
|
+
ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED
|
|
70
|
+
if (hasCameraPermission) {
|
|
71
|
+
types = types or ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
val hasMicrophonePermission =
|
|
75
|
+
ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED
|
|
76
|
+
if (hasMicrophonePermission) {
|
|
77
|
+
types = types or ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return types
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
private fun startForegroundCompat(notification: android.app.Notification) {
|
|
84
|
+
try {
|
|
85
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
|
86
|
+
val types =
|
|
87
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) computeForegroundServiceTypes()
|
|
88
|
+
else ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK
|
|
89
|
+
startForeground(NOTIFICATION_ID, notification, types)
|
|
90
|
+
} else {
|
|
91
|
+
startForeground(NOTIFICATION_ID, notification)
|
|
92
|
+
}
|
|
93
|
+
} catch (e: Exception) {
|
|
94
|
+
// Avoid crashing the app if the system rejects starting a foreground service (e.g.
|
|
95
|
+
// background start restrictions, invalid notification/channel, or permission issues).
|
|
96
|
+
Log.e(
|
|
97
|
+
TAG,
|
|
98
|
+
"startForegroundCompat: Failed to start foreground service: ${e.message}",
|
|
99
|
+
e
|
|
100
|
+
)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
companion object {
|
|
105
|
+
private const val TAG = "StreamCallKeepAliveHeadlessService"
|
|
106
|
+
|
|
107
|
+
const val TASK_NAME = "StreamVideoKeepCallAlive"
|
|
108
|
+
|
|
109
|
+
const val EXTRA_CALL_CID = "callCid"
|
|
110
|
+
const val EXTRA_CHANNEL_ID = "channelId"
|
|
111
|
+
const val EXTRA_CHANNEL_NAME = "channelName"
|
|
112
|
+
const val EXTRA_TITLE = "title"
|
|
113
|
+
const val EXTRA_BODY = "body"
|
|
114
|
+
const val EXTRA_SMALL_ICON_NAME = "smallIconName"
|
|
115
|
+
|
|
116
|
+
private const val NOTIFICATION_ID = 6061
|
|
117
|
+
|
|
118
|
+
private const val DEFAULT_CHANNEL_ID = "stream_call_foreground_service"
|
|
119
|
+
private const val DEFAULT_CHANNEL_NAME = "Call in progress"
|
|
120
|
+
private const val DEFAULT_TITLE = "Call in progress"
|
|
121
|
+
private const val DEFAULT_BODY = "Tap to return to the call"
|
|
122
|
+
|
|
123
|
+
fun buildStartIntent(
|
|
124
|
+
context: android.content.Context,
|
|
125
|
+
callCid: String,
|
|
126
|
+
channelId: String,
|
|
127
|
+
channelName: String,
|
|
128
|
+
title: String,
|
|
129
|
+
body: String,
|
|
130
|
+
smallIconName: String?
|
|
131
|
+
): Intent {
|
|
132
|
+
return Intent(context, StreamCallKeepAliveHeadlessService::class.java).apply {
|
|
133
|
+
putExtra(EXTRA_CALL_CID, callCid)
|
|
134
|
+
putExtra(EXTRA_CHANNEL_ID, channelId)
|
|
135
|
+
putExtra(EXTRA_CHANNEL_NAME, channelName)
|
|
136
|
+
putExtra(EXTRA_TITLE, title)
|
|
137
|
+
putExtra(EXTRA_BODY, body)
|
|
138
|
+
if (!smallIconName.isNullOrBlank()) {
|
|
139
|
+
putExtra(EXTRA_SMALL_ICON_NAME, smallIconName)
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
fun buildStopIntent(context: android.content.Context): Intent {
|
|
145
|
+
return Intent(context, StreamCallKeepAliveHeadlessService::class.java)
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
package com.streamvideo.reactnative.screenshare
|
|
2
|
+
|
|
3
|
+
import android.annotation.SuppressLint
|
|
4
|
+
import android.media.AudioAttributes
|
|
5
|
+
import android.media.AudioFormat
|
|
6
|
+
import android.media.AudioPlaybackCaptureConfiguration
|
|
7
|
+
import android.media.AudioRecord
|
|
8
|
+
import android.media.projection.MediaProjection
|
|
9
|
+
import android.os.Build
|
|
10
|
+
import android.util.Log
|
|
11
|
+
import androidx.annotation.RequiresApi
|
|
12
|
+
import java.nio.ByteBuffer
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Captures system media audio using [AudioPlaybackCaptureConfiguration].
|
|
16
|
+
*
|
|
17
|
+
* Uses the given [MediaProjection] to set up an [AudioRecord] that captures
|
|
18
|
+
* audio from media playback, games, and other apps (USAGE_MEDIA, USAGE_GAME,
|
|
19
|
+
* USAGE_UNKNOWN) but not notifications, alarms, or system sounds.
|
|
20
|
+
*
|
|
21
|
+
* Audio is captured in a pull-based manner via [getScreenAudioBytes], which
|
|
22
|
+
* reads exactly the requested number of bytes using [AudioRecord.READ_BLOCKING].
|
|
23
|
+
* This is designed to be called from the WebRTC audio processing thread.
|
|
24
|
+
*
|
|
25
|
+
* Format: 48kHz, mono, PCM 16-bit (matching WebRTC's audio pipeline).
|
|
26
|
+
*
|
|
27
|
+
* Requires Android 10 (API 29+).
|
|
28
|
+
*/
|
|
29
|
+
@RequiresApi(Build.VERSION_CODES.Q)
|
|
30
|
+
class ScreenAudioCapture(private val mediaProjection: MediaProjection) {
|
|
31
|
+
|
|
32
|
+
private var audioRecord: AudioRecord? = null
|
|
33
|
+
private var screenAudioBuffer: ByteBuffer? = null
|
|
34
|
+
|
|
35
|
+
companion object {
|
|
36
|
+
private const val TAG = "ScreenAudioCapture"
|
|
37
|
+
const val SAMPLE_RATE = 48000
|
|
38
|
+
private const val CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_MONO
|
|
39
|
+
private const val AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
@SuppressLint("MissingPermission")
|
|
43
|
+
fun start() {
|
|
44
|
+
val playbackConfig = AudioPlaybackCaptureConfiguration.Builder(mediaProjection)
|
|
45
|
+
.addMatchingUsage(AudioAttributes.USAGE_MEDIA)
|
|
46
|
+
.addMatchingUsage(AudioAttributes.USAGE_GAME)
|
|
47
|
+
.addMatchingUsage(AudioAttributes.USAGE_UNKNOWN)
|
|
48
|
+
.build()
|
|
49
|
+
|
|
50
|
+
val audioFormat = AudioFormat.Builder()
|
|
51
|
+
.setSampleRate(SAMPLE_RATE)
|
|
52
|
+
.setChannelMask(CHANNEL_CONFIG)
|
|
53
|
+
.setEncoding(AUDIO_FORMAT)
|
|
54
|
+
.build()
|
|
55
|
+
|
|
56
|
+
audioRecord = AudioRecord.Builder()
|
|
57
|
+
.setAudioFormat(audioFormat)
|
|
58
|
+
.setAudioPlaybackCaptureConfig(playbackConfig)
|
|
59
|
+
.build()
|
|
60
|
+
|
|
61
|
+
if (audioRecord?.state != AudioRecord.STATE_INITIALIZED) {
|
|
62
|
+
Log.e(TAG, "AudioRecord failed to initialize")
|
|
63
|
+
audioRecord?.release()
|
|
64
|
+
audioRecord = null
|
|
65
|
+
return
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
audioRecord?.startRecording()
|
|
69
|
+
Log.d(TAG, "Screen audio capture started")
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Pull-based read: returns a [ByteBuffer] containing exactly [bytesRequested] bytes
|
|
74
|
+
* of captured screen audio.
|
|
75
|
+
*
|
|
76
|
+
* Called from the WebRTC audio processing thread. Uses [AudioRecord.READ_BLOCKING]
|
|
77
|
+
* so it will block until the requested bytes are available.
|
|
78
|
+
*
|
|
79
|
+
* @return A [ByteBuffer] with screen audio data, or `null` if capture is not active.
|
|
80
|
+
*/
|
|
81
|
+
fun getScreenAudioBytes(bytesRequested: Int): ByteBuffer? {
|
|
82
|
+
val record = audioRecord ?: return null
|
|
83
|
+
if (bytesRequested <= 0) return null
|
|
84
|
+
|
|
85
|
+
val buffer = screenAudioBuffer?.takeIf { it.capacity() >= bytesRequested }
|
|
86
|
+
?: ByteBuffer.allocateDirect(bytesRequested).also { screenAudioBuffer = it }
|
|
87
|
+
|
|
88
|
+
buffer.clear()
|
|
89
|
+
buffer.limit(bytesRequested)
|
|
90
|
+
|
|
91
|
+
val bytesRead = record.read(buffer, bytesRequested, AudioRecord.READ_BLOCKING)
|
|
92
|
+
if (bytesRead > 0) {
|
|
93
|
+
buffer.position(0)
|
|
94
|
+
buffer.limit(bytesRead)
|
|
95
|
+
return buffer
|
|
96
|
+
}
|
|
97
|
+
return null
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
fun stop() {
|
|
101
|
+
try {
|
|
102
|
+
audioRecord?.stop()
|
|
103
|
+
} catch (e: Exception) {
|
|
104
|
+
Log.w(TAG, "Error stopping AudioRecord: ${e.message}")
|
|
105
|
+
}
|
|
106
|
+
audioRecord?.release()
|
|
107
|
+
audioRecord = null
|
|
108
|
+
screenAudioBuffer = null
|
|
109
|
+
Log.d(TAG, "Screen audio capture stopped")
|
|
110
|
+
}
|
|
111
|
+
}
|