ensureReady() had a race condition where concurrent callers from init
block and ViewModels would all run the loading logic simultaneously.
Added Mutex to ensure only the first caller loads; others await.
Also added a cooldown to refreshHomeData() since LifecycleResumeEffect
fires immediately on first composition, triggering a redundant reload
right after the initial load completes.
Switch seek parameters from CLOSEST_SYNC to PREVIOUS_SYNC so seeks always
land at or before the requested position, preventing subtitle cues from
being skipped. On backward seek discontinuity, briefly disable and re-enable
the text track to flush TextRenderer state so the current cue is re-delivered.
Removing the item key disables Compose's scroll anchoring, which was
shifting the scroll offset rightward whenever a refresh prepended new
items, making the list appear to start in the middle.
Implement media downloading with foreground notification showing progress
percentage and speed. Uses Media3's DownloadManager + SimpleCache with
OkHttp datasource for authenticated Jellyfin downloads. Downloaded movies
are saved to the offline database and the player reads from cache
automatically via CacheDataSource.
Replaced competing pointerInput modifiers with a single unified gesture handler that determines drag direction early. This eliminates conflicts between horizontal seeking and vertical brightness/volume gestures, making swipe interactions more reliable and predictable.
Subtitles were not showing the active cue after rewinding/seeking because
they were delivered externally and Media3 only picks up forward-starting
cues. Changed device profile to prefer EMBED delivery so subtitles stay
in the container where Media3's extraction-time parser handles them
correctly. Also added support for attaching external subtitle tracks to
MediaItem when embedding isn't possible.
Implements a toggle switch in the HomeScreen top bar (next to Menu) that allows users to switch between online mode (fetching from Jellyfin server) and offline mode (using local database only). The preference persists across app restarts via Proto DataStore.
Key changes:
- Added ActiveMediaRepository that delegates to online/offline repositories based on user preference
- Extended MediaRepository interface with continueWatching, nextUp, and latestLibraryContent
- Added isOfflineMode state to UserSession with reactive Flow
- Added Cloud/CloudOff icon toggle button to HomeTopBar
- Updated ViewModels to use MediaRepository interface for better abstraction
Add support for audio codecs like DTS-HD that aren't natively supported
by Android's MediaCodec by integrating Jellyfin's FFmpeg decoder extension.
Changes:
- Add media3-ffmpeg-decoder dependency for software audio decoding
- Create AndroidDeviceProfile to detect and report device codec capabilities
- Configure ExoPlayer with extension renderer mode and decoder fallback
- Update playback URL selection to use transcoding when direct play unsupported
- Add CodecDebugHelper for debugging available device codecs
This fixes playback failures when encountering premium audio formats
by falling back to FFmpeg software decoding when hardware decoding fails.
Add persistent storage for audio and subtitle track preferences with automatic selection on playback. Track preferences are stored by matching semantic properties (language, channel count, forced flag) rather than track IDs.
Key features:
- Movies remember individual audio/subtitle selections
- Series share preferences across all episodes (by series ID)
- Property-based matching with scoring algorithm
- DataStore persistence with kotlinx.serialization
- Graceful fallback to Media3 defaults when no match found
Implementation:
- Created TrackPreferences data layer with DataStore serialization
- Added TrackMatcher for property-based track matching
- Enhanced TrackOption model with forced flag for subtitles
- Integrated auto-selection into PlayerManager on tracks available
- Save preferences on manual track selection
- PlayerViewModel determines media type and constructs preference key
Separated track selection UI from player controls overlay to prevent
the gesture layer from dismissing the track selection panel when
selecting options. Created PersistentOverlayContainer that manages
its own visibility state independent of player controls.
Wrap all JellyfinApiClient suspend functions with withContext(Dispatchers.IO)
so callers (ViewModels on Main dispatcher) no longer block the UI thread.
Replace runBlocking in JellyfinAuthInterceptor with a reactive cached token
to avoid blocking OkHttp threads. Add IO dispatching to player MediaRepository
for DataStore reads.
- Inject UserSessionRepository into MediaRepository to access server URL
- Build artwork URLs using JellyfinImageHelper for both initial and next-up episodes
- Add artworkUrl parameter to MediaItem metadata via setArtworkUri()
- Fix PlayerQueuePanel thumbnail display with proper 4:3 aspect ratio
- Increase next-up queue count from 2 to 5 episodes
- Implement `loadLatestLibraryContent` in `InMemoryMediaRepository` to fetch and categorize latest media from Jellyfin libraries.
- Update `HomePageViewModel` to use `mapLatest` and `stateIn` for asynchronous, thread-safe loading of "Continue Watching" and "Latest" content.
- Refactor `HomePage` and its UI components (`HomeContent`, `HomeDrawer`, `HomeSections`) to pass data and callbacks as parameters instead of using `hiltViewModel()` internally.
- Enhance `PosterCard` and `ContinueWatchingCard` with explicit click listeners and image request optimization using `Coil`.
- Add `SeasonMedia` type to the `Media` sealed class to support more granular library item tracking.
- Standardize `UUID` usage for media selection callbacks across `LibraryViewModel` and common UI components.
- Improve UI styling by replacing shadows with subtle borders and consistent corner radii on media cards.
- Rename `getNextUpEpisode` to `getNextUpEpisodes` and update it to return a list of episodes with expanded request fields.
- Rename `getMediaPlaybackInfo` to `getMediaPlaybackUrl` and update its usages in `MediaRepository`.
- Simplify logging format by removing curly brace placeholders across multiple API call methods.
- Reorder `getContinueWatching` and `getLibraryContent` for better class organization.
- Remove unused `episode` import in `MediaRepository`.
- Integrate Room database with entities for `Movie`, `Series`, `Season`, `Episode`, and `CastMember`.
- Implement `RoomMediaLocalDataSource` to handle persistent storage and retrieval of media data.
- Refactor `InMemoryMediaRepository` to use the local data source and synchronize with the Jellyfin API.
- Update `HomePageViewModel`, `SeriesViewModel`, and `EpisodeScreenViewModel` to leverage the new repository logic.
- Replace generic `ItemDto` with specific `MovieDto`, `SeriesDto`, and `EpisodeDto` for type-safe navigation.
- Refactor UI models and components in `SeriesScreen`, `EpisodeScreen`, and `HomeSections` to use domain models directly.
- Enhance `JellyfinApiClient` requests to include necessary fields like `CHILD_COUNT` and `PARENT_ID`.
- Update Gradle dependencies to include Room and KSP.