Subject: TrackCompositeEgress for Internal Streami...
# ask-ai
h
Subject: TrackCompositeEgress for Internal Streaming Without Pre-existing Track IDs Context: We are building a live streaming platform where hosts stream their scenes to YouTube. We have two streaming types: 1. External — OBS pushes via RTMP to LiveKit Ingress → we use
TrackCompositeEgress
with ingress track IDs ✅ works perfectly 2. Internal — Host shares browser tab (scene URL) via WebRTC screen share → we want to use
TrackCompositeEgress
but facing issues Current Problem: For internal streaming, we currently use
WebEgress
which opens our display page (
/display
) in a headless browser. This causes duplicate audio — the display page plays participant audio AND the host hears it echoed back through their screen share capture. What We Tried: We want to switch internal streaming to
TrackCompositeEgress
to avoid the headless browser and duplicate audio issue. However: When stream starts, the host may not have published their screen share tracks yet — so
TrackCompositeEgress
fails with
invalid_argument: audio_track_id or video_track_id missing
We tested passing
nil
for track IDs — LiveKit returns
invalid_argument
— so empty track IDs are not supported 1. We need the egress to capture screen share audio + video when host starts screen sharing, and fall back to mic + camera when not sharing Questions: Is there a way to create
TrackCompositeEgress
without specifying track IDs upfront and have LiveKit auto-select tracks as they are published? Is there a
track_published
webhook event we can listen to so we can create egress after the host publishes their screen share tracks? 1. Can we update an existing
TrackCompositeEgress
to swap track IDs when the host switches between camera and screen share? 2. What is the recommended approach for capturing a host's screen share + audio for streaming to RTMP when tracks are published after egress creation? Tech Stack: Ruby on Rails backend, LiveKit Ruby SDK, Next.js frontend, LiveKit JS SDK
✅ 1
t
Thanks for reaching out, Chandan. I'm an AI assistant and I'm here to help. Give me a moment while I research this topic.
Thanks — I’m responding to your questions about using TrackCompositeEgress for internal screen-share streaming and avoiding the WebEgress duplicate-audio problem. Short answers and recommended approach • You cannot create a TrackCompositeEgress without valid audio/video track IDs. Empty or nil IDs are rejected with
invalid_argument
. • LiveKit does not auto-select tracks for an existing TrackCompositeEgress, and you cannot update an active TrackCompositeEgress to swap track IDs. • There is a
track_published
webhook you can listen to. Use it to detect when the host publishes screen-share (video and/or screen audio). Recommended pattern for your use case 1. Start no egress initially (avoid WebEgress) or leave any egress stopped. 2. Subscribe to
track_published
webhooks for the room. 3. When you receive
track_published
for the host’s screen-share tracks (identify by track kind or metadata indicating SCREEN_SHARE): ◦ Create a TrackCompositeEgress using those screen-share track IDs (audio/video) so the egress captures screen audio+video directly — no headless browser needed. 1. If the host hasn’t published screen share and you must start streaming immediately: ◦ Create TrackCompositeEgress using mic+camera track IDs (host’s published tracks) as a fallback. ◦ When a screen-share
track_published
arrives, stop the current egress and start a new TrackCompositeEgress with the screen-share track IDs (since you cannot swap IDs on a running egress). 1. To minimize downtime when switching, pre-plan logic to quickly stop/start egresses and keep RTMP destination consistent (same RTMP target) so the transition is fast and transparent on YouTube. Implementation notes for your stack • Use the LiveKit Ruby SDK on your Rails backend to: ◦ Receive and validate
track_published
webhooks. ◦ Look up the published track IDs for that participant (room/participant/track info) and then call TrackCompositeEgress creation with those IDs. • On Next.js/LiveKit JS, ensure you set clear track metadata or identify screen-share tracks by `source`/`name` so server-side logic can reliably detect screen-share vs camera/mic. • Stopping and restarting egress is the practical way to swap inputs because active egress config is immutable. If helpful, I can: • Provide a concise Rails webhook handler example that detects screen-share tracks and calls the Ruby SDK to start TrackCompositeEgress. • Sketch the minimal fallback flow (start with mic+cam, replace with screen-share on publish) including the exact webhook fields to check. Which would you prefer? Sources: Universal Egress | LiveKit | server-sdk-js | livekit/livekit_composite | DeepWiki | Best practices for managing webhook event streams | LiveKit | Server-side | livekit/livekit_composite | DeepWiki, with interactive elements
h
any other solution
t
Thanks — I hear you want alternatives that avoid frequent stop/start of TrackCompositeEgress and still prevent duplicate audio. Options to consider (short list): • Use RoomCompositeEgress instead of TrackCompositeEgress ◦ Start a RoomCompositeEgress for the whole room (no per-track IDs). It captures all published tracks and lets you compose/layout streams server-side. This removes the need to know track IDs up front. Note: composition is room-wide, so you’ll need to filter/select what you want in the final layout. • Client-side mix + single published mixed track ◦ Have the host (or a lightweight mixing participant) mix screen video + the desired audio locally (or in your app), then publish a single “stream” track to the room. Start TrackCompositeEgress against that single mixed track ID. This avoids server-side switching and prevents echo because the browser capture excludes playback audio. • Use a small dedicated “muxer” participant/Bot that joins and captures locally ◦ Run a minimal headless or server-side participant that subscribes to the host’s tracks (or the room), performs mixing (mute playback to avoid echo), and re-publishes a stable track ID for egress to use. This keeps egress stable and offloads switching to the muxer. • Faster stop/start orchestration (optimized fallback) — keep if other options infeasible ◦ Start with mic+cam egress, listen for
track_published
for screen-share, then stop/start TrackCompositeEgress quickly. Keep the same RTMP target to reduce visible transitions; optimize for minimal downtime. Questions to pick the best path: • Do you need selective per-participant composition (only host), or is capturing the entire room acceptable? • Can you run a small mixing process (client or server-side bot), or must all logic stay purely server-side without extra participants? Tell me which constraints apply and I’ll give a focused implementation sketch (Rails webhook + Ruby SDK, or a small muxer design). , with interactive elements
h
Multiple participants can share their screen, not just the host. The scene layout determines what gets streamed — it's a custom Rails page that renders: • Video feed elements (showing specific participants' camera/screen share) • Text overlays, graphics, banners • Custom layouts with multiple participants So we need to capture the entire scene layout as rendered, not individual tracks. This makes
RoomCompositeEgress
with a custom layout URL the most suitable option. Updated preferred approach:
RoomCompositeEgress
with custom layout URL
— if it supports providing our own display page URL (like
WebEgress
does), this would be perfect because: • No track ID management needed • Captures the full scene with all overlays and elements • No duplicate audio since it's a separate headless browser context • Handles any participant's screen share automatically • No stop/start needed when participants switch between camera and screen share Key question: Can
RoomCompositeEgress
accept a custom layout URL (our scene display page at
<https://our-domain.com/display/:uuid>
) similar to how
WebEgress
works? If yes, this solves all our problems cleanly. If not, what is the recommended way to stream a custom scene layout that includes multiple participants' tracks, overlays, and dynamic elements to RTMP?
t
Thanks — I’m responding to your key question about using RoomCompositeEgress with your custom scene URL and the best way to stream a fully custom layout (multiple participants, overlays, dynamic screen shares) to RTMP. Short answer • RoomCompositeEgress does not accept an arbitrary external URL (like
https://our-domain.com/display/:uuid
) in the same way WebEgress does. • RoomCompositeEgress uses the Egress Template SDK: you must provide a template (hosted for the egress service) that implements your layout logic. The egress service runs the template in a headless browser and captures the composed output. • WebEgress is the feature that captures a supplied web page URL directly, but RoomCompositeEgress won’t load your Rails page by URL. What that means for your requirements • If you need the exact Rails-rendered page (your scene HTML/CSS/JS) captured as-is, WebEgress is the intended match because it accepts a page URL. If WebEgress supports RTMP in your environment, it solves your needs directly. • If you must use RoomCompositeEgress (for RTMP support or other reasons), you’ll need to reproduce your scene within an Egress Template. The template can: ◦ Render multiple participant tracks, screen shares, overlays, banners, and dynamic layouts. ◦ Be driven by the same data/logic you use in your Rails page, but implemented inside the template (web code run by the egress service). ◦ Capture any participant’s screen share automatically because it subscribes to room tracks. Alternatives and trade-offs • WebEgress (capture your actual display URL): Easiest to preserve exact Rails rendering and page logic. Confirm whether your deployment’s WebEgress supports RTMP output if RTMP is required. • RoomCompositeEgress + Egress Template SDK: More work to port your scene into the template, but offers server-managed composition and is compatible with RoomComposite workflows (and captures screen shares and overlays via template logic). • Hybrid: Replicate only the dynamic composition parts in the template (reuse components) and keep nonessential UI in Rails to minimize porting. Recommended next step (pick one) • If capturing the Rails page exactly is highest priority: try WebEgress for the display URL and verify RTMP support in your environment. • If RTMP support requires RoomCompositeEgress or you want the egress to control composition: plan to port the scene into an Egress Template. I can help sketch how to map your Rails components to a template (track placement, overlay injection, layout rules). Which path do you prefer — test WebEgress for RTMP, or get a template-sketch to port your Rails scene into RoomCompositeEgress? Sources: Universal Egress | LiveKit | server-sdk-js | livekit/livekit_composite | DeepWiki, with interactive elements