mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-08 10:46:06 +00:00
fix(core,studio): silence hidden audio in preview, and call it mute
Preview scheduled every audio[data-start] regardless of data-hidden, so a hidden audio track was silent in the export but audible in preview — render was already correct, this was a preview-only parity bug. Web Audio scheduling now skips (and re-syncs on toggle) any audio clip under a data-hidden ancestor; the HTMLMedia per-tick volume path folds the same check into effectiveVolume without touching el.muted (transport-owned). Ships unflagged since it's a bugfix restoring parity. Also relabels the eye as Mute/Muted on audio-only track rows (icon, strikethrough label, undo-history copy), gated behind the new audio-track-mute canary — the relabel is a copy/UX change, kept separate from the behavior fix above. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
e966311627
commit
adfdb69a78
@@ -194,6 +194,87 @@ describe("toggleTimelineTrackHidden", () => {
|
||||
|
||||
expect(recordEdit.mock.calls[0]?.[0]?.label).toBe("Show track 2");
|
||||
});
|
||||
|
||||
it("labels an audio-only track Mute/Unmute instead of Hide/Show", async () => {
|
||||
const files = new Map([
|
||||
["index.html", `<div id="voiceover" data-start="0" data-duration="2"></div>`],
|
||||
]);
|
||||
stubProjectFiles(files);
|
||||
|
||||
const recordEdit = vi.fn();
|
||||
|
||||
await toggleTimelineTrackHidden({
|
||||
projectId: "project-1",
|
||||
activeCompPath: "index.html",
|
||||
timelineElements: [element({ id: "voiceover", domId: "voiceover", track: 0, tag: "audio" })],
|
||||
track: 0,
|
||||
hidden: true,
|
||||
previewIframe: null,
|
||||
writeProjectFile: async () => {},
|
||||
recordEdit,
|
||||
domEditSaveTimestampRef: { current: 0 },
|
||||
pendingTimelineEditPathRef: { current: new Set() },
|
||||
});
|
||||
|
||||
expect(recordEdit.mock.calls[0]?.[0]?.label).toBe("Mute track 1");
|
||||
});
|
||||
|
||||
it("labels unmuting an audio-only track back on", async () => {
|
||||
const files = new Map([
|
||||
["index.html", `<div id="voiceover" data-start="0" data-duration="2" data-hidden=""></div>`],
|
||||
]);
|
||||
stubProjectFiles(files);
|
||||
|
||||
const recordEdit = vi.fn();
|
||||
|
||||
await toggleTimelineTrackHidden({
|
||||
projectId: "project-1",
|
||||
activeCompPath: "index.html",
|
||||
timelineElements: [
|
||||
element({ id: "voiceover", domId: "voiceover", track: 0, tag: "audio", hidden: true }),
|
||||
],
|
||||
track: 0,
|
||||
hidden: false,
|
||||
previewIframe: null,
|
||||
writeProjectFile: async () => {},
|
||||
recordEdit,
|
||||
domEditSaveTimestampRef: { current: 0 },
|
||||
pendingTimelineEditPathRef: { current: new Set() },
|
||||
});
|
||||
|
||||
expect(recordEdit.mock.calls[0]?.[0]?.label).toBe("Unmute track 1");
|
||||
});
|
||||
|
||||
it("keeps Hide/Show wording for a mixed (audio + visual) track", async () => {
|
||||
const files = new Map([
|
||||
[
|
||||
"index.html",
|
||||
`<div id="voiceover" data-start="0" data-duration="2"></div>
|
||||
<div id="caption" data-start="0" data-duration="2"></div>`,
|
||||
],
|
||||
]);
|
||||
stubProjectFiles(files);
|
||||
|
||||
const recordEdit = vi.fn();
|
||||
|
||||
await toggleTimelineTrackHidden({
|
||||
projectId: "project-1",
|
||||
activeCompPath: "index.html",
|
||||
timelineElements: [
|
||||
element({ id: "voiceover", domId: "voiceover", track: 0, tag: "audio" }),
|
||||
element({ id: "caption", domId: "caption", track: 0, tag: "div" }),
|
||||
],
|
||||
track: 0,
|
||||
hidden: true,
|
||||
previewIframe: null,
|
||||
writeProjectFile: async () => {},
|
||||
recordEdit,
|
||||
domEditSaveTimestampRef: { current: 0 },
|
||||
pendingTimelineEditPathRef: { current: new Set() },
|
||||
});
|
||||
|
||||
expect(recordEdit.mock.calls[0]?.[0]?.label).toBe("Hide track 1");
|
||||
});
|
||||
});
|
||||
|
||||
describe("toggleTimelineElementHidden", () => {
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
trackDisplaySuffix,
|
||||
} from "../player/components/timelineTrackDisplay";
|
||||
import { saveProjectFilesWithHistory } from "../utils/studioFileHistory";
|
||||
import { isAudioTimelineElement } from "../utils/timelineInspector";
|
||||
import { readTagSnippetByTarget, type PatchOperation } from "../utils/sourcePatcher";
|
||||
import {
|
||||
applyPatchByTarget,
|
||||
@@ -218,12 +219,21 @@ export async function toggleTimelineTrackHidden({
|
||||
const suffix = trackDisplaySuffix(
|
||||
trackDisplayNumber(timelineTrackOrder(timelineElements), track),
|
||||
);
|
||||
const trackElements = timelineElements.filter((element) => element.track === track);
|
||||
const isAudioOnlyTrack = trackElements.length > 0 && trackElements.every(isAudioTimelineElement);
|
||||
const label = isAudioOnlyTrack
|
||||
? hidden
|
||||
? `Mute track${suffix}`
|
||||
: `Unmute track${suffix}`
|
||||
: hidden
|
||||
? `Hide track${suffix}`
|
||||
: `Show track${suffix}`;
|
||||
return setElementsHidden({
|
||||
projectId,
|
||||
activeCompPath,
|
||||
elements: timelineElements.filter((element) => element.track === track),
|
||||
elements: trackElements,
|
||||
hidden,
|
||||
label: hidden ? `Hide track${suffix}` : `Show track${suffix}`,
|
||||
label,
|
||||
previewIframe,
|
||||
writeProjectFile,
|
||||
recordEdit,
|
||||
|
||||
Reference in New Issue
Block a user