Show dialog with current frame to be uploaded

This commit is contained in:
Nicolas Mowen 2024-05-02 10:41:51 -06:00
parent fba0f66d79
commit 40faa35199
4 changed files with 117 additions and 8 deletions

View File

@ -7,7 +7,6 @@ import os
import subprocess as sp import subprocess as sp
import time import time
from datetime import datetime, timedelta, timezone from datetime import datetime, timedelta, timezone
from io import BytesIO
from urllib.parse import unquote from urllib.parse import unquote
import cv2 import cv2
@ -15,7 +14,6 @@ import numpy as np
import pytz import pytz
from flask import Blueprint, Response, current_app, jsonify, make_response, request from flask import Blueprint, Response, current_app, jsonify, make_response, request
from peewee import DoesNotExist, fn from peewee import DoesNotExist, fn
from PIL import Image
from tzlocal import get_localzone_name from tzlocal import get_localzone_name
from werkzeug.utils import secure_filename from werkzeug.utils import secure_filename

View File

@ -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 (
<div
className={`relative flex items-center ${className ?? ""}`}
onClick={onClick}
>
<Logo className="size-full" />
<LuPlus className="absolute size-2 translate-x-3 translate-y-3/4" />
</div>
);
}

View File

@ -137,10 +137,15 @@ export default function HlsVideoPlayer({
className="absolute bottom-5 left-1/2 -translate-x-1/2 z-50" className="absolute bottom-5 left-1/2 -translate-x-1/2 z-50"
video={videoRef.current} video={videoRef.current}
isPlaying={isPlaying} isPlaying={isPlaying}
show={visible && controls} show={visible && (controls || controlsOpen)}
muted={muted} muted={muted}
volume={volume} volume={volume}
controlsOpen={controlsOpen} features={{
volume: true,
seek: true,
playbackRate: true,
plusUpload: true,
}}
setControlsOpen={setControlsOpen} setControlsOpen={setControlsOpen}
setMuted={setMuted} setMuted={setMuted}
playbackRate={videoRef.current?.playbackRate ?? 1} playbackRate={videoRef.current?.playbackRate ?? 1}

View File

@ -1,4 +1,4 @@
import { useCallback, useMemo } from "react"; import { useCallback, useMemo, useState } from "react";
import { isSafari } from "react-device-detect"; import { isSafari } from "react-device-detect";
import { LuPause, LuPlay } from "react-icons/lu"; import { LuPause, LuPlay } from "react-icons/lu";
import { import {
@ -18,17 +18,30 @@ import {
} from "react-icons/md"; } from "react-icons/md";
import useKeyboardListener from "@/hooks/use-keyboard-listener"; import useKeyboardListener from "@/hooks/use-keyboard-listener";
import { VolumeSlider } from "../ui/slider"; 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 = { type VideoControls = {
volume?: boolean; volume?: boolean;
seek?: boolean; seek?: boolean;
playbackRate?: boolean; playbackRate?: boolean;
plusUpload?: boolean;
}; };
const CONTROLS_DEFAULT: VideoControls = { const CONTROLS_DEFAULT: VideoControls = {
volume: true, volume: true,
seek: true, seek: true,
playbackRate: true, playbackRate: true,
plusUpload: false,
}; };
const PLAYBACK_RATE_DEFAULT = isSafari ? [0.5, 1, 2] : [0.5, 1, 2, 4, 8, 16]; const PLAYBACK_RATE_DEFAULT = isSafari ? [0.5, 1, 2] : [0.5, 1, 2, 4, 8, 16];
@ -40,7 +53,6 @@ type VideoControlsProps = {
show: boolean; show: boolean;
muted?: boolean; muted?: boolean;
volume?: number; volume?: number;
controlsOpen?: boolean;
playbackRates?: number[]; playbackRates?: number[];
playbackRate: number; playbackRate: number;
hotKeys?: boolean; hotKeys?: boolean;
@ -58,7 +70,6 @@ export default function VideoControls({
show, show,
muted, muted,
volume, volume,
controlsOpen,
playbackRates = PLAYBACK_RATE_DEFAULT, playbackRates = PLAYBACK_RATE_DEFAULT,
playbackRate, playbackRate,
hotKeys = true, hotKeys = true,
@ -189,7 +200,6 @@ export default function VideoControls({
)} )}
{features.playbackRate && ( {features.playbackRate && (
<DropdownMenu <DropdownMenu
open={controlsOpen == true}
onOpenChange={(open) => { onOpenChange={(open) => {
if (setControlsOpen) { if (setControlsOpen) {
setControlsOpen(open); setControlsOpen(open);
@ -214,6 +224,81 @@ export default function VideoControls({
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
)} )}
{features.plusUpload && (
<FrigatePlusUploadButton
video={video}
onClose={() => {
if (setControlsOpen) {
setControlsOpen(false);
}
}}
onOpen={() => {
onPlayPause(false);
if (setControlsOpen) {
setControlsOpen(true);
}
}}
/>
)}
</div> </div>
); );
} }
type FrigatePlusUploadButtonProps = {
video?: HTMLVideoElement | null;
onOpen: () => void;
onClose: () => void;
};
function FrigatePlusUploadButton({
video,
onOpen,
onClose,
}: FrigatePlusUploadButtonProps) {
const [videoImg, setVideoImg] = useState<string>();
return (
<AlertDialog
onOpenChange={(open) => {
if (!open) {
onClose();
}
}}
>
<AlertDialogTrigger asChild>
<FrigatePlusIcon
className="size-5 cursor-pointer"
onClick={() => {
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"));
}
}
}}
/>
</AlertDialogTrigger>
<AlertDialogContent className="md:max-w-[80%]">
<AlertDialogHeader>
<AlertDialogTitle>
Submit this frame to Frigate Plus?
</AlertDialogTitle>
</AlertDialogHeader>
<img className="w-full object-contain" src={videoImg} />
<AlertDialogFooter>
<AlertDialogAction>Submit</AlertDialogAction>
<AlertDialogCancel>Cancel</AlertDialogCancel>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
);
}