Compare commits

..

2 Commits

Author SHA1 Message Date
0x464e
3acff5212e
Use separate persistent mute and temporary forced mute states 2026-03-30 13:56:05 +03:00
0x464e
bdc838cc5a
Attempt to replay video muted on NotAllowedError 2026-03-30 13:54:51 +03:00
3 changed files with 49 additions and 6 deletions

View File

@ -216,7 +216,11 @@ export default function HlsVideoPlayer({
const [tallCamera, setTallCamera] = useState(false);
const [isPlaying, setIsPlaying] = useState(true);
const [muted, setMuted] = useUserPersistence("hlsPlayerMuted", true);
const [persistedMuted, setPersistedMuted] = useUserPersistence(
"hlsPlayerMuted",
true,
);
const [temporaryMuted, setTemporaryMuted] = useState(false);
const [volume, setVolume] = useOverlayState("playerVolume", 1.0);
const [defaultPlaybackRate] = useUserPersistence("playbackRate", 1);
const [playbackRate, setPlaybackRate] = useOverlayState(
@ -232,6 +236,16 @@ export default function HlsVideoPlayer({
height: number;
}>({ width: 0, height: 0 });
const muted = persistedMuted || temporaryMuted;
const onSetMuted = useCallback(
(muted: boolean) => {
setTemporaryMuted(false);
setPersistedMuted(muted);
},
[setPersistedMuted],
);
useEffect(() => {
if (!isDesktop) {
return;
@ -297,7 +311,7 @@ export default function HlsVideoPlayer({
fullscreen: supportsFullscreen,
}}
setControlsOpen={setControlsOpen}
setMuted={(muted) => setMuted(muted)}
setMuted={onSetMuted}
playbackRate={playbackRate ?? 1}
hotKeys={hotKeys}
onPlayPause={onPlayPause}
@ -404,9 +418,20 @@ export default function HlsVideoPlayer({
: undefined
}
onVolumeChange={() => {
setVolume(videoRef.current?.volume ?? 1.0, true);
if (!frigateControls) {
setMuted(videoRef.current?.muted);
if (!videoRef.current) {
return;
}
setVolume(videoRef.current.volume ?? 1.0, true);
if (frigateControls) {
if (videoRef.current.muted && !persistedMuted) {
setTemporaryMuted(true);
} else if (!videoRef.current.muted && temporaryMuted) {
setTemporaryMuted(false);
}
} else {
setPersistedMuted(videoRef.current.muted);
}
}}
onPlay={() => {

View File

@ -6,6 +6,7 @@ import {
calculateInpointOffset,
calculateSeekPosition,
} from "@/utils/videoUtil";
import { playWithTemporaryMuteFallback } from "@/utils/videoUtil.ts";
type PlayerMode = "playback" | "scrubbing";
@ -107,7 +108,7 @@ export class DynamicVideoController {
return new Promise((resolve) => {
const onSeekedHandler = () => {
this.playerController.removeEventListener("seeked", onSeekedHandler);
this.playerController.play();
playWithTemporaryMuteFallback(this.playerController);
resolve(undefined);
};

View File

@ -78,3 +78,20 @@ export function calculateSeekPosition(
return seekSeconds >= 0 ? seekSeconds : undefined;
}
/**
* Attempts to play the video, and if it fails due to a NotAllowedError (often caused by browser autoplay restrictions),
* it temporarily mutes the video and tries to play again.
* @param video - The HTMLVideoElement to play
*/
export function playWithTemporaryMuteFallback(video: HTMLVideoElement) {
return video.play().catch((error: { name?: string }) => {
if (error.name === "NotAllowedError" && !video.muted) {
video.muted = true;
return video.play().catch(() => undefined);
}
throw error;
});
}