Skip to content

feat(react): Embeddable/pre-built video components#2117

Merged
jdimovska merged 173 commits intomainfrom
embedded-component
Feb 27, 2026
Merged

feat(react): Embeddable/pre-built video components#2117
jdimovska merged 173 commits intomainfrom
embedded-component

Conversation

@jdimovska
Copy link
Copy Markdown
Contributor

@jdimovska jdimovska commented Feb 10, 2026

💡 Overview

This PR adds two new components <EmbeddedMeeting /> and <EmbeddedLivestream /> that embed a complete video experience from a single tag. The consumer provides configuration like apiKey, callId, user credentials, layout options and the component handles client and call initialization, provider wiring, and all state transitions from lobby to active call to post-call cleanup. Both components ship as a separate bundle entry point so existing SDK consumers are unaffected.

📝 Implementation notes

🎫 Ticket: https://linear.app/stream/issue/REACT-753/stream-video-embedded

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

Summary by CodeRabbit

  • New Features

    • Added full Embedded SDK: EmbeddedCall and EmbeddedLivestream drop-in components, provider, routing, host/viewer flows, lobbies, layouts, controls, participant views, feedback, connection notifications, and viewers count.
    • New hooks: client/call initialization, call duration, layout resolution, livestream sorting, noise-cancellation loader, wake-lock, and livestream pause detection.
  • Packaging

    • Public package entrypoints and exports added for embedded surface.
  • Style & Translations

    • New embedded styles, icons, and expanded English translations.

- hide buttons when video is disabled in dashboard
- improve livestream waiting room
- remove option for name change in Lobby
- add layout as initial prop to EmbeddedStreamClient.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: 3

🧹 Nitpick comments (5)
packages/react-sdk/src/embedded/livestream/viewer/ViewerLayout.tsx (2)

144-155: Consider managing focus when the participants sidebar opens.

The toggle button correctly sets aria-pressed, but when the sidebar opens, focus remains on the toggle. Keyboard and screen-reader users would benefit from focus being moved into the CallParticipantsList (or its close button) when it appears, and returned to the toggle when it closes. This is a minor accessibility improvement that can be deferred.

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

In `@packages/react-sdk/src/embedded/livestream/viewer/ViewerLayout.tsx` around
lines 144 - 155, When the participants sidebar opens/ closes, move keyboard
focus so screen-reader and keyboard users aren't left on the toggle: add a ref
to the CompositeButton used for the toggle (reference the existing
CompositeButton and handleToggleParticipants) and add a ref inside the
CallParticipantsList (e.g., to its first focusable element or its close button).
Use an effect (watching the showParticipants boolean) to focus the
CallParticipantsList element when showParticipants becomes true and to return
focus to the CompositeButton ref when it becomes false; ensure the
CallParticipantsList component exposes a focusable element via a forwarded ref
or a prop so the effect can call .focus().

89-101: Extract the duplicated live-info block into a shared sub-component.

The live badge + ViewersCount + elapsed time JSX (lines 51–60) is duplicated verbatim here (lines 90–99). Extracting it into a small LiveInfo component (or a local render helper) would eliminate the duplication and make future changes safer.

♻️ Example extraction
+const LiveInfo = ({ elapsed, participantCount, t }: {
+  elapsed: string | undefined;
+  participantCount: number;
+  t: (key: string) => string;
+}) => (
+  <div className="str-video__embedded-livestream-duration">
+    <span className="str-video__embedded-livestream-duration__live-badge">
+      {t('Live')}
+    </span>
+    <ViewersCount count={participantCount} />
+    {elapsed && (
+      <span className="str-video__embedded-livestream-duration__elapsed">
+        {elapsed}
+      </span>
+    )}
+  </div>
+);

Then use <LiveInfo ... /> in both the header and the desktop controls section.

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

In `@packages/react-sdk/src/embedded/livestream/viewer/ViewerLayout.tsx` around
lines 89 - 101, The live-info JSX (Live badge + ViewersCount + elapsed)
duplicated in ViewerLayout.tsx should be extracted into a small shared component
(e.g., function LiveInfo) that accepts props like count (participantCount) and
elapsed; implement LiveInfo to render the same structure (including calling
t('Live') or accepting a label prop) and replace both inline blocks (the header
and the .str-video__embedded-desktop section) with <LiveInfo
count={participantCount} elapsed={elapsed} /> so future changes are made in one
place and prop names match existing symbols ViewersCount, participantCount,
elapsed and the i18n usage.
packages/react-sdk/package.json (1)

