@stream-io/video-react-native-sdk 2.0.0-beta.0 → 2.0.0-beta.2
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 +95 -0
- package/README.md +15 -0
- package/android/build.gradle +7 -1
- package/android/src/main/java/com/streamvideo/reactnative/audio/AudioDeviceManager.kt +110 -58
- package/android/src/main/java/com/streamvideo/reactnative/audio/CommunicationModeKeepAlive.kt +247 -0
- package/android/src/main/java/com/streamvideo/reactnative/callmanager/StreamInCallManagerModule.kt +16 -5
- package/dist/commonjs/components/Call/CallContent/CallContent.js +1 -1
- package/dist/commonjs/components/Call/CallContent/CallContent.js.map +1 -1
- package/dist/commonjs/components/Call/CallContent/RTCViewPipIOS.js +78 -7
- package/dist/commonjs/components/Call/CallContent/RTCViewPipIOS.js.map +1 -1
- package/dist/commonjs/components/Call/CallContent/RTCViewPipNative.js +3 -0
- package/dist/commonjs/components/Call/CallContent/RTCViewPipNative.js.map +1 -1
- package/dist/commonjs/components/Call/Lobby/JoinCallButton.js +3 -2
- package/dist/commonjs/components/Call/Lobby/JoinCallButton.js.map +1 -1
- package/dist/commonjs/components/Call/Lobby/Lobby.js +4 -3
- package/dist/commonjs/components/Call/Lobby/Lobby.js.map +1 -1
- package/dist/commonjs/components/Call/Lobby/LobbyFooter.js +10 -10
- package/dist/commonjs/components/Call/Lobby/LobbyFooter.js.map +1 -1
- package/dist/commonjs/components/Call/RingingCallContent/CallLeftIndicator.js +3 -3
- package/dist/commonjs/components/Call/RingingCallContent/CallLeftIndicator.js.map +1 -1
- package/dist/commonjs/components/Call/RingingCallContent/CallPreparingIndicator.js +3 -3
- package/dist/commonjs/components/Call/RingingCallContent/CallPreparingIndicator.js.map +1 -1
- package/dist/commonjs/components/Call/RingingCallContent/IncomingCall.js +3 -2
- package/dist/commonjs/components/Call/RingingCallContent/IncomingCall.js.map +1 -1
- package/dist/commonjs/components/Call/RingingCallContent/OutgoingCall.js +7 -14
- package/dist/commonjs/components/Call/RingingCallContent/OutgoingCall.js.map +1 -1
- package/dist/commonjs/components/Livestream/LivestreamControls/HostStartStreamButton.js +3 -2
- package/dist/commonjs/components/Livestream/LivestreamControls/HostStartStreamButton.js.map +1 -1
- package/dist/commonjs/components/Livestream/LivestreamPlayer/LivestreamEnded.js +4 -3
- package/dist/commonjs/components/Livestream/LivestreamPlayer/LivestreamEnded.js.map +1 -1
- package/dist/commonjs/components/Livestream/LivestreamTopView/LiveIndicator.js +3 -3
- package/dist/commonjs/components/Livestream/LivestreamTopView/LiveIndicator.js.map +1 -1
- package/dist/commonjs/components/Livestream/ViewerLivestream/ViewerLobby.js +9 -4
- package/dist/commonjs/components/Livestream/ViewerLivestream/ViewerLobby.js.map +1 -1
- package/dist/commonjs/components/Participant/ParticipantView/ParticipantLabel.js +4 -3
- package/dist/commonjs/components/Participant/ParticipantView/ParticipantLabel.js.map +1 -1
- package/dist/commonjs/components/Participant/ParticipantView/VideoRenderer/TrackSubscriber.js +27 -4
- package/dist/commonjs/components/Participant/ParticipantView/VideoRenderer/TrackSubscriber.js.map +1 -1
- package/dist/commonjs/components/utility/ScreenShareOverlay.js +4 -3
- package/dist/commonjs/components/utility/ScreenShareOverlay.js.map +1 -1
- package/dist/commonjs/i18n/Streami18n.js +77 -0
- package/dist/commonjs/i18n/Streami18n.js.map +1 -0
- package/dist/commonjs/i18n/TranslationContext.js +52 -0
- package/dist/commonjs/i18n/TranslationContext.js.map +1 -0
- package/dist/commonjs/i18n/index.js +85 -0
- package/dist/commonjs/i18n/index.js.map +1 -0
- package/dist/commonjs/i18n/keys.js +2 -0
- package/dist/commonjs/i18n/keys.js.map +1 -0
- package/dist/commonjs/i18n/runtimeDefaults.js +24 -0
- package/dist/commonjs/i18n/runtimeDefaults.js.map +1 -0
- package/dist/commonjs/i18n/types.js +6 -0
- package/dist/commonjs/i18n/types.js.map +1 -0
- package/dist/commonjs/i18n/useStreami18n.js +61 -0
- package/dist/commonjs/i18n/useStreami18n.js.map +1 -0
- package/dist/commonjs/index.js +18 -5
- package/dist/commonjs/index.js.map +1 -1
- package/dist/commonjs/modules/encryption/EncryptionManager.js +305 -0
- package/dist/commonjs/modules/encryption/EncryptionManager.js.map +1 -0
- package/dist/commonjs/modules/encryption/eventMapping.js +143 -0
- package/dist/commonjs/modules/encryption/eventMapping.js.map +1 -0
- package/dist/commonjs/modules/encryption/index.js +13 -0
- package/dist/commonjs/modules/encryption/index.js.map +1 -0
- package/dist/commonjs/modules/encryption/parity.js +6 -0
- package/dist/commonjs/modules/encryption/parity.js.map +1 -0
- package/dist/commonjs/providers/NoiseCancellation/NoiseCancellationProvider.js +6 -2
- package/dist/commonjs/providers/NoiseCancellation/NoiseCancellationProvider.js.map +1 -1
- package/dist/commonjs/providers/StreamVideo.js +28 -12
- package/dist/commonjs/providers/StreamVideo.js.map +1 -1
- package/dist/commonjs/utils/StreamVideoRN/index.js +46 -0
- package/dist/commonjs/utils/StreamVideoRN/index.js.map +1 -1
- package/dist/commonjs/utils/internal/callingx/callingx.js +29 -4
- package/dist/commonjs/utils/internal/callingx/callingx.js.map +1 -1
- package/dist/commonjs/utils/internal/iosPipTrack.js +41 -0
- package/dist/commonjs/utils/internal/iosPipTrack.js.map +1 -0
- package/dist/commonjs/utils/internal/registerSDKGlobals.js +6 -0
- package/dist/commonjs/utils/internal/registerSDKGlobals.js.map +1 -1
- package/dist/commonjs/utils/internal/ringingCallLifecycle.js +142 -0
- package/dist/commonjs/utils/internal/ringingCallLifecycle.js.map +1 -0
- package/dist/commonjs/utils/push/internal/utils.js +4 -0
- package/dist/commonjs/utils/push/internal/utils.js.map +1 -1
- package/dist/commonjs/version.js +1 -1
- package/dist/module/components/Call/CallContent/CallContent.js +1 -1
- package/dist/module/components/Call/CallContent/CallContent.js.map +1 -1
- package/dist/module/components/Call/CallContent/RTCViewPipIOS.js +80 -9
- package/dist/module/components/Call/CallContent/RTCViewPipIOS.js.map +1 -1
- package/dist/module/components/Call/CallContent/RTCViewPipNative.js +3 -0
- package/dist/module/components/Call/CallContent/RTCViewPipNative.js.map +1 -1
- package/dist/module/components/Call/Lobby/JoinCallButton.js +3 -2
- package/dist/module/components/Call/Lobby/JoinCallButton.js.map +1 -1
- package/dist/module/components/Call/Lobby/Lobby.js +4 -3
- package/dist/module/components/Call/Lobby/Lobby.js.map +1 -1
- package/dist/module/components/Call/Lobby/LobbyFooter.js +10 -10
- package/dist/module/components/Call/Lobby/LobbyFooter.js.map +1 -1
- package/dist/module/components/Call/RingingCallContent/CallLeftIndicator.js +2 -2
- package/dist/module/components/Call/RingingCallContent/CallLeftIndicator.js.map +1 -1
- package/dist/module/components/Call/RingingCallContent/CallPreparingIndicator.js +2 -2
- package/dist/module/components/Call/RingingCallContent/CallPreparingIndicator.js.map +1 -1
- package/dist/module/components/Call/RingingCallContent/IncomingCall.js +3 -2
- package/dist/module/components/Call/RingingCallContent/IncomingCall.js.map +1 -1
- package/dist/module/components/Call/RingingCallContent/OutgoingCall.js +7 -14
- package/dist/module/components/Call/RingingCallContent/OutgoingCall.js.map +1 -1
- package/dist/module/components/Livestream/LivestreamControls/HostStartStreamButton.js +3 -2
- package/dist/module/components/Livestream/LivestreamControls/HostStartStreamButton.js.map +1 -1
- package/dist/module/components/Livestream/LivestreamPlayer/LivestreamEnded.js +4 -3
- package/dist/module/components/Livestream/LivestreamPlayer/LivestreamEnded.js.map +1 -1
- package/dist/module/components/Livestream/LivestreamTopView/LiveIndicator.js +2 -2
- package/dist/module/components/Livestream/LivestreamTopView/LiveIndicator.js.map +1 -1
- package/dist/module/components/Livestream/ViewerLivestream/ViewerLobby.js +9 -4
- package/dist/module/components/Livestream/ViewerLivestream/ViewerLobby.js.map +1 -1
- package/dist/module/components/Participant/ParticipantView/ParticipantLabel.js +4 -3
- package/dist/module/components/Participant/ParticipantView/ParticipantLabel.js.map +1 -1
- package/dist/module/components/Participant/ParticipantView/VideoRenderer/TrackSubscriber.js +28 -5
- package/dist/module/components/Participant/ParticipantView/VideoRenderer/TrackSubscriber.js.map +1 -1
- package/dist/module/components/utility/ScreenShareOverlay.js +4 -3
- package/dist/module/components/utility/ScreenShareOverlay.js.map +1 -1
- package/dist/module/i18n/Streami18n.js +73 -0
- package/dist/module/i18n/Streami18n.js.map +1 -0
- package/dist/module/i18n/TranslationContext.js +48 -0
- package/dist/module/i18n/TranslationContext.js.map +1 -0
- package/dist/module/i18n/index.js +20 -0
- package/dist/module/i18n/index.js.map +1 -0
- package/dist/module/i18n/keys.js +2 -0
- package/dist/module/i18n/keys.js.map +1 -0
- package/dist/module/i18n/runtimeDefaults.js +20 -0
- package/dist/module/i18n/runtimeDefaults.js.map +1 -0
- package/dist/module/i18n/types.js +4 -0
- package/dist/module/i18n/types.js.map +1 -0
- package/dist/module/i18n/useStreami18n.js +56 -0
- package/dist/module/i18n/useStreami18n.js.map +1 -0
- package/dist/module/index.js +6 -1
- package/dist/module/index.js.map +1 -1
- package/dist/module/modules/encryption/EncryptionManager.js +301 -0
- package/dist/module/modules/encryption/EncryptionManager.js.map +1 -0
- package/dist/module/modules/encryption/eventMapping.js +135 -0
- package/dist/module/modules/encryption/eventMapping.js.map +1 -0
- package/dist/module/modules/encryption/index.js +4 -0
- package/dist/module/modules/encryption/index.js.map +1 -0
- package/dist/module/modules/encryption/parity.js +4 -0
- package/dist/module/modules/encryption/parity.js.map +1 -0
- package/dist/module/providers/NoiseCancellation/NoiseCancellationProvider.js +6 -2
- package/dist/module/providers/NoiseCancellation/NoiseCancellationProvider.js.map +1 -1
- package/dist/module/providers/StreamVideo.js +29 -14
- package/dist/module/providers/StreamVideo.js.map +1 -1
- package/dist/module/utils/StreamVideoRN/index.js +46 -0
- package/dist/module/utils/StreamVideoRN/index.js.map +1 -1
- package/dist/module/utils/internal/callingx/callingx.js +29 -4
- package/dist/module/utils/internal/callingx/callingx.js.map +1 -1
- package/dist/module/utils/internal/iosPipTrack.js +35 -0
- package/dist/module/utils/internal/iosPipTrack.js.map +1 -0
- package/dist/module/utils/internal/registerSDKGlobals.js +6 -0
- package/dist/module/utils/internal/registerSDKGlobals.js.map +1 -1
- package/dist/module/utils/internal/ringingCallLifecycle.js +134 -0
- package/dist/module/utils/internal/ringingCallLifecycle.js.map +1 -0
- package/dist/module/utils/push/internal/utils.js +4 -0
- package/dist/module/utils/push/internal/utils.js.map +1 -1
- package/dist/module/version.js +1 -1
- package/dist/typescript/components/Call/CallContent/RTCViewPipIOS.d.ts.map +1 -1
- package/dist/typescript/components/Call/CallContent/RTCViewPipNative.d.ts +12 -0
- package/dist/typescript/components/Call/CallContent/RTCViewPipNative.d.ts.map +1 -1
- package/dist/typescript/components/Call/Lobby/JoinCallButton.d.ts.map +1 -1
- package/dist/typescript/components/Call/Lobby/Lobby.d.ts.map +1 -1
- package/dist/typescript/components/Call/Lobby/LobbyFooter.d.ts.map +1 -1
- package/dist/typescript/components/Call/RingingCallContent/OutgoingCall.d.ts.map +1 -1
- package/dist/typescript/components/Livestream/LivestreamControls/HostStartStreamButton.d.ts.map +1 -1
- package/dist/typescript/components/Livestream/LivestreamPlayer/LivestreamEnded.d.ts.map +1 -1
- package/dist/typescript/components/Livestream/ViewerLivestream/ViewerLobby.d.ts.map +1 -1
- package/dist/typescript/components/Participant/ParticipantView/ParticipantLabel.d.ts.map +1 -1
- package/dist/typescript/components/Participant/ParticipantView/VideoRenderer/TrackSubscriber.d.ts +11 -0
- package/dist/typescript/components/Participant/ParticipantView/VideoRenderer/TrackSubscriber.d.ts.map +1 -1
- package/dist/typescript/components/utility/ScreenShareOverlay.d.ts.map +1 -1
- package/dist/typescript/i18n/Streami18n.d.ts +61 -0
- package/dist/typescript/i18n/Streami18n.d.ts.map +1 -0
- package/dist/typescript/i18n/TranslationContext.d.ts +31 -0
- package/dist/typescript/i18n/TranslationContext.d.ts.map +1 -0
- package/dist/typescript/i18n/index.d.ts +17 -0
- package/dist/typescript/i18n/index.d.ts.map +1 -0
- package/dist/typescript/i18n/keys.d.ts +38 -0
- package/dist/typescript/i18n/keys.d.ts.map +1 -0
- package/dist/typescript/i18n/runtimeDefaults.d.ts +18 -0
- package/dist/typescript/i18n/runtimeDefaults.d.ts.map +1 -0
- package/dist/typescript/i18n/types.d.ts +46 -0
- package/dist/typescript/i18n/types.d.ts.map +1 -0
- package/dist/typescript/i18n/useStreami18n.d.ts +20 -0
- package/dist/typescript/i18n/useStreami18n.d.ts.map +1 -0
- package/dist/typescript/index.d.ts +2 -1
- package/dist/typescript/index.d.ts.map +1 -1
- package/dist/typescript/modules/encryption/EncryptionManager.d.ts +190 -0
- package/dist/typescript/modules/encryption/EncryptionManager.d.ts.map +1 -0
- package/dist/typescript/modules/encryption/eventMapping.d.ts +38 -0
- package/dist/typescript/modules/encryption/eventMapping.d.ts.map +1 -0
- package/dist/typescript/modules/encryption/index.d.ts +2 -0
- package/dist/typescript/modules/encryption/index.d.ts.map +1 -0
- package/dist/typescript/modules/encryption/parity.d.ts +55 -0
- package/dist/typescript/modules/encryption/parity.d.ts.map +1 -0
- package/dist/typescript/providers/NoiseCancellation/NoiseCancellationProvider.d.ts.map +1 -1
- package/dist/typescript/providers/StreamVideo.d.ts +17 -4
- package/dist/typescript/providers/StreamVideo.d.ts.map +1 -1
- package/dist/typescript/utils/StreamVideoRN/index.d.ts +33 -1
- package/dist/typescript/utils/StreamVideoRN/index.d.ts.map +1 -1
- package/dist/typescript/utils/StreamVideoRN/types.d.ts +67 -0
- package/dist/typescript/utils/StreamVideoRN/types.d.ts.map +1 -1
- package/dist/typescript/utils/internal/callingx/callingx.d.ts +1 -1
- package/dist/typescript/utils/internal/callingx/callingx.d.ts.map +1 -1
- package/dist/typescript/utils/internal/iosPipTrack.d.ts +9 -0
- package/dist/typescript/utils/internal/iosPipTrack.d.ts.map +1 -0
- package/dist/typescript/utils/internal/registerSDKGlobals.d.ts.map +1 -1
- package/dist/typescript/utils/internal/ringingCallLifecycle.d.ts +27 -0
- package/dist/typescript/utils/internal/ringingCallLifecycle.d.ts.map +1 -0
- package/dist/typescript/utils/push/internal/utils.d.ts.map +1 -1
- package/dist/typescript/version.d.ts +1 -1
- package/ios/RTCViewPip.swift +33 -8
- package/ios/RTCViewPipManager.mm +1 -0
- package/package.json +17 -13
- package/src/components/Call/CallContent/CallContent.tsx +1 -1
- package/src/components/Call/CallContent/RTCViewPipIOS.tsx +92 -10
- package/src/components/Call/CallContent/RTCViewPipNative.tsx +13 -0
- package/src/components/Call/Lobby/JoinCallButton.tsx +5 -2
- package/src/components/Call/Lobby/Lobby.tsx +6 -3
- package/src/components/Call/Lobby/LobbyFooter.tsx +15 -10
- package/src/components/Call/RingingCallContent/CallLeftIndicator.tsx +2 -2
- package/src/components/Call/RingingCallContent/CallPreparingIndicator.tsx +2 -2
- package/src/components/Call/RingingCallContent/IncomingCall.tsx +2 -2
- package/src/components/Call/RingingCallContent/OutgoingCall.tsx +7 -14
- package/src/components/Livestream/LivestreamControls/HostStartStreamButton.tsx +5 -8
- package/src/components/Livestream/LivestreamPlayer/LivestreamEnded.tsx +11 -3
- package/src/components/Livestream/LivestreamTopView/LiveIndicator.tsx +2 -2
- package/src/components/Livestream/ViewerLivestream/ViewerLobby.tsx +23 -10
- package/src/components/Participant/ParticipantView/ParticipantLabel.tsx +15 -6
- package/src/components/Participant/ParticipantView/VideoRenderer/TrackSubscriber.tsx +58 -5
- package/src/components/utility/ScreenShareOverlay.tsx +7 -3
- package/src/i18n/Streami18n.ts +71 -0
- package/src/i18n/TranslationContext.tsx +55 -0
- package/src/i18n/index.ts +22 -0
- package/src/i18n/keys.ts +42 -0
- package/src/i18n/runtimeDefaults.ts +17 -0
- package/src/i18n/types.ts +78 -0
- package/src/i18n/useStreami18n.ts +66 -0
- package/src/index.ts +6 -1
- package/src/modules/call-manager/native-module.d.ts +14 -0
- package/src/modules/encryption/EncryptionManager.ts +360 -0
- package/src/modules/encryption/eventMapping.ts +151 -0
- package/src/modules/encryption/index.ts +1 -0
- package/src/modules/encryption/parity.ts +83 -0
- package/src/providers/NoiseCancellation/NoiseCancellationProvider.tsx +8 -4
- package/src/providers/StreamVideo.tsx +39 -28
- package/src/utils/StreamVideoRN/index.ts +51 -1
- package/src/utils/StreamVideoRN/types.ts +68 -0
- package/src/utils/internal/callingx/callingx.ts +37 -2
- package/src/utils/internal/iosPipTrack.ts +39 -0
- package/src/utils/internal/registerSDKGlobals.ts +2 -0
- package/src/utils/internal/ringingCallLifecycle.ts +148 -0
- package/src/utils/push/internal/utils.ts +4 -0
- package/src/version.ts +1 -1
- package/dist/commonjs/translations/en.json +0 -32
- package/dist/commonjs/translations/index.js +0 -12
- package/dist/commonjs/translations/index.js.map +0 -1
- package/dist/module/translations/en.json +0 -32
- package/dist/module/translations/index.js +0 -7
- package/dist/module/translations/index.js.map +0 -1
- package/dist/typescript/translations/index.d.ts +0 -35
- package/dist/typescript/translations/index.d.ts.map +0 -1
- package/src/translations/en.json +0 -32
- package/src/translations/index.ts +0 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,101 @@
|
|
|
2
2
|
|
|
3
3
|
This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver).
|
|
4
4
|
|
|
5
|
+
## [2.0.0-beta.2](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-native-sdk-2.0.0-beta.1...@stream-io/video-react-native-sdk-2.0.0-beta.2) (2026-09-28)
|
|
6
|
+
|
|
7
|
+
### ⚠ BREAKING CHANGES
|
|
8
|
+
|
|
9
|
+
- **react-native:** Requires React Native >=0.79.0 with the New
|
|
10
|
+
Architecture enabled. RN 0.79 provides the iOS module-provider
|
|
11
|
+
registration used by this implementation.
|
|
12
|
+
|
|
13
|
+
### 📝 Implementation notes.
|
|
14
|
+
|
|
15
|
+
### 1. Migration to React Native New Architecture and TurboModules
|
|
16
|
+
|
|
17
|
+
- Updated documentation in `README.md` to specify that the package now
|
|
18
|
+
requires React Native 0.79+ with the New Architecture enabled, and only
|
|
19
|
+
supports TurboModule registration.
|
|
20
|
+
- Refactored Android native module to use the TurboModule codegen
|
|
21
|
+
pattern, removing legacy code and simplifying the module/package
|
|
22
|
+
classes.
|
|
23
|
+
- Refactored iOS implementation to use a new Objective-C host
|
|
24
|
+
(`NoiseCancellationReactNative.h`/`.mm`) delegating to a Swift
|
|
25
|
+
implementation, fully adopting the TurboModule spec and removing legacy
|
|
26
|
+
bridging code.
|
|
27
|
+
|
|
28
|
+
### 2. Android build system modernization
|
|
29
|
+
|
|
30
|
+
- Updated `build.gradle` to use AGP 8.7.2, Kotlin 2.0.21, Java 17, and
|
|
31
|
+
simplified/extensively cleaned up build logic, removing legacy property
|
|
32
|
+
indirection and supporting only the new namespace system.
|
|
33
|
+
- Removed obsolete manifest files and properties.
|
|
34
|
+
|
|
35
|
+
### 3. iOS native module refactoring
|
|
36
|
+
|
|
37
|
+
- Removed old Swift implementation and replaced it with a new
|
|
38
|
+
implementation in `NoiseCancellationReactNativeImpl.swift`, with the
|
|
39
|
+
Objective-C wrapper delegating all logic to this class and conforming to
|
|
40
|
+
the TurboModule interface.
|
|
41
|
+
|
|
42
|
+
### 4. JavaScript/TypeScript and testing improvements
|
|
43
|
+
|
|
44
|
+
- Added contract tests (`__tests__/native-module.test.cjs`) to verify
|
|
45
|
+
the JS interface against a stubbed TurboModule registry, ensuring
|
|
46
|
+
correct JS-native interaction and error propagation.
|
|
47
|
+
- Added a `test` script to `package.json` for running these contract
|
|
48
|
+
tests.
|
|
49
|
+
|
|
50
|
+
🎫 Ticket: https://linear.app/stream/issue/XYZ-123
|
|
51
|
+
|
|
52
|
+
📑 Docs: https://github.com/GetStream/docs-content/pull/<id>
|
|
53
|
+
|
|
54
|
+
<!-- This is an auto-generated comment: release notes by coderabbit.ai
|
|
55
|
+
-->
|
|
56
|
+
|
|
57
|
+
## Summary by CodeRabbit
|
|
58
|
+
|
|
59
|
+
- **New Features**
|
|
60
|
+
- Noise-cancellation controls now provide synchronous status,
|
|
61
|
+
enablement, and device-support checks.
|
|
62
|
+
- The React Native integration now supports the New Architecture through
|
|
63
|
+
TurboModules.
|
|
64
|
+
- Noise-cancellation state and errors are handled more reliably in the
|
|
65
|
+
provider.
|
|
66
|
+
|
|
67
|
+
- **Bug Fixes**
|
|
68
|
+
- Checking whether noise cancellation is enabled before registration now
|
|
69
|
+
returns `false` instead of failing.
|
|
70
|
+
|
|
71
|
+
- **Documentation**
|
|
72
|
+
- Added setup requirements, native installation guidance, synchronous
|
|
73
|
+
API usage notes, and peer dependency information.
|
|
74
|
+
- Updated SDK installation guidance to require React Native 0.79+ with
|
|
75
|
+
the New Architecture enabled.
|
|
76
|
+
|
|
77
|
+
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
|
|
78
|
+
|
|
79
|
+
- **i18n:** adopt the shared @stream-io/i18n runtime (#2436)
|
|
80
|
+
|
|
81
|
+
### Features
|
|
82
|
+
|
|
83
|
+
- **i18n:** adopt the shared @stream-io/i18n runtime ([#2436](https://github.com/GetStream/stream-video-js/issues/2436)) ([2fcd426](https://github.com/GetStream/stream-video-js/commit/2fcd4269227d5db8f46342c597d17f2c7d835f88)), closes [stream-chat-react#3261](https://github.com/GetStream/stream-chat-react/issues/3261)
|
|
84
|
+
- **react-native:** Migrate noise cancellation module to TurboModule architecture ([#2447](https://github.com/GetStream/stream-video-js/issues/2447)) ([2842986](https://github.com/GetStream/stream-video-js/commit/2842986f6d3adeef8617af243fa11a3076aae4b2))
|
|
85
|
+
- **rn:** add end-to-end encryption support ([#2427](https://github.com/GetStream/stream-video-js/issues/2427)) ([fd70b35](https://github.com/GetStream/stream-video-js/commit/fd70b3597beb5867b21f254c76fd6d4e6e2e731b))
|
|
86
|
+
|
|
87
|
+
### Bug Fixes
|
|
88
|
+
|
|
89
|
+
- **android:** skip explicit Kotlin plugin when AGP registers the kotlin extension ([#2417](https://github.com/GetStream/stream-video-js/issues/2417)) ([1bff5e0](https://github.com/GetStream/stream-video-js/commit/1bff5e0f5b4a2f2bbe62ab5eff2a6457251f6350)), closes [RevenueCat/react-native-purchases#1934](https://github.com/RevenueCat/react-native-purchases/issues/1934)
|
|
90
|
+
- **react-native-callingx:** use <packageName>:<callId> as the Android Telecom address ([#2453](https://github.com/GetStream/stream-video-js/issues/2453)) ([22d18d8](https://github.com/GetStream/stream-video-js/commit/22d18d8b55bab81946989d791b5fa18569428712)), closes [#2452](https://github.com/GetStream/stream-video-js/issues/2452) [#2452](https://github.com/GetStream/stream-video-js/issues/2452)
|
|
91
|
+
- **react-native:** iOS Picture in Picture window size must be reported ([#2448](https://github.com/GetStream/stream-video-js/issues/2448)) ([b7e4165](https://github.com/GetStream/stream-video-js/commit/b7e4165113ad3277d2b32fab690ecef81b582b4a))
|
|
92
|
+
- replaced RTCView with camera preview for outgoing call component ([#2450](https://github.com/GetStream/stream-video-js/issues/2450)) ([e8e360b](https://github.com/GetStream/stream-video-js/commit/e8e360b559309267177a64df58385bb67b8a90da))
|
|
93
|
+
|
|
94
|
+
## [2.0.0-beta.1](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-native-sdk-2.0.0-beta.0...@stream-io/video-react-native-sdk-2.0.0-beta.1) (2026-09-04)
|
|
95
|
+
|
|
96
|
+
### Bug Fixes
|
|
97
|
+
|
|
98
|
+
- **react-native-sdk:** publish the corrected satellite peer ranges ([15172ec](https://github.com/GetStream/stream-video-js/commit/15172ec5a2e96ec7ad62c83be631776a9f315485)), closes [#2414](https://github.com/GetStream/stream-video-js/issues/2414)
|
|
99
|
+
|
|
5
100
|
## [2.0.0-beta.0](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-native-sdk-1.45.0...@stream-io/video-react-native-sdk-2.0.0-beta.0) (2026-09-04)
|
|
6
101
|
|
|
7
102
|
### Bug Fixes
|
package/README.md
CHANGED
|
@@ -79,6 +79,21 @@ The monorepo consists of multiple folders, the most important of which are `pack
|
|
|
79
79
|
Before running this project please ensure you have set up your [development environment for React Native](https://reactnative.dev/docs/environment-setup).
|
|
80
80
|
The Stream Video React Native SDK supports apps created with the React Native CLI.
|
|
81
81
|
|
|
82
|
+
### Optional native add-ons
|
|
83
|
+
|
|
84
|
+
Noise cancellation, video filters and CallKit/Telecom ringing ship as separate
|
|
85
|
+
optional peer packages. On the `2.0.0-beta` line they are published on their own
|
|
86
|
+
`1.0.0-beta` line under the `beta` npm tag, so install them from that tag:
|
|
87
|
+
|
|
88
|
+
```sh
|
|
89
|
+
yarn add @stream-io/noise-cancellation-react-native@beta # requires React Native >= 0.79 with the New Architecture enabled
|
|
90
|
+
yarn add @stream-io/video-filters-react-native@beta
|
|
91
|
+
yarn add @stream-io/react-native-callingx@beta
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Installing the `latest` builds of these packages alongside the v2 beta pulls the
|
|
95
|
+
v1 line and will report an unmet peer dependency.
|
|
96
|
+
|
|
82
97
|
## **Contributing** 🤝
|
|
83
98
|
|
|
84
99
|
- How can I submit a sample app?
|
package/android/build.gradle
CHANGED
|
@@ -19,7 +19,13 @@ def isNewArchitectureEnabled() {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
apply plugin: "com.android.library"
|
|
22
|
-
|
|
22
|
+
// AGP 9 ships built-in Kotlin support and registers the `kotlin` extension
|
|
23
|
+
// itself. Applying the Kotlin plugin on top of it fails configuration with
|
|
24
|
+
// "Cannot add extension with name 'kotlin'". Only apply it when nothing has
|
|
25
|
+
// registered that extension yet.
|
|
26
|
+
if (project.extensions.findByName('kotlin') == null) {
|
|
27
|
+
apply plugin: "kotlin-android"
|
|
28
|
+
}
|
|
23
29
|
|
|
24
30
|
if (isNewArchitectureEnabled()) {
|
|
25
31
|
apply plugin: "com.facebook.react"
|
|
@@ -107,6 +107,30 @@ class AudioDeviceManager(
|
|
|
107
107
|
*/
|
|
108
108
|
var telecomManagedMode: Boolean = false
|
|
109
109
|
|
|
110
|
+
/**
|
|
111
|
+
* Opt-out for the Android 11+ communication-mode keep-alive. Sticky developer preference —
|
|
112
|
+
* intentionally NOT reset in [stop], unlike the per-call fields above.
|
|
113
|
+
*/
|
|
114
|
+
var disableCommunicationModeWorkaround: Boolean = false
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* True once [stop]/[close] have torn routing down, until the next [start].
|
|
118
|
+
*
|
|
119
|
+
* [updateAudioDeviceState] is enqueued from off-thread sources (Bluetooth receivers,
|
|
120
|
+
* audio-focus and device callbacks), so a task can still land after teardown. Re-routing
|
|
121
|
+
* then would re-assert MODE_IN_COMMUNICATION via AudioManagerUtil, undoing the
|
|
122
|
+
* MODE_NORMAL restore in [stop]. Audio-thread confined.
|
|
123
|
+
*/
|
|
124
|
+
private var routingStopped = false
|
|
125
|
+
|
|
126
|
+
/** Keeps MODE_IN_COMMUNICATION owned for the whole Communicator call on Android 11+. */
|
|
127
|
+
private val communicationModeKeepAlive: CommunicationModeKeepAlive =
|
|
128
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
|
129
|
+
SilentPlaybackKeepAlive(mReactContext)
|
|
130
|
+
} else {
|
|
131
|
+
NoCommunicationModeKeepAlive
|
|
132
|
+
}
|
|
133
|
+
|
|
110
134
|
val bluetoothManager = BluetoothManager(mReactContext, this)
|
|
111
135
|
|
|
112
136
|
private val proximityManager by lazy { ProximityManager(mReactContext, this) }
|
|
@@ -130,53 +154,64 @@ class AudioDeviceManager(
|
|
|
130
154
|
audioFocusUtil.setup(callAudioRole, mReactContext)
|
|
131
155
|
}
|
|
132
156
|
|
|
157
|
+
/** Runs inline; callers must already be on the audio thread (see [runInAudioThread]). */
|
|
133
158
|
fun start(activity: Activity) {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
if (!telecomManagedMode) {
|
|
142
|
-
// Telecom owns routing/focus; only run our own routing when not Telecom-managed.
|
|
143
|
-
bluetoothManager.start()
|
|
144
|
-
mAudioManager.registerAudioDeviceCallback(this, null)
|
|
145
|
-
updateAudioDeviceState()
|
|
146
|
-
}
|
|
147
|
-
proximityManager.start()
|
|
148
|
-
} else {
|
|
149
|
-
activity.volumeControlStream = AudioManager.USE_DEFAULT_STREAM_TYPE
|
|
150
|
-
}
|
|
159
|
+
routingStopped = false
|
|
160
|
+
setup()
|
|
161
|
+
selectedAudioDeviceEndpoint = null
|
|
162
|
+
if (callAudioRole == CallAudioRole.Communicator) {
|
|
163
|
+
// Audio routing is manually controlled by the SDK in communication media mode
|
|
164
|
+
// and local microphone can be published
|
|
165
|
+
activity.volumeControlStream = AudioManager.STREAM_VOICE_CALL
|
|
151
166
|
if (!telecomManagedMode) {
|
|
152
|
-
|
|
167
|
+
// Telecom owns routing/focus; only run our own routing when not Telecom-managed.
|
|
168
|
+
bluetoothManager.start()
|
|
169
|
+
mAudioManager.registerAudioDeviceCallback(this, null)
|
|
170
|
+
updateAudioDeviceState()
|
|
153
171
|
}
|
|
172
|
+
proximityManager.start()
|
|
173
|
+
} else {
|
|
174
|
+
activity.volumeControlStream = AudioManager.USE_DEFAULT_STREAM_TYPE
|
|
175
|
+
}
|
|
176
|
+
if (!telecomManagedMode) {
|
|
177
|
+
audioFocusUtil.requestFocus(callAudioRole, mReactContext)
|
|
178
|
+
}
|
|
179
|
+
// Started last, after focus/routing, so the silent track uses the correct route.
|
|
180
|
+
if (callAudioRole == CallAudioRole.Communicator &&
|
|
181
|
+
!telecomManagedMode &&
|
|
182
|
+
!disableCommunicationModeWorkaround
|
|
183
|
+
) {
|
|
184
|
+
communicationModeKeepAlive.start()
|
|
154
185
|
}
|
|
155
186
|
}
|
|
156
187
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
if (Build.VERSION.SDK_INT >= 31) {
|
|
163
|
-
mAudioManager.clearCommunicationDevice()
|
|
164
|
-
} else {
|
|
165
|
-
mAudioManager.setSpeakerphoneOn(false)
|
|
166
|
-
}
|
|
167
|
-
bluetoothManager.stop()
|
|
168
|
-
}
|
|
169
|
-
callAudioRole = CallAudioRole.Communicator
|
|
170
|
-
enableStereo = false
|
|
171
|
-
defaultAudioDevice = AudioDeviceEndpoint.TYPE_SPEAKER
|
|
172
|
-
proximityManager.stop()
|
|
173
|
-
}
|
|
174
|
-
activity.volumeControlStream = AudioManager.USE_DEFAULT_STREAM_TYPE
|
|
188
|
+
/** Runs inline; callers must already be on the audio thread (see [runInAudioThread]). */
|
|
189
|
+
fun stop(activity: Activity?) {
|
|
190
|
+
routingStopped = true
|
|
191
|
+
communicationModeKeepAlive.stop()
|
|
192
|
+
if (callAudioRole == CallAudioRole.Communicator) {
|
|
175
193
|
if (!telecomManagedMode) {
|
|
176
|
-
|
|
194
|
+
// Only tear down what we set up ourselves; Telecom owns its own teardown.
|
|
195
|
+
if (Build.VERSION.SDK_INT >= 31) {
|
|
196
|
+
mAudioManager.clearCommunicationDevice()
|
|
197
|
+
} else {
|
|
198
|
+
mAudioManager.setSpeakerphoneOn(false)
|
|
199
|
+
}
|
|
200
|
+
bluetoothManager.stop()
|
|
201
|
+
// Restore the mode set in setup(); it was previously left at
|
|
202
|
+
// MODE_IN_COMMUNICATION, holding the device in in-call routing.
|
|
203
|
+
mAudioManager.mode = AudioManager.MODE_NORMAL
|
|
177
204
|
}
|
|
178
|
-
|
|
205
|
+
callAudioRole = CallAudioRole.Communicator
|
|
206
|
+
enableStereo = false
|
|
207
|
+
defaultAudioDevice = AudioDeviceEndpoint.TYPE_SPEAKER
|
|
208
|
+
proximityManager.stop()
|
|
179
209
|
}
|
|
210
|
+
activity?.volumeControlStream = AudioManager.USE_DEFAULT_STREAM_TYPE
|
|
211
|
+
if (!telecomManagedMode) {
|
|
212
|
+
audioFocusUtil.abandonFocus()
|
|
213
|
+
}
|
|
214
|
+
telecomManagedMode = false
|
|
180
215
|
}
|
|
181
216
|
|
|
182
217
|
fun setMicrophoneMute(enable: Boolean) {
|
|
@@ -228,6 +263,9 @@ class AudioDeviceManager(
|
|
|
228
263
|
}
|
|
229
264
|
|
|
230
265
|
private fun switchDeviceEndpointType(@EndpointType deviceType: Int) {
|
|
266
|
+
// Sole sink for routing changes, and AudioManagerUtil re-asserts MODE_IN_COMMUNICATION
|
|
267
|
+
// here — so late work of any origin must not reach it after teardown.
|
|
268
|
+
if (routingStopped) return
|
|
231
269
|
val newDevice = AudioManagerUtil.switchDeviceEndpointType(
|
|
232
270
|
deviceType,
|
|
233
271
|
mEndpointMaps,
|
|
@@ -237,38 +275,49 @@ class AudioDeviceManager(
|
|
|
237
275
|
this.selectedAudioDeviceEndpoint = newDevice
|
|
238
276
|
}
|
|
239
277
|
|
|
278
|
+
/** Runs inline; callers must already be on the audio thread (see [runInAudioThread]). */
|
|
240
279
|
fun switchDeviceById(
|
|
241
280
|
deviceId: String
|
|
242
281
|
) {
|
|
243
282
|
Log.d(TAG, "switchDeviceById: deviceId = $deviceId")
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
AudioDeviceEndpoint.TYPE_BLUETOOTH
|
|
254
|
-
)
|
|
255
|
-
}
|
|
283
|
+
// Guarded here too: the branch below sets the communication device directly rather
|
|
284
|
+
// than going through switchDeviceEndpointType().
|
|
285
|
+
if (routingStopped) return
|
|
286
|
+
val btDevice = mEndpointMaps.bluetoothEndpoints[deviceId]
|
|
287
|
+
if (btDevice != null) {
|
|
288
|
+
if (Build.VERSION.SDK_INT >= 31) {
|
|
289
|
+
mAudioManager.setCommunicationDevice(btDevice.deviceInfo)
|
|
290
|
+
bluetoothManager.updateDevice()
|
|
291
|
+
this.selectedAudioDeviceEndpoint = btDevice
|
|
256
292
|
} else {
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
293
|
+
switchDeviceEndpointType(
|
|
294
|
+
AudioDeviceEndpoint.TYPE_BLUETOOTH
|
|
295
|
+
)
|
|
296
|
+
}
|
|
297
|
+
} else {
|
|
298
|
+
val endpoint = nonBluetoothEndpointById(deviceId)
|
|
299
|
+
if (endpoint != null) {
|
|
300
|
+
switchDeviceEndpointType(endpoint.type)
|
|
301
|
+
} else {
|
|
302
|
+
Log.e(TAG, "switchDeviceById: no endpoint found for id $deviceId")
|
|
263
303
|
}
|
|
264
304
|
}
|
|
265
305
|
}
|
|
266
306
|
|
|
267
307
|
override fun close() {
|
|
268
|
-
|
|
269
|
-
|
|
308
|
+
// Unlike start()/stop(), this is called straight from the module, so it must be queued
|
|
309
|
+
// to land after any start/stop already pending on the audio thread.
|
|
310
|
+
runInAudioThread {
|
|
311
|
+
routingStopped = true
|
|
312
|
+
communicationModeKeepAlive.release()
|
|
313
|
+
mAudioManager.unregisterAudioDeviceCallback(this)
|
|
314
|
+
proximityManager.onDestroy()
|
|
315
|
+
}
|
|
270
316
|
}
|
|
271
317
|
|
|
318
|
+
/** Short description of the keep-alive state, for the audio debug log. */
|
|
319
|
+
fun communicationModeKeepAliveState(): String = communicationModeKeepAlive.describeState()
|
|
320
|
+
|
|
272
321
|
override fun onAudioDevicesAdded(addedDevices: Array<out AudioDeviceInfo>?) {
|
|
273
322
|
if (addedDevices != null) {
|
|
274
323
|
runInAudioThread {
|
|
@@ -423,6 +472,9 @@ class AudioDeviceManager(
|
|
|
423
472
|
*/
|
|
424
473
|
fun updateAudioDeviceState() {
|
|
425
474
|
runInAudioThread {
|
|
475
|
+
// Bail early: on API < 31 bluetoothState survives stop(), so a late pass could
|
|
476
|
+
// still start SCO after the call ended.
|
|
477
|
+
if (routingStopped) return@runInAudioThread
|
|
426
478
|
if (telecomManagedMode) {
|
|
427
479
|
// Telecom is the single source of truth for routing/endpoints in this mode.
|
|
428
480
|
return@runInAudioThread
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
package com.streamvideo.reactnative.audio
|
|
2
|
+
|
|
3
|
+
import android.annotation.SuppressLint
|
|
4
|
+
import android.content.Context
|
|
5
|
+
import android.content.Context.AUDIO_SERVICE
|
|
6
|
+
import android.media.AudioAttributes
|
|
7
|
+
import android.media.AudioFormat
|
|
8
|
+
import android.media.AudioManager
|
|
9
|
+
import android.media.AudioTrack
|
|
10
|
+
import android.os.Build
|
|
11
|
+
import android.util.Log
|
|
12
|
+
import androidx.annotation.RequiresApi
|
|
13
|
+
import com.streamvideo.reactnative.callmanager.StreamInCallManagerModule
|
|
14
|
+
import java.nio.ByteBuffer
|
|
15
|
+
import java.util.concurrent.Executors
|
|
16
|
+
import java.util.concurrent.ScheduledExecutorService
|
|
17
|
+
import java.util.concurrent.TimeUnit
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Holds the audio system in [AudioManager.MODE_IN_COMMUNICATION] for the duration
|
|
21
|
+
* of a communicator-role call.
|
|
22
|
+
*
|
|
23
|
+
* Why this is needed: from Android 11 (API 30) the platform demotes the
|
|
24
|
+
* audio mode back to `MODE_NORMAL` about six seconds after `setMode()` unless the
|
|
25
|
+
* call is actively playing or recording on the voice-communication path.
|
|
26
|
+
* During a call whose microphone is muted and that has no inbound audio yet,
|
|
27
|
+
* neither condition is met, so routing silently falls back to the media path and
|
|
28
|
+
* hardware echo cancellation is switched off.
|
|
29
|
+
* Details: https://issuetracker.google.com/issues/209493718
|
|
30
|
+
*
|
|
31
|
+
* We satisfy the "actively playing" condition by continuously looping an inaudible
|
|
32
|
+
* audo track on the voice-communication stream, which keeps the platform counting us
|
|
33
|
+
* as an active player and therefore keeps the mode in place.
|
|
34
|
+
*/
|
|
35
|
+
interface CommunicationModeKeepAlive {
|
|
36
|
+
/** Begin holding the communication mode. Idempotent. */
|
|
37
|
+
fun start()
|
|
38
|
+
|
|
39
|
+
/** Stop holding the communication mode. Idempotent; the instance can be started again. */
|
|
40
|
+
fun stop()
|
|
41
|
+
|
|
42
|
+
/** Permanently release all resources. The instance must not be used afterwards. */
|
|
43
|
+
fun release()
|
|
44
|
+
|
|
45
|
+
/** Short human-readable state, surfaced in the audio-state debug log. */
|
|
46
|
+
fun describeState(): String
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** No-op variant used on platforms where the mode reset does not occur (Android API < 30). */
|
|
50
|
+
object NoCommunicationModeKeepAlive : CommunicationModeKeepAlive {
|
|
51
|
+
override fun start() {}
|
|
52
|
+
override fun stop() {}
|
|
53
|
+
override fun release() {}
|
|
54
|
+
override fun describeState(): String = "disabled (android platform API < 30)"
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Default implementation backed by a silent, looping voice-communication
|
|
59
|
+
* [AudioTrack]. If the silent player cannot be created on a given device, it
|
|
60
|
+
* degrades to a lightweight poller that re-applies the mode whenever the platform
|
|
61
|
+
* has reset it.
|
|
62
|
+
*
|
|
63
|
+
* All lifecycle transitions and every access to [track] are serialized on [gate],
|
|
64
|
+
* so a play/pause can never overlap a release. The poller only ever touches the
|
|
65
|
+
* audio mode (never the track), so it runs lock-free.
|
|
66
|
+
*
|
|
67
|
+
* @suppress
|
|
68
|
+
*/
|
|
69
|
+
@RequiresApi(Build.VERSION_CODES.R)
|
|
70
|
+
internal class SilentPlaybackKeepAlive(
|
|
71
|
+
private val context: Context,
|
|
72
|
+
) : CommunicationModeKeepAlive {
|
|
73
|
+
|
|
74
|
+
private val gate = Any()
|
|
75
|
+
|
|
76
|
+
private var track: AudioTrack? = null
|
|
77
|
+
|
|
78
|
+
/** Whether we currently intend the silent player to be running. */
|
|
79
|
+
private var engaged = false
|
|
80
|
+
|
|
81
|
+
/** Read by the poller (off-gate) to drop ticks that were queued before we stopped. */
|
|
82
|
+
@Volatile
|
|
83
|
+
private var live = false
|
|
84
|
+
|
|
85
|
+
private var modePoller: ScheduledExecutorService? = null
|
|
86
|
+
|
|
87
|
+
override fun start(): Unit = synchronized(gate) {
|
|
88
|
+
live = true
|
|
89
|
+
if (engaged) return
|
|
90
|
+
engaged = true
|
|
91
|
+
|
|
92
|
+
val player = track ?: createSilentTrack()?.also { track = it }
|
|
93
|
+
if (player != null && player.state == AudioTrack.STATE_INITIALIZED && play(player)) {
|
|
94
|
+
return
|
|
95
|
+
}
|
|
96
|
+
// No usable silent player on this device — fall back to reactive repair.
|
|
97
|
+
player?.release()
|
|
98
|
+
track = null
|
|
99
|
+
startModePoller()
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Starts playback, reporting whether the track actually entered the playing state.
|
|
104
|
+
* `play()` can throw or leave the track stopped even from STATE_INITIALIZED; treating
|
|
105
|
+
* that as success would strand the keep-alive with neither a track nor the poller.
|
|
106
|
+
*/
|
|
107
|
+
private fun play(player: AudioTrack): Boolean =
|
|
108
|
+
try {
|
|
109
|
+
player.play()
|
|
110
|
+
player.playState == AudioTrack.PLAYSTATE_PLAYING
|
|
111
|
+
} catch (e: IllegalStateException) {
|
|
112
|
+
Log.w(TAG, "Silent keep-alive track refused to start.", e)
|
|
113
|
+
false
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
override fun stop() = synchronized(gate) {
|
|
117
|
+
live = false
|
|
118
|
+
if (engaged) {
|
|
119
|
+
engaged = false
|
|
120
|
+
track?.takeIf { it.state == AudioTrack.STATE_INITIALIZED }?.pause()
|
|
121
|
+
}
|
|
122
|
+
stopModePoller()
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
override fun release() = synchronized(gate) {
|
|
126
|
+
live = false
|
|
127
|
+
engaged = false
|
|
128
|
+
stopModePoller()
|
|
129
|
+
track?.let {
|
|
130
|
+
if (it.state == AudioTrack.STATE_INITIALIZED) it.pause()
|
|
131
|
+
it.release()
|
|
132
|
+
}
|
|
133
|
+
track = null
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
override fun describeState(): String = synchronized(gate) {
|
|
137
|
+
val playing = track?.playState == AudioTrack.PLAYSTATE_PLAYING
|
|
138
|
+
"enabled, engaged=$engaged, built=${track != null}, playing=$playing, " +
|
|
139
|
+
"modePoller=${modePoller != null}"
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
@SuppressLint("Range")
|
|
143
|
+
private fun createSilentTrack(): AudioTrack? {
|
|
144
|
+
return try {
|
|
145
|
+
val audioManager = context.getSystemService(AUDIO_SERVICE) as AudioManager
|
|
146
|
+
val sampleRate = positiveOrDefault(
|
|
147
|
+
audioManager.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE)?.toIntOrNull(),
|
|
148
|
+
DEFAULT_SAMPLE_RATE,
|
|
149
|
+
)
|
|
150
|
+
// One buffer worth of frames is enough — we loop it forever.
|
|
151
|
+
val frameCount = positiveOrDefault(
|
|
152
|
+
audioManager.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER)
|
|
153
|
+
?.toIntOrNull(),
|
|
154
|
+
sampleRate / 100, // ~10 ms
|
|
155
|
+
)
|
|
156
|
+
val bufferBytes = frameCount * BYTES_PER_FRAME
|
|
157
|
+
// A freshly allocated direct buffer is zero-filled, i.e. pure silence.
|
|
158
|
+
val silence = ByteBuffer.allocateDirect(bufferBytes)
|
|
159
|
+
|
|
160
|
+
val player = AudioTrack.Builder()
|
|
161
|
+
.setAudioAttributes(
|
|
162
|
+
AudioAttributes.Builder()
|
|
163
|
+
.setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
|
|
164
|
+
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
|
|
165
|
+
.build(),
|
|
166
|
+
)
|
|
167
|
+
.setAudioFormat(
|
|
168
|
+
AudioFormat.Builder()
|
|
169
|
+
.setEncoding(ENCODING)
|
|
170
|
+
.setSampleRate(sampleRate)
|
|
171
|
+
.setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
|
|
172
|
+
.build(),
|
|
173
|
+
)
|
|
174
|
+
.setBufferSizeInBytes(bufferBytes)
|
|
175
|
+
.setTransferMode(AudioTrack.MODE_STATIC)
|
|
176
|
+
.setSessionId(AudioManager.AUDIO_SESSION_ID_GENERATE)
|
|
177
|
+
.build()
|
|
178
|
+
|
|
179
|
+
val written = player.write(silence, silence.remaining(), AudioTrack.WRITE_BLOCKING)
|
|
180
|
+
val loopResult = player.setLoopPoints(0, frameCount - 1, -1)
|
|
181
|
+
val ready = player.state == AudioTrack.STATE_INITIALIZED &&
|
|
182
|
+
written >= 0 &&
|
|
183
|
+
loopResult == AudioTrack.SUCCESS
|
|
184
|
+
if (ready) {
|
|
185
|
+
player
|
|
186
|
+
} else {
|
|
187
|
+
Log.w(
|
|
188
|
+
TAG,
|
|
189
|
+
"Silent keep-alive track not usable " +
|
|
190
|
+
"(state=${player.state}, written=$written, loop=$loopResult); discarding.",
|
|
191
|
+
)
|
|
192
|
+
player.release()
|
|
193
|
+
null
|
|
194
|
+
}
|
|
195
|
+
} catch (e: Exception) {
|
|
196
|
+
Log.w(TAG, "Could not create silent keep-alive track.", e)
|
|
197
|
+
null
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
private fun startModePoller() {
|
|
202
|
+
if (modePoller != null) return
|
|
203
|
+
modePoller = Executors.newSingleThreadScheduledExecutor().also { poller ->
|
|
204
|
+
poller.scheduleWithFixedDelay(
|
|
205
|
+
::reapplyModeIfReset,
|
|
206
|
+
MODE_POLL_INTERVAL_MS,
|
|
207
|
+
MODE_POLL_INTERVAL_MS,
|
|
208
|
+
TimeUnit.MILLISECONDS,
|
|
209
|
+
)
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
private fun reapplyModeIfReset() {
|
|
214
|
+
// Read/write the mode on the shared audio thread so it can't race routing changes.
|
|
215
|
+
AudioDeviceManager.runInAudioThread {
|
|
216
|
+
if (!live) return@runInAudioThread
|
|
217
|
+
val audioManager = context.getSystemService(AUDIO_SERVICE) as AudioManager
|
|
218
|
+
if (audioManager.mode != AudioManager.MODE_IN_COMMUNICATION) {
|
|
219
|
+
Log.d(TAG, "Re-applying MODE_IN_COMMUNICATION after a platform reset.")
|
|
220
|
+
audioManager.mode = AudioManager.MODE_IN_COMMUNICATION
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
private fun stopModePoller() {
|
|
226
|
+
modePoller?.shutdownNow()
|
|
227
|
+
modePoller = null
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
private fun positiveOrDefault(value: Int?, default: Int): Int =
|
|
231
|
+
if (value != null && value > 0) value else default
|
|
232
|
+
|
|
233
|
+
companion object {
|
|
234
|
+
private const val TAG = StreamInCallManagerModule.TAG
|
|
235
|
+
private const val DEFAULT_SAMPLE_RATE = 16000
|
|
236
|
+
private const val ENCODING = AudioFormat.ENCODING_PCM_16BIT
|
|
237
|
+
|
|
238
|
+
// We always emit mono 16-bit PCM, so a frame is exactly 2 bytes.
|
|
239
|
+
private const val BYTES_PER_FRAME = 2
|
|
240
|
+
|
|
241
|
+
// Fallback poll cadence. Deliberately shorter than AOSP's ~6s reset window: the
|
|
242
|
+
// interval bounds only the worst-case wrong-routing gap (<= interval), while the
|
|
243
|
+
// real setMode/HAL transition rate stays governed by the OS re-arm, so polling
|
|
244
|
+
// faster only costs cheap reads and shortens the recovery latency.
|
|
245
|
+
private const val MODE_POLL_INTERVAL_MS = 2000L
|
|
246
|
+
}
|
|
247
|
+
}
|
package/android/src/main/java/com/streamvideo/reactnative/callmanager/StreamInCallManagerModule.kt
CHANGED
|
@@ -75,6 +75,18 @@ class StreamInCallManagerModule(reactContext: ReactApplicationContext) :
|
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
@ReactMethod
|
|
79
|
+
fun setDisableCommunicationModeWorkaround(disabled: Boolean) {
|
|
80
|
+
AudioDeviceManager.runInAudioThread {
|
|
81
|
+
if (audioManagerActivated) {
|
|
82
|
+
Log.e(TAG, "setDisableCommunicationModeWorkaround(): AudioManager is already activated and so it cannot be changed")
|
|
83
|
+
return@runInAudioThread
|
|
84
|
+
}
|
|
85
|
+
Log.d(TAG, "setDisableCommunicationModeWorkaround(): $disabled")
|
|
86
|
+
mAudioDeviceManager.disableCommunicationModeWorkaround = disabled
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
78
90
|
@ReactMethod
|
|
79
91
|
fun setDefaultAudioDeviceEndpointType(endpointDeviceTypeName: String) {
|
|
80
92
|
AudioDeviceManager.runInAudioThread {
|
|
@@ -129,10 +141,8 @@ class StreamInCallManagerModule(reactContext: ReactApplicationContext) :
|
|
|
129
141
|
AudioDeviceManager.runInAudioThread {
|
|
130
142
|
if (audioManagerActivated) {
|
|
131
143
|
Log.d(TAG, "stop() mAudioDeviceManager")
|
|
132
|
-
reactApplicationContext.currentActivity
|
|
133
|
-
|
|
134
|
-
audioManagerActivated = false
|
|
135
|
-
}
|
|
144
|
+
mAudioDeviceManager.stop(reactApplicationContext.currentActivity)
|
|
145
|
+
audioManagerActivated = false
|
|
136
146
|
setMicrophoneMute(false)
|
|
137
147
|
setKeepScreenOn(false)
|
|
138
148
|
}
|
|
@@ -185,7 +195,8 @@ class StreamInCallManagerModule(reactContext: ReactApplicationContext) :
|
|
|
185
195
|
|
|
186
196
|
@ReactMethod(isBlockingSynchronousMethod = true)
|
|
187
197
|
fun getAudioStateLog(): String {
|
|
188
|
-
return WebRtcAudioUtils.getAudioStateLog(reactApplicationContext)
|
|
198
|
+
return WebRtcAudioUtils.getAudioStateLog(reactApplicationContext) +
|
|
199
|
+
"Communication mode keep-alive: ${mAudioDeviceManager.communicationModeKeepAliveState()}\n"
|
|
189
200
|
}
|
|
190
201
|
|
|
191
202
|
@Suppress("unused")
|
|
@@ -164,7 +164,7 @@ const CallContent = ({
|
|
|
164
164
|
supportedReactions
|
|
165
165
|
};
|
|
166
166
|
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
|
|
167
|
-
children: [!disablePictureInPicture && /*#__PURE__*/(0, _jsxRuntime.jsx)(_RTCViewPipIOS.RTCViewPipIOS, {
|
|
167
|
+
children: [_reactNative.Platform.OS === 'ios' && !disablePictureInPicture && /*#__PURE__*/(0, _jsxRuntime.jsx)(_RTCViewPipIOS.RTCViewPipIOS, {
|
|
168
168
|
includeLocalParticipantVideo: iOSPiPIncludeLocalParticipantVideo,
|
|
169
169
|
mirror: mirror
|
|
170
170
|
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|