ladybird/Tests/LibWeb/Text/input/HTML/media-source-setup.html
Luke Wilde 8dc8835b64 LibWeb+LibWebView+WebContent: Allow muted media to autoplay by default
The autoplay setting was binary and its default blocked all media,
including muted video, leaving sites that rely on muted autoplay
visibly broken. Replace it with a tri-state user-agent autoplay
policy (allow audio and video, block audio, or block audio and video)
defaulting to allowing only inaudible media to autoplay.

This is enforced through the media element's "allowed to play" check,
so unmuting a muted autoplay or calling `play()` cannot slip audio
past the policy; audible playback is permitted once the document has
been activated by the user. The policy lives in a dedicated
AutoplaySettings consulted from HTMLMediaElement instead of the
Permissions Policy "allowed to use feature" check it was previously
conflated with.
2026-06-19 09:41:32 +02:00

129 lines
5.5 KiB
HTML

<!DOCTYPE html>
<!-- Allow autoplay so the scripted play() works without a user gesture, independent of the browser's
autoplay policy that test-web would otherwise inherit. -->
<script>internals.setAutoplayPolicy("allow-audio-and-video");</script>
<video id="video"></video>
<script src="../include.js"></script>
<script>
asyncTest(done => {
function step(name) {
println(`PASS: ${name}`);
}
function fail(name, error) {
println(`FAIL: ${name}: ${error}`);
done();
}
try {
const video = document.getElementById("video");
// Step 1: Check isTypeSupported
const mimeType = 'video/webm;codecs="vp9,opus"';
if (!MediaSource.isTypeSupported(mimeType)) {
fail("isTypeSupported", `${mimeType} not supported`);
return;
}
step("isTypeSupported");
// Step 2: Create MediaSource
const mediaSource = new MediaSource();
step("created MediaSource");
// Step 3: Create object URL and assign to video
const url = URL.createObjectURL(mediaSource);
video.src = url;
step("assigned object URL to video.src");
// Step 4: Wait for sourceopen
mediaSource.addEventListener("sourceopen", async () => {
try {
step("sourceopen fired");
// Step 5: Add a SourceBuffer
const sourceBuffer = mediaSource.addSourceBuffer(mimeType);
step("addSourceBuffer succeeded");
// Step 6: Fetch test media
const response = await fetch("../../../Assets/test-webm.webm");
if (!response.ok) {
fail("fetch media", `HTTP ${response.status}`);
return;
}
const data = await response.arrayBuffer();
step(`loaded media data (${data.byteLength} bytes)`);
// Step 7: Append buffer in slices and print buffered ranges
function printBuffered(label) {
const buffered = sourceBuffer.buffered;
if (!buffered) {
println(`buffered after ${label}: not available`);
return;
}
const ranges = [];
for (let i = 0; i < buffered.length; i++)
ranges.push(`${buffered.start(i)}-${buffered.end(i)}`);
println(`buffered after ${label}: [${ranges.join(", ")}]`);
}
async function appendSlice(slice, label) {
await new Promise((resolve, reject) => {
sourceBuffer.addEventListener("updateend", resolve, { once: true });
sourceBuffer.addEventListener("error", () =>
reject(new Error("error event on SourceBuffer")), { once: true });
sourceBuffer.appendBuffer(slice);
step(`appendBuffer() for ${label}`);
});
printBuffered(label);
}
// Embed the data in a padded buffer and append as typed array views, so that if the view slice is
// ignored by appendBuffer(), it will cause read errors. YouTube uses these typed array views.
const padding = 1024;
const paddedBuffer = new ArrayBuffer(padding + data.byteLength + padding);
new Uint8Array(paddedBuffer).set(new Uint8Array(data), padding);
const dataView = new Uint8Array(paddedBuffer, padding, data.byteLength);
const numSlices = 3;
const sliceSize = Math.ceil(dataView.length / numSlices);
let pos = 0;
for (let i = 0; i < numSlices; i++) {
const end = Math.min(pos + sliceSize, dataView.length);
await appendSlice(dataView.subarray(pos, end), `slice ${i + 1}/${numSlices}`);
step(`slice ${i + 1} appended`);
pos = end;
}
// Step 8: End of stream and wait for sourceended
await new Promise(resolve => {
mediaSource.addEventListener("sourceended", resolve);
mediaSource.endOfStream();
});
step("endOfStream succeeded");
printBuffered("endOfStream");
// Step 9: Play
video.addEventListener("error", () => {
fail("playback", video.error?.message ?? "unknown error");
});
await video.play();
step("play() promise resolved");
await new Promise(resolve => {
if (!video.paused)
resolve();
else
video.addEventListener("playing", resolve);
});
step("playing");
done();
} catch (e) {
fail("sourceopen handler", e);
}
});
} catch (e) {
fail("setup", e);
}
});
</script>