feat(audio-rooms): "Listen to recording" CTA for closed rooms
Wire MeetingSpaceEvent's existing RecordingTag into the note-view renderer. Closed rooms (status=CLOSED) that ship a `["recording", url]` tag get an OutlinedButton that hands the URL to the system media player via ACTION_VIEW — most users have a podcast / audio app registered for HTTPS audio URLs. Live and scheduled rooms keep the Join button. Closed rooms without a recording render no CTA — there's nothing to enter. Adds: * MeetingSpaceEvent.recording() accessor * TagArrayBuilder<MeetingSpaceEvent>.recording(url) DSL builder * ListenToRecordingButton composable in the note renderer Note: nostrnests' moq-lite refactor dropped the LiveKit-era recording HTTP surface, but the kind-30312 `recording` tag is still spec-allowed and individual hosts can populate it via out-of-band capture. This commit makes Amethyst the receiving end for any host who does.
This commit is contained in:
@@ -101,6 +101,7 @@ fun RenderMeetingSpaceEventInner(
|
||||
val summary = remember(eventUpdates) { noteEvent.summary() }
|
||||
val status = remember(eventUpdates) { noteEvent.status() }
|
||||
val starts = remember(eventUpdates) { noteEvent.starts() }
|
||||
val recording = remember(eventUpdates) { noteEvent.recording() }
|
||||
val participants = remember(eventUpdates) { noteEvent.participants() }
|
||||
|
||||
Row(
|
||||
@@ -160,17 +161,20 @@ fun RenderMeetingSpaceEventInner(
|
||||
|
||||
RenderParticipants(participants, accountViewModel, nav)
|
||||
|
||||
// In-feed Join button — closes the deep-link loop. A
|
||||
// `nostr:naddr1...` for a kind-30312 lands here through
|
||||
// MainActivity's URI handler → Route.Note(addressTag) → this
|
||||
// renderer; without a button the user has no path into the
|
||||
// audio room. JoinAudioRoomButton hides itself when the
|
||||
// event lacks service / endpoint / d-tag.
|
||||
if (status != MeetingSpaceStatusTag.STATUS.CLOSED) {
|
||||
// In-feed CTA. Live / scheduled rooms get a Join button; closed
|
||||
// rooms with a recording get a "Listen to recording" button so
|
||||
// audience members who missed the live session can listen back.
|
||||
// Closed rooms without a recording stay button-less — there's
|
||||
// nothing to enter.
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().padding(top = 8.dp),
|
||||
horizontalArrangement = Arrangement.End,
|
||||
) {
|
||||
if (status == MeetingSpaceStatusTag.STATUS.CLOSED) {
|
||||
recording?.let {
|
||||
ListenToRecordingButton(url = it)
|
||||
}
|
||||
} else {
|
||||
com.vitorpamplona.amethyst.ui.screen.loggedIn.audiorooms.room.JoinAudioRoomButton(
|
||||
event = noteEvent,
|
||||
accountViewModel = accountViewModel,
|
||||
@@ -179,6 +183,29 @@ fun RenderMeetingSpaceEventInner(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hand off a kind-30312 `recording` URL to the system media
|
||||
* player via ACTION_VIEW. Most users have a podcast / audio app
|
||||
* registered for `https://...mp3`-style URLs; the system picker
|
||||
* lets them choose. Stays out of the in-app audio path on
|
||||
* purpose — Amethyst doesn't ship its own audio player surface.
|
||||
*/
|
||||
@Composable
|
||||
private fun ListenToRecordingButton(url: String) {
|
||||
val context = androidx.compose.ui.platform.LocalContext.current
|
||||
androidx.compose.material3.OutlinedButton(onClick = {
|
||||
runCatching {
|
||||
context.startActivity(
|
||||
android.content
|
||||
.Intent(android.content.Intent.ACTION_VIEW, android.net.Uri.parse(url))
|
||||
.addFlags(android.content.Intent.FLAG_ACTIVITY_NEW_TASK),
|
||||
)
|
||||
}
|
||||
}) {
|
||||
Text(stringRes(R.string.audio_room_listen_to_recording))
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun RenderMeetingRoomEvent(
|
||||
baseNote: Note,
|
||||
|
||||
@@ -503,6 +503,7 @@
|
||||
<string name="audio_room_connecting_handshake">Negotiating audio</string>
|
||||
<string name="audio_room_connected">Audio connected</string>
|
||||
<string name="audio_room_reconnecting">Reconnecting…</string>
|
||||
<string name="audio_room_listen_to_recording">Listen to recording</string>
|
||||
<string name="audio_room_audio_failed">Audio failed: %1$s</string>
|
||||
<string name="audio_room_audio_unavailable">Audio is not available for this room</string>
|
||||
<string name="audio_room_talk">Talk</string>
|
||||
|
||||
+9
@@ -93,6 +93,15 @@ class MeetingSpaceEvent(
|
||||
*/
|
||||
fun font() = tags.firstNotNullOfOrNull(com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.tags.FontTag::parse)
|
||||
|
||||
/**
|
||||
* Optional URL to a post-room recording. Hosts that record a
|
||||
* room re-publish the kind-30312 with `status=closed` and
|
||||
* `["recording", url]` so audience members who missed the live
|
||||
* session can listen back. Returns null when no recording is
|
||||
* available.
|
||||
*/
|
||||
fun recording() = tags.firstNotNullOfOrNull(com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.tags.RecordingTag::parse)
|
||||
|
||||
/**
|
||||
* Scheduled start time as unix seconds, only meaningful when
|
||||
* [status] is [StatusTag.STATUS.PLANNED]. Returns null on
|
||||
|
||||
+6
@@ -61,6 +61,12 @@ fun TagArrayBuilder<MeetingSpaceEvent>.font(
|
||||
.assemble(family, url),
|
||||
)
|
||||
|
||||
fun TagArrayBuilder<MeetingSpaceEvent>.recording(url: String) =
|
||||
addUnique(
|
||||
com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.tags.RecordingTag
|
||||
.assemble(url),
|
||||
)
|
||||
|
||||
fun TagArrayBuilder<MeetingSpaceEvent>.relays(urls: List<NormalizedRelayUrl>) = addUnique(RelayListTag.assemble(urls))
|
||||
|
||||
fun TagArrayBuilder<MeetingSpaceEvent>.participant(
|
||||
|
||||
Reference in New Issue
Block a user