Skip to content

feat: migrate video playback from AVPro to UUAV - #9463

Open
NickKhalow wants to merge 36 commits into
devfrom
feat/migrate-avpro-to-uuav
Open

feat: migrate video playback from AVPro to UUAV#9463
NickKhalow wants to merge 36 commits into
devfrom
feat/migrate-avpro-to-uuav

Conversation

@NickKhalow

@NickKhalow NickKhalow commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Pull Request Description

What does this PR change?

Migrates video/audio media streaming from the AVPro Video Ultra package to UUAV - an FFmpeg-based player with a native Rust core, embedded directly in the repo under Assets/Plugins/UUAV. Supported platforms: Windows x86_64 (D3D11) and macOS (Metal, Apple Silicon primary; binaries ship universal); Linux stays compiled out, as before.

Decoding runs out of process: FFmpeg never shares Unity's address space or GPU device, so a decoder crash cannot take the engine down - the helper is respawned and playback restores automatically (see Crash resilience below). The decode process is also sandboxed on both platforms, so a decoder exploit lands in a process that cannot launch anything or write to disk (see Sandboxing below).

  • Removes com.renderheads.avpro.video-ultra from Packages/manifest.json - UUAV ships in-tree, no external package reference.

Plugin layout (Assets/Plugins/UUAV)

native/ - a Rust workspace of four crates:

  • src/ (uuav-core) - the player core: FFmpeg 8 demux/decode, hardware-accelerated video decoding (D3D11VA on Windows, VideoToolbox on macOS), playback clock, audio resampling. Platform-specific code lives in _windows/_macos sibling modules selected with #[cfg(target_os)] at the dispatch site - no cross-platform stub contracts.
  • uuav-server/ - builds the uuav-helper executable that hosts the core out of process: it owns its own GPU device, drives the core's render events from a ~60 Hz pacing thread, pulls decoded audio, and copies each presented frame into one of 3 cross-process shared texture slots per player (keyed-mutex NV12 textures on Windows, per-plane IOSurfaces on macOS). It watches the parent pid and exits if Unity dies.
  • uuav-client/ - builds the uuav.dll / libuuav.dylib Unity loads. Same C ABI the C# layer always used; internally it spawns, monitors and respawns the helper, mirrors player state (client-side clock extrapolation between 50 Hz state updates), and blits the shared slots into client-owned presentation textures on Unity's device.
  • uuav-ipc/ - the wire protocol and OS channel shared by both sides: an inherited AF_UNIX socketpair on macOS, a message-mode named pipe on Windows (no transport library ships at all), plus the mach channel that transfers IOSurface ports on macOS.

Platform notes:

  • Windows: D3D11VA hardware decode; NV12 keyed-mutex shared textures; the sandboxed helper cannot push texture handles into Unity, so the client pulls them out of the helper's process. FFmpeg comes from prebuilt BtbN LGPL shared DLLs; the helper's import closure is exactly avcodec/avformat/avutil/swresample.
  • macOS: VideoToolbox decode -> IOSurface -> two Metal plane textures (Y R8Unorm, UV RG8Unorm), surface ports transferred over an authenticated mach channel. FFmpeg (n8.1) is built from source via native/scripts/build-ffmpeg-macos.sh into a git-ignored .third_party/. build.sh handles @rpath naming, universal (arm64 + x86_64) lipo, and the mandatory ad-hoc code signing; doctor-libs.sh verifies every shipped binary (dylibs and uuav-helper) is universal.
  • build.sh is cross-platform: it detects the host OS, builds the right cargo target(s), and deploys binaries into Packages/UUAV/Runtime/Plugins/{x86_64,macOS}.

Crash resilience

The client watches the helper process. On unexpected death it freezes every player mirror (capturing the current media time), respawns the helper with backoff (0 s / 1 s / 3 s), and rebuilds every player from its desired state: reopen, resume playback, seek back to the captured position once open. If all three respawn attempts fail, players degrade to UUAV_ERROR until the next open/play re-arms recovery (which then hands over to the existing MediaPlayerRetryState ECS retry). Unity exit always reaps the helper: a kill-on-close job object on Windows, a parent-pid watch on macOS.

Sandboxing

  • Windows (uuav-client/src/sandbox_windows.rs): the helper is created suspended under a restricted low-integrity token (privileges stripped), bound to a single-process kill-on-close job object, given a process mitigation policy (DEP, ASLR, CFG, strict handle checks), and only then resumed - the limits bind before its first instruction.
  • macOS (uuav-server/src/sandbox_macos.rs + uuav-server/helper.sb): as the first act of main, before any untrusted byte is parsed, the helper applies a deny-by-default Seatbelt profile (embedded at compile time). process-exec, process-fork and every persistent file write are denied; fail-closed with no bypass switch. The helper logs uuav-helper: seatbelt sandbox active once the profile is applied. Editor-only file: playback works via an --allow-file-read spawn flag (broad reads only; writes/exec stay denied) - player builds never pass it.
  • macOS IOSurface channel authentication (uuav-ipc/src/mach_channel.rs): the per-session bootstrap service name is discoverable by other local processes, so the client's receiver requests the kernel audit trailer on every mach message and destroys, unread, anything not sent by the exact helper pid it spawned.

