diff --git a/client/src/routes/Music.svelte b/client/src/routes/Music.svelte index bc5d707..d38591b 100644 --- a/client/src/routes/Music.svelte +++ b/client/src/routes/Music.svelte @@ -8,6 +8,20 @@ let loading = true; let error = null; + // Player state + let currentTrack = null; + let isPlaying = false; + let currentTime = 0; + let duration = 0; + let volume = 0.8; + let isMuted = false; + let previousVolume = 0.8; + let audioEl; + let progressInterval; + + // View mode + let viewMode = "list"; // "list" or "grid" + async function loadMusic() { loading = true; error = null; @@ -39,15 +53,21 @@ if (!item.thumbnail) return null; const token = localStorage.getItem("token"); const separator = item.thumbnail.includes("?") ? "&" : "?"; - // Cache buster eklemiyoruz; server Cache-Control/ETag ile tarayıcı cache'i kullanacak return `${API}${item.thumbnail}${separator}token=${token}`; } function formatDuration(seconds) { - if (!Number.isFinite(seconds) || seconds <= 0) return ""; + if (!Number.isFinite(seconds) || seconds <= 0) return "0:00"; const mins = Math.floor(seconds / 60); const secs = Math.floor(seconds % 60); - return `${String(mins).padStart(2, "0")}:${String(secs).padStart(2, "0")}`; + return `${mins}:${String(secs).padStart(2, "0")}`; + } + + function formatTime(seconds) { + if (!Number.isFinite(seconds) || seconds <= 0) return "0:00"; + const mins = Math.floor(seconds / 60); + const secs = Math.floor(seconds % 60); + return `${mins}:${String(secs).padStart(2, "0")}`; } function sourceLabel(item) { @@ -55,39 +75,302 @@ return "Music"; } + // Player functions + function playTrack(item, index) { + if (currentTrack?.id === item.id) { + togglePlay(); + return; + } + + currentTrack = { ...item, index }; + currentTime = 0; + duration = item.mediaInfo?.format?.duration || 0; + isPlaying = true; + + if (audioEl) { + audioEl.src = streamURL(item); + audioEl.play().catch((err) => { + console.error("Play error:", err); + isPlaying = false; + }); + } + } + + function togglePlay() { + if (!audioEl) return; + if (isPlaying) { + audioEl.pause(); + } else { + audioEl.play().catch((err) => { + console.error("Play error:", err); + }); + } + isPlaying = !isPlaying; + } + + function playNext() { + if (!currentTrack || items.length === 0) return; + const currentIndex = items.findIndex((i) => i.id === currentTrack.id); + const nextIndex = (currentIndex + 1) % items.length; + playTrack(items[nextIndex], nextIndex); + } + + function playPrevious() { + if (!currentTrack || items.length === 0) return; + const currentIndex = items.findIndex((i) => i.id === currentTrack.id); + const prevIndex = (currentIndex - 1 + items.length) % items.length; + playTrack(items[prevIndex], prevIndex); + } + + function seek(e) { + if (!audioEl || !duration) return; + const percent = parseFloat(e.target.value); + currentTime = (percent / 100) * duration; + audioEl.currentTime = currentTime; + } + + function setVolume(e) { + volume = parseFloat(e.target.value); + if (audioEl) { + audioEl.volume = volume; + } + isMuted = volume === 0; + } + + function toggleMute() { + if (isMuted) { + volume = previousVolume; + isMuted = false; + } else { + previousVolume = volume; + volume = 0; + isMuted = true; + } + if (audioEl) { + audioEl.volume = volume; + } + } + + function handleTimeUpdate() { + if (audioEl) { + currentTime = audioEl.currentTime; + duration = audioEl.duration || 0; + } + } + + function handleLoadedMetadata() { + if (audioEl) { + duration = audioEl.duration || 0; + } + } + + function handleEnded() { + playNext(); + } + + function handlePlay() { + isPlaying = true; + } + + function handlePause() { + isPlaying = false; + } + + function startProgressInterval() { + if (progressInterval) clearInterval(progressInterval); + progressInterval = setInterval(() => { + if (isPlaying && audioEl) { + currentTime = audioEl.currentTime; + } + }, 100); + } + onMount(() => { loadMusic(); + startProgressInterval(); + + return () => { + if (progressInterval) clearInterval(progressInterval); + if (audioEl) { + audioEl.pause(); + audioEl.src = ""; + } + }; });
+
-
+

Music

{#if !loading && !error} {items.length} Songs {/if}
- +
+
+ + +
+ +
- {#if loading} -
Yükleniyor…
- {:else if error} -
{error}
- {:else if !items.length} -
Henüz müzik videosu yok.
- {:else} -
- {#each items as item, idx (item.id)} -
-
{String(idx + 1).padStart(2, "0")}
-
- {#if thumbnailURL(item)} - {item.title} + +
+ {#if loading} +
+
+

Yükleniyor…

+
+ {:else if error} +
+ +

{error}

+
+ {:else if !items.length} +
+ +

Henüz müzik dosyası yok.

+
+ {:else if viewMode === 'list'} + +
+
+
#
+
+
Başlık
+
Kaynak
+
+
+
+ {#each items as item, idx (item.id)} +
playTrack(item, idx)} + on:dblclick={() => { + if (currentTrack?.id === item.id) togglePlay(); + }} + > +
+ {#if currentTrack?.id === item.id && isPlaying} +
+ + + +
+ {:else} + {String(idx + 1).padStart(2, "0")} + {/if} +
+
+ {#if thumbnailURL(item)} + {item.title} + {:else} +
+ +
+ {/if} +
+
+
{cleanFileName(item.title)}
+
{sourceLabel(item)}
+
+
+ {formatDuration(item.mediaInfo?.format?.duration || item.duration)} +
+
+ + + + +
+
+ {/each} +
+ {:else} + +
+ {#each items as item, idx (item.id)} +
playTrack(item, idx)} + > +
+ {#if thumbnailURL(item)} + {item.title} + {:else} +
+ +
+ {/if} +
+ +
+ {#if currentTrack?.id === item.id && isPlaying} +
+ +
+ {/if} +
+
+
{cleanFileName(item.title)}
+
{sourceLabel(item)}
+
+ {formatDuration(item.mediaInfo?.format?.duration || item.duration)} +
+
+
+ {/each} +
+ {/if} +
+ + + {#if currentTrack} +
+ + +
+ +
+
+ {#if thumbnailURL(currentTrack)} + {currentTrack.title} {:else}
@@ -95,191 +378,925 @@ {/if}
-
{cleanFileName(item.title)}
-
{sourceLabel(item)}
-
-
- {formatDuration(item.mediaInfo?.format?.duration || item.duration)} -
-
- - - - - - +
{cleanFileName(currentTrack.title)}
+
{sourceLabel(currentTrack)}
+
- {/each} + + +
+ + + +
+ + +
+ {formatTime(currentTime)} +
+ +
+ {formatTime(duration)} +
+ + +
+
+ +
+ +
+
+ + + +
+
{/if}
diff --git a/server/server.js b/server/server.js index 3026073..4c1ee58 100644 --- a/server/server.js +++ b/server/server.js @@ -6575,7 +6575,8 @@ function collectMusicEntries() { ? `https://www.youtube.com/watch?v=${fileMeta.youtube.videoId}` : null, thumbnail, - categories: metadata?.categories || fileMeta?.categories || null + categories: metadata?.categories || fileMeta?.categories || null, + mediaInfo: fileMeta?.mediaInfo || null }); } entries.sort((a, b) => (b.added || 0) - (a.added || 0));