Domanda

I often read that it's not possible to pause/resume audio files with the Web Audio API.
But now I saw a example where they actually made it possible to pause and resume it. I tried to figure out what how they did it. I thought maybe source.looping = falseis the key, but it wasn't.
For now my audio is always re-playing from the start.

This is my current code

var context = new (window.AudioContext || window.webkitAudioContext)();

function AudioPlayer() {
  this.source = context.createBufferSource();
  this.analyser = context.createAnalyser();
  this.stopped = true;
}

AudioPlayer.prototype.setBuffer = function(buffer) {
  this.source.buffer = buffer;
  this.source.looping = false;
};

AudioPlayer.prototype.play = function() {
  this.source.connect(this.analyser);
  this.analyser.connect(context.destination);

  this.source.noteOn(0);
  this.stopped = false;
};

AudioPlayer.prototype.stop = function() {
  this.analyser.disconnect();
  this.source.disconnect();
  this.stopped = true;
};

Does anybody know what to do, to get it work?

È stato utile?

Soluzione

Without spending any time checking the source of your example, I'd say you'll want to use the noteGrainOn method of the AudioBufferSourceNode (https://dvcs.w3.org/hg/audio/raw-file/tip/webaudio/specification.html#methodsandparams-AudioBufferSourceNode)

Just keep track of how far into the buffer you were when you called noteOff, and then do noteGrainOn from there when resuming on a new AudioBufferSourceNode.

Did that make sense?

EDIT: See comments below for updated API calls.

EDIT 2, 2019: See MDN for updated API calls; https://developer.mozilla.org/en-US/docs/Web/API/AudioBufferSourceNode/start

Altri suggerimenti

Oskar's answer and ayke's comment are very helpful, but I was missing a code example. So I wrote one: http://jsfiddle.net/v3syS/2/ I hope it helps.

var url = 'http://thelab.thingsinjars.com/web-audio-tutorial/hello.mp3';

var ctx = new webkitAudioContext();
var buffer;
var sourceNode;

var startedAt;
var pausedAt;
var paused;

function load(url) {
    var request = new XMLHttpRequest();
    request.open('GET', url, true);
    request.responseType = 'arraybuffer';
    request.onload = function() {
        ctx.decodeAudioData(request.response, onBufferLoad, onBufferError);
    };
    request.send();
};

function play() {
    sourceNode = ctx.createBufferSource();
    sourceNode.connect(ctx.destination);
    sourceNode.buffer = buffer;
    paused = false;

    if (pausedAt) {
        startedAt = Date.now() - pausedAt;
        sourceNode.start(0, pausedAt / 1000);
    }
    else {
        startedAt = Date.now();
        sourceNode.start(0);
    }
};

function stop() {
    sourceNode.stop(0);
    pausedAt = Date.now() - startedAt;
    paused = true;
};

function onBufferLoad(b) {
    buffer = b;
    play();
};

function onBufferError(e) {
    console.log('onBufferError', e);
};

document.getElementById("toggle").onclick = function() {
    if (paused) play();
    else stop();
};

load(url);

In current browsers (Chrome 43, Firefox 40) there are now 'suspend' and 'resume' methods available for AudioContext:

var audioCtx = new AudioContext();
susresBtn.onclick = function() {
  if(audioCtx.state === 'running') {
    audioCtx.suspend().then(function() {
      susresBtn.textContent = 'Resume context';
    });
  } else if(audioCtx.state === 'suspended') {
    audioCtx.resume().then(function() {
      susresBtn.textContent = 'Suspend context';
    });  
  }
}

(modified example code from https://developer.mozilla.org/en-US/docs/Web/API/AudioContext/suspend)

Actually the web-audio API can do the pause and play task for you. It knows the current state of the audio context (running or suspended), so you can do this in this easy way:

susresBtn.onclick = function() {
  if(audioCtx.state === 'running') {
    audioCtx.suspend()
  } else if(audioCtx.state === 'suspended') {
    audioCtx.resume()  
  }
}

I hope this can help.

For chrome fix, every time you want to play sound, set it like:

if(audioCtx.state === 'suspended') {
    audioCtx.resume().then(function() {
      audio.play();
   });  
}else{
     audio.play();
}

The lack of a built-in pause functionality in the WebAudio API seems like a major oversight to me. Possibly, in the future it will be possible to do this using the planned MediaElementSource, which will let you hook up an element (which supports pausing) to Web Audio. For now, most workarounds seem to be based on remembering playback time (such as described in imbrizi's answer). Such a workaround has issues when looping sounds (does the implementation loop gapless or not?), and when you allow dynamically change the playbackRate of sounds (as both affect timing). Another, equally hack-ish and technically incorrect, but much simpler workaround you can use is:

source.playbackRate = paused?0.0000001:1;

Unfortunately, 0 is not a valid value for playbackRate (which would actually pause the sound). However, for many practical purposes, some very low value, like 0.000001, is close enough, and it won't produce any audible output.

UPDATE: This is only valid for Chrome. Firefox (v29) does not yet implement the MediaElementAudioSourceNode.mediaElement property.

Assuming that you already have the AudioContext reference and your media source (e.g. via AudioContext.createMediaElementSource() method call), you can call MediaElement.play() and MediaElement.pause()on your source, e.g.

source.mediaElement.pause();
source.mediaElement.play();

No need for hacks and workarounds, it's supported. If you are working with an <audio> tag as your source, you should not call pause directly on the audio element in your JavaScript, that will stop playback.

In 2017, using ctx.currentTime works well for keeping track of the point in the song. The code below uses one button (songStartPause) that toggles between a play & pause button. I used global variables for simplicity's sake. The variable musicStartPoint keeps track of what time you're at in the song. The music api keeps track of time in seconds.

Set your initial musicStartPoint at 0 (beginning of the song)

var ctx = new webkitAudioContext();
var buff, src;
var musicLoaded = false;
var musicStartPoint = 0;
var songOnTime, songEndTime;
var songOn = false;

songStartPause.onclick = function() {

  if(!songOn) {
    if(!musicLoaded) {
      loadAndPlay();
      musicLoaded = true;
    } else {
      play();
    }

    songOn = true;
    songStartPause.innerHTML = "||"  //a fancy Pause symbol

  } else {
    songOn = false;
    src.stop();
    setPausePoint();
    songStartPause.innerHTML = ">"  //a fancy Play symbol
  }
}

Use ctx.currentTime to subtract the time the song ends from when it started, and append this length of time to however far you were in the song initially.

function setPausePoint() {
  songEndTime = ctx.currentTime;
  musicStartPoint += (songEndTime - songOnTime);
}

Load/play functions.

function loadAndPlay() {
  var req = new XMLHttpRequest();
  req.open("GET", "//mymusic.com/unity.mp3")
  req.responseType = "arraybuffer";
  req.onload = function() {
    ctx.decodeAudioData(req.response, function(buffer) {
      buff = buffer;
      play();
    })
  }
  req.send();
}

function createBuffer() {
      src = ctx.createBufferSource();
      src.buffer = buff;
    }


function connectNodes() {  
  src.connect(ctx.destination); 
}

Lastly, the play function tells the song to start at the specified musicStartPoint (and to play it immediately), and also sets the songOnTime variable.

function play(){
  createBuffer() 
  connectNodes();
  songOnTime = ctx.currentTime;
  src.start(0, musicStartPoint);
}

*Sidenote: I know it might look cleaner to set songOnTime up in the click function, but I figure it makes sense to grab the time code as close as possible to src.start, just like how we grab the pause time as close as possible to src.stop.

I didn't follow the full discussion, but I will soon. I simply headed over HAL demo to understand. For those who now do like me, I would like to tell 1 - how to make this code working now. 2 - a trick to get pause/play, from this code.

1 : replace noteOn(xx) with start(xx) and put any valid url in sound.load(). I think it's all I've done. You will get a few errors in the console that are pretty directive. Follow them. Or not : sometimes you can ignore them, it works now : it's related to the -webkit prefix in some function. New ones are given. 2 : at some point, when it works, you may want to pause the sound. It will work. But, as everybody knows, a new pressing on play would raise an error. As a result, the code in this.play() after the faulty source_.start(0) is not executed. I simply enclosed those line in a try/catch :

      this.play = function() {
    analyser_ = context_.createAnalyser();

    // Connect the processing graph: source -> analyser -> destination
    source_.connect(analyser_);
    analyser_.connect(context_.destination);
  try{
    source_.start(0);
  }
  catch(e){
    this.playing = true;

    (function callback(time) {
      processAudio_(time);
      reqId_ = window.webkitRequestAnimationFrame(callback);
    })();
  }

And it works : you can use play/pause. I would like to mention that this HAL simulation is really incredible. Follow those simple steps, it's worth it !

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top