new: implement desktop audio player with Ogg Vorbis support using VorbisSPI

This commit is contained in:
2026-06-17 17:18:33 +02:00
parent 8fb8ea24cf
commit 1fc8fbbc0d
5 changed files with 73 additions and 10 deletions
+4
View File
@@ -43,6 +43,10 @@ kotlin {
implementation(libs.media3.exoplayer)
implementation(libs.media3.common)
}
jvmMain.dependencies {
// Desktop audio: Ogg Vorbis SPI so javax.sound.sampled can decode .ogg
implementation(libs.vorbisspi)
}
commonMain.dependencies {
// DI
implementation(project.dependencies.platform(libs.koin.bom))
@@ -1,23 +1,61 @@
package fr.ajaury.gwenedeg.player
import java.io.BufferedInputStream
import java.net.URI
import javax.sound.sampled.AudioFormat
import javax.sound.sampled.AudioSystem
import javax.sound.sampled.Clip
/**
* Desktop [AudioPlayer] backed by [javax.sound.sampled].
*
* The Ogg Vorbis recordings are decoded to PCM by the VorbisSPI providers on the classpath, which
* the JDK [AudioSystem] discovers automatically, so loading works the same as any natively
* supported format.
*/
class JvmAudioPlayer : AudioPlayer {
private var clip: Clip? = null
override fun load(uri: String) {
release()
val encodedInput = BufferedInputStream(URI(uri).toURL().openStream())
.let { AudioSystem.getAudioInputStream(it) }
val baseFormat = encodedInput.format
val pcmFormat = AudioFormat(
AudioFormat.Encoding.PCM_SIGNED,
baseFormat.sampleRate,
16,
baseFormat.channels,
baseFormat.channels * 2,
baseFormat.sampleRate,
false,
)
val decodedInput = AudioSystem.getAudioInputStream(pcmFormat, encodedInput)
clip = AudioSystem.getClip().apply { open(decodedInput) }
}
override fun play() {
TODO("Not yet implemented")
clip?.start()
}
override fun pause() {
TODO("Not yet implemented")
clip?.stop()
}
override fun stop() {
TODO("Not yet implemented")
clip?.apply {
stop()
framePosition = 0
}
}
override fun release() {
TODO("Not yet implemented")
}
override fun load(uri: String) {
TODO("Not yet implemented")
clip?.apply {
stop()
close()
}
clip = null
}
}
@@ -0,0 +1,12 @@
package fr.ajaury.gwenedeg.player
/**
* Desktop has no system audio focus model to honour, so activation is a no-op.
*/
class JvmAudioSessionManager : AudioSessionManager {
override fun activate() {
}
override fun deactivate() {
}
}
@@ -1,6 +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.JvmAudioPlayer
import fr.ajaury.gwenedeg.player.JvmAudioSessionManager
import org.koin.core.module.Module
import org.koin.dsl.module
actual val audioPlayerModule: Module
get() = TODO("Not yet implemented")
actual val audioPlayerModule: Module = module {
single<AudioPlayer> { JvmAudioPlayer() }
single<AudioSessionManager> { JvmAudioSessionManager() }
}
+2
View File
@@ -16,6 +16,7 @@ kotlinxCoroutines = "1.11.0"
kotlinSerialization = "2.4.0"
material3 = "1.11.0-alpha07"
koin = "4.2.1"
vorbisspi = "1.0.3.3"
[libraries]
androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidxActivity" }
@@ -40,6 +41,7 @@ lifecycle-viewmodelNavigation3 = { module = "org.jetbrains.androidx.lifecycle:li
media3-exoplayer = { module = "androidx.media3:media3-exoplayer", version.ref = "media3" }
media3-common = { module = "androidx.media3:media3-common", version.ref = "media3" }
navigation3 = { module = "org.jetbrains.androidx.navigation3:navigation3-ui", version.ref = "navigation3" }
vorbisspi = { module = "com.googlecode.soundlibs:vorbisspi", version.ref = "vorbisspi" }
wrappers-browser = { module = "org.jetbrains.kotlin-wrappers:kotlin-browser", version.ref = "kotlinWrappers" }
[plugins]