Files
client/plans/vj-episode-playlist-unification.md
2026-04-17 16:52:51 -04:00

8.0 KiB

VJ Episode Playlist Unification — Kind 30004

Vision

The VJ page is always locked to a playlist. Everything revolves around selecting or creating a playlist, configuring the show, and playing through it. The playlist IS the show — all show metadata lives in the playlist event.

New 5-Column Layout

Col 1: PLAYLISTS       Col 2: SHOW CONFIG      Col 3: CHAT       Col 4: SEARCH        Col 5: SETLIST
- MY PLAYLISTS list    - Stream site selector   - Chat feed       - Search bar         - Active playlist tracks
- Create new playlist  - Video preview          - Chat composer   - Search results     - Reorderable, removable
- Select existing      - Stream health/viewers                    - Albums, artists    - Currently playing highlighted
- Friends playlists    - Show selector                            - Drag/click to      - Player bar at bottom
- AI Import            - Show info fields                           add to setlist     - Edits sync to kind 30004
- Playlist profile     - OBS config                                                   - This IS the queue
                       - Episode config
                       - Go Live / End Stream
                       - Auto announce settings

Key Principles

  1. You always start by selecting or creating a playlist in Col 1
  2. Col 2 show config is saved INTO the selected playlist event
  3. Col 5 Setlist = Queue — they are the same thing, locked to the selected playlist
  4. Any edit in Col 5 updates the kind 30004 playlist event
  5. Searching in Col 4 and adding songs goes directly to the setlist in Col 5

Kind 30004 Event Structure

The playlist event carries everything — tracks AND show metadata:

kind: 30004
tags:
  [d, {identifier}]                    # playlist identifier
  [title, {playlist/show title}]       # title
  [t, music]                           # standard
  [t, playlist]                        # standard
  [t, show-playlist]                   # marks as show playlist
  [description, {description}]         # optional
  [image, {url}]                       # cover image
  [banner, {url}]                      # banner
  [i, tidal:track:{id}]               # track references - standard format
  [track, {num}, {artist}, {title}, {ref}, {isoTime}]  # extended track info
  # Show-specific tags:
  [show, {slug}]                       # show slug
  [episode, {episodeId}]               # episode timestamp
  [a, 30311:{pubkey}:{slug}]           # stream coordinate
  [status, planned|live|ended]         # episode status
  [episode_description, {text}]        # episode description
  [streaming_url, {url}]              # streaming URL
  [streaming_site, {name}]            # streaming site name
  [viewers, {iso}, {count}, ...]       # viewer snapshots
content: JSON {tracks: [{id, title, artist, duration, cover, albumTitle}]}

Implementation Plan

Phase 1: Restructure the HTML layout

File: www/vj.html

1a. Rearrange columns

Current: vjStreamCard | Chat | musicSidebar | musicMain | musicQueuePanel New: musicSidebar | vjStreamCard | Chat | musicSearch | musicSetlist

  • Col 1 musicSidebar: Move to far left. Contains MY PLAYLISTS, friends, AI import, playlist profile card.
  • Col 2 vjStreamCard: Show config column. Stream site, video, show selector, show info, OBS, episode, controls, auto announce.
  • Col 3 vjChatPanel: Chat - stays the same.
  • Col 4 musicSearch: Rename from musicMain. Contains search bar, search results, album/artist detail. Remove playlist track view from here - that moves to Col 5.
  • Col 5 musicSetlist: Rename from musicQueuePanel. This is the setlist = queue. Locked to the selected playlist. Player bar at bottom.

1b. Update CSS grid

  • Reorder grid-template-columns to match new column order
  • Update VJ_COLUMN_VAR_NAMES and VJ_COLUMN_MIN_WIDTHS arrays
  • Update gutter indices

Phase 2: Make Setlist = Queue = Playlist

File: www/vj.html

2a. Lock queue to selected playlist

  • When a playlist is selected in Col 1, its tracks populate the setlist/queue in Col 5
  • Add lockedPlaylistId state variable
  • Modify renderQueue() to show the locked playlist's tracks
  • Modify queue mutation functions to also update the playlist:
    • addTrackToQueue() → also adds to playlist
    • removeFromQueue() → also removes from playlist
    • reorderQueue() → also reorders playlist
    • After each mutation, call publishPlaylistRecord() to sync to relay

2b. Playing from setlist

  • playQueueAt() already works — it plays from the queue array
  • Since queue = playlist tracks, playing works naturally
  • onTrackChanged callback triggers auto-announce as before
  • When user clicks "Add to queue" or drags from search results in Col 4, it adds to both queue and playlist
  • Remove the separate "Add to playlist" action — adding to queue IS adding to playlist

Phase 3: Publish kind 30004 with show metadata

File: www/js/vj-stream.mjs

3a. Change playlist publishing to kind 30004

  • Replace PLAYLIST_KIND = 30078 with MUSIC_PLAYLIST_KIND = 30004
  • Keep LEGACY_PLAYLIST_KIND = 30078 for reading old events
  • buildPlaylistTags() produces kind 30004 format with show extension tags
  • publishPlaylistEvent() publishes kind 30004

3b. Save show config into playlist event

  • Show title, summary, image, episode description, streaming URL, streaming site — all saved as tags on the kind 30004 event
  • When show config changes, republish the playlist event
  • When loading a playlist, extract show config from its tags

3c. Load playlist as show

  • New function: loadShowFromPlaylist(playlist)
    • Reads show tags from the playlist event
    • Populates stream config fields
    • Sets episode ID
    • Populates queue/setlist

3d. Expose playlist sync API

  • getActivePlaylist() — returns current playlist object
  • syncQueueToPlaylist() — writes queue state back to playlist
  • onSetlistChange callback — notifies vj.html when setlist changes

Phase 4: Subscription and hydration

File: www/js/vj-stream.mjs

4a. Subscribe to show playlists

  • Query kind 30004 with #t: [show-playlist] and #show: [streamDTag]
  • Also query kind 30078 for backward compat

4b. Hydrate from incoming events

  • When a kind 30004 event with show-playlist tag arrives, parse tracks and show config
  • Update setlist UI in real-time

Phase 5: Backward compatibility

5a. Read old kind 30078 events

  • handleIncomingEvent() still accepts kind 30078
  • subscribeToPlaylist() queries both kinds
  • vj-playlist.html continues to work

5b. Migration path

  • Old 30078 events are read-only — never updated
  • All new publishes use kind 30004
  • A playlist created from an old 30078 event gets a new kind 30004 identity

Files to Modify

File Changes
www/vj.html Rearrange 5-column layout, lock queue to playlist, merge queue+playlist mutations
www/js/vj-stream.mjs Publish kind 30004, save show config in playlist, load show from playlist
www/js/greyscale-playlists.mjs No changes needed

User Flow

Starting a new show

  1. Click "Create new playlist" in Col 1 → name it "Saturday Reggae Ep 42"
  2. Search for songs in Col 4 → add them to setlist in Col 5
  3. Configure show in Col 2 (stream site, OBS key, etc.)
  4. Click "Go Live" → stream starts, auto-announce begins
  5. As songs play, they get announced in chat
  6. Add more songs from search during the show
  7. Reorder upcoming songs in the setlist
  8. Click "End Stream" → playlist is complete, ready to share

Playing a pre-existing playlist

  1. Click an existing playlist in Col 1 → its tracks load into Col 5 setlist
  2. Show config loads from the playlist's tags (if it was a previous show playlist)
  3. Hit play → songs play through the setlist
  4. Optionally go live to stream it

Editing during a show

  1. Drag to reorder songs in Col 5 → playlist event updates
  2. Click X to remove a song → playlist event updates
  3. Search and add new songs from Col 4 → playlist event updates
  4. All changes are live and published