Skip to content

feat(react-sdk): show blocked audio overlay per participant#2341

Draft
jdimovska wants to merge 2 commits into
mainfrom
audio-blocked-indicator
Draft

feat(react-sdk): show blocked audio overlay per participant#2341
jdimovska wants to merge 2 commits into
mainfrom
audio-blocked-indicator

Conversation

@jdimovska

@jdimovska jdimovska commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

💡 Overview

This PR adds a blocked-audio overlay to the default React participant UI. When the browser blocks playback for a remote participant’s audio, the affected participant tile shows an overlay that lets the user tap/click to resume call audio. This mainly addresses WebKit/Safari autoplay behavior. WebKit allows audible playback when there is an active transient user gesture, but that activation is short-lived. In current WebKit source, the default transient activation duration is 5s. If remote audio arrives, or is reattached after mute/unmute, outside that activation window, audioElement.play() can reject with NotAllowedError.

📝 Implementation notes

The overlay can be customized through DefaultParticipantViewUI via the AudioBlockedNotification prop, or disabled with AudioBlockedNotification={null}.

🎫 Ticket: https://linear.app/stream/issue/REACT-1045/display-blocked-audio-per-participant

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

Summary by CodeRabbit

  • New Features

    • Added an audio-blocked notification for remote participants when browser autoplay prevents playback.
    • Users can click or tap the notification to resume audio.
    • Added a hook to access participant session IDs with blocked audio.
    • Added support for customizing or disabling the notification component.
  • Bug Fixes

    • Blocked audio state now clears automatically when playback starts or a call ends.
    • Improved tracking and recovery of audio elements that fail to resume.

@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Draft detected.

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: Pro

Run ID: 62266298-7a8f-4894-bcb5-e177248dc09c

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
📝 Walkthrough

Walkthrough

Blocked audio tracking now retains participant session IDs, exposes them through React bindings, and renders a configurable participant-view notification with resume behavior. Audio lifecycle cleanup and shared shallow-array comparison utilities are also updated.

Changes

Blocked audio experience

Layer / File(s) Summary
Session-aware blocked audio tracking
packages/client/src/helpers/BlockedAudioTracker.ts, packages/client/src/store/rxUtils.ts, packages/client/src/store/CallState.ts, packages/client/src/helpers/__tests__/BlockedAudioTracker.test.ts
Blocked audio entries retain optional session IDs, expose deduplicated session IDs, preserve failed resume entries, and use shared shallow-array comparison logic.
Audio playback lifecycle integration
packages/client/src/helpers/DynascaleManager.ts, packages/client/src/Call.ts
Playback events clear blocked state, policy failures record session IDs, and call-leave cleanup disposes the tracker and removes listeners.
React blocked-session notification flow
packages/react-bindings/src/hooks/callStateHooks.ts, packages/react-sdk/src/core/components/ParticipantView/DefaultParticipantViewUI.tsx
A hook exposes blocked session IDs, while participant views render a configurable notification for affected remote audio tracks.
Notification presentation and styling
packages/react-sdk/src/translations/en.json, packages/styling/src/AudioBlockedNotification/*, packages/styling/index.scss
English notification text and layout/theme styles are added and included in the main stylesheet.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Suggested reviewers: oliverlaz, greenfrvr

Sequence Diagram(s)

sequenceDiagram
  participant DynascaleManager
  participant BlockedAudioTracker
  participant ReactHook
  participant ParticipantView
  DynascaleManager->>BlockedAudioTracker: markBlocked(audioElement, true, sessionId)
  BlockedAudioTracker-->>ReactHook: emit blockedSessionIds$
  ReactHook-->>ParticipantView: provide blocked session IDs
  ParticipantView->>ParticipantView: render audio blocked notification
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the main change: a per-participant blocked audio overlay in the React SDK.
Description check ✅ Passed The description matches the template and includes a solid overview, implementation notes, ticket, and docs link.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch audio-blocked-indicator

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.

@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown

Bundle size

Built package output. Sizes in KB; delta vs main@ccb2273.

Package Unminified Minified Δ min vs main
@stream-io/video-client 744.9 KB 264.1 KB +597 B (+0.2%)
@stream-io/video-react-bindings 30.5 KB 9.2 KB +99 B (+1.1%)
@stream-io/video-react-sdk 183.9 KB 90.5 KB +881 B (+1.0%)
↳ install total (+ client + react-bindings) 959.4 KB 363.7 KB +1.5 KB (+0.4%)
@stream-io/video-react-sdk (embedded) 194.4 KB 94.7 KB +845 B (+0.9%)
@stream-io/video-react-native-sdk 402.5 KB 192.7 KB 0 KB
↳ install total (+ client + react-bindings) 1177.9 KB 465.9 KB +696 B (+0.1%)

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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 (2)
packages/client/src/store/CallState.ts (1)

424-427: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Multicast blockedUserIds$.

Add shareReplay({ bufferSize: 1, refCount: true }) after duc(...) so subscribers share the derived stream and cached value.

Proposed fix
-    this.blockedUserIds$ = duc(
-      this.blockedUserIdsSubject,
-      RxUtils.isShallowArrayEqual,
-    );
+    this.blockedUserIds$ = duc(
+      this.blockedUserIdsSubject,
+      RxUtils.isShallowArrayEqual,
+    ).pipe(shareReplay({ bufferSize: 1, refCount: true }));

As per coding guidelines, “For RxJS observables, use shareReplay(1) for multicast, distinctUntilChanged() to prevent unnecessary re-renders, and cache computed derived observables.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/client/src/store/CallState.ts` around lines 424 - 427, Update the
blockedUserIds$ initialization in CallState to pipe the duc(...) result through
shareReplay with bufferSize 1 and refCount true, ensuring subscribers share the
derived stream and receive its cached latest value. Reuse the existing RxJS
import conventions.

Source: Coding guidelines

packages/react-sdk/src/core/components/ParticipantView/DefaultParticipantViewUI.tsx (1)

48-54: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Handle element overrides before invoking them as components.

The prop only accepts ComponentType | null, and the render path always uses <AudioBlockedNotification />. This prevents ReactElement overrides and does not follow the SDK override contract. Accept ComponentType | ReactElement | null, use isComponentType(), and render either the component or element accordingly.

As per coding guidelines: “Handle optional component override props with defaults. Check if prop is ComponentType using isComponentType() before rendering, otherwise render as ReactElement or null.”

Also applies to: 162-164

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/react-sdk/src/core/components/ParticipantView/DefaultParticipantViewUI.tsx`
around lines 48 - 54, The AudioBlockedNotification override contract and render
path must support both component types and React elements. Update the
AudioBlockedNotification prop type, use isComponentType() before rendering it,
and render the component when true or the provided ReactElement otherwise, while
preserving null/default behavior.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/client/src/store/rxUtils.ts`:
- Around line 17-25: Update isShallowArrayEqual to compare element occurrence
counts rather than only membership, so arrays with different duplicate
frequencies are unequal while preserving order-independent shallow equality. Add
a regression test covering arrays such as [a, a, b] and [a, b, b], verifying
distinctUntilChanged does not suppress the update.

In
`@packages/react-sdk/src/core/components/ParticipantView/DefaultParticipantViewUI.tsx`:
- Around line 90-103: Update DefaultAudioBlockedNotification to validate that
useCall() returns a call context before rendering the button; throw a clear
error when it is rendered outside StreamCall, and keep call.resumeAudio as the
click handler only after the context is confirmed.

---

Nitpick comments:
In `@packages/client/src/store/CallState.ts`:
- Around line 424-427: Update the blockedUserIds$ initialization in CallState to
pipe the duc(...) result through shareReplay with bufferSize 1 and refCount
true, ensuring subscribers share the derived stream and receive its cached
latest value. Reuse the existing RxJS import conventions.

In
`@packages/react-sdk/src/core/components/ParticipantView/DefaultParticipantViewUI.tsx`:
- Around line 48-54: The AudioBlockedNotification override contract and render
path must support both component types and React elements. Update the
AudioBlockedNotification prop type, use isComponentType() before rendering it,
and render the component when true or the provided ReactElement otherwise, while
preserving null/default behavior.
🪄 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: cfd678f7-62c5-4379-98ea-e3ca43e902a3

📥 Commits

Reviewing files that changed from the base of the PR and between a9b75d0 and 6422fd7.

📒 Files selected for processing (13)
  • packages/client/src/Call.ts
  • packages/client/src/helpers/BlockedAudioTracker.ts
  • packages/client/src/helpers/DynascaleManager.ts
  • packages/client/src/helpers/__tests__/BlockedAudioTracker.test.ts
  • packages/client/src/store/CallState.ts
  • packages/client/src/store/rxUtils.ts
  • packages/react-bindings/src/hooks/callStateHooks.ts
  • packages/react-sdk/src/core/components/ParticipantView/DefaultParticipantViewUI.tsx
  • packages/react-sdk/src/translations/en.json
  • packages/styling/index.scss
  • packages/styling/src/AudioBlockedNotification/AudioBlockedNotification-layout.scss
  • packages/styling/src/AudioBlockedNotification/AudioBlockedNotification-theme.scss
  • packages/styling/src/AudioBlockedNotification/index.scss

Comment thread packages/client/src/store/rxUtils.ts
@jdimovska
jdimovska marked this pull request as draft July 22, 2026 13:14
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