From 40faa3519972f5d53cacc0669d64eccb3ffc456c Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Thu, 2 May 2024 10:41:51 -0600 Subject: [PATCH] Show dialog with current frame to be uploaded --- frigate/api/media.py | 2 - web/src/components/icons/FrigatePlusIcon.tsx | 21 +++++ web/src/components/player/HlsVideoPlayer.tsx | 9 +- web/src/components/player/VideoControls.tsx | 93 +++++++++++++++++++- 4 files changed, 117 insertions(+), 8 deletions(-) create mode 100644 web/src/components/icons/FrigatePlusIcon.tsx diff --git a/frigate/api/media.py b/frigate/api/media.py index ba4bba608..4aae06672 100644 --- a/frigate/api/media.py +++ b/frigate/api/media.py @@ -7,7 +7,6 @@ import os import subprocess as sp import time from datetime import datetime, timedelta, timezone -from io import BytesIO from urllib.parse import unquote import cv2 @@ -15,7 +14,6 @@ import numpy as np import pytz from flask import Blueprint, Response, current_app, jsonify, make_response, request from peewee import DoesNotExist, fn -from PIL import Image from tzlocal import get_localzone_name from werkzeug.utils import secure_filename diff --git a/web/src/components/icons/FrigatePlusIcon.tsx b/web/src/components/icons/FrigatePlusIcon.tsx new file mode 100644 index 000000000..1a9ff2e05 --- /dev/null +++ b/web/src/components/icons/FrigatePlusIcon.tsx @@ -0,0 +1,21 @@ +import { LuPlus } from "react-icons/lu"; +import Logo from "../Logo"; + +type FrigatePlusIconProps = { + className?: string; + onClick?: () => void; +}; +export default function FrigatePlusIcon({ + className, + onClick, +}: FrigatePlusIconProps) { + return ( +
+ + +
+ ); +} diff --git a/web/src/components/player/HlsVideoPlayer.tsx b/web/src/components/player/HlsVideoPlayer.tsx index 5f96a017d..7024ff971 100644 --- a/web/src/components/player/HlsVideoPlayer.tsx +++ b/web/src/components/player/HlsVideoPlayer.tsx @@ -137,10 +137,15 @@ export default function HlsVideoPlayer({ className="absolute bottom-5 left-1/2 -translate-x-1/2 z-50" video={videoRef.current} isPlaying={isPlaying} - show={visible && controls} + show={visible && (controls || controlsOpen)} muted={muted} volume={volume} - controlsOpen={controlsOpen} + features={{ + volume: true, + seek: true, + playbackRate: true, + plusUpload: true, + }} setControlsOpen={setControlsOpen} setMuted={setMuted} playbackRate={videoRef.current?.playbackRate ?? 1} diff --git a/web/src/components/player/VideoControls.tsx b/web/src/components/player/VideoControls.tsx index 872289aa0..7b511ba53 100644 --- a/web/src/components/player/VideoControls.tsx +++ b/web/src/components/player/VideoControls.tsx @@ -1,4 +1,4 @@ -import { useCallback, useMemo } from "react"; +import { useCallback, useMemo, useState } from "react"; import { isSafari } from "react-device-detect"; import { LuPause, LuPlay } from "react-icons/lu"; import { @@ -18,17 +18,30 @@ import { } from "react-icons/md"; import useKeyboardListener from "@/hooks/use-keyboard-listener"; import { VolumeSlider } from "../ui/slider"; +import FrigatePlusIcon from "../icons/FrigatePlusIcon"; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogTrigger, +} from "../ui/alert-dialog"; type VideoControls = { volume?: boolean; seek?: boolean; playbackRate?: boolean; + plusUpload?: boolean; }; const CONTROLS_DEFAULT: VideoControls = { volume: true, seek: true, playbackRate: true, + plusUpload: false, }; const PLAYBACK_RATE_DEFAULT = isSafari ? [0.5, 1, 2] : [0.5, 1, 2, 4, 8, 16]; @@ -40,7 +53,6 @@ type VideoControlsProps = { show: boolean; muted?: boolean; volume?: number; - controlsOpen?: boolean; playbackRates?: number[]; playbackRate: number; hotKeys?: boolean; @@ -58,7 +70,6 @@ export default function VideoControls({ show, muted, volume, - controlsOpen, playbackRates = PLAYBACK_RATE_DEFAULT, playbackRate, hotKeys = true, @@ -189,7 +200,6 @@ export default function VideoControls({ )} {features.playbackRate && ( { if (setControlsOpen) { setControlsOpen(open); @@ -214,6 +224,81 @@ export default function VideoControls({ )} + {features.plusUpload && ( + { + if (setControlsOpen) { + setControlsOpen(false); + } + }} + onOpen={() => { + onPlayPause(false); + + if (setControlsOpen) { + setControlsOpen(true); + } + }} + /> + )} ); } + +type FrigatePlusUploadButtonProps = { + video?: HTMLVideoElement | null; + onOpen: () => void; + onClose: () => void; +}; +function FrigatePlusUploadButton({ + video, + onOpen, + onClose, +}: FrigatePlusUploadButtonProps) { + const [videoImg, setVideoImg] = useState(); + + return ( + { + if (!open) { + onClose(); + } + }} + > + + { + onOpen(); + + if (video) { + const videoSize = [video.clientWidth, video.clientHeight]; + const canvas = document.createElement("canvas"); + canvas.width = videoSize[0]; + canvas.height = videoSize[1]; + + const context = canvas?.getContext("2d"); + + if (context) { + context.drawImage(video, 0, 0, videoSize[0], videoSize[1]); + setVideoImg(canvas.toDataURL("image/webp")); + } + } + }} + /> + + + + + Submit this frame to Frigate Plus? + + + + + Submit + Cancel + + + + ); +}