feat: migrate video playback from AVPro to UUAV - #9463
Conversation
|
Windows and Mac build successful in Unity Cloud! You can find a link to the downloadable artifact below. |
|
Lint did not finish ( |
|
|
This comment has been minimized.
This comment has been minimized.
decentraland-bot
left a comment
There was a problem hiding this comment.
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:
- Native (Rust) — FFmpeg 8 demux/decode, D3D11VA/Metal HW accel, audio ring buffer. Ships as prebuilt
uuav.dll/libuuav.dylibvia LFS. - Runtime (C#) —
UUAVPlayer(MonoBehaviour),UUAVRuntime(static lifecycle),NativeMethods(P/Invoke). Standalone, no DCL dependency. - AVProCompat (C#) — Thin facade (
MediaPlayer,IMediaControl,ITextureProducer, etc.) inRenderHeads.Media.AVProVideonamespace so DCL'sMediaStreamsystems 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
Security-Focused Dependency Review — PR #9463 (AVPro → UUAV) · re-run on synchronize
|
|
I'll analyze this and get back to you. |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
dalkia
left a comment
There was a problem hiding this comment.
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
This comment has been minimized.
This comment has been minimized.
decentraland-bot
left a comment
There was a problem hiding this comment.
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:
- UUAV Plugin (
Assets/Plugins/UUAV/) — standalone reusable package: native Rust core (NativeMethods,UUAVRuntime,UUAVPlayer) plus an AVPro-compatible facade (UUAV.Compat). - AvProSwitch (
Assets/DCL/AvProSwitch/) — Explorer-specific abstraction:MediaPlayerBackendREnum tagged union dispatching toUuavBackendorAvProBackend,MediaPlayerBackendSelectionstatic global driven by feature flag, and aMediaPlayerMonoBehaviour that picks the backend inAwake(). - Explorer integration — namespace swaps from
RenderHeads.Media.AVProVideotoDCL.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()— callsuuav_player_free(playerId), releases plane textures, destroysruntimeSurfaceandnv12Material. ✅UUAVRuntime.Deinit()— called onApplication.quittingand onbeforeAssemblyReloadin editor. Callsuuav_deinit()and resetsrenderCallback. ✅UUAVPlayer.OnDisable()— pauses if playing. ✅Compat.MediaPlayer— does not own any additional subscriptions;Update()callsbackend.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 tohttps,http,tls,tcp,crypto,data,udp,rtp,rtcp,rtsp.file:is only added inUNITY_EDITOR. This correctly prevents local file access from untrusted scene URLs. ✅ - Native binaries: Prebuilt
uuav.dll/libuuav.dyliband FFmpeg shared libraries are committed as binary blobs. Trust depends on the build pipeline integrity. Thenew-dependencylabel 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
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
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.MediaPlayerinto a singleUuavadapter that attachesUUAVPlayerdirectly (in its constructor, likeAvProBackenddoes) and absorbsCompat.UUAVBackend's state-mapping. Delete theCompatIMediaControl/Info/TextureProducerinterfaces and the duplicatedEnums/TimeRanges— that layer only exists to mimic AVPro's shape. - The per-frame
Tick()(seek-settling) that currently forcesCompat.MediaPlayerto be a MonoBehaviour has a natural home:AvProSwitch.MediaPlayeris already a MonoBehaviour and already owns the backend, so addUpdate() => backend.Tick();there, withTick()as aMatcharm 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:
-
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.
-
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.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
Claude finished @NickKhalow's task in 4m 39s —— View job Security-Focused Dependency Review — PR #9463 (AVPro → UUAV) · re-run on synchronize
What landed this roundTwo commits, three files, +3/−3:
🟢 The Windows binaries are no longer stale — carry-forward closedThis was the standing 🟠 from the last two rounds, and
The practical consequence for the QA pass this PR asks for: One thing I want to flag honestly rather than assert either way: both files are byte-identical in size across the rebuild ( I could not verify the export table directly — this runner has no LFS content ( 🔵 Nit on the typo commit: the qualification looks redundantregistry.Update(sceneData.SceneShortInfo.ToString(), videoPlayerCount, audioStreamCount, rowsBuffer, UnityEngine.Time.frameCount);
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 🟡 Still open from last round:
|
| 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 → |
✅ resolved in ad82db6e2 |
|
M3 — HelperBuildPostprocessor ungated by the feature flag, coupled to the CI hard-fail at build-unitycloud.yml:617-621 |
unchanged |
M4 — protocol_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 W — build-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 activewith nouuav helper restart attempt N failed; for denials uselog 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 thanUnavailable. That readout was untestable before this commit; it's the cheapest possible check that the rebuild picked upuuav_lifecycle. Also verify the low-IL spawn succeeds on real drivers. - Run
doctor-libs.shlocally — 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

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).
com.renderheads.avpro.video-ultrafromPackages/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/_macossibling modules selected with#[cfg(target_os)]at the dispatch site - no cross-platform stub contracts.uuav-server/- builds theuuav-helperexecutable 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 theuuav.dll/libuuav.dylibUnity 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 inheritedAF_UNIXsocketpair 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:
R8Unorm, UVRG8Unorm), surface ports transferred over an authenticated mach channel. FFmpeg (n8.1) is built from source vianative/scripts/build-ffmpeg-macos.shinto a git-ignored.third_party/.build.shhandles@rpathnaming, universal (arm64 + x86_64) lipo, and the mandatory ad-hoc code signing;doctor-libs.shverifies every shipped binary (dylibs anduuav-helper) is universal.build.shis cross-platform: it detects the host OS, builds the right cargo target(s), and deploys binaries intoPackages/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_ERRORuntil the next open/play re-arms recovery (which then hands over to the existingMediaPlayerRetryStateECS retry). Unity exit always reaps the helper: a kill-on-close job object on Windows, a parent-pid watch on macOS.Sandboxing
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.uuav-server/src/sandbox_macos.rs+uuav-server/helper.sb): as the first act ofmain, before any untrusted byte is parsed, the helper applies a deny-by-default Seatbelt profile (embedded at compile time).process-exec,process-forkand every persistent file write are denied; fail-closed with no bypass switch. The helper logsuuav-helper: seatbelt sandbox activeonce the profile is applied. Editor-onlyfile:playback works via an--allow-file-readspawn flag (broad reads only; writes/exec stay denied) - player builds never pass it.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 withTexture2D.CreateExternalTextureand converts to RGB with the bundledHidden/UUAV/NV12ToRGBshader (Graphics.Blit), with frame presentation driven byGL.IssuePluginEvent. On macOS,UUAVRuntimerefuses to initialize on any graphics API other than Metal. Local-file playback is editor-only; player builds accept streaming URLs only.Packages/UUAV/AVProCompat- aRenderHeads.Media.AVProVideo.MediaPlayerfacade overUUAVPlayer(MediaPlayer,MediaPlayerEvent,Enums,Interfaces,UUAVBackend), so the existingMediaStreamcode (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
MediaStreamto the compat surface: drops theAutoOpenflag inMediaPlayerCustomPooland the manualSetAudioSourcewiring inMultiMediaPlayer(the compatMediaPlayerhandles audio itself); updatesMediaPlayer.prefabaccordingly.UUAV.AVProCompatinto theECS.Unity,DCL.Plugins, andDCL.EditMode.Testsasmdefs; adds injection/skip logs toMediaPlayerPluginWrapper.AV_PRO_PRESENTversion-define inDCL.Pluginsfromcom.renderheads.avpro.video-ultratocom.nickkhalow.uuav(ProjectSettingsscripting defines are unchanged vsdev).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-playerfeature flag, and can be forced locally with the--use-custom-media-playerlaunch argument (--use-custom-media-player falseforces AVPro even if the flag is on). The startup log prints which backend is active:Media player backend: AVProorMedia player backend: UUAV.Scenario 1 - default backend (AVPro), happy path
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 usingVideoPlayer/media streams), with audio in sync - identical todev.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):(or enable the
use-custom-media-playerfeature flag)Expected result:
Media player backend: UUAV.uuav-helperprocess is running alongside Explorer (Task Manager / Activity Monitor).uuav-helper: seatbelt sandbox activeand nouuav helper restart attempt N failedlines - this proves the sandbox applied and did not break decoding on your OS/GPU combination.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:
Expected result:
uuav helper terminated (<exit status>); recovering, followed within a few seconds byuuav helper restarted; players restored.uuav-helperprocess (new PID) is running.Failedstate only occurs if the helper cannot be spawned 3 times in a row).uuav-helperprocess is left behind (kill-on-close job on Windows, parent watch on macOS).Scenario 4 - fresh account
Expected result:
Same as Scenario 1 - media streams play with no errors in logs.
Prerequisites
Test Steps
Run once per backend (AVPro default, then with
--use-custom-media-player):PBVideoPlayer).uuav-helper, verify resurrection and playback auto-restore).Additional Testing Notes
AV_PRO_PRESENTgate is gone) - watch for the[MediaPlayerPluginWrapper] InjectvsIgnore Injectlog 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 theuuav helper terminatederror with a loader exit code.log stream --style compact --predicate 'sender == "Sandbox"'and look foruuav-helper(pid) deny(1) ...lines.MediaStreamconsumes - it is poll-based and will need extending as new AVPro API surface is used.Quality Checklist
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.