Skip to content

Commit 2e324bf

Browse files
authored
Fix UI Crash (#78)
* Attempted fix for UI crash * Bump version
1 parent b029eb1 commit 2e324bf

5 files changed

Lines changed: 46 additions & 14 deletions

File tree

backend/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "smart-sec-cam"
7-
version = "0.4.2"
7+
version = "0.4.3"
88
authors = [
99
{ name = "Scott Barnes", email = "sgbarnes@protonmail.com" }
1010
]

frontend/smart-sec-cam/package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/smart-sec-cam/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "smart-sec-cam",
3-
"version": "0.4.2",
3+
"version": "0.4.3",
44
"private": true,
55
"dependencies": {
66
"@emotion/react": "^11.14.0",
Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,52 @@
1-
import React from "react";
1+
import React, { useCallback } from "react";
22

33
import { useCookies } from 'react-cookie';
44
import io from "socket.io-client";
55

66
import "./ImageViewer.css";
7+
import { base64ToBlob } from "../utils/base64ToBlob";
78

89
const SERVER_URL = "https://localhost:8443"
910
let socket = io(SERVER_URL)
1011

1112
export default function ImageViewer({ room, isPreview }) {
12-
const [srcBlob, setSrcBlob] = React.useState(null);
13-
const [cookies, setCookie] = useCookies(["token"]);
13+
const [srcUrl, setSrcUrl] = React.useState(null);
14+
const [cookies] = useCookies(["token"]);
1415

15-
React.useEffect(() => {
16-
socket.on('image', (payload) => {
17-
if (payload.room === room) {
18-
setSrcBlob(payload.data);
16+
// Stable handler so we can deregister it reliably
17+
const imageHandler = useCallback((payload) => {
18+
if (payload.room !== room) return;
19+
if (!payload.data) return;
20+
try {
21+
const blob = base64ToBlob(payload.data);
22+
const newUrl = URL.createObjectURL(blob);
23+
// Revoke the previous URL to free memory
24+
if (srcUrl) {
25+
URL.revokeObjectURL(srcUrl);
1926
}
20-
});
27+
setSrcUrl(newUrl);
28+
} catch (e) {
29+
// Silently ignore malformed data
30+
}
31+
}, [room, srcUrl]);
32+
33+
React.useEffect(() => {
34+
// Register listener and join the room
35+
socket.on('image', imageHandler);
2136
socket.emit('join', { room, token: cookies.token });
22-
}, [room]);
37+
38+
// Cleanup on unmount or when room changes
39+
return () => {
40+
if (srcUrl) {
41+
URL.revokeObjectURL(srcUrl);
42+
}
43+
socket.off('image', imageHandler);
44+
};
45+
}, [room, cookies.token, imageHandler]);
2346

2447
return (
2548
<div className={`imageviewer ${isPreview ? 'preview' : 'main'}`}>
26-
{srcBlob && <img src={`data:image/jpeg;base64,${srcBlob}`} alt={room} />}
49+
{srcUrl && <img src={srcUrl} alt={room} />}
2750
</div>
2851
);
2952
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export function base64ToBlob(b64, mime = 'image/jpeg') {
2+
const binary = atob(b64);
3+
const len = binary.length;
4+
const bytes = new Uint8Array(len);
5+
for (let i = 0; i < len; i++) {
6+
bytes[i] = binary.charCodeAt(i);
7+
}
8+
return new Blob([bytes], { type: mime });
9+
}

0 commit comments

Comments
 (0)