Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Chrome autoplay policy changes #939

Open
robbue opened this Issue Apr 19, 2018 · 61 comments

Comments

Projects
None yet
@robbue
Copy link

robbue commented Apr 19, 2018

Chrome 66 have changed its autoplay policy:

An AudioContext must be created or resumed after the document received a user gesture to enable audio playback.
https://developers.google.com/web/updates/2017/09/autoplay-policy-changes#webaudio

mobileAutoEnable could be updated to adjust for Chrome (desktop and mobile) as well.

@goldfire goldfire added this to the 2.1.0 milestone May 4, 2018

@inear

This comment has been minimized.

Copy link

inear commented May 6, 2018

Maybe a subject of its own, but could we somehow register the start time of a play call and use that to seek to the offset position when the AudioContext is resumed? This to avoid having all created instances fire of at the same time when resumed. Sounds are very often timed to visual events which is so frustrating with the new policy. This would be a very good reason for more devs to use this library.

@TxAg12

This comment has been minimized.

Copy link

TxAg12 commented May 6, 2018

Maybe we could organize to unbreak the internet? I cannot even imagine the economic cost of everyone--most working for free--spending time to hack around these content restrictions.

@sammcgrail

This comment has been minimized.

Copy link

sammcgrail commented May 7, 2018

Hoping for a fix in 2.1.0 ! Gosh darn it chrome you broke my little toy app!

@goldfire

This comment has been minimized.

Copy link
Owner

goldfire commented May 8, 2018

FYI, the mobileAutoEnable system should still be working on desktop Chrome as well. I just marked it for 2.1.0 as it now makes sense to update the API naming since this isn't a mobile-only issue anymore. If anyone is seeing issues with this not working with Chrome desktop then that would be a more urgent matter.

@dangrima90

This comment has been minimized.

Copy link

dangrima90 commented May 9, 2018

@goldfire I'm having issues due to this Chrome update. In the application I'm working on every time a sound is loaded I'm seeing the warning mentioned above, which results in the sounds not playing.

Since in Google's autoplay policy (https://developers.google.com/web/updates/2017/09/autoplay-policy-changes#webaudio) it is mentioned that muted autoplay is allowed I've tried to load the application sounds with the mute setting set to true - but so far I didn't see any difference. I don't know if there's any workaround at the moment, or maybe I'm doing something wrong.

Since you mentioned mobileAutoEnable, am I correct in assuming that the mobileAutoEnable is enabled by default? And if so, sounds are played once there's the first user interaction?

@goldfire

This comment has been minimized.

Copy link
Owner

goldfire commented May 9, 2018

There is currently an open Chromium report about this that is picking up a lot of support. If we can at least get them to add a way to detect this it would make it much easier to handle within the library: https://bugs.chromium.org/p/chromium/issues/detail?id=840866.

@dangrima90

This comment has been minimized.

Copy link

dangrima90 commented May 9, 2018

Thank you for your reply, let's hope we'll have a way forward soon. I'm a bit disappointed with this change but that's the life of a developer I guess haha.

@inear

This comment has been minimized.

Copy link

inear commented May 9, 2018

It's not just the problem with resuming the AudioContext, it's also about what should happen when it does. Right now all the sounds that I've tried playing before it's resumed (showing the warning) is played in the same time when resumed.

@jmo84

This comment has been minimized.

Copy link

jmo84 commented May 9, 2018

Everybody go to the bug report and vote for it. https://bugs.chromium.org/p/chromium/issues/detail?id=840866

@Sieabah

This comment has been minimized.

Copy link

Sieabah commented May 12, 2018

@goldfire I believe there is a way to test it by checking the state of the AudioContext.

From their docs there is this:

To detect whether browser will require user interaction to play audio, you can check the state of the AudioContext after you've created it. If you are allowed to play, it should immediately switch to running. Otherwise it will be suspended. If you listen to the statechange event, you can detect changes asynchronously.

At least that way people using Howler will be able to inspect the state. I've been looking through how howl.state() works and if we had access to the raw state value from the audio context it'd allow us to check if interaction is required.

Edit: As it currently stands, state() only returns if it's loaded essentially and playing() will be true despite it not actually playing.

Somewhat related

@goldfire

This comment has been minimized.

Copy link
Owner

goldfire commented May 12, 2018