Packages/UUAV/Runtime - the C# binding layer (NativeMethods, UUAVRuntime, UUAVPlayer, UUAVClient.asmdef) plus the prebuilt binaries. On both platforms the native side presents NV12 planes; Unity wraps them with Texture2D.CreateExternalTexture and converts to RGB with the bundled Hidden/UUAV/NV12ToRGB shader (Graphics.Blit), with frame presentation driven by GL.IssuePluginEvent. On macOS, UUAVRuntime refuses to initialize on any graphics API other than Metal. Local-file playback is editor-only; player builds accept streaming URLs only.

Packages/UUAV/AVProCompat - a RenderHeads.Media.AVProVideo.MediaPlayer facade over UUAVPlayer (MediaPlayer, MediaPlayerEvent, Enums, Interfaces, UUAVBackend), so the existing MediaStream code (systems, pool, tests) keeps compiling against the AVPro API surface largely unchanged.

Packages/UUAV/Example - a minimal standalone example scene/assembly for the player, independent of Explorer.

Explorer integration

  • Adapts MediaStream to the compat surface: drops the AutoOpen flag in MediaPlayerCustomPool and the manual SetAudioSource wiring in MultiMediaPlayer (the compat MediaPlayer handles audio itself); updates MediaPlayer.prefab accordingly.
  • Links UUAV.AVProCompat into the ECS.Unity, DCL.Plugins, and DCL.EditMode.Tests asmdefs; adds injection/skip logs to MediaPlayerPluginWrapper.
  • Retargets the AV_PRO_PRESENT version-define in DCL.Plugins from com.renderheads.avpro.video-ultra to com.nickkhalow.uuav (ProjectSettings scripting defines are unchanged vs dev).

Test Instructions

Media streaming now runs on two switchable backends: AVPro (default) and UUAV. The backend is picked once at startup from the use-custom-media-player feature flag, and can be forced locally with the --use-custom-media-player launch argument (--use-custom-media-player false forces AVPro even if the flag is on). The startup log prints which backend is active: Media player backend: AVPro or Media player backend: UUAV.

Scenario 1 - default backend (AVPro), happy path

metaforge explorer run 9463

Expected result:
Startup log prints Media player backend: AVPro. Video streams in scenes play back correctly (e.g. video screens in Genesis Plaza or any scene using VideoPlayer/media streams), with audio in sync - identical to dev.

Scenario 2 - UUAV backend, happy path

Install the build via metaforge explorer run 9463, then launch the installed Explorer executable directly with the launch argument (metaforge does not pass args through):

# Windows
Decentraland.exe --use-custom-media-player
# macOS
open Decentraland.app --args --use-custom-media-player

(or enable the use-custom-media-player feature flag)

Expected result:

  • Startup log prints Media player backend: UUAV.
  • A uuav-helper process is running alongside Explorer (Task Manager / Activity Monitor).
  • macOS: the log contains uuav-helper: seatbelt sandbox active and no uuav helper restart attempt N failed lines - this proves the sandbox applied and did not break decoding on your OS/GPU combination.
  • Media behavior is indistinguishable from the AVPro run - the flag must not change SDK-observable behavior. Exercise play, pause, seek, scene-driven stop (resets to start, not just pause), audio sync, and roaming between scenes (player pooling).

Scenario 3 - helper force-kill: resurrection and playback auto-restore (UUAV backend)

With the UUAV backend active and a video visibly playing in a scene:

  1. Find and force-kill the helper process:
    # Windows (or Task Manager -> Details -> uuav-helper.exe -> End task)
    taskkill /F /IM uuav-helper.exe
    # macOS (or Activity Monitor -> uuav-helper -> Force Quit)
    pkill -9 uuav-helper
  2. Watch the video and the logs.

Expected result:

  • Explorer does not crash, hang, or lose audio output for other sources.
  • The log shows uuav helper terminated (<exit status>); recovering, followed within a few seconds by uuav helper restarted; players restored.
  • The video freezes for a moment, then playback resumes automatically: seekable media resumes near the position where it was killed; live streams rejoin at the live edge. Audio comes back in sync. No Unity restart, no scene reload, no user action.
  • A new uuav-helper process (new PID) is running.
  1. Kill the helper a few more times in a row - every death must recover the same way (each spawn success resets the retry budget; the parked Failed state only occurs if the helper cannot be spawned 3 times in a row).
  2. Quit Explorer and verify no uuav-helper process is left behind (kill-on-close job on Windows, parent watch on macOS).

Scenario 4 - fresh account

metaforge account create --clear
metaforge explorer run 9463

Expected result:
Same as Scenario 1 - media streams play with no errors in logs.

Prerequisites

  • Windows x86_64 build, or
  • macOS Apple Silicon build - requires Metal (UUAV backend)

Test Steps

Run once per backend (AVPro default, then with --use-custom-media-player):

  1. Enter a scene that uses video streaming (video screens / PBVideoPlayer).
  2. Verify the video plays, pauses, and seeks as driven by the scene.
  3. Verify a scene-driven stop resets the video to the start (not just a pause) - this must behave the same on both backends.
  4. Verify audio plays and stays in sync with video.
  5. Roam between scenes to exercise media player pooling (create/release).
  6. UUAV only: run Scenario 3 (force-kill uuav-helper, verify resurrection and playback auto-restore).
  7. Repeat on both platforms - for UUAV, the Windows D3D11 path and the macOS Metal path are separate native implementations.

