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.html → vj.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.html→www/vj.html - Update
<title>toVJ - Update all log prefixes from
template.html/music.htmltovj.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.mjsthat stream-ctrl uses but music does not:subscribe— already imported in musicpublishEvent— already imported in musicndkFetchEvents,queryCache,storeProfile— needed for post-interactions
- Add imports for
mountComposerfrompost-composer.mjs - Add imports for
initInteractions,renderPostItemfrompost-interactions.mjs
Step 3: Restructure HTML layout
- Rename
#musicAppto#vjAppor 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
#vjAppto usedisplay: 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_TAGstreamAuthorPubkey,streamDTag,streamCoordinate,latestStreamEventstreamSubId,chatSubId,chatComposer,chatComposerHostElhlsInstance,streamListenersBound,renderedChatIds,chatParticipantPubkeyscurrentNaddr,currentStreamStatusSTATS_URL,STATS_POLL_INTERVAL,viewerCount,statsIntervalId,wasStreamLive
Functions:
getTagValue— helper for reading event tagsparseCoordinate,parseStreamTargetFromUrl— URL parsingbuildStreamTags,publishStreamStatus— stream event publishingsetStreamPlayerSource— HLS player managementbuildNaddr,updateNaddrDisplay— naddr encodingupdateStreamButtonStates,updateStreamUi— UI state managementappendChatEvent— render chat messages usingrenderPostItemsubscribeToStream,subscribeToChat— Nostr subscriptionspollViewerCount,startViewerCountPolling,stopViewerCountPolling— stats pollinghandleIncomingEvent,bindStreamEventListeners— event routingmountChatComposerIfAllowed— chat input setupinitializeStreamPage— 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 ndkEventbut handle different event kinds
Step 7: Handle shared concerns
- Both pages use
initInteractions/renderPostItem— stream-ctrl needs these for chat rendering. Import them and callinitInteractionsonce 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.cssstylesheet 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
-
No ID conflicts: stream-ctrl uses IDs like
videoStream,btnGoLive,divChatFeedetc. Music uses IDs likemusicSearchInput,musicAudio,musicPlayerBaretc. No overlaps. -
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. -
Event routing: Both stream and music features listen on
window ndkEvent. The stream handler checks forkind === 30311andkind === 1311. The music handler checks forkind === 30004playlist events. No conflicts. -
Auth mode: Use
optionalauth mode from music.html since it supports public viewing with login-on-demand.