Understanding the HTML Audio Element Buffered Regions

The HTML audio element exposes multiple properties and methods which prove to be useful for making a reactive web music player. In addition to the usual play/pause controls, the <audio> tag has a currentTime, a duration, and a buffered attributes which can all be used to get the playing media state in real time using a bit of JavaScript code.

currentTime and duration

The currentTime and duration attributes are pretty straightforward. currentTime returns the playback position from the audio file start, in seconds, as a float. Similarly, duration is a float corresponding to the total media length. One can then simply convert to a more natural time format using a bit of formatting code.

export function formatDuration(floatSeconds) {
  const durationMillis = floatSeconds * 1000;
  const timeInSeconds = Math.floor(durationMillis / 1000);
  const minutes = Math.floor(timeInSeconds / 60);
  const seconds = timeInSeconds - (minutes * 60);
  const formattedSeconds = seconds > 9 ? `${seconds}` : `0${seconds}`;
  return `${minutes}:${formattedSeconds}`;
}

buffered regions

The HTML audio element internally uses multiple buffered regions, which live independently. Each buffered region start and stop times can be accessed using the following JavaScript lines.

audioelement.buffered.start(<buffered_region_id>)
audioelement.buffered.end(<buffered_region_id>)

Initially, the browser creates a first buffered region (numbered 0) which is enough to play the first few seconds. As the playback approaches the end of that region, a new part of the media file is automatically requested.

If the user happens to seek the media to an unbuffered region, the browser creates a new buffered region starting at that point. This means that, to properly reflect the next buffered zone, our application has to now show the buffered region #1 in gray. Then, when the user rewinds back to a time inside the first buffered region, our 0th region gets reused. Note that the browser likes to rearrange these ranges in the background, so hardcoded values will definitely become an issue.

Here is a simple JavaScript/VueJS snippet which finds the present buffered region and updates the “buffered but unplayed” region in our application.

updateSeekBar() {
      const currentTime = this.$refs.audiotag.currentTime;
      const totalDuration = this.$refs.audiotag.duration;

      const timeRangeLength = this.$refs.audiotag.buffered.length;
      let currentBufferRange = 0;
      for (let range = 0; range < timeRangeLength; range++) { 
             const rangeStartTime = this.$refs.audiotag.buffered.start(range); 
             const rangeEndTime = this.$refs.audiotag.buffered.end(range); 
             if (currentTime >= rangeStartTime && currentTime < rangeEndTime) {
                 currentBufferRange = range;
         }
      }
      this.bufferedPercentage = (this.$refs.audiotag.buffered.end(currentBufferRange) - currentTime) / totalDuration * 100;
    },

Leave a Reply

Your email address will not be published. Required fields are marked *