Skip to content

feat: screen share audio#2157

Merged
greenfrvr merged 13 commits intomainfrom
feat/screen-share-audio
Mar 31, 2026
Merged

feat: screen share audio#2157
greenfrvr merged 13 commits intomainfrom
feat/screen-share-audio

Conversation

@greenfrvr
Copy link
Copy Markdown
Contributor

@greenfrvr greenfrvr commented Mar 11, 2026

💡 Overview

This PR contains implementation for screen share audio capturing and mixing.

For Android we intercept webrtc audio buffer and mix it with audio data captured by AudioRecord converted to PCM 16-bit format. (note: investigate stereo output case)

For iOS we presented new screen share mode – in app screen sharing, which limits for capturing only app content. For in app capturing mode we modify audio engine graph – we intercept audio signal in the end of processing chain and mix captured audio from RPScreenRecorder.

📝 Implementation notes

Corresponding PR for webrtc package: GetStream/react-native-webrtc#28

🎫 Ticket: https://linear.app/stream/issue/RN-371/screen-share-audio-capture

📑 Docs: https://github.com/GetStream/docs-content/pull/1098

Summary by CodeRabbit

  • New Features

    • Screen-share audio mixing for Android and iOS with lifecycle-managed start/stop.
    • iOS in‑app screen capture with optional audio; screen share can be broadcast or in‑app and include audio.
    • Public hook and manager to control screen‑share audio mixing; UI updated to pass screen‑share options.
  • Chores

    • Bumped native WebRTC dependency versions across packages and samples.

@greenfrvr greenfrvr self-assigned this Mar 11, 2026
@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Mar 11, 2026

⚠️ No Changeset found

Latest commit: 1976dec

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 11, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds cross-platform screen-share audio mixing: Android MediaProjection-based capture and RN bridge methods, iOS in‑app capturer + mixer integration, a JS manager and hook to control lifecycle and noise‑cancellation, UI prop to opt into in‑app/broadcast modes with audio, and sample app wiring.

Changes

Cohort / File(s) Summary
Android audio capture & bridge
packages/react-native-sdk/android/src/main/java/com/streamvideo/reactnative/screenshare/ScreenAudioCapture.kt, packages/react-native-sdk/android/src/main/java/com/streamvideo/reactnative/StreamVideoReactNativeModule.kt
Added ScreenAudioCapture (AudioPlaybackCapture using MediaProjection, Q+): start/get/stop; RN module adds startScreenShareAudioMixing/stopScreenShareAudioMixing, creates MediaProjection from Activity result, registers screenAudioBytesProvider, and cleans up on invalidate.
iOS in‑app capture & mixing bridge
packages/react-native-sdk/ios/StreamVideoReactNative.m
Conditional imports and four exported RN methods: startInAppScreenCapture(includeAudio)/stopInAppScreenCapture() and startScreenShareAudioMixing()/stopScreenShareAudioMixing(); wires ScreenShareAudioMixer into RTC audio processing and connects/clears in‑app capturer audio buffer handler.
JS manager & native delegations
packages/react-native-sdk/src/modules/ScreenShareAudioManager.ts
New ScreenShareAudioManager singleton forwarding start/stop and in‑app capture calls to native modules; iOS-only in‑app capture methods no‑op on other platforms.
Hook for lifecycle & noise‑cancellation handling
packages/react-native-sdk/src/hooks/useScreenShareAudioMixing.ts, packages/react-native-sdk/src/hooks/index.ts
New useScreenShareAudioMixing hook: starts/stops native mixing based on screen-share state and call.screenShare audio flag, temporarily disables/restores noise cancellation, exported via hooks index.
Screen share button & options
packages/react-native-sdk/src/hooks/useScreenShareButton.ts, packages/react-native-sdk/src/components/Call/CallControls/ScreenShareToggleButton.tsx
Introduces ScreenShareType/ScreenShareOptions and includeAudio; useScreenShareButton accepts screenShareOptions and handles in‑app vs broadcast flows (including enabling audio preference); toggle button forwards screenShareOptions.
Provider integration
packages/react-native-sdk/src/providers/StreamCall/index.tsx
Adds renderless ScreenShareAudioMixer component that invokes useScreenShareAudioMixing() for automatic lifecycle management.
Sample app wiring
sample-apps/react-native/dogfood/src/components/CallControlls/BottomControls.tsx
Sample now passes screenShareOptions={{ type: 'inApp', includeAudio: true }} to the screen-share toggle.
Dependency bumps
packages/noise-cancellation-react-native/package.json, packages/react-native-sdk/package.json, packages/video-filters-react-native/package.json, sample-apps/.../package.json
Bumped @stream-io/react-native-webrtc dev/peer dependency versions from 137.1.0 to 137.1.3 across packages.

Sequence Diagram

sequenceDiagram
    participant User
    participant Button as ScreenShareToggleButton
    participant Hook as useScreenShareButton
    participant MixerHook as useScreenShareAudioMixing
    participant Manager as ScreenShareAudioManager
    participant Native as NativeModule
    participant Capture as ScreenAudioCapture / ScreenShareAudioMixer

    User->>Button: Tap screen share
    Button->>Hook: start flow with screenShareOptions
    Hook->>Hook: set includeAudio / type
    Hook->>MixerHook: update desired mixing state

    alt iOS in‑app + includeAudio
        Hook->>Manager: startInAppScreenCapture(includeAudio)
        Manager->>Native: startInAppScreenCapture()
        Native->>Capture: init in‑app capturer
    else Android or broadcast
        Hook->>Hook: request MediaProjection / show system picker
    end

    alt mixing requested
        MixerHook->>Manager: startScreenShareAudioMixing()
        Manager->>Native: startScreenShareAudioMixing()
        Native->>Capture: start capture / mixer
        Capture->>Native: supply audio bytes to WebRTC mic path
    end

    User->>Button: Stop share
    Hook->>MixerHook: stop mixing
    MixerHook->>Manager: stopScreenShareAudioMixing()
    Manager->>Native: stopScreenShareAudioMixing()
    Native->>Capture: stop and cleanup

    alt iOS in‑app
        Hook->>Manager: stopInAppScreenCapture()
        Manager->>Native: stopInAppScreenCapture()
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Poem

🐰 I hopped through Android and iOS nights,
Mixing screen sounds with meeting lights,
MediaProjection and buffers hum,
A rabbit jukebox—audio mixing fun! 🎶
I thumped my foot—now screen share audio's done.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: screen share audio' accurately captures the main feature addition—screen share audio capturing and mixing for both Android and iOS platforms.
Description check ✅ Passed The PR description includes all required template sections with comprehensive technical details about the Android and iOS implementations, references the linked WebRTC PR, Linear ticket, and documentation PR.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/screen-share-audio

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 and usage tips.

@greenfrvr greenfrvr requested review from oliverlaz and santhoshvai and removed request for oliverlaz March 20, 2026 15:05
@greenfrvr greenfrvr marked this pull request as ready for review March 20, 2026 15:06
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
packages/react-native-sdk/src/hooks/useScreenShareButton.ts (1)

156-167: Inconsistent handler invocation pattern.

In the iOS in-app branch (line 163), the handler is called directly as onScreenShareStartedHandler?.(), while in the broadcast mode event listener (line 120), it's called via the ref as onScreenShareStartedHandlerRef.current?.(). The ref pattern was intentionally used to avoid stale closure issues in the effect. Consider using the ref consistently.

💡 Suggested fix for consistency
           await screenShareAudioMixingManager.startInAppScreenCapture(
             includeAudio,
           );
           await call?.screenShare.enable();