8-12: Move "types" before "import" in the "." export entry.

The TypeScript 4.7 docs explicitly state: "The 'types' condition should always come first in 'exports'." In the "." entry, "types" is last, whereas "./embedded" already has it first. The "types" field must come before other conditions for TypeScript to pick it up correctly — if it's placed after, TypeScript will ignore it when consumers use moduleResolution: "bundler", "node16", or "nodenext" (which evaluate conditions in key order). The top-level "types" field provides a fallback for older tooling but not for these modern resolution modes.

♻️ Proposed fix
  ".": {
+   "types": "./dist/index.d.ts",
    "import": "./dist/index.es.js",
    "require": "./dist/index.cjs.js",
-   "types": "./dist/index.d.ts"
  },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/react-sdk/package.json` around lines 8 - 12, Reorder the exports
entry for the "." export in package.json so that the "types" condition appears
before "import" and "require"; specifically update the object keys within the
"." export entry (currently listing "import", "require", "types") to place
"types" first (e.g., "types", "import", "require") so TypeScript's condition
evaluation picks up the declaration file for modern moduleResolution modes;
mirror the same ordering used in the "./embedded" export entry.
packages/react-sdk/src/embedded/call/CallControls.tsx (1)

51-61: Duration display is duplicated between CallHeader and CallControls.

Both components independently call useCallSession() and useCallDuration(startedAt) to render nearly identical duration markup (icon + elapsed span). This appears intentional for responsive layouts (header vs. control bar), but the repeated hook calls and JSX could be extracted into a small shared <CallDuration /> presentational component to keep things DRY.

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

In `@packages/react-sdk/src/embedded/call/CallControls.tsx` around lines 51 - 61,
Extract the duplicated duration UI and hook usage into a small presentational
component (e.g., CallDuration) and replace the inline markup in both
CallControls and CallHeader with that component; specifically, move the
useCallSession() / useCallDuration(startedAt) logic and the Icon + span JSX into
CallDuration, export it from the same folder, and import/use <CallDuration /> in
place of the existing duplicated blocks in CallControls.tsx and CallHeader so
both components render the same duration UI without repeating hook calls or JSX.
packages/react-sdk/src/embedded/livestream/host/HostLayout.tsx (1)

9-9: Consolidate the two imports from ../../hooks into one.

useLayout (Line 9) and useCallDuration (Line 26) are both imported from the same path.

♻️ Proposed fix
-import { useLayout } from '../../hooks';
 import {
   ...
 } from '../../../components';
-import { useCallDuration } from '../../hooks';
+import { useCallDuration, useLayout } from '../../hooks';

Also applies to: 26-26

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

In `@packages/react-sdk/src/embedded/livestream/host/HostLayout.tsx` at line 9,
Import consolidation: combine the two separate imports from '../../hooks' into a
single import statement that includes both useLayout and useCallDuration to
avoid duplicate module resolutions; update the import at the top of
HostLayout.tsx so the single import lists useLayout and useCallDuration together
(referencing the existing useLayout and useCallDuration symbols) and remove the
extra import line.
🤖 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-sdk/src/embedded/livestream/host/HostLayout.tsx`:
- Around line 77-95: Wrap or update the livestreamStatus container (the JSX
constant livestreamStatus in HostLayout/HostLayout.tsx) to be an ARIA live
region so screen readers are notified when the status or elapsed time changes:
add an appropriate aria-live value (e.g., "polite" or "assertive" depending on
urgency) and aria-atomic="true" to the element that holds the status text (the
div containing the Live/Backstage span, ViewersCount, and elapsed span) so
dynamic changes to the Live/Backstage label and elapsed time are announced;
ensure you do this on the same element rendered as livestreamStatus so
ViewersCount remains unchanged.
- Around line 179-196: The Stop/Go Live buttons in HostLayout.tsx are missing
explicit types and may submit ancestor forms; update the two button elements
with classNames "str-video__embedded-end-stream-button" and
"str-video__embedded-go-live-button" to include type="button" (the buttons that
call onStopLive and onGoLive respectively) so they do not default to
type="submit".
- Around line 33-46: The SVG in the StartBroadcastIcon component is decorative
and should not be announced by screen readers; update the <svg> element inside
StartBroadcastIcon to include aria-hidden="true" (and optionally
focusable="false" for older IE) so only the button's visible text is announced,
keeping the component unchanged otherwise.

