Skip to content

Technology Stack

Anjishnu Nandi edited this page Jun 21, 2026 · 5 revisions

This document details the technical architecture and libraries used in Rhythm Music Player.

πŸ—οΈ Core Technologies

UI & Design

Technology Purpose
Jetpack Compose Modern declarative UI toolkit for Android
Material 3 Material Design components and theming system
Material Symbols Variable Font Custom static font asset replacing deprecated material-icons-extended
AndroidX Palette Dynamic color extraction from images

Audio & Media

Technology Purpose
Media3 ExoPlayer Professional-grade media playback engine
FFmpeg Decoder Extended codec support (EAC3-JOC, AC-3, WMA)
MediaStore API Android media content provider
AudioFocus Audio focus management for calls/notifications

Widgets

Technology Purpose
Glance Modern reactive widgets with Material 3 design
RemoteViews Legacy widget support
WorkManager Background widget updates

Programming Language

Technology Purpose
Kotlin 100% Kotlin codebase
Kotlin Coroutines Asynchronous programming
Kotlin Flow Reactive streams and state management

🎨 Architecture

Design Pattern

MVVM (Model-View-ViewModel) + Clean Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    UI Layer                      β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚  β”‚   Composables (Screens & Components)     β”‚   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β”‚                      ↕                           β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚  β”‚         ViewModels (State)               β”‚   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                      ↕
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                 Domain Layer                     β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚  β”‚      Use Cases (Business Logic)          β”‚   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚  β”‚     Repository Interfaces                β”‚   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚  β”‚      Models (Data Entities)              β”‚   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                      ↕
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                  Data Layer                      β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚  β”‚    Repository Implementations            β”‚   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚  β”‚  Data Sources (Local & Remote)           β”‚   β”‚
β”‚  β”‚  β€’ MediaStore                            β”‚   β”‚
β”‚  β”‚  β€’ LRCLib API                            β”‚   β”‚
β”‚  β”‚  β€’ Deezer API                            β”‚   β”‚
β”‚  β”‚  β€’ Local Storage                         β”‚   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Project Structure

app/src/main/java/chromahub/rhythm/app/
β”œβ”€β”€ activities/                  # Main Android activities (e.g. MainActivity.kt)
β”œβ”€β”€ core/                        # Core Shared Business/Domain Logic
β”‚   └── domain/
β”‚       β”œβ”€β”€ model/               # Core domain entities (Song, Album, Artist, Playlist)
β”‚       β”œβ”€β”€ repository/          # Core repository interfaces
β”‚       └── usecase/             # Core business use cases
β”œβ”€β”€ features/                    # Feature Modules
β”‚   β”œβ”€β”€ local/                   # Local media playback feature (Clean Architecture)
β”‚   β”‚   β”œβ”€β”€ data/                # Local repositories, room database, MediaStore integration
β”‚   β”‚   β”œβ”€β”€ di/                  # Local dependency injection configs
β”‚   β”‚   β”œβ”€β”€ domain/              # Local use cases and business logic
β”‚   β”‚   └── presentation/        # Local screens, viewmodels, themes, and views
β”‚   └── streaming/               # Streaming server client feature (Clean Architecture)
β”‚       β”œβ”€β”€ data/                # Remote repositories and networking clients
β”‚       β”œβ”€β”€ di/                  # Streaming dependency injection configs
β”‚       β”œβ”€β”€ domain/              # Streaming use cases
β”‚       └── presentation/        # Streaming screens and viewmodels
β”œβ”€β”€ infrastructure/              # Base Infrastructure layer
β”‚   β”œβ”€β”€ audio/                   # ExoPlayer setup, controller, and FFmpeg configuration
β”‚   β”œβ”€β”€ network/                 # Retrofit, OkHttp, and REST API definitions
β”‚   β”œβ”€β”€ service/                 # MusicService.kt & background media session handlers
β”‚   β”œβ”€β”€ widget/                  # Glance app widget definitions
β”‚   └── worker/                  # WorkManager background sync/scan workers
β”œβ”€β”€ shared/                      # Shared Cross-Cutting Presentation/Domain/Data components
β”‚   β”œβ”€β”€ data/
β”‚   β”œβ”€β”€ domain/
β”‚   └── presentation/            # Shared UI elements, color themes, styling, and custom icons
└── util/ & utils/               # General utility files and extension functions

πŸ“¦ Libraries & Dependencies

AndroidX & Jetpack

// Core
androidx.core:core-ktx
androidx.core:core-splashscreen
androidx.lifecycle:lifecycle-runtime-ktx
androidx.lifecycle:lifecycle-viewmodel-compose
androidx.fragment:fragment-ktx

// Compose
androidx.compose.ui:ui
androidx.compose.material3:material3
androidx.compose.ui:ui-tooling

// Navigation
androidx.navigation:navigation-compose

// Media
androidx.media3:media3-exoplayer
androidx.media3:media3-session
androidx.media3:media3-ui

// Widgets
androidx.glance:glance-appwidget
androidx.work:work-runtime-ktx

// Room Database
androidx.room:room-runtime
androidx.room:room-ktx

// Other
androidx.palette:palette-ktx

Networking

// HTTP Client
com.squareup.retrofit2:retrofit
com.squareup.retrofit2:converter-gson
com.squareup.okhttp3:okhttp
com.squareup.okhttp3:logging-interceptor

// JSON
com.google.code.gson:gson

Image Loading

// Coil for Compose
io.coil-kt:coil-compose

Utilities

// Permissions
com.google.accompanist:accompanist-permissions

// Coroutines
org.jetbrains.kotlinx:kotlinx-coroutines-android

πŸ”„ State Management

StateFlow & Compose State

Rhythm uses Kotlin Flow and Compose state for reactive UI updates:

// ViewModel observes ExoPlayer state via MediaController
class MusicViewModel(application: Application) : AndroidViewModel(application) {
    private var mediaController: MediaController? = null

    val playbackState: StateFlow<@Player.State Int> = 
        MutableStateFlow(Player.STATE_IDLE)

    fun connect(controller: MediaController) {
        mediaController = controller
        controller.addListener(object : Player.Listener {
            override fun onPlaybackStateChanged(state: Int) {
                (playbackState as MutableStateFlow).value = state
            }
        })
    }
}

Repository Pattern

Data access abstracted through repositories:

interface MusicRepository {
    fun getSongs(): Flow<List<PlayableItem>>
    fun getAlbums(): Flow<List<AlbumItem>>
    fun getArtists(): Flow<List<ArtistItem>>
    fun getPlaylists(): Flow<List<PlaylistItem>>
    suspend fun getSongById(id: String): PlayableItem?
    suspend fun getAlbumById(id: String): AlbumItem?
    suspend fun searchSongs(query: String): List<PlayableItem>
}

🎡 Audio Playback Architecture

ExoPlayer Integration

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚         MusicService                β”‚
β”‚   (Foreground Service)              β”‚
β”‚                                     β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚      ExoPlayer               β”‚  β”‚
β”‚  β”‚  β€’ Media3 ExoPlayer 1.10.1   β”‚  β”‚
β”‚  β”‚  β€’ FFmpeg decoder extension  β”‚  β”‚
β”‚  β”‚  β€’ Gapless playback          β”‚  β”‚
β”‚  β”‚  β€’ Audio focus handling      β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚                                     β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚   MediaSession               β”‚  β”‚
β”‚  β”‚  β€’ Playback state            β”‚  β”‚
β”‚  β”‚  β€’ Queue management          β”‚  β”‚
β”‚  β”‚  β€’ Media buttons             β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚                                     β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚   MediaNotification          β”‚  β”‚
β”‚  β”‚  β€’ Playback controls         β”‚  β”‚
β”‚  β”‚  β€’ Album art                 β”‚  β”‚
β”‚  β”‚  β€’ Metadata display          β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
            ↕
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚     UI (Player Composables)         β”‚
β”‚  β€’ Observe playback state           β”‚
β”‚  β€’ Send playback commands           β”‚
β”‚  β€’ Display metadata                 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸ“± Widget Architecture

