added support for generating a listening link
This commit is contained in:
parent
adf53cadaa
commit
34d4c4ef66
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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" },
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue