feat: Add AudioVisualizer

This commit is contained in:
Myzel394 2023-08-05 15:38:11 +02:00
parent c8a02567ab
commit 9b1a439657
No known key found for this signature in database
GPG Key ID: 79CC92F37B3E1A2B
3 changed files with 87 additions and 23 deletions

View File

@ -11,6 +11,7 @@ import android.os.Handler
import android.os.IBinder
import android.os.Looper
import android.util.Log
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
@ -32,6 +33,8 @@ import java.util.Date
import java.util.UUID;
const val AMPLITUDE_UPDATE_INTERVAL = 100L
class RecorderService: Service() {
private val binder = LocalBinder()
private val handler = Handler(Looper.getMainLooper())
@ -56,6 +59,7 @@ class RecorderService: Service() {
get() = recordingStart.value != null
val filePaths = mutableListOf<String>()
val amplitudes = mutableStateListOf<Int>()
var originalRecordingStart: LocalDateTime? = null
private set
@ -121,6 +125,17 @@ class RecorderService: Service() {
return File(outputFile)
}
private fun updateAmplitude() {
if (!isRecording || mediaRecorder == null) {
return
}
val amplitude = mediaRecorder!!.maxAmplitude
amplitudes.add(amplitude)
handler.postDelayed(::updateAmplitude, AMPLITUDE_UPDATE_INTERVAL)
}
private fun startNewRecording() {
if (!isRecording) {
return
@ -183,6 +198,7 @@ class RecorderService: Service() {
private fun start() {
amplitudes.clear()
filePaths.clear()
// Create folder
File(this.fileFolder!!).mkdirs()
@ -196,6 +212,7 @@ class RecorderService: Service() {
showNotification()
startNewRecording()
updateAmplitude()
}
}
}

View File

@ -0,0 +1,48 @@
package app.myzel394.locationtest.ui.components.AudioRecorder.atoms
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.width
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.CornerRadius
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.drawscope.translate
import androidx.compose.ui.unit.dp
// Inspired by https://github.com/Bnyro/RecordYou/blob/main/app/src/main/java/com/bnyro/recorder/ui/components/AudioVisualizer.kt
private const val MAX_AMPLITUDE = 10000
@Composable
fun AudioVisualizer(
amplitudes: List<Int>,
) {
val primary = MaterialTheme.colorScheme.primary
val primaryMuted = primary.copy(alpha = 0.3f)
Canvas(
modifier = Modifier.width(300.dp).height(300.dp)
) {
val height = this.size.height / 2f
val width = this.size.width
translate(width, height) {
amplitudes.forEachIndexed { index, amplitude ->
val amplitudePercentage = (amplitude.toFloat() / MAX_AMPLITUDE).coerceAtMost(1f)
val boxHeight = height * amplitudePercentage
drawRoundRect(
color = if (amplitudePercentage > 0.05f) primary else primaryMuted,
topLeft = Offset(
30f * (index - amplitudes.size),
-boxHeight / 2f
),
size = Size(15f, boxHeight),
cornerRadius = CornerRadius(3f, 3f)
)
}
}
}
}

View File

@ -1,12 +1,5 @@
package app.myzel394.locationtest.ui.components.AudioRecorder.atoms
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.animation.core.RepeatMode
import androidx.compose.animation.core.animateFloat
import androidx.compose.animation.core.infiniteRepeatable
import androidx.compose.animation.core.rememberInfiniteTransition
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
@ -27,10 +20,13 @@ import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
@ -39,38 +35,43 @@ import app.myzel394.locationtest.services.RecorderService
import app.myzel394.locationtest.ui.components.atoms.Pulsating
import app.myzel394.locationtest.ui.utils.formatDuration
import app.myzel394.locationtest.ui.utils.rememberFileSaverDialog
import kotlinx.coroutines.delay
import java.time.Duration
import java.time.LocalDateTime
import java.time.ZoneId
@Composable
fun RecordingStatus(
service: RecorderService,
) {
val context = LocalContext.current
val saveFile = rememberFileSaverDialog("audio/*")
var now by remember { mutableStateOf(LocalDateTime.now()) }
val start = service.recordingStart.value!!
val duration = now.toEpochSecond(ZoneId.systemDefault().rules.getOffset(now)) - start.toEpochSecond(ZoneId.systemDefault().rules.getOffset(start))
val progress = duration / (service.settings.maxDuration / 1000f)
LaunchedEffect(Unit) {
while (true) {
now = LocalDateTime.now()
delay(1000)
}
}
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
) {
// Forces real time update for the text
val transition = rememberInfiniteTransition()
val forceUpdateValue by transition.animateFloat(
initialValue = .999999f,
targetValue = 1f,
animationSpec = infiniteRepeatable(
animation = tween(1000),
repeatMode = RepeatMode.Reverse
)
)
AudioVisualizer(amplitudes = service.amplitudes)
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center,
) {
val distance = Duration.between(service.recordingStart.value, LocalDateTime.now()).toMillis()
val distance = Duration.between(service.recordingStart.value, now).toMillis()
Pulsating {
Box(
@ -84,15 +85,13 @@ fun RecordingStatus(
Text(
text = formatDuration(distance),
style = MaterialTheme.typography.headlineLarge,
modifier = Modifier.alpha(forceUpdateValue)
)
}
Spacer(modifier = Modifier.height(8.dp))
LinearProgressIndicator(
progress = service.progress,
progress = progress,
modifier = Modifier
.width(300.dp)
.alpha(forceUpdateValue)
)
Spacer(modifier = Modifier.height(32.dp))
Button(