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:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user