mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-09 20:07:39 +00:00
fix(studio): play preview audio at speeds above 1x (#2691)
* fix(studio): play preview audio at speeds above 1x
The player force-muted preview audio whenever playback rate exceeded 1x
and disabled the mute button (and the M shortcut) in that state. Users
changing speed to 2x heard nothing, and clicking unmute did nothing.
Media elements play audio fine at any rate (pitch preserved by default),
so remove the special case: audio now follows only the user's mute
toggle at every speed. Drops the shouldMutePreviewAudio helper and the
audioAutoMuted UI/keyboard gating that existed solely for it.
* test(studio): update seek audio test for unmuted-above-1x behavior
useTimelinePlayer.seek.test.ts still asserted the old force-mute: at 2x
with the user unmuted it expected a set-muted{muted:true} message. With
audio now following only the user's toggle, the preview receives
set-muted{muted:false}. Update the assertion and title to match.
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { resolveSeekPercent } from "./PlayerControls";
|
||||
import { shouldMutePreviewAudio } from "../lib/timelineIframeHelpers";
|
||||
|
||||
describe("resolveSeekPercent", () => {
|
||||
it("returns 0 when the track width is invalid", () => {
|
||||
@@ -19,19 +18,3 @@ describe("resolveSeekPercent", () => {
|
||||
expect(resolveSeekPercent(150, 100, 200)).toBe(0.25);
|
||||
});
|
||||
});
|
||||
|
||||
describe("shouldMutePreviewAudio", () => {
|
||||
it("mutes when the user toggled audio off", () => {
|
||||
expect(shouldMutePreviewAudio(true, 1)).toBe(true);
|
||||
});
|
||||
|
||||
it("auto-mutes above 1x playback", () => {
|
||||
expect(shouldMutePreviewAudio(false, 1.5)).toBe(true);
|
||||
expect(shouldMutePreviewAudio(false, 2)).toBe(true);
|
||||
});
|
||||
|
||||
it("keeps audio on at 1x or slower when the user has not muted it", () => {
|
||||
expect(shouldMutePreviewAudio(false, 1)).toBe(false);
|
||||
expect(shouldMutePreviewAudio(false, 0.5)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,7 +2,6 @@ import { useRef, useCallback, useEffect, memo } from "react";
|
||||
import gsap from "gsap";
|
||||
import { MorphSVGPlugin } from "gsap/MorphSVGPlugin";
|
||||
import { formatFrameTime, formatTime, stepFrameTime } from "../lib/time";
|
||||
import { shouldMutePreviewAudio } from "../lib/timelineIframeHelpers";
|
||||
import { usePlayerStore } from "../store/playerStore";
|
||||
import { trackStudioEvent } from "../../utils/studioTelemetry";
|
||||
import { Tooltip } from "../../components/ui";
|
||||
@@ -60,40 +59,30 @@ function PlayPauseMorphIcon({ playing }: { playing: boolean }) {
|
||||
|
||||
const MuteButton = memo(function MuteButton({
|
||||
audioMuted,
|
||||
audioAutoMuted,
|
||||
effectiveAudioMuted,
|
||||
controlsDisabled,
|
||||
setAudioMuted,
|
||||
}: {
|
||||
audioMuted: boolean;
|
||||
audioAutoMuted: boolean;
|
||||
effectiveAudioMuted: boolean;
|
||||
controlsDisabled: boolean;
|
||||
setAudioMuted: (v: boolean) => void;
|
||||
}) {
|
||||
const label = audioAutoMuted
|
||||
? "Audio muted above 1x speed"
|
||||
: audioMuted
|
||||
? "Unmute audio"
|
||||
: "Mute audio";
|
||||
const label = audioMuted ? "Unmute audio" : "Mute audio";
|
||||
return (
|
||||
<Tooltip label={label}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
if (!audioAutoMuted) {
|
||||
trackStudioEvent("playback", { action: "mute_toggle", muted: !audioMuted });
|
||||
setAudioMuted(!audioMuted);
|
||||
}
|
||||
trackStudioEvent("playback", { action: "mute_toggle", muted: !audioMuted });
|
||||
setAudioMuted(!audioMuted);
|
||||
}}
|
||||
disabled={controlsDisabled || audioAutoMuted}
|
||||
disabled={controlsDisabled}
|
||||
aria-label={label}
|
||||
aria-pressed={effectiveAudioMuted}
|
||||
aria-pressed={audioMuted}
|
||||
className={`h-7 w-7 flex-shrink-0 flex items-center justify-center rounded-md border transition-colors disabled:pointer-events-none ${
|
||||
effectiveAudioMuted
|
||||
audioMuted
|
||||
? "text-studio-accent bg-studio-accent/10 border-studio-accent/30"
|
||||
: "border-neutral-700 text-neutral-400 hover:border-neutral-500 hover:bg-neutral-800"
|
||||
} ${audioAutoMuted ? "opacity-70" : ""}`}
|
||||
}`}
|
||||
>
|
||||
<svg
|
||||
width="13"
|
||||
@@ -107,7 +96,7 @@ const MuteButton = memo(function MuteButton({
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path d="M11 5 6 9H3v6h3l5 4V5Z" />
|
||||
{effectiveAudioMuted ? (
|
||||
{audioMuted ? (
|
||||
<>
|
||||
<path d="m19 9-6 6" />
|
||||
<path d="m13 9 6 6" />
|
||||
@@ -383,8 +372,6 @@ export const PlayerControls = memo(function PlayerControls({
|
||||
const durationRef = useRef(duration);
|
||||
durationRef.current = duration;
|
||||
const controlsDisabled = disabled || !timelineReady;
|
||||
const audioAutoMuted = playbackRate > 1;
|
||||
const effectiveAudioMuted = shouldMutePreviewAudio(audioMuted, playbackRate);
|
||||
|
||||
useEffect(() => {
|
||||
if (!timeDisplayRef.current) return;
|
||||
@@ -486,8 +473,6 @@ export const PlayerControls = memo(function PlayerControls({
|
||||
|
||||
<MuteButton
|
||||
audioMuted={audioMuted}
|
||||
audioAutoMuted={audioAutoMuted}
|
||||
effectiveAudioMuted={effectiveAudioMuted}
|
||||
controlsDisabled={controlsDisabled}
|
||||
setAudioMuted={setAudioMuted}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user