Support opening pip from live view

This commit is contained in:
Nicolas Mowen 2024-04-01 14:32:02 -06:00
parent 4b6a3b22ed
commit eeef39c1c0
4 changed files with 65 additions and 5 deletions

View File

@ -22,6 +22,7 @@ type LivePlayerProps = {
playAudio?: boolean;
micEnabled?: boolean; // only webrtc supports mic
iOSCompatFullScreen?: boolean;
pip?: boolean;
onClick?: () => void;
};
@ -35,6 +36,7 @@ export default function LivePlayer({
playAudio = false,
micEnabled = false,
iOSCompatFullScreen = false,
pip,
onClick,
}: LivePlayerProps) {
// camera activity
@ -105,6 +107,7 @@ export default function LivePlayer({
microphoneEnabled={micEnabled}
iOSCompatFullScreen={iOSCompatFullScreen}
onPlaying={() => setLiveReady(true)}
pip={pip}
/>
);
} else if (liveMode == "mse") {
@ -116,6 +119,7 @@ export default function LivePlayer({
playbackEnabled={cameraActive}
audioEnabled={playAudio}
onPlaying={() => setLiveReady(true)}
pip={pip}
/>
);
} else {

View File

@ -6,6 +6,7 @@ type MSEPlayerProps = {
className?: string;
playbackEnabled?: boolean;
audioEnabled?: boolean;
pip?: boolean;
onPlaying?: () => void;
};
@ -14,6 +15,7 @@ function MSEPlayer({
className,
playbackEnabled = true,
audioEnabled = false,
pip = false,
onPlaying,
}: MSEPlayerProps) {
let connectTS: number = 0;
@ -268,6 +270,16 @@ function MSEPlayer({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [playbackEnabled, onDisconnect, onConnect]);
// control pip
useEffect(() => {
if (!videoRef.current || !pip) {
return;
}
videoRef.current.requestPictureInPicture();
}, [pip, videoRef]);
return (
<video
ref={videoRef}

View File

@ -8,6 +8,7 @@ type WebRtcPlayerProps = {
audioEnabled?: boolean;
microphoneEnabled?: boolean;
iOSCompatFullScreen?: boolean; // ios doesn't support fullscreen divs so we must support the video element
pip?: boolean;
onPlaying?: () => void;
};
@ -18,6 +19,7 @@ export default function WebRtcPlayer({
audioEnabled = false,
microphoneEnabled = false,
iOSCompatFullScreen = false,
pip = false,
onPlaying,
}: WebRtcPlayerProps) {
// metadata
@ -173,8 +175,19 @@ export default function WebRtcPlayer({
]);
// ios compat
const [iOSCompatControls, setiOSCompatControls] = useState(false);
// control pip
useEffect(() => {
if (!videoRef.current || !pip) {
return;
}
videoRef.current.requestPictureInPicture();
}, [pip, videoRef]);
return (
<video
ref={videoRef}

View File

@ -51,7 +51,14 @@ import {
import { GiSpeaker, GiSpeakerOff } from "react-icons/gi";
import { HiViewfinderCircle } from "react-icons/hi2";
import { IoMdArrowBack } from "react-icons/io";
import { LuEar, LuEarOff, LuVideo, LuVideoOff } from "react-icons/lu";
import {
LuEar,
LuEarOff,
LuPictureInPicture,
LuPictureInPicture2,
LuVideo,
LuVideoOff,
} from "react-icons/lu";
import {
MdNoPhotography,
MdPersonOff,
@ -113,20 +120,25 @@ export default function LiveCameraView({ camera }: LiveCameraViewProps) {
[clickOverlayRef, clickOverlay, sendPtz],
);
// fullscreen state
// fullscreen / pip state
useEffect(() => {
if (mainRef.current == null) {
return;
}
const listener = () => {
const fsListener = () => {
setFullscreen(document.fullscreenElement != null);
};
document.addEventListener("fullscreenchange", listener);
const pipListener = () => {
setPip(document.pictureInPictureElement != null);
};
document.addEventListener("fullscreenchange", fsListener);
document.addEventListener("focusin", pipListener);
return () => {
document.removeEventListener("fullscreenchange", listener);
document.removeEventListener("fullscreenchange", fsListener);
document.removeEventListener("focusin", pipListener);
};
}, [mainRef]);
@ -135,6 +147,7 @@ export default function LiveCameraView({ camera }: LiveCameraViewProps) {
const [audio, setAudio] = useState(false);
const [mic, setMic] = useState(false);
const [fullscreen, setFullscreen] = useState(false);
const [pip, setPip] = useState(false);
const growClassName = useMemo(() => {
const aspect = camera.detect.width / camera.detect.height;
@ -237,6 +250,23 @@ export default function LiveCameraView({ camera }: LiveCameraViewProps) {
}}
/>
)}
{!isIOS && (
<CameraFeatureToggle
className="p-2 md:p-0"
variant={fullscreen ? "overlay" : "primary"}
Icon={LuPictureInPicture}
isActive={pip}
title={pip ? "Close" : "Picture in Picture"}
onClick={() => {
if (!pip) {
setPip(true);
} else {
document.exitPictureInPicture();
setPip(false);
}
}}
/>
)}
{window.isSecureContext && (
<CameraFeatureToggle
className="p-2 md:p-0"
@ -293,6 +323,7 @@ export default function LiveCameraView({ camera }: LiveCameraViewProps) {
micEnabled={mic}
iOSCompatFullScreen={isIOS}
preferredLiveMode={preferredLiveMode}
pip={pip}
/>
</div>
{camera.onvif.host != "" && (