Skip to main content

Documentation 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.

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 and 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, no LiveData.

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 under com.tamed.music has a single, well-defined responsibility.
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.
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.
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.
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.
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.
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.
AI provider integrations and lyrics translation capabilities. Connects to configurable AI backends and feeds results back into the lyrics pipeline.
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.
Utilities shared across layers: DiscordRPC for rich presence, ScrobbleManager for Last.fm scrobbling, CoilBitmapLoader for custom image loading with the Media3 session, NetworkConnectivityObserver for reactive network state, and reportException for crash reporting.
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. The App class carries @HiltAndroidApp, which generates the top-level Hilt component and kicks off the injection graph at process start.
@HiltAndroidApp
class App : Application(), SingletonImageLoader.Factory {
    private val applicationScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
    // ...
}

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 named song.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

TableEntity ClassPurpose
songSongEntityCore song metadata, play counts, liked state, download state
albumAlbumEntityAlbum metadata, artwork, year, song count
artistArtistEntityArtist name, thumbnail, channel ID
playlistPlaylistEntityUser and YouTube Music playlists
song_artist_mapSongArtistMapMany-to-many song ↔ artist relationships
song_album_mapSongAlbumMapMany-to-many song ↔ album relationships
album_artist_mapAlbumArtistMapMany-to-many album ↔ artist relationships
playlist_song_mapPlaylistSongMapOrdered song ↔ playlist membership
lyricsLyricsEntityCached lyrics with source tracking
eventEventPlay event log for statistics and recommendations
playCountPlayCountEntityAggregated per-song play counts by year/month
formatFormatEntityAudio format metadata (bitrate, codec, etc.)
tagTagEntityUser-defined playlist tags
playlist_tag_mapPlaylistTagMapTag ↔ playlist associations
search_historySearchHistoryRecent search queries
set_video_idSetVideoIdEntityYouTube Music set video ID mappings
related_song_mapRelatedSongMapRelated 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’s AutoMigration 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.
Schema files are exported to app/schemas/ and committed to version control. If you add or modify a @Database entity, you must increment CURRENT_VERSION in MusicDatabase.kt and provide a corresponding AutoMigration or manual Migration entry.

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.

Build docs developers (and LLMs) love