如何避免“播放(play())请求被暂停(pause())调用中断”的错误?

152

我做了一个网站,如果用户点击,它会播放声音。为了防止声音重叠,我不得不添加以下代码:

n.pause();
n.currentTime = 0;
n.play();

但这会导致错误: 播放请求被暂停调用中断
每次声音事件在另一个触发器之后立即触发时,都会出现这种情况。声音仍然可以正常播放,但我想防止不断弹出此错误消息。有什么建议吗?


这是一个错误还是更像是一条通知? - dandavis
这是一个错误,以下是完整的错误信息: 未捕获的(在 Promise 中)DOMException:播放请求被暂停调用中断。 - Owen M
7
这是一个较新的漏洞,不用担心:https://bugs.chromium.org/p/chromium/issues/detail?id=593273 - dandavis
3
简单的解决方法是不要立即连续调用播放或暂停。相反,使用媒体事件来确定何时暂停或播放。例如:onCanPlay()。所有这些答案都是肮脏的黑客行为。 - Martin Dawson
1
我有相反的问题。我试图先执行 n.play() 然后再 执行 n.pause()。对此,这篇Web基础知识文章描述了解决方案。简而言之:n.play().then(() => { n.pause()}) - totymedli
https://developer.chrome.com/blog/play-request-was-interrupted/ - Piet van Leeuwen
30个回答

119

最近我也遇到了这个问题——这可能是play()pause()之间的竞争条件。看起来有一个与此问题相关的参考或相关内容在这里

正如@Patrick所指出的那样,pause不会返回任何承诺(或其他任何东西),因此上述解决方案将不起作用。虽然MDN没有关于pause()的文档,但在WC3媒体元素草案中,它说:

media.pause()

如果必要的话,将paused属性设置为true,加载媒体资源。

因此,在超时回调中,一个人还可以检查paused属性。

根据这个很棒的SO答案,这里是一种你可以检查视频是否真正播放(或没有播放)的方法,这样你就可以安全地触发play()而不会出现错误。

var isPlaying = video.currentTime > 0 && !video.paused && !video.ended 
    && video.readyState > video.HAVE_CURRENT_DATA;

if (!isPlaying) {
  video.play();
}
否则,@Patrick答案 应该可行。

1
正如@regency-software所说,pause方法不返回Promise(.pause()没有文档),而只是“undefined”。因此,此解决方案仅适用于play()然后pause()的情况。 - Splact
谢谢提供信息 - 我已经更新了我的答案以反映@regency software发布的正确信息,他还提供了一个可行的解决方案 :-)。 - User 1058612
你是如何确定150毫秒的等待时间的?似乎是Chrome的问题: https://bugs.chromium.org/p/chromium/issues/detail?id=593273 - connect2krish
请参考@Regency Software答案了解超时原因。我在他们的答案基础上进行了扩展,这是一个很好的解决方案。此外,我的答案还引用了你在答案开头提供的链接。 - User 1058612
这对于流媒体视频不起作用。如果我初始化WebRTC视频,仍然会在控制台上抛出异常。 - Stepan Yakovenko
为什么它不能用于流媒体视频?尽管我不熟悉WebRTC流,但对我来说肯定可以用HLS。 - User 1058612

96

经过几个小时的搜索和努力,我找到了完美的解决方案

// Initializing values
var isPlaying = true;

// On video playing toggle values
video.onplaying = function() {
    isPlaying = true;
};

// On video pause toggle values
video.onpause = function() {
    isPlaying = false;
};

// Play video function
async function playVid() {      
    if (video.paused && !isPlaying) {
        return video.play();
    }
} 

// Pause video function
function pauseVid() {     
    if (!video.paused && isPlaying) {
        video.pause();
    }
}

之后,你可以尝试快速切换 播放/暂停它会正常工作


1
这就是我解决问题的方式。我认为这比依赖超时要好得多。 - Malcor
2
为什么它没有被标记为正确答案?这绝对是一个解决方案,而不是使用超时的黑客行为。 - jeron-diovis
17
为什么需要两个变量?我并不是在说这是一个不好的解决方案,只是试图理解它。 - benevolentprof
1
我不知道这在做什么以及它是如何工作的,但它正在运行。 - Abdalla Arbab
1
如果你调用了 playVid() 然后立即调用 pauseVid(),会发生什么?看起来 isPlaying 仍然是 false,所以 video.pause() 没有被调用。因此视频将继续播放,即使我最后调用的是 pauseVid()。这正确吗? - teedyay
显示剩余5条评论

27

我遇到了这个问题,并且有一种情况需要先使用pause()然后使用play(),但是当使用pause().then()时,我得到了undefined的结果。

我发现如果在暂停后150毫秒开始播放,就可以解决这个问题。(希望Google尽快修复)

playerMP3.volume = 0;
playerMP3.pause();

//Avoid the Promise Error
setTimeout(function () {      
   playerMP3.play();
}, 150);

如果你是指150毫秒延迟的话?我尝试了几个不同的值,最终选择了这个值来满足我的需求。我针对的是Chrome和Android平台,这个值适用于我的应用。它可能还可以更低。 - Elim Garak
4
因此,它几乎是随机值,与您的实现以外的任何内容都没有关系。感谢澄清! - y_nk
1
看起来像是Chrome的bug: https://bugs.chromium.org/p/chromium/issues/detail?id=593273 - connect2krish
14
不应该使用任意的setTimeout延迟来解决竞态条件问题。 - Gadget Blaster
树,我在这种情况下有3个播放器正在运行:声音轨道,潜意识轨道,然后我用相同的声音轨道进行交叉淡入淡出,以获得连续播放。 - Elim Garak
显示剩余2条评论

10

8

试一下

n.pause();
n.currentTime = 0;
var nopromise = {
   catch : new Function()
};
(n.play() || nopromise).catch(function(){}); ;

4
这个解决方案对我很有帮助:
n.cloneNode(true).play();

2

原因一 - 在等待播放承诺解决之前调用暂停

对于这种情况有太多的答案,所以我只会参考最好的文档:

https://developers.google.com/web/updates/2017/06/play-request-was-interrupted

原因二 - 在标签页未聚焦时调用播放

在这种情况下,当标签页未聚焦时,浏览器可能通过调用pause来中断play,以便为活动标签页节省资源。

因此,您可以在调用播放之前等待标签页被聚焦:


async function waitForTabFocus() {
  return new Promise((resolve, reject) => {
    const onFocus = () => { resolve(); window.removeEventListener('focus', onFocus) };
    window.addEventListener('focus', onFocus)
  })
}

if (!document.hasFocus()) await this.waitForTabFocus();
videoEl.play();

2

根据您解决问题的复杂程度,这可能会有所帮助:

var currentPromise=false;    //Keeps track of active Promise

function playAudio(n){
    if(!currentPromise){    //normal behavior
        n.pause();
        n.currentTime = 0;
        currentPromise = n.play();    //Calls play. Will store a Promise object in newer versions of chrome;
                                      //stores undefined in other browsers
        if(currentPromise){    //Promise exists
            currentPromise.then(function(){ //handle Promise completion
                promiseComplete(n);
            });
        }
    }else{    //Wait for promise to complete
        //Store additional information to be called
        currentPromise.calledAgain = true;
    }
}

function promiseComplete(n){
    var callAgain = currentPromise.calledAgain;    //get stored information
    currentPromise = false;    //reset currentPromise variable
    if(callAgain){
        playAudio(n);
    }
}

这有点过度,但在处理独特的 Promise 场景时非常有帮助。


2

这里提出的解决方案对我不起作用或太大了,所以我正在寻找其他东西,并在bountysource.com/issues/上找到了由@dighan提出的解决方案。

这是解决我的问题的代码:

var media = document.getElementById("YourVideo");
const playPromise = media.play();
if (playPromise !== null){
    playPromise.catch(() => { media.play(); })
}

它仍然会在控制台中抛出一个错误,但至少视频正在播放 :)


2

我有一个类似的问题,我认为做这样的事情是最容易的:

video.play().then(() => {
    video.pause();
    video.currentTime = 0;
    video.play();
})

无论视频是否正在播放,最终视频都将被暂停,然后重置为零并重新播放。
即使视频已经在播放,调用play()也可以正常工作,它会像预期的那样返回一个promise。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接