changed from playsound to exoplayer2 because of 5,6seconds limit
This commit is contained in:
Generated
+8
@@ -4,6 +4,14 @@
|
|||||||
<selectionStates>
|
<selectionStates>
|
||||||
<SelectionState runConfigName="app">
|
<SelectionState runConfigName="app">
|
||||||
<option name="selectionMode" value="DROPDOWN" />
|
<option name="selectionMode" value="DROPDOWN" />
|
||||||
|
<DropdownSelection timestamp="2026-01-24T22:53:27.667021Z">
|
||||||
|
<Target type="DEFAULT_BOOT">
|
||||||
|
<handle>
|
||||||
|
<DeviceId pluginId="PhysicalDevice" identifier="serial=000988cf644b8f" />
|
||||||
|
</handle>
|
||||||
|
</Target>
|
||||||
|
</DropdownSelection>
|
||||||
|
<DialogSelection />
|
||||||
</SelectionState>
|
</SelectionState>
|
||||||
</selectionStates>
|
</selectionStates>
|
||||||
</component>
|
</component>
|
||||||
|
|||||||
@@ -58,4 +58,5 @@ dependencies {
|
|||||||
debugImplementation(libs.androidx.compose.ui.tooling)
|
debugImplementation(libs.androidx.compose.ui.tooling)
|
||||||
debugImplementation(libs.androidx.compose.ui.test.manifest)
|
debugImplementation(libs.androidx.compose.ui.test.manifest)
|
||||||
implementation("androidx.appcompat:appcompat:1.7.0")
|
implementation("androidx.appcompat:appcompat:1.7.0")
|
||||||
|
implementation("com.google.android.exoplayer:exoplayer:2.19.1")
|
||||||
}
|
}
|
||||||
@@ -2,51 +2,79 @@ package com.example.hairdryer
|
|||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.media.AudioAttributes
|
//import android.os.Build
|
||||||
import android.media.SoundPool
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.widget.EditText
|
import android.widget.*
|
||||||
import android.widget.ProgressBar
|
import android.content.pm.PackageManager
|
||||||
import android.widget.TextView
|
import java.io.File
|
||||||
import kotlin.math.pow
|
|
||||||
import kotlin.math.max
|
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() {
|
class MainActivity : Activity() {
|
||||||
|
|
||||||
private lateinit var soundPool: SoundPool
|
private var player: ExoPlayer? = null
|
||||||
private var soundId = 0
|
|
||||||
private var streamId = 0
|
|
||||||
private val handler = Handler()
|
private val handler = Handler()
|
||||||
|
|
||||||
private var isPlaying = false
|
private var isPlaying = false
|
||||||
private var fadeEnabled = true
|
private var fadeEnabled = true
|
||||||
private var totalMillis: Long = 0
|
private var totalMillis: Long = 0
|
||||||
private var startTime: Long = 0
|
private var startTime: Long = 0
|
||||||
|
|
||||||
private var fadeRunnable: Runnable? = null
|
private var fadeRunnable: Runnable? = null
|
||||||
private var countdownRunnable: Runnable? = null
|
private var countdownRunnable: Runnable? = null
|
||||||
|
|
||||||
|
private var audioFiles: List<File> = emptyList()
|
||||||
|
private var selectedFileIndex = 0
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContentView(R.layout.activity_main)
|
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<TextView>(R.id.statusText)
|
val status = findViewById<TextView>(R.id.statusText)
|
||||||
val input = findViewById<EditText>(R.id.timerInput)
|
val input = findViewById<EditText>(R.id.timerInput)
|
||||||
val fadeToggle = findViewById<TextView>(R.id.fadeToggle)
|
val fadeToggle = findViewById<TextView>(R.id.fadeToggle)
|
||||||
val volumeBar = findViewById<ProgressBar>(R.id.volumeBar)
|
val volumeBar = findViewById<ProgressBar>(R.id.volumeBar)
|
||||||
|
val spinner = findViewById<Spinner>(R.id.mp3Spinner)
|
||||||
|
|
||||||
val attrs = AudioAttributes.Builder()
|
audioFiles = getAudioFilesFromFolder(soundFolder)
|
||||||
.setUsage(AudioAttributes.USAGE_MEDIA)
|
val names = audioFiles.map { it.name }
|
||||||
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
soundPool = SoundPool.Builder()
|
val adapter = ArrayAdapter(this, android.R.layout.simple_spinner_item, names)
|
||||||
.setMaxStreams(1)
|
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||||
.setAudioAttributes(attrs)
|
spinner.adapter = adapter
|
||||||
.build()
|
|
||||||
|
|
||||||
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)
|
updateUI(status, volumeBar, "Bereit", Color.GREEN, 1f)
|
||||||
|
|
||||||
fadeToggle.setOnClickListener {
|
fadeToggle.setOnClickListener {
|
||||||
fadeEnabled = !fadeEnabled
|
fadeEnabled = !fadeEnabled
|
||||||
fadeToggle.text = "Fade-Out: " + if (fadeEnabled) "ON" else "OFF"
|
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<String>) {
|
||||||
|
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<File> {
|
||||||
|
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<out String>,
|
||||||
|
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) {
|
private fun startSound(minutes: Int, status: TextView, volumeBar: ProgressBar) {
|
||||||
stopSound(status, volumeBar)
|
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
|
isPlaying = true
|
||||||
startTime = System.currentTimeMillis()
|
startTime = System.currentTimeMillis()
|
||||||
|
|
||||||
if (minutes > 0) {
|
if (minutes > 0) {
|
||||||
totalMillis = minutes * 60_000L
|
totalMillis = minutes * 60_000L
|
||||||
updateCountdown(minutes * 60, status)
|
updateCountdown(minutes * 60, status)
|
||||||
// Countdown jede Sekunde
|
|
||||||
countdownRunnable = object : Runnable {
|
countdownRunnable = object : Runnable {
|
||||||
override fun run() {
|
override fun run() {
|
||||||
val elapsedSec = ((System.currentTimeMillis() - startTime) / 1000).toInt()
|
val elapsedSec = ((System.currentTimeMillis() - startTime) / 1000).toInt()
|
||||||
val remainingSec = max(minutes * 60 - elapsedSec, 0)
|
val remainingSec = max(minutes * 60 - elapsedSec, 0)
|
||||||
|
|
||||||
updateCountdown(remainingSec, status)
|
updateCountdown(remainingSec, status)
|
||||||
|
|
||||||
if (remainingSec > 0) {
|
if (remainingSec > 0) {
|
||||||
handler.postDelayed(this, 1000L)
|
handler.postDelayed(this, 1000L)
|
||||||
} else {
|
} else {
|
||||||
@@ -86,16 +190,18 @@ class MainActivity : Activity() {
|
|||||||
}
|
}
|
||||||
handler.postDelayed(countdownRunnable!!, 1000L)
|
handler.postDelayed(countdownRunnable!!, 1000L)
|
||||||
|
|
||||||
// Fade-Out (optional)
|
|
||||||
if (fadeEnabled) {
|
if (fadeEnabled) {
|
||||||
fadeRunnable = object : Runnable {
|
fadeRunnable = object : Runnable {
|
||||||
override fun run() {
|
override fun run() {
|
||||||
if (!isPlaying) return
|
if (!isPlaying) return
|
||||||
|
|
||||||
val elapsed = System.currentTimeMillis() - startTime
|
val elapsed = System.currentTimeMillis() - startTime
|
||||||
val progress = (elapsed.toFloat() / totalMillis).coerceIn(0f, 1f)
|
val progress = (elapsed.toFloat() / totalMillis).coerceIn(0f, 1f)
|
||||||
val volume = (1 - progress).pow(2) // exponentielles Fade
|
val volume = (1 - progress).pow(2)
|
||||||
soundPool.setVolume(streamId, volume, volume)
|
|
||||||
|
player?.volume = volume
|
||||||
volumeBar.progress = (volume * 100).toInt()
|
volumeBar.progress = (volume * 100).toInt()
|
||||||
|
|
||||||
if (progress < 1f) {
|
if (progress < 1f) {
|
||||||
handler.postDelayed(this, 200L)
|
handler.postDelayed(this, 200L)
|
||||||
}
|
}
|
||||||
@@ -105,13 +211,14 @@ class MainActivity : Activity() {
|
|||||||
} else {
|
} else {
|
||||||
volumeBar.progress = 100
|
volumeBar.progress = 100
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// unendlich
|
|
||||||
updateUI(status, volumeBar, "Stopp: ∞", Color.RED, 1f)
|
updateUI(status, volumeBar, "Stopp: ∞", Color.RED, 1f)
|
||||||
volumeBar.progress = 100
|
volumeBar.progress = 100
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Countdown ---
|
||||||
private fun updateCountdown(remainingSec: Int, status: TextView) {
|
private fun updateCountdown(remainingSec: Int, status: TextView) {
|
||||||
val minutes = remainingSec / 60
|
val minutes = remainingSec / 60
|
||||||
val seconds = remainingSec % 60
|
val seconds = remainingSec % 60
|
||||||
@@ -120,17 +227,22 @@ class MainActivity : Activity() {
|
|||||||
status.setTextColor(Color.BLACK)
|
status.setTextColor(Color.BLACK)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- STOP ---
|
||||||
private fun stopSound(status: TextView, volumeBar: ProgressBar) {
|
private fun stopSound(status: TextView, volumeBar: ProgressBar) {
|
||||||
if (isPlaying) {
|
if (isPlaying) {
|
||||||
soundPool.stop(streamId)
|
player?.release()
|
||||||
|
player = null
|
||||||
isPlaying = false
|
isPlaying = false
|
||||||
}
|
}
|
||||||
|
|
||||||
handler.removeCallbacks(countdownRunnable ?: Runnable {})
|
handler.removeCallbacks(countdownRunnable ?: Runnable {})
|
||||||
handler.removeCallbacks(fadeRunnable ?: Runnable {})
|
handler.removeCallbacks(fadeRunnable ?: Runnable {})
|
||||||
|
|
||||||
updateUI(status, volumeBar, "Start", Color.GREEN, 0f)
|
updateUI(status, volumeBar, "Start", Color.GREEN, 0f)
|
||||||
volumeBar.progress = 0
|
volumeBar.progress = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- UI ---
|
||||||
private fun updateUI(view: TextView, bar: ProgressBar, text: String, color: Int, volume: Float) {
|
private fun updateUI(view: TextView, bar: ProgressBar, text: String, color: Int, volume: Float) {
|
||||||
view.text = text
|
view.text = text
|
||||||
view.setBackgroundColor(color)
|
view.setBackgroundColor(color)
|
||||||
@@ -140,6 +252,6 @@ class MainActivity : Activity() {
|
|||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
soundPool.release()
|
player?.release()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -19,6 +19,15 @@
|
|||||||
<Space
|
<Space
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="50dp" />
|
android:layout_height="50dp" />
|
||||||
|
|
||||||
|
<Spinner
|
||||||
|
android:id="@+id/mp3Spinner"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
<Space
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="20dp" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/statusText"
|
android:id="@+id/statusText"
|
||||||
android:text="Start"
|
android:text="Start"
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user