Additional Testing Notes

  • AVPro stays in the manifest and remains the default; UUAV is opt-in via the flag, so an absent or failed feature-flag fetch keeps the battle-tested player.
  • The media player systems are always compiled in except on Linux, where they are excluded by platform defines (the AV_PRO_PRESENT gate is gone) - watch for the [MediaPlayerPluginWrapper] Inject vs Ignore Inject log on startup.
  • With the UUAV backend active, watch logs for native library load errors on startup (uuav.dll/libuuav.dylib, FFmpeg libraries). A missing FFmpeg DLL on a user machine kills the helper at load - it shows up as the uuav helper terminated error with a loader exit code.
  • On macOS, UUAV refuses to initialize on any graphics API other than Metal (logs an error by design).
  • Debugging a macOS sandbox denial: log stream --style compact --predicate 'sender == "Sandbox"' and look for uuav-helper(pid) deny(1) ... lines.
  • The AVProCompat layer intentionally covers only the AVPro features MediaStream consumes - it is poll-based and will need extending as new AVPro API surface is used.

Quality Checklist

  • Changes have been tested locally
  • Documentation has been updated (if required)
  • Performance impact has been considered
  • For SDK features: Test scene is included

Code Review Reference

Please review our Branch & PR Standards before submitting. It explains the automated review flow, QA/DEV approval requirements, and what each label does - especially useful for first-time contributors.

@NickKhalow NickKhalow added the force-build Used to trigger a build on draft PR label Jul 22, 2026
@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

@NickKhalow NickKhalow self-assigned this Jul 22, 2026
@NickKhalow NickKhalow added Windows Only Issue only happens on Windows builds force-build Used to trigger a build on draft PR and removed force-build Used to trigger a build on draft PR Windows Only Issue only happens on Windows builds labels Jul 22, 2026
@github-actions

github-actions Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

badge

Lint did not finish (cancelled) — the warning ratchet could not be evaluated. See logs.

@github-actions

github-actions Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

badge

⚠️ EditMode produced no results — the run likely crashed or timed out before finishing. Check the Unity Test / Test (editmode) job.

⚠️ PlayMode produced no results — the run likely crashed or timed out before finishing. Check the Unity Test / Test (playmode) job.

TESTS SUITE Result Passed Failed Skipped
EditMode ⚠️ No results
PlayMode ⚠️ No results

@NickKhalow
NickKhalow marked this pull request as ready for review July 24, 2026 11:21
@NickKhalow
NickKhalow requested review from a team as code owners July 24, 2026 11:21
@github-actions
github-actions Bot requested review from DafGreco, dalkia and popuz July 24, 2026 11:21
@decentraland-bot
decentraland-bot self-requested a review July 24, 2026 11:21
@claude

This comment has been minimized.

@decentraland-bot decentraland-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.

PR Review: feat: migrate video playback from AVPro to UUAV

STEP 2 — Root-cause check

This PR replaces a commercial video playback dependency (AVPro Video Ultra) with an embedded FFmpeg-based Rust/C# plugin (UUAV). This is a deliberate component swap, not a bug fix. The migration strategy — providing an AVProCompat facade in the same RenderHeads.Media.AVProVideo namespace so existing consumers compile unchanged — is a sound approach that minimizes integration risk.

Verdict: PASS — the change addresses a dependency swap at its root.

STEP 3 — Design & integration

