Tamed is structured around a strict Clean Architecture approach that keeps the audio engine completely independent of the UI layer. Every major concern — rendering, business logic, persistence, and media playback — lives in its own layer, and data flows in one direction through Kotlin Coroutines andDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/calmerism/Tamed/llms.txt
Use this file to discover all available pages before exploring further.
StateFlow/Flow pipelines. This separation makes the codebase predictable to navigate, straightforward to test, and resilient to change in any single layer.
Architectural Pattern
Tamed combines Clean Architecture with MVVM and Unidirectional Data Flow (UDF). The UI observes state emitted by ViewModels, which coordinate with the Data and Service layers but never reference Android framework classes directly. All asynchronous work runs through Kotlin Coroutines and Flow — no callbacks, noLiveData.
UI Layer (Compose)
Jetpack Compose screens and components using Material 3. State is collected from ViewModels as
StateFlow. Source lives under ui/screens, ui/component, and ui/player. The UI layer never touches the database or network directly.Domain Layer
Business logic, use cases, and high-level audio processing interfaces. ViewModels under
viewmodels/ live here and act as the boundary between presentation and data concerns. No Android framework classes are imported at this layer.Data Layer
The single source of truth. Coordinates between the Room database (
MusicDatabase, DatabaseDao), DataStore preferences, Retrofit/OkHttp network clients (NetworkModule), the YouTube Music API, JioSaavn, and local file scanning.Service Layer (Media3)
A specialized background layer built on
MediaLibraryService. MusicService owns the ExoPlayer instance and MediaSession. All UI-to-player communication goes through PlayerConnection, never through direct service binding.Key Packages
Every package undercom.tamed.music has a single, well-defined responsibility.
com.tamed.music.playback — Audio Engine
com.tamed.music.playback — Audio Engine
The core playback package contains
MusicService (a MediaLibraryService subclass), PlayerConnection (the UI-facing bridge to the player), queue management classes, crossfade logic, silence detection, and the download pipeline. PlayerConnection exposes player state as MutableStateFlow properties — playbackState, isPlaying, mediaMetadata, queueWindows, shuffleModeEnabled, repeatMode, and more — that the UI collects reactively.com.tamed.music.lyrics — Lyrics Engine
com.tamed.music.lyrics — Lyrics Engine
Houses
LyricsHelper, all provider integrations (KuGou, LrcLib, BetterLyrics, and others), and LyricsEntry model types. Supports both line-by-line LRC and word-by-word TTML formats. LyricsHelperEntryPoint is used to inject the helper into non-Hilt contexts such as the service layer.com.tamed.music.db — Persistence
com.tamed.music.db — Persistence
Contains
InternalDatabase (the Room @Database class), MusicDatabase (the public wrapper that delegates to DatabaseDao), and all entity and DAO definitions. The schema is version-tracked with exported JSON files under app/schemas/ and supports automatic migrations from version 2 onward via Room’s AutoMigration mechanism.com.tamed.music.di — Dependency Injection
com.tamed.music.di — Dependency Injection
All Hilt modules live here.
AppModule provides the MusicDatabase singleton and both Media3 Cache instances (@PlayerCache and @DownloadCache). NetworkModule provides NetworkConnectivityObserver. LyricsHelperEntryPoint is an entry point interface for injecting into the service context.com.tamed.music.ui — Presentation
com.tamed.music.ui — Presentation
Jetpack Compose screens, shared components, the player UI, and Material 3 theming. The
theme/ subdirectory manages ThemeSeedPalette and dynamic color extraction from album art via the Android Palette library.com.tamed.music.together — Collaborative Listening
com.tamed.music.together — Collaborative Listening
TogetherServer (a Ktor CIO WebSocket server) and TogetherClient manage the Music Together feature, which lets multiple users listen in sync. Sessions communicate over WebSocket using typed sealed interface event types.com.tamed.music.ai — AI Features
com.tamed.music.ai — AI Features
AI provider integrations and lyrics translation capabilities. Connects to configurable AI backends and feeds results back into the lyrics pipeline.
com.tamed.music.applecanvas / vivimusiccanvas — Animated Visuals
com.tamed.music.applecanvas / vivimusiccanvas — Animated Visuals
Two animated visual backends that render the Apple Music-inspired ambient backdrop. Both are driven by album art color data extracted at playback time and cached by
CanvasArtworkPlaybackCache.com.tamed.music.utils — Shared Utilities
com.tamed.music.utils — Shared Utilities
com.tamed.music.constants — Configuration
com.tamed.music.constants — Configuration
All DataStore
PreferenceKey definitions and app-wide enums live here, including LanguageCodeToName (40+ locales), CountryCodeToName, sort type enums, and every typed preference key used across the app.Dependency Injection
Tamed uses Hilt (Dagger) for compile-time dependency injection. TheApp class carries @HiltAndroidApp, which generates the top-level Hilt component and kicks off the injection graph at process start.
AppModule
Installed in
SingletonComponent. Provides the MusicDatabase singleton via InternalDatabase.newInstance(context), the Media3 DatabaseProvider, the @PlayerCache (LRU-evicted, size from user preferences), and the @DownloadCache (no eviction).NetworkModule
Installed in
SingletonComponent. Provides a NetworkConnectivityObserver singleton that exposes network state as a Kotlin Flow, consumed by the playback pipeline to pause and resume gracefully on connectivity changes.LyricsHelperEntryPoint is a Hilt EntryPoint interface, not a @Module. It allows MusicService and other non-Hilt-injected contexts to pull LyricsHelper out of the Hilt component manually via EntryPointAccessors.Room Database
The Room database is namedsong.db and is currently at schema version 30. The InternalDatabase class is annotated with @Database and delegates all DAO access through the MusicDatabase wrapper, which adds suspend transaction helpers and WAL-mode optimizations.
Entity Tables
| Table | Entity Class | Purpose |
|---|---|---|
song | SongEntity | Core song metadata, play counts, liked state, download state |
album | AlbumEntity | Album metadata, artwork, year, song count |
artist | ArtistEntity | Artist name, thumbnail, channel ID |
playlist | PlaylistEntity | User and YouTube Music playlists |
song_artist_map | SongArtistMap | Many-to-many song ↔ artist relationships |
song_album_map | SongAlbumMap | Many-to-many song ↔ album relationships |
album_artist_map | AlbumArtistMap | Many-to-many album ↔ artist relationships |
playlist_song_map | PlaylistSongMap | Ordered song ↔ playlist membership |
lyrics | LyricsEntity | Cached lyrics with source tracking |
event | Event | Play event log for statistics and recommendations |
playCount | PlayCountEntity | Aggregated per-song play counts by year/month |
format | FormatEntity | Audio format metadata (bitrate, codec, etc.) |
tag | TagEntity | User-defined playlist tags |
playlist_tag_map | PlaylistTagMap | Tag ↔ playlist associations |
search_history | SearchHistory | Recent search queries |
set_video_id | SetVideoIdEntity | YouTube Music set video ID mappings |
related_song_map | RelatedSongMap | Related song graph for Quick Picks recommendations |
Database Views
Three database views are generated for efficient query access:SortedSongArtistMap, SortedSongAlbumMap, and PlaylistSongMapPreview (which limits results to the first three positions for playlist thumbnail previews).
Schema Migrations
Room’sAutoMigration handles incremental upgrades from version 2 to version 22. A custom UniversalMigration class handles all remaining version jumps by reconciling the live schema against an in-memory reference database, safely adding missing columns, recreating mismatched tables with data preservation, and updating the Room identity hash. The legacy MIGRATION_1_2 migration performs a full schema rewrite to bootstrap the modern entity model.
Network Stack
OkHttp + Retrofit
Used for all YouTube Music API requests (via the
:innertube module), JioSaavn (:jiosaavn), AI provider calls, and Last.fm scrobbling (:lastfm). OkHttp is also the data source driver for Media3 stream fetching via OkHttpDataSource.Ktor CIO
Powers the Music Together WebSocket server (
TogetherServer) using ktor-server-cio and ktor-server-websockets. The client side uses ktor-client-okhttp with content negotiation and WebSocket support.Coil 3
Handles all image loading with disk caching backed by a configurable size limit (default 512 MB). A custom
CoilBitmapLoader integrates with the Media3 MediaSession to supply artwork for the notification and lock screen.Multi-module Clients
Lyrics providers are isolated into their own Gradle modules:
:kugou, :lrclib, :betterlyrics. The Shazam recognition feature lives in :shazamkit. Discord rich presence in :kizzy. All are included as implementation(project(...)) dependencies in app/build.gradle.kts.