Glance Widgets (Modern)

class RhythmMusicWidget : GlanceAppWidget() {
    override suspend fun provideGlance(context: Context, id: GlanceId) {
        provideContent {
            RhythmMusicWidgetContent()
        }
    }
}

@Composable
fun RhythmMusicWidgetContent() {
    // Observe playback data via GlanceState
    // Material 3 widget UI
    MaterialTheme {
        // Widget content with play/pause, skip, track info, album art
    }
}

Background Updates

class RhythmWidgetWorker(
    context: Context,
    params: WorkerParameters
) : CoroutineWorker(context, params) {
    override suspend fun doWork(): Result {
        // Update widget data from current playback state
        GlanceAppWidgetManager(context)
            .getGlanceIds(RhythmMusicWidget::class.java)
            .forEach { glanceId ->
                RhythmMusicWidget().update(context, glanceId)
            }
        return Result.success()
    }
}

πŸ”§ Build System

Gradle Kotlin DSL

// build.gradle.kts
plugins {
    alias(libs.plugins.android.application)
    alias(libs.plugins.kotlin.compose)
    alias(libs.plugins.ksp)
    id("kotlin-parcelize")
}

android {
    namespace = "chromahub.rhythm.app"
    compileSdk = 37
    
    defaultConfig {
        applicationId = "chromahub.rhythm.app"
        minSdk = 26
        targetSdk = 37
        versionCode = 514081066
        versionName = "5.1.408.1066"
    }
    
    buildFeatures {
        compose = true
        buildConfig = true
    }
    
    kotlin {
        compilerOptions {
            jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17)
            freeCompilerArgs.addAll(
                "-opt-in=androidx.compose.material3.ExperimentalMaterial3ExpressiveApi",
                "-opt-in=androidx.compose.material3.ExperimentalMaterial3Api"
            )
        }
    }
}

Version Catalog

# gradle/libs.versions.toml
[versions]
agp = "9.2.1"
kotlin = "2.4.0"
ksp = "2.3.6"
composeBom = "2026.06.00"
material3 = "1.5.0-alpha22"
media3 = "1.10.1"

[libraries]
androidx-ui = { group = "androidx.compose.ui", name = "ui" }
androidx-material3-android = { group = "androidx.compose.material3", name = "material3-android", version.ref = "material3" }
androidx-media3-exoplayer = { group = "androidx.media3", name = "media3-exoplayer", version.ref = "media3" }

πŸ§ͺ Testing

Unit Tests

  • ViewModel logic testing (JUnit 4)
  • Repository testing
  • Use case testing

UI Tests

  • Compose UI testing (Compose Test)
  • Navigation testing
  • Integration testing
  • Macrobenchmark for baseline profiles

Build & Run Tests

# Unit tests
./gradlew test

# Instrumented tests
./gradlew connectedAndroidTest

πŸ” Security & Privacy

  • No Analytics: Zero tracking code
  • Local Storage: All data stored on device
  • Minimal Permissions: Only essential permissions
  • FOSS Compliance: Fully open source
  • Reproducible Builds: Consistent APK generation

πŸ“Š Performance Optimizations

  • Lazy Loading: Load music library on demand
  • Image Caching: Coil caches album art efficiently
  • Background Processing: WorkManager for non-urgent tasks
  • Compose Optimization: Remember, derivedStateOf, keys
  • ExoPlayer Buffering: Optimized buffer sizes

πŸ”„ CI/CD

  • GitHub Actions for automated builds (android.yml, beta.yml, release.yml)
  • Automated testing on push
  • Release automation with signing
  • Code quality checks and linting
  • Reproducible build support

Want to contribute? Check the Contributing Guide! Questions? Ask in Telegram or Discord.

Clone this wiki locally