Architecture: The UUAV plugin is structured in three layers:

  1. Native (Rust) — FFmpeg 8 demux/decode, D3D11VA/Metal HW accel, audio ring buffer. Ships as prebuilt uuav.dll/libuuav.dylib via LFS.
  2. Runtime (C#)UUAVPlayer (MonoBehaviour), UUAVRuntime (static lifecycle), NativeMethods (P/Invoke). Standalone, no DCL dependency.
  3. AVProCompat (C#) — Thin facade (MediaPlayer, IMediaControl, ITextureProducer, etc.) in RenderHeads.Media.AVProVideo namespace so DCL's MediaStream systems compile without changes.

Owner search: The MediaStream lifecycle is owned by MediaPlayerPluginWrapper (injection), MediaPlayerCustomPool (creation/pooling), and per-scene ECS systems (CreateMediaPlayerSystem, UpdateMediaPlayerSystem, CleanUpMediaPlayerSystem). The UUAV plugin does NOT duplicate these lifecycle owners — it replaces the underlying engine behind the same facade. The existing create/pool/release/cleanup paths remain unchanged.

Extension method compatibility verified: MediaPlayerExtensions.cs defines CloseCurrentStream, CrossfadeVolume, UpdatePlayback, UpdatePlaybackProperties as extension methods on MediaPlayer. All call only properties/methods that exist on the compat MediaPlayer (Stop(), CloseMedia(), Events, AudioVolume, MediaOpened, Control). Confirmed compatible.

Security review: Protocol whitelist (UNTRUSTED_STREAMING_PROTOCOLS) correctly restricts untrusted scene URLs to streaming protocols (https,http,tls,tcp,...). file: protocol is only added in UNITY_EDITOR builds. No credentials, secrets, or hardcoded tokens found. FFI boundary uses proper marshaling and string cleanup via uuav_string_free. No security issues identified.

STEP 5 — Line-level findings

See inline comments below for all findings with suggestion blocks.

Summary of findings:

# Severity File Issue
1 P1 DCL.Plugins.asmdef AV_PRO_PRESENT define likely not activated — package not registered with UPM
2 P2 UUAVBackend.cs Stop() only pauses, does not seek to 0 (diverges from AVPro semantics)
3 P2 MediaPlayer.cs Same Stop() issue on the compat MonoBehaviour
4 P2 UUAVPlayer.cs #region AI generated with TODO "recheck carefully" should not ship
5 P2 UUAVPlayer.cs Redundant P/Invoke uuav_player_state() calls in Update() — cache once
6 P2 package.json TODO placeholder values in description and keywords
7 P2 Enums.cs Typos in comment: "Effectivly" → "Effectively", "compatabilitty" → "compatibility"

STEP 6 — Complexity assessment

COMPLEX — Introduces an embedded native Rust/FFmpeg plugin with C# FFI bindings (P/Invoke, render-thread callbacks via GL.IssuePluginEvent, audio-thread callbacks via OnAudioFilterRead), an AVPro API compatibility layer, and modifies assembly definitions, package references, and the media playback pipeline.

STEP 7 — QA assessment

QA_REQUIRED: YES — This changes the entire video/audio playback engine. User-visible media playback (video screens, audio streams in scenes) must be tested on Windows. Audio sync, seeking, looping, pooling (scene transitions), and error handling all need manual verification.

STEP 8 — Non-blocking warnings

No warnings (Main scene not modified).

STEP 9 — Verdict

REVIEW_RESULT: FAIL ❌
COMPLEXITY: COMPLEX
COMPLEXITY_REASON: Introduces an embedded native Rust/FFmpeg plugin with C# P/Invoke bindings, AVPro API compat layer, audio/render thread callbacks, and modifies assembly definitions and package references across the media playback pipeline.
QA_REQUIRED: YES


Reviewed by Jarvis 🤖 · Requested by decentraland-bot via GitHub

Comment thread Explorer/Assets/DCL/PluginSystem/DCL.Plugins.asmdef Outdated
Comment thread Explorer/Assets/Plugins/UUAV/Packages/UUAV/AVProCompat/UUAVBackend.cs Outdated
Comment thread Explorer/Assets/Plugins/UUAV/Packages/UUAV/AVProCompat/MediaPlayer.cs Outdated
Comment thread Explorer/Assets/Plugins/UUAV/Packages/UUAV/Runtime/UUAVPlayer.cs Outdated
Comment thread Explorer/Assets/Plugins/UUAV/Packages/UUAV/Runtime/UUAVPlayer.cs
Comment thread Explorer/Assets/Plugins/UUAV/Packages/UUAV/package.json Outdated
Comment thread Explorer/Assets/Plugins/UUAV/Packages/UUAV/AVProCompat/Enums.cs
@claude

claude Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Security-Focused Dependency Review — PR #9463 (AVPro → UUAV) · re-run on synchronize

  • Re-inventory dependency & binary changes (PR updated since last review)
  • Re-check manifest / asmdef / AV_PRO_PRESENT gating
  • Re-check native binaries: LFS consistency, provenance, integrity
  • Re-check C# binding & compat layer (protocol whitelist, interop)
  • Classify risk & write verdict

View job run

@claude

claude Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Claude Code is working…

I'll analyze this and get back to you.

View job run

@claude

This comment has been minimized.

@claude

This comment has been minimized.

@claude

This comment has been minimized.

@dalkia dalkia left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Is it worth considering setting UUAV as another video provider which lives along AVPro for some time? Given that the same API is shared, using RENum show allow it

@claude

This comment has been minimized.

@decentraland-bot decentraland-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.

PR Review: feat: migrate video playback from AVPro to UUAV

STEP 2 — Root-cause check

This is a feature migration, not a bug fix. The PR replaces the AVPro Video Ultra commercial package with UUAV — a custom FFmpeg-based player with a native Rust core. The diff directly addresses the stated goal: it introduces the UUAV plugin, adds a backend-switching abstraction layer (DCL.AvProSwitch), wires it into the composition root via a use-custom-media-player feature flag, and replaces all RenderHeads.Media.AVProVideo references with DCL.AvProSwitch.

PASS — the diff addresses the migration goal directly.


STEP 3 — Design & integration

Architecture summary. Three layers:

  1. UUAV Plugin (Assets/Plugins/UUAV/) — standalone reusable package: native Rust core (NativeMethods, UUAVRuntime, UUAVPlayer) plus an AVPro-compatible facade (UUAV.Compat).
  2. AvProSwitch (Assets/DCL/AvProSwitch/) — Explorer-specific abstraction: MediaPlayerBackend REnum tagged union dispatching to UuavBackend or AvProBackend, MediaPlayerBackendSelection static global driven by feature flag, and a MediaPlayer MonoBehaviour that picks the backend in Awake().
  3. Explorer integration — namespace swaps from RenderHeads.Media.AVProVideo to DCL.AvProSwitch, assembly reference updates, prefab update.

Owner search — MediaPlayerBackendSelection: This static global holds the backend choice. It is installed in MediaPlayerContainer.InitializeInternalAsync() (the container that owns the media player composition root) before any player is created, and reset via [RuntimeInitializeOnLoadMethod] for domain reload safety. This correctly lives outside the pool/player lifecycle — it's a one-time configuration value, not a per-entity lifecycle. No existing owner can host this — justified.

Owner search — DCL.AvProSwitch.MediaPlayer: This MonoBehaviour is the prefab component that owns the backend union. It replaces the old RenderHeads.Media.AVProVideo.MediaPlayer component on the MediaPlayer.prefab. The pool (MediaPlayerCustomPool) creates and manages these instances — that lifecycle is unchanged. Existing owner (pool + prefab) hosts this — correct placement.

Teardown trace:

  • UUAVPlayer.OnDestroy() — calls uuav_player_free(playerId), releases plane textures, destroys runtimeSurface and nv12Material. ✅
  • UUAVRuntime.Deinit() — called on Application.quitting and on beforeAssemblyReload in editor. Calls uuav_deinit() and resets renderCallback. ✅
  • UUAVPlayer.OnDisable() — pauses if playing. ✅
  • Compat.MediaPlayer — does not own any additional subscriptions; Update() calls backend.Tick() which is stateless. ✅
  • DCL.AvProSwitch.MediaPlayer — no subscriptions to tear down; backends are Plain Old Classes, not IDisposable. ✅

Note on the three-layer facade stack: The path UUAV → Compat.MediaPlayer → AvProSwitch.UuavBackend → MediaPlayerBackend → AvProSwitch.MediaPlayer is deep. The Compat layer exists for reusability (the UUAV plugin is designed to work outside Explorer), and the AvProSwitch layer provides a neutral abstraction independent of both backends. This is defensible for the transition period but adds significant forwarding boilerplate. Consider collapsing the Compat layer into UuavBackend if the UUAV plugin doesn't need to be consumed by other projects.

PASS — design is sound, lifecycles are correctly managed.


STEP 4 — Member audit

MediaPlayer.Control, .Info, .TextureProducer — all return MediaPlayerBackend (the full tagged union). In the old API these returned different interfaces (IMediaControl, IMediaInfo, ITextureProducer). This widens the surface per accessor, but consumers only call the relevant subset of methods. This is an acceptable trade-off for the REnum dispatch approach — no issue.

MediaPlayer.AudioSource — forwards to backend.AudioSource. Used by MultiMediaPlayer (pool audio setup). Single forwarding accessor that centralizes access to an owned object — legitimate encapsulation, not a merge candidate.

MediaPlayerBackendSelection.UseCustomPlayer — consumed by MediaPlayer.Awake() and logged by MediaPlayerContainer. Two consumers, both legitimate reads of the same config value.

MediaPlayer.HasControl, .IsReady — forward to backend.HasControl / backend.IsReady. These replace direct null-checks against player.Control != null / player.TextureProducer != null (see MultiMediaPlayer.cs diff). The new accessors encapsulate the null-check semantics correctly — UuavBackend always returns true (surfaces exist for the component's lifetime), AvProBackend delegates to AVPro's lazy initialization. Good refactor.


STEP 5 — Line-level review

P1 — Lint regression (CI blocker)

The lint check reports 14,159 compilation warnings vs. a baseline of 14,094 — 65 new warnings, failing the Enforce warning reduction (PR) step. These likely originate from the UUAV plugin code under Assets/Plugins/UUAV/ (nullable warnings from csc.rsp -nullable:enable, REnum source-generated code for MediaPlayerBackend.g.cs, etc.).

Fix: Add an .editorconfig or a Directory.Build.props under Assets/Plugins/UUAV/ to suppress warnings in the plugin directory, or add targeted #pragma warning disable directives to the UUAV files that generate the most noise. The REnum-generated MediaPlayerBackend.g.cs warnings (CS8625) may also need a #nullable disable in the AvProSwitch csc.rsp or a pragma in the source.

P2 — FeatureId naming convention violation

FeatureId.USE_CUSTOM_MEDIA_PLAYER uses SCREAMING_SNAKE_CASE. All other 70 members of this enum use PascalCase (McpServer, CreditsTopup, ByteWeightedLoadingProgress, etc.). Rename to UseCustomMediaPlayer for consistency. This also applies to the dictionary entry in FeaturesRegistry and the reference in MediaPlayerContainer.cs.


Security review

  • FFmpeg protocol whitelist (UUAVRuntime.cs): Production builds restrict protocols to https,http,tls,tcp,crypto,data,udp,rtp,rtcp,rtsp. file: is only added in UNITY_EDITOR. This correctly prevents local file access from untrusted scene URLs. ✅
  • Native binaries: Prebuilt uuav.dll / libuuav.dylib and FFmpeg shared libraries are committed as binary blobs. Trust depends on the build pipeline integrity. The new-dependency label is correctly applied, and the Dependency Security Review CI check is pending. ✅
  • No secrets or credentials in the diff.
  • No injection surfaces — all string inputs go through the native FFI boundary with proper marshalling. ✅

No security findings.


CI Status

Check Status Notes
Test (playmode) ✅ PASS
Test (editmode) ❌ FAIL Appears to be a CI infrastructure issue (Unity licensing error in logs), not caused by this PR
Lint ❌ FAIL 65 new warnings (14,159 vs. 14,094 baseline) — must be addressed
Build (windows64) ⏳ PENDING
Build (macos) ⏳ PENDING
Dependency Security Review ⏳ PENDING MEDIUM RISK — human review required for new native binaries
Semantic title ✅ PASS feat: prefix is correct

STEP 6 — Complexity

COMPLEX — introduces new native dependencies, modifies assembly definitions, changes plugin registration/container wiring, touches the media playback subsystem, adds feature flag integration.

STEP 7 — QA assessment

QA_REQUIRED: YES — changes runtime video/audio playback behavior (the core media player backend), affects what users see and hear in-world.

STEP 8 — Non-blocking warnings

No Main.unity scene changes detected.

STEP 9 — Verdict

REVIEW_RESULT: FAIL ❌
COMPLEXITY: COMPLEX
COMPLEXITY_REASON: Introduces native UUAV plugin with FFI bindings, adds AvProSwitch backend abstraction with REnum dispatch, modifies assembly definitions and plugin registration, adds feature flag integration for runtime backend selection
QA_REQUIRED: YES


Reviewed by Jarvis 🤖 · Requested by Juan Ignacio Molteni [Dalkia] (<@U03JSUQ5Z7U>) via Slack

Comment thread Explorer/Assets/DCL/FeatureFlags/FeaturesRegistry.cs Outdated
Comment thread Explorer/Assets/DCL/FeatureFlags/FeaturesRegistry.cs Outdated
Comment thread Explorer/Assets/DCL/PluginSystem/csc.rsp Outdated
@claude

This comment has been minimized.

@dalkia dalkia left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Im reviewing the C# side:

Architecture note + a couple of general questions

UUAV path has two redundant wrapper layers behind the switch

The REnum switch itself is a good fit for engine selection. My concern is the UUAV side of it, which stacks two independently-designed abstraction layers that each invented a "backend."

When an AvProSwitch.MediaPlayer wakes with the flag on, the two Awakes chain into each other and the GameObject ends up with three media components and two separate backend fields:

AvProSwitch.MediaPlayer.Awake()
  → new DCL.AvProSwitch.UuavBackend(go)              // backend #1: "which engine"
       → go.AddComponent<UUAV.Compat.MediaPlayer>()
            → Compat.MediaPlayer.Awake()
                 → go.AddComponent<UUAVPlayer>()
                 → new UUAV.Compat.UUAVBackend(player) // backend #2: "adapt to AVPro shape"

So DCL.AvProSwitch.UuavBackend and UUAV.Compat.MediaPlayer are both near-pure forwarding layers — the only real logic (UUAVState → play/pause/buffering, Stop = pause+seek0, seek-settling, readiness gate) lives in a third class, UUAV.Compat.UUAVBackend. Compare the AVPro side: AvProBackend wraps RenderHeads' MediaPlayer in a single hop. UUAV takes three, for the same result.

The root cause is that UUAV.Compat (inside the vendored plugin) was built as a standalone AVPro drop-in — its own MediaPlayer + backend + IMediaControl/Info/TextureProducer — and then AvProSwitch wraps that facade again. Two "backend"s in a trench coat.

Suggestion — collapse the UUAV column to one hop, matching AVPro:

  • Merge DCL.AvProSwitch.UuavBackend + UUAV.Compat.MediaPlayer into a single Uuav adapter that attaches UUAVPlayer directly (in its constructor, like AvProBackend does) and absorbs Compat.UUAVBackend's state-mapping. Delete the Compat IMediaControl/Info/TextureProducer interfaces and the duplicated Enums/TimeRanges — that layer only exists to mimic AVPro's shape.
  • The per-frame Tick() (seek-settling) that currently forces Compat.MediaPlayer to be a MonoBehaviour has a natural home: AvProSwitch.MediaPlayer is already a MonoBehaviour and already owns the backend, so add Update() => backend.Tick(); there, with Tick() as a Match arm on the union (UUAV settles the seek, AVPro is a no-op since RenderHeads' component self-ticks). No ECS involvement, and it runs under the same active/enabled condition as today since it's the same GameObject.

I'm not suggesting we flatten AVPro + UUAV into MultiMediaPlayer as peer REnum cases — they're the same kind of player (same ops, which is exactly why the compat facade works), so that would just duplicate every Match arm. The real capability split is file-video vs LiveKit, which MultiMediaPlayer already models. Keep the engine switch, just make it one clean layer.

General questions

Rename classes/namespaces so the stack is engine-agnostic Right now the AVPro name leaks all the way up even though the whole point is that either engine can run: the namespace is AvProSwitch, the MultiMediaPlayer case is AvProPlayer, and its field is AvProMediaPlayer. That privileges AVPro in the naming and makes MultiMediaPlayer read as if it's tied to one engine. I'd rename the switch and its members engine-neutral — e.g. type/namespace MediaBackend, cases Uuav/AvPro — so nothing above the switch has to know which player is running.

The next two are nice-to-haves, not blocking this PR — just worth keeping in mind:

  1. Are we still committed to supporting Intel (x86_64) macs? UUAV ships native binaries via P/Invoke, so every supported platform is another native build target to produce and maintain — Intel mac support in particular is worth confirming before we lock in the build matrix.

  2. Worth moving UUAV to its own repo and consuming prebuilt binaries via GitHub Actions? Right now the native libs are vendored into this repo, which means a maintainer has to build for every platform locally to iterate. Splitting UUAV into a standalone repo (e.g. https://github.com/NickKhalow/uuav) with CI building the per-platform binaries would make iteration much easier and keep the native artifacts out of the Unity repo. Just something to keep in mind, and it pairs naturally with the platform-matrix question above.

@claude

This comment has been minimized.

@claude

This comment has been minimized.

@claude

This comment has been minimized.

@claude

This comment has been minimized.

@claude

This comment has been minimized.

@claude

This comment has been minimized.

@claude

This comment has been minimized.

@claude

This comment has been minimized.

@claude

This comment has been minimized.

@claude

This comment has been minimized.

@claude

This comment has been minimized.

@claude

This comment has been minimized.

@claude

claude Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Claude finished @NickKhalow's task in 4m 39s —— View job


Security-Focused Dependency Review — PR #9463 (AVPro → UUAV) · re-run on synchronize

  • Diff new commits against last-reviewed state (611c47a95)
  • Assess 668ddbf1e resolve typo + ad82db6e2 windows dll
  • Re-check binaries / lockfile / manifest / defines / protocol whitelist
  • STEP W — workflow diff (nothing new)
  • Re-verify carry-forward findings
  • Classify risk & write verdict

What landed this round

Two commits, three files, +3/−3:

Commit Change
668ddbf1e resolve typo Time.frameCountUnityEngine.Time.frameCount in GatherMediaStreamDebugSystem.cs:60
ad82db6e2 windows dll Rebuilt uuav.dll + uuav-helper.exe (LFS pointer OIDs updated)

git diff 611c47a95..HEAD -- '*Cargo.lock' '*Cargo.toml' '*manifest.json' '*packages-lock.json' '.github/**' '*.rsp' '*.asmdef' '.gitattributes' is empty. No new dependencies, no new crates, no Rust source changes at all, no new native exports, no asmdef or platform-list movement, no workflow changes. protocol_whitelist and the #if UNITY_EDITOR file: gate are untouched. All 15 shipped native binaries re-verified as LFS pointers with an intact OID trail (16th file in Plugins/ is doctor-libs.sh, a plain script — correct). STEP W has nothing new: .github/ is still last touched at 44e4c3547, so every W-finding stands exactly as previously recorded.


🟢 The Windows binaries are no longer stale — carry-forward closed

This was the standing 🟠 from the last two rounds, and ad82db6e2 resolves it. Freshness now checks out on all four UUAV binaries:

Binary Built at Last source change Current?
uuav.dll ad82db6e2 uuav-client @ 711bf22a7
uuav-helper.exe ad82db6e2 uuav-server @ 03c12c0bf
libuuav.dylib 711bf22a7 uuav-client @ 711bf22a7
uuav-helper (macOS) 03c12c0bf uuav-server @ 03c12c0bf

The practical consequence for the QA pass this PR asks for: uuav_lifecycle (added in uuav-client/src/lib.rs:954 at 711bf22a7) should now be present in the shipped DLL, so the Media Player widget's Lifecycle row can actually show Recovering/Failed on Windows instead of Unavailable (stale native binary?) — which is precisely the signal Scenario 3 (force-kill uuav-helper, watch recovery) exists to observe.

One thing I want to flag honestly rather than assert either way: both files are byte-identical in size across the rebuild (uuav.dll 1,347,584 → 1,347,584; uuav-helper.exe 1,411,072 → 1,411,072), only the hash moved. I checked whether that's suspicious and concluded it isn't: the entire Windows-side source delta since the previous build (ae5e27df5) is the ~11-line uuav_lifecycle probe, because every other change in that range is macOS-gated — spawn.rs's new allow_file_read parameter and HelperChild::id() are both under #[cfg(target_os = "macos")], and mach_channel.rs / sandbox_macos.rs / helper.sb never enter the Windows build. A few dozen bytes of new code plus one export-table entry sits comfortably inside PE's 512-byte FileAlignment padding, so an unchanged total size is the expected outcome, and the differing hash is ordinary build nondeterminism.

I could not verify the export table directly — this runner has no LFS content (.git/lfs/objects has no entry for either new OID, and git lfs pull isn't available to me here), so I'm reasoning from source and PE layout, not from the binary. Cheap confirmation on the author's Windows box: dumpbin /exports uuav.dll | findstr uuav_lifecycle, or just open the Media Player tab on a Windows build and check the Lifecycle row renders a real state.


🔵 Nit on the typo commit: the qualification looks redundant

registry.Update(sceneData.SceneShortInfo.ToString(), videoPlayerCount, audioStreamCount, rowsBuffer, UnityEngine.Time.frameCount);

using UnityEngine; is already on line 12 of that file, and I searched for a competing Time type that could have made the short form ambiguous — nothing in the file's imported namespaces (Arch.*, DCL.AvProSwitch, DCL.Diagnostics, DCL.ECSComponents, ECS.*, SceneRunner.Scene, UUAV), nothing under Explorer/Assets, nothing under Explorer/Packages. So as the tree stands the fully-qualified name is a no-op and reads inconsistently with the rest of the file.

Harmless either way — but if this did resolve a real ambiguity locally (a source-generated partial would be the plausible candidate), the durable form is a using Time = UnityEngine.Time; alias with a one-line note, since the next person to touch the line will otherwise "clean up" the qualifier and reintroduce whatever it was fixing.


🟡 Still open from last round: CopyPlayers breaks the graceful-degradation contract

Re-verified unchanged at UUAVDebug.cs:124-131:

public static void CopyPlayers(List<PlayerInfo> target)
{
    target.Clear();
    foreach (var player in players)
        target.Add(new PlayerInfo(player.PlayerId, player.State, player.CurrentUrl));  // ← unguarded P/Invoke
}

player.State is an unconditional NativeMethods.uuav_player_state(playerId) P/Invoke, while the sibling Query() (:98) and QueryLifecycle() (:181) both catch their loader exceptions — the class doc comment promises it "degrades gracefully when the native library … is missing." UUAVPlayer.Awake deliberately registers before its early returns, so an instance whose uuav_player_new() threw DllNotFoundException stays in the list; the PlayerId == 0 check lives in the consumer (MediaPlayerDebugContainer.cs:110) and only picks a display string, after the call has already happened. Failure scenario is carry-forward M8 (missing libuuav.dylib in the bundle): opening the Media Player tab throws out of RenderUuavSection(), so RenderSceneSection() on line 75 never runs and the scene half of the tab silently shows stale data — the tab added to diagnose a broken media stack goes dark in one of the cases it was built to diagnose.

target.Add(new PlayerInfo(
    player.PlayerId,
    player.PlayerId == 0 ? UUAVState.Unknown : player.State,
    player.CurrentUrl));

…plus a try/catch (DllNotFoundException) around the loop for the library-vanishes-mid-session case.

Fix this →


Dependency inventory — one row moved, and it moved the right way

Dependency / file Version Source Type / scope Risk Confidence
libuuav.dylib (1,524,304 B) crate 0.2.0 vendored Rust native, LFS ✓, current LOW HIGH
uuav-helper macOS (1,736,976 B) crate 0.2.0 vendored Rust native exe, LFS ✓, Seatbelt-confined LOW HIGH
uuav.dll (1,347,584 B) crate 0.2.0 vendored Rust native, LFS ✓, now current ⬆️ LOW MEDIUM (export table unverifiable here)
uuav-helper.exe (1,411,072 B) crate 0.2.0 vendored Rust native exe, LFS ✓, now current ⬆️, low-IL + job + mitigations LOW MEDIUM
Windows FFmpeg ×4 8.1 LGPL shared BtbN release, unhashed manual download native, LFS, runtime MEDIUM (M1) HIGH
macOS FFmpeg ×7 n8.1 source build build-ffmpeg-macos.sh, mutable tag native, LFS, runtime MEDIUM (M1) HIGH
Rust crate graph unchanged crates.io 0 new crates, Cargo.lock untouched LOW HIGH
sslcom/esigner-codesign b7f8ff36… SHA-pinned GitHub Action release CI only LOW HIGH
com.renderheads.avpro.video-ultra no ref/tag unity-explorer-packages.git?path=/AVProVideo UPM git, floating, default backend MEDIUM HIGH

Carry-forward, re-verified

Status
M1 — FFmpeg unhashed on both platforms; macOS pinned to a mutable tag. The one unresolved MEDIUM with a concrete fix unchanged — Fix this →
Stale Windows binaries resolved in ad82db6e2
M3HelperBuildPostprocessor ungated by the feature flag, coupled to the CI hard-fail at build-unitycloud.yml:617-621 unchanged
M4protocol_whitelist client-authored; file: only under #if UNITY_EDITOR, no runtime setter unchanged (untouched)
M5 — mach channel sender authentication ✅ resolved in 03c12c0bf
M6 — unconditional [RuntimeInitializeOnLoadMethod]; UUAVClient.asmdef still has empty platform lists. Linux is not newly exposed — MediaStream is compiled out there, so players stays empty unchanged; not worsened
M8 — macOS bundle seal vs. the post-build helper copy into Contents/PlugIns/ unchanged — and still the trigger for the 🟡 above
LOW (prior rounds) — debug row prints the resolved URL (signed sig/expire params for YouTube/Drive), untruncated; "invalid (native creation failed)" conflates shader-missing / native-error / library-missing; no test for the player registry; UUAVDebug.Push on the retail native-log path; empty DeviceRemoveReason row; ~200 ms pid-reuse residual in mach_channel.rs; missing rust-toolchain.toml; PlayerVideo.superseded unbounded when acks stop; helper_job() lacks memory/UI limits; Channel::pair() inheritance race unchanged (files untouched)
STEP Wbuild-unitycloud.yml at 44e4c3547, no diff. No permissions: block (W.10); tag-pinned checkout@v6/setup-python@v6/retry@v3 on secret-holding jobs (W.7b). Both pre-existing, neither introduced here. No pull_request_target, no LLM invocation, no untrusted event text in a prompt unchanged

Still needs a human, and it still isn't code

  • The cross-platform runtime pass has not been recorded. "Changes have been tested locally" is still unchecked. On macOS confirm uuav-helper: seatbelt sandbox active with no uuav helper restart attempt N failed; for denials use log stream --predicate 'sender == "Sandbox"'. Fail-closed is the right default but means a profile defect takes UUAV from degraded to dead on that machine.
  • Windows, and now genuinely worth doing since the DLL is fresh: run Scenario 3 and confirm the Lifecycle row shows Recovering → back to normal, rather than Unavailable. That readout was untestable before this commit; it's the cheapest possible check that the rebuild picked up uuav_lifecycle. Also verify the low-IL spawn succeeds on real drivers.
  • Run doctor-libs.sh locally — CI reports [UNKNOWN] arch because LFS content isn't fetched in that job.
  • CI: EditMode/PlayMode have not produced a green run on this branch yet (the last recorded attempt produced no results at all), so the new tests and asmdef references from earlier rounds are still not compile-verified. "Enforce QA and DEV Approvals" failing is expected process gating, not a code signal.

Verdict

From a supply-chain standpoint this round is strictly an improvement and adds zero surface: no new dependencies, no new crates, no Rust source changes whatsoever, no manifest/lockfile/workflow/asmdef movement — and it closes the stale-Windows-binary carry-forward that had been standing for two rounds. The identical file sizes are explained by the Windows-side delta being just the uuav_lifecycle probe (everything else in the range is cfg-gated to macOS) sitting inside PE alignment padding; I flag it only because I can't read the export table from this runner.

What keeps this at NEEDS_ATTENTION is now a shorter list: M1 (FFmpeg fetched without hash verification on Windows, macOS pinned to a mutable tag) is the sole unresolved MEDIUM, the CopyPlayers P/Invoke guard is still open from last round, and the cross-platform runtime pass still hasn't been recorded — with the Windows lifecycle readout now being the one high-value check that just became possible.

DEPENDENCY_REVIEW: NEEDS_ATTENTION
· feat/migrate-avpro-to-uuav

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

force-build Used to trigger a build on draft PR new-dependency

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants