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

Browser generated waveform loading previous waveform on differents peaks instances in chrome #355

Open WisePlatypus opened 3 years ago

WisePlatypus commented 3 years ago

When generating waveform with an audioContext for the second time on a different instance of peaks. (destroying the previous and init another one). The waveform shown is the same as the previous one. On next call n audio will be loaded with n-1 waveform and timeline.

This bug only works in chrome.

(I only tested this by downloading audio file manually as a blob)

Minimal bug replication: https://repl.it/@daWisePlatypus/browserWaveformPeaksJs#script.js

I'm reporting this because I think it is not the intended behaviour. If you have downloaded your file as a blob you can easily work around by extracting manually the audioBuffer and provide it in peaks init as such:

blob.arrayBuffer().then(arrayBuffer=> {

      let audioContext = new AudioContext();      

      audioContext.decodeAudioData(arrayBuffer, (audioBuffer) => {

      peaksInstance = Peaks.init(
      {
             webAudio:
            {
                  audioBuffer: audioBuffer,
            },
            ...otherPeaksOptions
       });
      audioContext.close();
});
chrisn commented 3 years ago

Thanks for reporting this, and for including the code to reproduce the issue. This will help me investigate what's going on.

chrisn commented 3 years ago

We may need to fix Peaks.init(), but the way I suggest you implement this is to call peaksInstance.setSource() to change the audio URL instead of destroying and re-creating the Peaks instance:

peaksInstance.setSource({
  mediaUrl: yourMp3Url,
  webAudio: { audioContext: audioContext }
},
(err) => {
  if (err) console.error(err);
});

Peaks.init() checks currentSrc to detect whether the media element has been initialised, see WaveformBuilder._buildWaveformDataUsingWebAudio and https://github.com/bbc/peaks.js/issues/112#issuecomment-356131073.

The difference between Chrome and Firefox is that Firefox sets the media element's currentSrc to an empty string after you change src, whereas Chrome does not, and this leads to Peaks.init() getting confused.

There's a related HTML spec issue here: https://github.com/whatwg/html/issues/3988. FWIW, my reading of the spec is that Chrome's behaviour is per-spec, and I wouldn't expect currentSrc to change to an empty string on setting src - although arguably the Firefox behaviour could be preferable.

WisePlatypus commented 3 years ago

Thanks!

I need to have different peaks instance. The init will be called a second time anyway. My work around works, so I'll just decode my audioBuffer before for now.