---

Nitpick comments:
In `@packages/react-sdk/package.json`:
- Around line 8-12: Reorder the exports entry for the "." export in package.json
so that the "types" condition appears before "import" and "require";
specifically update the object keys within the "." export entry (currently
listing "import", "require", "types") to place "types" first (e.g., "types",
"import", "require") so TypeScript's condition evaluation picks up the
declaration file for modern moduleResolution modes; mirror the same ordering
used in the "./embedded" export entry.

In `@packages/react-sdk/src/embedded/call/CallControls.tsx`:
- Around line 51-61: Extract the duplicated duration UI and hook usage into a
small presentational component (e.g., CallDuration) and replace the inline
markup in both CallControls and CallHeader with that component; specifically,
move the useCallSession() / useCallDuration(startedAt) logic and the Icon + span
JSX into CallDuration, export it from the same folder, and import/use
<CallDuration /> in place of the existing duplicated blocks in CallControls.tsx
and CallHeader so both components render the same duration UI without repeating
hook calls or JSX.

In `@packages/react-sdk/src/embedded/livestream/host/HostLayout.tsx`:
- Line 9: Import consolidation: combine the two separate imports from
'../../hooks' into a single import statement that includes both useLayout and
useCallDuration to avoid duplicate module resolutions; update the import at the
top of HostLayout.tsx so the single import lists useLayout and useCallDuration
together (referencing the existing useLayout and useCallDuration symbols) and
remove the extra import line.

In `@packages/react-sdk/src/embedded/livestream/viewer/ViewerLayout.tsx`:
- Around line 144-155: When the participants sidebar opens/ closes, move
keyboard focus so screen-reader and keyboard users aren't left on the toggle:
add a ref to the CompositeButton used for the toggle (reference the existing
CompositeButton and handleToggleParticipants) and add a ref inside the
CallParticipantsList (e.g., to its first focusable element or its close button).
Use an effect (watching the showParticipants boolean) to focus the
CallParticipantsList element when showParticipants becomes true and to return
focus to the CompositeButton ref when it becomes false; ensure the
CallParticipantsList component exposes a focusable element via a forwarded ref
or a prop so the effect can call .focus().
- Around line 89-101: The live-info JSX (Live badge + ViewersCount + elapsed)
duplicated in ViewerLayout.tsx should be extracted into a small shared component
(e.g., function LiveInfo) that accepts props like count (participantCount) and
elapsed; implement LiveInfo to render the same structure (including calling
t('Live') or accepting a label prop) and replace both inline blocks (the header
and the .str-video__embedded-desktop section) with <LiveInfo
count={participantCount} elapsed={elapsed} /> so future changes are made in one
place and prop names match existing symbols ViewersCount, participantCount,
elapsed and the i18n usage.

Comment thread packages/react-sdk/src/embedded/livestream/host/HostLayout.tsx Outdated
Comment thread packages/react-sdk/src/embedded/livestream/host/HostLayout.tsx
Comment thread packages/react-sdk/src/embedded/livestream/host/HostLayout.tsx
Comment thread packages/react-sdk/src/embedded/call/CallStateRouter.tsx Outdated
Comment thread packages/react-sdk/src/embedded/hooks/useInitializeVideoClient.ts Outdated
Comment thread packages/react-sdk/src/embedded/hooks/useIsLivestreamPaused.ts Outdated
Comment thread packages/react-sdk/src/embedded/hooks/useLivestreamSortPreset.ts Outdated
@oliverlaz oliverlaz changed the title feat(react): introduce drop-in embedded video components feat(react): Embeddable/pre-built video components Feb 25, 2026
@jdimovska jdimovska merged commit 11b4b9f into main Feb 27, 2026
19 checks passed
@jdimovska jdimovska deleted the embedded-component branch February 27, 2026 09:07
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.

2 participants