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. |
|
Warnings count reduced: 14054 => 13981 Warnings/errors in files changed by this PR (42) |
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.
ca2e106 to
45425f7
Compare
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 5m 50s —— View job Security-Focused Dependency Review — PR #9463 (AVPro → UUAV) · re-run on synchronize
What landed this roundTwo commits (
Two findings close. One reopens with a new failure mode. ✅ M9 — resolved (cleanest fix of the round)The seven-DLL unscripted MSYS2 drop is gone entirely, and the diff explains why rather than just deleting:
✅ New — FFmpeg is now ABI-aligned across platforms and tied to a releaseWindows moved off a post-8.0 master snapshot (avcodec 63 / avutil 61 / swresample 7) onto the same 8.1 majors the macOS source build produces (avcodec 62 / avformat 62 / avutil 60 / swresample 6). 🔴 HIGH — H1: half fixed on Windows, and the half that moved is now a build breakThe Windows helper's blob is an LFS pointer now: But no committed
So the pointer was written with the LFS clean filter active locally while the tracking rule stayed out of the commit. Git LFS decides what to smudge from the
This is H3's build failure again one layer down, and it's the specific reason the rule has to be committed rather than applied locally. Both helpers still need path-based rules plus a Explorer/Assets/Plugins/UUAV/Packages/UUAV/Runtime/Plugins/**/uuav-helper filter=lfs diff=lfs merge=lfs -text
Explorer/Assets/Plugins/UUAV/Packages/UUAV/Runtime/Plugins/**/uuav-helper.exe filter=lfs diff=lfs merge=lfs -textI'm reading the repo, not a Windows build log — if the Windows job is green, the runner is being fed a smudged copy some other way and that's worth understanding before merge, because a contributor's clone won't be. Fix this → 🔴 HIGH — H4: unchanged, byte-identicalRe-verified this round so it isn't read as stale. On Windows, // registry.rs:236-254
#[cfg(target_os = "windows")]
pub fn apply_texture_set(registry: &Registry, id: PlayerId, …, handles: Vec<u64>) {
if let Some(mirror) = registry.by_helper(id)
&& let Ok(mut video) = mirror.video.lock()
{ video.store_texture_set(generation, width, height, handles); return; }
// no live mirror to own the duplicated handles: close them here or they
// leak in Unity's process (the player was freed mid-announcement)
crate::present::close_handles(&handles); // ← unconditional CloseHandle loop
}
🔴 HIGH — H2: unchanged, byte-identical
Consequences unchanged: unauthenticated init DoS, Smallest sufficient fix is still: token on every frame, dropped in 🟡 MEDIUMM1 — narrowed, still open. libzmq is sha256-pinned ( M6 — helper still spawns even when UUAV is off. M7 — no M10 — the Windows helper is still a console-subsystem executable. No M3 (postprocessor gating) — M8 — macOS bundle signing vs. the helper copy, unchanged ( M5 — macOS mach service in the shared bootstrap namespace: M4 — FFmpeg 🔵 Minor — LFS payload weight
Windows Other minor, all unchanged:
Dependency inventory — delta this round
VerdictGood round on the dependency surface. M9 is genuinely closed — not hashed but deleted, which is better — and the Windows FFmpeg drop moved off a master snapshot onto the same 8.1 majors macOS builds, with But H2/H4 are byte-identical to two rounds ago: H1 got worse in a new way. Converting DEPENDENCY_REVIEW: BLOCK |

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 arm64 (Metal); Linux stays compiled out, as before.com.renderheads.avpro.video-ultrafromPackages/manifest.json- UUAV ships in-tree, no external package reference.Plugin layout (
Assets/Plugins/UUAV)native/- the Rust crate that builds the native player (uuav.dll/libuuav.dylib): FFmpeg 8 demux/decode, hardware-accelerated video decoding, audio pulled throughOnAudioFilterRead. Platform-specific code lives in_windows/_macossibling modules (hw_device_*,video_decoder_*,video_output_*) selected with#[cfg(target_os)]at the dispatch site - no cross-platform stub contracts.CVPixelBuffer/IOSurface → two Metal plane textures (YR8Unorm, UVRG8Unorm) blitted on the plugin's ownMTLCommandQueue. FFmpeg (n8.1) is built from source vianative/scripts/build-ffmpeg-macos.shinto a git-ignored.third_party/(source-build sonames differ from the Windows DLLs: avcodec 62 etc. -build.shresolves majors dynamically).build.shalso handles@rpathdylib naming and the mandatory ad-hoc code signing on arm64.build.shis cross-platform: it detects the host OS, builds the right cargo target, and deploys binaries intoPackages/UUAV/Runtime/Plugins/{x86_64,macOS}.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.Steps (default backend - AVPro):
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.Steps (UUAV backend - launch argument):
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:
Startup log prints
Media player backend: UUAV. Media behavior is indistinguishable from the AVPro run - the flag must not change SDK-observable behavior.Steps (fresh account):
Expected result:
Same as above - media streams play with no errors in logs.
Prerequisites
Test Steps
Run once per backend (AVPro default, then with
--use-custom-media-player):PBVideoPlayer).Additional Testing Notes
AV_PRO_PRESENTgate is gone) - watch for the[MediaPlayerPluginWrapper] InjectvsIgnore Injectlog on startup.uuav.dll/libuuav.dylib, FFmpeg libraries).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.