@Sieabah Unfortunately, this behavior is not consistent across browsers, which is why howler tries to resume the audio context on play. However, if the audio is still locked, the promise on resume neither resolves nor rejects, essentially leaving the state in limbo. I'm sure we can find a way to work around this, but I'm hoping we hear something soon on what/if anything is going to be changed.

@Sieabah

This comment has been minimized.

Copy link

Sieabah commented May 12, 2018

@goldfire Right, but you could detect this issue by detecting (for chrome) whether the state is suspended or not immediately after play? I'm all for the universal solution, but currently there is no way to use howler is a reactive way.

I'm against google making this huge change just because they're a huge browser, but I don't believe they're going to back down on this. Is there any reason why you use resume() and not play() to check if audio is locked?

Off the wall solution, would it be possible to play a tiny sound file that is dead silent at init to see if audio is locked?

@goldfire

This comment has been minimized.

Copy link
Owner

goldfire commented May 14, 2018

I just added this Chromium bug relating to the issue with AudioContext.resume: https://bugs.chromium.org/p/chromium/issues/detail?id=842921

@goldfire

This comment has been minimized.

Copy link
Owner

goldfire commented May 16, 2018

Chrome has reverted this change on Web Audio (not on HTML5 Audio) and has said it will come back in Chrome 70, due for release in October. The AudioContext issue is also fixed in Chrome 68.

@Sieabah

This comment has been minimized.

Copy link

Sieabah commented May 16, 2018

@goldfire Great news!

@robbue

This comment has been minimized.

Copy link
Author

robbue commented May 16, 2018

Good news, but only delays the massive side-effects of this change until October. But at least we (and Chrome) have some time to smooth things out.

@goldfire

This comment has been minimized.

Copy link
Owner

goldfire commented Jul 12, 2018

Commit 310736b updates mobileAutoEnable to work on Chrome as well. I'm going to keep this open though as the naming really needs to updated as a breaking change since this will no longer only apply to mobile.

@Sieabah

This comment has been minimized.

Copy link

Sieabah commented Jul 12, 2018

@goldfire Where does howler stand in supporting the new chrome autoplay api changes?

@abelsonlive

This comment has been minimized.

Copy link

abelsonlive commented Jul 13, 2018

I'm using react-howler, but I had some luck detecting instances when autoplay is blocked by checking if window.Howler.state == 'suspended'

@Sieabah

This comment has been minimized.

Copy link

Sieabah commented Jul 13, 2018

@abelsonlive Seems messy to check the window object directly.

@dangrima90

This comment has been minimized.

Copy link

dangrima90 commented Aug 3, 2018

Any way forward on this? At the moment the following warning is being shown in the console:

The Web Audio autoplay policy will be re-enabled in Chrome 70 (October 2018). Please check that your website is compatible with it. https://goo.gl/7K7WLu

I'm not understanding what I should do, will this be handled by howler or must the application find a solution for this?

@sajjanbalar

This comment has been minimized.

Copy link

sajjanbalar commented Aug 9, 2018

Hi,

I was able to catch this error and handle it using the onplayerror handler. There is not much you can do to stop this from happening, but handling it and showing a play button (instead of a pause) and stuff like that is easy.

@goldfire

This comment has been minimized.

Copy link
Owner

goldfire commented Aug 9, 2018

Sorry about the delay, I've been swamped lately. I'm working on some changes to address this though.

@goldfire

This comment has been minimized.

Copy link
Owner

goldfire commented Aug 10, 2018

Okay, so I'm considering two different approaches here:

  1. If the audio is locked, howler just throws a playerror and discards the playback.
  2. As suggested above by @inear, the time the playback was started is tracked and then when audio is unlocked, howler either seeks to the position the sound would have been at or discards the playback if it would have already concluded.

Option 2 I think would be the most beneficial for most applications, but does anyone have any scenarios where this might be best left to the application to handle (and thus going with option 1)?

@Sieabah

This comment has been minimized.

Copy link

Sieabah commented Aug 10, 2018

The issue with Option 2 is you wouldn't know that the play failed unless you kept checking either after playing or in a loop elsewhere. Which creates an awkward loop of logic whereas if you errored on play you could create some popup asking for user input.

@goldfire

This comment has been minimized.

Copy link
Owner

goldfire commented Aug 11, 2018

@Sieabah The library would still be detecting that the audio is locked either way, that would be able to be checked at a global level.

@jmo84

This comment has been minimized.

Copy link

jmo84 commented Aug 12, 2018

Option 2 sounds like the best option but please keep emitting the playerror event described in Option 1. The only thing I can think of that would be a problem with Option 2 is if it adds too much code to the library.

@inear

This comment has been minimized.

Copy link

inear commented Aug 12, 2018

I would appreciate the functionality in option 2 to be available if possible, but could live with using a option-parameter to activate sync to intended global time for sounds that is part of a visual timeline. And use option 1 as default. With that per-sound-parameter we could use it in places where we don't know of context is created, like in cinematic intros, or after the first load. Also, we then don't magically solve the issue, but encourage devs to think twice about their solution and learn to design for the autoplay policy, because it seems like it will stick around. The option 2 is a way out for existing sites where we don't have control over the flow. Or if we just can't have an interaction point at the beginning of the experience.

@Sieabah

This comment has been minimized.

Copy link

Sieabah commented Aug 13, 2018

@goldfire So the intended solution is to have code which checks every x seconds or so to see if it's locked and provide a prompt? How would you trigger all the sounds to play again or would Howler automatically do that?

It just seems messy to have check at the global scope.

@Sieabah

This comment has been minimized.

Copy link

Sieabah commented Sep 21, 2018

Google is poorly handling this clusterfuck

@goldfire

This comment has been minimized.

Copy link
Owner

goldfire commented Oct 3, 2018

I just pushed a155a19 to master, which fixes playing() not showing the correct value when audio is locked.

@fallingC0de

This comment has been minimized.

Copy link

fallingC0de commented Oct 8, 2018

Thanks for coding a workaround into the new version of Howler, Gold!

@goldfire

This comment has been minimized.

Copy link
Owner

goldfire commented Oct 18, 2018

FYI, the autoplay change has again been pushed back to Chrome 71 in December.

@daniloacim

This comment has been minimized.

Copy link

daniloacim commented Nov 18, 2018

Does it mean that we can't enable autoplay on Chrome until Chrome 71 version ?

@goldfire

This comment has been minimized.

Copy link
Owner

goldfire commented Dec 10, 2018

Commit 8cfb397 has changed the name of this to autoUnlock since it applies to more than just mobile. There is still no way to directly test for the audio being locked, though there are some proposals in the works. Chrome is going ahead with the change anyway, which means you need to make sure your first audio happens in a user interaction for it to work. I'll leave this open as this continues to evolve.

@aaclayton

This comment has been minimized.

Copy link

aaclayton commented Dec 10, 2018

Hey goldfire, thanks for the update. Any chance you could provide a high level summary of the recommended usage pattern given the changes, I've been following along. but not super closely.

Any chance of a small example code illustrating how to ensure that the first audio playback is triggered by a "user interaction" and how using the autoUnlock flag would factor in?

I can probably figure this out through some trial and error plus searching about, but if there's any existing documentation or example code which explains how to comply with the policy changes I know it would help me out and I suspect others as well who may be discovering this issue.

Either way, thanks for your work to continue improving your great library!

@Sieabah

This comment has been minimized.

Copy link

Sieabah commented Dec 11, 2018

@aaclayton The general rule is do not play audio unless it's after a direct user gesture, only then can you play.

Essentially this change makes "autoplay" unreliable until your site has a high usage score, of which you still can't rely on.

@aaclayton

This comment has been minimized.

Copy link

aaclayton commented Dec 11, 2018

Hey @Sieabah, thanks for your clarification, but I'm afraid this was the portion of the change that I did already understand.

What isn't clear to me (and I likely need to just spend some time googling about) is what the best practices are to associate the audio playback with a user gesture. For example, my app has a click listener, when the button is pressed I get Howl to begin playback of some audio. Does Howler expose a convenient API to determine whether or not the audio context is available to be used?

EDIT: Nevermind, my example usage does in fact work properly. So I was mixing something up which caused confusion on the root cause of the error.

For example.... simple usage:

$("#mybutton").click(ev => {
   new Howl({src: "path", autoplay: true});
});

Currently exposes a Chrome warning for me, although clearly this audio is played as the result of a user gesture. What's the right way to do this?

@goldfire

This comment has been minimized.

Copy link
Owner

goldfire commented Dec 11, 2018

@aaclayton Create the new Howl outside of the click listener and then call play() inside the click.

@Sieabah

This comment has been minimized.

Copy link

Sieabah commented Dec 12, 2018

@aaclayton You never explicitly played it inside. It was an audio tag created async from the click event handler but it's "autoplaying" which is unreliable.

It should work if you create the howler instance and call play in the same click handler. Alternatively the approach of having the howler created outside and playing it inside also works. I wouldn't create howler instances unless they self destruct after sometime.

@aaclayton

This comment has been minimized.

Copy link

aaclayton commented Dec 12, 2018

Thanks for the feedback, both of you. Appreciate the follow-up!

@shanemielke

This comment has been minimized.

Copy link

shanemielke commented Dec 15, 2018

Hey all. Sorry for the long winded buildup just giving context. I have an interactive comic book for a client that I started well over a year ago (before all of the autoplay stuff). The comic is spread out across 17 chapters (which are on 17 individual html pages) and contains multiple sound objects starting/playing/stopping in sync to animations on a page and soon even a surround sound version. Basically, there's a lot going on once you enter each chapter of the comic. Unfortunately, It's too late to try and combine all of this work into a single page experience with a single enter button. Because of all the autoplay issues across the various browsers & mobile I currently preload all of the imagery/audio and after the preloading is complete I animate in an "ENTER" button for ALL pages regardless of browser/desktop/mobile. That is not a great experience but it works. I would like to see if I can reduce the need for the ENTER button on every page if possible.

I've read about the autoplay policies like Media Engagement Index (MEI) and I'm seeing that working on the dev version of the comic site. On chrome 71 the audio now plays when I change up the code on the pages to bypass the enter button after the preloader. Audio now plays even if I have have not interacted with the page. I am assuming this is because I've refreshed these pages hundreds of times the past year and or just in the last couple of hours tonight and Chrome knows it. This is great for me but it makes me nervous because it's now a little harder to test the site under typical user situations of a first time or unfrequent visitor.

Given my above situation, I'm looking for current best practices to try and know whether or not the audio has been unlocked. What I'm reading here is that my only strategies with howler is to try and play a small mp3 file (without any loud audio) when any page in the site is preloading and then listen for the "onplayerror". Then based upon if there's an error show the button? Does that sound like a safe solution? Or am I missing some variable that I can check that would give me this? Thanks in advance for any help and taking the time to read this.

@Sieabah

This comment has been minimized.

Copy link

Sieabah commented Dec 15, 2018

@shanemielke So unfortunately because it's based on metrics you can't get around the autoplay per-page. That's the entire point of why they did it this way I assume. You're going to have to make it a SPA app if you don't want to have to have an enter page per page.

The only reason why it's playing while you're developing because you're engaging with the url enough times (I've experienced this locally). You have to start chrome from the CLI with some extra flags to be able to test from the perspective of a user first coming to the page. I haven't found a better way to do this.

I think the best approach, is to have an enter per page, it's not a great experience but it's the experience chrome wants for the future. For some stupid reason.

@ellisio

This comment has been minimized.

Copy link

ellisio commented Dec 19, 2018

We use Howler to play pops when our chat application receives a new message to be read, using Vue. Below is a solution to eliminate the warning, and to only play pops on unreads once our users actually engage with the page (by activating a conversation, for example).

mixins/popper.js:

import { Howl } from 'howler';

let Howler = null;
let canPlayPop = true;

export default {
    methods: {
        pusherMessageReceived() {
            // This is called by our pusher.listen() config elsewhere in the app.
            this.playPop();
        },

        playPop() {
            // This will bail out until Howler has been initialized, or if we're currently playing a pop sound.
            if (!Howler || !canPlayPop) {
                return;
            }

            canPlayPop = false;
            Howler.play();
        },

        waitForClickForHowler() {
            // Remove the event listener right away so we don't call this more than once for a click happy user.
            document.removeEventListener('click', this.waitForClickForHowler);

            // Initialize our Howl instance.
            Howler = new Howl({
                volume: 0.4,
                src: ['/sounds/notification.mp3'],
                preload: true,
                onend: () => {
                    canPlayPop = true;
                },
            });
       },
    },

    mounted() {
        document.addEventListener('click', this.waitForClickForHowler);
    },
};

This mixin is imported into our primary App.vue. Once a user clicks anywhere on the document, we initialize Howler, and then when a new message comes in we can play a pop. No warning, functional, and adheres to the Chrome reasoning for this behavior.

