Blog

Designing Libryva's playback history layer

How Libryva uses Drift and SQLite to remember audiobook position, progress, speed, chapters, bookmarks, and listening stats locally.

  • engineering
  • flutter
  • drift

An audiobook player lives or dies by one small promise: when you come back, it starts where you stopped. In Libryva that promise is not a UI trick. It is modeled directly in the local database and playback coordinator.

One database, no server

Libryva stores app state in an on-device SQLite database managed by Drift. The first release track uses that database for audiobooks, chapters, bookmarks, playback history, playlists, download queue entries, sleep timer sessions, user preferences, and listening statistics.

That local-first choice gives the app three practical advantages:

  • Fast resume. The app can load recent history without waiting for a network call.
  • Offline listening. Imported media and playback history remain available anywhere.
  • A clean sync path later. Future cloud sync can move well-structured local state instead of inventing the product model on a server.

Playback history is first-class

class PlaybackHistories extends Table {
  TextColumn get id => text()();
  TextColumn get audiobookId => text()();
  IntColumn get position => integer()();
  IntColumn get duration => integer()();
  RealColumn get progress => real()();
  TextColumn get chapterId => text().nullable()();
  RealColumn get playbackSpeed => real().withDefault(const Constant(1.0))();
  TextColumn get lastPlayedAt => text()();
  IntColumn get totalListenTime => integer().withDefault(const Constant(0))();
  TextColumn get completedAt => text().nullable()();
  BoolColumn get isCompleted => boolean().withDefault(const Constant(false))();
}

The important fields are not just position and progress. Libryva also stores chapter context, playback speed, last played time, total listen time, and completion state. That makes “Continue Listening” a direct database query instead of a fragile reconstruction from player state.

Saving happens during the session

The playback coordinator saves progress in three moments:

  1. When a book is opened, so it can appear in recent history even before the first autosave tick.
  2. While playback is active, on a periodic autosave interval.
  3. When playback pauses, stops, or the app lifecycle needs to flush pending progress.

That same coordinator also records daily listening time and completion events, which keeps statistics tied to real playback instead of screen visits.

Bookmarks and chapters use the same clock

Bookmarks are position-based. Chapters are stored with start and end times. The player can jump to a chapter, save a bookmark at the current timestamp, or resume a bookmark later because those features all speak the same unit: integer seconds.

What’s next

This local schema is also the base for future ebook progress, ebook-to-audiobook sync, cloud backup, and cross-device sync. For where the whole build stands today — and the launch timing — see the September 2026 build update. The hard part is not putting data in the cloud. The hard part is making sure the local model is worth syncing.