-          onScreenShareStartedHandler?.();
+          onScreenShareStartedHandlerRef.current?.();
         } catch (error) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/react-native-sdk/src/hooks/useScreenShareButton.ts` around lines 156
- 167, The iOS in-app branch in useScreenShareButton calls the start handler
directly (onScreenShareStartedHandler?.()), which is inconsistent with the
broadcast branch that uses the ref to avoid stale closures; change the direct
invocation to use the ref (onScreenShareStartedHandlerRef.current?.()) after
successful start so both branches use the same onScreenShareStartedHandlerRef
pattern used elsewhere in useScreenShareButton.
packages/react-native-sdk/ios/StreamVideoReactNative.m (1)

688-727: Consider handling the case when capturer is nil.

When activeInAppScreenCapturer is nil, the method still starts mixing and bypasses voice processing but never sets up the audioBufferHandler. This could leave the system in a partially configured state where mixing is active but no audio data flows.

💡 Suggested improvement
     // Wire audio buffer handler on the active capturer → mixer.enqueue
     InAppScreenCapturer *capturer = options.activeInAppScreenCapturer;
     if (capturer) {
         capturer.audioBufferHandler = ^(CMSampleBufferRef sampleBuffer) {
             ScreenShareAudioMixer *currentMixer = [WebRTCModuleOptions sharedInstance].screenShareAudioMixer;
             if (currentMixer) {
                 [currentMixer enqueue:sampleBuffer];
             }
         };
+    } else {
+        // No active capturer — log warning but continue; audio may start flowing later
+        NSLog(@"[StreamVideoReactNative] startScreenShareAudioMixing: No active capturer available");
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/react-native-sdk/ios/StreamVideoReactNative.m` around lines 688 -
727, The method startScreenShareAudioMixing currently starts mixing and bypasses
voice processing before checking activeInAppScreenCapturer, which can leave
mixing active with no audio handler; modify startScreenShareAudioMixing (and use
WebRTCModuleOptions, InAppScreenCapturer, startMixing,
setVoiceProcessingBypassed, audioBufferHandler) to check
options.activeInAppScreenCapturer before enabling mixing/VP bypass and, if nil,
do not start mixing or bypass voice processing and instead reject the promise
(or return an error) with a clear code/message; alternatively, if you must start
mixing first, add cleanup logic to stopMixing and restore
_vpBypassedBeforeMixing (via audioDeviceModule.setVoiceProcessingBypassed:)
before rejecting so the system is not left partially configured.
packages/react-native-sdk/android/src/main/java/com/streamvideo/reactnative/StreamVideoReactNativeModule.kt (1)

495-545: Consider checking if ScreenAudioCapture.start() actually succeeded.

After ScreenAudioCapture(mediaProjection).also { it.start() } on line 530, the audioRecord inside may still be null if initialization failed (as handled in ScreenAudioCapture.start()). The screenAudioBytesProvider callback on line 536 will then always return null, but the method resolves successfully, potentially misleading the caller.

💡 Suggested improvement to verify capture started
-            screenAudioCapture = ScreenAudioCapture(mediaProjection).also { it.start() }
+            val capture = ScreenAudioCapture(mediaProjection)
+            capture.start()
+            
+            // Verify capture actually started (audioRecord initialized successfully)
+            if (capture.getScreenAudioBytes(0) == null) {
+                // This is a lightweight check - getScreenAudioBytes returns null when audioRecord is null
+                Log.w(NAME, "Screen audio capture may not have initialized correctly")
+            }
+            screenAudioCapture = capture

Alternatively, expose an isActive property on ScreenAudioCapture to check initialization state.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/react-native-sdk/android/src/main/java/com/streamvideo/reactnative/StreamVideoReactNativeModule.kt`
around lines 495 - 545, The startScreenShareAudioMixing method currently assumes
ScreenAudioCapture.start() succeeded; change the flow to verify the capture
actually started before resolving and setting
WebRTCModuleOptions.screenAudioBytesProvider: after creating ScreenAudioCapture
(symbol: ScreenAudioCapture) call start() and check a success indicator (either
a boolean return from start(), an exposed property like isActive on
ScreenAudioCapture, or by verifying internal audioRecord != null on the instance
referenced by screenAudioCapture) and if initialization failed reject the
promise with a descriptive error and do not assign screenAudioBytesProvider;
only assign the provider, log success, and resolve the promise when the capture
is confirmed active (references: function startScreenShareAudioMixing, field
screenAudioCapture, method ScreenAudioCapture.start(), and
WebRTCModuleOptions.screenAudioBytesProvider).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/react-native-sdk/src/hooks/useScreenShareAudioMixing.ts`:
- Around line 107-129: The cleanup effect can miss stopping in-progress mixing
because isMixingActiveRef.current is set after an await in startMixing; ensure
the component can cancel an in-flight startMixing on unmount by adding an
AbortController (or set isMixingActiveRef.current = true before awaiting) inside
startMixing and/or the start/stop useEffect, pass the controller/signal to any
async media calls, and in the cleanup use the controller.abort() then call
screenShareAudioMixingManager.stopScreenShareAudioMixing() and
restoreNoiseCancellation() if needed; update references to startMixing,
isMixingActiveRef, ncWasEnabledRef,
screenShareAudioMixingManager.stopScreenShareAudioMixing, and
restoreNoiseCancellation to use the abort signal and ensure deterministic
cleanup.

---

Nitpick comments:
In
`@packages/react-native-sdk/android/src/main/java/com/streamvideo/reactnative/StreamVideoReactNativeModule.kt`:
- Around line 495-545: The startScreenShareAudioMixing method currently assumes
ScreenAudioCapture.start() succeeded; change the flow to verify the capture
actually started before resolving and setting
WebRTCModuleOptions.screenAudioBytesProvider: after creating ScreenAudioCapture
(symbol: ScreenAudioCapture) call start() and check a success indicator (either
a boolean return from start(), an exposed property like isActive on
ScreenAudioCapture, or by verifying internal audioRecord != null on the instance
referenced by screenAudioCapture) and if initialization failed reject the
promise with a descriptive error and do not assign screenAudioBytesProvider;
only assign the provider, log success, and resolve the promise when the capture
is confirmed active (references: function startScreenShareAudioMixing, field
screenAudioCapture, method ScreenAudioCapture.start(), and
WebRTCModuleOptions.screenAudioBytesProvider).

In `@packages/react-native-sdk/ios/StreamVideoReactNative.m`:
- Around line 688-727: The method startScreenShareAudioMixing currently starts
mixing and bypasses voice processing before checking activeInAppScreenCapturer,
which can leave mixing active with no audio handler; modify
startScreenShareAudioMixing (and use WebRTCModuleOptions, InAppScreenCapturer,
startMixing, setVoiceProcessingBypassed, audioBufferHandler) to check
options.activeInAppScreenCapturer before enabling mixing/VP bypass and, if nil,
do not start mixing or bypass voice processing and instead reject the promise
(or return an error) with a clear code/message; alternatively, if you must start
mixing first, add cleanup logic to stopMixing and restore
_vpBypassedBeforeMixing (via audioDeviceModule.setVoiceProcessingBypassed:)
before rejecting so the system is not left partially configured.

In `@packages/react-native-sdk/src/hooks/useScreenShareButton.ts`:
- Around line 156-167: The iOS in-app branch in useScreenShareButton calls the
start handler directly (onScreenShareStartedHandler?.()), which is inconsistent
with the broadcast branch that uses the ref to avoid stale closures; change the
direct invocation to use the ref (onScreenShareStartedHandlerRef.current?.())
after successful start so both branches use the same
onScreenShareStartedHandlerRef pattern used elsewhere in useScreenShareButton.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4af24a58-88d7-47a5-a055-a48049448684

📥 Commits

Reviewing files that changed from the base of the PR and between adf0dc1 and 9431910.

📒 Files selected for processing (10)
  • packages/react-native-sdk/android/src/main/java/com/streamvideo/reactnative/StreamVideoReactNativeModule.kt
  • packages/react-native-sdk/android/src/main/java/com/streamvideo/reactnative/screenshare/ScreenAudioCapture.kt
  • packages/react-native-sdk/ios/StreamVideoReactNative.m
  • packages/react-native-sdk/src/components/Call/CallControls/ScreenShareToggleButton.tsx
  • packages/react-native-sdk/src/hooks/index.ts
  • packages/react-native-sdk/src/hooks/useScreenShareAudioMixing.ts
  • packages/react-native-sdk/src/hooks/useScreenShareButton.ts
  • packages/react-native-sdk/src/modules/ScreenShareAudioManager.ts
  • packages/react-native-sdk/src/providers/StreamCall/index.tsx
  • sample-apps/react-native/dogfood/src/components/CallControlls/BottomControls.tsx

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
packages/react-native-sdk/ios/StreamVideoReactNative.m (1)

676-683: Make in-app stop path defensively teardown mixer wiring

stopInAppScreenCapture (Line 676-683) only flips option flags. If JS call order breaks or partial failures occur, handler/delegate cleanup may be skipped and mixing can remain wired longer than intended. Prefer idempotent teardown from this path too.

