1

I'm working on a custom HTML5 audio player, but it seems i missed something at some point:
The <input> range slider is not following audio, and the <div> span for the playback's current time is not working - it's adding zeros next to each other.

Here is my code so far:

HTML

<div class="player">
    <audio id="HAE">
        <source src="https://upload.wikimedia.org/wikipedia/commons/transcoded/b/b1/Haussperling2008.ogg/Haussperling2008.ogg.mp3" type="audio/mpeg">
    </audio>
    <div id="playpause">PLAY</div>
    <input id="progress" type="range" min="0" max="100" value="0" step="0.1">
    <div id="ct">00:00</div>
</div>

JavaScript

// VARIABLES

hae = document.getElementById('HAE');
pp = document.getElementById('playpause');
progress = document.getElementById('progress');
seeking = false;
seekto = hae.duration * (progress.value / 100);
ct = document.getElementById('ct');
time = hae.currentTime * (100 / hae.duration);
mins = Math.floor(hae.currentTime / 60);
secs = Math.floor(hae.currentTime - mins * 60);

// EVENTS

pp.addEventListener('click', togglePlay);
progress.addEventListener('mousedown', function(event) {seeking = true; seek(event);});
progress.addEventListener('mousemove', function(event) {seek(event);});
progress.addEventListener('mouseup', function() {seeking = false;});
hae.addEventListener('timeupdate', function(){ seekTimeUpdate(); });


// TOGGLE PLAY/PAUSE

function togglePlay() {
    if (hae.paused) {
        hae.play();
        pp.innerHTML = "PAUSE";
    }
    else {
        hae.pause();
        pp.innerHTML = "PLAY";
    }
}

// PROGRESS BAR

function seek(event){
    if(seeking){
        progress.value = event.clientX - progress.offsetLeft;
        hae.currentTime = seekto;
    }
}

// MM:SS

function seekTimeUpdate(){
    progress.value = time;
    if(secs < 10) {secs = "0" + secs;}
    if(mins < 10) {mins = "0" + mins;}
    ct.innerHTML = mins + ":" + secs;
}

Here is a working Fiddle.
Can someone help me to fix my problem?

Thanks in advance!

