@ -79,29 +79,30 @@ fun MessageBubble(message: Message, onPlayVoice: (String, () -> Unit, () -> Unit
}
}
}
private data class PlayState ( val playId : Int , val elapsedMs : Int , val progress : Float )
@Composable
private fun VoiceBubble ( message : Message , textColor : Color ,
onPlay : ( String , ( ) -> Unit , ( ) -> Unit ) -> Unit ,
onSeek : ( String , Int , ( ) -> Unit ) -> Unit ) {
val wf = message . waveform // List<Float> 0..1, 100 elements
val wf = message . waveform
val wfCount = wf . size
val durMs = message . voiceDurationMs
var playing by remember { mutableStateOf ( false ) }
var paused by remember { mutableStateOf ( false ) }
var seekVersion by remember { mutableStateOf ( 0 ) }
var elapsedMs by remember { mutableStateOf ( 0 ) }
var progress by remember { mutableStateOf ( 0f ) }
var state by remember { mutableStateOf ( PlayState ( 0 , 0 , 0f ) ) }
val onDone = remember { { playing = false ; paused = false ; elapsedMs = 0 ; progress = 0f } }
val onDone = remember { { playing = false ; paused = false ; state = PlayState ( 0 , 0 , 0f ) } }
val onPaused = remember { { paused = true ; playing = false } }
/* elapsed timer while playing */
LaunchedEffect ( playing , seekVersion ) {
/* elapsed timer while playing — keyed on atomic playId */
LaunchedEffect ( state . playId ) {
if ( playing && ! paused ) {
val start = System . currentTimeMillis ( ) - elapsedMs
val start = System . currentTimeMillis ( ) - state . elapsedMs
while ( playing && ! paused ) {
elapsedMs = ( System . currentTimeMillis ( ) - start ) . toInt ( ) . coerceIn ( 0 , durMs )
if ( durMs > 0 ) progress = ( elapsedMs . toFloat ( ) / durMs . toFloat ( ) ) . coerceIn ( 0f , 1f )
val ms = ( System . currentTimeMillis ( ) - start ) . toInt ( ) . coerceIn ( 0 , durMs )
val frac = if ( durMs > 0 ) ms . toFloat ( ) / durMs . toFloat ( ) else 0f
state = state . copy ( elapsedMs = ms , progress = frac . coerceIn ( 0f , 1f ) )
delay ( 80 )
}
}
@ -117,7 +118,7 @@ private fun VoiceBubble(message: Message, textColor: Color,
if ( playing && ! paused ) {
onPlay ( message . filePath , onDone , onPaused )
} else {
seekVersion ++
state = state . copy ( playId = state . playId + 1 )
playing = true ; paused = false
onPlay ( message . filePath , onDone , onPaused )
}
@ -138,8 +139,8 @@ private fun VoiceBubble(message: Message, textColor: Color,
if ( message . filePath . isEmpty ( ) ) return @detectTapGestures
val seekFrac = ( offset . x / size . width ) . coerceIn ( 0f , 1f )
val seekMs = ( seekFrac * durMs ) . toInt ( )
seekVersion ++
playing = true ; paused = false ; elapsedMs = seekMs ; progress = seekFrac
state = PlayState ( state . playId + 1 , seekMs , seekFrac )
playing = true ; paused = false
onSeek ( message . filePath , seekMs , onDone )
}
}
@ -148,7 +149,7 @@ private fun VoiceBubble(message: Message, textColor: Color,
val barW = size . width / wfCount
val ph = size . height
val showCursor = playing || paused
val playedIdx = if ( showCursor && progress > 0f ) ( progress * wfCount ) . toInt ( ) else 0
val playedIdx = if ( showCursor && state . progress > 0f ) ( state . progress * wfCount ) . toInt ( ) else 0
for ( i in 0 until wfCount ) {
val level = wf [ i ] . coerceIn ( 0f , 1f )
val barH = ( 2f + level * ( ph - 2f ) ) . coerceAtMost ( ph )
@ -161,8 +162,8 @@ private fun VoiceBubble(message: Message, textColor: Color,
size = Size ( barW * 0.75f , barH )
)
}
if ( showCursor && progress > 0f && progress < 1f ) {
val cursorX = progress * size . width
if ( showCursor && state . progress > 0f && state . progress < 1f ) {
val cursorX = state . progress * size . width
drawLine ( Color ( 0xFFFFD600 ) , Offset ( cursorX , 0f ) , Offset ( cursorX , ph ) , strokeWidth = 2.5f )
}
}
@ -179,7 +180,7 @@ private fun VoiceBubble(message: Message, textColor: Color,
verticalAlignment = Alignment . Bottom
) {
Text (
if ( playing || paused ) " ${formatDurationMs(elapsedMs)} / ${formatDurationMs(durMs)} "
if ( playing || paused ) " ${formatDurationMs(state. elapsedMs)} / ${formatDurationMs(durMs)} "
else formatDurationMs ( durMs ) ,
fontSize = 12. sp ,
color = textColor . copy ( alpha = 0.55f )