【问题标题】:Web Audio API resume from pauseWeb Audio API 从暂停中恢复
【发布时间】:2012-07-15 09:52:06
【问题描述】:

我经常读到无法使用Web Audio API 暂停/恢复音频文件。
但现在我看到了example,他们实际上使暂停和恢复它成为可能。我试图弄清楚他们是怎么做到的。我想也许source.looping = false是关键,但它不是。
现在我的音频总是从头开始重新播放。

这是我当前的代码

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;
};

有人知道该怎么做才能让它发挥作用吗?

【问题讨论】:

    标签: javascript html google-chrome html5-audio web-audio-api


    【解决方案1】:

    如果不花任何时间检查示例的来源,我会说您会想要使用 AudioBufferSourceNode (https://dvcs.w3.org/hg/audio/raw-file/tip/webaudio/specification.html#methodsandparams-AudioBufferSourceNode) 的 noteGrainOn 方法

    只需跟踪调用 noteOff 时进入缓冲区的距离,然后在新的 AudioBufferSourceNode 上恢复时从那里执行 noteGrainOn。

    这有意义吗?

    编辑: 请参阅下面的 cmets 了解更新的 API 调用。

    2019 年第 2 版:请参阅 MDN 了解更新的 API 调用; https://developer.mozilla.org/en-US/docs/Web/API/AudioBufferSourceNode/start

    【讨论】:

    • 好吧,他在示例中没有使用noteGrainOn()(但仍然无法弄清楚他是如何做到的)。但无论如何,这就像一个魅力,所以我会把它作为一个公认的答案。谢谢你:)
    • noteGrainOn() 现在已在规范中重命名,noteOn() 也是如此——两者现在都是 play(),有或没有颗粒偏移参数。不过,还没有看到这种支持。
    • 它实际上是 start(带或不带参数 start(howSoonRelativeToCurrentTime, fromSecondInBuffer, forDuration)),它似乎在 Chrome Canary 中工作
    • startstop 只能使用一次:w3.org/TR/webaudio/#methodsandparams-AudioBufferSourceNode,但是可以从同一个AudioBufferSourceNode 中创建一个新的AudioBufferSourceNode 并在最后一个已停止(使用offset)。
    • 所有这些现在都被弃用了,所以这个答案是没用的。没有noteGrainOn,没有noteOn,没有play,什么都没有。
    【解决方案2】:

    对于 chrome 修复,每次你想播放声音时,设置如下:

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

    【讨论】:

      【解决方案3】:

      我没有关注完整的讨论,但我很快就会关注。我只是通过 HAL 演示来了解。对于现在喜欢我的人,我想告诉 1 - 如何使这段代码现在工作。 2 - 从这段代码中获得暂停/播放的技巧。

      1 : 用 start(xx) 替换 noteOn(xx) 并将任何有效的 url 放入 sound.load()。我想这就是我所做的一切。您将在控制台中收到一些非常有指导意义的错误。跟着他们。或不:有时你可以忽略它们,它现在可以工作了:它与某些函数中的 -webkit 前缀有关。新的被给予。 2:在某些时候,当它起作用时,您可能想要暂停声音。 它会起作用的。但是,众所周知,新的比赛压力会引发错误。结果,错误的source_.start(0)之后的this.play()中的代码没有被执行。 我只是将这些行包含在 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);
          })();
        }
      

      它有效:您可以使用播放/暂停。 我想提一下,这个HAL simulation 真的很不可思议。按照这些简单的步骤,这是值得的!

      【讨论】:

        【解决方案4】:

        在 2017 年,使用 ctx.currentTime 可以很好地跟踪歌曲中的点。下面的代码使用一个按钮 (songStartPause) 在播放和暂停按钮之间切换。为了简单起见,我使用了全局变量。变量 musicStartPoint 跟踪您在歌曲中的时间。音乐 api 以秒为单位跟踪时间。

        将您的初始 musicStartPoint 设置为 0(歌曲的开头)

        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
          }
        }
        

        使用 ctx.currentTime 从歌曲开始的时间减去歌曲结束的时间,并将这个时间长度附加到您最初在歌曲中的距离。

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

        加载/播放功能。

        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); 
        }
        

        最后,播放函数告诉歌曲从指定的 musicStartPoint 开始(并立即播放),并且还设置了 songOnTime 变量。

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

        *旁注:我知道在 click 函数中设置 songOnTime 可能看起来更简洁,但我认为尽可能接近 src.start 获取时间码是有意义的,就像我们如何获取暂停时间一样尽可能靠近 src.stop。

        【讨论】:

          【解决方案5】:

          实际上网络音频 API 可以为您完成暂停和播放任务。它知道音频上下文的当前状态(运行或暂停),因此您可以通过以下简单方式执行此操作:

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

          我希望这会有所帮助。

          【讨论】:

            【解决方案6】:

            在当前的浏览器(Chrome 43、Firefox 40)中,现在有可供 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';
                });  
              }
            }
            

            (来自https://developer.mozilla.org/en-US/docs/Web/API/AudioContext/suspend的修改示例代码)

            【讨论】:

              【解决方案7】:

              更新:这仅对 Chrome 有效。 Firefox (v29) 尚未实现 MediaElementAudioSourceNode.mediaElement 属性。

              假设您已经拥有 AudioContext 引用和媒体源(例如通过AudioContext.createMediaElementSource() 方法调用),您可以在源上调用MediaElement.play()MediaElement.pause(),例如

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

              无需破解和变通方法,它受支持。 如果您使用 <audio> 标签作为源,则不应直接在 JavaScript 中的音频元素上调用 pause,这将停止播放。

              【讨论】:

                【解决方案8】:

                WebAudio API 中缺少内置暂停功能对我来说似乎是一个重大疏忽。将来可能会使用计划的 MediaElementSource 来执行此操作,这将使您可以将元素(支持暂停)连接到 Web 音频。目前,大多数解决方法似乎都是基于记住播放时间(例如 imbrizi 的回答中所述)。这样的解决方法在循环声音(实现是否无间隙循环?)以及允许动态更改声音的播放速率(因为两者都会影响时间)时存在问题。另一个同样骇人听闻且在技术上不正确但您可以使用的更简单的解决方法是:

                source.playbackRate = paused?0.0000001:1;
                

                不幸的是,0 不是一个有效的回放速率值(它实际上会暂停声音)。但是,对于许多实际用途,一些非常低的值(例如 0.000001)已经足够接近,并且不会产生任何声音输出。

                【讨论】:

                • 这不是一个好主意,因为您会阻止浏览器清理任何未使用的音频缓冲区。按原样使用 API 有点笨拙,但出于性能和内存效率的原因,它是这样设计的。像 OPs 问题中这样的小型包装库是一种更好的方法。
                【解决方案9】:

                Oskar 的回答和 ayke 的评论非常有帮助,但我错过了一个代码示例。所以我写了一个:http://jsfiddle.net/v3syS/2/希望对你有帮助。

                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);
                

                【讨论】:

                • 您最好使用ctx.currentTime 而不是Date.now()。这样,在暂停和恢复上下文时可以保留时间。而且我认为它甚至可能更准确。
                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2015-11-24
                • 2013-12-12
                • 1970-01-01
                相关资源
                最近更新 更多