diff options
| -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.');          } | 
