Kanade is a high-performance, modern music player for Android designed to provide a seamless experience for both local music and external media providers. Its core architecture focuses on extensibility and immersive UI.
- Unified Playback: Combines local
MediaStoretracks with external sources via a custom scripting engine. - Advanced Lyrics: Supports standard LRC, Enhanced LRC (word-by-word), and Apple-style TTML lyrics with smooth transitions and sharing capabilities.
- Kanade Scripting System (KSS): A QuickJS-powered engine that allows developers to write JavaScript scripts to fetch music, search, and resolve streaming URLs from third-party services.
- Modern UI/UX: Built entirely with Jetpack Compose, featuring dynamic gradients (Palette API), immersive player transitions, and a "last-minute" resolution strategy to optimize network usage.
- Languages: Kotlin (JVM 17), JavaScript (ES2015+ via QuickJS).
- Core Frameworks: Jetpack Compose (Material 3), Android Media3 (ExoPlayer + MediaSession).
- Target Platform: Android 8.0 (API 26) to Android 16 (API 36).
- Minimum Runtime: Android SDK 26, Gradle 8.13+.
- Build System: Kotlin DSL (
.gradle.kts) with version catalogs (libs.versions.toml).
app/src/main/java/org/parallel_sekai/kanade/
├── data/
│ ├── model/ # Immutable Data Classes (Music, Lyric, Artist, Album)
│ ├── parser/ # Parsers for LRC (Standard/Enhanced) and TTML formats
│ ├── repository/ # Domain Logic & State Management
│ │ ├── PlaybackRepository # Media3 controller bridge; persists playback state
│ │ └── SettingsRepository # User preferences via DataStore
│ ├── script/ # Kanade Scripting System (KSS)
│ │ ├── ScriptEngine # QuickJS runtime with Promise/Async/Bridge support
│ │ ├── HostBridge # Kotlin-to-JS bridges (HTTP, Crypto, Logging)
│ │ ├── ScriptManager # Script lifecycle, manifest parsing, and local storage
│ │ └── ScriptMusicSource # Adapter converting JS script output to MusicModels
│ ├── source/ # Data Sources
│ │ ├── IMusicSource # Interface for local/script-based sources
│ │ ├── SourceManager # Aggregates local and active script sources
│ │ └── local/ # Local MediaStore integration
│ └── utils/ # Utilities (LyricSplitter, CacheManager, URL handling)
├── service/
│ └── KanadePlaybackService # Media3 MediaSessionService; handles background playback
├── ui/
│ ├── screens/ # MVI UI Components
│ │ ├── player/ # Player View, Lyrics, and UI state (Contract/ViewModel)
│ │ ├── library/ # Local/Home browsing
│ │ ├── search/ # Multi-source debounced search
│ │ └── settings/ # Config for KSS, UI, and Lyric broadcasting
│ └── theme/ # Theme definition, Dimens, and Material 3 palettes
└── MainActivity.kt # Entry point, navigation, and permission handling
| Library | Role |
|---|---|
androidx.media3 |
Comprehensive audio engine, session management, and UI controls. |
app.cash.quickjs |
Lightweight JS engine for executing KSS provider scripts. |
io.coil-kt:coil-compose |
Efficient image loading for album art. |
androidx.palette |
Color extraction from album art to drive dynamic UI gradients. |
androidx.datastore |
Type-safe preference storage for app settings and history. |
kotlinx.serialization |
JSON parsing for script communication and state persistence. |
Lyric-Getter-Api |
Integration for system-wide lyric broadcasting. |
- MVI Pattern: Every screen follows a strict
State,Intent,Effectcontract.PlayerState: Immutable snapshot of the UI.PlayerIntent: User actions (e.g.,PlayPause,SeekTo).PlayerEffect: One-time events (e.g.,ShowError).
- Clean Architecture: Separation between Data (Sources), Domain (Repositories), and Presentation (ViewModels).
- Naming:
- Classes:
PascalCase. - Functions/Variables:
camelCase. - Constants:
SCREAMING_SNAKE_CASE.
- Classes:
- Asynchronous Flow: Heavy use of Kotlin
CoroutinesandFlow. UI observesStateFlow.
PlaybackRepository: The "Source of Truth" for playback. Bridges the UI withMediaController. HandlessavePlaybackStateandrestorePlaybackState.ScriptEngine: Manages a dedicated single-threaded JS runtime. SupportscallAsyncfor Promise-based JS calls and provideshttp/cryptobridges to scripts.LyricParserFactory: Centralized factory that detects format (LRC/TTML) and returns the correctLyricParser.KanadePlaybackService: ExtendsMediaSessionService. Implements "last-minute" resolution for script-based URIs using a customResolvingDataSource.MusicUtils: Shared logic for parsing artist strings with custom delimiters and formatting metadata.
- The kanade:// Scheme: External songs use the
kanade://resolve?source_id=...&original_id=...URI. TheKanadePlaybackServiceintercepts this and triggers a scriptresolve()call only when the song is about to play. - Lyric Synchronization:
PlayerViewModelobserves theprogressFlowfrom the repository. It throttles updates to 200ms to calculate the current lyric line and broadcasts it to external lyric APIs if enabled. - Dynamic Theming: When a song changes,
PlayerViewModelusesPaletteto extract colors from the cover art. These colors are stored inPlayerState.gradientColorsand applied to the player background with smooth transitions. - Script Scoping: Each script is wrapped in a
ScriptMusicSource.SourceManagerhandles the activation/deactivation of scripts based on user settings.
- State Management: Always use
MutableStateFlowin ViewModels and expose asStateFlow. UI MUST be stateless and only react to the state. - Media Actions: Never interact with
MediaControllerorExoPlayerdirectly in UI code. All actions MUST go throughPlaybackRepository. - KSS Threading: All host bridge calls (HTTP, Crypto) must be performed within the
ScriptEngine's dedicatedexecutorthread to satisfy QuickJS thread safety. - Theming: Use
MaterialTheme.colorSchemeand theDimensobject for spacing. Do not hardcode pixel values. - Error Handling: Use
PlayerEffect.ShowErrororShowMessagefor UI feedback. In repositories, catch exceptions and return safe defaults (e.g.,emptyList()) to avoid app crashes. - Testing: Place unit tests in
src/testand UI tests insrc/androidTest. - Code Quality: Run
./gradlew spotlessApplybefore committing to ensure ktlint compliance.