WebAudio/web-audio-api-v2

Add ability to pause/resume AudioBufferSourceNode

croraf opened this issue ยท 11 comments

Describe the feature
Add ability to pause/resume AudioBufferSourceNode

Is there a prototype?

Describe the feature in more detail
I think people would love to have that. Not sure why is it not possible and explicitly stated as not being provided.

The requirement can be achieved using a single, dedicated AudioContext, resume(), suspend(), and close() methods of the same

<!DOCTYPE html>

<html>
  <head>
  </head>

  <body>
    <input type="file" id="file" accept=".webm,.opus" /><br />
    <button id="start">start</button>
    <button id="pause">pause</button>
    <button id="resume">resume</button>
    <button id="close">close</button>
    <script>
      onload = async (_) => {
        const ac = new AudioContext();
        console.log(ac.state);
        ac.onstatechange = (e) => console.log(ac.state);

        let absn;
        let buffer;
        let ab; 

        const sources = _ => absn instanceof AudioBufferSourceNode 
                               && buffer instanceof AudioBuffer;
        document.getElementById('file').onchange = async (e) => {
          ab = await e.target.files[0].arrayBuffer();
          if (ac.state === 'running') {
            await ac.suspend();
          }
        };
        document.getElementById('start').onclick = async (e) => {
          if (!sources()) {
            buffer = await ac.decodeAudioData(ab);
            absn = new AudioBufferSourceNode(ac, { buffer, loop: true });
            absn.onended = async (e) => {
              console.log(e);
            };
            absn.connect(ac.destination);

            if (ac.state === 'suspended') {
              ac.resume();
              console.log({ab, buffer}, ac.state);
              absn.start(ac.currentTime);
            }
          }
        };
        document.getElementById('pause').onclick = async (e) => {
          if (sources() && ac.state === 'running') {
            await ac.suspend();
          }
        };
        document.getElementById('resume').onclick = async (e) => {
          if (sources() && ac.state === 'suspended') {
            await ac.resume();
          }
        };
        document.getElementById('close').onclick = async (e) => {
          await ac.close();
        };
      };
    </script>
  </body>
</html>

Cool. Ty very much for that. I made a simpler example with just play/pause buttons and it works nice.

But this is resuming/suspending on the context level, wouldn't it be nice if you could suspend/resume on the source node level?

But this is resuming/suspending on the context level, wouldn't it be nice if you could suspend/resume on the source node level?

Would there be an observable difference?

AudioBufferSourceNode class could probably be extended with pause() and resume() methods that use AudioContext suspend() and resume().

The difference is if you have 2 AudioBufferSourceNodes or AudioBufferSourceNode along some other source nodes. You wouldn't have to pause all of them as with context.suspend(), but only certain source.

One option to connect source nodes is to an MediaStreamAudioDestinationNode with the stream property set at HTMLMediaElement srcObject, use the <audio> or <video> element to play and pause (output audio) an AudioBufferSourceNode and, or other connected audio nodes

    const ac = new AudioContext();
    const mediaElement = new Audio();
    mediaElement.controls = mediaElement.autoplay = true;
    document.body.appendChild(mediaElement);
    const source = new MediaStreamAudioDestinationNode(ac);
    absn.connect(source);
    mediaElement.srcObject = source.stream;
rtoy commented

Currently, the only way I know of to pause an AudioBufferSourceNode is to set the playbackRate to 0. Nice thing about this is that you can do it with sample-accurate timing using setValueAtTime(). Well, to the accuracy of a render quantum; playbackRate is a k-rate AudioParam.

@guest271314 I tried your solution but it seems not to be working as expected. https://jsfiddle.net/croraf/m49rwpqn/

(Try in FF which doesn't respect the autoplay policy) The audio element shows "streaming" but there is no sound.
image

@rtoy Yes, I heard about the playbackRate=0 hack.

But these are all workarounds. I'm wondering why can't AudioBufferSourceNode be just paused and resumed.

OK, I forgot to add

sourceNode.start();

for the AudioBufferSourceNode to start "playing" its content into the next node, but even then the pause on the audio element just stops the sound to be outputted on my device, the AudioBufferSourceNode is not paused and continues to play and consume the buffer.

rtoy commented
rtoy commented

Teleconf: Current browsers take advantage of the fact that once stop is called, it can't be restarted. This includes cleaning up all internal storage and methods and possibly disconnecting (internally) from downstream nodes. These improve memory usage and performance.

If setting the playbackRate is not good enough, we also note that we're adding a playbackPosition #26 so you can schedule a stop and get the playback position at that time. Then construct a new ABSN with the same buffer, and start the ABSN where you left off using start with the appropriate offset.

AudioWG virtual F2F:

  • playbackRate.value = 0 works without any downside, this would just be syntactic sugar, that one can add by mangling the prototype if they want
  • The solution proposed by @rtoy also works