Display kind 1 voice replies as audio waveform

This commit is contained in:
davotoula
2026-01-11 10:27:12 +01:00
parent 58b97e0695
commit d8093f64b1
2 changed files with 91 additions and 0 deletions
@@ -68,6 +68,13 @@ fun RenderTextEvent(
) {
val noteEvent = note.event ?: return
// Check if this is an audio-only event (content is just an audio URL with waveform IMeta)
val isAudioOnly = remember(noteEvent) { noteEvent.isAudioOnlyContent() }
if (isAudioOnly) {
RenderAudioFromIMeta(note, accountViewModel, nav)
return
}
val showReply by
remember(note) {
derivedStateOf {
@@ -69,8 +69,11 @@ import com.vitorpamplona.amethyst.ui.theme.Size50Modifier
import com.vitorpamplona.amethyst.ui.theme.Size75Modifier
import com.vitorpamplona.amethyst.ui.theme.VoiceHeightModifier
import com.vitorpamplona.amethyst.ui.theme.imageModifier
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.tags.hashtags.hasHashtags
import com.vitorpamplona.quartz.nip14Subject.subject
import com.vitorpamplona.quartz.nip92IMeta.imetas
import com.vitorpamplona.quartz.nipA0VoiceMessages.AudioMeta
import com.vitorpamplona.quartz.nipA0VoiceMessages.BaseVoiceEvent
@Composable
@@ -263,3 +266,84 @@ fun PlayPauseButton(controllerState: MediaControllerState) {
}
}
}
/**
* Extracts AudioMeta from an event's IMeta tags if it has audio content with waveform.
* Returns the first audio IMeta that has a waveform, or null if none found.
*/
fun Event.getAudioMetaWithWaveform(): AudioMeta? {
val audioMetas = imetas().map { AudioMeta.parse(it) }
return audioMetas.firstOrNull { meta ->
meta.waveform != null &&
meta.mimeType?.startsWith("audio/") == true
}
}
/**
* Checks if the event content is primarily an audio attachment (content is just the audio URL).
*/
fun Event.isAudioOnlyContent(): Boolean {
val audioMeta = getAudioMetaWithWaveform() ?: return false
return content.trim() == audioMeta.url
}
/**
* Renders audio with waveform for any event type that has audio IMeta attachment.
* This allows KIND 1 (TextNoteEvent) and KIND 1111 (CommentEvent) with voice
* attachments to display the same waveform UI as VoiceEvent.
*/
@Composable
fun RenderAudioFromIMeta(
note: Note,
accountViewModel: AccountViewModel,
nav: INav,
) {
val noteEvent = note.event ?: return
val audioMeta = remember(noteEvent) { noteEvent.getAudioMetaWithWaveform() } ?: return
val waveform =
remember(audioMeta) {
audioMeta.waveform?.let { WaveformData(it) }
}
val callbackUri = remember(note) { note.toNostrUri() }
Column(modifier = MaxWidthPaddingTop5dp, horizontalAlignment = Alignment.CenterHorizontally) {
Row(
verticalAlignment = Alignment.CenterVertically,
) {
GetMediaItem(
videoUri = audioMeta.url,
title = null,
artworkUri = null,
authorName = note.author?.toBestDisplayName(),
callbackUri = callbackUri,
mimeType = audioMeta.mimeType,
aspectRatio = null,
proxyPort = accountViewModel.httpClientBuilder.proxyPortForVideo(audioMeta.url),
keepPlaying = false,
waveformData = waveform,
) { mediaItem ->
GetVideoController(
mediaItem = mediaItem,
muted = false,
) { controller ->
RenderVoicePlayer(
mediaItem = mediaItem,
controllerState = controller,
waveform = waveform,
borderModifier = MaterialTheme.colorScheme.imageModifier,
accountViewModel = accountViewModel,
)
}
}
}
if (noteEvent.hasHashtags()) {
Row(Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
DisplayUncitedHashtags(noteEvent, callbackUri, accountViewModel, nav)
}
}
}
}