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

9.8 KiB

VJ Page Plan — vj.html

Overview

Merge stream-ctrl.html and music.html into a single 5-column VJ dashboard at vj.html. The page is designed for wide monitors and serves as a live DJ streaming control center: manage the stream, interact with chat, and build/play music playlists — all in one view.

Layout

┌──────────────┬──────────────┬──────────────┬──────────────┬──────────────┐
│   Column 1   │   Column 2   │   Column 3   │   Column 4   │   Column 5   │
│              │              │              │              │              │
│   STREAM     │   CHAT       │   PLAYLIST   │   SEARCH /   │   QUEUE      │
│   CARD       │              │   SIDEBAR    │   RESULTS    │   PANEL      │
│              │              │              │              │              │
│ - Video HLS  │ - Chat input │ - My lists   │ - Search bar │ - Queue list │
│ - Play/Pause │ - Chat feed  │ - Friend     │ - Track/     │ - Player bar │
│ - Mute       │   kind 1311  │   lists      │   album/     │ - Playback   │
│ - Status     │              │ - AI import  │   artist     │   mode       │
│ - Title      │              │ - Create new │   results    │              │
│ - Summary    │              │ - Viewer     │ - Playlist   │              │
│ - Image URL  │              │   pubkey     │   profile    │              │
│ - Stream URL │              │   loader     │   card       │              │
│ - Go Live    │              │              │              │              │
│ - End Stream │              │              │              │              │
│ - Save Plan  │              │              │              │              │
│ - Copy naddr │              │              │              │              │
│ - Viewer cnt │              │              │              │              │
└──────────────┴──────────────┴──────────────┴──────────────┴──────────────┘

Column widths

Column Content Width
1 Stream card 320px fixed
2 Chat 280px fixed
3 Playlist sidebar 280px fixed — same as music.html
4 Search / results 1fr — fills remaining space
5 Queue panel 300px fixed — same as music.html

Base File

Copy music.htmlvj.html because it is the more complex page with ~5,333 lines of code including the full music player, playlist management, search, queue, drag-and-drop, AI import, and share features.

Architecture Diagram

graph TD
    subgraph vj.html
        A[Column 1: Stream Card]
        B[Column 2: Chat]
        C[Column 3: Playlist Sidebar]
        D[Column 4: Search/Results]
        E[Column 5: Queue Panel]
    end

    subgraph From stream-ctrl.html
        S1[HLS Video Player]
        S2[Stream Metadata Inputs]
        S3[Stream Control Buttons]
        S4[Viewer Count Polling]
        S5[NIP-53 Chat - kind 1311]
        S6[Chat Composer]
        S7[Stream Event Subscriptions]
    end

    subgraph From music.html
        M1[Greyscale Music API]
        M2[SimplePlayer Audio]
        M3[Playlist CRUD + Nostr Publish]
        M4[Search - tracks/albums/artists]
        M5[Queue Management]
        M6[AI Import]
        M7[Share Modal]
    end

    S1 --> A
    S2 --> A
    S3 --> A
    S4 --> A
    S5 --> B
    S6 --> B
    S7 --> B

    M3 --> C
    M6 --> C
    M4 --> D
    M5 --> E
    M2 --> E
    M1 --> D
    M7 --> D

Implementation Steps

Step 1: Copy base file

  • Copy www/music.htmlwww/vj.html
  • Update <title> to VJ
  • Update all log prefixes from template.html / music.html to vj.html

Step 2: Add stream-ctrl dependencies

  • Add <script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script> in <head>
  • Add <link rel="stylesheet" href="./css/post-composer.css" /> in <head>
  • Add additional imports from init-ndk.mjs that stream-ctrl uses but music does not:
    • subscribe — already imported in music
    • publishEvent — already imported in music
    • ndkFetchEvents, queryCache, storeProfile — needed for post-interactions
  • Add imports for mountComposer from post-composer.mjs
  • Add imports for initInteractions, renderPostItem from post-interactions.mjs

