diff --git a/package.json b/package.json index 076b6226..1df4d91d 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,9 @@ "traces:markdown": "bun run scripts/extract-traces.ts --format markdown > docs/traceability.md", "traces:coverage": "bun run scripts/extract-traces.ts --format coverage", "traces:validate": "bun run scripts/extract-traces.ts --format validate", - "release:notes": "bun run scripts/release-notes.ts" + "release:notes": "bun run scripts/release-notes.ts", + "test:player": "./scripts/test-player-conformance.sh", + "test:player:android": "./scripts/test-player-conformance.sh android" }, "dependencies": { "@tauri-apps/api": "^2.11.1", diff --git a/scripts/test-player-conformance.sh b/scripts/test-player-conformance.sh new file mode 100755 index 00000000..75e6b01a --- /dev/null +++ b/scripts/test-player-conformance.sh @@ -0,0 +1,60 @@ +#!/usr/bin/env bash +# Run the MediaPlayer conformance suite. +# +# See docs/specs/media-player-controller.md. One set of behaviours, run against +# every engine — so a wrapper is verified without building or launching the app. +# +# ./scripts/test-player-conformance.sh desktop engines (mpv, legacy) +# ./scripts/test-player-conformance.sh android ExoPlayer, on a connected device +# +set -euo pipefail + +PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +TARGET="${1:-desktop}" + +run_desktop() { + local fixture="${TMPDIR:-/tmp}/jellytau-conformance-1200s.mp4" + if [ ! -f "$fixture" ]; then + # Generated, not committed: the repo carries no media, and the duration + # is exact — the seek assertions depend on it. + echo "Generating a 20-minute fixture at $fixture" + ffmpeg -y -loglevel error \ + -f lavfi -i "testsrc2=size=640x360:rate=25" \ + -f lavfi -i "sine=frequency=440" \ + -t 1200 -c:v libx264 -preset ultrafast -pix_fmt yuv420p -g 50 \ + -c:a aac -shortest "$fixture" + fi + + cd "$PROJECT_ROOT/src-tauri" + local status=0 + for engine in mpv legacy; do + echo + cargo run --quiet --features conformance --bin player-conformance -- \ + "$fixture" "$engine" || status=1 + done + return $status +} + +run_android() { + if ! adb get-state >/dev/null 2>&1; then + echo "No device. Connect one and enable USB debugging." >&2 + exit 1 + fi + + "$PROJECT_ROOT/scripts/sync-android-sources.sh" >/dev/null + cd "$PROJECT_ROOT/src-tauri/gen/android" + + # `-x rustBuild...` because raw gradle drives the Rust build through Tauri's + # android-studio-script, which expects a dev-server address file that only + # exists under `tauri android dev`. The native library already in + # app/src/main/jniLibs is what the test process loads. + ANDROID_HOME="${ANDROID_HOME:-$HOME/Android/Sdk}" \ + ./gradlew :app:connectedUniversalDebugAndroidTest \ + -x :app:rustBuildUniversalDebug --console=plain +} + +case "$TARGET" in + desktop) run_desktop ;; + android) run_android ;; + *) echo "usage: $0 [desktop|android]" >&2; exit 2 ;; +esac