diff options
author | Indrajith K L | 2025-07-01 01:33:12 +0530 |
---|---|---|
committer | Indrajith K L | 2025-07-01 01:33:12 +0530 |
commit | c908577d3621aab29a40025fb69fd8695e021e57 (patch) | |
tree | 60def56fdccf59863b7de9f5b7d7eb9fa734d567 | |
parent | b304a682e8545d8b2458c23963ebe7addd924321 (diff) | |
download | retrowave-player-c908577d3621aab29a40025fb69fd8695e021e57.tar.gz retrowave-player-c908577d3621aab29a40025fb69fd8695e021e57.tar.bz2 retrowave-player-c908577d3621aab29a40025fb69fd8695e021e57.zip |
Adds Seekbar
-rw-r--r-- | hg.css | 69 | ||||
-rw-r--r-- | index.html | 10 | ||||
-rw-r--r-- | player.js | 190 |
3 files changed, 269 insertions, 0 deletions
@@ -427,6 +427,75 @@ canvas { top: 0; } +.progress-bar-container { + position: fixed; + bottom: 0; + left: 0; + right: 0; + height: 55px; + background: linear-gradient(145deg, rgba(0, 0, 0, 0.95), rgba(0, 0, 0, 0.85)); + border-top: 2px solid #ff2975; + z-index: 1000; + padding: 10px 20px; + box-shadow: + 0 -2px 10px rgba(0, 0, 0, 0.6), + 0 0 15px rgba(255, 41, 117, 0.2); +} + +.progress-info { + display: flex; + justify-content: space-between; + margin-bottom: 6px; + font-family: "VCR", monospace; + font-size: 10px; + color: #00ffff; + text-shadow: 0 0 5px rgba(0, 255, 255, 0.5); +} + +.progress-bar { + position: relative; + height: 6px; + background: linear-gradient(145deg, rgba(0, 0, 0, 0.8), rgba(20, 20, 20, 0.9)); + border: 1px solid #333; + border-radius: 3px; + cursor: pointer; + overflow: hidden; + box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.8); +} + +.progress-fill { + height: 100%; + background: linear-gradient(90deg, #ff2975, #00ffff); + border-radius: 3px; + width: 0%; + transition: width 0.1s ease-out; + box-shadow: 0 0 8px rgba(255, 41, 117, 0.4); +} + +.progress-handle { + position: absolute; + top: -4px; + width: 14px; + height: 14px; + background: radial-gradient(circle, #00ffff, #ff2975); + border: 1px solid #fff; + border-radius: 50%; + cursor: pointer; + opacity: 0; + transition: opacity 0.3s ease; + box-shadow: 0 0 8px rgba(0, 255, 255, 0.6); + transform: translateX(-50%); +} + +.progress-bar:hover .progress-handle { + opacity: 1; +} + +.progress-handle:hover { + transform: translateX(-50%) scale(1.1); + box-shadow: 0 0 12px rgba(0, 255, 255, 0.8); +} + .player-controls { position: absolute; top: 0; @@ -129,6 +129,16 @@ </div> </div> </div> + <div id="progress-bar-container" class="progress-bar-container"> + <div class="progress-info"> + <span id="current-time">00:00</span> + <span id="total-time">00:00</span> + </div> + <div id="progress-bar" class="progress-bar"> + <div id="progress-fill" class="progress-fill"></div> + <div id="progress-handle" class="progress-handle"></div> + </div> + </div> <div id="codef-canvas"></div> <div class="OVR hidden"></div> <div class="ERRORS hidden"></div> @@ -18,6 +18,16 @@ let currentEffectIndex = 0; const modalEl = document.getElementById("intro-modal"); + // Progress bar elements (initialized later to avoid null references) + let progressBarContainer; + let progressBar; + let progressFill; + let progressHandle; + let currentTimeEl; + let totalTimeEl; + let progressUpdateInterval; + let isDragging = false; + // const uploadInfoEl = document.getElementById("upload-info"); const fileUploadEl = document.getElementById("file-upload"); let volume = 1; @@ -111,6 +121,7 @@ }); initHydra(); initControls(); + initProgressBar(); }); document.getElementById("history").addEventListener("click", () => { @@ -223,6 +234,18 @@ onend: function () { playNextTrack(); }, + onload: function () { + updateProgressBar(); + }, + onplay: function () { + startProgressTracking(); + }, + onpause: function () { + stopProgressTracking(); + }, + onstop: function () { + stopProgressTracking(); + }, }); setVolume(); isPlaying = true; @@ -318,6 +341,7 @@ https://retrowave.ru/${musicData.streamUrl} } function resetHowler(destroy = false) { + stopProgressTracking(); howlerInstance.stop(); if (destroy) { howlerInstance.unload(); @@ -325,6 +349,134 @@ https://retrowave.ru/${musicData.streamUrl} } } + // Progress Bar Utility Functions + function formatTime(seconds) { + const mins = Math.floor(seconds / 60); + const secs = Math.floor(seconds % 60); + return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`; + } + + function updateProgressBar() { + if (!howlerInstance || isDragging || !progressFill || !progressHandle || !currentTimeEl || !totalTimeEl) return; + + const seek = howlerInstance.seek() || 0; + const duration = howlerInstance.duration() || 0; + + if (duration > 0) { + const percentage = (seek / duration) * 100; + progressFill.style.width = `${percentage}%`; + progressHandle.style.left = `${percentage}%`; + + currentTimeEl.textContent = formatTime(seek); + totalTimeEl.textContent = formatTime(duration); + } + } + + function startProgressTracking() { + if (progressUpdateInterval) { + clearInterval(progressUpdateInterval); + } + progressUpdateInterval = setInterval(updateProgressBar, 100); + } + + function stopProgressTracking() { + if (progressUpdateInterval) { + clearInterval(progressUpdateInterval); + progressUpdateInterval = null; + } + } + + function seekToPosition(percentage) { + if (!howlerInstance) return; + + const duration = howlerInstance.duration() || 0; + if (duration > 0) { + const seekTime = (percentage / 100) * duration; + howlerInstance.seek(seekTime); + updateProgressBar(); + } + } + + function initProgressBar() { + // Initialize DOM elements + progressBarContainer = document.getElementById("progress-bar-container"); + progressBar = document.getElementById("progress-bar"); + progressFill = document.getElementById("progress-fill"); + progressHandle = document.getElementById("progress-handle"); + currentTimeEl = document.getElementById("current-time"); + totalTimeEl = document.getElementById("total-time"); + + if (!progressBar || !progressFill || !progressHandle || !currentTimeEl || !totalTimeEl) { + console.warn("Progress bar elements not found"); + return; + } + + // Click to seek + progressBar.addEventListener("click", (e) => { + if (isDragging) return; + + const rect = progressBar.getBoundingClientRect(); + const percentage = ((e.clientX - rect.left) / rect.width) * 100; + seekToPosition(Math.max(0, Math.min(100, percentage))); + }); + + // Drag to seek functionality + let startX = 0; + let startPercentage = 0; + + progressHandle.addEventListener("mousedown", (e) => { + isDragging = true; + startX = e.clientX; + const rect = progressBar.getBoundingClientRect(); + startPercentage = ((startX - rect.left) / rect.width) * 100; + + document.addEventListener("mousemove", handleMouseMove); + document.addEventListener("mouseup", handleMouseUp); + e.preventDefault(); + }); + + function handleMouseMove(e) { + if (!isDragging) return; + + const rect = progressBar.getBoundingClientRect(); + const percentage = ((e.clientX - rect.left) / rect.width) * 100; + const clampedPercentage = Math.max(0, Math.min(100, percentage)); + + progressFill.style.width = `${clampedPercentage}%`; + progressHandle.style.left = `${clampedPercentage}%`; + + if (howlerInstance) { + const duration = howlerInstance.duration() || 0; + if (duration > 0) { + const seekTime = (clampedPercentage / 100) * duration; + currentTimeEl.textContent = formatTime(seekTime); + } + } + } + + function handleMouseUp(e) { + if (!isDragging) return; + + const rect = progressBar.getBoundingClientRect(); + const percentage = ((e.clientX - rect.left) / rect.width) * 100; + const clampedPercentage = Math.max(0, Math.min(100, percentage)); + + seekToPosition(clampedPercentage); + isDragging = false; + + document.removeEventListener("mousemove", handleMouseMove); + document.removeEventListener("mouseup", handleMouseUp); + } + + // Touch support for mobile + progressBar.addEventListener("touchstart", (e) => { + const touch = e.touches[0]; + const rect = progressBar.getBoundingClientRect(); + const percentage = ((touch.clientX - rect.left) / rect.width) * 100; + seekToPosition(Math.max(0, Math.min(100, percentage))); + }); + } + function initControls() { refreshBtn.addEventListener("click", () => { playNextTrack(); @@ -1267,6 +1419,7 @@ https://retrowave.ru/${musicData.streamUrl} addTerminalLine(' pause - Pause playback'); addTerminalLine(' next - Skip to next track'); addTerminalLine(' volume [0-10] - Set volume (0-10)'); + addTerminalLine(' seek [time] - Seek to time (seconds or mm:ss)'); addTerminalLine(' status - Show current track info'); addTerminalLine(' history - Download playlist history'); addTerminalLine(' effect [list|name]- Change/list visual effects'); @@ -1319,12 +1472,49 @@ https://retrowave.ru/${musicData.streamUrl} } break; + case 'seek': + if (args[1] && howlerInstance) { + let seekTime = 0; + const input = args[1]; + + if (input.includes(':')) { + const [mins, secs] = input.split(':').map(Number); + seekTime = (mins * 60) + secs; + } else { + seekTime = parseFloat(input); + } + + const duration = howlerInstance.duration() || 0; + if (seekTime >= 0 && seekTime <= duration) { + howlerInstance.seek(seekTime); + updateProgressBar(); + addTerminalLine(`Seeked to ${formatTime(seekTime)}`); + } else { + addTerminalLine(`Invalid seek time. Duration: ${formatTime(duration)}`); + } + } else if (!howlerInstance) { + addTerminalLine('No track loaded.'); + } else { + if (howlerInstance) { + const seek = howlerInstance.seek() || 0; + const duration = howlerInstance.duration() || 0; + addTerminalLine(`Current position: ${formatTime(seek)} / ${formatTime(duration)}`); + } + } + break; + case 'status': if (currentTracks.length > 0) { const track = currentTracks[0]; addTerminalLine(`Now playing: ${track.title}`); addTerminalLine(`Status: ${isPlaying ? 'Playing' : 'Paused'}`); addTerminalLine(`Volume: ${Math.round(volume * 10)}/10`); + if (howlerInstance) { + const seek = howlerInstance.seek() || 0; + const duration = howlerInstance.duration() || 0; + const progress = duration > 0 ? ((seek / duration) * 100).toFixed(1) : 0; + addTerminalLine(`Progress: ${formatTime(seek)} / ${formatTime(duration)} (${progress}%)`); + } } else { addTerminalLine('No track loaded.'); } |