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.
247 lines
5.8 KiB
Svelte
247 lines
5.8 KiB
Svelte
<script>
|
|
import { musicPlayer, togglePlay, playNext, playPrevious, stopPlayback } from "../stores/musicPlayerStore.js";
|
|
import { API } from "../utils/api.js";
|
|
import { cleanFileName } from "../utils/filename.js";
|
|
|
|
function thumbnailURL(item) {
|
|
if (!item?.thumbnail) return null;
|
|
const token = localStorage.getItem("token");
|
|
const separator = item.thumbnail.includes("?") ? "&" : "?";
|
|
return `${API}${item.thumbnail}${separator}token=${token}`;
|
|
}
|
|
|
|
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 remainingTime(current, total) {
|
|
if (!Number.isFinite(total) || total <= 0) return "-0:00";
|
|
const remaining = Math.max(total - (current || 0), 0);
|
|
return `-${formatTime(remaining)}`;
|
|
}
|
|
|
|
function sourceLabel(item) {
|
|
if (item?.tracker === "youtube" || item?.thumbnail) return "YouTube";
|
|
return "Music";
|
|
}
|
|
</script>
|
|
|
|
{#if $musicPlayer.currentTrack}
|
|
<div class="mini-player" aria-label="Mini music player">
|
|
<div class="mini-top">
|
|
<div class="mini-user-meta">
|
|
<div class="mini-user-name">
|
|
{cleanFileName($musicPlayer.currentTrack.title)}
|
|
</div>
|
|
<div class="mini-user-handle">{sourceLabel($musicPlayer.currentTrack)}</div>
|
|
</div>
|
|
|
|
<div class="mini-actions">
|
|
<button class="mini-icon-btn" title="Paylaş">
|
|
<i class="fa-solid fa-arrow-up-from-bracket"></i>
|
|
</button>
|
|
<button class="mini-icon-btn" title="Beğen">
|
|
<i class="fa-regular fa-heart"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mini-cover">
|
|
{#if thumbnailURL($musicPlayer.currentTrack)}
|
|
<img
|
|
src={thumbnailURL($musicPlayer.currentTrack)}
|
|
alt={$musicPlayer.currentTrack.title}
|
|
/>
|
|
{:else}
|
|
<div class="mini-cover-placeholder">
|
|
<i class="fa-solid fa-music"></i>
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
|
|
<div class="mini-progress">
|
|
<span class="mini-time">{formatTime($musicPlayer.currentTime)}</span>
|
|
<div class="mini-bar">
|
|
<div
|
|
class="mini-bar-fill"
|
|
style="width: {($musicPlayer.currentTime / $musicPlayer.duration) * 100 || 0}%"
|
|
></div>
|
|
</div>
|
|
<span class="mini-time">
|
|
{remainingTime($musicPlayer.currentTime, $musicPlayer.duration)}
|
|
</span>
|
|
</div>
|
|
|
|
<div class="mini-controls">
|
|
<button class="mini-btn" on:click={playPrevious} title="Önceki">
|
|
<i class="fa-solid fa-backward-step"></i>
|
|
</button>
|
|
<button class="mini-btn main" on:click={togglePlay} title="Oynat/Durdur">
|
|
{#if $musicPlayer.isPlaying}
|
|
<i class="fa-solid fa-pause"></i>
|
|
{:else}
|
|
<i class="fa-solid fa-play"></i>
|
|
{/if}
|
|
</button>
|
|
<button class="mini-btn" on:click={playNext} title="Sonraki">
|
|
<i class="fa-solid fa-forward-step"></i>
|
|
</button>
|
|
<button class="mini-btn ghost" on:click={stopPlayback} title="Kapat">
|
|
<i class="fa-solid fa-xmark"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
{/if}
|
|
|
|
<style>
|
|
.mini-player {
|
|
position: fixed;
|
|
right: 24px;
|
|
bottom: 24px;
|
|
width: 240px;
|
|
padding: 16px;
|
|
background: rgba(12, 12, 12, 0.72);
|
|
border: 1px solid rgba(255, 255, 255, 0.08);
|
|
border-radius: 26px;
|
|
box-shadow: 0 20px 45px rgba(0, 0, 0, 0.35);
|
|
color: #f6f6f6;
|
|
backdrop-filter: blur(14px);
|
|
z-index: 50;
|
|
}
|
|
|
|
.mini-top {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 10px;
|
|
}
|
|
|
|
.mini-user-meta {
|
|
min-width: 0;
|
|
text-align: left;
|
|
}
|
|
|
|
.mini-user-name {
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
.mini-user-handle {
|
|
font-size: 11px;
|
|
color: rgba(255, 255, 255, 0.6);
|
|
}
|
|
|
|
.mini-actions {
|
|
display: flex;
|
|
gap: 8px;
|
|
}
|
|
|
|
.mini-icon-btn {
|
|
width: 32px;
|
|
height: 32px;
|
|
border-radius: 12px;
|
|
border: none;
|
|
background: rgba(255, 255, 255, 0.12);
|
|
color: #f6f6f6;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.mini-cover {
|
|
margin: 14px auto 12px;
|
|
width: 160px;
|
|
height: 160px;
|
|
border-radius: 20px;
|
|
overflow: hidden;
|
|
background: rgba(255, 255, 255, 0.06);
|
|
box-shadow: 0 18px 30px rgba(0, 0, 0, 0.35);
|
|
border: 1px solid rgba(255, 255, 255, 0.12);
|
|
display: grid;
|
|
place-items: center;
|
|
transform: translateY(-6px);
|
|
}
|
|
|
|
.mini-cover img {
|
|
width: 100%;
|
|
height: 100%;
|
|
object-fit: cover;
|
|
}
|
|
|
|
.mini-cover-placeholder {
|
|
color: rgba(255, 255, 255, 0.45);
|
|
font-size: 40px;
|
|
}
|
|
|
|
.mini-progress {
|
|
display: grid;
|
|
grid-template-columns: auto 1fr auto;
|
|
align-items: center;
|
|
gap: 8px;
|
|
margin: 8px 0 14px;
|
|
}
|
|
|
|
.mini-time {
|
|
font-size: 11px;
|
|
color: rgba(255, 255, 255, 0.55);
|
|
}
|
|
|
|
.mini-bar {
|
|
height: 3px;
|
|
background: rgba(255, 255, 255, 0.15);
|
|
border-radius: 999px;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.mini-bar-fill {
|
|
height: 100%;
|
|
background: var(--yellow);
|
|
border-radius: 999px;
|
|
}
|
|
|
|
.mini-controls {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 12px;
|
|
}
|
|
|
|
.mini-btn {
|
|
width: 34px;
|
|
height: 34px;
|
|
border-radius: 999px;
|
|
border: none;
|
|
background: rgba(255, 255, 255, 0.14);
|
|
color: #f6f6f6;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.mini-btn.main {
|
|
width: 44px;
|
|
height: 44px;
|
|
border-radius: 999px;
|
|
background: var(--yellow);
|
|
color: #1e1e1e;
|
|
}
|
|
|
|
.mini-btn.ghost {
|
|
background: rgba(255, 255, 255, 0.06);
|
|
}
|
|
|
|
@media (max-width: 820px) {
|
|
.mini-player {
|
|
right: 16px;
|
|
bottom: 16px;
|
|
width: 220px;
|
|
}
|
|
.mini-cover {
|
|
width: 140px;
|
|
height: 140px;
|
|
}
|
|
}
|
|
</style>
|