fix(engine): a muted video no longer plays at full volume in renders that skip the browser probe (#6044)

* fix(engine): a muted video no longer plays at full volume in renders that skip the browser probe

A <video data-has-audio="true" muted> with an audio stream is silent in
preview and in renders that run the browser probe, but a render that
skipped the probe mixed it at volume 1.0, ignoring data-volume. The audio
parser drops muted videos; the video parser kept hasAudio from
data-has-audio alone, so the auto-detect fallback re-added the sound.
The video parser now asks the same isAudibleVideoElement check.

* style(producer): format the muted-video parity test
This commit is contained in:
Miguel Ángel
2026-10-21 18:58:45 +00:00
committed by GitHub
parent 25126bca6c
commit 207c8a4daf
2 changed files with 38 additions and 1 deletions
@@ -768,7 +768,7 @@ export function parseVideoElements(html: string): VideoElement[] {
mediaStart: readMediaStart(el),
playbackRate: readElementRateSpec(el),
loop: el.hasAttribute("loop"),
hasAudio: hasAudioAttr === "true",
hasAudio: hasAudioAttr === "true" && isAudibleVideoElement(el),
...(isSelfOrAncestorHidden(el) ||
(isAudibleVideoElement(el) && isMemberGroupHidden(groupsById, el))
? { hidden: true }
@@ -1,5 +1,7 @@
import { describe, expect, it } from "bun:test";
import { pruneMutedBrowserMedia } from "./probeStage.js";
import { appendAutoDetectedVideoAudio } from "./extractVideosStage.js";
import { collectRenderMedia } from "../../renderMediaCollector.js";
describe("pruneMutedBrowserMedia", () => {
it("drops muted audio from the mix and clears hasAudio on muted video", () => {
@@ -36,3 +38,38 @@ describe("pruneMutedBrowserMedia", () => {
expect(composition.videos[0]?.hasAudio).toBe(true);
});
});
describe("a muted video's sound in the mix", () => {
const html = `<div data-composition-id="root" data-start="0" data-duration="4">
<video id="quiet" src="quiet.mp4" data-start="0" data-duration="4" data-has-audio="true" data-volume="0.72" muted></video>
<video id="voice" src="voice.mp4" data-start="0" data-duration="4" data-has-audio="true" data-volume="0.5"></video>
<audio id="music" src="music.mp3" data-start="0" data-duration="4" data-volume="0.19"></audio>
</div>`;
// Both files carry an audio stream, so the auto-detect fallback would add any video the parsers miss.
const extracted = ["quiet", "voice"].map((videoId) => ({
videoId,
metadata: { hasAudio: true },
}));
for (const probed of [false, true]) {
it(`stays out of the mix ${probed ? "after" : "without"} the browser probe`, () => {
const composition = collectRenderMedia(html);
if (probed) {
pruneMutedBrowserMedia(composition, [
{ id: "quiet", tagName: "video", muted: true },
{ id: "voice", tagName: "video", muted: false },
{ id: "music", tagName: "audio", muted: false },
]);
}
appendAutoDetectedVideoAudio(
composition,
extracted as unknown as Parameters<typeof appendAutoDetectedVideoAudio>[1],
);
expect(composition.audios.map(({ id, volume }) => [id, volume])).toEqual([
["music", 0.19],
["voice-audio", 0.5],
]);
});
}
});