Adds Seekbar
This commit is contained in:
69
hg.css
69
hg.css
@@ -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;
|
||||
|
||||
10
index.html
10
index.html
@@ -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>
|
||||
|
||||
190
player.js
190
player.js
@@ -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.');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user