22_4
  • 55
  • 2
  • 12
  • Does this answer your question? [Custom progress bar for – ggorlen Jan 09 '22 at 05:29

2 Answers2

1

secs = "0" + secs; prepends "0" to secs, so this string will continually increase as seekTimeUpdate is called on the timeupdate event.

Starting with the progress code in the related question Custom progress bar for <audio> and <progress> HTML5 elements (see that post for an explanation), one can add a time readout using one of the formatting options in the thread JavaScript seconds to time string with format hh:mm:ss. The approach I chose uses Date and toISOString to avoid worrying about the details, but something like this should also work in a pinch:

const padTime = n => (~~(n) + "").padStart(2, "0");
const fmtTime = s =>
  s < 1 ? "00:00" : `${padTime(s / 60)}:${padTime(s % 60)}`
;

const url = "https://upload.wikimedia.org/wikipedia/commons/transcoded/b/b1/Haussperling2008.ogg/Haussperling2008.ogg.mp3";
const audio = new Audio(url);
const playBtn = document.querySelector("button");
const currTimeEl = document.querySelector(".current-time");
const durationEl = document.querySelector(".duration");
const progressEl = document.querySelector('input[type="range"]');
let mouseDownOnSlider = false;

const fmtTime = s => {
  const d = new Date(0);

  if (s > 0) {
    d.setSeconds(s % 60);
    d.setMinutes(s / 60);
  }

  return d.toISOString().slice(14, 19);
};

audio.addEventListener("loadeddata", () => {
  progressEl.value = 0;
  currTimeEl.textContent = fmtTime(audio.currentTime);
  durationEl.textContent = fmtTime(audio.duration);
});
audio.addEventListener("timeupdate", () => {
  if (!mouseDownOnSlider) {
    progressEl.value = audio.currentTime / audio.duration * 100;
    currTimeEl.textContent = fmtTime(audio.currentTime);
    durationEl.textContent = fmtTime(audio.duration);
  }
});
audio.addEventListener("ended", () => {
  playBtn.textContent = "▶️";
});

playBtn.addEventListener("click", () => {
  audio.paused ? audio.play() : audio.pause();
  playBtn.textContent = audio.paused ? "▶️" : "⏸️";
});

progressEl.addEventListener("change", () => {
  const pct = progressEl.value / 100;
  audio.currentTime = (audio.duration || 0) * pct;
});
progressEl.addEventListener("mousedown", () => {
  mouseDownOnSlider = true;
});
progressEl.addEventListener("mouseup", () => {
  mouseDownOnSlider = false;
});
button {
  font-size: 1.5em;
}
<button>▶️</button>
<input type="range" value="0" min="0" max="100" step="1"><span class="current-time">00:00</span>/<span class="duration">00:00</span>
ggorlen
  • 44,755
  • 7
  • 76
  • 106
0

Basic problem here is that your variables time, mins, secs and seekto don't get the audio's currentTime attribute - they are defined but every time you call them they are always the same value. What you need are getter functions that will use the real-time currentTime value in your existing code. Here's a revised version keeping your original HTML and as much JavaScript as possible:

// VARIABLES

hae = document.getElementById('HAE');
pp = document.getElementById('playpause');
progress = document.getElementById('progress');
seeking = false;
ct = document.getElementById('ct');

// FUNCTIONS

function pad(str) {
    return (parseInt(str)<10 ? '0' : '') + str;
}

audioData = {};

Object.defineProperties(audioData, {
    seekto: {
      get: function() {
          return hae.duration * (progress.value / 100);
      },
      enumerable: true,
      configurable: true
    },
    time: {
      get: function() {
          return hae.currentTime * (100 / hae.duration);
      },
      enumerable: true,
      configurable: true
    },
    mins: {
      get: function() {
          return Math.floor(hae.currentTime / 60);
      },
      enumerable: true,
      configurable: true
    },
    secs: {
      get: function() {
          return Math.floor(hae.currentTime % 60);
      },
      enumerable: true,
      configurable: true
    },
});

// EVENTS

pp.addEventListener('click', togglePlay);
progress.addEventListener('mousedown', function(event) {seeking = true; seek(event);});
progress.addEventListener('mousemove', function(event) {seek(event);});
progress.addEventListener('mouseup', function() {seeking = false;});
hae.addEventListener('timeupdate', function(){ seekTimeUpdate(); });


// TOGGLE PLAY/PAUSE

function togglePlay() {
    if (hae.paused) {
        hae.play();
        pp.innerHTML = "PAUSE";
    }
    else {
        hae.pause();
        pp.innerHTML = "PLAY";
    }
}

// PROGRESS BAR

function seek(event){
    if(seeking){
        progress.value = event.clientX - progress.offsetLeft;
        hae.currentTime = audioData.seekto;
    }
}

// CURRENT TIME

function seekTimeUpdate(){
    progress.value = audioData.time;
    ct.innerHTML = pad(audioData.mins) + ":" + pad(audioData.secs);
}

JsFiddle here

  • Can you (please) identify what you changed (and why?) so we can learn from it? (thx) – elbrant Mar 21 '19 at 05:10
  • @elbrant Fair enough. The original code contains code statements such as `time = hae.currentTime * (100 / hae.duration);` However this is not a function but a constant, which will always be zero when called which was the value when declared. A [getter function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get) is needed. So the original code `time = hae.currentTime * (100 / hae.duration);` becomes `audioData.time = { get: function() { return hae.currentTime * (100 / hae.duration); }}` – Puddingsan Mar 21 '19 at 05:46
  • I created an `audioData` static object to contain the getter functions so you can plainly see where the original code was modified ( by prepending `audioData.` to the changes ) – Puddingsan Mar 21 '19 at 06:16
  • Let me re-word my request: Can you, please, _edit your answer_ to include information about what you changed (and why) so that others can understand how you fixed the problem outlined in the question? That would make your answer so helpful!! – elbrant Mar 21 '19 at 16:19