-
Notifications
You must be signed in to change notification settings - Fork 3.7k
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
[Feature]: Add option to disable AudioContext autoplay #33590
Comments
Could you provide a full repro case and example of proposed API? |
It may not be possible without changing the browser build settings. See bootstrap.diff:23122 for the webkit build for example. I assume there's something similar for the chromium build. It might be better to leave the autoplay build settings alone so autoplay is disabled by default, and then add CLI flags for each browser to enable it. So for chromium the flag to set would be If it were done that way then |
@andy-fillebrown can you share a repro where the behavior differs from the stock browsers? |
The following test passes in all 3 browsers on macOS for me: import { test, expect } from '@playwright/test';
test("context audio", async ({ page }) => {
await page.route("**/*", async (route) => {
console.log('server response');
route.fulfill({
status: 200,
contentType: 'text/html',
body: `<script>
function onLoad() {
const log = document.getElementById('log');
const audio = new AudioContext();
log.innerHTML = 'State: ' + audio.state;
}
</script>
<body onload="onLoad()">
<div id="log"></div>
</body>`,
});
});
await page.goto("http://127.0.0.1:8080/audio.html");
await expect(page.locator('#log')).toHaveText('State: suspended', { timeout: 1000 });
}); |
Changing that test to make it start a sound makes it pass for chromium and firefox, but fail for webkit on macOS for me: test("context audio", async ({ page }) => {
await page.route("**/*", async (route) => {
console.log("server response");
route.fulfill({
status: 200,
contentType: "text/html",
body: `<script>
async function onLoad() {
const log = document.getElementById('log');
const audioContext = new AudioContext();
const gainNode = new GainNode(audioContext);
gainNode.connect(audioContext.destination);
gainNode.gain.value = 0.025;
const sineNode = new OscillatorNode(audioContext);
sineNode.connect(gainNode);
sineNode.start();
await new Promise((resolve) => setTimeout(resolve, 2000));
log.innerHTML = 'State: ' + audioContext.state;
}
</script>
<body onload="onLoad()">
<div id="log"></div>
</body>`,
});
});
await page.goto("http://127.0.0.1:8080/audio.html");
await expect(page.locator("#log")).toHaveText("State: suspended", { timeout: 5000 });
}); And doing it more simply with test("AudioContext should stay suspended", async ({ page }) => {
const audioContextState = await page.evaluate(async () => {
const audioContext = new AudioContext();
const gainNode = new GainNode(audioContext);
gainNode.connect(audioContext.destination);
gainNode.gain.value = 0.025;
const sineNode = new OscillatorNode(audioContext);
sineNode.connect(gainNode);
sineNode.start();
await new Promise<void>((resolve) => setTimeout(resolve, 5000));
return audioContext.state;
});
expect(audioContextState).toBe("suspended");
}); |
Thanks, I can reproduce it.
This is probably because each |
Ok, thanks for the info. Is there a way to prevent |
The test is actually consistent with vendor browsers behavior. Safari also creates the context in 'running' state on my macOS machine, so it seems to be working as intended.
No. But you can probably trigger the code with intermediate setTimeout . |
How are you determining this? When I run a webpack dev server using that example, Safari creates the context as 'suspended', which is what I would expect. How are you getting Safari to create the context as 'running' without user interaction?
Can you give an example of triggering the code with an intermediate setTimeout? I tried it with a setTimeout and got the same failure: test("AudioContext should stay suspended", async ({ page }) => {
const audioContextState = await page.evaluate(async () => {
return new Promise<string>((resolve) =>
setTimeout(async () => {
const audioContext = new AudioContext();
const gainNode = new GainNode(audioContext);
gainNode.connect(audioContext.destination);
gainNode.gain.value = 0.025;
const sineNode = new OscillatorNode(audioContext);
sineNode.connect(gainNode);
sineNode.start();
setTimeout(() => {
sineNode.stop();
resolve(audioContext.state);
}, 3000);
}, 1000)
);
});
expect(audioContextState).toBe("suspended");
}); |
I open this page in Safari: <script>
async function onLoad() {
const log = document.getElementById('log');
const audioContext = new AudioContext();
const gainNode = new GainNode(audioContext);
gainNode.connect(audioContext.destination);
gainNode.gain.value = 0.025;
const sineNode = new OscillatorNode(audioContext);
sineNode.connect(gainNode);
sineNode.start();
await new Promise((resolve) => setTimeout(resolve, 2000));
log.innerHTML = 'State: ' + audioContext.state;
}
</script>
<body onload="onLoad()">
<div id="log"></div>
</body> |
I ran this on my Mac with the following results: Chrome: suspended |
Right, but how are you serving the page and what URL are you pointing Safari to? |
Just http://127.0.0.1:8081/audio.html, serving with |
Thanks. Doing exactly that with the same html on my machine shows Can you check your autoplay preferences in Safari and make sure 127.0.0.1 is set to "Stop Media with Sound"? |
On my machine the "Auto-Play" websites list is empty until I actually go to |
Interesting. What version of Safari and macOS is this? I'm on Sonoma 14.7 running Safari 17.6 (19618.3.11.11.5) |
Looks like it only starts in the "running" state when I paste the URL after Safari's cold start (e.g. after the computer's reboot), if I repeat the same action later the context starts in a suspended state! The settings look exactly the same in both cases. |
Sequoia 15.1 Safari 18.1 (20619.2.8.11.10). But I believe this is just a race as described above, it might be that it starts in |
Yes, this repros on my machine, too. Nice find! |
I'm thinking the Playwright webkit browser is giving localhost a free pass on autoplay, but chromium and firefox are not. I'll try testing with a non-localhost server next to see if that changes things for webkit. |
Nope, this gives the same result. AFAICT the Playwright webkit browser always allows autoplay, so this feature request is only needed for the webkit browser. |
🚀 Feature Request
When creating a new
AudioContext
, its initialstate
is"running"
, which is good for testing audio, but does not reflect the initial state encountered by users of a website, which is"suspended"
and requires a user interaction for theAudioContext.resume()
promise to resolve.If possible, please add an option that makes
AudioContext
's initial state"suspended"
, and require a user interaction for theAudioContext.resume()
promise to resolve.Example
No response
Motivation
This would make it possible to test the user interaction requirements for autoplay implemented by browsers. See https://developer.chrome.com/blog/autoplay for more info on Chrome's requirements. Webkit's requirements are similar.
The text was updated successfully, but these errors were encountered: