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

View File

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

View File

@ -78,3 +78,20 @@ export function calculateSeekPosition(
return seekSeconds >= 0 ? seekSeconds : undefined; 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;
});
}