diff --git a/package.json b/package.json
index 70c4708..dd10d30 100644
--- a/package.json
+++ b/package.json
@@ -16,6 +16,8 @@
"@testing-library/user-event": "^7.2.1",
"boring-avatars": "^1.11.2",
"dayjs": "^1.11.13",
+ "hls": "^0.0.1",
+ "hls.js": "^1.5.17",
"js-yaml": "^4.1.0",
"material-ui-confirm": "^3.0.5",
"mqtt": "^5.7.3",
diff --git a/src/App.jsx b/src/App.jsx
index 7efe692..d863b1a 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -18,6 +18,7 @@ import EditExperimentProfile from "./EditExperimentProfile";
import EditConfig from "./EditConfig";
import Updates from "./Updates";
import Plugins from "./Plugins";
+import Webcam from "./Webcam";
import {Profiles} from "./Profiles";
import Inventory from "./Inventory";
import Leader from "./Leader";
@@ -121,6 +122,7 @@ function MainSite() {
}/>
}/>
}/>
+ }/>
}/>
}/>
}/>
diff --git a/src/Webcam.jsx b/src/Webcam.jsx
new file mode 100644
index 0000000..2055349
--- /dev/null
+++ b/src/Webcam.jsx
@@ -0,0 +1,98 @@
+import React, { useState } from "react";
+import Grid from '@mui/material/Grid';
+import Card from '@mui/material/Card';
+import CardContent from '@mui/material/CardContent';
+import TextField from '@mui/material/TextField';
+import Typography from '@mui/material/Typography';
+import Box from '@mui/material/Box';
+import HLSVideoPlayer from "./components/HLSVideoPlayer"; // Import your HLSVideoPlayer component
+
+function WebcamContainer() {
+ // State to store stream URLs for two video players
+ const [streamUrl1, setStreamUrl1] = useState("data/webcam.m3u8");
+ const [streamUrl2, setStreamUrl2] = useState("data/webcam.m3u8");
+
+ // Function to handle stream URL change for video player 1
+ const handleUrlChange1 = (e) => {
+ setStreamUrl1(e.target.value);
+ };
+
+ // Function to handle stream URL change for video player 2
+ const handleUrlChange2 = (e) => {
+ setStreamUrl2(e.target.value);
+ };
+
+ return (
+
+
+
+
+
+ Webcams
+
+
+
+
+
+
+
+
+
+
+ {/* Grid layout to display two video players side by side */}
+
+
+
+
+ {/* Input for stream URL 1 */}
+
+ {/* First Video Player */}
+
+
+
+
+
+
+
+
+ {/* Input for stream URL 2 */}
+
+ {/* Second Video Player */}
+
+
+
+
+
+
+ );
+}
+
+function Webcam(props) {
+ React.useEffect(() => {
+ document.title = props.title;
+ }, [props.title]);
+
+ return (
+
+
+
+
+
+ );
+}
+
+export default Webcam;
diff --git a/src/components/HLSVideoPlayer.js b/src/components/HLSVideoPlayer.js
new file mode 100644
index 0000000..741d7f3
--- /dev/null
+++ b/src/components/HLSVideoPlayer.js
@@ -0,0 +1,56 @@
+import React, { useEffect, useRef } from 'react';
+import Hls from 'hls.js';
+
+const HLSVideoPlayer = ({ streamUrl }) => {
+ const videoRef = useRef(null);
+
+ useEffect(() => {
+ if (!streamUrl) {
+ console.error('Stream URL is not provided');
+ return;
+ }
+
+ const video = videoRef.current;
+ let hls;
+
+ if (Hls.isSupported()) {
+ hls = new Hls({
+ startLevel: -1,
+ maxBufferLength: 10, // Lower max buffer length
+ maxBufferSize: 60 * 1000 * 1000, // 60 MB
+ maxBufferHole: 0.2, // Allow smaller buffer holes
+ fragLoadingTimeOut: 5000, // Timeout for fragment loading
+ liveSyncDurationCount: 2, // Reduce live sync duration to lower latency
+ });
+ hls.loadSource(streamUrl);
+ hls.attachMedia(video);
+ hls.on(Hls.Events.MANIFEST_PARSED, () => {
+ video.play();
+ });
+ } else if (video.canPlayType('application/vnd.apple.mpegurl')) {
+ // For Safari
+ video.src = streamUrl;
+ video.addEventListener('loadedmetadata', () => {
+ video.play();
+ });
+ }
+
+ return () => {
+ if (hls) {
+ hls.destroy();
+ }
+ };
+ }, [streamUrl]);
+
+ return (
+
+ {streamUrl ? (
+
+ ) : (
+
Error: Stream URL is missing.
+ )}
+
+ );
+};
+
+export default HLSVideoPlayer;
diff --git a/src/components/SideNavAndHeader.jsx b/src/components/SideNavAndHeader.jsx
index 3e05c98..04218ca 100644
--- a/src/components/SideNavAndHeader.jsx
+++ b/src/components/SideNavAndHeader.jsx
@@ -23,6 +23,7 @@ import DashboardOutlinedIcon from '@mui/icons-material/DashboardOutlined';
import SettingsOutlinedIcon from '@mui/icons-material/SettingsOutlined';
import InsertChartOutlinedIcon from '@mui/icons-material/InsertChartOutlined';
import ViewTimelineOutlinedIcon from '@mui/icons-material/ViewTimelineOutlined';
+import VideocamOutlinedIcon from '@mui/icons-material/VideocamOutlined';
import ChatOutlinedIcon from '@mui/icons-material/ChatOutlined';
import { Link, useLocation, useNavigate } from 'react-router-dom';
import { Sidebar, Menu, MenuItem, SubMenu} from "react-pro-sidebar";
@@ -310,6 +311,15 @@ export default function SideNavAndHeader() {
Profiles
+
+ }
+ component={}
+ active={isSelected("/webcam")}
+ >
+ Webcams
+