Also applies to: 718-743

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/react-native-sdk/ios/StreamVideoReactNative.m` around lines 676 -
683, The stopInAppScreenCapture method currently only flips WebRTCModuleOptions
flags; update it to perform an idempotent teardown of any mixer/wiring and
handlers so lingering delegates or active mixing cannot persist: call the shared
module's cleanup/stop methods (e.g., stop any in-app capture session, disconnect
the ScreenShareMixer or mixController, remove/nil out delegate references and
observers) and make those teardown calls safe to run multiple times; ensure you
reference WebRTCModuleOptions sharedInstance plus the relevant mixer/manager
instance (the module or ScreenShareMixer/mixController used elsewhere) and
invoke its stop/teardown and delegate-nil methods before resolving the promise.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/react-native-sdk/ios/StreamVideoReactNative.m`:
- Around line 687-716: Ensure startScreenShareAudioMixing and
stopScreenShareAudioMixing validate prerequisites before resolving: check
WebRTCModule (via [self.bridge moduleForClass:[WebRTCModule class]]), its
audioDeviceModule, and the screenShareAudioMixer and reject the promise if any
are nil; verify WebRTCModuleOptions.sharedInstance.includeScreenShareAudio is
true at the start and reject if false; when wiring the audio processing module,
ensure options.audioProcessingModule is of class RTCDefaultAudioProcessingModule
and reject if not (do not silently continue), and return early after calling
reject in each failure path so JS state stays consistent with native state.
- Around line 10-19: The import of InAppScreenCapturer.h in
StreamVideoReactNative.m is unconditional while the Swift header is guarded, and
the podspec lacks the version constraint for stream-react-native-webrtc that
package.json requires; update stream-video-react-native.podspec to add the
version constraint (>= 137.1.0) for the stream-react-native-webrtc dependency
and also wrap the InAppScreenCapturer.h import in StreamVideoReactNative.m with
the same `#if` __has_include guard (matching the Swift header guard) so the
Objective-C header is only included when available.

---

Nitpick comments:
In `@packages/react-native-sdk/ios/StreamVideoReactNative.m`:
- Around line 676-683: The stopInAppScreenCapture method currently only flips
WebRTCModuleOptions flags; update it to perform an idempotent teardown of any
mixer/wiring and handlers so lingering delegates or active mixing cannot
persist: call the shared module's cleanup/stop methods (e.g., stop any in-app
capture session, disconnect the ScreenShareMixer or mixController, remove/nil
out delegate references and observers) and make those teardown calls safe to run
multiple times; ensure you reference WebRTCModuleOptions sharedInstance plus the
relevant mixer/manager instance (the module or ScreenShareMixer/mixController
used elsewhere) and invoke its stop/teardown and delegate-nil methods before
resolving the promise.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 401622be-bd9e-405f-b7e0-dc25842c573a

📥 Commits

Reviewing files that changed from the base of the PR and between 9431910 and 3f6e739.

📒 Files selected for processing (1)
  • packages/react-native-sdk/ios/StreamVideoReactNative.m

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
packages/react-native-sdk/src/hooks/useScreenShareButton.ts (1)

165-165: Inconsistent handler invocation: direct call vs ref.

Lines 120 and 129 use onScreenShareStartedHandlerRef.current?.() and onScreenShareStoppedHandlerRef.current?.() to avoid stale closure issues in the effect. However, lines 165 and 191 call the handlers directly (onScreenShareStartedHandler?.() and onScreenShareStoppedHandler?.()). This inconsistency is safe within onPress since it's not captured in a closure, but using the refs consistently would be clearer.

♻️ Suggested consistency fix
-          onScreenShareStartedHandler?.();
+          onScreenShareStartedHandlerRef.current?.();

And at line 191:

-      onScreenShareStoppedHandler?.();
+      onScreenShareStoppedHandlerRef.current?.();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/react-native-sdk/src/hooks/useScreenShareButton.ts` at line 165, The
onPress handler in useScreenShareButton calls the handlers directly
(onScreenShareStartedHandler?.() and onScreenShareStoppedHandler?.()) which is
inconsistent with other places using refs; update the onPress logic to invoke
the ref-stored callbacks instead (use onScreenShareStartedHandlerRef.current?.()
and onScreenShareStoppedHandlerRef.current?.()) and do the same for the stopped
handler (onScreenShareStoppedHandlerRef.current?.()) so all invocations
consistently use the ref-backed callbacks and avoid stale closure issues.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/react-native-sdk/src/hooks/useScreenShareButton.ts`:
- Around line 160-169: The code starts native capture with
screenShareAudioMixingManager.startInAppScreenCapture then enables call screen
share, but if call?.screenShare.enable() throws the native capture keeps
running; modify the try block so that after startInAppScreenCapture succeeds you
attempt call?.screenShare.enable() in its own try/catch and on any failure await
a cleanup call on the audio mixing manager (e.g.
screenShareAudioMixingManager.stopInAppScreenCapture or the appropriate stop
method) to stop native capture, catch/log any cleanup errors via
videoLoggerSystem.getLogger('useScreenShareButton'), and only call
onScreenShareStartedHandler after both startInAppScreenCapture and
call?.screenShare.enable() have completed successfully.
- Around line 154-156: The call to disableScreenShareAudio() in
useScreenShareButton is not awaited, causing a race with subsequent
screenShare.enable() calls; update the handler in useScreenShareButton to await
call?.screenShare.disableScreenShareAudio() (making the enclosing function async
if it isn't already) so the Promise resolves before calling screenShare.enable()
or proceeding with state changes; ensure error handling (try/catch) or propagate
the Promise as appropriate so failures don't leave the app in an inconsistent
state.

---

Nitpick comments:
In `@packages/react-native-sdk/src/hooks/useScreenShareButton.ts`:
- Line 165: The onPress handler in useScreenShareButton calls the handlers
directly (onScreenShareStartedHandler?.() and onScreenShareStoppedHandler?.())
which is inconsistent with other places using refs; update the onPress logic to
invoke the ref-stored callbacks instead (use
onScreenShareStartedHandlerRef.current?.() and
onScreenShareStoppedHandlerRef.current?.()) and do the same for the stopped
handler (onScreenShareStoppedHandlerRef.current?.()) so all invocations
consistently use the ref-backed callbacks and avoid stale closure issues.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d47ebc65-8d38-485e-84d2-7ee6065f6f31

📥 Commits

Reviewing files that changed from the base of the PR and between 3f6e739 and ae0a08d.

📒 Files selected for processing (1)
  • packages/react-native-sdk/src/hooks/useScreenShareButton.ts

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
packages/react-native-sdk/src/hooks/useScreenShareButton.ts (1)

116-124: ⚠️ Potential issue | 🟠 Major

Await the screen-share startup calls before signaling success.

disableScreenShareAudio() is still fire-and-forget in the button handler, and the new broadcast-start effect adds the same pattern for enableScreenShareAudio() plus screenShare.enable(). If any of those promises reject, this hook can emit the started callback before anything is actually published and leave an unhandled rejection on the event/effect path.

🧭 Proposed fix
     if (
       iosScreenShareStartedFromSystem &&
       !prevIosScreenShareStartedFromSystem
     ) {
-      onScreenShareStartedHandlerRef.current?.();
-      if (includeAudio) {
-        call?.screenShare.enableScreenShareAudio();
-      }
-      call?.screenShare.enable();
+      void (async () => {
+        try {
+          if (includeAudio) {
+            await call?.screenShare.enableScreenShareAudio();
+          }
+          await call?.screenShare.enable();
+          onScreenShareStartedHandlerRef.current?.();
+        } catch (error) {
+          videoLoggerSystem
+            .getLogger('useScreenShareButton')
+            .warn('Failed to publish iOS broadcast screen share', error);
+        }
+      })();
     } else if (
       !iosScreenShareStartedFromSystem &&
       prevIosScreenShareStartedFromSystem
     ) {
       onScreenShareStoppedHandlerRef.current?.();
@@
     if (!hasPublishedScreenShare) {
-      // Set audio mixing preference before starting screen share
-      if (includeAudio) {
-        call?.screenShare.enableScreenShareAudio();
-      } else {
-        call?.screenShare.disableScreenShareAudio();
-      }
+      try {
+        if (includeAudio) {
+          await call?.screenShare.enableScreenShareAudio();
+        } else {
+          await call?.screenShare.disableScreenShareAudio();
+        }
+      } catch (error) {
+        videoLoggerSystem
+          .getLogger('useScreenShareButton')
+          .warn('Failed to configure screen share audio', error);
+        return;
+      }

Run this read-only check to confirm the method signatures and current call sites:

#!/bin/bash
rg -n -C3 --type=ts --type=tsx '\b(enableScreenShareAudio|disableScreenShareAudio)\s*\('
printf '\n--- useScreenShareButton call sites ---\n'
rg -n -C3 --type=ts --type=tsx '\bscreenShare\.enable\s*\(|\benableScreenShareAudio\s*\(|\bdisableScreenShareAudio\s*\(' packages/react-native-sdk/src/hooks/useScreenShareButton.ts

Also applies to: 150-156

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/react-native-sdk/src/hooks/useScreenShareButton.ts` around lines 116
- 124, The effect and button handler currently call call?.screenShare.enable(),
call?.screenShare.enableScreenShareAudio(), and
call?.screenShare.disableScreenShareAudio() without awaiting them, causing
onScreenShareStartedHandlerRef.current to fire before the operations complete
and leaving unhandled rejections; update the broadcast-start effect and the
button handler in useScreenShareButton to await those promises and wrap them in
try/catch (e.g., await call.screenShare.enable() and await
call.screenShare.enableScreenShareAudio()/disableScreenShareAudio() when call is
defined), only invoke onScreenShareStartedHandlerRef.current after the awaited
calls succeed, and handle/log any errors to prevent unhandled rejections while
preserving existing null-safe checks (call?).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/react-native-sdk/src/hooks/useScreenShareButton.ts`:
- Around line 193-196: The call to
screenShareAudioMixingManager.stopInAppScreenCapture() can reject and prevent
call?.screenShare.disable(true) from running, leaving the call still publishing;
wrap the stopInAppScreenCapture() invocation in a try-catch-finally (or
try/finally) inside useScreenShareButton so that any error from
stopInAppScreenCapture() is caught/logged but call?.screenShare.disable(true) is
always executed in the finally block; reference the existing symbols
screenShareAudioMixingManager.stopInAppScreenCapture() and
call?.screenShare.disable(true) and ensure errors are handled without blocking
the disable call.
- Around line 171-175: The iOS branch in useScreenShareButton calls
findNodeHandle(screenCapturePickerViewiOSRef.current) and awaits
NativeModules.ScreenCapturePickerViewManager.show(...) without guarding null or
handling errors; update the handler in useScreenShareButton to first check that
reactTag (from findNodeHandle on screenCapturePickerViewiOSRef) is non-null
before calling ScreenCapturePickerViewManager.show, wrap the await in a
try/catch, log or handle the error via the existing state updater (e.g., the
local sharing/button state) to revert any optimistic UI changes, and ensure
failure paths match the other branches' behavior so the button isn't left in an
ambiguous state.

---

Duplicate comments:
In `@packages/react-native-sdk/src/hooks/useScreenShareButton.ts`:
- Around line 116-124: The effect and button handler currently call
call?.screenShare.enable(), call?.screenShare.enableScreenShareAudio(), and
call?.screenShare.disableScreenShareAudio() without awaiting them, causing
onScreenShareStartedHandlerRef.current to fire before the operations complete
and leaving unhandled rejections; update the broadcast-start effect and the
button handler in useScreenShareButton to await those promises and wrap them in
try/catch (e.g., await call.screenShare.enable() and await
call.screenShare.enableScreenShareAudio()/disableScreenShareAudio() when call is
defined), only invoke onScreenShareStartedHandlerRef.current after the awaited
calls succeed, and handle/log any errors to prevent unhandled rejections while
preserving existing null-safe checks (call?).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1c0eb4da-174b-40c5-93fe-00d2ee181f27

📥 Commits

Reviewing files that changed from the base of the PR and between ae0a08d and a04243f.

📒 Files selected for processing (1)
  • packages/react-native-sdk/src/hooks/useScreenShareButton.ts

@greenfrvr greenfrvr merged commit ba3b9d8 into main Mar 31, 2026
17 of 18 checks passed
@greenfrvr greenfrvr deleted the feat/screen-share-audio branch March 31, 2026 08:11
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.

1 participant