From 3eee27edfde3f87728d97e58fa4492f2658d8815 Mon Sep 17 00:00:00 2001 From: Marcin Macierzanka Date: Sat, 23 May 2026 15:04:42 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20dodano=20pasek=20post=C4=99pu=20odtwarz?= =?UTF-8?q?ania=20w=20czasie=20rzeczywistym=20w=20panelu=20webowym?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- static/app.js | 68 +++++++++++++++++++++++++++++++++++++++++++++++ static/index.html | 9 +++++++ static/style.css | 44 ++++++++++++++++++++++++++++++ web.py | 4 ++- ytdl.py | 8 ++++++ 5 files changed, 132 insertions(+), 1 deletion(-) diff --git a/static/app.js b/static/app.js index 49da1b6..48949c5 100644 --- a/static/app.js +++ b/static/app.js @@ -4,6 +4,11 @@ let currentStatus = null; let currentVolume = 100; let isUpdatingVolume = false; +// Progress Bar Variables +let localElapsed = 0; +let totalDuration = 0; +let isPlaying = false; + // DOM Elements const statusDot = document.getElementById('status-dot'); const statusText = document.getElementById('status-text'); @@ -34,6 +39,12 @@ const emptyQueueMsg = document.getElementById('empty-queue-msg'); const queueCountBadge = document.getElementById('queue-count'); const toastContainer = document.getElementById('toast-container'); +// Progress Bar DOM Elements +const progressContainer = document.getElementById('progress-container'); +const timeElapsed = document.getElementById('time-elapsed'); +const timeTotal = document.getElementById('time-total'); +const progressBarFill = document.getElementById('progress-bar-fill'); + // Initialize document.addEventListener('DOMContentLoaded', () => { // Initial fetch @@ -45,6 +56,17 @@ document.addEventListener('DOMContentLoaded', () => { fetchStatus(); } }, 1000); + + // Start Smooth Progress Tracker (every 100ms) + setInterval(() => { + if (isPlaying && currentStatus && currentStatus.current) { + localElapsed += 0.1; + if (totalDuration > 0 && localElapsed > totalDuration) { + localElapsed = totalDuration; + } + updateProgressBar(); + } + }, 100); // Bind Event Listeners bindEvents(); @@ -139,6 +161,28 @@ async function fetchStatus() { } } +// Progress Bar Helpers +function formatTime(secs) { + if (secs === null || secs === undefined || isNaN(secs) || secs < 0) return "0:00"; + const h = Math.floor(secs / 3600); + const m = Math.floor((secs % 3600) / 60); + const s = Math.floor(secs % 60); + const formattedSecs = s < 10 ? `0${s}` : s; + if (h > 0) { + const formattedMins = m < 10 ? `0${m}` : m; + return `${h}:${formattedMins}:${formattedSecs}`; + } + return `${m}:${formattedSecs}`; +} + +function updateProgressBar() { + timeElapsed.textContent = formatTime(localElapsed); + if (totalDuration > 0) { + const pct = Math.min((localElapsed / totalDuration) * 100, 100); + progressBarFill.style.width = `${pct}%`; + } +} + // Update UI States function updateUI(status) { // 1. Connection Status & Available Channels @@ -219,6 +263,24 @@ function updateUI(status) { trackLink.href = status.current.url || '#'; trackLink.style.display = status.current.url ? 'inline-flex' : 'none'; + // Sync progress bar values + totalDuration = status.current.duration || 0; + localElapsed = status.current.elapsed || 0; + isPlaying = status.playing; // Update playing state + + // Show progress bar + progressContainer.style.display = 'flex'; + timeElapsed.textContent = formatTime(localElapsed); + + if (totalDuration > 0) { + timeTotal.textContent = formatTime(totalDuration); + const pct = Math.min((localElapsed / totalDuration) * 100, 100); + progressBarFill.style.width = `${pct}%`; + } else { + timeTotal.textContent = "Live"; + progressBarFill.style.width = "100%"; + } + // Vinyl animation if (status.playing) { vinylRecord.classList.add('playing'); @@ -233,6 +295,12 @@ function updateUI(status) { trackLink.style.display = 'none'; vinylRecord.classList.remove('playing'); musicWave.classList.remove('active'); + + // Hide progress bar + progressContainer.style.display = 'none'; + isPlaying = false; + localElapsed = 0; + totalDuration = 0; } // 3. Play/Pause Button State diff --git a/static/index.html b/static/index.html index 654ace9..9ee970f 100644 --- a/static/index.html +++ b/static/index.html @@ -90,6 +90,15 @@ + + +
Zapętlenie aktywne diff --git a/static/style.css b/static/style.css index 5b30aa4..600a532 100644 --- a/static/style.css +++ b/static/style.css @@ -438,6 +438,50 @@ body { transform: scale(1.05); } +/* Progress Bar styling */ +.track-progress-container { + display: flex; + align-items: center; + gap: 12px; + width: 100%; + margin-bottom: 20px; + padding: 0 10px; + box-sizing: border-box; +} + +.time-text { + font-size: 0.8rem; + font-weight: 600; + color: var(--text-muted); + min-width: 40px; + text-align: center; +} + +.progress-bar-bg { + flex-grow: 1; + height: 6px; + background: rgba(255, 255, 255, 0.08); + border-radius: 4px; + position: relative; + overflow: hidden; + transition: var(--transition-smooth); + border: 1px solid rgba(255, 255, 255, 0.03); +} + +.progress-bar-bg:hover { + height: 8px; + background: rgba(255, 255, 255, 0.15); +} + +.progress-bar-fill { + height: 100%; + background: linear-gradient(90deg, var(--primary) 0%, var(--secondary) 100%); + border-radius: 4px; + width: 0%; + box-shadow: 0 0 10px var(--primary-glow); + transition: width 0.2s linear; +} + .playback-state-bar { display: flex; justify-content: center; diff --git a/web.py b/web.py index 8ba334e..d371cbf 100644 --- a/web.py +++ b/web.py @@ -118,7 +118,9 @@ async def get_status(): "current": { "title": current_track.title, "url": current_track.data.get("webpage_url") or current_track.url, - "id": current_track.id + "id": current_track.id, + "duration": current_track.duration, + "elapsed": round(current_track.elapsed, 1) } if current_track else None, "queue": [ { diff --git a/ytdl.py b/ytdl.py index 33230ae..bc3a946 100644 --- a/ytdl.py +++ b/ytdl.py @@ -32,6 +32,14 @@ class YTDLSource(discord.PCMVolumeTransformer): self.title = data.get('title') self.url = data.get('url') self.id = uuid.uuid4().hex # Unique track identifier + self.duration = data.get('duration') # Duration in seconds + self.elapsed = 0.0 # Elapsed time in seconds + + def read(self): + data = super().read() + if data: + self.elapsed += 0.02 # 20ms frames read + return data @classmethod async def from_url(cls, url, *, loop=None, stream=False):