110 lines
3.1 KiB
TypeScript
110 lines
3.1 KiB
TypeScript
import React, { useState, useEffect, useRef } from "react";
|
|
import Hls from "hls.js";
|
|
|
|
const API_URL = "http://localhost:8081/start"; // Replace with your actual API endpoint
|
|
|
|
const RtspStream: React.FC = () => {
|
|
const [rtspUrl, setRtspUrl] = useState<string>("");
|
|
const [cameraName, setCameraName] = useState<string>("");
|
|
const [m3u8Url, setM3u8Url] = useState<string | null>(null);
|
|
const [loading, setLoading] = useState<boolean>(false); // Loading state
|
|
const videoRef = useRef<HTMLVideoElement | null>(null);
|
|
|
|
useEffect(() => {
|
|
if (m3u8Url && videoRef.current) {
|
|
if (Hls.isSupported()) {
|
|
const hls = new Hls();
|
|
hls.loadSource(m3u8Url);
|
|
hls.attachMedia(videoRef.current);
|
|
} else if (
|
|
videoRef.current.canPlayType("application/vnd.apple.mpegurl")
|
|
) {
|
|
videoRef.current.src = m3u8Url;
|
|
}
|
|
}
|
|
}, [m3u8Url]);
|
|
|
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
setLoading(true); // Set loading to true when submitting
|
|
|
|
try {
|
|
const response = await fetch(API_URL, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({
|
|
uri: rtspUrl,
|
|
alias: cameraName,
|
|
}),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error("Failed to fetch stream URL");
|
|
}
|
|
|
|
const data = await response.json();
|
|
console.log("Stream data:", data);
|
|
setM3u8Url(`http://localhost:8081${data?.uri}`);
|
|
} catch (error) {
|
|
console.error("Error fetching stream:", error);
|
|
alert("Failed to load stream.");
|
|
} finally {
|
|
setLoading(false); // Reset loading state after API response
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div style={{ maxWidth: "600px", margin: "auto", textAlign: "center" }}>
|
|
<h2>RTSP Stream</h2>
|
|
<form onSubmit={handleSubmit}>
|
|
<input
|
|
type="text"
|
|
value={rtspUrl}
|
|
onChange={(e) => setRtspUrl(e.target.value)}
|
|
placeholder="Enter RTSP URL"
|
|
style={{ width: "80%", padding: "8px", marginBottom: "10px" }}
|
|
required
|
|
/>
|
|
<br />
|
|
<input
|
|
type="text"
|
|
value={cameraName}
|
|
onChange={(e) => setCameraName(e.target.value)}
|
|
placeholder="Enter Camera Name"
|
|
style={{ width: "80%", padding: "8px", marginBottom: "10px" }}
|
|
required
|
|
/>
|
|
<br />
|
|
<button
|
|
type="submit"
|
|
style={{
|
|
padding: "8px 12px",
|
|
cursor: loading ? "not-allowed" : "pointer",
|
|
opacity: loading ? 0.6 : 1,
|
|
}}
|
|
disabled={loading}
|
|
>
|
|
{loading ? "Starting stream..." : "Start Stream"}
|
|
</button>
|
|
</form>
|
|
|
|
{loading && (
|
|
<p style={{ marginTop: "15px", fontWeight: "bold" }}>
|
|
Stream is starting...
|
|
</p>
|
|
)}
|
|
|
|
{m3u8Url && !loading && (
|
|
<video
|
|
ref={videoRef}
|
|
controls
|
|
autoPlay
|
|
style={{ width: "100%", marginTop: "20px" }}
|
|
/>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default RtspStream;
|