add ability to continue playing stream in background

This commit is contained in:
Josh Hawkins 2024-12-20 09:42:55 -06:00
parent 9aa81f3e08
commit 1309cb0d7f
3 changed files with 46 additions and 3 deletions

View File

@ -32,6 +32,7 @@ type LivePlayerProps = {
useWebGL: boolean; useWebGL: boolean;
windowVisible?: boolean; windowVisible?: boolean;
playAudio?: boolean; playAudio?: boolean;
playInBackground: boolean;
micEnabled?: boolean; // only webrtc supports mic micEnabled?: boolean; // only webrtc supports mic
iOSCompatFullScreen?: boolean; iOSCompatFullScreen?: boolean;
pip?: boolean; pip?: boolean;
@ -53,6 +54,7 @@ export default function LivePlayer({
useWebGL = false, useWebGL = false,
windowVisible = true, windowVisible = true,
playAudio = false, playAudio = false,
playInBackground = false,
micEnabled = false, micEnabled = false,
iOSCompatFullScreen = false, iOSCompatFullScreen = false,
pip, pip,
@ -202,6 +204,7 @@ export default function LivePlayer({
camera={streamName} camera={streamName}
playbackEnabled={cameraActive || liveReady} playbackEnabled={cameraActive || liveReady}
audioEnabled={playAudio} audioEnabled={playAudio}
playInBackground={playInBackground}
onPlaying={playerIsPlaying} onPlaying={playerIsPlaying}
pip={pip} pip={pip}
setFullResolution={setFullResolution} setFullResolution={setFullResolution}

View File

@ -15,6 +15,7 @@ type MSEPlayerProps = {
className?: string; className?: string;
playbackEnabled?: boolean; playbackEnabled?: boolean;
audioEnabled?: boolean; audioEnabled?: boolean;
playInBackground?: boolean;
pip?: boolean; pip?: boolean;
onPlaying?: () => void; onPlaying?: () => void;
setFullResolution?: React.Dispatch<SetStateAction<VideoResolutionType>>; setFullResolution?: React.Dispatch<SetStateAction<VideoResolutionType>>;
@ -26,6 +27,7 @@ function MSEPlayer({
className, className,
playbackEnabled = true, playbackEnabled = true,
audioEnabled = false, audioEnabled = false,
playInBackground = false,
pip = false, pip = false,
onPlaying, onPlaying,
setFullResolution, setFullResolution,
@ -508,12 +510,22 @@ function MSEPlayer({
} }
}; };
document.addEventListener("visibilitychange", listener); if (!playInBackground) {
document.addEventListener("visibilitychange", listener);
}
return () => { return () => {
document.removeEventListener("visibilitychange", listener); if (!playInBackground) {
document.removeEventListener("visibilitychange", listener);
}
}; };
}, [playbackEnabled, visibilityCheck, onConnect, onDisconnect]); }, [
playbackEnabled,
visibilityCheck,
playInBackground,
onConnect,
onDisconnect,
]);
// control pip // control pip

View File

@ -55,6 +55,7 @@ import {
FaMicrophone, FaMicrophone,
FaMicrophoneSlash, FaMicrophoneSlash,
} from "react-icons/fa"; } from "react-icons/fa";
import { CiStreamOff, CiStreamOn } from "react-icons/ci";
import { GiSpeaker, GiSpeakerOff } from "react-icons/gi"; import { GiSpeaker, GiSpeakerOff } from "react-icons/gi";
import { TbViewfinder, TbViewfinderOff } from "react-icons/tb"; import { TbViewfinder, TbViewfinderOff } from "react-icons/tb";
import { IoIosWarning, IoMdArrowRoundBack } from "react-icons/io"; import { IoIosWarning, IoMdArrowRoundBack } from "react-icons/io";
@ -220,6 +221,11 @@ export default function LiveCameraView({
const [pip, setPip] = useState(false); const [pip, setPip] = useState(false);
const [lowBandwidth, setLowBandwidth] = useState(false); const [lowBandwidth, setLowBandwidth] = useState(false);
const [playInBackground, setPlayInBackground] = usePersistence<boolean>(
`${camera.name}-background-play`,
false,
);
const [fullResolution, setFullResolution] = useState<VideoResolutionType>({ const [fullResolution, setFullResolution] = useState<VideoResolutionType>({
width: 0, width: 0,
height: 0, height: 0,
@ -481,6 +487,8 @@ export default function LiveCameraView({
streamName={streamName ?? ""} streamName={streamName ?? ""}
setStreamName={setStreamName} setStreamName={setStreamName}
preferredLiveMode={preferredLiveMode} preferredLiveMode={preferredLiveMode}
playInBackground={playInBackground ?? false}
setPlayInBackground={setPlayInBackground}
/> />
</div> </div>
</TooltipProvider> </TooltipProvider>
@ -513,6 +521,7 @@ export default function LiveCameraView({
showStillWithoutActivity={false} showStillWithoutActivity={false}
cameraConfig={camera} cameraConfig={camera}
playAudio={audio} playAudio={audio}
playInBackground={playInBackground ?? false}
micEnabled={mic} micEnabled={mic}
iOSCompatFullScreen={isIOS} iOSCompatFullScreen={isIOS}
preferredLiveMode={preferredLiveMode} preferredLiveMode={preferredLiveMode}
@ -779,6 +788,8 @@ type FrigateCameraFeaturesProps = {
streamName: string; streamName: string;
setStreamName?: (value: string | undefined) => void; setStreamName?: (value: string | undefined) => void;
preferredLiveMode: string; preferredLiveMode: string;
playInBackground: boolean;
setPlayInBackground: (value: boolean | undefined) => void;
}; };
function FrigateCameraFeatures({ function FrigateCameraFeatures({
camera, camera,
@ -789,6 +800,8 @@ function FrigateCameraFeatures({
streamName, streamName,
setStreamName, setStreamName,
preferredLiveMode, preferredLiveMode,
playInBackground,
setPlayInBackground,
}: FrigateCameraFeaturesProps) { }: FrigateCameraFeaturesProps) {
const { payload: detectState, send: sendDetect } = useDetectState( const { payload: detectState, send: sendDetect } = useDetectState(
camera.name, camera.name,
@ -853,6 +866,14 @@ function FrigateCameraFeatures({
} }
/> />
)} )}
<CameraFeatureToggle
className="p-2 md:p-0"
variant={fullscreen ? "overlay" : "primary"}
Icon={playInBackground ? CiStreamOn : CiStreamOff}
isActive={playInBackground ?? false}
title={`${playInBackground ? "Disable" : "Enable"} Background Playback`}
onClick={() => setPlayInBackground(!playInBackground)}
/>
{Object.values(camera.live.streams).length > 1 && ( {Object.values(camera.live.streams).length > 1 && (
<Select <Select
value={streamName} value={streamName}
@ -952,6 +973,13 @@ function FrigateCameraFeatures({
} }
/> />
)} )}
<FilterSwitch
label="Play in Background"
isChecked={playInBackground}
onCheckedChange={(checked) => {
setPlayInBackground(checked);
}}
/>
{Object.values(camera.live.streams).length > 1 && ( {Object.values(camera.live.streams).length > 1 && (
<div className="mt-1 p-2"> <div className="mt-1 p-2">
<div className="mb-1 text-sm">Live stream selection</div> <div className="mb-1 text-sm">Live stream selection</div>