@Sieabah

This comment has been minimized.

Copy link

Sieabah commented Dec 20, 2018

@ellisio I'm curious what your Howler global variable is set to after the click event occurs.

I still think from a usability point where you load the page (and are logged in) that page should be able to play the pops. What about the use case where they don't click anything but just load the page and switch tabs, waiting for a chat?

@ellisio

This comment has been minimized.

Copy link

ellisio commented Dec 20, 2018

When I’m back at the computer I can post a JSON string of my Howler global variable.

I agree about that use case whole heartedly. It’s annoying that Howler is blocked until interaction with the page happens. Since it’s blocked, this way just eliminates the annoying console warning.

We can initialize the Howler variable in mounted(), which causes the console warning, and will still play the pop after a user clicks on the page at least once. Until a click happens, no pops play.

@Sieabah

This comment has been minimized.

Copy link

Sieabah commented Dec 20, 2018

No need to paste the whole output, I was just wondering from your code if you may have overwritten the global variable. I guess it'd just overwritten in your scope which could be fine.

@Sieabah

This comment has been minimized.

Copy link

Sieabah commented Jan 22, 2019

Seems the issues are still intermittent... If you start with a click event and call load(), playing from the on('loaded') event does not allow you to play.

@MaxYari

This comment has been minimized.

Copy link

MaxYari commented Jan 29, 2019

Hey everyone, having a problem recently with the fact that all audio plays simultaneously after a user action, I was wondering is there a cross-browser way to detect that chrome "locked" the audio context? any property on a Howler obj? I have a wrapper already so would rather just check for the possibility to play and not play audio at all instead of queuing it to destroy user's eardrums.

I would've poked around myself but I'm not sure how do I force chrome to lock my audio context as if I'm entering the page for the first time, incognito mode doesn't help... any tips?

@MaxYari

This comment has been minimized.

Copy link

MaxYari commented Feb 8, 2019

dead silent :(

@goldfire

This comment has been minimized.

Copy link
Owner

goldfire commented Feb 8, 2019

@MaxYari There is currently no way to detect this, though there are some proposal currently being evaluated at the standards level. Once those make their way into browsers then we'll get them into howler. Google's documentation tells you how to force the audio to be locked: https://developers.google.com/web/updates/2017/09/autoplay-policy-changes.

@MaxYari

This comment has been minimized.

Copy link

MaxYari commented Feb 16, 2019

Hm, so the fact that all sounds then play simultaneously after user action - is this a default chrome behaviour or something that howler does? There are sites that don't have this problem of all audio blowing your speakers upon user actions.

@nicholashza

This comment has been minimized.

Copy link

nicholashza commented Feb 21, 2019

Having the same issue with sprites all playing at once. Based on a Howler internal investigation, it seems Howler cancels the end timer if audio is suspended (removing the clear timer does not fix this), and when it unlocks triggers the playWebAudio function. The function then essentially triggers a play on all sprites/contexts even if the sprites duration has already passed since the call to play. Calling stop in the locked state at least causes it to not play. But even if you play a bunch of 1 second sprites, tapping the screen 5 minutes later causes every single one to play at once.

@MaxYari

This comment has been minimized.

Copy link

MaxYari commented Mar 20, 2019

@goldfire maybe I've put it wrong while asking about it, but https://developers.google.com/web/updates/2017/09/autoplay-policy-changes that you've linked does describe (or at least gives some idea) how to detect that audio is locked by browser/can't be played, so it's possible to omit any playback attempts in such case. Thanks for the link.
For anyone interested: In essence, I check for Howler.state right after I attempt to construct any Howls (after all of my new Howl(...) initialisations). If Howler.state == "suspended" I create a bool signalling that audio is locked and page interaction is required. Whenever I want to play a sound (I have a wrapper function) I check for that bool and don't play anything if true. I also listen on window for click events and as soon as such arrives - set the aforementioned bool to false.

@chuski1212

This comment has been minimized.

Copy link

chuski1212 commented Apr 5, 2019

So right now there is a way to detect Howler audio blocked by chrome policy?

@gevgeny

This comment has been minimized.

Copy link

gevgeny commented Apr 17, 2019

@cmbuckley You can. Just play test sound and check onerror: https://github.com/goldfire/howler.js/#mobilechrome-playback

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.