How to Detect the End of an Audio Loop in Howler.js

This article explains how to detect when a long audio loop completes an iteration using howler.js. While howler.js handles seamless looping automatically, tracking the exact moment a loop restarts requires utilizing the library’s event system or monitoring playback positioning. Below, you will find direct methods to capture this event using the built-in end listener and a high-precision polling alternative.

Method 1: Using the Built-In end Event

The simplest way to detect when a loop finishes is by using the onend callback or binding to the end event. Even when the loop property is set to true, howler.js fires the end event every time the audio track reaches its conclusion and restarts.

const sound = new Howl({
  src: ['long-ambient-loop.mp3'],
  loop: true,
  onend: function(soundId) {
    console.log(`Loop finished playing for sound ID: ${soundId}`);
    // Trigger your custom logic here (e.g., updating UI, incrementing a loop counter)
  }
});

// Play the sound
sound.play();

Alternatively, you can register the event listener after initializing the object:

sound.on('end', (id) => {
  console.log('A loop iteration has ended.');
});

Method 2: High-Precision Detection Using Polling (seek)

For extremely long audio tracks, browser backgrounding or system latency can occasionally cause event-firing delays. If you require frame-perfect precision or want to trigger an event slightly before the loop actually ends (to pre-load resources, for example), you can poll the playback position using the seek() method inside a request animation frame loop.

const sound = new Howl({
  src: ['long-ambient-loop.mp3'],
  loop: true
});

sound.play();

let lastPosition = 0;

function monitorPlayback() {
  if (sound.playing()) {
    const currentPosition = sound.seek();
    const duration = sound.duration();

    // If the current position is less than the last recorded position,
    // the audio has looped back to the beginning.
    if (currentPosition < lastPosition && lastPosition > 0) {
      console.log('Loop detection triggered via position tracking.');
      // Execute loop-end code here
    }

    lastPosition = currentPosition;
  }
  requestAnimationFrame(monitorPlayback);
}

// Start monitoring
requestAnimationFrame(monitorPlayback);

Using either the built-in end event for standard use cases or the seek() polling method for precise sync requirements ensures you can reliably track loop iterations in your application.