Context
The web side recently gained a fallback in textLocatorToAudioLocator (flutter_readium/web/_scripts/Audio/syncNarration.ts): when a text Locator's fragment / cssSelector is set but no SyncNarrationItem.textId matches, we now fall back to the first item in the matching resource. Without the fallback, tapping a ToC entry whose fragment points at a section heading or anchor that isn't itself a Sync Narration item (e.g. chap1.xhtml#title) only moved the visual reader — the audio player kept playing wherever it was.
iOS and Android have the same logic gap in FlutterMediaOverlay.itemFromLocator / findItemFromLocator. They already have a "no fragments + HTML → first item by href" path, but it's only reached when getTextId() returns nil / null. When textId is set but doesn't match any item, both early-return with no match and the audio nav is left alone.
Files / lines
Suggested fix
When findItemFromTextId returns nil/null, fall through to the existing items.first(where: textFile == href) / items.firstOrNull { it.textFile == href.path } line instead of returning early. Mirrors the web fix and the "no fragments + HTML" fallback both natives already have for the no-fragment case. Add a debug/warn log when the fallback fires so unmatched fragments stay discoverable.
Test scenario
- Open a Media Overlay EPUB whose ToC entries include fragments pointing at section headings without sync data (e.g.
chap1.xhtml#title where title is an <h2 id="title"> and only <p> paragraphs have narration items).
- Start audio playback.
- Tap that ToC entry.
- Expected: visual reader scrolls to the section, audio resumes at the start of the matched resource.
- Actual (today): visual reader scrolls to the section, audio keeps playing wherever it was.
Why a follow-up
Discovered while fixing the web equivalent in the feat/web-feature-parity branch; kept scoped to web there to avoid expanding that PR into native code paths.
Context
The web side recently gained a fallback in
textLocatorToAudioLocator(flutter_readium/web/_scripts/Audio/syncNarration.ts): when a text Locator's fragment / cssSelector is set but noSyncNarrationItem.textIdmatches, we now fall back to the first item in the matching resource. Without the fallback, tapping a ToC entry whose fragment points at a section heading or anchor that isn't itself a Sync Narration item (e.g.chap1.xhtml#title) only moved the visual reader — the audio player kept playing wherever it was.iOS and Android have the same logic gap in
FlutterMediaOverlay.itemFromLocator/findItemFromLocator. They already have a "no fragments + HTML → first item by href" path, but it's only reached whengetTextId()returnsnil/null. WhentextIdis set but doesn't match any item, both early-return with no match and the audio nav is left alone.Files / lines
flutter_readium/ios/flutter_readium/Sources/flutter_readium/model/FlutterMediaOverlay.swift:49-52flutter_readium/android/src/main/kotlin/dk/nota/flutter_readium/models/FlutterMediaOverlay.kt:130-132Suggested fix
When
findItemFromTextIdreturns nil/null, fall through to the existingitems.first(where: textFile == href)/items.firstOrNull { it.textFile == href.path }line instead of returning early. Mirrors the web fix and the "no fragments + HTML" fallback both natives already have for the no-fragment case. Add a debug/warn log when the fallback fires so unmatched fragments stay discoverable.Test scenario
chap1.xhtml#titlewheretitleis an<h2 id="title">and only<p>paragraphs have narration items).Why a follow-up
Discovered while fixing the web equivalent in the
feat/web-feature-paritybranch; kept scoped to web there to avoid expanding that PR into native code paths.