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
The AudioContext was not allowed to start #22
Comments
Hm that's a bummer :/ For context: initially (before this repo existed) I created a When I switched to Howler, the warning disappeared, so I figured they knew something I didn't 🤷🏻♂️ Unfortunately I don't see much of a workaround (though I'd accept PRs if anyone has ideas that don't compromise the user experience!) |
+1 I'm also getting this error @joshwcomeau. I'm having issues playing the audio correctly, also. |
Yeah this seems to be an issue -- Howler defaults to attempting to auto-unlock the audio but that doesn't seem to be working. I'm trying to implement a use-sound use in a web-based application here, but pretty much everything i try just fails to get it working correctly -- The first attempt to play back any sound inside a specific React component fails. Not the first time in the application, but the first for each independent component. It fails without error, usually, but sometimes with complaints about the AudioContext not being allowed. I've got my main application, which has a button that takes you to a menu. On that menu are a bunch of on/off switches. The intent is to have the on/off switches click nicely. The switches only work after the first time. So, I threw in a click when you press the menu button. In that case, the click only occurs the second time you click the menu button, and still only the second time you click the on/off switch. It's very weird. Perhaps this snippet from the Howler docs would help, but I can't see any obvious way of implementing it here:
Perhaps later today or tomorrow I can actually try sticking that entire snippet into my app, instead of using use-sound and see if that works. |
We ended up rolling our own Howler hooks due to this issue. We came up with an elegant solution: Simply waiting for user interaction as the error states. First need a hook that tracks first user interaction events according to the specs: import { useEffect, useState } from 'react';
const events = ['mousedown', 'touchstart'];
export default function useInteraction() {
const [ready, setReady] = useState(false);
const listener = () => {
if (ready === false) {
setReady(true);
}
};
useEffect(() => {
events.forEach((event) => {
document.addEventListener(event, listener);
});
return () => {
events.forEach((event) => {
document.removeEventListener(event, listener);
});
};
}, []);
return ready;
} Then we wrapped howler in a simple async dynamic import hook that only loads it after the first user interaction. import { useState, useEffect } from 'react';
import useInteraction from '../useInteraction';
export default function useAudio(options) {
const [audio, setAudio] = useState();
const interacted = useInteraction();
useEffect(() => {
async function createAudoContext() {
const { Howl } = await import('howler');
setAudio(new Howl(options));
}
if (interacted) {
createAudoContext();
}
return () => {
if (audio) {
audio.unload();
}
};
}, [options]);
const ready = Boolean(interacted && audio);
return { audio, ready };
} All this in only a few Kb of js. Simply use it like this: const UsersList = ({ users }) => {
const { audio, ready } = useAudio({ src: "https://g.com/ding.mp3" });
useEffect(() => {
if (ready) {
audio.play()
}
}, [users.length, ready]);
return <div>{users}</div>;
}; (pseudo code) |
@andrewmclagan I really like your guys' solution, but I ran into some issues with it. For some reason, using the hook was causing infinite looping for me. I also found that I didn't need to load Howler asynchronously—I didn't see the Here's my modified version of your import { Howl } from "howler";
import { useEffect, useRef } from "react";
import useInteraction from "./use-interaction";
export default function useAudio(soundPath) {
const hasInteracted = useInteraction();
const audioRef = useRef();
useEffect(() => {
if (!hasInteracted) {
return;
}
let audio = new Howl({ src: soundPath });
audioRef.current = audio;
return () => audio.unload();
}, [ hasInteracted, soundPath ]);
return () => audioRef.current?.play();
} Thanks for posting this! It really helped out my project a lot. |
Hm, so it seems like Howler / the audio file are only loaded after the user interacts with the page? I'd rather pre-load everything so that the sound can be triggered the moment the user interacts with the page. As annoying as the console warning is, I think that's the better trade-off here |
hello sir, can i see full of your code to play audio? |
I just bumped into this issue too, and I only needed some background music... Your solutions @andrewmclagan @LandonSchropp worked just great! But then I went to the Howler API docs and found an even better way. Howler has its own "unlock" feature and can listen to the errors, this can be used to help us. And finally, my hook looks like that: import { Howl } from 'howler'
import { useEffect, useState } from 'react'
import clown from '@assets/music/clown.mp3'
export const useMusic = () => {
const [audio, setAudio] = useState<Howl | null>(null)
useEffect(() => {
const howl = new Howl({
src: clown,
onplayerror: (e, d) => {
console.log(e, d)
howl.once('unlock', () => {
howl.play()
})
},
loop: true,
volume: 0.25,
autoplay: true,
})
setAudio(howl)
return () => {
howl.unload()
}
}, [])
return [audio] as const
} This still will create a bunch of warnings in the console, but works! Note that I have Linked issues: |
I get a warning on page load even when I haven't actually called
play()
yet. It seems like this is an issue in howler because they callnew AudioContext()
immediately.Is it possible to somehow workaround this in
use-sound
?The text was updated successfully, but these errors were encountered: