Initial release of GPS2Audio
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
package de.waypointaudio.service
|
||||
|
||||
import android.content.Context
|
||||
import android.media.MediaPlayer
|
||||
import android.net.Uri
|
||||
import android.util.Log
|
||||
|
||||
/**
|
||||
* Einfacher Audio-Player für Wegpunkt-Sounds (GPS-Dienst).
|
||||
* Nutzt Android MediaPlayer; spielt Content-URIs oder File-URIs ab.
|
||||
*
|
||||
* Completion- und Error-Callbacks erlauben dem Aufrufer, exakt nach dem
|
||||
* Ende der Wiedergabe (oder bei einem Fehler) auf das Ereignis zu reagieren –
|
||||
* z. B. um die Begleitmusik korrekt wiederherzustellen.
|
||||
*
|
||||
* Thread-Sicherheit: Die Callbacks werden auf dem MediaPlayer-internen Thread
|
||||
* aufgerufen. Der Aufrufer ist dafür verantwortlich, ggf. in den richtigen
|
||||
* Dispatcher zu wechseln (z. B. Dispatchers.Main für ExoPlayer-Zugriffe).
|
||||
*/
|
||||
class AudioPlayer {
|
||||
|
||||
private var mediaPlayer: MediaPlayer? = null
|
||||
|
||||
/**
|
||||
* Spielt eine Audiodatei ab.
|
||||
*
|
||||
* @param context Android-Context
|
||||
* @param soundUri URI-String (content:// oder file://)
|
||||
* @param onCompletion Wird aufgerufen, wenn die Wiedergabe natürlich endet.
|
||||
* Wird auf dem MediaPlayer-Thread aufgerufen.
|
||||
* @param onError Wird aufgerufen, wenn die Wiedergabe nicht gestartet
|
||||
* werden kann oder ein MediaPlayer-Fehler auftritt.
|
||||
* Wird auf dem MediaPlayer-Thread (oder dem Aufruf-Thread
|
||||
* bei Initialisierungsfehlern) aufgerufen.
|
||||
*/
|
||||
fun play(
|
||||
context: Context,
|
||||
soundUri: String,
|
||||
onCompletion: (() -> Unit)? = null,
|
||||
onError: ((Exception?) -> Unit)? = null
|
||||
) {
|
||||
if (soundUri.isBlank()) {
|
||||
Log.w(TAG, "Keine Audiodatei angegeben.")
|
||||
onError?.invoke(IllegalArgumentException("soundUri ist leer"))
|
||||
return
|
||||
}
|
||||
|
||||
stop() // vorherige Wiedergabe stoppen
|
||||
|
||||
// callbackFired verhindert Doppel-Callbacks: completion und error
|
||||
// schließen sich gegenseitig aus.
|
||||
var callbackFired = false
|
||||
|
||||
runCatching {
|
||||
val uri = Uri.parse(soundUri)
|
||||
val mp = MediaPlayer()
|
||||
mediaPlayer = mp
|
||||
mp.setDataSource(context, uri)
|
||||
mp.prepare()
|
||||
|
||||
mp.setOnCompletionListener { finishedMp ->
|
||||
if (!callbackFired) {
|
||||
callbackFired = true
|
||||
finishedMp.release()
|
||||
if (mediaPlayer === finishedMp) mediaPlayer = null
|
||||
Log.d(TAG, "Wiedergabe beendet: $soundUri")
|
||||
onCompletion?.invoke()
|
||||
}
|
||||
}
|
||||
|
||||
mp.setOnErrorListener { errorMp, what, extra ->
|
||||
if (!callbackFired) {
|
||||
callbackFired = true
|
||||
errorMp.release()
|
||||
if (mediaPlayer === errorMp) mediaPlayer = null
|
||||
val msg = "MediaPlayer-Fehler: what=$what extra=$extra"
|
||||
Log.e(TAG, msg)
|
||||
onError?.invoke(RuntimeException(msg))
|
||||
}
|
||||
true // Fehler wurde behandelt → kein zusätzlicher onCompletion-Aufruf
|
||||
}
|
||||
|
||||
mp.start()
|
||||
}.onFailure { e ->
|
||||
Log.e(TAG, "Fehler beim Abspielen von $soundUri", e)
|
||||
// Ressource freigeben falls teilweise initialisiert
|
||||
runCatching { mediaPlayer?.release() }
|
||||
mediaPlayer = null
|
||||
if (!callbackFired) {
|
||||
callbackFired = true
|
||||
onError?.invoke(e as? Exception ?: RuntimeException(e))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Stoppt die aktuelle Wiedergabe und gibt Ressourcen frei. */
|
||||
fun stop() {
|
||||
runCatching {
|
||||
mediaPlayer?.let {
|
||||
if (it.isPlaying) it.stop()
|
||||
it.release()
|
||||
}
|
||||
}
|
||||
mediaPlayer = null
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "AudioPlayer"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user