bbc / peaks.js

JavaScript UI component for interacting with audio waveforms
https://waveform.prototyping.bbc.co.uk
GNU Lesser General Public License v3.0
3.2k stars 279 forks source link

Using setSource with audio context and buffer #351

Closed Christilut closed 3 years ago

Christilut commented 3 years ago

Currently I'm loading audio into Peaks through init as such:

    const audioContext: AudioContext = new AudioContext()
    const arrayBuffer = new Uint8Array(await readFile(location)).buffer
    const dataUri = 'data:' + getHowlerMimetype(location) + ';base64,' + base64ArrayBuffer(arrayBuffer)

    const audioBuffer: AudioBuffer = await audioContext.decodeAudioData(arrayBuffer)

 this.peaks = await Peaks.init({
      containers: {
        overview: this.overviewContainer,
        zoomview: this.container
      },
      mediaElement: undefined as any,
      webAudio: {
        audioContext,
        audioBuffer
      }
})

Full config in #349 (using Howler as player).

This works but I'd like to re-use Peaks without doing a re-init when I change audio file.

According to the documentation I can use setSource so I tried this:

      this.peaks.setSource({
        webAudio: {
          audioContext,
          audioBuffer
        }
      }, () => {
        console.log('loaded')
      })

The callback fires but no waveform is loaded in Peaks.

chrisn commented 3 years ago

Does your player emit a player.canplay event?

Christilut commented 3 years ago

I added these 2 events to the player and neither fires:

    this.peaks.on('player.canplay', function () {
      console.log('canplay')
    })

    this.peaks.on('player.error', function () {
      console.log('error')
    })

This is my player config:

      player: {
        init: (eventEmitter) => {
          this.eventEmitter = eventEmitter

          this.eventEmitter.emit('player.canplay')

          setInterval(() => {
            const time = this.getCurrentTime()

            eventEmitter.emit('player.timeupdate', time)

            if (time >= this.getDuration()) {
              this.howl.stop()
            }
          }, 0.25)
        },

        destroy: () => {
          this.howl?.unload()
        },

        play: () => {
          this.howl.play()
          this.playing = true

          this.eventEmitter.emit('player.play', this.getCurrentTime())
        },

        pause: () => {
          this.howl.pause()
          this.playing = false

          this.eventEmitter.emit('player.pause', this.getCurrentTime())
        },

        seek: (seconds) => {
          this.howl.seek(seconds)

          this.eventEmitter.emit('player.seeked', this.getCurrentTime())
          this.eventEmitter.emit('player.timeupdate', this.getCurrentTime())
        },

        isPlaying: () => {
          return this.howl.playing()
        },

        isSeeking: () => {
          return false
        },

        getCurrentTime: () => {
          return this.getCurrentTime()
        },

        getDuration: () => {
          return this.getDuration()
        }
      }
Christilut commented 3 years ago

Same problem when using an arraybuffer:

      const createFromAudio = promisify(WaveformData.createFromAudio)

      const arrayBuffer = new Uint8Array(await readFile(track.location)).buffer

      const audioContext: AudioContext = new AudioContext()

      const waveform = await createFromAudio({
        audio_context: audioContext,
        array_buffer: arrayBuffer
      })

      const waveformPath: string = join(DATA_PATH, WAVEFORM_FOLDER_NAME, track._id, WAVEFORM_PEAKS_FILENAME)

      await outputFile(waveformPath, Buffer.from(waveform._adapter._data.buffer))
      this.peaks.setSource({
        waveformData: {
          arraybuffer: await readFile(path to file)
        }
    })
Christilut commented 3 years ago

Managed to get it working, there were several problems: