Skip to content

Commit

Permalink
Camera Feed Fixes: Gracefully handle full-screen errors for unsupport…
Browse files Browse the repository at this point in the history
…ed 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
  • Loading branch information
rithviknishad authored Jun 3, 2024
1 parent 6e4114b commit 9addcae
Show file tree
Hide file tree
Showing 6 changed files with 71 additions and 24 deletions.
15 changes: 12 additions & 3 deletions src/CAREUI/misc/Fullscreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<HTMLDivElement>(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]);

Expand All @@ -27,6 +35,7 @@ export default function Fullscreen(props: Props) {
};

document.addEventListener("fullscreenchange", listener);

return () => {
document.removeEventListener("fullscreenchange", listener);
};
Expand Down
19 changes: 15 additions & 4 deletions src/Components/CameraFeed/AssetBedSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,28 +35,35 @@ export default function CameraPresetSelect(props: Props) {
{props.options.length > 5 && (
<CameraPresetDropdown
{...props}
placeholder="More preset"
options={props.options.slice(4)}
value={props.options.slice(4).find((o) => o.id === props.value?.id)}
/>
)}
</div>
<div className="w-full lg:hidden">
{/* Mobile View */}
<CameraPresetDropdown {...props} />
<CameraPresetDropdown {...props} placeholder="Select preset" />
</div>
</>
);
}

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");

const label = props.label ?? defaultLabel;

return (
<Listbox value={selected} onChange={props.onChange}>
<Listbox
value={selected}
onChange={props.onChange}
disabled={options.length === 0}
>
<div className="relative flex-1">
<Listbox.Button
className={classNames(
Expand All @@ -67,7 +74,11 @@ export const CameraPresetDropdown = (props: Props) => {
)}
>
<span className="block truncate">
{selected ? label(selected) : "Select preset"}
{options.length === 0
? "No presets"
: selected
? label(selected)
: props.placeholder}
</span>
<span className="pointer-events-none absolute inset-y-0 right-0 mr-1 mt-1 flex items-center">
<CareIcon icon="l-angle-down" className="text-xl text-zinc-400" />
Expand Down
41 changes: 30 additions & 11 deletions src/Components/CameraFeed/CameraFeed.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@ 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";
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;
Expand All @@ -27,6 +28,7 @@ interface Props {
constrolsDisabled?: boolean;
shortcutsDisabled?: boolean;
onMove?: () => void;
onReset?: () => void;
}

export default function CameraFeed(props: Props) {
Expand Down Expand Up @@ -86,14 +88,29 @@ export default function CameraFeed(props: Props) {

const resetStream = () => {
setState("loading");
props.onReset?.();
initializeStream();
};
return (
<Fullscreen fullscreen={isFullscreen} onExit={() => setFullscreen(false)}>
<Fullscreen
fullscreen={isFullscreen}
onExit={(reason) => {
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.",
});
}
}}
>
<div
className={classNames(
"flex max-h-screen flex-col overflow-clip rounded-xl bg-black",
"flex flex-col overflow-clip rounded-xl bg-black md:max-h-screen",
props.className,
isAppleDevice && isFullscreen && "px-20",
)}
>
<div className="flex items-center justify-between bg-zinc-900 px-4 py-1.5 md:py-2">
Expand All @@ -106,14 +123,16 @@ export default function CameraFeed(props: Props) {
/>
{props.asset.name}
</span>
<div className={state === "loading" ? "animate-pulse" : ""}>
<FeedNetworkSignal
playerRef={playerRef as any}
playedOn={player.playedOn}
status={player.status}
onReset={resetStream}
/>
</div>
{!isIOS && (
<div className={state === "loading" ? "animate-pulse" : ""}>
<FeedNetworkSignal
playerRef={playerRef as any}
playedOn={player.playedOn}
status={player.status}
onReset={resetStream}
/>
</div>
)}
</div>
</div>

Expand Down
1 change: 1 addition & 0 deletions src/Components/CameraFeed/CameraFeedWithBedPresets.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export default function LocationFeedTile(props: Props) {
options={data?.results ?? []}
value={preset}
onChange={setPreset}
placeholder="Select preset"
/>
)}
</div>
Expand Down
10 changes: 5 additions & 5 deletions src/Components/CameraFeed/FeedAlert.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ interface Props {
state?: FeedAlertState;
}

const ALERT_ICON_MAP: Record<FeedAlertState, IconName> = {
const ALERT_ICON_MAP: Partial<Record<FeedAlertState, IconName>> = {
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",
Expand Down Expand Up @@ -53,14 +53,14 @@ export default function FeedAlert({ state }: Props) {
leaveFrom="opacity-100 translate-y-0"
leaveTo="opacity-0 -translate-y-5"
>
<div className="absolute right-8 top-6 flex items-center gap-1.5 rounded bg-white/20 px-2 py-1 text-white">
{state && (
<div className="absolute right-8 top-4 z-20 flex items-center gap-1.5 rounded bg-white/20 px-2 py-1 text-white">
{state && ALERT_ICON_MAP[state] && (
<CareIcon
className={classNames(
"text-base",
state === "loading" && "animate-spin",
)}
icon={ALERT_ICON_MAP[state]}
icon={ALERT_ICON_MAP[state]!}
/>
)}
<span className="text-xs font-medium capitalize">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -27,6 +27,7 @@ export const ConsultationFeedTab = (props: ConsultationTabProps) => {
const [preset, setPreset] = useState<AssetBedModel>();
const [isUpdatingPreset, setIsUpdatingPreset] = useState(false);
const [hasMoved, setHasMoved] = useState(false);
const [key, setKey] = useState(0);
const divRef = useRef<any>();

const operate = useOperateCamera(asset?.id ?? "", true);
Expand Down Expand Up @@ -106,9 +107,15 @@ export const ConsultationFeedTab = (props: ConsultationTabProps) => {
</span>
<div ref={divRef}>
<CameraFeed
key={key}
asset={asset}
preset={preset?.meta.position}
onMove={() => setHasMoved(true)}
onReset={() => {
if (isIOS) {
setKey(key + 1);
}
}}
onStreamError={() => {
triggerGoal("Camera Feed Viewed", {
consultationId: props.consultationId,
Expand Down

0 comments on commit 9addcae

Please sign in to comment.