flutter_readium 0.9.0
flutter_readium: ^0.9.0 copied to clipboard
Flutter plugin for EPUB, PDF, audiobook, comic, and WebPub reading with a unified Dart API, powered by Readium's toolkits for iOS, Android, and Web.
Changelog #
All notable changes to flutter_readium are documented here.
Format follows Keep a Changelog.
0.9.0 - 2026-11-04 #
Added #
- New
onInternalLinkActivatedcallback onReadiumReaderWidget. Fires when an EPUB follows an internal link (end-note, index entry, cross-reference), with the position before the jump inevent.fromand the link inevent.target, so apps can offer "Return to previous position". Notify-only: the link is always followed. Android and iOS only. - Android:
loadPublication/openPublicationnow accept URLs with any absolute scheme. Previously any string not starting withhttporfilewas blindly prefixed withfile://, which corrupted Storage Access Frameworkcontent://URIs handed out by a file picker. Such URLs are now passed through untouched and resolved by Readium'sAssetRetriever(which readscontent://through itscontentResolver). Plain local file paths keep working as before.
Changed #
- Web: Readium ts-toolkit upgraded to navigator 2.13.2 / shared 2.8.0. Decorations drawn as overlays now follow the text when the reader scrolls instead of staying where they were drawn, and decorations no longer render hidden behind the page content in some layouts. EPUB navigation no longer hangs after the page was hidden mid-navigation, and swiping to the next chapter of a reflowable EPUB no longer opens it at the previous chapter's progression.
Removed #
- macOS: removed the no-op desktop stub, so pub.dev no longer lists macOS as supported.
The stub answered every call with "not implemented", because the Readium swift-toolkit is iOS-only.
Host apps that also target macOS still build, and reader calls there throw
MissingPluginExceptionas before. Mac users can run the iOS build via "Designed for iPad". (#246)
Fixed #
- EPUB (Android, iOS): tapping the reader now hides or toggles the controls consistently with the native tap zones. An edge tap turns the page and hides the controls, a center tap toggles them once without turning a page, and in scroll mode a tap that content ignores toggles them. Swipe behavior is unchanged. (#281)
ReadiumReaderWidgetcallbacks now follow rebuilds, andonExternalLinkActivatedfires on every platform. On iOS and Android, callbacks from a rebuilt widget were ignored; the first ones kept being called.onExternalLinkActivatedwas never delivered to the app on any platform. On web, passing it now replaces the built-in confirm dialog and new tab. (#298)- Correct the current section in publications with nested tables of contents. Playback could keep reporting a previous subsection after entering another section when its section wrapper was absent from the narration cues. Section boundaries now update the reported entry and the next/previous navigation lookup consistently on iOS, Android, and web.
- Web: EPUBs failed to open in release builds when
fontFamilyDeclarationswas set. The minified bundle resolved the font base URL from the wrong variable, so loading a chapter threwInvalid URL. Font faces now resolve against the host page again. - Web:
goToLocatorignored the position inside a chapter. Only the chapter's href reached the navigator, so fragments, progression and text were dropped and the chapter opened at its first page. The full locator is now passed on. A position-lessinitialLocatoralso keeps its progression and text. - Web: custom fonts from
fontFamilyDeclarationsnever loaded in EPUBs. The EPUB frame's Content Security Policy only allowed fonts from the publication's own location, so the browser blocked the app's font assets. The app's asset directory is now allowed too.
0.8.0 - 2026-10-27 #
Changed (breaking) #
- Web: publication loading and reopening now follow the native lifecycle.
loadPublication()no longer changes the current book. AwaitopenPublication()before mountingReadiumReaderWidget; reopening the same URL preserves the existing publication. To reload it, callclosePublication()first. - Android and iOS: streamed Web Publication positions are now chapter-based.
Locator.locations.positionadvances per chapter.Locator.totalProgressionis also chapter-granular on Android but remains continuous on iOS. Local publications are unchanged.
Fixed #
- Android and iOS: streamed Web Publications open faster. Opening no longer requests every chapter's content length before showing the reader, avoiding delays in large books. Local publications are unchanged. (#288)
- Android: playback notifications and system media controls are restored. Audiobook and TTS playback could lack notification and lock-screen controls with Media3 1.11, including when resuming after switching books. Media3 is updated to 1.11.1.
- Android:
loadPublicationwith the open book's URL left the reader blank. It closed the open publication. It now reads the open publication's manifest without closing it, as on iOS. - Text-only Guided Navigation no longer blocks TTS. A manifest that linked a Guided Navigation document but listed no audio files was treated as narrated, so play tried narration and had nothing to play. Such publications are now treated as text on iOS, Android and web; on web, play reads them with TTS.
- iOS: closed EPUB and PDF readers are now released correctly. Reader views remained in memory after closing, and reopening a book after a Flutter hot restart could freeze the app.
0.7.0 - 2026-10-19 #
Changed (breaking) #
- Android PDFs now open in paginated mode by default.
Previously, they opened in vertical scroll mode. To preserve that behavior, call
setDefaultPDFPreferences(PDFPreferences(layout: PDFLayout.scrollVertical))before opening the reader. - Android apps now require
compileSdk37+, Android Gradle Plugin 9.1.1+, Gradle 9.3.1+, and Kotlin Gradle Plugin 2.3+. The Readium kotlin-toolkit 3.4.0 upgrade raises these build requirements. API 37 requires AGP 9.1.1 or newer, so AGP 8 is no longer supported. Gradle 9.3.1 is the minimum for AGP 9.1.x; newer AGP versions can require a newer wrapper. Update your app's Android configuration before upgrading; see the installation guide.
Added #
- Android now supports page-by-page PDF navigation with
PDFLayout.paginated. Previously, this setting used horizontal scrolling without snapping to individual pages. It now snaps to one page at a time, matching iOS. (#265) - Set the initial PDF layout with
setDefaultPDFPreferences()on iOS and Android. PDFs can now open with your chosen layout without switching after the reader appears. CallingsetPDFPreferences()also updates the default for subsequent PDF readers.
Fixed #
- Android PDFs retain their layout and reading position after returning from the background. Previously, recreating the reader could revert to its initial layout and position. It now uses the current preferences and reading position.
- Android and web preserve the narration position after a seek to an unknown anchor in the current content file.
Unknown anchors now leave playback at its previous position. On Android, these seeks could also prevent
play(null)from resuming; the last valid playback position is now retained. - Android now honors
goToLocatorcalls made while the reader is opening. Previously, navigation could be ignored while the reader initialized, leaving it at the initial location. Navigation now waits for the current reader to initialize. - Rapid repeated skips no longer interrupt web Sync Narration playback or highlighting. Previously, closely spaced skips could leave playback paused or continuing without position and highlight updates. Playback and highlighting now remain in sync across repeated skips.
- Sync Narration now navigates to table-of-contents sections that have no audio of their own on iOS, Android, and web. Sections whose child paragraphs contain narration could fail to navigate or report no current chapter. Navigation now finds the narrated content within those sections, including the next narrated passage after a heading without audio.
- Sync Narration reports the correct chapter when moving between subsections and content files on iOS, Android, and web. Previously, chapter references could disappear or continue to identify the previous file. Narrated passages now retain their section's chapter reference and use the new file's chapter when crossing a file boundary.
- Web audiobook playback can seek back to a table-of-contents entry at the start of the current audio file. Previously, seeking to the track's beginning left playback at its current position. Playback now returns to the start of the track.
0.6.2 - 2026-10-13 #
Fixed #
- Web: sync-narration audio had no stream error recovery.
A failing media-overlay audio track only gave
TimebasedState.failure: noAudioStreamAuthError, no retries. The recovery was wired for plain audiobooks only. Media-overlay sessions now get the same classification, retries andplay()-after-failure as audiobooks, as on iOS and Android. - iOS:
resume()afteraudioEnable(fromLocator:)played a media-overlay book from 0:00. The initial locator was mapped before the media overlays loaded. It is now mapped after they load. - Web could be reported as iOS or Android on mobile browsers. The browser user agent can influence
defaultTargetPlatform, which sent web code down native platform paths. Runtime platform flags now report iOS and Android only for native apps. - Android: Google Play rejected apps under its Photo and Video Permissions policy.
The plugin declared
READ_EXTERNAL_STORAGEwithout using it, and Play counts it as broad media access. The permission is now removed. Apps that open books from shared storage on Android 12 or lower must declare it themselves.
Added #
ttsGetAvailableVoices()now adds Readium Speech metadata on every platform. Voice enrichment previously ran only on Android. Known voices now get a friendlylabeleverywhere; catalog gender fills unspecified gender, and catalog quality fills missing quality. Reported gender and quality are preserved.
0.6.1 - 2026-10-06 #
Fixed #
- Web: TTS could stop for good after a speechSynthesis stall.
The stall watchdog treated
speaking: truewith noonstartyet as a wedge, which is also what a perfectly normal queued utterance looks like. After two timeouts it emittedfailureand left the engine stuck, so playback only came back by reloading. The watchdog now waits for real silence, always resets the engine before reporting failure, and can no longer cancel the utterance that replaced it or speak again afterstop().
Added #
- Optional scroll-mode page-turn lock — Set
ReadiumReaderWidget.disablePageTurnsWhileScrollingto prevent horizontal gestures from switching EPUB resources while vertically scrolling on iOS and Android. Apps enabling it should provide explicit page or table-of-contents navigation.
Changed #
equatable3.x is now supported. Theflutter_readium_platform_interfaceconstraint was^2.1.0, which blocked apps from upgrading. It is now>=2.1.0 <4.0.0. Model equality is unchanged: equatable 3 still comparesruntimeType, and the models never relied onEquatableMixin.
0.6.0 - 2026-09-22 #
Fixed #
- iOS: Re-sync stopped working after the first use within one narration cue. The current cue is now retained until narration stops, allowing repeated manual-navigation/re-sync cycles.
- Web: synchronised audio never started until the reader view was on screen.
audioEnableissued right afteropenPublicationhit a publication whose audio capability had not been detected yet, and gave up silently. Audio capability is now detected as the publication loads, soaudioEnablebuilds the audio navigator without a reader view — matching iOS and Android, where audio can play without showing book content. Mounting the reader view no longer stops a book that is already narrating. - DiViNa comics played no audio on Android and web. Their guided-navigation cues point at the page image instead of a text document, and both parsers required a text reference, so every cue was dropped and playback aborted silently. Image-only cues are now accepted, matching iOS; on web the page image also drives page turns and panel panning.
Changed #
- Web:
audioEnablenow requires an awaitedopenPublication. A call that arrives before the publication is loaded used to be remembered and replayed; it now fails withNoPublication, matching Android's awaited error contract.
0.5.0 - 2026-09-17 #
Added #
- Extra JS/CSS injection —
FlutterReadium().setJavaScriptInjections(List<InjectionAsset>)andsetCssInjections(List<InjectionAsset>)inject additional assets into every EPUB HTML resource, alongside the built-influtterReadiumToolsfiles. iOS and Android only.
Fixed #
- iOS: reader views created after
audioEnablemissed the media-overlay column-break CSS. The active flag is now seeded from the live navigator on registration, matching Android. - iOS: Swift Package Manager builds resolved swift-toolkit 3.9.0 instead of 3.11.0 due to a
stale lower bound in
Package.swift. Both build systems now pin 3.11.0. - Android: EPUB navigation could report the initial position after an immediate jump,
because a restore raced an explicit
goToLocator. It now waits for the first page to load. - Guided-navigation playback reported the wrong reading-order position on iOS and web, off by one due to a double index conversion.
- Web:
audioEnablecalled before the reader view mounted was silently dropped, so synchronised audio for a text publication could never start. It's now remembered and replayed once the publication opens. - Web: reopening an audiobook restored the saved position but stayed paused, because
audioEnabledidn't await its restore seek. The seek is now awaited before it resolves.
0.4.4 - 2026-09-08 #
Fixed #
- Seeking an audiobook to a position in a different track could hang the app. Jumping via
goToLocatorwhile playback was paused never returned from Readium's iOS audio navigator, so the call's result never resolved. Cross-track seeks now complete normally, report their settled offset, and use a timeout to ensure the native call always resolves. - Audiobook playback could briefly stutter or replay a few seconds during healthy playback.
The watchdog kept monitoring after a resource had started and could rebuild the player during a
later pause in progress. It now checks only that playback starts after play/resume or a resource
change. Loading timeouts now only report
loadingby default; applications can opt into navigator recovery withrecoverOnResourceLoadingTimeout. Explicit player errors still recover.
0.4.3 - 2026-09-01 #
Fixed #
ReadiumReaderWidgetcould leave its loading widget visible after the publication had rendered. Native readers now send a dedicated per-widget visual-ready event instead of making the loading state depend on a later, asynchronously enriched page-location event.- Creating
ReadiumReaderWidgetcould freeze the Android UI while the native reader initialized. Publication positions, which Readium's EPUB and comic (DiViNa/CBZ) fragments read with a blocking call, are now fetched and cached on the I/O dispatcher first. - Android builds warned that
flutter_readiumstill applied the Kotlin Gradle Plugin. The Android source layout and example now support AGP's built-in Kotlin, andwakelock_plusis constrained to a compatible release.
0.4.2 - 2026-08-28 #
Fixed #
- Jumping to a saved position in a different audiobook track landed at the wrong time (iOS). The offset was scaled by the length of the track already playing instead of the target track. Restoring in a freshly opened book was the worst case: it started that track from the beginning.
0.4.1 - 2026-08-27 #
Added #
onExternalPlaybackCommandstream for play/pause/seek commands received from system media controls such as headphones, iOS Control Center, and Android media-session controls.
Fixed #
- iOS build failed on Swift 6. Building with Xcode 26 / Swift 6.3 stopped with
Cannot find 'NSEC_PER_SEC' in scope. The timeout helper used thatDarwinmacro from a file with no imports, which only resolved because older Swift leaked imports between files of one compilation unit. It now uses a plain nanosecond literal. - Text selection was dead on the first mount of the reader on Android.
onTextSelectednever fired and custom selection actions never appeared, with nothing logged.selectionActionsis now read from the creation params — as iOS already did — so it is known before the navigator fragment is built, instead of relying on the laterconfigureSelectionActionscall.
Documentation #
- Document the core library desugaring requirement in the installation guide and the README, and
correct the error message shown when
MainActivitydoes not extendFlutterFragmentActivity.
0.4.0 - 2026-08-17 #
Added #
- App-supplied static EPUB fonts on iOS, Android, and Web.
ReadiumReaderWidget.fontFamilyDeclarationsregisters Flutter font assets with each platform navigator, including normal/italic styles, numeric weights, and fallback families.
Fixed #
- URI-templated manifest links are now expanded for guided-navigation and sync-narration sidecar resources on web, iOS, and Android.
- Bundled OpenDyslexic now loads on Android. Kotlin Toolkit 3.2.0 served its embedded font from a separate WebView origin without a CORS response header. Android now uses Kotlin Toolkit 3.3.0, which fixes asset font loading.
- iOS custom fonts now apply to headings in unstyled publications/WebPubs.
- Reader could report
readyand then never emit a text locator (iOS, Android). Locator enrichment (a JavaScript page-info call plus a ToC lookup) was unbounded, so a stalled platform webview silently frozeonTextLocatorChangedfor good. Enrichment now times out after 5 seconds and the un-enriched locator is emitted instead.
0.3.3 - 2026-08-04 #
Bundle built javascript helpers, previously accidentally skipped by release pipeline.
0.3.1 - 2026-07-24 #
Fixed #
- iOS build failure on newer Flutter/Xcode toolchains (e.g. Flutter 3.44):
Cannot find type 'TimeInterval'/Cannot find 'JSONEncoder'inEPUBReaderView+Navigation.swift,EPUBReaderView+Preferences.swift, andEPUBReaderView+Decorations.swift, caused by a missingimport Foundation.
0.3.0 - 2026-07-13 #
Added #
- Audio streaming error events + connection recovery (iOS, Android, Web) —
remote audiobook streaming failures, including network/auth errors and
stalled playback, are now reported on the error stream instead of being
silently swallowed. Transient failures retry automatically with backoff;
terminal failures emit a typed code and
TimebasedState.failure, after whichplay()retries from the last position. Configure the retry budget, backoff, stall timeout, and per-attempt connection timeout withFlutterReadium().setAudioRecoveryPolicy(AudioRecoveryPolicy(...)); its defaults preserve the prior recovery behaviour. The code vocabulary and Web HTTP-probe caveat are documented indocs/api-reference/error-codes.md.
Changed #
- Breaking: awaited plugin failures now use the unified
ReadiumException(ReadiumError)surface across native and web transports;InvalidArgumentvalidation failures remainPlatformException. The opening-specific Dart exception API (OpeningReadiumException,OpeningReadiumExceptionType,PublicationNotSetReadiumException, andOfflineReadiumException) has been removed. CatchReadiumExceptionand inspecte.codeore.codeEnuminstead.PlatformException.codefor Readium/domain failures now matches the sharedReadiumErrorCodewire strings. - Breaking:
ReadiumError.datais replaced by the structured, unmodifiableReadiumError.detailsmap with typed getters.ReadiumErroris now a value type rather than a DartError, and its nativestackTraceis no longer available. Error codes are additionally exposed as the typedReadiumError.codeEnum(ReadiumErrorCode); seedocs/api-reference/error-codes.md.
0.2.1 - 2026-07-09 #
Fixed #
- Web: audio/TTS/decoration control calls no longer leak raw JS errors —
goBackward/goForward, playback controls, decoration application, and TTS/audio preference and voice setters called straight into the JS bundle with no error handling; a thrown JS error surfaced as an unconverted exception instead ofReadiumException. FlutterReadium.imageProvider(href)now throwsReadiumExceptionon failure — a failed resource load previously surfaced as a rawPlatformExceptionto the image'serrorBuilder, inconsistent with every other plugin call.- Android: Fixed-layout books can now navigate — Previously navigating in an FXL publication was a no-op.
- iOS: audiobooks now reliably emit
TimebasedState.endedat end of book — the end-of-book heuristic compared playback progress against1.0, but AVPlayer's reported position at end-of-track rarely lines up with duration, so.endedwas never emitted. We now derive from the navigator's own "resource finished" signal instead. - Android: audiobook
goToLocatornow waits for the seek to take effect — previously it could return before the player had moved, so a followingplay()resumed from the old position (e.g. jumping to a bookmark and playing could start from the wrong place).
0.2.0 - 2026-07-02 #
Added #
- EPUB image tap — tapping an image in an EPUB now fires
onImageTappedwith anImageTapEventcarrying the publication-relativehref, optionalalt/caption, on-screenrect, and pixel dimensions. Detection runs on iOS, Android, and Web. Android and Web suppress image-tap events for DiViNa publications and Nota comic page images so narrated comics keep their panel navigation behavior. getResourceUrl(href)— new API onFlutterReadiumand the underlying platform interface that resolves any manifest resource to a loadable URL — a native-cachedfile://URL on iOS/Android, or the served resource URL on Web — plus a companionFlutterReadium.imageProvider(href)that plugs into Flutter's image pipeline for lazy display. Implemented on iOS, Android, and Web. On Web, displaying the resolved URL requires the resource's server to sendAccess-Control-Allow-Origin— a CanvasKit/browser constraint, not fixable client-side (seedocs/troubleshooting.md). iOS/Android are unaffected.- Narration sync state & manual mode (iOS, Android, Web) — a new
FlutterReadium.onNarrationSyncChangedstream (Stream<bool>:true= following narration,false= manual mode) andFlutterReadium.setNarrationSyncEnabled(bool). While Media Overlay or TTS narration is driving the reader, manually turning the page (swipe, edge-tap, or next/previous) now enters manual mode: audio keeps playing while the reader stops auto-following cues, andonNarrationSyncChangedemitsfalse(e.g. to show a "Re-sync" control). CallsetNarrationSyncEnabled(true)to snap the reader back to the current narration position. An explicit jump (goToLocatorto a TOC entry, bookmark, or search result) instead re-seeks narration to the new location, keeping audio and reader together. - Comic panel pan/zoom during narration (iOS, Android) — Nota EPUB+MediaOverlay comics now
pan and zoom to the active panel as narration plays on native platforms, matching the existing
web behaviour. A manual pinch-zoom in the EPUB webview enters the same manual mode as a page
swipe, emitting
onNarrationSyncChanged(false);setNarrationSyncEnabled(true)re-pans to the current narrated panel. - Comic explore mode — Nota MO comics now open directly to the comic page view (no EPUB chrome) and accept pinch-zoom for free exploration before narration is started. When narration begins, the view transitions automatically to panel-by-panel mode — no Re-sync action needed. Stopping narration snaps the comic back to the full-page view instantly.
Changed #
TTSPreferences.pageBreakBehavior— controls how EPUB page-break elements (DAISY/Nordic EPUB3epub:type="pagebreak") are handled during TTS. Accepts aPageBreakBehaviorenum:readAsIs(default — raw label text spoken unchanged),prefixLabel(label rewritten with a localized prefix, e.g. "Page 42" / "side 42"; supports English, Danish, Swedish, Norwegian, Icelandic; falls back to the raw label otherwise),skip(element filtered out entirely). Replaces the previousskipPageBreaksbool.EPUBPreferences.disableSynchronizationis deprecated in favour of the runtimeFlutterReadium.setNarrationSyncEnabled(bool)/onNarrationSyncChanged. The preference still works and now seeds the unified narration-sync state when a publication is opened.
Fixed #
-
Media Overlay ToC navigation to cue-less anchors — tapping a ToC entry whose fragment points at a heading or element with no narration cue (e.g.
chap1.xhtml#title) now seeks audio to the start of that chapter when navigating to a different chapter, and leaves playback untouched when the anchor is within the current chapter (no spurious rewind). Previously iOS and Android kept audio playing at the wrong position (visual/audio desync on chapter cross), and Web always rewound to the chapter start even for same-chapter anchors. -
iOS media-overlay playback crashes on malformed sync-narration data — starting playback in a publication with a reversed or non-finite audio time fragment (
t=start,endwhereend < start) no longer traps withRange requires lowerBound <= upperBound, and anarrationblock with no valid audio/text pairs no longer crashes navigator setup. Such items now degrade gracefully.
0.1.1 - 2026-06-26 #
Changed (breaking) #
EPUBPreferences.fontSizeis now adoubleratio (1.0= default,1.5= 150%) instead of a percentageint. Divide existing values by 100 to migrate (fontSize: 130→fontSize: 1.3). This fixes Android font-size having no visible effect (#140) and aligns the API with Readium's ownEpubPreferences.fontSize.- Web
stop()now tears down the active audio/TTS navigator, matching iOS and Android. CallaudioEnable()orttsEnable()again before resuming audiobook, Media Overlay, Guided Navigation, or TTS playback afterstop().
Added #
- CBZ comic support (iOS, Android, Web) — CBZ archives (Comic Book ZIP) now open and render.
Pages are displayed one at a time; swipe/tap navigates between pages and
goToLocatorrestores saved positions. On iOS/Android the existingblackAndWhiteComicModepreference inReaderEpubPreferencesapplies the grayscale filter to CBZ pages as well. - DiViNa narrated-comic support (iOS, Android, Web) — DiViNa publications (
profiles/divina) that carry a Guided Navigation document open as comics with page-synced audio narration. Page images render (via the fixed-layout path on iOS/Android; via a plugin-side image navigator on Web, since ts-toolkit ships no DiViNa navigator), andaudioEnable/playdrive page-synced audio from the guided-navigation document. Panel-level zoom is not yet implemented on any platform (the segments'xywh/imgrefregions are carried in the asset for that follow-up). - Web comic navigation — DiViNa/CBZ publications page one image at a time, emit page locators
(
onPageChanged), and supportgoToLocator/goToProgression.
Fixed #
EPUBPreferences.columnCount(one/two) not applied — the serialized value diverged from Readium's canonicalColumnCount(auto/1/2), so Android threw aPlatformExceptionand iOS silently ignored the setting when starting playback. The shared Dart serialization now matches the native toolkits; column count is applied on all platforms.- iOS: possible crash (
Index out of range) when enabling audio / starting playback on media-overlay books and comics, caused by an unchecked reading-order index in locator resolution. Out-of-range positions now degrade gracefully instead of trapping. - iOS + Web: synchronization catch-up after re-enable — when
EPUBPreferences.disableSynchronizationis turned back off (true -> false), the visual EPUB navigator now jumps to the last sync locator that was reached while synchronization was disabled, matching Android behavior. - iOS: Media Overlay clips that span a CSS column boundary no longer desync — in
paginated mode, a paragraph straddling two columns would start audio on the first
column but continue playing through text visible only on the second column. When
media-overlay playback is active, a
break-inside: avoidCSS rule is now injected so each paragraph stays whole on one page, keeping audio and visible text in sync. Controlled byEPUBPreferences.preventMOColumnBreaks(defaulttrue; set tofalseto opt out and restore the original layout). - Android + Web: Media Overlay clips that span a CSS column boundary no longer desync — same fix
as iOS above, now applied to Android and Web via the shared
flutterReadiumhelper-script bundle. - iOS: TTS no longer snaps back to the previous page mid-sentence — when a spoken sentence
crossed a paginated page boundary, the reader correctly advanced to page N+1 for the word being
spoken but then flickered back to page N on each subsequent word. The cause was a double-assignment
to the
@Published playingUtteranceproperty (raw locator, then position mutation), which defeatedremoveDuplicates()and fired the page-sync on every word update instead of only on utterance changes. - Web: Improved error-handling -
ttsEnable,audioEnable, andttsGetAvailableVoicesfailures are now caught and.stackis now included inPlatformException.message. - iOS: early reader events are no longer dropped — the
text-locatorandreader-statusevent channels now buffer the most-recent event on the native side when Dart has not yet attached a listener. The buffer is flushed immediately whenonListenfires.
0.1.0 - 2026-06-20 #
Brings the Web platform up to feature parity with iOS / Android (audio, Media Overlay, TTS, Guided Navigation, decorations), plus a handful of supporting cross-platform additions.
Added #
- Web: Audio Navigator — audiobook publications now play on web.
audioEnable,play,pause,resume,stop,next,previous,audioSetPreferencesare all wired up via the upstreamAudioNavigator(ts-toolkit 2.4.0+). Playback state (offset, duration, locator) streams throughonTimebasedPlayerStateChanged, matching the iOS / Android contract. - Web: Media Overlay (Sync Narration) — EPUBs with embedded Sync Narration JSON
alternates (
application/vnd.readium.narration+json) can now play their synchronized narration.audioEnable()parses the narration, builds a synthetic audio reading order, and drivesAudioNavigator; audio time is mapped back to text locators soonTextLocatorChangedemits text-href locators as narration advances (matching iOS / AndroidreachedLocator). Enabling audio resumes from the visual reader's current position,goToLocatorand ToC / bookmark taps seek the audio to the matching narration item, andaudioSeekByis wired up viaAudioNavigator.jump(). - Web: TTS (text-to-speech) —
ttsEnable,ttsGetAvailableVoices,ttsSetVoice,ttsSetPreferencesare implemented on web using the browser'sSpeechSynthesisAPI and@readium/shared'sPublicationContentIterator+HTMLResourceContentIteratorfor paragraph-level text extraction. Playback state streams throughonTimebasedPlayerStateChangedand position bookmarks throughonTextLocatorChanged. Voice gender / quality is enriched via the bundledvoices.jsonfrom https://readium.org/speech/.play,pause,resume,stop,next,previousdispatch to the TTS engine when active, falling back toAudioNavigatorotherwise. - Web: Guided Navigation support — the web platform now detects EPUBs carrying
application/guided-navigation+json(publication-level link or reading-order alternate) and plays them through the Media Overlay pipeline, mirroring iOS / Android. When both Guided Navigation and Sync Narration are present, Guided Navigation takes precedence — matching native behaviour. Playback keeps the visual reader scrolled in sync. - Web: comic / FXL publication support — fixed-layout (Nota comic) publications now render on web.
- Web:
goToProgression— navigates to an absolute progression (0.0–1.0) on web. Supports EPUB (position-list lookup), audiobook (seek toprogression × duration), and Media Overlay content types. - Web:
audioSeekBy—audioSeekBy(Duration offset)is implemented for audiobook and Media Overlay playback viaAudioNavigator.jump(). - Web:
onErrorEventstream implemented — subscribing toFlutterReadium().onErrorEventon web no longer returns an empty stream. A broadcastStreamController<ReadiumError>now backs the stream;openPublicationfailures in the JS bundle are forwarded to Dart via anonErrorCallbackwindow setter. Pure audiobook paths register the same callback via_AudiobookCallbacks. - Web: ToC enrichment for media-overlay items — Sync Narration and Guided
Navigation items are enriched with
tocTitle/tocHrefderived from the publication's table of contents, matchingenrichOverlaysWithTocon iOS / Android. - Web:
onTextLocatorChangedlocators now carrytocHref— the EPUB navigator enriches each emitted locator with the current chapter's ToC href, matching the iOS / Android contract and unblocking chapter-skip features on the consumer side. - Web: TTS locators now carry
tocHref—Locator.locations.tocHrefis now populated on every locator emitted during TTS playback (utterance-start and word-boundary events), so chapter-aware features work during TTS on web — matching the existing behaviour for visual navigation and audiobook / media-overlay playback. - Web: reading-order item duration propagated to media-overlay items — the parent
reading-order link's declared
duration(when present) is carried on each item and used as the authoritative fallback for the synthetic audio Link's duration, replacing the cue-sum-only computation that underestimated total length when cues left gaps. - Web:
scrollPaddingLeft/scrollPaddingRightEPUB preferences — new fields in ts-toolkit 2.5.x are now passed through to the navigator. - Web: structured console logging — all web TS modules now log through a tagged
logger (
[Readium/<Module>] LEVEL: message) with runtime level control.setLogLevelnow propagates to the JS bundle so web logging verbosity is controlled from Dart alongside the native platforms. - Dart: tagged logging (
TaggedReadiumLog) — newReadiumLog.tag('Name')factory creates child loggers namedflutter_readium.<Name>, surfacing the source / area in log records (e.g.[INFO] flutter_readium.WebPlugin: ...). - PDF preferences: three new iOS-only fields —
offsetFirstPage: bool?,spread: PDFSpread?(new enum:auto/never/always), andvisibleScrollbar: bool?. These map to the matching properties on the iOSPDFNavigatorViewController.Preferences. AndroidPdfiumPreferencesdoes not expose these fields; they are silently ignored on Android and web. totalProgressionfor EPUB and audio navigators (web) — computed and surfaced for the progress slider on web.totalProgressDurationon timebased playback state —onTimebasedPlayerStateChangednow includes a publication-level elapsed duration (ReadiumTimebasedState.totalProgressDuration) computed fromcurrentLocator.locations.totalProgressionand publication duration when available.totalDurationon timebased playback state —onTimebasedPlayerStateChangednow includes the total publication duration (ReadiumTimebasedState.totalDuration), the sum of all reading-order link durations;nullwhen any link is missing a duration.DecorationStyle.spotlight— new decoration style that dims everything outside the decorated range and (optionally) renders the tint inside it. Implemented across Dart API, iOS (box-shadow+ body dim), Android (box-shadow+ body dim), and web (body.flutter-readium-spotlightgates a body-wide dim; a per-group::highlight()restore rule keeps the spotlit range readable, and the caller-supplied tint colours the range's fill). Pass a non-transparent tint for "dim outside + tinted fill"; pass a transparent tint for "pure dim outside".DecorationStyle.ruler— new decoration style that renders a typoscope / reading mask: two full-viewport-width dim bands sit above and below the decorated range, leaving the range itself clear. Best suited to scrolled layouts. Implemented on the Dart API and on web (geometry-driven from the resolved range element, so it works in both the CSS Custom Highlight API path and the DOM-fallback path).ReaderDecorationStyle.isActive— newboolfield (defaultfalse) that renders the decoration in a visually distinct "active" state to mark the currently-focused annotation. Maps to the upstreamDecoration.Style.HighlightConfig.isActiveon iOS andDecoration.Style.Highlight/Underline.isActiveon Android; surfaces on web for the highlight / underline / spotlight paths.
Changed #
- Web: ts-toolkit version bump —
@readium/navigator^2.2.4→^2.5.5,@readium/navigator-html-injectables^2.2.1→^2.4.2,@readium/shared^2.1.1→^2.2.0. Picks up FXLpositionChangedreliability fix (navigator #218), vertical / RTL writing-mode support, Readium CSS v2.0.0, and content-protection infrastructure. - Web Decorator API —
applyDecorationsandsetDecorationStyleare now functional on web.applyDecorationsreplaces a group's decorations by sending a"clear"then an"add"per decoration via the upstream@readium/navigator-html-injectablesFrameComms"decorate"command. Thehighlight(filled box) andunderline(border-bottom) styles are both supported. - Web underline-style decorations —
DecorationStyle.underlinerenders as a border-bottom in the tint colour rather than a filled box, routed to a separate upstream group (<group>__underline) with an injected stylesheet +MutationObserverper iframe. The same distinction works in the CSS Custom Highlight API path (modern Chrome) via a paired sibling<style>whose::highlight()rule wins by cascade order. - Web: EPUB preferences mapping cleanup —
epubPreferences.tsnow mirrors the DartEPUBPreferencesshape (one preference per Dart field), with documented conversions:columnCountenum (auto/one/two) →number | null,imageFilterenum (darken/invert) →darkenFilter/invertFilter, andfontSizedivided by 100 to match the iOS plugin (Dart120→ web1.2). Dart fields the web navigator can't honor (publisherStyles,readingProgression,spread,typeScale,verticalText,language,blackAndWhiteComicMode,firstElementTopMargin) are dropped with inline rationale. - Web: content-protection, peripheral, and context-menu listener stubs — new required listener fields from ts-toolkit 2.3.0 are now present on both EPUB and WebPub navigator configurations.
- Docs: removed
EpubThemeType/themepreference — thethemefield referenced indocs/api-reference/preferences.mdanddocs/guides/preferences.mdwas never implemented; the docs now show how to achieve light / dark / sepia by settingbackgroundColorandtextColordirectly.
Fixed #
These are fixes to behaviour that shipped in 0.0.1 — chiefly iOS playback / locator
positioning, the existing web EPUB visual reader, and cross-platform serialization. (Bugs
introduced and resolved while building the new web audio / TTS / Media Overlay features are
not listed separately; their net effect is the Added entries above.)
- iOS: media-overlay books now resume at the saved in-chapter position — reopening a
sync-narration book restored the top of the chapter instead of where you left off (it only
snapped to the right place once playback started). swift-toolkit's reflowable navigator positions
via
fragments.firstand ignorescssSelector, where the media-overlay locator's DOM anchor was stored; the locator's DOM anchor is now promoted intofragments.firstfor the iOS visual navigator. Seedocs/parity/locator-field-priority.md. - iOS: TTS no longer snaps back to the previous page mid-sentence — when a spoken sentence
crossed a paginated page boundary, the reader correctly advanced to page N+1 for the word being
spoken but then flickered back to page N on each subsequent word. The cause was a double-assignment
to the
@Published playingUtteranceproperty (raw locator, then position mutation), which defeatedremoveDuplicates()and fired the page-sync on every word update instead of only on utterance changes. - iOS: early reader events are no longer dropped — the
text-locatorandreader-statusevent channels now buffer the most-recent event on the native side when Dart has not yet attached a listener. The buffer is flushed immediately whenonListenfires. - Web: improved error-handling —
ttsEnable,audioEnable, andttsGetAvailableVoicesfailures are now caught and.stackis now included inPlatformException.message. - Web:
setEPUBPreferencesno longer wipes existing preferences — the converter now emits only fields the Dart caller explicitly set, leaving prior preferences untouched on merge. Previously every unset field was sent asnull, which the navigator'smerging()does not skip (onlyundefined), so a partial update reset everything. - Web:
onTextLocatorChangedno longer floods consumers during scroll — text-locator events are trailing-edge debounced at 250 ms, matching the per-page cadence of the iOS / Android plugins (the ts-toolkit emits ~60 events/sec in scroll mode). - Web: EPUB navigation (
goTo,goForward,goBackward, ToC links) now works —ReadiumReader.goTosearchesreadingOrderbeforeresources(ToC chapter links point into reading order, so the previous resources-only lookup always failed), and the JS bridge now implements the progression-aware navigation methodsgoForward/goBackwardcall (previously errored withis not a function). - Web: nested ToC entries are no longer dropped —
flattenToctreated@readium/shared'sLinksas a plain array, silently discarding nested children; it now uses theLinksAPI. LocalizedStringtranslation-map parsing andProperties.toJsonpagekey serialization corrected (affects all platforms).- iOS:
applyDecorationsandsetEPUBPreferencesno longer hang when awaited — the EPUB reader view's native handlers now return a method-channel result on success (matching the PDF reader view); previously they never completed, so awaiting these methods could hang forever. - Web:
WebPubNavigatornow debouncesonTextLocatorChangedduring scroll — matches the existing EPUB debounce. The WebPub navigator was emitting position-changed events at rAF rate (~60 Hz), flooding the Dart-side text-locator stream with redundant updates. - Web: spotlight decoration now honours its tint and dims reliably on EPUB-profile
publications — the spotlight CSS no longer strips the caller-supplied tint (it
used to force
background-color: transparent !important, silently dropping any fill colour), and the body-wide dim selector is now specific enough to beat the ReadiumCSScustomColors_pref.cssuser-text-colour rule that previously won the cascade on EPUB-profile publications.
0.0.1 - 2026-06-01 #
Added #
- Core reader API —
FlutterReadiumsingleton providingopenPublication,closePublication,loadPublication,goToLocator,goToProgression,goForward,goBackward. - EPUB reader widget —
ReadiumReaderWidgetrenders EPUB and WebPub content via a native platform view (iOS/macOS/Android) or a WebView (web). - EPUB preferences —
EPUBPreferenceswith font family, font size, scroll mode, line height, word spacing, letter spacing, paragraph spacing, text alignment, column count, publisher styles, vertical writing, custom CSS properties and first-element margin. - TTS (text-to-speech) —
ttsEnable,ttsSetPreferences,ttsSetVoice,ttsGetAvailableVoiceswith voice metadata loaded from the Readium speech voice-data registry. TTS decoration styles are configurable viasetDecorationStyle. - Audio / MediaOverlay playback —
audioEnable,audioSetPreferences,audioSeekBy,play,pause,resume,stop,next,previousfor pre-recorded audio publications and MediaOverlay synchronized narration. - Decorations —
applyDecorationslets callers add highlights, underlines, and custom decoration styles to the visual reader. - Decoration styles —
DecorationStylehas two modes:highlight(filled rectangle behind text — default) andunderline(border-bottom in tint colour). Both are supported on iOS and Android. - Text selection callback —
ReadiumReaderWidget.onTextSelectedfires aTextSelectionEvent(locator + selected text) when the user selects text in the reader. - Selection actions —
ReadiumReaderWidget.selectionActionsconfigures native context menu items (up to 5 on iOS) shown on text selection. Tapping an action firesReadiumReaderWidget.onSelectionActionwith aSelectionActionEvent. - Decoration interaction —
ReadiumReaderWidget.onDecorationInteractionfires aDecorationInteractionEventwhen the user taps an existing decoration/highlight. Supported on iOS and Android. - Allowed default actions —
ReadiumReaderWidget.allowedDefaultActionscontrols which system-provided selection menu items (Copy, Share, Look Up, Translate, Select All) are shown. Passnullfor all defaults, or a specificSet<DefaultSelectionAction>to filter. iOS supportscopy,share,lookup,translate; Android supportscopy,share,selectAll. Unsupported values for a platform are silently ignored. - PDF reading —
ReadiumReaderWidgetopens PDF publications on iOS (PDFKit viaPDFNavigatorViewControllerfrom swift-toolkit) and Android (PDFium viaPdfiumNavigatorFragmentfrom kotlin-toolkit). PDF is not supported on Web. - PDF preferences —
FlutterReadium.setPDFPreferences(PDFPreferences)applies runtime display settings (layout,readingProgression,pageSpacing,fit) to the active PDF navigator.PDFLayoutunifies iOS'sscroll+scrollAxisand Android'sscrollAxisinto one cross-platform setting (paginated,scrollVertical,scrollHorizontal);PDFFitcontrols page fitting (auto,page,width). - PDF TOC enrichment —
onTextLocatorChangedevents for PDF publications includetitle(chapter name) andlocations.otherLocations["tocHref"]derived from#page=NTOC fragments, matching the existing EPUB enrichment behaviour. - Search —
searchInPublicationreturns a list ofTextSearchResultmatching a query string. - Navigation helpers —
skipToNextTOC/skipToPreviousTOCwalk the publication's table of contents;toPhysicalPageIndexandgoByLinknavigate by page-list entry or link. - Event streams —
onReaderStatusChanged,onTextLocatorChanged,onTimebasedPlayerStateChanged,onErrorEventexpose real-time reader state as Dart streams. - Platform support — iOS (swift-toolkit 3.7.0), macOS (same), Android (kotlin-toolkit 3.1.2), Web (TypeScript webpack bundle using @readium/navigator).
- Custom HTTP headers —
setCustomHeadersforwards headers to the native HTTP layer. - Log level control —
setLogLevelconfigures the plugin's internal logging verbosity. - Page information —
Locations.pageandLocations.totalPagesextension getters expose the current page and total page count (parsed from locator fragments) for publications that include a page list. - Progress slider support in the example app via a slider bound to
totalProgression.