feat: dodano pasek postępu odtwarzania w czasie rzeczywistym w panelu webowym
This commit is contained in:
5 files changed
+132
-1
No files matched your search
@@ -4,6 +4,11 @@ let currentStatus = null;
|
|||||||
let currentVolume = 100;
|
let currentVolume = 100;
|
||||||
let isUpdatingVolume = false;
|
let isUpdatingVolume = false;
|
||||||
|
|
||||||
|
// Progress Bar Variables
|
||||||
|
let localElapsed = 0;
|
||||||
|
let totalDuration = 0;
|
||||||
|
let isPlaying = false;
|
||||||
|
|
||||||
// DOM Elements
|
// DOM Elements
|
||||||
const statusDot = document.getElementById('status-dot');
|
const statusDot = document.getElementById('status-dot');
|
||||||
const statusText = document.getElementById('status-text');
|
const statusText = document.getElementById('status-text');
|
||||||
@@ -34,6 +39,12 @@ const emptyQueueMsg = document.getElementById('empty-queue-msg');
|
|||||||
const queueCountBadge = document.getElementById('queue-count');
|
const queueCountBadge = document.getElementById('queue-count');
|
||||||
const toastContainer = document.getElementById('toast-container');
|
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
|
// Initialize
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
// Initial fetch
|
// Initial fetch
|
||||||
@@ -45,6 +56,17 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
fetchStatus();
|
fetchStatus();
|
||||||
}
|
}
|
||||||
}, 1000);
|
}, 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
|
// Bind Event Listeners
|
||||||
bindEvents();
|
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
|
// Update UI States
|
||||||
function updateUI(status) {
|
function updateUI(status) {
|
||||||
// 1. Connection Status & Available Channels
|
// 1. Connection Status & Available Channels
|
||||||
@@ -219,6 +263,24 @@ function updateUI(status) {
|
|||||||
trackLink.href = status.current.url || '#';
|
trackLink.href = status.current.url || '#';
|
||||||
trackLink.style.display = status.current.url ? 'inline-flex' : 'none';
|
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
|
// Vinyl animation
|
||||||
if (status.playing) {
|
if (status.playing) {
|
||||||
vinylRecord.classList.add('playing');
|
vinylRecord.classList.add('playing');
|
||||||
@@ -233,6 +295,12 @@ function updateUI(status) {
|
|||||||
trackLink.style.display = 'none';
|
trackLink.style.display = 'none';
|
||||||
vinylRecord.classList.remove('playing');
|
vinylRecord.classList.remove('playing');
|
||||||
musicWave.classList.remove('active');
|
musicWave.classList.remove('active');
|
||||||
|
|
||||||
|
// Hide progress bar
|
||||||
|
progressContainer.style.display = 'none';
|
||||||
|
isPlaying = false;
|
||||||
|
localElapsed = 0;
|
||||||
|
totalDuration = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Play/Pause Button State
|
// 3. Play/Pause Button State
|
||||||
|
|||||||
@@ -90,6 +90,15 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Track Progress Bar -->
|
||||||
|
<div class="track-progress-container" id="progress-container" style="display: none;">
|
||||||
|
<span class="time-text" id="time-elapsed">0:00</span>
|
||||||
|
<div class="progress-bar-bg" id="progress-bar-bg">
|
||||||
|
<div class="progress-bar-fill" id="progress-bar-fill" style="width: 0%;"></div>
|
||||||
|
</div>
|
||||||
|
<span class="time-text" id="time-total">0:00</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Progress / State (Simple status text) -->
|
<!-- Progress / State (Simple status text) -->
|
||||||
<div class="playback-state-bar">
|
<div class="playback-state-bar">
|
||||||
<span class="badge" id="loop-badge"><i data-lucide="repeat-1"></i> Zapętlenie aktywne</span>
|
<span class="badge" id="loop-badge"><i data-lucide="repeat-1"></i> Zapętlenie aktywne</span>
|
||||||
|
|||||||
@@ -438,6 +438,50 @@ body {
|
|||||||
transform: scale(1.05);
|
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 {
|
.playback-state-bar {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|||||||
@@ -118,7 +118,9 @@ async def get_status():
|
|||||||
"current": {
|
"current": {
|
||||||
"title": current_track.title,
|
"title": current_track.title,
|
||||||
"url": current_track.data.get("webpage_url") or current_track.url,
|
"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,
|
} if current_track else None,
|
||||||
"queue": [
|
"queue": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -32,6 +32,14 @@ class YTDLSource(discord.PCMVolumeTransformer):
|
|||||||
self.title = data.get('title')
|
self.title = data.get('title')
|
||||||
self.url = data.get('url')
|
self.url = data.get('url')
|
||||||
self.id = uuid.uuid4().hex # Unique track identifier
|
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
|
@classmethod
|
||||||
async def from_url(cls, url, *, loop=None, stream=False):
|
async def from_url(cls, url, *, loop=None, stream=False):
|
||||||
|
|||||||
Reference in new issue
Block a user