Skip to content

chore: backport #2441, #2450, #2453, #2448, #2460, #2427 and #2465 to v1 - #2468

Merged
oliverlaz merged 7 commits into
release-v1from
backport/v1-2026-09-28
Sep 28, 2026
Merged

oliverlaz merged 7 commits into
release-v1from
backport/v1-2026-09-28

Conversation

@oliverlaz

Copy link
Copy Markdown
Member

💡 Overview

Backports the merged backport-v1 PRs to release-v1, in merge order. Every commit keeps its original author and carries a (cherry picked from commit ...) trailer pointing at the commit on main.

Skipped: #2456. Its changes are already carried by the #2427 backport; trial-picking it on top of this branch produces no changes.

Important

Merge with Rebase and merge, not squash, and only once every check below is green. release-v1 has no required status checks, so the button works while CI is still running. Squashing replaces the commit messages with this description and drops the authorship and cherry-pick trailers.

Note

#2465 raises the RN SDK's @stream-io/react-native-webrtc peer range to ^145.4.1, matching main. Apps on latest will need to upgrade WebRTC.

📝 Implementation notes

release-v1 has neither #2422 (single ClientState) nor #2436 (shared i18n runtime), and neither is flagged for backport. The adapted picks rewrite against the APIs v1 still has instead of pulling those in; each commit body records exactly what changed.

✅ Verification (local)

  • Typecheck: no new errors versus untouched release-v1 in client, react-native-sdk, react-native-callingx, noise-cancellation-react-native, and the RN dogfood, with the workspace packages resolved to this branch's v1 sources.
  • client tests: 1248 passed. Call.test.ts, StreamVideoClient.test.ts and StreamVideoClient.ringing.test.ts need STREAM_SECRET and fail to load locally, same as on release-v1; they run here in CI.
  • react-native-sdk tests: 172 passed (20 suites), against this branch's built v1 client and bindings.
  • Dogfood tests: 33 passed.
  • ESLint on all changed files: clean.
  • Not covered locally: the native Kotlin and Swift changes in fix(react-native-callingx): use <packageName>:<callId> as the Android Telecom address #2453 and fix(react-native): iOS Picture in Picture window size must be reported #2448, which only the Android and iOS CI builds exercise.

🎫 Ticket: https://linear.app/stream/issue/REACT-1190

santhoshvai and others added 7 commits September 28, 2026 15:02
Fixes a race where join() could keep retrying after leave(), causing a
call to rejoin after the user had left.

Stops pending join setup and retries when a leave occurs. Normal retries
and explicitly joining again still work.
Adds regression coverage for leaving during setup, an active join
attempt, and retry delays.

🎫 Ticket: https://linear.app/stream/issue/XYZ-123

📑 Docs: https://github.com/GetStream/docs-content/pull/<id>

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

* **Bug Fixes**
* Improved call lifecycle handling when leaving during an in-progress
join or reconnect.
* Prevented interrupted operations from continuing setup, retries,
reconnection, media setup, or connection work.
* Ensured interrupted calls remain in the left state without creating or
restoring call sessions.
* Prevented late responses and failures from triggering unwanted call
updates or cleanup.
* Preserved normal retry, error handling, and rejoining behavior when no
leave interruption occurs.

* **Tests**
* Added coverage for leave races across joining, reconnecting, retries,
teardown, and error scenarios.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Backport note: adapted to `release-v1`, which does not have the
`ClientState` refactor (#2422): `clientState` -> `clientStore` and
`ClientState` -> `StreamVideoWriteableStateStore`. No behaviour change.

(cherry picked from commit 1936cb5)
…#2450)

Replaced RTCView with new camera preview view for outgoing call
component

🎫 Ticket:
https://linear.app/stream/issue/RN-427/outgoing-call-component-fix

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

* **Bug Fixes**
* Improved the outgoing call screen’s camera preview behavior by
reflecting the current camera mute state immediately.
* Updated the preview presentation for better consistency with the call
lobby experience.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Backport note: adapted to `release-v1`, which does not have the shared
i18n runtime (#2436): `useI18n` keeps coming from
`@stream-io/video-react-bindings` instead of `../../../i18n`. No
behaviour change.

(cherry picked from commit e8e360b)
… Telecom address (#2453)

### 💡 Overview

On ColorOS / Realme / Oppo devices, registering a Telecom call whose
address has a null scheme crashes `system_server` and reboots the device
(`PhoneNumberUtilsExtImpl.getNumberFromIntent` NPE →
`UserCallIntentProcessor.blockAndLaunchSystemDialer`). callingx built
the address with `String.toUri()` from an opaque Stream user id, which
parses with `scheme == null`. Reproduced with `enableOngoingCalls:
true`; Pixel/Samsung are unaffected. `main` (1.0.0-beta) still ships
this.

This PR:

1. Cherry-picks the contributor's fix from #2452 (authorship preserved),
which introduced `toTelecomAddress` and a `sip:` scheme.
2. Aligns the Android address with the Stream Android SDK: scheme = app
package name, so the address is `<packageName>:<callId>`.

### 📝 Implementation notes

**Why the package name instead of `sip:`.** Nothing displays the address
for self-managed calls; Telecom only needs a non-null scheme. AOSP
`PhoneNumberUtils.getNumberFromIntent` returns `null` for an unknown
scheme, whereas `sip:`/`tel:` make Telecom treat the handle as a
dialable number. This is exactly what the Android SDK does
(`ServiceLauncher`: `"$appSchema:${callId.id}".toUri()`, with the demo
app passing `TelecomConfig(context.packageName)`). The handle is always
wrapped, never parsed: parsing would promote anything before a `:` in
the handle to the scheme, and the address is never displayed or dialed.
With every address carrying the package-name scheme, the `tel`/`mailto`
`Person.setUri` branch in `CallNotificationManager` could no longer fire
and is removed. Contact lookup for real phone numbers can return later
as an explicit `handleType`-driven scheme (`tel:`/`mailto:`), alongside
the UUID-based `ACTION_CALL_BACK` work for core-telecom 1.1 unified call
history.

**Per-platform handle, decided in JS.** `getCallingxCallArgs` in the RN
SDK is the single source of truth for what each platform receives as
`phoneNumber`; Kotlin only guarantees a non-null scheme.

| SDK | Handle / address |
| --- | --- |
| stream-video-swift | `CXHandle(.generic, created_by_id)` — visible in
Recents, used for call-back |
| stream-video-android | `<packageName>:<callId>` — never displayed |
| callingx iOS (unchanged) | `CXHandle(.generic, createdBy.id ??
displayName)` |
| callingx Android (this PR) | `<packageName>:<callId>`; JS sends
`call.id`, the FCM push path (no JS) uses `call_cid` after the `:` |

The call type is stripped because `"default:abc".toUri()` would make
`default` the scheme.

**Side effect.** `CallNotificationManager.createPerson` uses
`Person.setKey(address.toString())`, so the key moves from per-user to
per-call. Harmless for CallStyle notifications.

**Verification.** callingx Kotlin compile with zero warnings, `yarn
lint:ci:packages`, callingx `typecheck`, `yarn test:react-native:sdk`.
No Android test source set exists in callingx; on-device ColorOS
verification with `enableOngoingCalls: true` is still pending, hence
draft.

**Out of scope.** The fate of #2452 on `release-v1` (merge as a 0.11.x
hotfix or close in favour of this) is decided separately.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
* Improved Android call addressing for devices requiring valid Telecom
addresses.
* Android Telecom routing now uses the call ID, while iOS continues
using the caller handle for CallKit display and recents.
* Standardized Android call handles as app-scoped Telecom addresses,
including incoming calls and empty or scheme-based handles.
* Android caller entries now use stable call keys without platform
contact-URI lookup.

* **Documentation**
* Clarified `phoneNumber` handling and platform-specific call address
behavior.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: sujeet kumar <ksujeetkumar7678@gmail.com>
(cherry picked from commit 22d18d8)
#2448)

### 💡 Overview

On iOS, the native Picture in Picture window renders one participant's
track, but the SFU was still asked for the dimensions of the hidden
inline layout behind it instead of the actual PiP window size.

This PR reports the actual window bounds to the SFU while PiP is on
screen, stops the hidden inline views from overwriting them, and hands
the demand back to the inline views when PiP ends.

Android subscription behaviour is unchanged.

### 📝 Implementation notes

**Why it needs coordination.** Each participant has one `videoDimension`
/ `screenShareDimension` slot per track type, and every mounted
`TrackSubscriber` writes into it, last writer wins. The PiP window is
one more writer for the same slot.

**Native (`RTCViewPip.swift`, `RTCViewPipManager.mm`)**

* New `onPiPBoundsChange` event with the laid out window size in logical
points, truncated and deduplicated, wired through the existing
`onSizeUpdate` of the PiP controller.
* Controller callbacks check that they still belong to the current
controller, and are cleared in `onCallClosed`, so a disposed controller
cannot emit.

**JS**

* `iosPipTrack.ts`: one `BehaviorSubject` per `Call` instance holding
the track the PiP window currently renders. `setIosPipTrack` clears the
previous track's demand *before* flipping the subject, so inline views
replay their cached layout synchronously and overwrite the clear. If no
inline view is mounted, the clear stands and the SFU stops sending that
track.
* `TrackSubscriber`: on iOS, one extra `combineLatest` input gates
writes. Inline subscribers stop writing while their track is owned by
PiP; the PiP writer (`isPipWriter`) only writes while it owns the track.
The writer releases ownership in a microtask so sibling inline views
finish unmounting first on whole-screen teardown.
* `RTCViewPipIOS`: feeds the bounds event into a `BehaviorSubject` and
renders a `TrackSubscriber` with `isPipWriter` for the spotlight
participant while PiP is active (never for the local participant). The
native view is keyed per `Call` instance and the handlers are bound to
that key, so events queued by a replaced call's controller are rejected.
Lifecycle events are deduplicated before reaching `isInPiPMode$` and
`onPiPChange`.
* `CallContent`: renders `RTCViewPipIOS` on iOS only.

**Tests**

* `RTCViewPipIOS.test.tsx`: bounds win over hidden layout (start-first
and bounds-first), demand transfer between participants and from camera
to screen share, clearing a track without an inline view, local-only
PiP, hand-back on unmount / `call.ended` / `LEFT`, teardown ordering,
restart during a pending stop, per-`Call` isolation with the same cid,
and rejection of stale events after call replacement.
* `TrackSubscriber.test.tsx`: PiP writer follows publication and rejoin,
inline visibility, and unchanged Android behaviour.

**Validation**

SDK lint, Prettier, spec type check, and the RN SDK test suite pass.
Coverage of the touched files is unchanged. Physical-device validation
is still pending.

🎫 Ticket: https://linear.app/stream/issue/XYZ-123

📑 Docs: https://github.com/GetStream/docs-content/pull/<id>

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
* iOS Picture-in-Picture now waits for the PiP window’s dimensions
before requesting video quality, and updates sizing when the window is
resized.
* Video demand transfers more reliably between inline and PiP views when
participants or shared content change.
* Events from a closed or replaced PiP window no longer affect the
active call.
* PiP lifecycle callbacks now report state changes consistently after
calls end or are replaced.
* **Platform Compatibility**
* The iOS-specific PiP view is rendered only on iOS when PiP is enabled.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

(cherry picked from commit b7e4165)
…m is attached (#2460)

### 💡 Overview

Closes a gap where an E2EE publisher could put unencrypted media on the
wire. The sender used to be created with its track and the encryptor
attached only after an `await`, so another negotiation (ICE restart, a
second publish) could send the track without a transform. If E2EE setup
failed partway, the half-initialized sender stayed in the transceiver
cache and a retry reused it unencrypted.

### 📝 Implementation notes

- E2EE senders are created with no track (`addTransceiver(kind)`).
`encrypt()` runs in the same task, then sender parameters, then
`replaceTrack(track)`. The sender has no media until all of these
succeed.
- On any setup failure the sender is retired: removed from
`TransceiverCache` (new `remove()`), stopped, and its clone released
(not on React Native, where clones share the native source). A sender
whose encryption succeeded but negotiation failed is kept and reused.
- `EncryptionManager.pipe` marks a target only after
`createEncodedStreams()` succeeds, so a failed attach can be retried and
a successful one is never piped twice.
- **Behavior change for custom `E2EEManager` implementations:**
`encrypt()` now receives a sender whose `track` is `null`. Use the
`codec` and `trackType` arguments instead of reading the track.
Documented on the interface and in `SPEC.md`.
- Removes the unused `e2ee/workerMessages.ts`.
- Known follow-up: if an ICE restart happens while the first and only
E2EE sender is still being set up, the restart has no tracks to announce
and falls back to a rejoin. Nothing unencrypted is sent.
- Tested with unit tests covering every failure point, the ICE-restart
race and cleanup, plus a manual E2EE call in react-dogfood.

🎫 Ticket: https://linear.app/stream/issue/REACT-1182

📑 Docs: https://github.com/GetStream/docs-content/pull/<id>

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* Improved reliability when starting encrypted audio and video
publishing. Media is attached only after encryption setup succeeds, and
failed setup is cleaned up so publishing can be retried.
* Prevented incomplete encrypted media from being announced during
connection recovery, reducing the chance of publishing interruptions or
inconsistent media state.
* Improved cleanup after encryption setup errors, helping preserve the
original error and preventing failed publishing attempts from
interfering with later retries.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

(cherry picked from commit 2419b01)
💡 Overview

Adds end-to-end encryption to the React Native SDK. Encryption runs
natively in @stream-io/react-native-webrtc, using the same wire format
as the web and iOS SDKs, so encrypted calls interoperate across
platforms.

- EncryptionManager — native-backed implementation of the core
E2EEManager interface, API-compatible with the web manager.
- StreamVideoRN.setRingingCallLifecycleHooks() — ringing calls are
joined by the SDK, not by app code, so there is no moment where the app
can attach a manager before the join. These hooks are that moment, on
every ringing path.
- Dogfood: passphrase entry, lock badge, live re-keying, key-mismatch
warning.

 📝 Implementation notes

- Call.ts: globalThis.streamRNVideoSDK bridge plus cancellation checks
reusing leaveGeneration. No new Call fields; web and non-ringing calls
are untouched.
- Setup failure or the 5s timeout aborts the join and ends the ringing
flow. Joining unencrypted on a call the user believes is private is the
worse outcome.
- One Call is one call flow — discard it after leave/cancel/failed join.
Documented contract, not an enforced ban; inherited reuse behaviour and
tests are untouched.
- dispose() is mandatory on RN (no native detach, closing peer
connections doesn't release it), and gated on teardown succeeding.

webrtc PR: GetStream/react-native-webrtc#68

docs PR: GetStream/docs-content#1586

🎫 Ticket: https://linear.app/stream/issue/RN-434/

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

- **New Features**
- Added React Native end-to-end encryption with AES-GCM key management
and status handling.
- Added encryption key entry, mismatch notifications, and an encryption
indicator during calls.
  - Encrypted deep links now preserve and apply meeting keys securely.
- Added configurable ringing-call lifecycle hooks for call preparation
and cleanup.

- **Bug Fixes**
- Improved cancellation and cleanup when calls are left during joining.
- Failed push-call joins now correctly report failure to the calling
platform.

- **Documentation**
- Added guidance and testing documentation for React Native encryption
and sample-app workflows.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: GitHub Actions Bot <>
Co-authored-by: Gabriel Donadel Dall'Agnol <donadeldev@gmail.com>
Co-authored-by: Oliver Lazoroski <oliver.lazoroski@gmail.com>

Backport note: adapted to `release-v1`, which does not have the
`ClientState` refactor (#2422) or the shared i18n runtime (#2436).

- `clientState` -> `clientStore` and `ClientState` ->
  `StreamVideoWriteableStateStore` in `Call.ts` and the two new ringing
  lifecycle test files.
- `ringingJoinIntegration.test.ts` hands the Call `client['writeableStateStore']`:
  v1 still has two stores, and `client.state` is the read-only one.
- RN SDK peer ranges for the sibling packages stay `>=0.1.0`; only the
  `@stream-io/react-native-webrtc` range and dev dependency change.
- Dogfood: adds a v1 `hooks/useAppI18n.ts` that maps the
  `t(key, fallback, options)` calls to v1's `t(key, options)`.
  `CallErrorComponent` and `ParticipantsInfoListModal` keep their v1
  strings and carry only this PR's own change.
- `yarn.lock` regenerated from v1's with `yarn install
  --mode=update-lockfile`, not merged from `main`.

(cherry picked from commit fd70b35)
Bump `@stream-io/react-native-webrtc` to `145.4.1` and remove the alpha
peer dependency range from the React Native SDK.

- React Native SDK: peer dependency is now `^145.4.1`. The
`>=145.4.0-alpha.1` range is removed.
- Helper packages (callingx, noise-cancellation, video-filters): dev
dependency bumped to `145.4.1`. Peer dependency stays at `^145.3.1`.
- Sample apps (dogfood, expo-video-sample, ringing-tutorial): updated to
`145.4.1`.

🎫 Ticket: https://linear.app/stream/issue/RN-434

📑 Docs: N/A

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

* **Chores**
* Updated the React Native WebRTC version used by calling, noise
cancellation, video filters, and sample apps to 145.4.1.
* The React Native SDK now uses and supports the 145.4.1 release,
replacing its earlier and prerelease versions. Included sample apps and
development setups are aligned with this version.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Backport note: the version bump is re-applied to `release-v1`'s own
`package.json` files, whose sibling peer ranges differ from `main`'s,
and `yarn.lock` is regenerated from v1's with `yarn install
--mode=update-lockfile` rather than merged from `main`.

(cherry picked from commit 891c4c9)
@coderabbitai

coderabbitai Bot commented Sep 28, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: 69176060-b349-448e-b00b-e8142df1ddd4

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@oliverlaz
oliverlaz requested review from greenfrvr, jdimovska and santhoshvai and removed request for jdimovska and santhoshvai September 28, 2026 14:28
@github-actions

Copy link
Copy Markdown

Bundle size

Built package output. Sizes in KB; delta vs release-v1@cfdc12b.

Package Unminified Minified Δ min vs main
@stream-io/video-client 812.2 KB 286.4 KB +2.7 KB (+0.9%)
@stream-io/video-react-sdk 365.4 KB 221.9 KB 0 KB
↳ install total (+ client + react-bindings) 1210.4 KB 520.2 KB +2.7 KB (+0.5%)
@stream-io/video-react-native-sdk 444.4 KB 204.8 KB +7.7 KB (+3.9%)
↳ install total (+ client + react-bindings) 1289.5 KB 503.2 KB +10.3 KB (+2.1%)

@santhoshvai
santhoshvai self-requested a review September 28, 2026 14:35
@oliverlaz
oliverlaz merged commit 9315fb5 into release-v1 Sep 28, 2026
19 checks passed
@oliverlaz
oliverlaz deleted the backport/v1-2026-09-28 branch September 28, 2026 14:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants