refactor: extract audio player logic into a dedicated multiplatform module

This commit is contained in:
2026-06-16 17:26:48 +02:00
parent 45a8f7ac7f
commit 1de2d43b3b
22 changed files with 96 additions and 33 deletions
+55
View File
@@ -0,0 +1,55 @@
import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
alias(libs.plugins.kotlinMultiplatform)
alias(libs.plugins.androidMultiplatformLibrary)
}
kotlin {
iosArm64()
iosSimulatorArm64()
jvm()
js {
browser()
}
@OptIn(ExperimentalWasmDsl::class)
wasmJs {
browser()
}
androidLibrary {
namespace = "fr.ajaury.gwenedeg.core.audioplayer"
compileSdk =
libs.versions.androidCompileSdk
.get()
.toInt()
minSdk =
libs.versions.androidMinSdk
.get()
.toInt()
compilerOptions {
jvmTarget = JvmTarget.JVM_11
}
}
sourceSets {
androidMain.dependencies {
// Player audio
implementation(libs.media3.exoplayer)
implementation(libs.media3.common)
}
commonMain.dependencies {
// DI
implementation(project.dependencies.platform(libs.koin.bom))
implementation(libs.koin.core)
}
commonTest.dependencies {
implementation(libs.kotlin.test)
}
}
}
@@ -0,0 +1,33 @@
package fr.ajaury.gwenedeg.player
import android.content.Context
import androidx.media3.common.MediaItem
import androidx.media3.exoplayer.ExoPlayer
class AndroidAudioPlayer(
context: Context,
) : AudioPlayer {
private val player = ExoPlayer.Builder(context).build()
override fun load(uri: String) {
val mediaItem = MediaItem.fromUri(uri)
player.setMediaItem(mediaItem)
player.prepare()
}
override fun play() {
player.play()
}
override fun pause() {
player.pause()
}
override fun stop() {
player.stop()
}
override fun release() {
player.release()
}
}
@@ -0,0 +1,9 @@
package fr.ajaury.gwenedeg.player
class AndroidAudioSessionManager : AudioSessionManager {
override fun activate() {
}
override fun deactivate() {
}
}
@@ -0,0 +1,13 @@
package fr.ajaury.gwenedeg.player.di
import fr.ajaury.gwenedeg.player.AndroidAudioPlayer
import fr.ajaury.gwenedeg.player.AndroidAudioSessionManager
import fr.ajaury.gwenedeg.player.AudioPlayer
import fr.ajaury.gwenedeg.player.AudioSessionManager
import org.koin.core.module.Module
import org.koin.dsl.module
actual val audioPlayerModule: Module = module {
single<AudioPlayer> { AndroidAudioPlayer(context = get()) }
single<AudioSessionManager> { AndroidAudioSessionManager() }
}
@@ -0,0 +1,13 @@
package fr.ajaury.gwenedeg.player
interface AudioPlayer {
fun play()
fun pause()
fun stop()
fun release()
fun load(uri: String)
}
@@ -0,0 +1,10 @@
package fr.ajaury.gwenedeg.player
/**
* An Audio Session will allow to request Audio focus and declare our audio source to the system
*/
interface AudioSessionManager {
fun activate()
fun deactivate()
}
@@ -0,0 +1,5 @@
package fr.ajaury.gwenedeg.player.di
import org.koin.core.module.Module
expect val audioPlayerModule: Module
@@ -0,0 +1,37 @@
package fr.ajaury.gwenedeg.player
import kotlinx.cinterop.ExperimentalForeignApi
import platform.AVFoundation.AVPlayer
import platform.AVFoundation.pause
import platform.AVFoundation.play
import platform.AVFoundation.replaceCurrentItemWithPlayerItem
import platform.Foundation.NSURL
class IosAudioPlayer : AudioPlayer {
private var player: AVPlayer? = null
@OptIn(ExperimentalForeignApi::class)
override fun load(uri: String) {
val nsUrl = NSURL.URLWithString(uri) ?: error("Invalid audio URI: $uri")
player = AVPlayer(nsUrl)
}
override fun play() {
player?.play()
}
override fun pause() {
player?.pause()
}
override fun stop() {
player?.pause()
player?.replaceCurrentItemWithPlayerItem(null)
}
override fun release() {
player?.pause()
player = null
}
}
@@ -0,0 +1,35 @@
package fr.ajaury.gwenedeg.player
import kotlinx.cinterop.ExperimentalForeignApi
import platform.AVFAudio.AVAudioSession
import platform.AVFAudio.AVAudioSessionCategoryOptionDuckOthers
import platform.AVFAudio.AVAudioSessionCategoryPlayback
import platform.AVFAudio.AVAudioSessionModeSpokenAudio
import platform.AVFAudio.setActive
class IosAudioSessionManager : AudioSessionManager {
private val session = AVAudioSession.sharedInstance()
@OptIn(ExperimentalForeignApi::class)
override fun activate() {
session.setCategory(
category = AVAudioSessionCategoryPlayback,
mode = AVAudioSessionModeSpokenAudio,
options = AVAudioSessionCategoryOptionDuckOthers,
error = null,
)
session.setActive(
active = true,
error = null,
)
}
@OptIn(ExperimentalForeignApi::class)
override fun deactivate() {
session.setActive(
false,
error = null,
)
}
}
@@ -0,0 +1,13 @@
package fr.ajaury.gwenedeg.player.di
import fr.ajaury.gwenedeg.player.AudioPlayer
import fr.ajaury.gwenedeg.player.AudioSessionManager
import fr.ajaury.gwenedeg.player.IosAudioPlayer
import fr.ajaury.gwenedeg.player.IosAudioSessionManager
import org.koin.core.module.Module
import org.koin.dsl.module
actual val audioPlayerModule: Module = module {
single<AudioPlayer> { IosAudioPlayer() }
single<AudioSessionManager> { IosAudioSessionManager() }
}
@@ -0,0 +1,23 @@
package fr.ajaury.gwenedeg.player
class JsAudioPlayer : AudioPlayer {
override fun play() {
TODO("Not yet implemented")
}
override fun pause() {
TODO("Not yet implemented")
}
override fun stop() {
TODO("Not yet implemented")
}
override fun release() {
TODO("Not yet implemented")
}
override fun load(uri: String) {
TODO("Not yet implemented")
}
}
@@ -0,0 +1,6 @@
package fr.ajaury.gwenedeg.player.di
import org.koin.core.module.Module
actual val audioPlayerModule: Module
get() = TODO("Not yet implemented")
@@ -0,0 +1,23 @@
package fr.ajaury.gwenedeg.player
class JvmAudioPlayer : AudioPlayer {
override fun play() {
TODO("Not yet implemented")
}
override fun pause() {
TODO("Not yet implemented")
}
override fun stop() {
TODO("Not yet implemented")
}
override fun release() {
TODO("Not yet implemented")
}
override fun load(uri: String) {
TODO("Not yet implemented")
}
}
@@ -0,0 +1,6 @@
package fr.ajaury.gwenedeg.player.di
import org.koin.core.module.Module
actual val audioPlayerModule: Module
get() = TODO("Not yet implemented")
@@ -0,0 +1,23 @@
package fr.ajaury.gwenedeg.player
class WasmAudioPlayer : AudioPlayer {
override fun play() {
TODO("Not yet implemented")
}
override fun pause() {
TODO("Not yet implemented")
}
override fun stop() {
TODO("Not yet implemented")
}
override fun release() {
TODO("Not yet implemented")
}
override fun load(uri: String) {
TODO("Not yet implemented")
}
}
@@ -0,0 +1,6 @@
package fr.ajaury.gwenedeg.player.di
import org.koin.core.module.Module
actual val audioPlayerModule: Module
get() = TODO("Not yet implemented")