EARHORNMUSIC UPnP · Android TV · Phones · Android Auto · GNU/Linux
Release 0.5.0

build.gradle.kts

plugins {
    alias(libs.plugins.kotlin.multiplatform)
    alias(libs.plugins.kotlin.serialization)
    alias(libs.plugins.compose.multiplatform)
    alias(libs.plugins.compose.compiler)
    alias(libs.plugins.android.library)
}

kotlin {
    jvmToolchain(17)

    androidTarget {
        compilations.all {
            compilerOptions.configure {
                jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17)
            }
        }
    }

    jvm("desktop") {
        compilations.all {
            compilerOptions.configure {
                jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17)
            }
        }
    }

    // Hierarchical source sets:
    //   commonMain  ── platform-agnostic domain + ViewModels + Compose theme
    //     └── jvmCommonMain  ── JVM-only shared between android & desktop (jUPnP logic)
    //           ├── androidMain  ── Android holder, Android-flavour Media3 renderer
    //           └── desktopMain  ── DesktopUpnpServiceHolder, VlcjLocalRenderer
    sourceSets {
        val commonMain by getting {
            dependencies {
                implementation(compose.runtime)
                implementation(compose.foundation)
                implementation(compose.material3)
                implementation(compose.materialIconsExtended)
                implementation(compose.ui)
                @OptIn(org.jetbrains.compose.ExperimentalComposeLibrary::class)
                implementation(compose.components.resources)

                implementation(libs.kotlinx.coroutines.core)
                implementation(libs.kotlinx.serialization.json)

                // Multiplatform ViewModel + viewModelScope
                implementation(libs.androidx.lifecycle.viewmodel)

                implementation(libs.koin.core)
                implementation(libs.koin.compose)
            }
        }

        val jvmCommonMain by creating {
            dependsOn(commonMain)
            dependencies {
                // UPnP — jUPnP, matching upstream Android app.
                implementation(libs.jupnp.core)
                implementation(libs.jupnp.support)
                implementation(libs.okhttp)
                // RawCastClient parses Cast wire-protocol JSON payloads. Android
                // bundles org.json; on JVM we pull it from Maven. Tiny artifact
                // (~70KB), no transitive deps, used only inside cast/raw/.
                implementation("org.json:json:20240303")
            }
        }

        val androidMain by getting {
            dependsOn(jvmCommonMain)
            dependencies {
                implementation(libs.kotlinx.coroutines.android)
                implementation(libs.koin.android)
                implementation(libs.androidx.activity.compose)
                // Media3 / ExoPlayer stay in the :android app module, not :shared.
            }
        }

        val desktopMain by getting {
            dependsOn(jvmCommonMain)
            dependencies {
                implementation(compose.desktop.currentOs)
                implementation(libs.kotlinx.coroutines.swing)
                implementation(libs.vlcj)
                implementation(libs.chromecast.api)
                // jUPnP's default stream server is Jetty 9.x, whose deps (including the
                // javax.servlet API) are marked provided-scope. We supply them here.
                implementation(libs.javax.servlet.api)
                implementation(libs.jetty.server)
                implementation(libs.jetty.servlet)
                implementation(libs.jetty.client)
                // Both jUPnP and Jetty log through SLF4J. jUPnP 3.0.2 brings
                // slf4j-api 2.x in transitively, so the binding has to match that
                // line — 1.7.x bindings are ignored at runtime. Without a matching
                // binding loaded, errors during stream-server startup are silently
                // swallowed (e.g. when Jetty fails to bind we just see SSDP packets
                // advertising a port nothing's listening on). slf4j-simple writes
                // everything to stderr.
                implementation(libs.slf4j.simple)
            }
        }

        val commonTest by getting {
            dependencies {
                implementation(kotlin("test"))
                implementation(libs.kotest.assertions)
            }
        }
    }
}

android {
    namespace = "dev.stave.shared"
    compileSdk = libs.versions.android.compileSdk.get().toInt()
    defaultConfig {
        minSdk = libs.versions.android.minSdk.get().toInt()
    }
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_17
        targetCompatibility = JavaVersion.VERSION_17
    }
}