Skip to content

Commit 1debbcb

Browse files
authored
feat: migrate video playback from AVPro to UUAV (#9463)
* feat: migrate video playback from AVPro to UUAV * feature flag per platform * ipc protocol * zmq lib * build script zmq * update deps ips * helper deploy * uuav-server * client uuav * update build flow * update binaries * update ipc * update server * update client - video receive * update bins * audio support cross-platform * update example * media ui example with timeline * update protocol * add windows binaries * windows server * windows client * auto recovery on helper's death * update dlls * update build pipeline * update ci yml * implemented zmq migration * compile mac * sandboxing windows * macos sandboxing * update readme file * address the bot feedback * debug panel widgets * track native player infos * resolve typo * windows dll * allOverride log matrix * fix nv12 shader ref * update logging skill * reference nv12 as always include * uuav binary verification and native build ci * pin rust toolchain, test the lock verifier, document uuav * apply feedback * expose audio info * address silence gaps * update mac bins * stress test * address feedback * multi-urls stress test * high-level tests * guard linux uuav tests * update yml ci * add new case * remove duplicates * address gaps in long packets * NONE IPC mode via defines * macos bins * guard NO_IPC mode to DEBUG, lock uuav_core binaries, verifier completeness check * fix CRLF in windows job GITHUB_OUTPUT values * fix CRLF from windows python in fetch-ffmpeg provenance read * zero PE linker timestamp for reproducible windows builds, gate uuav_core artifacts * relock native binaries from CI build (pinned 1.97.1, reproducible timestamps) * remove hard linking macos * address the feedback * lock * remove prod verbose logs * remind in the run summary that the workflow commits nothing
1 parent 2f93fff commit 1debbcb

307 files changed

Lines changed: 29653 additions & 254 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.claude/skills/diagnostics-and-logging/SKILL.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ Drop a `.json` file in the build root folder, launch with `--use-log-matrix "fil
9797

9898
```json
9999
{
100-
"override": true,
100+
"isOverride": true,
101101
"debugLogMatrix": [
102102
{ "category": "VOICE_CHAT", "severity": "Warning" }
103103
],
@@ -107,8 +107,10 @@ Drop a `.json` file in the build root folder, launch with `--use-log-matrix "fil
107107
}
108108
```
109109

110-
- `"override": true` — Only use file values (replaces entire matrix)
111-
- `"override": false` — Merge with existing matrix values
110+
Keys must match `CategorySeverityMatrixDto` field names — `JsonUtility` ignores unknown ones. `{ "allOverride": true }` enables every category at every severity in the log file only, and takes precedence over `isOverride`/`debugLogMatrix`. A log captured under `allOverride` contains resolved media stream URLs with signed `sig`/`expire` query params — do not attach it to a public issue.
111+
112+
- `"isOverride": true` — Only use file values (replaces entire matrix)
113+
- `"isOverride": false` — Merge with existing matrix values
112114

113115
### Use Cases
114116

.gitattributes

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,28 @@ Explorer/Assets/StreamingAssets/AssetBundles/**/*.meta -filter=lfs -diff=lfs -me
1414
*.glb filter=lfs diff=lfs merge=lfs -text
1515
*.png filter=lfs diff=lfs merge=lfs -text
1616
*.dll filter=lfs diff=lfs merge=lfs -text
17+
*.dylib filter=lfs diff=lfs merge=lfs -text
18+
Explorer/Assets/Plugins/UUAV/Packages/UUAV/Runtime/Plugins/x86_64/uuav-helper.exe filter=lfs diff=lfs merge=lfs -text
19+
Explorer/Assets/Plugins/UUAV/Packages/UUAV/Runtime/Plugins/macOS/uuav-helper filter=lfs diff=lfs merge=lfs -text
20+
21+
##############################################################################
22+
# UUAV native - hashed bytes, so the line endings are part of the content
23+
#
24+
# scripts/uuav/uuav-binaries.lock.json digests these files as raw bytes, and
25+
# scripts/uuav/build-canonical.sh compiles them with LF forced
26+
# (-c core.autocrlf=false -c core.eol=lf). Without eol=lf a checkout on a host
27+
# with core.autocrlf=true holds CRLF copies, so `verify-binaries.py --update`
28+
# there records digests no other host can reproduce. The shell scripts are here
29+
# too: bash on the Windows runner does not accept CRLF.
30+
##############################################################################
31+
Explorer/Assets/Plugins/UUAV/native/**/*.rs text eol=lf
32+
Explorer/Assets/Plugins/UUAV/native/**/*.toml text eol=lf
33+
Explorer/Assets/Plugins/UUAV/native/**/*.lock text eol=lf
34+
Explorer/Assets/Plugins/UUAV/native/**/*.sb text eol=lf
35+
Explorer/Assets/Plugins/UUAV/**/*.sh text eol=lf
36+
scripts/uuav/*.sh text eol=lf
37+
# --update rewrites the lock, so its own newlines must not depend on the host
38+
scripts/uuav/uuav-binaries.lock.json text eol=lf
1739

1840

1941
##############################################################################

.github/workflows/build-unitycloud.yml

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -634,6 +634,17 @@ jobs:
634634
echo "MAIN_EXE=$MAIN_EXE_ABS" >> "$GITHUB_ENV"
635635
echo "Located: $MAIN_EXE_ABS"
636636
637+
# the UUAV media helper (deployed by HelperBuildPostprocessor)
638+
# is a user-facing executable and must be signed like the game exe
639+
HELPER_EXE=$(find build -path '*/Plugins/x86_64/uuav-helper.exe' | head -n1)
640+
if [[ -z "$HELPER_EXE" ]]; then
641+
echo "::error::uuav-helper.exe not found in build/ (HelperBuildPostprocessor regression?)"
642+
exit 1
643+
fi
644+
HELPER_EXE_ABS=$(readlink -f "$HELPER_EXE")
645+
echo "HELPER_EXE=$HELPER_EXE_ABS" >> "$GITHUB_ENV"
646+
echo "Located: $HELPER_EXE_ABS"
647+
637648
- name: Sign Windows executable
638649
if: matrix.target == 'windows64' && (inputs.is_release_build == true || github.event.inputs.force_sign == 'true')
639650
continue-on-error: true
@@ -649,16 +660,33 @@ jobs:
649660
malware_block: 'false'
650661
environment_name: PROD
651662

663+
- name: Sign UUAV helper executable
664+
if: matrix.target == 'windows64' && (inputs.is_release_build == true || github.event.inputs.force_sign == 'true')
665+
continue-on-error: true
666+
uses: sslcom/esigner-codesign@b7f8ff36fc0de8690fbbab8e5b4421d29802f747 # develop @ 2025-06-23, pinned for supply-chain safety
667+
with:
668+
command: sign
669+
username: ${{ secrets.ES_USERNAME }}
670+
password: ${{ secrets.ES_PASSWORD }}
671+
credential_id: ${{ secrets.WINDOWS_CREDENTIAL_ID_SIGNER }}
672+
totp_secret: ${{ secrets.ES_TOTP_SECRET }}
673+
file_path: ${{ env.HELPER_EXE }}
674+
override: 'true'
675+
malware_block: 'false'
676+
environment_name: PROD
677+
652678
- name: Verify Windows signature
653679
if: matrix.target == 'windows64' && (inputs.is_release_build == true || github.event.inputs.force_sign == 'true')
654680
continue-on-error: true
655681
run: |
656682
set -euo pipefail
657683
sudo apt-get install -y -qq osslsigncode > /dev/null 2>&1
658684
659-
echo "Verifying signature on $MAIN_EXE ..."
660-
osslsigncode verify -in "$MAIN_EXE"
661-
echo "::notice::Signature verified for $MAIN_EXE"
685+
for exe in "$MAIN_EXE" "$HELPER_EXE"; do
686+
echo "Verifying signature on $exe ..."
687+
osslsigncode verify -in "$exe"
688+
echo "::notice::Signature verified for $exe"
689+
done
662690
663691
- name: 'Tar artifact to maintain original permissions'
664692
if: matrix.target == 'macos'

.github/workflows/uuav-native.yml

Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
name: UUAV Native Build
2+
3+
4+
on:
5+
workflow_dispatch:
6+
pull_request:
7+
types: [labeled, synchronize]
8+
9+
permissions:
10+
contents: read
11+
12+
concurrency:
13+
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
14+
cancel-in-progress: true
15+
16+
env:
17+
PLUGIN_LFS_GLOB: 'Explorer/Assets/Plugins/UUAV/Packages/UUAV/Runtime/Plugins/**'
18+
LOCK: scripts/uuav/uuav-binaries.lock.json
19+
20+
jobs:
21+
gate:
22+
name: Should this run
23+
runs-on: ubuntu-latest
24+
outputs:
25+
run: ${{ steps.decide.outputs.run }}
26+
steps:
27+
- id: decide
28+
env:
29+
LABELLED: ${{ contains(github.event.pull_request.labels.*.name, 'build-uuav-native') }}
30+
run: |
31+
if [ "${{ github.event_name }}" = "workflow_dispatch" ] || [ "$LABELLED" = "true" ]; then
32+
echo "run=true" >> "$GITHUB_OUTPUT"
33+
else
34+
echo "run=false" >> "$GITHUB_OUTPUT"
35+
fi
36+
37+
macos:
38+
name: Build macos-universal
39+
needs: gate
40+
if: needs.gate.outputs.run == 'true'
41+
runs-on: macos-15
42+
steps:
43+
- uses: actions/checkout@v6
44+
with:
45+
lfs: false
46+
47+
- run: git lfs pull --include "$PLUGIN_LFS_GLOB"
48+
49+
- name: Read pins from the lock
50+
id: pins
51+
run: |
52+
set -euo pipefail
53+
python3 - <<'PY' >> "$GITHUB_OUTPUT"
54+
import json, os
55+
target = json.load(open(os.environ["LOCK"]))["targets"]["macos-universal"]
56+
print(f"ffmpeg_tag={target['ffmpeg']['tag']}")
57+
print(f"ffmpeg_commit={target['ffmpeg']['commit']}")
58+
print(f"builder={target['ffmpeg']['builder']}")
59+
PY
60+
61+
- name: Install nasm
62+
run: brew install nasm
63+
64+
# In native/, always: rustup reads native/rust-toolchain.toml there, and
65+
# nowhere else in this checkout. At the repo root every rustc/cargo/rustup
66+
# command below would address the runner image's default toolchain instead
67+
# of the pinned one the build compiles with, so the slices would be added
68+
# to the wrong toolchain and the recorded identities would name a compiler
69+
# that produced none of the bytes - the identities Gate B then pins
70+
# reproduction against.
71+
- name: Install both Rust slices
72+
working-directory: Explorer/Assets/Plugins/UUAV/native
73+
run: |
74+
set -euo pipefail
75+
rustup target add aarch64-apple-darwin x86_64-apple-darwin
76+
# The x86_64 slice is the leg that dies when a rustc without that std
77+
# is first on PATH, and it dies inside cargo talking about a missing
78+
# crate rather than about the toolchain. Say so here instead.
79+
sysroot="$(rustc --print sysroot)"
80+
for slice in aarch64-apple-darwin x86_64-apple-darwin; do
81+
if [ ! -d "$sysroot/lib/rustlib/$slice/lib" ]; then
82+
echo "::error::the pinned rustc ($(rustc --version), sysroot $sysroot) has no $slice std; the universal build cannot work"
83+
exit 1
84+
fi
85+
done
86+
87+
# rustc and cargo are asked from native/ for the reason above; the file
88+
# itself is written at the repo root, where Gate B and the upload expect
89+
# it. Nothing else here is toolchain-file sensitive.
90+
- name: Record the toolchain actually used
91+
run: |
92+
set -uo pipefail
93+
native=Explorer/Assets/Plugins/UUAV/native
94+
{
95+
echo "rustc: $(cd "$native" && rustc --version)"
96+
echo "cargo: $(cd "$native" && cargo --version)"
97+
echo "clang: $(clang --version | head -1)"
98+
echo "xcode: $(xcodebuild -version | tr '\n' ' ' | sed 's/ *$//')"
99+
echo "sdk: macosx$(xcrun --sdk macosx --show-sdk-version) ($(xcrun --sdk macosx --show-sdk-build-version))"
100+
echo "ld: $(ld -v 2>&1 | head -1 || true)"
101+
} | tee toolchain-macos.txt
102+
103+
- name: Build FFmpeg from the pinned source
104+
working-directory: Explorer/Assets/Plugins/UUAV/native
105+
run: |
106+
set -euo pipefail
107+
bash "scripts/$(basename "${{ steps.pins.outputs.builder }}")"
108+
actual=$(git -C .ffmpeg-src rev-parse HEAD)
109+
expected="${{ steps.pins.outputs.ffmpeg_commit }}"
110+
if [ "$actual" != "$expected" ]; then
111+
echo "::error::FFmpeg tag ${{ steps.pins.outputs.ffmpeg_tag }} resolved to $actual but the lock pins $expected"
112+
exit 1
113+
fi
114+
echo "FFmpeg source commit $actual matches the lock."
115+
116+
- name: Gate A - the same runner must build the same bytes twice
117+
run: bash scripts/uuav/repro-gate.sh macos-universal
118+
119+
- name: Gate B - reproduce the committed binaries, toolchain permitting
120+
run: python3 scripts/uuav/reproduces-lock.py macos-universal --toolchain toolchain-macos.txt
121+
122+
- name: Compare the fresh build against the lock
123+
run: python3 scripts/uuav/verify-binaries.py --report macos-universal
124+
125+
# always(): a round that fails a gate is exactly the round whose fresh
126+
# hashes and recorded toolchain someone needs to read. Without this the
127+
# job that found the problem hands back nothing to diagnose it with.
128+
- uses: actions/upload-artifact@v4
129+
if: always()
130+
with:
131+
name: uuav-macos-universal
132+
path: |
133+
Explorer/Assets/Plugins/UUAV/Packages/UUAV/Runtime/Plugins/macOS/*.dylib
134+
Explorer/Assets/Plugins/UUAV/Packages/UUAV/Runtime/Plugins/macOS/uuav-helper
135+
toolchain-macos.txt
136+
uuav-macos-universal.sha256
137+
if-no-files-found: error
138+
139+
windows:
140+
name: Build windows-x86_64
141+
needs: gate
142+
if: needs.gate.outputs.run == 'true'
143+
runs-on: windows-latest
144+
defaults:
145+
run:
146+
shell: bash
147+
steps:
148+
- uses: actions/checkout@v6
149+
with:
150+
lfs: false
151+
152+
- run: git lfs pull --include "$PLUGIN_LFS_GLOB"
153+
154+
- name: Read pins from the lock
155+
id: pins
156+
run: |
157+
set -euo pipefail
158+
python - <<'PY' >> "$GITHUB_OUTPUT"
159+
import json, os, sys
160+
# Windows Python writes CRLF in text mode; the \r would survive into
161+
# every output value and break bash string comparisons downstream.
162+
sys.stdout.reconfigure(newline="\n")
163+
target = json.load(open(os.environ["LOCK"]))["targets"]["windows-x86_64"]
164+
print(f"rust_target={target['rust']['target']}")
165+
print(f"provenance={target['ffmpeg']['provenance']}")
166+
print(f"release_tag={target['ffmpeg'].get('release_tag', '-')}")
167+
print(f"asset={target['ffmpeg'].get('asset', '-')}")
168+
PY
169+
170+
- name: Fetch the pinned FFmpeg release asset
171+
run: |
172+
set -euo pipefail
173+
provenance="${{ steps.pins.outputs.provenance }}"
174+
if [ "$provenance" != "third-party-release-asset" ]; then
175+
echo "::error::the lock records windows-x86_64 FFmpeg as '$provenance', not a downloadable release asset; this job only knows how to fetch one. See Explorer/Assets/Plugins/UUAV/README.md."
176+
exit 1
177+
fi
178+
bash scripts/uuav/fetch-ffmpeg-windows.sh
179+
180+
- name: Install the mingw toolchain the build links with
181+
shell: cmd
182+
run: C:\msys64\usr\bin\pacman.exe -S --noconfirm --needed mingw-w64-x86_64-gcc
183+
184+
# In native/, always - see the macOS job's note: rustup honours
185+
# native/rust-toolchain.toml only there, so at the repo root this would
186+
# target the runner image's default toolchain and record a compiler that
187+
# built none of the shipped bytes.
188+
- name: Install the Rust target
189+
working-directory: Explorer/Assets/Plugins/UUAV/native
190+
run: |
191+
set -euo pipefail
192+
rustup target add "${{ steps.pins.outputs.rust_target }}"
193+
# native/build.sh hardcodes the triple (the mingw linker hangs off it
194+
# in .cargo/config.toml), and the build does not take it from the
195+
# lock, so the two have to be checked against each other rather than
196+
# assumed equal.
197+
if [ "${{ steps.pins.outputs.rust_target }}" != "x86_64-pc-windows-gnu" ]; then
198+
echo "::error::the lock pins rust target '${{ steps.pins.outputs.rust_target }}' but native/build.sh builds x86_64-pc-windows-gnu"
199+
exit 1
200+
fi
201+
202+
# rustc and cargo are asked from native/ - see the macOS job's note; gcc
203+
# is an absolute path and the file lands at the repo root, where Gate B
204+
# and the upload expect it.
205+
- name: Record the toolchain actually used
206+
run: |
207+
set -uo pipefail
208+
native=Explorer/Assets/Plugins/UUAV/native
209+
{
210+
echo "rustc: $(cd "$native" && rustc --version)"
211+
echo "cargo: $(cd "$native" && cargo --version)"
212+
echo "gcc: $(/c/msys64/mingw64/bin/gcc --version | head -1)"
213+
} | tee toolchain-windows.txt
214+
215+
- name: Gate A - the same runner must build the same bytes twice
216+
run: bash scripts/uuav/repro-gate.sh windows-x86_64
217+
218+
- name: Gate B - reproduce the committed binaries, toolchain permitting
219+
run: python scripts/uuav/reproduces-lock.py windows-x86_64 --toolchain toolchain-windows.txt
220+
221+
- name: Compare the fresh build against the lock
222+
run: python scripts/uuav/verify-binaries.py --report windows-x86_64
223+
224+
# always(): see the macOS job's note.
225+
- uses: actions/upload-artifact@v4
226+
if: always()
227+
with:
228+
name: uuav-windows-x86_64
229+
path: |
230+
Explorer/Assets/Plugins/UUAV/Packages/UUAV/Runtime/Plugins/x86_64/*.dll
231+
Explorer/Assets/Plugins/UUAV/Packages/UUAV/Runtime/Plugins/x86_64/uuav-helper.exe
232+
toolchain-windows.txt
233+
uuav-windows-x86_64.sha256
234+
if-no-files-found: error
235+
236+
publish:
237+
name: Publish manifest
238+
needs: [macos, windows]
239+
if: always() && needs.macos.result == 'success' && needs.windows.result == 'success'
240+
runs-on: ubuntu-latest
241+
steps:
242+
- uses: actions/checkout@v6
243+
with:
244+
lfs: false
245+
246+
- uses: actions/download-artifact@v4
247+
with:
248+
path: built
249+
250+
- name: Assemble the release manifest
251+
run: |
252+
set -euo pipefail
253+
mkdir -p manifest
254+
cp "$LOCK" manifest/uuav-binaries.lock.json
255+
cat built/*/*.sha256 > manifest/SHA256SUMS
256+
cat built/*/toolchain-*.txt > manifest/TOOLCHAINS.txt
257+
{
258+
echo "### UUAV native build"
259+
echo
260+
echo '```'
261+
cat manifest/SHA256SUMS
262+
echo '```'
263+
echo
264+
echo '```'
265+
cat manifest/TOOLCHAINS.txt
266+
echo '```'
267+
echo
268+
echo '> [!WARNING]'
269+
echo "> **Nothing was committed** — this workflow has \`contents: read\` and only verifies. If this build is meant to ship, follow the relock steps in \`Explorer/Assets/Plugins/UUAV/README.md\`: commit the binaries from this run's artifacts, then run \`verify-binaries.py --update --only <target> --toolchain toolchain-<os>.txt\`."
270+
} >> "$GITHUB_STEP_SUMMARY"
271+
272+
- uses: actions/upload-artifact@v4
273+
with:
274+
name: uuav-manifest
275+
path: manifest
276+
if-no-files-found: error

0 commit comments

Comments
 (0)