From 9addcae21102c11ac0da9fcd5a57de82a6717360 Mon Sep 17 00:00:00 2001 From: Rithvik Nishad Date: Mon, 3 Jun 2024 21:27:08 +0530 Subject: [PATCH] Camera Feed Fixes: Gracefully handle full-screen errors for unsupported devices; fixes clipping of content in certain sizes in landscape mode (#7965) * fix screen height of camera feed * change label to more preset * fix fullscreen issues with iOS devices * fix presets placeholder and disable if no options * fixes feed alert z index * corrections for feed alert * hacks for iOS * fix missing placeholder for preset dropdown in live feed --- src/CAREUI/misc/Fullscreen.tsx | 15 +++++-- src/Components/CameraFeed/AssetBedSelect.tsx | 19 +++++++-- src/Components/CameraFeed/CameraFeed.tsx | 41 ++++++++++++++----- .../CameraFeed/CameraFeedWithBedPresets.tsx | 1 + src/Components/CameraFeed/FeedAlert.tsx | 10 ++--- .../ConsultationFeedTab.tsx | 9 +++- 6 files changed, 71 insertions(+), 24 deletions(-) diff --git a/src/CAREUI/misc/Fullscreen.tsx b/src/CAREUI/misc/Fullscreen.tsx index 5cfa7865128..82c6d9e91ed 100644 --- a/src/CAREUI/misc/Fullscreen.tsx +++ b/src/CAREUI/misc/Fullscreen.tsx @@ -5,17 +5,25 @@ interface Props { fullscreenClassName?: string; children: React.ReactNode; fullscreen: boolean; - onExit: () => void; + onExit: (reason?: "DEVICE_UNSUPPORTED") => void; } export default function Fullscreen(props: Props) { const ref = useRef(null); useEffect(() => { + if (!ref.current) { + return; + } + if (props.fullscreen) { - ref.current?.requestFullscreen(); + if (ref.current.requestFullscreen) { + ref.current.requestFullscreen(); + } else { + props.onExit("DEVICE_UNSUPPORTED"); + } } else { - document.exitFullscreen(); + document.exitFullscreen?.(); } }, [props.fullscreen]); @@ -27,6 +35,7 @@ export default function Fullscreen(props: Props) { }; document.addEventListener("fullscreenchange", listener); + return () => { document.removeEventListener("fullscreenchange", listener); }; diff --git a/src/Components/CameraFeed/AssetBedSelect.tsx b/src/Components/CameraFeed/AssetBedSelect.tsx index 56def3d41c9..f970a920abc 100644 --- a/src/Components/CameraFeed/AssetBedSelect.tsx +++ b/src/Components/CameraFeed/AssetBedSelect.tsx @@ -35,6 +35,7 @@ export default function CameraPresetSelect(props: Props) { {props.options.length > 5 && ( o.id === props.value?.id)} /> @@ -42,13 +43,15 @@ export default function CameraPresetSelect(props: Props) {
{/* Mobile View */} - +
); } -export const CameraPresetDropdown = (props: Props) => { +export const CameraPresetDropdown = ( + props: Props & { placeholder: string }, +) => { const selected = props.value; const options = props.options.filter(({ meta }) => meta.type !== "boundary"); @@ -56,7 +59,11 @@ export const CameraPresetDropdown = (props: Props) => { const label = props.label ?? defaultLabel; return ( - +
{ )} > - {selected ? label(selected) : "Select preset"} + {options.length === 0 + ? "No presets" + : selected + ? label(selected) + : props.placeholder} diff --git a/src/Components/CameraFeed/CameraFeed.tsx b/src/Components/CameraFeed/CameraFeed.tsx index a5f0a3d80e0..1c6781ee51b 100644 --- a/src/Components/CameraFeed/CameraFeed.tsx +++ b/src/Components/CameraFeed/CameraFeed.tsx @@ -4,7 +4,7 @@ import useOperateCamera, { PTZPayload } from "./useOperateCamera"; import usePlayer from "./usePlayer"; import { getStreamUrl } from "./utils"; import ReactPlayer from "react-player"; -import { classNames, isIOS } from "../../Utils/utils"; +import { classNames, isAppleDevice, isIOS } from "../../Utils/utils"; import FeedAlert, { FeedAlertState } from "./FeedAlert"; import FeedNetworkSignal from "./FeedNetworkSignal"; import NoFeedAvailable from "./NoFeedAvailable"; @@ -12,6 +12,7 @@ import FeedControls from "./FeedControls"; import Fullscreen from "../../CAREUI/misc/Fullscreen"; import FeedWatermark from "./FeedWatermark"; import CareIcon from "../../CAREUI/icons/CareIcon"; +import { Error } from "../../Utils/Notifications"; interface Props { children?: React.ReactNode; @@ -27,6 +28,7 @@ interface Props { constrolsDisabled?: boolean; shortcutsDisabled?: boolean; onMove?: () => void; + onReset?: () => void; } export default function CameraFeed(props: Props) { @@ -86,14 +88,29 @@ export default function CameraFeed(props: Props) { const resetStream = () => { setState("loading"); + props.onReset?.(); initializeStream(); }; return ( - setFullscreen(false)}> + { + setFullscreen(false); + + if (reason === "DEVICE_UNSUPPORTED") { + // iOS webkit allows only video/iframe elements to call full-screen + // APIs. But we need to show controls too, not just the video element. + Error({ + msg: "This device does not support viewing this content in full-screen.", + }); + } + }} + >
@@ -106,14 +123,16 @@ export default function CameraFeed(props: Props) { /> {props.asset.name} -
- -
+ {!isIOS && ( +
+ +
+ )}
diff --git a/src/Components/CameraFeed/CameraFeedWithBedPresets.tsx b/src/Components/CameraFeed/CameraFeedWithBedPresets.tsx index e3fc2ab2129..8ce9c9ef67f 100644 --- a/src/Components/CameraFeed/CameraFeedWithBedPresets.tsx +++ b/src/Components/CameraFeed/CameraFeedWithBedPresets.tsx @@ -33,6 +33,7 @@ export default function LocationFeedTile(props: Props) { options={data?.results ?? []} value={preset} onChange={setPreset} + placeholder="Select preset" /> )}
diff --git a/src/Components/CameraFeed/FeedAlert.tsx b/src/Components/CameraFeed/FeedAlert.tsx index a4f8a3beb18..138af509c8c 100644 --- a/src/Components/CameraFeed/FeedAlert.tsx +++ b/src/Components/CameraFeed/FeedAlert.tsx @@ -15,12 +15,12 @@ interface Props { state?: FeedAlertState; } -const ALERT_ICON_MAP: Record = { +const ALERT_ICON_MAP: Partial> = { playing: "l-play-circle", stop: "l-stop-circle", offline: "l-exclamation-triangle", loading: "l-spinner", - moving: "l-expand-from-corner", + // moving: "l-expand-from-corner", zooming: "l-search", saving_preset: "l-save", host_unreachable: "l-exclamation-triangle", @@ -53,14 +53,14 @@ export default function FeedAlert({ state }: Props) { leaveFrom="opacity-100 translate-y-0" leaveTo="opacity-0 -translate-y-5" > -
- {state && ( +
+ {state && ALERT_ICON_MAP[state] && ( )} diff --git a/src/Components/Facility/ConsultationDetails/ConsultationFeedTab.tsx b/src/Components/Facility/ConsultationDetails/ConsultationFeedTab.tsx index 3d12441eb10..91cd86c606e 100644 --- a/src/Components/Facility/ConsultationDetails/ConsultationFeedTab.tsx +++ b/src/Components/Facility/ConsultationDetails/ConsultationFeedTab.tsx @@ -16,7 +16,7 @@ import useOperateCamera, { PTZPayload, } from "../../CameraFeed/useOperateCamera"; import request from "../../../Utils/request/request"; -import { classNames } from "../../../Utils/utils"; +import { classNames, isIOS } from "../../../Utils/utils"; export const ConsultationFeedTab = (props: ConsultationTabProps) => { const authUser = useAuthUser(); @@ -27,6 +27,7 @@ export const ConsultationFeedTab = (props: ConsultationTabProps) => { const [preset, setPreset] = useState(); const [isUpdatingPreset, setIsUpdatingPreset] = useState(false); const [hasMoved, setHasMoved] = useState(false); + const [key, setKey] = useState(0); const divRef = useRef(); const operate = useOperateCamera(asset?.id ?? "", true); @@ -106,9 +107,15 @@ export const ConsultationFeedTab = (props: ConsultationTabProps) => {
setHasMoved(true)} + onReset={() => { + if (isIOS) { + setKey(key + 1); + } + }} onStreamError={() => { triggerGoal("Camera Feed Viewed", { consultationId: props.consultationId,