Skip to content

feat(playback): bring back and widen 3D controls and format coverage (#210) - #221

Merged
BGLuis merged 10 commits into
developfrom
feat/210-3d-controls-and-format-coverage
Oct 24, 2026
Merged

BGLuis merged 10 commits into
developfrom
feat/210-3d-controls-and-format-coverage

Conversation

@BGLuis

@BGLuis BGLuis commented Oct 24, 2026

Copy link
Copy Markdown
Owner

Summary

Implements Epic #210 across 5 atomic commits covering container allowlists, whole-word 3D filename token detection, 3D view controls in the format modal, per-file fisheye lens FOV, and manifest/analytics CI guards:

  1. [FEATURE] File browser: list .m4v, .m2ts, .mts and .mk3d videos #211 (141d626) — File browser container allowlists:
    • Adds .m4v, .m2ts, .mts, and .mk3d to DirectoryLister.VIDEO_EXTENSIONS and folder_scan::MEDIA_EXTENSIONS while keeping .vob, .asf, .ogv, and .rm excluded.
  2. [BUG] 3D detection: match filename tokens as whole words on any separator #212 (a4c7041) — Whole-word 3D filename token matching:
    • Splits lowercase stem on non-alphanumeric separators in rust/media-logic/src/format3d.rs (detect_from_filename) and matches whole tokens and adjacent token pairs (half + sbs, 3d + sbs, etc.), adding fsbs, fou, and tb.
    • Matches projection numbers (180, 360, 360x180, 180x180) only as whole tokens so resolution tags (360p, 180p) and words sharing a prefix (out, ou2, lrx, table) no longer trigger false positives.
    • Adds .fsbs., .fou., and .tb. patterns to MediaFilterEngine.kt.
  3. [FEATURE] Screen format modal: restore swap eyes and add a 2D view of 3D #213 (3d50613) — Restore Swap Eyes and add 2D view in ScreenFormatModal:
    • Adds MONO_VIEW atomic flag (toggle_mono_view, get_mono_view) in rust/bridge/src/lib.rs, cleared on reset_process_state().
    • Forces eyeIndex = 0 and swapEyes = 0 for both eyes in GetStereoParams when MONO_VIEW is active on a stereo layout.
    • Exports JNI bridges (nativeGetSwapEyes, nativeToggleMonoView, nativeGetMonoView) in both vr_player_jni_vulkan.cpp and vr_player_app.cpp.
    • Adds ScreenFormatCatalog.isStereo and a "3D view" toggle row (player_btn_swap_eyes and player_btn_2d_view) in ScreenFormatModal.kt across all 3 locales.
  4. [FEATURE] Fisheye: make the lens field of view a per-file setting #214 (017a362) — Per-file Fisheye lens FOV (180°, 190°, 200°, 220°):
    • Packs polarMode (bits 0..7) and fisheyeFov (bits 8..23) into the 32-bit polar180 field via native/include/polar_mode.h, preserving sizeof(StereoPushConstants) == 128 (enforced by static_assert).
    • Decodes the FOV in native/shaders/vulkan/stereo.frag (defaulting to 190° when unset) and masks polar180 & 0xFF in debug stats and logs.
    • Adds FisheyeFovPreferenceStore keyed by historyKey and a 4-option angle selector in ScreenFormatModal for Fisheye190 and Fisheye190SBS.
  5. [FEATURE] Tests: fail when a manifest permission or analytics dependency is added #215 (7b844b0) — Manifest permission & analytics dependency guard test:
    • Seeds app/config/manifest-guards/permissions.txt from AndroidManifest.xml and adds ManifestGuardTest to fail on unapproved or stale <uses-permission> / <uses-feature> entries and on analytics/crash-reporting artifacts in app/build.gradle.kts.

Verification

Closes #211
Closes #212
Closes #213
Closes #214
Closes #215
Closes #210

@BGLuis BGLuis left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The epic lands in five clean, one-issue commits, and the hard parts are done carefully: the FOV is packed into polar180 instead of growing StereoPushConstants (now pinned by static_assert), the 2D view is a single edit in GetStereoParams that both RecordStereoFrame paths pick up through sp.eyeIndex, MONO_VIEW is cleared in reset_process_state() next to SWAP_EYES, and the GLES fallback gets the JNI symbols so it can't hit UnsatisfiedLinkError. ManifestGuardTest follows the UiSourceGuardsTest pattern and fails on new and stale entries, as #215 asks.

No blocking findings. My comments are mostly about the filename detector, where the tokenizer changes more than #212 describes.

Main points

  • Projection order in detect_from_filename: the bare-360 check now runs before the cubemap checks, so has_token("360") in the 6x1 rule can never fire, and dot/dash names like tour.360.6x1.mp4 go from Cubemap6x1Mono to Spherical360Mono (reproduced; inline).
  • The detector gains matches #212 didn't ask for: a bare vr token now means VR180 SBS (family_vr.mp4), and bare fisheye / fisheye180|200|220 / mkx200 map to fisheye, while the FOV from the name is never used, so clip_fisheye_220 plays at 190° (inline).
  • 2D view stays on across files and the swap is sticky too. #213 left that as an open question; could the answer be written down in the issue?

Smaller

  • The detect_from_filename doc comment and FilenameProjection variant docs still describe substring matching.
  • New and edited comments in ScreenFormatModal.kt and stereo.frag are in Portuguese (CLAUDE.md asks for English).
  • polar_mode.h isn't in the CI clang-format file list.
  • ManifestGuardTest reads the source manifest, so permissions merged in from a dependency's AAR aren't covered.
  • 15 || 16 and 190 are repeated as literals.

Dependencies: no lockfile or manifest version changed (dependency_diff.py). New library calls: SharedPreferences.edit().putInt().apply(), the same pattern as Format3DPreferenceStore; std::atomic and slice::windows. Nothing deprecated there, and the build showed no deprecation warnings on changed lines. Toolchain pins and dependency placement unchanged.

Checked: every changed line against CLAUDE.md (layering Kotlin→JNI→C++→Rust, GLES frozen, i18n positional placeholders and parity, English comments), the four-place screen-mode sync (no index changed), and the acceptance criteria of #211–#215.
Ran (in a separate worktree at 7b844b0): cargo fmt --check, cargo clippy -p protocols -p media-logic --all-targets --all-features -- -D warnings, cargo test -p protocols -p media-logic (all pass); ./scripts/check-shaders.sh (22 OK); ENABLE_ASAN=1 ./scripts/test-native-host.sh (all pass, including TestPolarModeEncodeDecode); clang-format --dry-run -Werror on polar_mode.h and test_screen_mode.cpp (clean); ./gradlew ktlintCheck testDebugUnitTest :app:lintDebug -PrustBuildMode=skip (pass).
Probes (thrown away afterwards): an old-vs-new run of detect_from_filename on about 40 names (the source of the detector points). #215 negatives: adding READ_PHONE_STATE, or removing ACCESS_WIFI_STATE from the manifest, makes ManifestGuardTest fail. Note that the literal criterion implementation("com.google.firebase:firebase-analytics:1.0.0") fails in checkDebugAarMetadata because that version doesn't exist, before the guard runs; with the same coordinate as a plain string in build.gradle.kts, testNoAnalyticsOrCrashReportingDependenciesInBuildGradle fails as intended.
Not run: assembleDebug with the real native + Rust build (the static_assert and the Vulkan/GLES TUs compiled only in CI's build-apk, which is green), and anything on the Quest 3: the headset criteria of #211, #213 and #214 are still unverified.

Comment thread rust/media-logic/src/format3d.rs Resolved
Comment thread rust/media-logic/src/format3d.rs Resolved
Comment thread rust/media-logic/src/format3d.rs Resolved
Comment thread native/include/polar_mode.h Resolved
…ted markers

Addresses review feedback on #221 (threads #r4366250178, #r4366250180, #r4366250186):
- Check EAC and Cubemap projections before bare 360 so that names like tour.360.6x1.mp4 resolve to Cubemap6x1Mono instead of Spherical360Mono.
- Require vr token to be an interior token flanked on both sides (restoring the old _vr_ invariant across separators) so boundary tokens like family_vr.mp4 or Movie.VR.mkv do not false-positive as VR180.
- Remove unrequested fisheye variants (mkx200, fisheye200, fisheye220, fisheye180, bare fisheye/eyefish) to maintain strict scope of #212.
- Update doc comments for detect_from_filename and FilenameProjection variants to describe whole-token matching.
…tants

Addresses review feedback on #221 (thread #r4366250193):
- Add ScreenFormatCatalog.isFisheye(index) alongside isStereo to centralize checking modes 15 and 16.
- Replace repeated literal 190 with FisheyeFovPreferenceStore.DEFAULT_FOV across ScreenFormatModal, VRControlsPresentation, and VRModalPresentation.
- Add testIsFisheye in ScreenFormatCatalogTest.
Addresses review feedback on #221 (thread #r4366250188):
- Translate Portuguese comments in ScreenFormatModal.kt (3D controls and fisheye sections) to English.
- Translate Portuguese comments in native/shaders/vulkan/stereo.frag (mapping, feathering, fisheye projection, crop, SGSR1 bypass, night mode) to English per repository conventions.
…t manifest guard scope

Addresses review feedback on #221 (threads #r4366250195, #r4366250200):
- Add native/include/polar_mode.h to the clang-format header list in .github/workflows/main.yml and GEMINI.md.
- Document in ManifestGuardTest that the test checks source AndroidManifest.xml and does not cover merged permissions from dependency AARs.
…og KDoc to English

- Accurately state in detect_from_filename doc comment that specific projections (Fisheye, EAC, Cubemap) precede 360, which precedes VR180-specific markers and bare 180.
- Add tests for flight.360.eac.mp4 and space.360.cubemap.mp4 asserting precedence over bare 360.
- Translate isFisheye KDoc in ScreenFormatCatalog.kt from Portuguese to English.
@BGLuis

BGLuis commented Oct 24, 2026

Copy link
Copy Markdown
Owner Author

Addressed in 3d50613: 2D view is kept sticky across video loads during the playback session (matching Swap Eyes), and is only reset on full player teardown in reset_process_state(), so users watching stereoscopic playlists in 2D do not have to re-enable it on every track change.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant