From 34d4c4ef6681de415408a34976e47574252cef49 Mon Sep 17 00:00:00 2001 From: peterino2 Date: Thu, 5 Feb 2026 23:58:08 -0800 Subject: [PATCH] added support for generating a listening link --- public/index.html | 24 +++++++++---------- public/init.js | 59 +++++++++++++++++++++++++++++++++++++++++++++++ public/queue.js | 26 +++++++++++++++++++++ routes/static.ts | 2 +- 4 files changed, 98 insertions(+), 13 deletions(-) diff --git a/public/index.html b/public/index.html index 651a4f3..e9be1c1 100644 --- a/public/index.html +++ b/public/index.html @@ -4,7 +4,7 @@ Blastoise! A very special music server - +
@@ -151,16 +151,16 @@
- - - - - - - - - - - + + + + + + + + + + + diff --git a/public/init.js b/public/init.js index 9e2c83a..e41b2f1 100644 --- a/public/init.js +++ b/public/init.js @@ -4,6 +4,57 @@ (function() { const M = window.MusicRoom; + // Check for /listen/ URL + function checkDirectTrackLink() { + const match = location.pathname.match(/^\/listen\/(.+)$/); + if (match) { + const trackId = decodeURIComponent(match[1]); + console.log("[Init] Direct track link detected:", trackId); + return trackId; + } + return null; + } + + // Play a specific track in local/desync mode + M.playDirectTrack = async function(trackId) { + // Find track in library + const track = M.library?.find(t => t.id === trackId); + if (!track) { + M.showToast("Track not found", "error"); + // Clear the URL + history.replaceState(null, "", "/"); + return; + } + + // Desync from server + M.wantSync = false; + M.synced = false; + if (M.ws) { + M.ws.close(); + M.ws = null; + } + + // Set up and play track + M.currentTrackId = trackId; + M.serverTrackDuration = track.duration; + M.setTrackTitle(track.title || track.filename); + M.loadingSegments.clear(); + + const cachedUrl = await M.loadTrackBlob(trackId); + M.audio.src = cachedUrl || M.getTrackUrl(trackId); + M.audio.currentTime = 0; + M.localTimestamp = 0; + M.audio.play().catch(() => { + M.showToast("Click to start playback"); + }); + + M.updateUI(); + M.showToast(`Playing: ${track.title || track.filename}`); + + // Clear the URL to normal + history.replaceState(null, "", "/"); + }; + // Fetch server status/config async function loadServerStatus() { try { @@ -69,6 +120,8 @@ } // Initialize the application + const directTrackId = checkDirectTrackLink(); + Promise.all([initStorage(), loadServerStatus()]).then(async () => { updateFeatureVisibility(); await M.loadLibrary(); @@ -76,5 +129,11 @@ if (M.currentUser) { M.loadChannels(); } + + // Handle direct track link after everything is loaded + if (directTrackId) { + // Small delay to ensure UI is ready + setTimeout(() => M.playDirectTrack(directTrackId), 500); + } }); })(); diff --git a/public/queue.js b/public/queue.js index 06d3372..7148c17 100644 --- a/public/queue.js +++ b/public/queue.js @@ -683,6 +683,19 @@ label: "Download", action: () => downloadTrack(trackId, track.filename) }); + + // Copy link option + menuItems.push({ + label: "🔗 Generate listening link", + action: () => { + const url = `${location.origin}/listen/${encodeURIComponent(trackId)}`; + navigator.clipboard.writeText(url).then(() => { + M.showToast("Link copied to clipboard"); + }).catch(() => { + M.showToast("Failed to copy link", "error"); + }); + } + }); } // Clear selection option (if items selected) @@ -912,6 +925,19 @@ label: "Download", action: () => downloadTrack(track.id, track.filename) }); + + // Copy link option + menuItems.push({ + label: "🔗 Generate listening link", + action: () => { + const url = `${location.origin}/listen/${encodeURIComponent(track.id)}`; + navigator.clipboard.writeText(url).then(() => { + M.showToast("Link copied to clipboard"); + }).catch(() => { + M.showToast("Failed to copy link", "error"); + }); + } + }); } // Export all cached option (if there are cached tracks) diff --git a/routes/static.ts b/routes/static.ts index d8806ec..0db7a53 100644 --- a/routes/static.ts +++ b/routes/static.ts @@ -4,7 +4,7 @@ import { PUBLIC_DIR } from "../config"; // Serve static files export async function handleStatic(path: string): Promise { - if (path === "/" || path === "/index.html") { + if (path === "/" || path === "/index.html" || path.startsWith("/listen/")) { return new Response(file(join(PUBLIC_DIR, "index.html")), { headers: { "Content-Type": "text/html" }, });