javascripttypescriptweb-audio-apihowler.js

Howler.js variable offset


I transitioned from Web Audio API to Howler.js for the convenience it offers, however I can't figure out how to implement a variable (silenced) offset at the beginning of the sound.


Here's an example:

Let snd be a simple Howl sound and offset be a variable number:

let snd = new Howl({ src: data })
let offset = 2000 // in ms

I want to be able to something along the lines of:

snd.seek(1000)

and that would not seek 1 second into the sound but rather halfway in the start offset.

Also, if snd is playing, I need to be able to do the following:

offset = 4000

and the audio would be moved 2 seconds further (4000ms - 2000ms) during playback.


Obviously, the example is oversimplified and the assignment of the offset would be in a function with a bunch of other logic. That being said, is there any way to achieve this with Howler.js, even if a bit hacky ?

I've seen options with timers and setTimeout but these seem either inaccurate or incompatible with my logic requirements. I've also tried negative values for seek() but these seem to get defaulted to 0.


Solution

  • Howl does not appear to support this, but you can wrap an instance of howl and add this logic yourself. You could create a new class that inherits from Howl with this logic included.

    And it's not going to be trivial.

    This example is woefully incomplete, but it illustrates an approach that might work. You'll basically want to overload every method in your subclass and make it offset aware.

    import { Howl, HowlOptions } from 'howler'
    
    interface HowlWithOffsetOptions extends HowlOptions {
        offset: number
    }
    
    class HowlWithOffset extends Howl {
        #offset: number = 0
    
        constructor(options: HowlWithOffsetOptions) {
            super(options)
            this.#offset = options.offset
        }
    
        offset(newOffset?: number): number {
            if (newOffset) {
                this.#offset = newOffset
            }
            return this.#offset
        }
        
        // start playing in `offset` milliseconds.
        play() {
            setTimeout(() => super.play(), this.#offset)
        }
    
        seek(position?: number): number {
            if (position != null) {
                if (position > this.#offset) {
                    super.seek(this.#offset - position)
                } else {
                    super.seek(0)
                }
            }
        }
    }
    

    Again, those implementations are incomplete. You'd need to handle the case where you seek to a value less than the offset, you probably need to keep track of whether you're in the offset part or the actual sound part. You may need to call seek(newPosition) when you change the offset if you to support changing the offset while it's playing.

    But in theory, you should be able to override each method in way that is offset aware.

    Good luck!