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:
Claude
2026-04-27 01:29:16 +00:00
parent 2ec19fe961
commit 3fb9f02b6d
4 changed files with 54 additions and 11 deletions
@@ -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
@@ -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(