Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix safari preview speed and other cleanup #9976

Merged
merged 7 commits into from
Feb 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion web/src/components/Statusbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export default function Statusbar({}) {
const gpu = parseInt(stats.gpu);

return (
<div className="flex items-center text-sm">
<div key={gpuTitle} className="flex items-center text-sm">
<MdCircle
className={`w-2 h-2 mr-2 ${
gpu < 50
Expand Down
62 changes: 45 additions & 17 deletions web/src/components/player/PreviewThumbnailPlayer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import TimeAgo from "../dynamic/TimeAgo";
import useSWR from "swr";
import { FrigateConfig } from "@/types/frigateConfig";
import { isMobile, isSafari } from "react-device-detect";
import Chip from "../Chip";

type PreviewPlayerProps = {
review: ReviewSegment;
Expand Down Expand Up @@ -121,26 +122,26 @@ export default function PreviewThumbnailPlayer({
) : (
<img
className="h-full w-full"
loading="lazy"
src={`${apiHost}${review.thumb_path.replace("/media/frigate/", "")}`}
/>
)}
{!playingBack &&
(review.severity == "alert" || review.severity == "detection") && (
<div className="absolute top-1 left-[6px] flex gap-1">
{review.data.objects.map((object) => {
return getIconForLabel(object, "w-3 h-3 text-white");
})}
{review.data.audio.map((audio) => {
return getIconForLabel(audio, "w-3 h-3 text-white");
})}
{review.data.sub_labels?.map((sub) => {
return getIconForSubLabel(sub, "w-3 h-3 text-white");
})}
</div>
)}
{(review.severity == "alert" || review.severity == "detection") && (
<Chip className="absolute top-2 left-2 flex gap-1 bg-gradient-to-br from-gray-400 to-gray-500 bg-gray-500 z-0">
{review.data.objects.map((object) => {
return getIconForLabel(object, "w-3 h-3 text-white");
})}
{review.data.audio.map((audio) => {
return getIconForLabel(audio, "w-3 h-3 text-white");
})}
{review.data.sub_labels?.map((sub) => {
return getIconForSubLabel(sub, "w-3 h-3 text-white");
})}
</Chip>
)}
{!playingBack && (
<div className="absolute left-[6px] right-[6px] bottom-1 flex justify-between text-white">
<TimeAgo time={review.start_time * 1000} />
<TimeAgo time={review.start_time * 1000} dense />
{config &&
formatUnixTimestampToDateTime(review.start_time, {
strftime_fmt:
Expand Down Expand Up @@ -184,6 +185,26 @@ function PreviewContent({
setProgress,
setReviewed,
}: PreviewContentProps) {
// manual playback
// safari is incapable of playing at a speed > 2x
// so manual seeking is required on iOS

const [manualPlayback, setManualPlayback] = useState(false);
useEffect(() => {
if (!manualPlayback || !playerRef.current) {
return;
}

const intervalId: NodeJS.Timeout = setInterval(() => {
if (playerRef.current) {
playerRef.current.currentTime(playerRef.current.currentTime()!! + 1);
}
}, 125);
return () => clearInterval(intervalId);
}, [manualPlayback, playerRef]);

// preview

if (relevantPreview && playback) {
return (
<VideoPlayer
Expand Down Expand Up @@ -218,10 +239,16 @@ function PreviewContent({
review.start_time - relevantPreview.start - 8
);

player.playbackRate(isSafari ? 2 : 8);
if (isSafari) {
player.pause();
setManualPlayback(true);
} else {
player.playbackRate(8);
}

player.currentTime(playerStartTime);
player.on("timeupdate", () => {
if (!setProgress || playerRef.current?.paused()) {
if (!setProgress) {
return;
}

Expand All @@ -242,6 +269,7 @@ function PreviewContent({

if (playerPercent > 100) {
playerRef.current?.pause();
setManualPlayback(false);
setProgress(100.0);
} else {
setProgress(playerPercent);
Expand Down
109 changes: 53 additions & 56 deletions web/src/views/events/DesktopEventView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,16 @@ export default function DesktopEventView() {
[reviewPages]
);

const currentItems = useMemo(() => {
const current = reviewItems[severity];

if (!current || current.length == 0) {
return null;
}

return current;
}, [reviewItems, severity]);

// review interaction

const pagingObserver = useRef<IntersectionObserver | null>();
Expand Down Expand Up @@ -244,8 +254,6 @@ export default function DesktopEventView() {
return <ActivityIndicator />;
}

console.log("end of the timeline is " + after + " vs " + (Math.floor(Date.now() / 1000) + 2 * 60 * 60))

return (
<div className="relative w-full h-full">
<div className="absolute flex justify-between left-0 top-0 right-0">
Expand Down Expand Up @@ -303,68 +311,57 @@ export default function DesktopEventView() {
ref={contentRef}
className="absolute left-0 top-12 bottom-0 right-28 flex flex-wrap content-start gap-2 overflow-y-auto no-scrollbar"
>
{reviewItems[severity]?.map((value, segIdx) => {
const lastRow = segIdx == reviewItems[severity].length - 1;
const relevantPreview = Object.values(allPreviews || []).find(
(preview) =>
preview.camera == value.camera &&
preview.start < value.start_time &&
preview.end > value.end_time
);

return (
<div
key={value.id}
ref={lastRow ? lastReviewRef : minimapRef}
data-start={value.start_time}
>
<div className="h-[234px] aspect-video rounded-lg overflow-hidden">
<PreviewThumbnailPlayer
review={value}
relevantPreview={relevantPreview}
setReviewed={() => setReviewed(value.id)}
/>
{currentItems ? (
currentItems.map((value, segIdx) => {
const lastRow = segIdx == reviewItems[severity].length - 1;
const relevantPreview = Object.values(allPreviews || []).find(
(preview) =>
preview.camera == value.camera &&
preview.start < value.start_time &&
preview.end > value.end_time
);

return (
<div
key={value.id}
ref={lastRow ? lastReviewRef : minimapRef}
data-start={value.start_time}
>
<div className="h-[234px] aspect-video rounded-lg overflow-hidden">
<PreviewThumbnailPlayer
review={value}
relevantPreview={relevantPreview}
setReviewed={() => setReviewed(value.id)}
/>
</div>
{lastRow && !isDone && <ActivityIndicator />}
</div>
{lastRow && !isDone && <ActivityIndicator />}
</div>
);
})}
);
})
) : (
<div ref={lastReviewRef} />
)}
</div>
<div className="absolute top-12 right-0 bottom-0">
{after != 0 && (<EventReviewTimeline
segmentDuration={60}
timestampSpread={15}
timelineStart={Math.floor(Date.now() / 1000)} // start of the timeline - all times are numeric, not Date objects
timelineEnd={after} // end of timeline - timestamp
showMinimap
minimapStartTime={minimapBounds.start}
minimapEndTime={minimapBounds.end}
events={reviewItems.all}
severityType={severity}
contentRef={contentRef}
/>)}
{after != 0 && (
<EventReviewTimeline
segmentDuration={60}
timestampSpread={15}
timelineStart={Math.floor(Date.now() / 1000)}
timelineEnd={after}
showMinimap
minimapStartTime={minimapBounds.start}
minimapEndTime={minimapBounds.end}
events={reviewItems.all}
severityType={severity}
contentRef={contentRef}
/>
)}
</div>
</div>
);
}

/**
* <EventReviewTimeline
segmentDuration={60} // seconds per segment
timestampSpread={15} // minutes between each major timestamp
timelineStart={Math.floor(Date.now() / 1000)} // start of the timeline - all times are numeric, not Date objects
timelineEnd={Math.floor(Date.now() / 1000) + 2 * 60 * 60} // end of timeline - timestamp
showHandlebar // show / hide the handlebar
handlebarTime={Math.floor(Date.now() / 1000) - 27 * 60} // set the time of the handlebar
showMinimap // show / hide the minimap
minimapStartTime={Math.floor(Date.now() / 1000) - 35 * 60} // start time of the minimap - the earlier time (eg 1:00pm)
minimapEndTime={Math.floor(Date.now() / 1000) - 21 * 60} // end of the minimap - the later time (eg 3:00pm)
events={mockEvents} // events, including new has_been_reviewed and severity properties
severityType={"alert"} // choose the severity type for the middle line - all other severity types are to the right
contentRef={contentRef} // optional content ref where previews are, can be used for observing/scrolling later
/>
*/

function ReviewCalendarButton() {
const disabledDates = useMemo(() => {
const tomorrow = new Date();
Expand Down
66 changes: 40 additions & 26 deletions web/src/views/events/MobileEventView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,16 @@ export default function MobileEventView() {
[reviewPages]
);

const currentItems = useMemo(() => {
const current = reviewItems[severity];

if (!current || current.length == 0) {
return null;
}

return current;
}, [reviewItems, severity]);

// review interaction

const pagingObserver = useRef<IntersectionObserver | null>();
Expand Down Expand Up @@ -278,33 +288,37 @@ export default function MobileEventView() {
ref={contentRef}
className="w-full h-full grid grid-cols-1 sm:grid-cols-2 mt-2 gap-2 overflow-y-auto"
>
{reviewItems[severity]?.map((value, segIdx) => {
const lastRow = segIdx == reviewItems[severity].length - 1;
const relevantPreview = Object.values(allPreviews || []).find(
(preview) =>
preview.camera == value.camera &&
preview.start < value.start_time &&
preview.end > value.end_time
);

return (
<div
key={value.id}
ref={lastRow ? lastReviewRef : minimapRef}
data-start={value.start_time}
>
<div className="w-full aspect-video rounded-lg overflow-hidden">
<PreviewThumbnailPlayer
review={value}
relevantPreview={relevantPreview}
autoPlayback={minimapBounds.end == value.start_time}
setReviewed={() => setReviewed(value.id)}
/>
{currentItems ? (
currentItems.map((value, segIdx) => {
const lastRow = segIdx == reviewItems[severity].length - 1;
const relevantPreview = Object.values(allPreviews || []).find(
(preview) =>
preview.camera == value.camera &&
preview.start < value.start_time &&
preview.end > value.end_time
);

return (
<div
key={value.id}
ref={lastRow ? lastReviewRef : minimapRef}
data-start={value.start_time}
>
<div className="w-full aspect-video rounded-lg overflow-hidden">
<PreviewThumbnailPlayer
review={value}
relevantPreview={relevantPreview}
autoPlayback={minimapBounds.end == value.start_time}
setReviewed={() => setReviewed(value.id)}
/>
</div>
{lastRow && !isDone && <ActivityIndicator />}
</div>
{lastRow && !isDone && <ActivityIndicator />}
</div>
);
})}
);
})
) : (
<div ref={lastReviewRef} />
)}
</div>
</>
);
Expand Down
Loading