Step 3: Restructure HTML layout

  • Rename #musicApp to #vjApp or wrap it in a new 5-column container
  • Add Column 1 HTML before the existing music columns:
    <aside id="vjStreamCard">
      <video id="videoStream" playsinline muted></video>
      <div id="divVideoControls">...</div>
      <input id="inputStreamTitle" ... />
      <textarea id="inputStreamSummary" ... />
      <input id="inputStreamingUrl" ... />
      <input id="inputStreamImage" ... />
      <div id="divStreamControls">
        <button id="btnSaveStream">Save Planned</button>
        <button id="btnGoLive">Go Live</button>
        <button id="btnEndStream">End Stream</button>
        <button id="btnCopyNaddr">Copy naddr</button>
      </div>
      <span id="spanStreamHealth">STATUS: OFFLINE</span>
      <span id="divViewerCount">TOTAL: — | HLS: — | RTMP: —</span>
    </aside>
    
  • Add Column 2 HTML:
    <aside id="vjChatPanel">
      <div class="musicSidebarTitle">CHAT</div>
      <div id="divChatComposer"></div>
      <div id="divChatFeed"></div>
    </aside>
    
  • Columns 3-5 remain the existing #musicSidebar, #musicMain, #musicQueuePanel

Step 4: Add CSS for 5-column layout

  • Change #vjApp to use display: flex; flex-direction: row; gap: 10px;
  • Column 1 #vjStreamCard: width: 320px; min-width: 280px;
  • Column 2 #vjChatPanel: width: 280px; min-width: 220px;
  • Columns 3-5 keep their existing widths from music.html
  • Style the stream card inputs and buttons to match the existing music UI aesthetic
  • Style the chat feed to scroll vertically within its column

Step 5: Merge stream-ctrl JavaScript

Port these functions/state from stream-ctrl.html into the vj.html module script:

State variables:

  • STREAM_KIND, CHAT_KIND, DEFAULT_STREAM_D_TAG
  • streamAuthorPubkey, streamDTag, streamCoordinate, latestStreamEvent
  • streamSubId, chatSubId, chatComposer, chatComposerHostEl
  • hlsInstance, streamListenersBound, renderedChatIds, chatParticipantPubkeys
  • currentNaddr, currentStreamStatus
  • STATS_URL, STATS_POLL_INTERVAL, viewerCount, statsIntervalId, wasStreamLive

Functions:

  • getTagValue — helper for reading event tags
  • parseCoordinate, parseStreamTargetFromUrl — URL parsing
  • buildStreamTags, publishStreamStatus — stream event publishing
  • setStreamPlayerSource — HLS player management
  • buildNaddr, updateNaddrDisplay — naddr encoding
  • updateStreamButtonStates, updateStreamUi — UI state management
  • appendChatEvent — render chat messages using renderPostItem
  • subscribeToStream, subscribeToChat — Nostr subscriptions
  • pollViewerCount, startViewerCountPolling, stopViewerCountPolling — stats polling
  • handleIncomingEvent, bindStreamEventListeners — event routing
  • mountChatComposerIfAllowed — chat input setup
  • initializeStreamPage — stream page initialization + button wiring

Step 6: Wire up initialization

In the main() IIFE, after initMusicFeature():

  • Call initializeStreamPage() to set up stream controls, subscriptions, and chat
  • The stream event listener must coexist with the music NDK event listener — both listen on window ndkEvent but handle different event kinds

Step 7: Handle shared concerns

  • Both pages use initInteractions / renderPostItem — stream-ctrl needs these for chat rendering. Import them and call initInteractions once during auth setup.
  • Both pages share the same footer, sidenav, hamburger, relay UI, blossom UI, AI section — these are already in the music.html base, no duplication needed.
  • The post-composer.css stylesheet is needed for the chat composer styling.

Step 8: Remove announcement composer

The user wants NIP-53 chat only in Column 2. Do not port the divAnnouncementComposer or mountAnnouncementComposer from stream-ctrl.html.

Step 9: Header title

Replace the static header text with the stream title input from stream-ctrl.html, or keep it simple with a "VJ" title. The stream title input can live in Column 1 instead.

Future Integration Ideas — not in scope for v1

  • Auto-post now-playing: when a new song plays in the music queue, automatically publish a kind-1 note or update the stream title
  • Update stream summary with current track info
  • Visual effects / OBS integration
  • Strudel live coding integration

Key Technical Notes

  1. No ID conflicts: stream-ctrl uses IDs like videoStream, btnGoLive, divChatFeed etc. Music uses IDs like musicSearchInput, musicAudio, musicPlayerBar etc. No overlaps.

  2. Two audio/video elements: The page will have both <video id="videoStream"> for the HLS stream preview and <audio id="musicAudio"> for music playback. These are independent — the video is the stream monitor, the audio is the DJ music source.

  3. Event routing: Both stream and music features listen on window ndkEvent. The stream handler checks for kind === 30311 and kind === 1311. The music handler checks for kind === 30004 playlist events. No conflicts.

  4. Auth mode: Use optional auth mode from music.html since it supports public viewing with login-on-demand.