Files
Ensemble/docs/AUDIOBOOKS_IMPLEMENTATION_PLAN.md
Your NameandClaude Opus 4.5 376f076bc1 feat(database): Add local database with Drift for user profiles and caching
Phase 1 of audiobooks integration - establishes local storage foundation:

- Add Drift (SQLite) database with tables for profiles, recently played,
  library cache, and sync metadata
- Add DatabaseService singleton for database access
- Add ProfileService for automatic user profile management
- Profiles auto-created from MA authentication (username) or manual entry
- Migration support for existing ownerName to profile
- Hook profile creation into MA login flow and login screen
- Update CI workflow to generate Drift code with build_runner
- Add comprehensive implementation plan document

This enables:
- Per-user recently played isolation (no cross-contamination)
- Fast app startup with cached data
- Foundation for audiobook progress tracking

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-17 09:46:30 +00:00

8.0 KiB

Audiobooks Integration - Implementation Plan

Overview

Add audiobook support to Ensemble with automatic user profile separation based on Music Assistant authentication. Includes local database for performance caching and per-user data isolation.


Phase 1: Database Foundation

Goal: Add persistent local storage layer using Drift (SQLite)

Tasks

  • Add Drift package dependencies (drift, drift_flutter, path_provider)
  • Create database class with schema version management
  • Create profiles table:
    class Profiles extends Table {
      TextColumn get username => text()();  // MA username (primary key)
      TextColumn get displayName => text().nullable()();
      TextColumn get source => text()();  // 'ma_auth' or 'manual'
      DateTimeColumn get createdAt => dateTime()();
      BoolColumn get isActive => boolean().withDefault(const Constant(false))();
    }
    
  • Create recently_played table:
    class RecentlyPlayed extends Table {
      IntColumn get id => integer().autoIncrement()();
      TextColumn get profileUsername => text().references(Profiles, #username)();
      TextColumn get mediaId => text()();
      TextColumn get mediaType => text()();  // 'track', 'album', 'artist', 'audiobook'
      TextColumn get name => text()();
      TextColumn get artist => text().nullable()();
      TextColumn get imageUrl => text().nullable()();
      DateTimeColumn get playedAt => dateTime()();
    }
    
  • Create library_cache table for albums/artists/audiobooks
  • Generate Drift code (dart run build_runner build)
  • Create DatabaseService singleton with initialization

Acceptance Criteria

  • Database initializes on app startup
  • Schema migrations work correctly
  • CRUD operations functional for all tables

Phase 2: Profile System

Goal: Auto-create and manage user profiles based on MA authentication

Tasks

  • Create ProfileService class
  • On MA login success: check if profile exists for username
    • If not, create new profile with displayName from MA user info
    • Set as active profile
  • On manual name entry (no-auth): create/select profile by entered name
  • Add getCurrentProfile() method
  • Migrate existing ownerName to profile on first launch post-update
  • Add profile indicator to settings screen (show current profile name)

Acceptance Criteria

  • Profile auto-created on first login
  • Correct profile selected on subsequent logins
  • Existing users migrated seamlessly

Phase 3: Recently Played Migration

Goal: Move recently played from memory cache to database, scoped by profile

Tasks

  • Update CacheService to use database for recently played
  • Scope all recently played queries by profileUsername
  • Update home screen to load recently played from database
  • Add addRecentlyPlayed() method that records profile + media info
  • Limit recently played to last 50 items per profile
  • Remove old memory-based recently played logic

Acceptance Criteria

  • Recently played persists across app restarts
  • Each profile sees only their own recently played
  • No cross-contamination between profiles

Phase 4: Library Caching

Goal: Cache library data locally for instant UI and reduced API calls

Tasks

  • Create SyncService for background library synchronization
  • On app launch: load from cache immediately, sync in background
  • Store albums, artists in library_cache table
  • Add lastSynced timestamp to track freshness
  • Implement delta sync (only fetch changed items if MA supports it)
  • Add pull-to-refresh that forces full sync
  • Show subtle sync indicator when background sync in progress

Acceptance Criteria

  • Home screen loads instantly from cache
  • Library tabs load instantly
  • Background sync updates data without blocking UI
  • Stale data refreshed within reasonable timeframe

Phase 5: Scrollable Library Tabs

Goal: Make library tab row scrollable to accommodate more tabs

Tasks

  • Convert TabBar to scrollable variant (isScrollable: true)
  • Adjust tab styling for scrollable layout
  • Ensure indicator line spans correctly
  • Test with varying number of tabs
  • Add "Books" tab placeholder (disabled until Phase 6)

Acceptance Criteria

  • Tabs scroll horizontally when they exceed screen width
  • Current tab indicator works correctly
  • Smooth scrolling experience

Phase 6: Audiobooks Tab - Browse by Author

Goal: Add Books tab with author → books hierarchy

Tasks

  • Add MA API method to fetch audiobooks: getAudiobooks()
  • Add MA API method to fetch audiobook authors
  • Create AudiobookAuthor model
  • Create Audiobook model with fields:
    • id, name, authors, narrators, description
    • duration, imageUrl, chapters, resumePosition, fullyPlayed
  • Create BooksScreen with author list view
  • Tapping author expands to show their books
  • Add to library tab bar
  • Cache audiobooks in local database

Acceptance Criteria

  • Books tab shows list of authors
  • Authors expandable to show their audiobooks
  • Audiobook covers and titles display correctly
  • Data cached locally for fast subsequent loads

Phase 7: Audiobook Detail Screen

Goal: Full audiobook detail view with metadata and chapters

Tasks

  • Create AudiobookDetailScreen
  • Display: cover art, title, author(s), narrator(s)
  • Display: description (expandable if long)
  • Display: total duration, progress indicator
  • Chapter list with:
    • Chapter title
    • Duration
    • Played indicator (based on resume position)
  • Tapping chapter starts playback from that chapter
  • "Resume" button that continues from resume_position_ms
  • "Start Over" option

Acceptance Criteria

  • All metadata displays correctly
  • Chapters show with accurate durations
  • Resume position reflected in UI
  • Can start playback from any chapter

Phase 8: Audiobook Playback Integration

Goal: Proper audiobook playback with seek and resume

Tasks

  • Implement playAudiobook(bookId, startPosition?) method
  • Use queueCommandSeek() to resume at correct position
  • Update now playing UI to show:
    • Book title instead of track
    • Current chapter name
    • Chapter progress (not just total progress)
  • Add skip forward/back buttons (30 sec for audiobooks?)
  • Add chapter skip buttons (next/previous chapter)
  • Record to recently played when audiobook starts (with 'audiobook' type)

Acceptance Criteria

  • Audiobooks resume from correct position
  • Chapter information shows in now playing
  • Skip controls work correctly
  • Recently played records audiobook plays

Phase 9: Polish & Edge Cases

Goal: Handle edge cases and improve UX

Tasks

  • Handle audiobooks with no chapters gracefully
  • Handle very long audiobooks (20+ hours)
  • Add "Mark as Finished" option
  • Add "Mark as Unplayed" option
  • Ensure offline browsing works for cached audiobooks
  • Test with various audiobook lengths and chapter counts
  • Performance testing with large audiobook libraries

Acceptance Criteria

  • No crashes on edge cases
  • Smooth performance
  • All user actions have appropriate feedback

Future Enhancements (Out of Scope)

  • Sleep timer
  • Playback speed control
  • Series support (requires MA provider update)
  • Bookmarks
  • Listening statistics
  • Download for offline playback

Technical Notes

Database Choice: Drift

  • SQLite wrapper with type-safe queries
  • Good for relational data (profiles → recently_played)
  • Mature, well-documented
  • Supports migrations

MA API Endpoints Used

  • music/audiobooks - List audiobooks
  • music/audiobook/{id} - Get audiobook details
  • player_queues/play_media - Start playback
  • player_queues/seek - Seek to position
  • music/in_progress_items - Get items with resume positions

Profile Data Flow

MA Login → Get user_info → Check/Create Profile → Set Active → Load Profile Data

Recently Played Flow

Play Media → Check Active Profile → Insert RecentlyPlayed Row → Update UI