Commit 9f7cd6f
feat: livekit cast improvements (#7347)
* feat: enrich getActiveVideoStreams response and auto-resolve audio for livekit user streams
Add participant metadata (name, speaking, trackName, width, height) to
GetActiveVideoStreamsResponse JSON output. Enable automatic audio track
resolution in LivekitPlayer when opening a user stream by pairing video
source to the correct audio source (camera→microphone, screenshare→screenshare audio).
* fix: resolve display name from participant metadata for castV2 support
Parse displayName from participant.Metadata JSON (used by unauthenticated
castV2 users), falling back to participant.Name then participant.Identity.
* feat: auto-recover livekit player when specific track dies
When a specific user stream track (livekit-video://identity/sid) is
unpublished, fallback to current-stream instead of retrying the same
dead address every frame. This lets EnsureVideoIsPlaying and
EnsureAudioIsPlaying self-heal by scanning for any available track.
* feat: play all audio tracks from livekit room in media player
Instead of pairing one audio track to the selected video track,
LivekitPlayer now plays every audio track in the room — so all
participants are heard regardless of which video is displayed.
* feat: auto-switch video to active speaker in CurrentStream mode
When LivekitPlayer plays in CurrentStream mode, the video now follows
the dominant speaker using room.ActiveSpeakers with a 1.5s debounce
to prevent flickering. UserStream mode remains pinned to the specified
participant. Also adds docs/cast.md documenting the full cast feature.
* fix: prevent audio disruption on video recovery, throttle empty-audio rescan, log metadata parse failures
- Add ReopenVideoStream() to recover video without tearing down audio
- Throttle empty-audio rescan to 2s interval to avoid per-frame lock
- Replace silent JsonException catch with ReportHub.LogWarning
* fix: make audio discovery additive to support late-joining participants
OpenAllAudioStreams now skips tracks that already have a source, so it
can be called periodically without disrupting healthy audio. Dead sources
are removed individually. EnsureVideoIsPlaying also drives audio discovery
since PBVideoPlayer entities don't trigger UpdateAudioStream.
* feat: expose LiveKit data messaging to scenes via CommsApi
Add PublishData, SubscribeToTopic, and ConsumeMessages methods to
CommsApiWrap, enabling scenes to send commands and receive state
updates through LiveKit data channels on arbitrary topics.
Includes rate limiting (10 msg/s per topic), 16KB payload cap,
thread-safe buffering for network-thread DataReceived events,
and proper cleanup on dispose.
* feat: expose LiveKit data messaging and metadata to scenes via CommsApi
Add PublishData, SubscribeToTopic, ConsumeMessages, and UpdateMetadata
methods to CommsApiWrap with corresponding JS module bindings, enabling
scenes to send commands, receive state updates, and set participant
roles through LiveKit data channels on arbitrary topics.
Includes rate limiting (10 msg/s per topic), 16KB payload cap,
thread-safe buffering for network-thread DataReceived events,
and proper cleanup on dispose.
Also handle non-protobuf data on the shared DataPipe gracefully in
MessagePipe by catching InvalidProtocolBufferException silently,
since JSON data from CommsApi messaging is now expected on the pipe.
* feat: prioritize presentation bot video track over active speaker
In CurrentStream mode, automatically lock onto a presentation bot's
video track when one is in the room. The bot is identified by the
identity prefix 'presentation-bot:'. Active speaker switching is
suppressed while a presentation bot is active, and resumes when the
bot leaves.
* docs: update cast.md with latest LiveKit features
Document presentation bot priority, audio rescan throttling,
video muted state tracking, resolution capping, data messaging
API, and wallet address identity format.
* refactor: LivekitPlayer review feedback — Weak.Null, hasLiveAudio, flip conditions
- Replace nullable Weak<IVideoStream>? with Weak<T>.Null struct pattern
- Rename tempStreamKeys to streamKeysBuffer
- Replace O(n) .Exists() with tracked hasLiveAudio boolean
- Flip negated if/else to positive condition first
- Clarify video-only audio discovery comment
- Remove unused IsVideoTrackMuted, videoTrackMuted, CheckVideoTrackMuted
- Mark class as main-thread only
* refactor: CommsApi review feedback — ConcurrentDictionary, ITypedArray, ctor subscription
- Subscribe to IDataPipe in constructor, make field readonly
- Replace Dictionary+List+bufferLock with ConcurrentDictionary+ConcurrentQueue
- PublishData accepts ITypedArray<byte> instead of string
- Use IJsOperations.LIVEKIT_MAX_SIZE for message size limit
- Name magic number as RATE_LIMIT_WINDOW_MS, remove unnecessary unchecked
- Simplify DisplayNameOf: remove unused metadata JSON parsing
- Flip ternary, extract computation to separate line
- Update MessagePipe catch comment with IMessagePipe migration TODO
* refactor: CommsApi.js — add type documentation, publishData passes Uint8Array
- Add parameter type comments for all exported functions
- publishData now passes Uint8Array directly to match ITypedArray<byte> C# signature
* fix: clarify EnsureAudioIsPlaying comment in LivekitPlayer
* feat: add gatekeeper mode dropdown to debug settings
Adds a GatekeeperMode selector (Org, Zone, Today, Localhost, Custom)
to DebugSettings, injecting --gatekeeper-url into app parameters.
The custom URL field is only shown when mode is set to Custom.
* feat: raise LiveKit video resolution cap to 2048x2048
* refactor: remove UpdateMetadata from CommsApi
Scene room metadata is managed internally by RemoteMetadata and would be
overwritten, making scene-set metadata unreliable.
* chore: reorder constants and remove dead code per coding standards
- Move private const fields before readonly fields in LivekitPlayer, UpdateMediaPlayerSystem, DebugSettings
- Remove unused FindPairedAudio() from LivekitPlayer (speculative code for hypothetical future)
* refactor: route CommsApi through ISceneCommunicationPipe with MsgType.CommsData
- Replace direct IDataPipe access with ISceneCommunicationPipe routing
- Add MsgType.CommsData = 3 for topic-based pub/sub data
- Wire format: [MsgType=3][topicLen 2B LE][topic UTF-8][data UTF-8]
- Move ISceneCommunicationPipe to CRDT.ECS.Bridge assembly (resolve circular dependency)
- Thread ISceneCommunicationPipe + ISceneData through RegisterAll/RegisterCommsApi
- PublishData now takes string instead of ITypedArray<byte>
- Update CommsApi.js to handle both string and Uint8Array from scenes
* fix: update MessagePipe catch comment and CommsApi.js publishData to string
- Update InvalidProtocolBufferException comment (cast2 webapp, not CommsApi)
- CommsApi.js publishData expects string data directly
* test: add unit tests for CommsApiWrap
- 12 tests covering: handler lifecycle, wire format, round-trip, topic buffering,
input validation, rate limiting, malformed message handling
* fix: rename stale test method to match MsgType.CommsData
* chore: add missing meta file for CommsApiWrapShould test
* refactor: move gatekeeper URL override from app args to URL source layer
DebugSettings no longer injects --gatekeeper-url into AppParameters.
Instead, GatekeeperMode and CustomGatekeeperUrl are exposed as
properties and resolved in MainSceneLoader into a base URL override
that is passed to DecentralandUrlsSource. All composed gatekeeper
URLs (SceneAdapter, ChatAdapter, Status, BannedUsers, and
LocalGateKeeperSceneAdapter) now inherit the override, ensuring
consistent cross-environment debugging.
* fix: align CommsApiWrap tests with wire format and JSON escaping
- PublishDataWithMsgTypeCommsDataPrefix: decode topic header + data
instead of treating bytes after MsgType as raw data
- SimulateIncomingMessage: encode wire format [topicLen][topic][data]
so OnDataReceived can parse incoming messages
- DrainBufferOnConsume: check sender identities instead of inner JSON
which gets escaped by JsonTextWriter
- PublishAndReceiveRoundTrip: relax assertion to account for
JSON-escaped inner data string
* fix: address code review — race condition, stackalloc overflow, misleading sentinel
- Wrap ActiveSpeakers iteration in try-catch for thread safety (LivekitPlayer)
- Cap PublishData payload at LIVEKIT_MAX_SIZE before stackalloc (CommsApiWrap)
- Replace unused tuple sentinel with bool currentWritten (CommsApiWrap)
- Add RejectOversizedData test
* refactor: use LKParticipant type alias in GetActiveVideoStreamsResponse
* fix: remove stale #if guard on cross-platform media throttle fields
* fix: address remaining code review items — TOCTOU race, writer recovery, null-conditional
- Snapshot ActiveSpeakers into a local list before iterating to eliminate
the TOCTOU race between Count and foreach (LivekitPlayer)
- Add ResetWriter() to recover JsonTextWriter state after mid-write
exceptions corrupt its internal depth stack (CommsApiWrap)
- Simplify confusing !livekitPlayer?.IsVideoOpened ?? false expression
to explicit null check (UpdateMediaPlayerSystem)
* refactor: address PR review — encapsulate video sourcing, dedup audio sources
- LivekitPlayer: introduce VideoEntity domain object + PresentationBotStream
variant on LivekitAddress; replace currentVideoIdentity/string-prefix probes
with exhaustive Match dispatch.
- Swap audioSources to Dictionary<StreamKey, ...> to collapse the O(N^2)
HasAudioSourceForKey scan and drop the streamKeysBuffer field.
- Drop hasLiveAudio flag; isAudioOpened now reads audioSources.Count directly.
- Simplify TryFollowActiveSpeaker (foreach/break to index access; is { } pattern
replaces null-forgiving operator).
- Collapse EnsureVideoIsPlaying else branch to a single CurrentStream reopen.
- CommsApiWrap: extract wire-format byte widths into named constants.
- LiveKitMediaExtensions: move PRESENTATION_BOT_IDENTITY_PREFIX out of
LivekitPlayer; add IsPresentationBotIdentity extension.
* fix: address code review — clarify MessagePipe catch and ResetWriter XML doc
MessagePipe InvalidProtocolBufferException handler now reads as an
explicit defensive fallback for non-DCL participants publishing
non-protobuf data on the shared IDataPipe. DCL clients (cast2 included)
speak protobuf and CommsApi traffic routes via MsgType.CommsData, so
neither reaches this catch.
ResetWriter XML doc expanded to explain JsonTextWriter's internal
depth/token stack: an exception thrown between WriteStart*/WriteEnd*
leaves the stack unbalanced, corrupting subsequent writes. Recreating
on the exception path is cheaper than wrapping each Write* call in
try/catch on the happy path.
No behaviour change.
* refactor: address LiveKit review — AnyExposedAudioSource rename, fold VideoEntity into LivekitAddress
- Rename ExposedAudioSource → AnyExposedAudioSource across LivekitPlayer,
MultiMediaPlayer and MediaPlayerComponent. The underlying storage moved
from a single field to a Dictionary so iteration order is unspecified
(and can reshuffle after audioSources.Remove), making "First" misleading.
Restore the original inline comments on the method and expand the XMLDoc
to call out the multi-source / non-deterministic semantics.
- Fix the "referene" typo in MultiMediaPlayer's XMLDoc while there.
- Eliminate VideoEntity: its IsPresentationBot and Identity accessors and
the FromIdentity factory move directly onto LivekitAddress, which
already discriminates over the UserStream / PresentationBotStream /
CurrentStream union. Migrate all 6 call sites in LivekitPlayer.
* clarify #7347 (comment)
* address https://github.com/decentraland/unity-explorer/pull/7347\#discussion_r3132329943
* register new category for reporting
* address https://github.com/decentraland/unity-explorer/pull/7347\#discussion_r3132469058
* allocation free write, address https://github.com/decentraland/unity-explorer/pull/7347\#discussion_r3132270763 and https://github.com/decentraland/unity-explorer/pull/7347\#discussion_r3132314939
* submit todo for futher optimisations
* remove redundant const
* implement message count bouding, add explicit comments to js contract, address https://github.com/decentraland/unity-explorer/pull/7347\#discussion_r3132503328 and https://github.com/decentraland/unity-explorer/pull/7347\#discussion_r3132457159
* implement ListPool
* link livekit package
* mitigate InvalidOperationException and ActiveSpeakers thread-safe access
* remove redundant locks, simplify "switch to current" method
* update package lock
* unify OpenVideoStream
* address LivekitPlayer
* remove redundant code
* remove redundant null check
* update comment
* align livekit deps
* chore: Add id-token permission
Co-authored-by: Copilot <copilot@github.com>
* fix: remove added prop
---------
Co-authored-by: NickKhalow <nickhalow@gmail.com>
Co-authored-by: Copilot <copilot@github.com>1 parent a9b84c6 commit 9f7cd6f
25 files changed
Lines changed: 1317 additions & 128 deletions
File tree
- Explorer
- Assets
- DCL
- Infrastructure
- CrdtEcsBridge
- Global
- Dynamic
- DebugSettings
- Editor
- SceneRunner
- SceneRuntime
- Apis/Modules/CommsApi
- Tests
- Utility/DecentralandUrls
- Multiplayer/Connections/Messaging/Pipe
- NetworkDefinitions/Browser
- PerformanceAndDiagnostics/Diagnostics/ReportsHandling
- SDKComponents/MediaStream
- Components
- Systems
- StreamingAssets/Js/Modules
- Packages
- docs
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | | - | |
2 | | - | |
3 | | - | |
| 1 | + | |
4 | 2 | | |
5 | 3 | | |
6 | 4 | | |
| |||
11 | 9 | | |
12 | 10 | | |
13 | 11 | | |
| 12 | + | |
14 | 13 | | |
15 | 14 | | |
16 | 15 | | |
| |||
Lines changed: 21 additions & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
5 | 5 | | |
6 | 6 | | |
7 | 7 | | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
8 | 17 | | |
9 | 18 | | |
10 | 19 | | |
| |||
34 | 43 | | |
35 | 44 | | |
36 | 45 | | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
37 | 53 | | |
38 | 54 | | |
39 | 55 | | |
| |||
52 | 68 | | |
53 | 69 | | |
54 | 70 | | |
| 71 | + | |
| 72 | + | |
55 | 73 | | |
56 | 74 | | |
57 | 75 | | |
| |||
68 | 86 | | |
69 | 87 | | |
70 | 88 | | |
71 | | - | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
72 | 92 | | |
73 | 93 | | |
Lines changed: 15 additions & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
45 | 45 | | |
46 | 46 | | |
47 | 47 | | |
| 48 | + | |
48 | 49 | | |
49 | 50 | | |
50 | 51 | | |
| |||
187 | 188 | | |
188 | 189 | | |
189 | 190 | | |
190 | | - | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
191 | 194 | | |
192 | 195 | | |
193 | 196 | | |
| |||
802 | 805 | | |
803 | 806 | | |
804 | 807 | | |
| 808 | + | |
| 809 | + | |
| 810 | + | |
| 811 | + | |
| 812 | + | |
| 813 | + | |
| 814 | + | |
| 815 | + | |
| 816 | + | |
| 817 | + | |
| 818 | + | |
805 | 819 | | |
806 | 820 | | |
807 | 821 | | |
| |||
Lines changed: 17 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
5 | 5 | | |
6 | 6 | | |
7 | 7 | | |
| 8 | + | |
8 | 9 | | |
9 | 10 | | |
10 | 11 | | |
| |||
157 | 158 | | |
158 | 159 | | |
159 | 160 | | |
| 161 | + | |
160 | 162 | | |
161 | 163 | | |
162 | 164 | | |
163 | 165 | | |
164 | 166 | | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
165 | 170 | | |
166 | 171 | | |
167 | 172 | | |
| |||
178 | 183 | | |
179 | 184 | | |
180 | 185 | | |
| 186 | + | |
181 | 187 | | |
182 | 188 | | |
183 | 189 | | |
184 | 190 | | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
185 | 195 | | |
186 | 196 | | |
187 | 197 | | |
188 | 198 | | |
189 | 199 | | |
190 | 200 | | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
191 | 208 | | |
192 | 209 | | |
193 | 210 | | |
| |||
Lines changed: 4 additions & 2 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
243 | 243 | | |
244 | 244 | | |
245 | 245 | | |
246 | | - | |
| 246 | + | |
| 247 | + | |
247 | 248 | | |
248 | 249 | | |
249 | 250 | | |
| |||
271 | 272 | | |
272 | 273 | | |
273 | 274 | | |
274 | | - | |
| 275 | + | |
| 276 | + | |
275 | 277 | | |
276 | 278 | | |
277 | 279 | | |
| |||
0 commit comments