feat(music): mini player ekle

Müzik çalar durumunu yönetmek için global store oluştur.
Özel bir mini player bileşeni ile çalma listesi ve kontrolleri ekle.
Müzik çaların uygulama genelinde kalıcı olmasını sağla.
This commit is contained in:
2026-01-18 01:51:15 +03:00
parent c945458a81
commit d5d9184872
4 changed files with 510 additions and 153 deletions

View File

@@ -1,24 +1,25 @@
<script>
import { onMount, tick } from "svelte";
import { onMount } from "svelte";
import { API, apiFetch, withToken } from "../utils/api.js";
import { musicCount } from "../stores/musicStore.js";
import { cleanFileName } from "../utils/filename.js";
import {
musicPlayer,
setQueue,
playTrack as playFromStore,
togglePlay,
playNext,
playPrevious,
seekToPercent,
setVolume,
toggleMute,
stopPlayback
} from "../stores/musicPlayerStore.js";
let items = [];
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
const VIEW_MODE_STORAGE_KEY = "musicViewMode";
let viewMode = "list"; // "list" or "grid"
@@ -48,6 +49,7 @@
const list = await resp.json();
items = Array.isArray(list) ? list : [];
musicCount.set(items.length);
setQueue(items);
} catch (err) {
console.error("Music load error:", err);
items = [];
@@ -90,113 +92,23 @@
}
// Player functions
async function playTrack(item, index) {
if (currentTrack?.id === item.id) {
function playTrack(item, index) {
if (!item) return;
const current = $musicPlayer.currentTrack;
if (current?.id === item.id) {
togglePlay();
return;
}
currentTrack = { ...item, index };
currentTime = 0;
duration = item.mediaInfo?.format?.duration || 0;
isPlaying = true;
await tick();
if (!audioEl) return;
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);
playFromStore(item, index, items);
}
function seek(e) {
if (!audioEl || !duration) return;
const percent = parseFloat(e.target.value);
currentTime = (percent / 100) * duration;
audioEl.currentTime = currentTime;
seekToPercent(percent);
}
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);
function setPlayerVolume(e) {
setVolume(e.target.value);
}
function setViewMode(mode) {
@@ -207,15 +119,6 @@
onMount(() => {
loadViewMode();
loadMusic();
startProgressInterval();
return () => {
if (progressInterval) clearInterval(progressInterval);
if (audioEl) {
audioEl.pause();
audioEl.src = "";
}
};
});
</script>
@@ -281,14 +184,14 @@
</div>
{#each items as item, idx (item.id)}
<div
class="music-row {currentTrack?.id === item.id ? 'playing' : ''}"
class="music-row {$musicPlayer.currentTrack?.id === item.id ? 'playing' : ''}"
on:click={() => playTrack(item, idx)}
on:dblclick={() => {
if (currentTrack?.id === item.id) togglePlay();
if ($musicPlayer.currentTrack?.id === item.id) togglePlay();
}}
>
<div class="row-index">
{#if currentTrack?.id === item.id && isPlaying}
{#if $musicPlayer.currentTrack?.id === item.id && $musicPlayer.isPlaying}
<div class="playing-indicator">
<span class="bar bar-1"></span>
<span class="bar bar-2"></span>
@@ -340,7 +243,7 @@
<div class="music-grid">
{#each items as item, idx (item.id)}
<div
class="music-card {currentTrack?.id === item.id ? 'playing' : ''}"
class="music-card {$musicPlayer.currentTrack?.id === item.id ? 'playing' : ''}"
on:click={() => playTrack(item, idx)}
>
<div class="card-thumb">
@@ -356,7 +259,7 @@
<i class="fa-solid fa-play"></i>
</button>
</div>
{#if currentTrack?.id === item.id && isPlaying}
{#if $musicPlayer.currentTrack?.id === item.id && $musicPlayer.isPlaying}
<div class="playing-badge">
<i class="fa-solid fa-volume-high"></i>
</div>
@@ -378,23 +281,17 @@
</div>
<!-- Bottom Player Bar -->
{#if currentTrack}
{#if $musicPlayer.currentTrack}
<div class="player-bar">
<audio
bind:this={audioEl}
on:timeupdate={handleTimeUpdate}
on:loadedmetadata={handleLoadedMetadata}
on:ended={handleEnded}
on:play={handlePlay}
on:pause={handlePause}
></audio>
<div class="player-content">
<!-- Track Info -->
<div class="player-track">
<div class="track-thumb">
{#if thumbnailURL(currentTrack)}
<img src={thumbnailURL(currentTrack)} alt={currentTrack.title} />
{#if thumbnailURL($musicPlayer.currentTrack)}
<img
src={thumbnailURL($musicPlayer.currentTrack)}
alt={$musicPlayer.currentTrack.title}
/>
{:else}
<div class="thumb-placeholder">
<i class="fa-solid fa-music"></i>
@@ -402,11 +299,15 @@
{/if}
</div>
<div class="track-info">
<div class="track-name">{cleanFileName(currentTrack.title)}</div>
<div class="track-source">{sourceLabel(currentTrack)}</div>
<div class="track-name">
{cleanFileName($musicPlayer.currentTrack.title)}
</div>
<div class="track-source">
{sourceLabel($musicPlayer.currentTrack)}
</div>
</div>
<button class="like-btn" title="Beğen">
<i class="fa-regular fa-heart"></i>
<button class="like-btn" title="Kapat" on:click={stopPlayback}>
<i class="fa-solid fa-xmark"></i>
</button>
</div>
@@ -418,9 +319,9 @@
<button
class="control-btn play-pause-btn"
on:click={togglePlay}
title={isPlaying ? "Durdaklat" : "Oynat"}
title={$musicPlayer.isPlaying ? "Durdaklat" : "Oynat"}
>
{#if isPlaying}
{#if $musicPlayer.isPlaying}
<i class="fa-solid fa-pause"></i>
{:else}
<i class="fa-solid fa-play"></i>
@@ -433,18 +334,22 @@
<!-- Progress -->
<div class="player-progress">
<span class="time-current">{formatTime(currentTime)}</span>
<span class="time-current">
{formatTime($musicPlayer.currentTime)}
</span>
<div class="progress-container">
<input
type="range"
min="0"
max="100"
value={(currentTime / duration) * 100 || 0}
value={
($musicPlayer.currentTime / $musicPlayer.duration) * 100 || 0
}
on:input={seek}
class="progress-slider"
/>
</div>
<span class="time-total">{formatTime(duration)}</span>
<span class="time-total">{formatTime($musicPlayer.duration)}</span>
</div>
<!-- Volume & Extra -->
@@ -455,11 +360,11 @@
on:click={toggleMute}
title="Ses"
>
{#if isMuted}
{#if $musicPlayer.isMuted}
<i class="fa-solid fa-volume-xmark"></i>
{:else if volume < 0.3}
{:else if $musicPlayer.volume < 0.3}
<i class="fa-solid fa-volume-off"></i>
{:else if volume < 0.7}
{:else if $musicPlayer.volume < 0.7}
<i class="fa-solid fa-volume-low"></i>
{:else}
<i class="fa-solid fa-volume-high"></i>
@@ -471,17 +376,17 @@
min="0"
max="1"
step="0.01"
bind:value={volume}
on:input={setVolume}
value={$musicPlayer.volume}
on:input={setPlayerVolume}
class="volume-slider"
style="--fill: {volume * 100}%"
style="--fill: {$musicPlayer.volume * 100}%"
/>
</div>
</div>
<a
class="control-btn download-btn"
href={streamURL(currentTrack)}
download={currentTrack.title}
href={streamURL($musicPlayer.currentTrack)}
download={$musicPlayer.currentTrack.title}
title="İndir"
>
<i class="fa-solid fa-download"></i>