added support for generating a listening link

This commit is contained in:
peterino2 2026-02-05 23:58:08 -08:00
parent adf53cadaa
commit 34d4c4ef66
4 changed files with 98 additions and 13 deletions

View File

@ -4,7 +4,7 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Blastoise! A very special music server</title> <title>Blastoise! A very special music server</title>
<link rel="stylesheet" href="styles.css?v=20"> <link rel="stylesheet" href="/styles.css?v=20">
</head> </head>
<body> <body>
<div id="app"> <div id="app">
@ -151,16 +151,16 @@
<div id="toast-history-list"></div> <div id="toast-history-list"></div>
</div> </div>
</div> </div>
<script src="trackStorage.js"></script> <script src="/trackStorage.js"></script>
<script src="core.js"></script> <script src="/core.js"></script>
<script src="utils.js"></script> <script src="/utils.js"></script>
<script src="audioCache.js"></script> <script src="/audioCache.js"></script>
<script src="channelSync.js"></script> <script src="/channelSync.js"></script>
<script src="ui.js"></script> <script src="/ui.js"></script>
<script src="queue.js"></script> <script src="/queue.js"></script>
<script src="controls.js"></script> <script src="/controls.js"></script>
<script src="auth.js"></script> <script src="/auth.js"></script>
<script src="upload.js"></script> <script src="/upload.js"></script>
<script src="init.js"></script> <script src="/init.js"></script>
</body> </body>
</html> </html>

View File

@ -4,6 +4,57 @@
(function() { (function() {
const M = window.MusicRoom; const M = window.MusicRoom;
// Check for /listen/<trackId> 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 // Fetch server status/config
async function loadServerStatus() { async function loadServerStatus() {
try { try {
@ -69,6 +120,8 @@
} }
// Initialize the application // Initialize the application
const directTrackId = checkDirectTrackLink();
Promise.all([initStorage(), loadServerStatus()]).then(async () => { Promise.all([initStorage(), loadServerStatus()]).then(async () => {
updateFeatureVisibility(); updateFeatureVisibility();
await M.loadLibrary(); await M.loadLibrary();
@ -76,5 +129,11 @@
if (M.currentUser) { if (M.currentUser) {
M.loadChannels(); M.loadChannels();
} }
// Handle direct track link after everything is loaded
if (directTrackId) {
// Small delay to ensure UI is ready
setTimeout(() => M.playDirectTrack(directTrackId), 500);
}
}); });
})(); })();

View File

@ -683,6 +683,19 @@
label: "Download", label: "Download",
action: () => downloadTrack(trackId, track.filename) 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) // Clear selection option (if items selected)
@ -912,6 +925,19 @@
label: "Download", label: "Download",
action: () => downloadTrack(track.id, track.filename) 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) // Export all cached option (if there are cached tracks)

View File

@ -4,7 +4,7 @@ import { PUBLIC_DIR } from "../config";
// Serve static files // Serve static files
export async function handleStatic(path: string): Promise<Response | null> { export async function handleStatic(path: string): Promise<Response | null> {
if (path === "/" || path === "/index.html") { if (path === "/" || path === "/index.html" || path.startsWith("/listen/")) {
return new Response(file(join(PUBLIC_DIR, "index.html")), { return new Response(file(join(PUBLIC_DIR, "index.html")), {
headers: { "Content-Type": "text/html" }, headers: { "Content-Type": "text/html" },
}); });