forked from Github/frigate
Compare commits
23 Commits
v0.11.0-be
...
v0.11.0-rc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
37325c70ba | ||
|
|
3c46a33992 | ||
|
|
ed1897db71 | ||
|
|
dfbebb63ff | ||
|
|
a67a768e89 | ||
|
|
43f05c18d6 | ||
|
|
3b076c28c2 | ||
|
|
cbf12e3f90 | ||
|
|
17b745434c | ||
|
|
37011c2fda | ||
|
|
fa95a041dd | ||
|
|
0879d7a2d1 | ||
|
|
061fb15a80 | ||
|
|
3246fcce22 | ||
|
|
f2a3797b46 | ||
|
|
b80080ac52 | ||
|
|
b36b63599b | ||
|
|
5d8c0e43c2 | ||
|
|
7845995dfd | ||
|
|
afe88d6e3a | ||
|
|
560ee0104d | ||
|
|
dc8b625d55 | ||
|
|
162c0147d2 |
@@ -6,6 +6,7 @@ services:
|
|||||||
# add groups from host for render, plugdev, video
|
# add groups from host for render, plugdev, video
|
||||||
group_add:
|
group_add:
|
||||||
- "109" # render
|
- "109" # render
|
||||||
|
- "110" # render
|
||||||
- "44" # video
|
- "44" # video
|
||||||
- "46" # plugdev
|
- "46" # plugdev
|
||||||
shm_size: "256mb"
|
shm_size: "256mb"
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ RUN apt-get -qq update \
|
|||||||
apt-transport-https \
|
apt-transport-https \
|
||||||
gnupg \
|
gnupg \
|
||||||
wget \
|
wget \
|
||||||
&& wget -O - http://archive.raspberrypi.org/debian/raspberrypi.gpg.key | apt-key add - \
|
&& apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 9165938D90FDDD2E \
|
||||||
&& echo "deb http://archive.raspberrypi.org/debian/ bullseye main" | tee /etc/apt/sources.list.d/raspi.list \
|
&& echo "deb http://raspbian.raspberrypi.org/raspbian/ bullseye main contrib non-free rpi" | tee /etc/apt/sources.list.d/raspi.list \
|
||||||
&& apt-get -qq update \
|
&& apt-get -qq update \
|
||||||
&& apt-get -qq install -y \
|
&& apt-get -qq install -y \
|
||||||
python3 \
|
python3 \
|
||||||
@@ -46,7 +46,7 @@ RUN pip3 wheel --wheel-dir=/wheels -r requirements-wheels.txt
|
|||||||
FROM debian:11-slim
|
FROM debian:11-slim
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
|
|
||||||
ARG JELLYFIN_FFMPEG_VERSION=4.4.1-4
|
ARG JELLYFIN_FFMPEG_VERSION=5.0.1-7
|
||||||
# https://askubuntu.com/questions/972516/debian-frontend-environment-variable
|
# https://askubuntu.com/questions/972516/debian-frontend-environment-variable
|
||||||
ARG DEBIAN_FRONTEND="noninteractive"
|
ARG DEBIAN_FRONTEND="noninteractive"
|
||||||
# http://stackoverflow.com/questions/48162574/ddg#49462622
|
# http://stackoverflow.com/questions/48162574/ddg#49462622
|
||||||
@@ -67,8 +67,8 @@ RUN apt-get -qq update \
|
|||||||
unzip tzdata libxml2 xz-utils \
|
unzip tzdata libxml2 xz-utils \
|
||||||
python3-pip \
|
python3-pip \
|
||||||
# add raspberry pi repo
|
# add raspberry pi repo
|
||||||
&& wget -O - http://archive.raspberrypi.org/debian/raspberrypi.gpg.key | apt-key add - \
|
&& apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 9165938D90FDDD2E \
|
||||||
&& echo "deb http://archive.raspberrypi.org/debian/ bullseye main" | tee /etc/apt/sources.list.d/raspi.list \
|
&& echo "deb http://raspbian.raspberrypi.org/raspbian/ bullseye main contrib non-free rpi" | tee /etc/apt/sources.list.d/raspi.list \
|
||||||
# add coral repo
|
# add coral repo
|
||||||
&& apt-key adv --fetch-keys https://packages.cloud.google.com/apt/doc/apt-key.gpg \
|
&& apt-key adv --fetch-keys https://packages.cloud.google.com/apt/doc/apt-key.gpg \
|
||||||
&& echo "deb https://packages.cloud.google.com/apt coral-edgetpu-stable main" > /etc/apt/sources.list.d/coral-edgetpu.list \
|
&& echo "deb https://packages.cloud.google.com/apt coral-edgetpu-stable main" > /etc/apt/sources.list.d/coral-edgetpu.list \
|
||||||
@@ -80,15 +80,24 @@ RUN apt-get -qq update \
|
|||||||
# coral drivers
|
# coral drivers
|
||||||
libedgetpu1-max python3-tflite-runtime python3-pycoral \
|
libedgetpu1-max python3-tflite-runtime python3-pycoral \
|
||||||
&& pip3 install -U /wheels/*.whl \
|
&& pip3 install -U /wheels/*.whl \
|
||||||
|
# jellyfin-ffmpeg
|
||||||
|
&& wget -O jellyfin.deb "https://repo.jellyfin.org/releases/server/debian/versions/jellyfin-ffmpeg/${JELLYFIN_FFMPEG_VERSION}/jellyfin-ffmpeg5_${JELLYFIN_FFMPEG_VERSION}-$( awk -F'=' '/^VERSION_CODENAME=/{ print $NF }' /etc/os-release )_$( dpkg --print-architecture ).deb" \
|
||||||
|
&& apt-get -qq install --no-install-recommends --no-install-suggests -y ./jellyfin.deb \
|
||||||
|
&& rm jellyfin.deb \
|
||||||
# arch specific packages
|
# arch specific packages
|
||||||
&& if [ "${TARGETARCH}" = "amd64" ]; then \
|
&& if [ "${TARGETARCH}" = "amd64" ]; then \
|
||||||
# jellyfin-ffmpeg
|
|
||||||
wget -O jellyfin.deb "https://repo.jellyfin.org/releases/server/debian/versions/jellyfin-ffmpeg/${JELLYFIN_FFMPEG_VERSION}/jellyfin-ffmpeg_${JELLYFIN_FFMPEG_VERSION}-$( awk -F'=' '/^VERSION_CODENAME=/{ print $NF }' /etc/os-release )_$( dpkg --print-architecture ).deb" \
|
|
||||||
&& apt-get -qq install --no-install-recommends --no-install-suggests -y \
|
|
||||||
mesa-va-drivers intel-media-va-driver-non-free ./jellyfin.deb \
|
|
||||||
&& rm jellyfin.deb; else \
|
|
||||||
apt-get -qq install --no-install-recommends --no-install-suggests -y \
|
apt-get -qq install --no-install-recommends --no-install-suggests -y \
|
||||||
ffmpeg; \
|
mesa-va-drivers intel-media-va-driver-non-free; \
|
||||||
|
fi \
|
||||||
|
# not sure why 32bit arm requires all these
|
||||||
|
&& if [ "${TARGETARCH}" = "arm" ]; then \
|
||||||
|
apt-get -qq install --no-install-recommends --no-install-suggests -y \
|
||||||
|
libgtk-3-dev \
|
||||||
|
libavcodec-dev libavformat-dev libswscale-dev libv4l-dev \
|
||||||
|
libxvidcore-dev libx264-dev libjpeg-dev libpng-dev libtiff-dev \
|
||||||
|
gfortran openexr libatlas-base-dev libssl-dev\
|
||||||
|
libtbb2 libtbb-dev libdc1394-22-dev libopenexr-dev \
|
||||||
|
libgstreamer-plugins-base1.0-dev libgstreamer1.0-dev; \
|
||||||
fi \
|
fi \
|
||||||
&& rm -rf /wheels \
|
&& rm -rf /wheels \
|
||||||
&& apt-get remove gnupg apt-transport-https -y \
|
&& apt-get remove gnupg apt-transport-https -y \
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ You will need to remove `nobuffer` flag for Blue Iris RTSP cameras
|
|||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
ffmpeg:
|
ffmpeg:
|
||||||
input_args: -avoid_negative_ts make_zero -flags low_delay -strict experimental -fflags +genpts+discardcorrupt -rtsp_transport tcp -stimeout 5000000 -use_wallclock_as_timestamps 1
|
input_args: -avoid_negative_ts make_zero -flags low_delay -strict experimental -fflags +genpts+discardcorrupt -rtsp_transport tcp -timeout 5000000 -use_wallclock_as_timestamps 1
|
||||||
```
|
```
|
||||||
|
|
||||||
### UDP Only Cameras
|
### UDP Only Cameras
|
||||||
@@ -111,5 +111,5 @@ If your cameras do not support TCP connections for RTSP, you can use UDP.
|
|||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
ffmpeg:
|
ffmpeg:
|
||||||
input_args: -avoid_negative_ts make_zero -fflags +genpts+discardcorrupt -rtsp_transport udp -stimeout 5000000 -use_wallclock_as_timestamps 1
|
input_args: -avoid_negative_ts make_zero -fflags +genpts+discardcorrupt -rtsp_transport udp -timeout 5000000 -use_wallclock_as_timestamps 1
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ ffmpeg:
|
|||||||
ffmpeg:
|
ffmpeg:
|
||||||
hwaccel_args: -hwaccel vaapi -hwaccel_device /dev/dri/renderD128 -hwaccel_output_format yuv420p
|
hwaccel_args: -hwaccel vaapi -hwaccel_device /dev/dri/renderD128 -hwaccel_output_format yuv420p
|
||||||
```
|
```
|
||||||
|
**NOTICE**: With some of the processors, like the J4125, the default driver `iHD` doesn't seem to work correctly for hardware acceleration. You may need to change the driver to `i965` by adding the following environment variable `LIBVA_DRIVER_NAME_JELLYFIN=i965` to your docker-compose file.
|
||||||
|
|
||||||
### Intel-based CPUs (>=10th Generation) via Quicksync
|
### Intel-based CPUs (>=10th Generation) via Quicksync
|
||||||
|
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ ffmpeg:
|
|||||||
# NOTE: See hardware acceleration docs for your specific device
|
# NOTE: See hardware acceleration docs for your specific device
|
||||||
hwaccel_args: []
|
hwaccel_args: []
|
||||||
# Optional: global input args (default: shown below)
|
# Optional: global input args (default: shown below)
|
||||||
input_args: -avoid_negative_ts make_zero -fflags +genpts+discardcorrupt -rtsp_transport tcp -stimeout 5000000 -use_wallclock_as_timestamps 1
|
input_args: -avoid_negative_ts make_zero -fflags +genpts+discardcorrupt -rtsp_transport tcp -timeout 5000000 -use_wallclock_as_timestamps 1
|
||||||
# Optional: global output args
|
# Optional: global output args
|
||||||
output_args:
|
output_args:
|
||||||
# Optional: output args for detect streams (default: shown below)
|
# Optional: output args for detect streams (default: shown below)
|
||||||
|
|||||||
@@ -208,3 +208,16 @@ npm run build
|
|||||||
```
|
```
|
||||||
|
|
||||||
This command generates static content into the `build` directory and can be served using any static contents hosting service.
|
This command generates static content into the `build` directory and can be served using any static contents hosting service.
|
||||||
|
|
||||||
|
## Official builds
|
||||||
|
|
||||||
|
Setup buildx for multiarch
|
||||||
|
|
||||||
|
```
|
||||||
|
docker buildx stop builder && docker buildx rm builder # <---- if existing
|
||||||
|
docker run --privileged --rm tonistiigi/binfmt --install all
|
||||||
|
docker buildx create --name builder --driver docker-container --driver-opt network=host --use
|
||||||
|
docker buildx inspect builder --bootstrap
|
||||||
|
make build_web
|
||||||
|
make push
|
||||||
|
```
|
||||||
|
|||||||
@@ -140,3 +140,19 @@ Topic to turn improve_contrast for a camera on and off. Expected values are `ON`
|
|||||||
### `frigate/<camera_name>/improve_contrast/state`
|
### `frigate/<camera_name>/improve_contrast/state`
|
||||||
|
|
||||||
Topic with current state of improve_contrast for a camera. Published values are `ON` and `OFF`.
|
Topic with current state of improve_contrast for a camera. Published values are `ON` and `OFF`.
|
||||||
|
|
||||||
|
### `frigate/<camera_name>/motion_threshold/set`
|
||||||
|
|
||||||
|
Topic to adjust motion threshold for a camera. Expected value is an integer.
|
||||||
|
|
||||||
|
### `frigate/<camera_name>/motion_threshold/state`
|
||||||
|
|
||||||
|
Topic with current motion threshold for a camera. Published value is an integer.
|
||||||
|
|
||||||
|
### `frigate/<camera_name>/motion_contour_area/set`
|
||||||
|
|
||||||
|
Topic to adjust motion contour area for a camera. Expected value is an integer.
|
||||||
|
|
||||||
|
### `frigate/<camera_name>/motion_contour_area/state`
|
||||||
|
|
||||||
|
Topic with current motion contour area for a camera. Published value is an integer.
|
||||||
@@ -95,6 +95,12 @@ class FrigateApp:
|
|||||||
"improve_contrast_enabled": mp.Value(
|
"improve_contrast_enabled": mp.Value(
|
||||||
"i", self.config.cameras[camera_name].motion.improve_contrast
|
"i", self.config.cameras[camera_name].motion.improve_contrast
|
||||||
),
|
),
|
||||||
|
"motion_threshold": mp.Value(
|
||||||
|
"i", self.config.cameras[camera_name].motion.threshold
|
||||||
|
),
|
||||||
|
"motion_contour_area": mp.Value(
|
||||||
|
"i", self.config.cameras[camera_name].motion.contour_area
|
||||||
|
),
|
||||||
"detection_fps": mp.Value("d", 0.0),
|
"detection_fps": mp.Value("d", 0.0),
|
||||||
"detection_frame": mp.Value("d", 0.0),
|
"detection_frame": mp.Value("d", 0.0),
|
||||||
"read_start": mp.Value("d", 0.0),
|
"read_start": mp.Value("d", 0.0),
|
||||||
|
|||||||
@@ -346,7 +346,7 @@ FFMPEG_INPUT_ARGS_DEFAULT = [
|
|||||||
"+genpts+discardcorrupt",
|
"+genpts+discardcorrupt",
|
||||||
"-rtsp_transport",
|
"-rtsp_transport",
|
||||||
"tcp",
|
"tcp",
|
||||||
"-stimeout",
|
"-timeout",
|
||||||
"5000000",
|
"5000000",
|
||||||
"-use_wallclock_as_timestamps",
|
"-use_wallclock_as_timestamps",
|
||||||
"1",
|
"1",
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ from flask import (
|
|||||||
from peewee import SqliteDatabase, operator, fn, DoesNotExist
|
from peewee import SqliteDatabase, operator, fn, DoesNotExist
|
||||||
from playhouse.shortcuts import model_to_dict
|
from playhouse.shortcuts import model_to_dict
|
||||||
|
|
||||||
from frigate.const import CLIPS_DIR, PLUS_ENV_VAR
|
from frigate.const import CLIPS_DIR
|
||||||
from frigate.models import Event, Recordings
|
from frigate.models import Event, Recordings
|
||||||
from frigate.stats import stats_snapshot
|
from frigate.stats import stats_snapshot
|
||||||
from frigate.version import VERSION
|
from frigate.version import VERSION
|
||||||
@@ -571,7 +571,7 @@ def config():
|
|||||||
for cmd in camera_dict["ffmpeg_cmds"]:
|
for cmd in camera_dict["ffmpeg_cmds"]:
|
||||||
cmd["cmd"] = " ".join(cmd["cmd"])
|
cmd["cmd"] = " ".join(cmd["cmd"])
|
||||||
|
|
||||||
config["plus"] = {"enabled": PLUS_ENV_VAR in os.environ}
|
config["plus"] = {"enabled": current_app.plus_api.is_active()}
|
||||||
|
|
||||||
return jsonify(config)
|
return jsonify(config)
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,14 @@ from frigate.config import MotionConfig
|
|||||||
|
|
||||||
|
|
||||||
class MotionDetector:
|
class MotionDetector:
|
||||||
def __init__(self, frame_shape, config: MotionConfig, improve_contrast_enabled):
|
def __init__(
|
||||||
|
self,
|
||||||
|
frame_shape,
|
||||||
|
config: MotionConfig,
|
||||||
|
improve_contrast_enabled,
|
||||||
|
motion_threshold,
|
||||||
|
motion_contour_area,
|
||||||
|
):
|
||||||
self.config = config
|
self.config = config
|
||||||
self.frame_shape = frame_shape
|
self.frame_shape = frame_shape
|
||||||
self.resize_factor = frame_shape[0] / config.frame_height
|
self.resize_factor = frame_shape[0] / config.frame_height
|
||||||
@@ -25,6 +32,8 @@ class MotionDetector:
|
|||||||
self.mask = np.where(resized_mask == [0])
|
self.mask = np.where(resized_mask == [0])
|
||||||
self.save_images = False
|
self.save_images = False
|
||||||
self.improve_contrast = improve_contrast_enabled
|
self.improve_contrast = improve_contrast_enabled
|
||||||
|
self.threshold = motion_threshold
|
||||||
|
self.contour_area = motion_contour_area
|
||||||
|
|
||||||
def detect(self, frame):
|
def detect(self, frame):
|
||||||
motion_boxes = []
|
motion_boxes = []
|
||||||
@@ -69,7 +78,7 @@ class MotionDetector:
|
|||||||
|
|
||||||
# compute the threshold image for the current frame
|
# compute the threshold image for the current frame
|
||||||
current_thresh = cv2.threshold(
|
current_thresh = cv2.threshold(
|
||||||
frameDelta, self.config.threshold, 255, cv2.THRESH_BINARY
|
frameDelta, self.threshold.value, 255, cv2.THRESH_BINARY
|
||||||
)[1]
|
)[1]
|
||||||
|
|
||||||
# black out everything in the avg_delta where there isnt motion in the current frame
|
# black out everything in the avg_delta where there isnt motion in the current frame
|
||||||
@@ -79,7 +88,7 @@ class MotionDetector:
|
|||||||
# then look for deltas above the threshold, but only in areas where there is a delta
|
# then look for deltas above the threshold, but only in areas where there is a delta
|
||||||
# in the current frame. this prevents deltas from previous frames from being included
|
# in the current frame. this prevents deltas from previous frames from being included
|
||||||
thresh = cv2.threshold(
|
thresh = cv2.threshold(
|
||||||
avg_delta_image, self.config.threshold, 255, cv2.THRESH_BINARY
|
avg_delta_image, self.threshold.value, 255, cv2.THRESH_BINARY
|
||||||
)[1]
|
)[1]
|
||||||
|
|
||||||
# dilate the thresholded image to fill in holes, then find contours
|
# dilate the thresholded image to fill in holes, then find contours
|
||||||
@@ -94,7 +103,7 @@ class MotionDetector:
|
|||||||
for c in cnts:
|
for c in cnts:
|
||||||
# if the contour is big enough, count it as motion
|
# if the contour is big enough, count it as motion
|
||||||
contour_area = cv2.contourArea(c)
|
contour_area = cv2.contourArea(c)
|
||||||
if contour_area > self.config.contour_area:
|
if contour_area > self.contour_area.value:
|
||||||
x, y, w, h = cv2.boundingRect(c)
|
x, y, w, h = cv2.boundingRect(c)
|
||||||
motion_boxes.append(
|
motion_boxes.append(
|
||||||
(
|
(
|
||||||
@@ -111,8 +120,7 @@ class MotionDetector:
|
|||||||
# print(self.frame_counter)
|
# print(self.frame_counter)
|
||||||
for c in cnts:
|
for c in cnts:
|
||||||
contour_area = cv2.contourArea(c)
|
contour_area = cv2.contourArea(c)
|
||||||
# print(contour_area)
|
if contour_area > self.contour_area.value:
|
||||||
if contour_area > self.config.contour_area:
|
|
||||||
x, y, w, h = cv2.boundingRect(c)
|
x, y, w, h = cv2.boundingRect(c)
|
||||||
cv2.rectangle(
|
cv2.rectangle(
|
||||||
thresh_dilated,
|
thresh_dilated,
|
||||||
|
|||||||
@@ -145,6 +145,52 @@ def create_mqtt_client(config: FrigateConfig, camera_metrics):
|
|||||||
state_topic = f"{message.topic[:-4]}/state"
|
state_topic = f"{message.topic[:-4]}/state"
|
||||||
client.publish(state_topic, payload, retain=True)
|
client.publish(state_topic, payload, retain=True)
|
||||||
|
|
||||||
|
def on_motion_threshold_command(client, userdata, message):
|
||||||
|
try:
|
||||||
|
payload = int(message.payload.decode())
|
||||||
|
except ValueError:
|
||||||
|
logger.warning(
|
||||||
|
f"Received unsupported value at {message.topic}: {message.payload.decode()}"
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
logger.debug(f"on_motion_threshold_toggle: {message.topic} {payload}")
|
||||||
|
|
||||||
|
camera_name = message.topic.split("/")[-3]
|
||||||
|
|
||||||
|
motion_settings = config.cameras[camera_name].motion
|
||||||
|
|
||||||
|
logger.info(f"Setting motion threshold for {camera_name} via mqtt: {payload}")
|
||||||
|
camera_metrics[camera_name]["motion_threshold"].value = payload
|
||||||
|
motion_settings.threshold = payload
|
||||||
|
|
||||||
|
state_topic = f"{message.topic[:-4]}/state"
|
||||||
|
client.publish(state_topic, payload, retain=True)
|
||||||
|
|
||||||
|
def on_motion_contour_area_command(client, userdata, message):
|
||||||
|
try:
|
||||||
|
payload = int(message.payload.decode())
|
||||||
|
except ValueError:
|
||||||
|
logger.warning(
|
||||||
|
f"Received unsupported value at {message.topic}: {message.payload.decode()}"
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
logger.debug(f"on_motion_contour_area_toggle: {message.topic} {payload}")
|
||||||
|
|
||||||
|
camera_name = message.topic.split("/")[-3]
|
||||||
|
|
||||||
|
motion_settings = config.cameras[camera_name].motion
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
f"Setting motion contour area for {camera_name} via mqtt: {payload}"
|
||||||
|
)
|
||||||
|
camera_metrics[camera_name]["motion_contour_area"].value = payload
|
||||||
|
motion_settings.contour_area = payload
|
||||||
|
|
||||||
|
state_topic = f"{message.topic[:-4]}/state"
|
||||||
|
client.publish(state_topic, payload, retain=True)
|
||||||
|
|
||||||
def on_restart_command(client, userdata, message):
|
def on_restart_command(client, userdata, message):
|
||||||
restart_frigate()
|
restart_frigate()
|
||||||
|
|
||||||
@@ -195,6 +241,14 @@ def create_mqtt_client(config: FrigateConfig, camera_metrics):
|
|||||||
f"{mqtt_config.topic_prefix}/{name}/improve_contrast/set",
|
f"{mqtt_config.topic_prefix}/{name}/improve_contrast/set",
|
||||||
on_improve_contrast_command,
|
on_improve_contrast_command,
|
||||||
)
|
)
|
||||||
|
client.message_callback_add(
|
||||||
|
f"{mqtt_config.topic_prefix}/{name}/motion_threshold/set",
|
||||||
|
on_motion_threshold_command,
|
||||||
|
)
|
||||||
|
client.message_callback_add(
|
||||||
|
f"{mqtt_config.topic_prefix}/{name}/motion_contour_area/set",
|
||||||
|
on_motion_contour_area_command,
|
||||||
|
)
|
||||||
|
|
||||||
client.message_callback_add(
|
client.message_callback_add(
|
||||||
f"{mqtt_config.topic_prefix}/restart", on_restart_command
|
f"{mqtt_config.topic_prefix}/restart", on_restart_command
|
||||||
@@ -250,6 +304,21 @@ def create_mqtt_client(config: FrigateConfig, camera_metrics):
|
|||||||
"ON" if config.cameras[name].motion.improve_contrast else "OFF",
|
"ON" if config.cameras[name].motion.improve_contrast else "OFF",
|
||||||
retain=True,
|
retain=True,
|
||||||
)
|
)
|
||||||
|
client.publish(
|
||||||
|
f"{mqtt_config.topic_prefix}/{name}/motion_threshold/state",
|
||||||
|
config.cameras[name].motion.threshold,
|
||||||
|
retain=True,
|
||||||
|
)
|
||||||
|
client.publish(
|
||||||
|
f"{mqtt_config.topic_prefix}/{name}/motion_contour_area/state",
|
||||||
|
config.cameras[name].motion.contour_area,
|
||||||
|
retain=True,
|
||||||
|
)
|
||||||
|
client.publish(
|
||||||
|
f"{mqtt_config.topic_prefix}/{name}/motion",
|
||||||
|
"OFF",
|
||||||
|
retain=False,
|
||||||
|
)
|
||||||
|
|
||||||
return client
|
return client
|
||||||
|
|
||||||
|
|||||||
@@ -99,11 +99,23 @@ class RecordingMaintainer(threading.Thread):
|
|||||||
# delete all cached files past the most recent 5
|
# delete all cached files past the most recent 5
|
||||||
keep_count = 5
|
keep_count = 5
|
||||||
for camera in grouped_recordings.keys():
|
for camera in grouped_recordings.keys():
|
||||||
if len(grouped_recordings[camera]) > keep_count:
|
segment_count = len(grouped_recordings[camera])
|
||||||
|
if segment_count > keep_count:
|
||||||
|
####
|
||||||
|
# Need to find a way to tell if these are aging out based on retention settings or if the system is overloaded.
|
||||||
|
####
|
||||||
|
# logger.warning(
|
||||||
|
# f"Too many recording segments in cache for {camera}. Keeping the {keep_count} most recent segments out of {segment_count}, discarding the rest..."
|
||||||
|
# )
|
||||||
to_remove = grouped_recordings[camera][:-keep_count]
|
to_remove = grouped_recordings[camera][:-keep_count]
|
||||||
for f in to_remove:
|
for f in to_remove:
|
||||||
Path(f["cache_path"]).unlink(missing_ok=True)
|
cache_path = f["cache_path"]
|
||||||
self.end_time_cache.pop(f["cache_path"], None)
|
####
|
||||||
|
# Need to find a way to tell if these are aging out based on retention settings or if the system is overloaded.
|
||||||
|
####
|
||||||
|
# logger.warning(f"Discarding a recording segment: {cache_path}")
|
||||||
|
Path(cache_path).unlink(missing_ok=True)
|
||||||
|
self.end_time_cache.pop(cache_path, None)
|
||||||
grouped_recordings[camera] = grouped_recordings[camera][-keep_count:]
|
grouped_recordings[camera] = grouped_recordings[camera][-keep_count:]
|
||||||
|
|
||||||
for camera, recordings in grouped_recordings.items():
|
for camera, recordings in grouped_recordings.items():
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ from playhouse.shortcuts import model_to_dict
|
|||||||
from frigate.config import FrigateConfig
|
from frigate.config import FrigateConfig
|
||||||
from frigate.http import create_app
|
from frigate.http import create_app
|
||||||
from frigate.models import Event, Recordings
|
from frigate.models import Event, Recordings
|
||||||
|
from frigate.plus import PlusApi
|
||||||
|
|
||||||
from frigate.test.const import TEST_DB, TEST_DB_CLEANUPS
|
from frigate.test.const import TEST_DB, TEST_DB_CLEANUPS
|
||||||
|
|
||||||
@@ -113,7 +114,7 @@ class TestHttp(unittest.TestCase):
|
|||||||
|
|
||||||
def test_get_event_list(self):
|
def test_get_event_list(self):
|
||||||
app = create_app(
|
app = create_app(
|
||||||
FrigateConfig(**self.minimal_config), self.db, None, None, None
|
FrigateConfig(**self.minimal_config), self.db, None, None, PlusApi()
|
||||||
)
|
)
|
||||||
id = "123456.random"
|
id = "123456.random"
|
||||||
id2 = "7890.random"
|
id2 = "7890.random"
|
||||||
@@ -142,7 +143,7 @@ class TestHttp(unittest.TestCase):
|
|||||||
|
|
||||||
def test_get_good_event(self):
|
def test_get_good_event(self):
|
||||||
app = create_app(
|
app = create_app(
|
||||||
FrigateConfig(**self.minimal_config), self.db, None, None, None
|
FrigateConfig(**self.minimal_config), self.db, None, None, PlusApi()
|
||||||
)
|
)
|
||||||
id = "123456.random"
|
id = "123456.random"
|
||||||
|
|
||||||
@@ -156,7 +157,7 @@ class TestHttp(unittest.TestCase):
|
|||||||
|
|
||||||
def test_get_bad_event(self):
|
def test_get_bad_event(self):
|
||||||
app = create_app(
|
app = create_app(
|
||||||
FrigateConfig(**self.minimal_config), self.db, None, None, None
|
FrigateConfig(**self.minimal_config), self.db, None, None, PlusApi()
|
||||||
)
|
)
|
||||||
id = "123456.random"
|
id = "123456.random"
|
||||||
bad_id = "654321.other"
|
bad_id = "654321.other"
|
||||||
@@ -169,7 +170,7 @@ class TestHttp(unittest.TestCase):
|
|||||||
|
|
||||||
def test_delete_event(self):
|
def test_delete_event(self):
|
||||||
app = create_app(
|
app = create_app(
|
||||||
FrigateConfig(**self.minimal_config), self.db, None, None, None
|
FrigateConfig(**self.minimal_config), self.db, None, None, PlusApi()
|
||||||
)
|
)
|
||||||
id = "123456.random"
|
id = "123456.random"
|
||||||
|
|
||||||
@@ -184,7 +185,7 @@ class TestHttp(unittest.TestCase):
|
|||||||
|
|
||||||
def test_event_retention(self):
|
def test_event_retention(self):
|
||||||
app = create_app(
|
app = create_app(
|
||||||
FrigateConfig(**self.minimal_config), self.db, None, None, None
|
FrigateConfig(**self.minimal_config), self.db, None, None, PlusApi()
|
||||||
)
|
)
|
||||||
id = "123456.random"
|
id = "123456.random"
|
||||||
|
|
||||||
@@ -203,7 +204,7 @@ class TestHttp(unittest.TestCase):
|
|||||||
|
|
||||||
def test_set_delete_sub_label(self):
|
def test_set_delete_sub_label(self):
|
||||||
app = create_app(
|
app = create_app(
|
||||||
FrigateConfig(**self.minimal_config), self.db, None, None, None
|
FrigateConfig(**self.minimal_config), self.db, None, None, PlusApi()
|
||||||
)
|
)
|
||||||
id = "123456.random"
|
id = "123456.random"
|
||||||
sub_label = "sub"
|
sub_label = "sub"
|
||||||
@@ -231,7 +232,7 @@ class TestHttp(unittest.TestCase):
|
|||||||
|
|
||||||
def test_sub_label_list(self):
|
def test_sub_label_list(self):
|
||||||
app = create_app(
|
app = create_app(
|
||||||
FrigateConfig(**self.minimal_config), self.db, None, None, None
|
FrigateConfig(**self.minimal_config), self.db, None, None, PlusApi()
|
||||||
)
|
)
|
||||||
id = "123456.random"
|
id = "123456.random"
|
||||||
sub_label = "sub"
|
sub_label = "sub"
|
||||||
@@ -253,7 +254,7 @@ class TestHttp(unittest.TestCase):
|
|||||||
self.db,
|
self.db,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
PlusApi(),
|
||||||
)
|
)
|
||||||
|
|
||||||
with app.test_client() as client:
|
with app.test_client() as client:
|
||||||
@@ -267,7 +268,7 @@ class TestHttp(unittest.TestCase):
|
|||||||
self.db,
|
self.db,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
PlusApi(),
|
||||||
)
|
)
|
||||||
id = "123456.random"
|
id = "123456.random"
|
||||||
|
|
||||||
@@ -284,7 +285,7 @@ class TestHttp(unittest.TestCase):
|
|||||||
self.db,
|
self.db,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
PlusApi(),
|
||||||
)
|
)
|
||||||
mock_stats.return_value = self.test_stats
|
mock_stats.return_value = self.test_stats
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ class CameraMetricsTypes(TypedDict):
|
|||||||
frame_queue: Queue
|
frame_queue: Queue
|
||||||
motion_enabled: Synchronized
|
motion_enabled: Synchronized
|
||||||
improve_contrast_enabled: Synchronized
|
improve_contrast_enabled: Synchronized
|
||||||
|
motion_threshold: Synchronized
|
||||||
|
motion_contour_area: Synchronized
|
||||||
process: Optional[Process]
|
process: Optional[Process]
|
||||||
process_fps: Synchronized
|
process_fps: Synchronized
|
||||||
read_start: Synchronized
|
read_start: Synchronized
|
||||||
|
|||||||
@@ -363,13 +363,19 @@ def track_camera(
|
|||||||
detection_enabled = process_info["detection_enabled"]
|
detection_enabled = process_info["detection_enabled"]
|
||||||
motion_enabled = process_info["motion_enabled"]
|
motion_enabled = process_info["motion_enabled"]
|
||||||
improve_contrast_enabled = process_info["improve_contrast_enabled"]
|
improve_contrast_enabled = process_info["improve_contrast_enabled"]
|
||||||
|
motion_threshold = process_info["motion_threshold"]
|
||||||
|
motion_contour_area = process_info["motion_contour_area"]
|
||||||
|
|
||||||
frame_shape = config.frame_shape
|
frame_shape = config.frame_shape
|
||||||
objects_to_track = config.objects.track
|
objects_to_track = config.objects.track
|
||||||
object_filters = config.objects.filters
|
object_filters = config.objects.filters
|
||||||
|
|
||||||
motion_detector = MotionDetector(
|
motion_detector = MotionDetector(
|
||||||
frame_shape, config.motion, improve_contrast_enabled
|
frame_shape,
|
||||||
|
config.motion,
|
||||||
|
improve_contrast_enabled,
|
||||||
|
motion_threshold,
|
||||||
|
motion_contour_area,
|
||||||
)
|
)
|
||||||
object_detector = RemoteObjectDetector(
|
object_detector = RemoteObjectDetector(
|
||||||
name, labelmap, detection_queue, result_connection, model_shape
|
name, labelmap, detection_queue, result_connection, model_shape
|
||||||
|
|||||||
13
web/package-lock.json
generated
13
web/package-lock.json
generated
@@ -30,6 +30,7 @@
|
|||||||
"@testing-library/preact": "^2.0.1",
|
"@testing-library/preact": "^2.0.1",
|
||||||
"@testing-library/preact-hooks": "^1.1.0",
|
"@testing-library/preact-hooks": "^1.1.0",
|
||||||
"@testing-library/user-event": "^13.5.0",
|
"@testing-library/user-event": "^13.5.0",
|
||||||
|
"@types/video.js": "^7.3.42",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.18.0",
|
"@typescript-eslint/eslint-plugin": "^5.18.0",
|
||||||
"@typescript-eslint/parser": "^5.18.0",
|
"@typescript-eslint/parser": "^5.18.0",
|
||||||
"autoprefixer": "^10.4.2",
|
"autoprefixer": "^10.4.2",
|
||||||
@@ -3234,6 +3235,12 @@
|
|||||||
"@types/jest": "*"
|
"@types/jest": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/video.js": {
|
||||||
|
"version": "7.3.42",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/video.js/-/video.js-7.3.42.tgz",
|
||||||
|
"integrity": "sha512-AD6AQNMgLTqrgoayC6SshKh8EDkDd9x5pmEuiY9YsniHlhn5jPXdkVqrzKLwviapaRhQF15TQYxo1JWpqXCUBg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/@types/yargs": {
|
"node_modules/@types/yargs": {
|
||||||
"version": "16.0.4",
|
"version": "16.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz",
|
||||||
@@ -14504,6 +14511,12 @@
|
|||||||
"@types/jest": "*"
|
"@types/jest": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@types/video.js": {
|
||||||
|
"version": "7.3.42",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/video.js/-/video.js-7.3.42.tgz",
|
||||||
|
"integrity": "sha512-AD6AQNMgLTqrgoayC6SshKh8EDkDd9x5pmEuiY9YsniHlhn5jPXdkVqrzKLwviapaRhQF15TQYxo1JWpqXCUBg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"@types/yargs": {
|
"@types/yargs": {
|
||||||
"version": "16.0.4",
|
"version": "16.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz",
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
"@testing-library/preact": "^2.0.1",
|
"@testing-library/preact": "^2.0.1",
|
||||||
"@testing-library/preact-hooks": "^1.1.0",
|
"@testing-library/preact-hooks": "^1.1.0",
|
||||||
"@testing-library/user-event": "^13.5.0",
|
"@testing-library/user-event": "^13.5.0",
|
||||||
|
"@types/video.js": "^7.3.42",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.18.0",
|
"@typescript-eslint/eslint-plugin": "^5.18.0",
|
||||||
"@typescript-eslint/parser": "^5.18.0",
|
"@typescript-eslint/parser": "^5.18.0",
|
||||||
"autoprefixer": "^10.4.2",
|
"autoprefixer": "^10.4.2",
|
||||||
|
|||||||
@@ -3,17 +3,17 @@ import { useCallback, useEffect, useRef, useState } from 'preact/hooks';
|
|||||||
import { useApiHost } from '../../api';
|
import { useApiHost } from '../../api';
|
||||||
import { isNullOrUndefined } from '../../utils/objectUtils';
|
import { isNullOrUndefined } from '../../utils/objectUtils';
|
||||||
|
|
||||||
|
import 'videojs-seek-buttons';
|
||||||
|
import 'video.js/dist/video-js.css';
|
||||||
|
import 'videojs-seek-buttons/dist/videojs-seek-buttons.css';
|
||||||
|
|
||||||
|
import videojs, { VideoJsPlayer } from 'video.js';
|
||||||
|
|
||||||
interface OnTimeUpdateEvent {
|
interface OnTimeUpdateEvent {
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
isPlaying: boolean;
|
isPlaying: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface VideoProperties {
|
|
||||||
posterUrl: string;
|
|
||||||
videoUrl: string;
|
|
||||||
height: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface HistoryVideoProps {
|
interface HistoryVideoProps {
|
||||||
id?: string;
|
id?: string;
|
||||||
isPlaying: boolean;
|
isPlaying: boolean;
|
||||||
@@ -32,68 +32,39 @@ export const HistoryVideo = ({
|
|||||||
onPlay,
|
onPlay,
|
||||||
}: HistoryVideoProps) => {
|
}: HistoryVideoProps) => {
|
||||||
const apiHost = useApiHost();
|
const apiHost = useApiHost();
|
||||||
const videoRef = useRef<HTMLVideoElement|null>(null);
|
const videoRef = useRef<HTMLVideoElement>(null);
|
||||||
const [videoHeight, setVideoHeight] = useState<number>(0);
|
|
||||||
const [videoProperties, setVideoProperties] = useState<VideoProperties>({
|
|
||||||
posterUrl: '',
|
|
||||||
videoUrl: '',
|
|
||||||
height: 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
const currentVideo = videoRef.current;
|
const [video, setVideo] = useState<VideoJsPlayer>();
|
||||||
if (currentVideo && !videoHeight) {
|
|
||||||
const currentVideoHeight = currentVideo.offsetHeight;
|
|
||||||
if (currentVideoHeight > 0) {
|
|
||||||
setVideoHeight(currentVideoHeight);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const idExists = !isNullOrUndefined(id);
|
let video: VideoJsPlayer
|
||||||
if (idExists) {
|
if (videoRef.current) {
|
||||||
if (videoRef.current && !videoRef.current.paused) {
|
video = videojs(videoRef.current, {})
|
||||||
videoRef.current = null;
|
setVideo(video)
|
||||||
}
|
|
||||||
|
|
||||||
setVideoProperties({
|
|
||||||
posterUrl: `${apiHost}/api/events/${id}/snapshot.jpg`,
|
|
||||||
videoUrl: `${apiHost}/vod/event/${id}/index.m3u8`,
|
|
||||||
height: videoHeight,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
setVideoProperties({
|
|
||||||
posterUrl: '',
|
|
||||||
videoUrl: '',
|
|
||||||
height: 0,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}, [id, videoHeight, videoRef, apiHost]);
|
() => video?.dispose()
|
||||||
|
}, [videoRef]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const playVideo = (video: HTMLMediaElement) => video.play();
|
if (!video) {
|
||||||
|
return
|
||||||
const attemptPlayVideo = (video: HTMLMediaElement) => {
|
|
||||||
const videoHasNotLoaded = video.readyState <= 1;
|
|
||||||
if (videoHasNotLoaded) {
|
|
||||||
video.oncanplay = () => {
|
|
||||||
playVideo(video);
|
|
||||||
};
|
|
||||||
video.load();
|
|
||||||
} else {
|
|
||||||
playVideo(video);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const video = videoRef.current;
|
|
||||||
const videoExists = !isNullOrUndefined(video);
|
|
||||||
if (video && videoExists) {
|
|
||||||
if (videoIsPlaying) {
|
|
||||||
attemptPlayVideo(video);
|
|
||||||
} else {
|
|
||||||
video.pause();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}, [videoIsPlaying, videoRef]);
|
|
||||||
|
|
||||||
|
if (!id) {
|
||||||
|
video.pause()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
video.src({
|
||||||
|
src: `${apiHost}/vod/event/${id}/index.m3u8`,
|
||||||
|
type: 'application/vnd.apple.mpegurl',
|
||||||
|
});
|
||||||
|
video.poster(`${apiHost}/api/events/${id}/snapshot.jpg`);
|
||||||
|
if (videoIsPlaying) {
|
||||||
|
video.play();
|
||||||
|
}
|
||||||
|
}, [video, id]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const video = videoRef.current;
|
const video = videoRef.current;
|
||||||
@@ -111,32 +82,38 @@ export const HistoryVideo = ({
|
|||||||
isPlaying: videoIsPlaying,
|
isPlaying: videoIsPlaying,
|
||||||
timestamp: target.currentTime,
|
timestamp: target.currentTime,
|
||||||
};
|
};
|
||||||
|
|
||||||
onTimeUpdate && onTimeUpdate(timeUpdateEvent);
|
onTimeUpdate && onTimeUpdate(timeUpdateEvent);
|
||||||
},
|
},
|
||||||
[videoIsPlaying, onTimeUpdate]
|
[videoIsPlaying, onTimeUpdate]
|
||||||
);
|
);
|
||||||
|
|
||||||
const videoPropertiesIsUndefined = isNullOrUndefined(videoProperties);
|
useEffect(() => {
|
||||||
if (videoPropertiesIsUndefined) {
|
if (video && video.readyState() >= 1) {
|
||||||
return <div style={{ height: `${videoHeight}px`, width: '100%' }} />;
|
if (videoIsPlaying) {
|
||||||
}
|
video.play()
|
||||||
|
} else {
|
||||||
|
video.pause()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [video, videoIsPlaying])
|
||||||
|
|
||||||
const { posterUrl, videoUrl, height } = videoProperties;
|
const onLoad = useCallback(() => {
|
||||||
|
if (video && video.readyState() >= 1 && videoIsPlaying) {
|
||||||
|
video.play()
|
||||||
|
}
|
||||||
|
}, [video, videoIsPlaying])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<video
|
<div data-vjs-player>
|
||||||
ref={videoRef}
|
<video
|
||||||
key={posterUrl}
|
ref={videoRef}
|
||||||
onTimeUpdate={onTimeUpdateHandler}
|
onTimeUpdate={onTimeUpdateHandler}
|
||||||
onPause={onPause}
|
onLoadedMetadata={onLoad}
|
||||||
onPlay={onPlay}
|
onPause={onPause}
|
||||||
poster={posterUrl}
|
onPlay={onPlay}
|
||||||
preload='metadata'
|
className="video-js vjs-fluid"
|
||||||
controls
|
data-setup="{}"
|
||||||
style={height ? { minHeight: `${height}px` } : {}}
|
/>
|
||||||
playsInline
|
</div>
|
||||||
>
|
|
||||||
<source type='application/vnd.apple.mpegurl' src={videoUrl} />
|
|
||||||
</video>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -20,11 +20,11 @@ export const TimelineBlocks = ({ timeline, firstBlockOffset, onEventClick }: Tim
|
|||||||
const timelineBlockOffset = (timelineContainerHeight - largestYOffsetInBlocks) / 2;
|
const timelineBlockOffset = (timelineContainerHeight - largestYOffsetInBlocks) / 2;
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className='relative'
|
className="relative"
|
||||||
style={{
|
style={{
|
||||||
height: `${timelineContainerHeight}px`,
|
height: `${timelineContainerHeight}px`,
|
||||||
width: `${timelineContainerWidth}px`,
|
width: `${timelineContainerWidth}px`,
|
||||||
background: "url('/marker.png')",
|
background: "url('/images/marker.png')",
|
||||||
backgroundPosition: 'center',
|
backgroundPosition: 'center',
|
||||||
backgroundSize: '30px',
|
backgroundSize: '30px',
|
||||||
backgroundRepeat: 'repeat',
|
backgroundRepeat: 'repeat',
|
||||||
@@ -41,7 +41,7 @@ export const TimelineBlocks = ({ timeline, firstBlockOffset, onEventClick }: Tim
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return <div />
|
return <div />;
|
||||||
}, [timeline, onEventClick, firstBlockOffset]);
|
}, [timeline, onEventClick, firstBlockOffset]);
|
||||||
|
|
||||||
return timelineEventBlocks;
|
return timelineEventBlocks;
|
||||||
|
|||||||
@@ -15,13 +15,13 @@ export default function Recording({ camera, date, hour = '00', minute = '00', se
|
|||||||
);
|
);
|
||||||
|
|
||||||
const apiHost = useApiHost();
|
const apiHost = useApiHost();
|
||||||
const { data: recordingsSummary } = useSWR(`${camera}/recordings/summary`);
|
const { data: recordingsSummary } = useSWR(`${camera}/recordings/summary`, { revalidateOnFocus: false });
|
||||||
|
|
||||||
const recordingParams = {
|
const recordingParams = {
|
||||||
before: getUnixTime(endOfHour(currentDate)),
|
before: getUnixTime(endOfHour(currentDate)),
|
||||||
after: getUnixTime(startOfHour(currentDate)),
|
after: getUnixTime(startOfHour(currentDate)),
|
||||||
};
|
};
|
||||||
const { data: recordings } = useSWR([`${camera}/recordings`, recordingParams]);
|
const { data: recordings } = useSWR([`${camera}/recordings`, recordingParams], { revalidateOnFocus: false });
|
||||||
|
|
||||||
// calculates the seek seconds by adding up all the seconds in the segments prior to the playback time
|
// calculates the seek seconds by adding up all the seconds in the segments prior to the playback time
|
||||||
const seekSeconds = useMemo(() => {
|
const seekSeconds = useMemo(() => {
|
||||||
|
|||||||
Reference in New Issue
Block a user