diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml
index b268ef3..f1ebaa5 100644
--- a/.idea/deploymentTargetSelector.xml
+++ b/.idea/deploymentTargetSelector.xml
@@ -4,6 +4,14 @@
+
+
+
+
+
+
+
+
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index f649f7e..7a61fff 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -58,4 +58,5 @@ dependencies {
debugImplementation(libs.androidx.compose.ui.tooling)
debugImplementation(libs.androidx.compose.ui.test.manifest)
implementation("androidx.appcompat:appcompat:1.7.0")
+ implementation("com.google.android.exoplayer:exoplayer:2.19.1")
}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/hairdryer/MainActivity.kt b/app/src/main/java/com/example/hairdryer/MainActivity.kt
index 14cf494..0814cff 100644
--- a/app/src/main/java/com/example/hairdryer/MainActivity.kt
+++ b/app/src/main/java/com/example/hairdryer/MainActivity.kt
@@ -2,51 +2,79 @@ package com.example.hairdryer
import android.app.Activity
import android.graphics.Color
-import android.media.AudioAttributes
-import android.media.SoundPool
+//import android.os.Build
import android.os.Bundle
import android.os.Handler
-import android.widget.EditText
-import android.widget.ProgressBar
-import android.widget.TextView
-import kotlin.math.pow
+import android.widget.*
+import android.content.pm.PackageManager
+import java.io.File
import kotlin.math.max
+import kotlin.math.pow
+
+// 🔥 ExoPlayer Imports
+import com.google.android.exoplayer2.ExoPlayer
+import com.google.android.exoplayer2.MediaItem
+import com.google.android.exoplayer2.Player
class MainActivity : Activity() {
- private lateinit var soundPool: SoundPool
- private var soundId = 0
- private var streamId = 0
+ private var player: ExoPlayer? = null
private val handler = Handler()
+
private var isPlaying = false
private var fadeEnabled = true
private var totalMillis: Long = 0
private var startTime: Long = 0
+
private var fadeRunnable: Runnable? = null
private var countdownRunnable: Runnable? = null
+ private var audioFiles: List = emptyList()
+ private var selectedFileIndex = 0
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
+// if (!checkPermissions()) return
+
+ val soundFolder = File(getExternalFilesDir(null), "babysounds")
+ if (!soundFolder.exists()) soundFolder.mkdirs()
+
+ cleanOldSounds(soundFolder, keepFiles = listOf("hairdryer.ogg", "fezforgotten.ogg"))
+
+ copyRawToFiles(R.raw.hairdryer, "hairdryer.ogg", soundFolder)
+ copyRawToFiles(R.raw.fezforgotten, "fezforgotten.ogg", soundFolder)
+
val status = findViewById(R.id.statusText)
val input = findViewById(R.id.timerInput)
val fadeToggle = findViewById(R.id.fadeToggle)
val volumeBar = findViewById(R.id.volumeBar)
+ val spinner = findViewById(R.id.mp3Spinner)
- val attrs = AudioAttributes.Builder()
- .setUsage(AudioAttributes.USAGE_MEDIA)
- .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
- .build()
+ audioFiles = getAudioFilesFromFolder(soundFolder)
+ val names = audioFiles.map { it.name }
- soundPool = SoundPool.Builder()
- .setMaxStreams(1)
- .setAudioAttributes(attrs)
- .build()
+ val adapter = ArrayAdapter(this, android.R.layout.simple_spinner_item, names)
+ adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
+ spinner.adapter = adapter
- soundId = soundPool.load(this, R.raw.sound, 1)
+ spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
+ override fun onItemSelected(
+ parent: AdapterView<*>?,
+ view: android.view.View?,
+ position: Int,
+ id: Long
+ ) {
+ selectedFileIndex = position
+ stopSound(status, volumeBar)
+ }
+
+ override fun onNothingSelected(parent: AdapterView<*>?) {}
+ }
updateUI(status, volumeBar, "Bereit", Color.GREEN, 1f)
+
fadeToggle.setOnClickListener {
fadeEnabled = !fadeEnabled
fadeToggle.text = "Fade-Out: " + if (fadeEnabled) "ON" else "OFF"
@@ -62,21 +90,97 @@ class MainActivity : Activity() {
}
}
+ // --- RAW -> files ---
+ private fun copyRawToFiles(rawId: Int, fileName: String, folder: File) {
+ if (!folder.exists()) folder.mkdirs()
+ val outFile = File(folder, fileName)
+
+ if (!outFile.exists()) {
+ resources.openRawResource(rawId).use { input ->
+ outFile.outputStream().use { output ->
+ input.copyTo(output)
+ }
+ }
+ }
+ }
+
+ // --- Alte Sounds löschen ---
+ private fun cleanOldSounds(folder: File, keepFiles: List) {
+ folder.listFiles { file ->
+ file.isFile &&
+ (file.extension.equals("mp3", true) || file.extension.equals("ogg", true)) &&
+ file.name !in keepFiles
+ }?.forEach { it.delete() }
+ }
+
+ // --- Audio-Dateien holen ---
+ private fun getAudioFilesFromFolder(folder: File): List {
+ return folder.listFiles { file ->
+ file.isFile &&
+ (file.extension.equals("mp3", true) || file.extension.equals("ogg", true))
+ }?.toList() ?: emptyList()
+ }
+
+ // --- Permissions ---
+/* private fun checkPermissions(): Boolean {
+ val permission = if (Build.VERSION.SDK_INT >= 33)
+ android.Manifest.permission.READ_MEDIA_AUDIO
+ else
+ android.Manifest.permission.READ_EXTERNAL_STORAGE
+
+ return if (checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED) {
+ true
+ } else {
+ requestPermissions(arrayOf(permission), 123)
+ false
+ }
+ }
+*/
+ override fun onRequestPermissionsResult(
+ requestCode: Int,
+ permissions: Array,
+ grantResults: IntArray
+ ) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults)
+ if (requestCode == 123 &&
+ grantResults.isNotEmpty() &&
+ grantResults[0] == PackageManager.PERMISSION_GRANTED
+ ) {
+ recreate()
+ }
+ }
+
+ // --- START ---
private fun startSound(minutes: Int, status: TextView, volumeBar: ProgressBar) {
stopSound(status, volumeBar)
- streamId = soundPool.play(soundId, 1f, 1f, 1, -1, 1f)
+
+ if (audioFiles.isEmpty()) return
+ val file = audioFiles[selectedFileIndex]
+ if (!file.exists()) return
+
+ player = ExoPlayer.Builder(this).build().apply {
+ val mediaItem = MediaItem.fromUri(file.toURI().toString())
+ setMediaItem(mediaItem)
+
+ repeatMode = Player.REPEAT_MODE_ONE // 🔥 GAPLESS LOOP
+ prepare()
+ play()
+ }
+
isPlaying = true
startTime = System.currentTimeMillis()
if (minutes > 0) {
totalMillis = minutes * 60_000L
updateCountdown(minutes * 60, status)
- // Countdown jede Sekunde
+
countdownRunnable = object : Runnable {
override fun run() {
val elapsedSec = ((System.currentTimeMillis() - startTime) / 1000).toInt()
val remainingSec = max(minutes * 60 - elapsedSec, 0)
+
updateCountdown(remainingSec, status)
+
if (remainingSec > 0) {
handler.postDelayed(this, 1000L)
} else {
@@ -86,16 +190,18 @@ class MainActivity : Activity() {
}
handler.postDelayed(countdownRunnable!!, 1000L)
- // Fade-Out (optional)
if (fadeEnabled) {
fadeRunnable = object : Runnable {
override fun run() {
if (!isPlaying) return
+
val elapsed = System.currentTimeMillis() - startTime
val progress = (elapsed.toFloat() / totalMillis).coerceIn(0f, 1f)
- val volume = (1 - progress).pow(2) // exponentielles Fade
- soundPool.setVolume(streamId, volume, volume)
+ val volume = (1 - progress).pow(2)
+
+ player?.volume = volume
volumeBar.progress = (volume * 100).toInt()
+
if (progress < 1f) {
handler.postDelayed(this, 200L)
}
@@ -105,13 +211,14 @@ class MainActivity : Activity() {
} else {
volumeBar.progress = 100
}
+
} else {
- // unendlich
updateUI(status, volumeBar, "Stopp: ∞", Color.RED, 1f)
volumeBar.progress = 100
}
}
+ // --- Countdown ---
private fun updateCountdown(remainingSec: Int, status: TextView) {
val minutes = remainingSec / 60
val seconds = remainingSec % 60
@@ -120,17 +227,22 @@ class MainActivity : Activity() {
status.setTextColor(Color.BLACK)
}
+ // --- STOP ---
private fun stopSound(status: TextView, volumeBar: ProgressBar) {
if (isPlaying) {
- soundPool.stop(streamId)
+ player?.release()
+ player = null
isPlaying = false
}
+
handler.removeCallbacks(countdownRunnable ?: Runnable {})
handler.removeCallbacks(fadeRunnable ?: Runnable {})
+
updateUI(status, volumeBar, "Start", Color.GREEN, 0f)
volumeBar.progress = 0
}
+ // --- UI ---
private fun updateUI(view: TextView, bar: ProgressBar, text: String, color: Int, volume: Float) {
view.text = text
view.setBackgroundColor(color)
@@ -140,6 +252,6 @@ class MainActivity : Activity() {
override fun onDestroy() {
super.onDestroy()
- soundPool.release()
+ player?.release()
}
-}
+}
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index ed37522..229817f 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -19,6 +19,15 @@
+
+
+
+