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 @@ + + + +