Refactor window handling, improve theme support, and update dependencies #94
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: A/V Sync Tests | |
| # Verifies audio/video sync correctness on every platform: | |
| # 1. Unit + property tests for the timestamp pipeline (encoders, drift | |
| # trackers, muxers). | |
| # 2. The synthetic device matrix: fake cameras/screens/microphones across | |
| # frame rates, sample rates, channel counts and delivery pathologies | |
| # (jitter, drops, static-screen gaps), driven through the real recording | |
| # pipeline and verified at the container level. No capture hardware or | |
| # GPU required, so results are deterministic on hosted runners. | |
| # | |
| # Findings are published as a job-summary table and a JSON artifact per OS. | |
| on: | |
| workflow_dispatch: | |
| schedule: | |
| - cron: "0 5 * * *" | |
| pull_request: | |
| paths: | |
| - "crates/recording/**" | |
| - "crates/enc-ffmpeg/**" | |
| - "crates/enc-avfoundation/**" | |
| - "crates/enc-mediafoundation/**" | |
| - "crates/timestamp/**" | |
| - "crates/rendering/**" | |
| - "crates/media-info/**" | |
| - ".github/workflows/sync-tests.yml" | |
| concurrency: | |
| group: sync-tests-${{ github.head_ref || github.ref_name }} | |
| cancel-in-progress: true | |
| permissions: | |
| contents: read | |
| jobs: | |
| sync-tests: | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| runner: | |
| - macos-latest | |
| - windows-2022 | |
| - ubuntu-24.04 | |
| runs-on: ${{ matrix.runner }} | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Rust setup | |
| uses: dtolnay/rust-toolchain@1.88.0 | |
| - name: Rust cache | |
| uses: ./.github/actions/setup-rust-cache | |
| with: | |
| target: ${{ runner.os == 'Windows' && 'x86_64-pc-windows-msvc' || runner.os == 'macOS' && 'aarch64-apple-darwin' || 'x86_64-unknown-linux-gnu' }} | |
| - name: Install desktop dependencies | |
| uses: ./.github/actions/install-desktop-deps | |
| - name: Setup Node | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 24 | |
| - name: Native dependencies | |
| env: | |
| RUST_TARGET_TRIPLE: ${{ runner.os == 'Linux' && 'x86_64-unknown-linux-gnu' || runner.os == 'Windows' && 'x86_64-pc-windows-msvc' || 'aarch64-apple-darwin' }} | |
| run: node scripts/setup.js | |
| - name: Add FFmpeg DLLs to PATH | |
| if: runner.os == 'Windows' | |
| shell: pwsh | |
| run: Add-Content -Path $env:GITHUB_PATH -Value "${{ github.workspace }}\\target\\ffmpeg\\bin" | |
| - name: Install software Vulkan driver (Linux) | |
| if: runner.os == 'Linux' | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y mesa-vulkan-drivers libvulkan1 | |
| # Hosted Windows runners have no audio endpoint, so WASAPI loopback | |
| # capture (system audio) cannot run at all without a virtual sound | |
| # card. Scream provides a signed null render device; with it installed, | |
| # the loopback keepalive, AUDCLNT_BUFFERFLAGS_SILENT zeroing and QPC | |
| # capture timestamps are exercised against a real audio engine. | |
| # | |
| # Pinned to 3.6: later releases (3.8/4.0) were signed after the 2021 | |
| # kernel cross-signing deprecation and PnP blocks their install on an | |
| # interactive consent prompt, which hangs headless runners. The step | |
| # timeout turns any such hang into a fast failure. | |
| - name: Install virtual audio device (Windows) | |
| if: runner.os == 'Windows' | |
| timeout-minutes: 10 | |
| shell: pwsh | |
| run: | | |
| Invoke-WebRequest -Uri https://github.com/duncanthrax/scream/releases/download/3.6/Scream3.6.zip -OutFile Scream3.6.zip | |
| $expected = "25ea5e778b4e6995a98d448b9b5f6d321f681663f1aeeec69d8e63183d008b19" | |
| $actual = (Get-FileHash -Algorithm SHA256 Scream3.6.zip).Hash.ToLowerInvariant() | |
| if ($actual -ne $expected) { throw "Scream3.6.zip SHA-256 mismatch: got $actual, expected $expected" } | |
| Expand-Archive -Path Scream3.6.zip -DestinationPath Scream | |
| $cert = (Get-AuthenticodeSignature Scream\Install\driver\Scream.sys).SignerCertificate | |
| $store = [System.Security.Cryptography.X509Certificates.X509Store]::new("TrustedPublisher", "LocalMachine") | |
| $store.Open("ReadWrite") | |
| $store.Add($cert) | |
| $store.Close() | |
| Scream\Install\helpers\devcon.exe install Scream\Install\driver\Scream.inf *Scream | |
| if ($LASTEXITCODE -gt 1) { throw "devcon install failed with exit code $LASTEXITCODE" } | |
| Start-Service -Name Audiosrv -ErrorAction SilentlyContinue | |
| - name: Timestamp pipeline unit + property tests | |
| shell: bash | |
| run: | | |
| cargo test --locked -p cap-timestamp -p cap-enc-ffmpeg | |
| cargo test --locked -p cap-recording --lib | |
| cargo test --locked -p cap-rendering | |
| # The AVFoundation encoder (studio camera/display on macOS) has its own | |
| # pts handling; its fps-matrix duration tests guard against re-timing | |
| # sources that deliver at a different rate than configured. | |
| - name: AVFoundation encoder tests (macOS) | |
| if: runner.os == 'macOS' | |
| shell: bash | |
| run: | | |
| cargo test --locked -p cap-enc-avfoundation | |
| # Runs against the Scream endpoint installed above; the env var turns | |
| # a missing endpoint (driver install regression) into a hard failure | |
| # instead of a silent skip. | |
| - name: WASAPI loopback endpoint tests (Windows) | |
| if: runner.os == 'Windows' | |
| shell: bash | |
| env: | |
| CAP_REQUIRE_AUDIO_ENDPOINT: "1" | |
| run: | | |
| cargo test --locked -p cap-recording --test windows_loopback -- --nocapture | |
| - name: Synthetic device matrix | |
| id: matrix | |
| continue-on-error: true | |
| shell: bash | |
| env: | |
| CAP_SYNC_MATRIX_REPORT: ${{ github.workspace }}/sync-matrix-${{ matrix.runner }}.json | |
| CAP_SYNC_MATRIX_RANDOM_CASES: ${{ github.event_name == 'schedule' && '40' || '6' }} | |
| run: | | |
| cargo test --locked -p cap-recording --test sync_matrix -- --nocapture | |
| # Verifies the editor's playback machinery (decoders, frame scheduling, | |
| # audio pipeline) preserves sync. The fixture recording is generated | |
| # through the real recording pipeline, so no capture hardware is needed; | |
| # rendering uses the platform's software adapter where no GPU exists. | |
| # 30s of pattern stabilizes the drift slope against frame-quantization | |
| # noise. Playback runs at the default 30 fps: lower rates trip the audio | |
| # sync policy's drift-correction threshold every few frames and accrue | |
| # real (policy-induced) drift that fails the gate. | |
| # | |
| # Linux-only: the Windows WARP adapter composites blank frames and the | |
| # macOS runners' paravirtualized Metal collapses to ~2 fps presentation | |
| # regardless of decoder, so neither can sustain a wall-clock playback | |
| # measurement. The decoder logic under test (FFmpeg gap holds) is fully | |
| # exercised here; the macOS AVAssetReader path is covered by running | |
| # `cap selftest playback` locally on real hardware. | |
| - name: Editor playback sync harness | |
| if: runner.os == 'Linux' | |
| shell: bash | |
| run: | | |
| cargo run --locked -p cap -- --log-level info selftest playback --duration 30 --json | |
| - name: Report findings | |
| if: always() | |
| shell: bash | |
| run: | | |
| REPORT="${{ github.workspace }}/sync-matrix-${{ matrix.runner }}.json" | |
| { | |
| echo "## A/V sync matrix — ${{ matrix.runner }}" | |
| echo "" | |
| if [ -f "$REPORT" ]; then | |
| PYTHONIOENCODING=utf-8 python3 - "$REPORT" << 'PYEOF' | |
| import json, sys | |
| report = json.load(open(sys.argv[1])) | |
| print(f"Randomized seed: `{report.get('seed')}` (rerun with CAP_SYNC_MATRIX_SEED)") | |
| print() | |
| print("| Case | Result | Detail |") | |
| print("| --- | --- | --- |") | |
| for case in report.get("cases", []): | |
| verdict = "PASS" if case["pass"] else "FAIL" | |
| detail = case["detail"].replace("|", "\\|") | |
| print(f"| {case['name']} | {verdict} | {detail} |") | |
| PYEOF | |
| else | |
| echo "No report produced — the matrix crashed before writing results." | |
| fi | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| - name: Upload findings | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: sync-matrix-${{ matrix.runner }} | |
| path: ${{ github.workspace }}/sync-matrix-${{ matrix.runner }}.json | |
| if-no-files-found: ignore | |
| - name: Fail on matrix failures | |
| if: steps.matrix.outcome == 'failure' | |
| shell: bash | |
| run: | | |
| echo "Synthetic sync matrix reported failures; see the job summary." >&2 | |
| exit 1 |