Skip to content

fix(media-overlay): gate resource-first fallback on cross-resource navigation - #228

Merged
ddfreiling merged 1 commit into
Notalib:mainfrom
ddfreiling:fix/media-overlay-cross-resource-navigation
Sep 15, 2026
Merged

ddfreiling merged 1 commit into
Notalib:mainfrom
ddfreiling:fix/media-overlay-cross-resource-navigation

Conversation

@ddfreiling

Copy link
Copy Markdown
Member

Summary

Fixes Media Overlay navigation when a ToC or bookmark anchor has no matching narration cue.

  • Falls back to the first narration cue only when navigation crosses into a different text resource.
  • Avoids rewinding audio when an uncued anchor is within the resource currently playing.
  • Makes Web, iOS, and Android follow the same policy.
  • Resolves the active text resource from the current audio locator on all platforms.

Bug

A ToC entry can point to an element without a narration cue, for example chapter.xhtml#title when narration starts at #p1. Previously, Web always sought to the resource start, while iOS and Android left audio at its prior location. This caused either an unexpected same-chapter rewind or visual/audio desynchronization across chapters.

Validation

  • bin/format
  • bin/analyze
  • bin/typecheck
  • Web unit tests: 18 suites, 279 tests
  • bin/update_web_example
  • Android :flutter_readium:testDebugUnitTest
  • iOS RunnerTests on simulator

Remaining Manual Check

  • Exercise an uncued ToC anchor while audio is playing in both the same and a different chapter on iOS, Android, and Web.

Fixes #139

…vigation

When a ToC entry's fragment points at an element with no narration cue (e.g.
a chapter heading `chap1.xhtml#title`), the text→audio mapper previously
behaved inconsistently across platforms:

- iOS/Android: returned nil → audio kept playing at wrong position (desync).
- Web: always fell back to the resource's first cue, even within the current
  chapter (spurious rewind).

Now all three platforms apply the same policy: fall back to the resource's
first cue only when tapping *into a different resource* than the one currently
playing. An uncued anchor within the current chapter leaves audio untouched.

Implementation:
- `FlutterMediaOverlay.itemFromLocator` (iOS) and `findItemFromLocator`
  (Android) gain an `allowResourceFallback` flag (default true) that gates
  both the new unmatched-id fallback and the existing no-fragment/html
  fallback. Exact matches (by time or matched id) are unaffected.
- `textLocatorToAudioLocator` (web) gains the same flag.
- Each navigator computes `crossResource` from the currently-playing item's
  text file vs. the tapped locator's href, and passes it as the flag.
  No current position (initial play / play-from-locator) → true → fallback
  allowed, preserving existing behaviour for those paths.
- iOS: new `FlutterMediaOverlayTests` unit tests cover exact-match,
  unmatched-id (flag true/false), no-fragment (flag true/false), wrong-href.
- Web: extended `syncNarration.test.ts` with `allowResourceFallback=false`
  cases for both fallback branches.
- Android: `FlutterMediaOverlayItem` depends on `android.os.Parcelable`
  and `android.util.Log`, blocking plain-JVM unit tests; gating logic is
  covered by the iOS tests (identical pattern) and E2E integration tests.

Fixes Notalib#139

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@ddfreiling
ddfreiling merged commit 13eea6d into Notalib:main Sep 15, 2026
27 of 29 checks passed
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.

iOS/Android: mirror web Media Overlay text→audio fallback

2 participants