From 8d82ac8376b4d71c65fab3214a6e45d43a6977b3 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Mon, 9 Feb 2026 10:54:45 -0600 Subject: [PATCH] Improve jsmpeg player websocket handling (#21943) * improve jsmpeg player websocket handling prevent websocket console messages from appearing when player is destroyed * reformat files after ruff upgrade --- frigate/api/classification.py | 6 ++-- .../real_time/custom_classification.py | 2 +- frigate/data_processing/real_time/face.py | 2 +- frigate/log.py | 9 +++--- frigate/record/maintainer.py | 6 ++-- web/src/components/player/JSMpegPlayer.tsx | 28 +++++++++++++++---- 6 files changed, 36 insertions(+), 17 deletions(-) diff --git a/frigate/api/classification.py b/frigate/api/classification.py index deafaf956..0e1b95d88 100644 --- a/frigate/api/classification.py +++ b/frigate/api/classification.py @@ -71,7 +71,7 @@ def get_faces(): face_dict[name] = [] for file in filter( - lambda f: (f.lower().endswith((".webp", ".png", ".jpg", ".jpeg"))), + lambda f: f.lower().endswith((".webp", ".png", ".jpg", ".jpeg")), os.listdir(face_dir), ): face_dict[name].append(file) @@ -580,7 +580,7 @@ def get_classification_dataset(name: str): dataset_dict[category_name] = [] for file in filter( - lambda f: (f.lower().endswith((".webp", ".png", ".jpg", ".jpeg"))), + lambda f: f.lower().endswith((".webp", ".png", ".jpg", ".jpeg")), os.listdir(category_dir), ): dataset_dict[category_name].append(file) @@ -638,7 +638,7 @@ def get_classification_images(name: str): status_code=200, content=list( filter( - lambda f: (f.lower().endswith((".webp", ".png", ".jpg", ".jpeg"))), + lambda f: f.lower().endswith((".webp", ".png", ".jpg", ".jpeg")), os.listdir(train_dir), ) ), diff --git a/frigate/data_processing/real_time/custom_classification.py b/frigate/data_processing/real_time/custom_classification.py index 25ec3bb86..2a90acfaf 100644 --- a/frigate/data_processing/real_time/custom_classification.py +++ b/frigate/data_processing/real_time/custom_classification.py @@ -642,7 +642,7 @@ def write_classification_attempt( # delete oldest face image if maximum is reached try: files = sorted( - filter(lambda f: (f.endswith(".webp")), os.listdir(folder)), + filter(lambda f: f.endswith(".webp"), os.listdir(folder)), key=lambda f: os.path.getctime(os.path.join(folder, f)), reverse=True, ) diff --git a/frigate/data_processing/real_time/face.py b/frigate/data_processing/real_time/face.py index 1901a81e1..e1c11bf11 100644 --- a/frigate/data_processing/real_time/face.py +++ b/frigate/data_processing/real_time/face.py @@ -539,7 +539,7 @@ class FaceRealTimeProcessor(RealTimeProcessorApi): cv2.imwrite(file, frame) files = sorted( - filter(lambda f: (f.endswith(".webp")), os.listdir(folder)), + filter(lambda f: f.endswith(".webp"), os.listdir(folder)), key=lambda f: os.path.getctime(os.path.join(folder, f)), reverse=True, ) diff --git a/frigate/log.py b/frigate/log.py index f2171ffe0..289bc365e 100644 --- a/frigate/log.py +++ b/frigate/log.py @@ -26,15 +26,16 @@ LOG_HANDLER.setFormatter( # filter out norfair warning LOG_HANDLER.addFilter( - lambda record: not record.getMessage().startswith( - "You are using a scalar distance function" + lambda record: ( + not record.getMessage().startswith("You are using a scalar distance function") ) ) # filter out tflite logging LOG_HANDLER.addFilter( - lambda record: "Created TensorFlow Lite XNNPACK delegate for CPU." - not in record.getMessage() + lambda record: ( + "Created TensorFlow Lite XNNPACK delegate for CPU." not in record.getMessage() + ) ) diff --git a/frigate/record/maintainer.py b/frigate/record/maintainer.py index 8bfa726de..18ad49acf 100644 --- a/frigate/record/maintainer.py +++ b/frigate/record/maintainer.py @@ -194,8 +194,10 @@ class RecordingMaintainer(threading.Thread): processed_segment_count = len( list( filter( - lambda r: r["start_time"].timestamp() - < most_recently_processed_frame_time, + lambda r: ( + r["start_time"].timestamp() + < most_recently_processed_frame_time + ), grouped_recordings[camera], ) ) diff --git a/web/src/components/player/JSMpegPlayer.tsx b/web/src/components/player/JSMpegPlayer.tsx index f85535013..c522ff0a8 100644 --- a/web/src/components/player/JSMpegPlayer.tsx +++ b/web/src/components/player/JSMpegPlayer.tsx @@ -118,6 +118,8 @@ export default function JSMpegPlayer({ const videoWrapper = videoRef.current; const canvas = canvasRef.current; let videoElement: JSMpeg.VideoElement | null = null; + let socket: WebSocket | null = null; + let socketMessageHandler: ((event: MessageEvent) => void) | null = null; let frameCount = 0; @@ -152,12 +154,14 @@ export default function JSMpegPlayer({ videoElement.player.source && videoElement.player.source.socket ) { - const socket = videoElement.player.source.socket; - socket.addEventListener("message", (event: MessageEvent) => { + socket = videoElement.player.source.socket as WebSocket; + socketMessageHandler = (event: MessageEvent) => { if (event.data instanceof ArrayBuffer) { bytesReceivedRef.current += event.data.byteLength; } - }); + }; + + socket.addEventListener("message", socketMessageHandler); } // Update stats every second @@ -197,11 +201,23 @@ export default function JSMpegPlayer({ } if (videoElement) { try { - // this causes issues in react strict mode - // https://stackoverflow.com/questions/76822128/issue-with-cycjimmy-jsmpeg-player-in-react-18-cannot-read-properties-of-null-o - videoElement.destroy(); + videoElement.player?.destroy(); // eslint-disable-next-line no-empty } catch (e) {} + + if (videoWrapper) { + videoWrapper.innerHTML = ""; + // @ts-expect-error playerInstance is set by jsmpeg + videoWrapper.playerInstance = null; + } + } + if (socket) { + if (socketMessageHandler) { + socket.removeEventListener("message", socketMessageHandler); + } + + socket = null; + socketMessageHandler = null; } }; }