Simple live waveform implementation.
This commit is contained in:
parent
4a390296cf
commit
510b1f63b9
10 changed files with 195 additions and 51 deletions
|
|
@ -40,6 +40,7 @@ import io.element.android.libraries.textcomposer.model.VoiceMessageState
|
||||||
import io.element.android.libraries.voicerecorder.api.VoiceRecorder
|
import io.element.android.libraries.voicerecorder.api.VoiceRecorder
|
||||||
import io.element.android.libraries.voicerecorder.api.VoiceRecorderState
|
import io.element.android.libraries.voicerecorder.api.VoiceRecorderState
|
||||||
import io.element.android.services.analytics.api.AnalyticsService
|
import io.element.android.services.analytics.api.AnalyticsService
|
||||||
|
import kotlinx.collections.immutable.toPersistentList
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
@ -172,7 +173,7 @@ class VoiceMessageComposerPresenter @Inject constructor(
|
||||||
voiceMessageState = when (val state = recorderState) {
|
voiceMessageState = when (val state = recorderState) {
|
||||||
is VoiceRecorderState.Recording -> VoiceMessageState.Recording(
|
is VoiceRecorderState.Recording -> VoiceMessageState.Recording(
|
||||||
duration = state.elapsedTime,
|
duration = state.elapsedTime,
|
||||||
level = state.level
|
levels = state.levels.toPersistentList()
|
||||||
)
|
)
|
||||||
is VoiceRecorderState.Finished -> if (isSending) {
|
is VoiceRecorderState.Finished -> if (isSending) {
|
||||||
VoiceMessageState.Sending
|
VoiceMessageState.Sending
|
||||||
|
|
|
||||||
|
|
@ -18,12 +18,13 @@ package io.element.android.features.messages.impl.voicemessages.composer
|
||||||
|
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
import io.element.android.libraries.textcomposer.model.VoiceMessageState
|
import io.element.android.libraries.textcomposer.model.VoiceMessageState
|
||||||
|
import kotlinx.collections.immutable.toPersistentList
|
||||||
import kotlin.time.Duration.Companion.seconds
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
internal open class VoiceMessageComposerStateProvider : PreviewParameterProvider<VoiceMessageComposerState> {
|
internal open class VoiceMessageComposerStateProvider : PreviewParameterProvider<VoiceMessageComposerState> {
|
||||||
override val values: Sequence<VoiceMessageComposerState>
|
override val values: Sequence<VoiceMessageComposerState>
|
||||||
get() = sequenceOf(
|
get() = sequenceOf(
|
||||||
aVoiceMessageComposerState(voiceMessageState = VoiceMessageState.Recording(duration = 61.seconds, level = 0.5f)),
|
aVoiceMessageComposerState(voiceMessageState = VoiceMessageState.Recording(duration = 61.seconds, levels = aWaveformLevels)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -35,3 +36,7 @@ internal fun aVoiceMessageComposerState(
|
||||||
showPermissionRationaleDialog = showPermissionRationaleDialog,
|
showPermissionRationaleDialog = showPermissionRationaleDialog,
|
||||||
eventSink = {},
|
eventSink = {},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
internal var aWaveformLevels = List(100) { it.toFloat() / 200 }.toPersistentList()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.designsystem.components.media
|
||||||
|
|
||||||
|
import androidx.compose.ui.geometry.CornerRadius
|
||||||
|
import androidx.compose.ui.geometry.Offset
|
||||||
|
import androidx.compose.ui.geometry.Size
|
||||||
|
import androidx.compose.ui.graphics.Brush
|
||||||
|
import androidx.compose.ui.graphics.drawscope.DrawScope
|
||||||
|
import androidx.compose.ui.graphics.drawscope.Fill
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import androidx.compose.ui.unit.DpSize
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
import kotlin.math.max
|
||||||
|
|
||||||
|
fun DrawScope.drawWaveform(
|
||||||
|
waveformData: ImmutableList<Float>,
|
||||||
|
canvasSize: DpSize,
|
||||||
|
brush: Brush,
|
||||||
|
minimumGraphAmplitude: Float = 2F,
|
||||||
|
lineWidth: Dp = 2.dp,
|
||||||
|
linePadding: Dp = 2.dp,
|
||||||
|
) {
|
||||||
|
val centerY = canvasSize.height.toPx() / 2
|
||||||
|
val cornerRadius = lineWidth / 2
|
||||||
|
waveformData.forEachIndexed { index, amplitude ->
|
||||||
|
val drawingAmplitude = max(minimumGraphAmplitude, amplitude * (canvasSize.height.toPx() - 2))
|
||||||
|
drawRoundRect(
|
||||||
|
brush = brush,
|
||||||
|
topLeft = Offset(
|
||||||
|
x = index * (linePadding + lineWidth).toPx(),
|
||||||
|
y = centerY - drawingAmplitude / 2
|
||||||
|
),
|
||||||
|
size = Size(
|
||||||
|
width = lineWidth.toPx(),
|
||||||
|
height = drawingAmplitude
|
||||||
|
),
|
||||||
|
cornerRadius = CornerRadius(cornerRadius.toPx(), cornerRadius.toPx()),
|
||||||
|
style = Fill
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -48,7 +48,6 @@ import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
import io.element.android.libraries.theme.ElementTheme
|
import io.element.android.libraries.theme.ElementTheme
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import kotlinx.collections.immutable.toPersistentList
|
import kotlinx.collections.immutable.toPersistentList
|
||||||
import kotlin.math.max
|
|
||||||
|
|
||||||
private const val DEFAULT_GRAPHICS_LAYER_ALPHA: Float = 0.99F
|
private const val DEFAULT_GRAPHICS_LAYER_ALPHA: Float = 0.99F
|
||||||
@OptIn(ExperimentalComposeUiApi::class)
|
@OptIn(ExperimentalComposeUiApi::class)
|
||||||
|
|
@ -64,7 +63,6 @@ fun WaveformPlaybackView(
|
||||||
cursorBrush: Brush = SolidColor(ElementTheme.colors.iconAccentTertiary),
|
cursorBrush: Brush = SolidColor(ElementTheme.colors.iconAccentTertiary),
|
||||||
lineWidth: Dp = 2.dp,
|
lineWidth: Dp = 2.dp,
|
||||||
linePadding: Dp = 2.dp,
|
linePadding: Dp = 2.dp,
|
||||||
minimumGraphAmplitude: Float = 2F,
|
|
||||||
) {
|
) {
|
||||||
val seekProgress = remember { mutableStateOf<Float?>(null) }
|
val seekProgress = remember { mutableStateOf<Float?>(null) }
|
||||||
var canvasSize by remember { mutableStateOf(DpSize(0.dp, 0.dp)) }
|
var canvasSize by remember { mutableStateOf(DpSize(0.dp, 0.dp)) }
|
||||||
|
|
@ -121,22 +119,13 @@ fun WaveformPlaybackView(
|
||||||
canvasSizePx = size
|
canvasSizePx = size
|
||||||
val centerY = canvasSize.height.toPx() / 2
|
val centerY = canvasSize.height.toPx() / 2
|
||||||
val cornerRadius = lineWidth / 2
|
val cornerRadius = lineWidth / 2
|
||||||
normalizedWaveformData.forEachIndexed { index, amplitude ->
|
drawWaveform(
|
||||||
val drawingAmplitude = max(minimumGraphAmplitude, amplitude * (canvasSize.height.toPx() - 2))
|
waveformData = normalizedWaveformData,
|
||||||
drawRoundRect(
|
canvasSize = canvasSize,
|
||||||
brush = brush,
|
brush = brush,
|
||||||
topLeft = Offset(
|
lineWidth = lineWidth,
|
||||||
x = index * (linePadding + lineWidth).toPx(),
|
linePadding = linePadding
|
||||||
y = centerY - drawingAmplitude / 2
|
)
|
||||||
),
|
|
||||||
size = Size(
|
|
||||||
width = lineWidth.toPx(),
|
|
||||||
height = drawingAmplitude
|
|
||||||
),
|
|
||||||
cornerRadius = CornerRadius(cornerRadius.toPx(), cornerRadius.toPx()),
|
|
||||||
style = Fill
|
|
||||||
)
|
|
||||||
}
|
|
||||||
drawRect(
|
drawRect(
|
||||||
brush = progressBrush,
|
brush = progressBrush,
|
||||||
size = Size(
|
size = Size(
|
||||||
|
|
|
||||||
|
|
@ -82,6 +82,7 @@ import io.element.android.wysiwyg.compose.RichTextEditor
|
||||||
import io.element.android.wysiwyg.compose.RichTextEditorState
|
import io.element.android.wysiwyg.compose.RichTextEditorState
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
import kotlinx.collections.immutable.toPersistentList
|
||||||
import kotlin.time.Duration.Companion.seconds
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -204,7 +205,7 @@ fun TextComposer(
|
||||||
onPauseClick = onPauseVoiceMessageClicked
|
onPauseClick = onPauseVoiceMessageClicked
|
||||||
)
|
)
|
||||||
is VoiceMessageState.Recording ->
|
is VoiceMessageState.Recording ->
|
||||||
VoiceMessageRecording(voiceMessageState.level, voiceMessageState.duration)
|
VoiceMessageRecording(voiceMessageState.levels, voiceMessageState.duration)
|
||||||
VoiceMessageState.Idle -> {}
|
VoiceMessageState.Idle -> {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -798,7 +799,7 @@ internal fun TextComposerVoicePreview() = ElementPreview {
|
||||||
enableVoiceMessages = true,
|
enableVoiceMessages = true,
|
||||||
)
|
)
|
||||||
PreviewColumn(items = persistentListOf({
|
PreviewColumn(items = persistentListOf({
|
||||||
VoicePreview(voiceMessageState = VoiceMessageState.Recording(61.seconds, 0.5f))
|
VoicePreview(voiceMessageState = VoiceMessageState.Recording(61.seconds, List(100) { it.toFloat() / 200 }.toPersistentList()))
|
||||||
}, {
|
}, {
|
||||||
VoicePreview(voiceMessageState = VoiceMessageState.Preview(isPlaying = false))
|
VoicePreview(voiceMessageState = VoiceMessageState.Preview(isPlaying = false))
|
||||||
}, {
|
}, {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,106 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.textcomposer.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.Canvas
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.derivedStateOf
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
|
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.graphics.Brush
|
||||||
|
import androidx.compose.ui.graphics.SolidColor
|
||||||
|
import androidx.compose.ui.graphics.graphicsLayer
|
||||||
|
import androidx.compose.ui.layout.onSizeChanged
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import androidx.compose.ui.unit.DpSize
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import io.element.android.libraries.designsystem.components.media.drawWaveform
|
||||||
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
|
import io.element.android.libraries.theme.ElementTheme
|
||||||
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
import kotlinx.collections.immutable.toPersistentList
|
||||||
|
|
||||||
|
private const val DEFAULT_GRAPHICS_LAYER_ALPHA: Float = 0.99F
|
||||||
|
private val waveFormHeight = 26.dp
|
||||||
|
@Composable
|
||||||
|
fun LiveWaveformView(
|
||||||
|
levels: ImmutableList<Float>,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
brush: Brush = SolidColor(ElementTheme.colors.iconQuaternary),
|
||||||
|
lineWidth: Dp = 2.dp,
|
||||||
|
linePadding: Dp = 2.dp,
|
||||||
|
) {
|
||||||
|
var canvasSize by remember { mutableStateOf(DpSize(0.dp, 0.dp)) }
|
||||||
|
|
||||||
|
val canvasWidth by remember(levels, lineWidth, linePadding) {
|
||||||
|
derivedStateOf {
|
||||||
|
levels.size * (lineWidth.value + linePadding.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var width by remember { mutableIntStateOf(0) }
|
||||||
|
|
||||||
|
Box(contentAlignment = Alignment.CenterEnd,
|
||||||
|
modifier = modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(waveFormHeight)
|
||||||
|
.onSizeChanged { width = it.width }
|
||||||
|
) {
|
||||||
|
Canvas(
|
||||||
|
modifier = Modifier
|
||||||
|
.width(Dp(canvasWidth))
|
||||||
|
.graphicsLayer(alpha = DEFAULT_GRAPHICS_LAYER_ALPHA)
|
||||||
|
.then(modifier)
|
||||||
|
) {
|
||||||
|
canvasSize = DpSize(Dp(canvasWidth), size.height.toDp())
|
||||||
|
val countThatFitsWidth = (width.toFloat() / (lineWidth.toPx() + linePadding.toPx())).toInt()
|
||||||
|
drawWaveform(
|
||||||
|
waveformData = levels.takeLast(countThatFitsWidth).toPersistentList(),
|
||||||
|
canvasSize = canvasSize,
|
||||||
|
brush = brush,
|
||||||
|
lineWidth = lineWidth,
|
||||||
|
linePadding = linePadding,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreviewsDayNight
|
||||||
|
@Composable
|
||||||
|
internal fun LiveWaveformViewPreview() = ElementPreview {
|
||||||
|
Column {
|
||||||
|
|
||||||
|
LiveWaveformView(
|
||||||
|
levels = List(100) { it.toFloat() / 200 }.toPersistentList(),
|
||||||
|
modifier = Modifier.height(34.dp),
|
||||||
|
)
|
||||||
|
LiveWaveformView(
|
||||||
|
levels = List(40) { it.toFloat() / 40 }.toPersistentList(),
|
||||||
|
modifier = Modifier.height(34.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -20,7 +20,6 @@ import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.heightIn
|
import androidx.compose.foundation.layout.heightIn
|
||||||
|
|
@ -37,12 +36,14 @@ import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
import io.element.android.libraries.designsystem.theme.components.Text
|
import io.element.android.libraries.designsystem.theme.components.Text
|
||||||
import io.element.android.libraries.theme.ElementTheme
|
import io.element.android.libraries.theme.ElementTheme
|
||||||
import io.element.android.libraries.ui.utils.time.formatShort
|
import io.element.android.libraries.ui.utils.time.formatShort
|
||||||
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
import kotlinx.collections.immutable.toPersistentList
|
||||||
import kotlin.time.Duration
|
import kotlin.time.Duration
|
||||||
import kotlin.time.Duration.Companion.seconds
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
internal fun VoiceMessageRecording(
|
internal fun VoiceMessageRecording(
|
||||||
level: Float,
|
levels: ImmutableList<Float>,
|
||||||
duration: Duration,
|
duration: Duration,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
|
|
@ -70,28 +71,11 @@ internal fun VoiceMessageRecording(
|
||||||
|
|
||||||
Spacer(Modifier.size(20.dp))
|
Spacer(Modifier.size(20.dp))
|
||||||
|
|
||||||
// TODO Replace with waveform UI
|
LiveWaveformView(
|
||||||
DebugAudioLevel(
|
|
||||||
modifier = Modifier.weight(1f), level = level
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun DebugAudioLevel(
|
|
||||||
level: Float,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
) {
|
|
||||||
Box(
|
|
||||||
modifier = modifier
|
|
||||||
.height(26.dp)
|
|
||||||
) {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.align(Alignment.CenterEnd)
|
.height(34.dp)
|
||||||
.fillMaxWidth(level)
|
.weight(1f),
|
||||||
.background(ElementTheme.colors.iconQuaternary, shape = MaterialTheme.shapes.small)
|
levels = levels
|
||||||
.fillMaxHeight()
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -108,5 +92,5 @@ private fun RedRecordingDot(
|
||||||
@PreviewsDayNight
|
@PreviewsDayNight
|
||||||
@Composable
|
@Composable
|
||||||
internal fun VoiceMessageRecordingPreview() = ElementPreview {
|
internal fun VoiceMessageRecordingPreview() = ElementPreview {
|
||||||
VoiceMessageRecording(0.5f, 0.seconds)
|
VoiceMessageRecording(List(100) { it.toFloat() / 200 }.toPersistentList(), 0.seconds)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
package io.element.android.libraries.textcomposer.model
|
package io.element.android.libraries.textcomposer.model
|
||||||
|
|
||||||
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlin.time.Duration
|
import kotlin.time.Duration
|
||||||
|
|
||||||
sealed class VoiceMessageState {
|
sealed class VoiceMessageState {
|
||||||
|
|
@ -27,6 +28,6 @@ sealed class VoiceMessageState {
|
||||||
data object Sending: VoiceMessageState()
|
data object Sending: VoiceMessageState()
|
||||||
data class Recording(
|
data class Recording(
|
||||||
val duration: Duration,
|
val duration: Duration,
|
||||||
val level: Float,
|
val levels: ImmutableList<Float>,
|
||||||
): VoiceMessageState()
|
): VoiceMessageState()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,9 +29,9 @@ sealed class VoiceRecorderState {
|
||||||
* The recorder is currently recording.
|
* The recorder is currently recording.
|
||||||
*
|
*
|
||||||
* @property elapsedTime The elapsed time since the recording started.
|
* @property elapsedTime The elapsed time since the recording started.
|
||||||
* @property level The current audio level of the recording as a fraction of 1.
|
* @property levels The current audio levels of the recording as a fraction of 1.
|
||||||
*/
|
*/
|
||||||
data class Recording(val elapsedTime: Duration, val level: Float) : VoiceRecorderState()
|
data class Recording(val elapsedTime: Duration, val levels: List<Float>) : VoiceRecorderState()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The recorder has finished recording.
|
* The recorder has finished recording.
|
||||||
|
|
|
||||||
|
|
@ -96,13 +96,13 @@ class VoiceRecorderImpl @Inject constructor(
|
||||||
when (audio) {
|
when (audio) {
|
||||||
is Audio.Data -> {
|
is Audio.Data -> {
|
||||||
val audioLevel = audioLevelCalculator.calculateAudioLevel(audio.buffer)
|
val audioLevel = audioLevelCalculator.calculateAudioLevel(audio.buffer)
|
||||||
_state.emit(VoiceRecorderState.Recording(elapsedTime, audioLevel))
|
|
||||||
levels.add(audioLevel)
|
levels.add(audioLevel)
|
||||||
|
_state.emit(VoiceRecorderState.Recording(elapsedTime, levels))
|
||||||
encoder.encode(audio.buffer, audio.readSize)
|
encoder.encode(audio.buffer, audio.readSize)
|
||||||
}
|
}
|
||||||
is Audio.Error -> {
|
is Audio.Error -> {
|
||||||
Timber.e("Voice message error: code=${audio.audioRecordErrorCode}")
|
Timber.e("Voice message error: code=${audio.audioRecordErrorCode}")
|
||||||
_state.emit(VoiceRecorderState.Recording(elapsedTime, 0.0f))
|
_state.emit(VoiceRecorderState.Recording(elapsedTime, listOf()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue