package com.example.hairdryer import android.app.Activity import android.graphics.Color //import android.os.Build import android.os.Bundle import android.os.Handler 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 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) copyRawToFiles(R.raw.feuer, "feuer.ogg", soundFolder) copyRawToFiles(R.raw.dwarfs, "dwarfs.ogg", soundFolder) copyRawToFiles(R.raw.meeresrauschen, "meeresrauschen.ogg", soundFolder) copyRawToFiles(R.raw.regen, "regen.ogg", soundFolder) copyRawToFiles(R.raw.zombi, "zombi.ogg", soundFolder) copyRawToFiles(R.raw.zombi_refrain, "zombi-refrain.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) audioFiles = getAudioFilesFromFolder(soundFolder) .sortedWith(compareByDescending { it.name == "hairdryer.ogg" } .thenBy { it.name }) val names = audioFiles.map { it.nameWithoutExtension } val adapter = ArrayAdapter(this, android.R.layout.simple_spinner_item, names) adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) spinner.adapter = adapter 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" } status.setOnClickListener { if (isPlaying) { stopSound(status, volumeBar) } else { val minutes = input.text.toString().toIntOrNull() ?: 0 startSound(minutes, status, volumeBar) } } } // --- 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) 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) 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 { stopSound(status, volumeBar) } } } handler.postDelayed(countdownRunnable!!, 1000L) 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) player?.volume = volume volumeBar.progress = (volume * 100).toInt() if (progress < 1f) { handler.postDelayed(this, 200L) } } } handler.postDelayed(fadeRunnable!!, 200L) } else { volumeBar.progress = 100 } } else { 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 status.text = String.format("Stopp: %02d:%02d", minutes, seconds) status.setBackgroundColor(Color.RED) status.setTextColor(Color.BLACK) } // --- STOP --- private fun stopSound(status: TextView, volumeBar: ProgressBar) { if (isPlaying) { 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) view.setTextColor(Color.BLACK) bar.progress = (volume * 100).toInt() } override fun onDestroy() { super.onDestroy() player?.release() } }