rtsp streaming implemented
This commit is contained in:
		
							parent
							
								
									75c8c99481
								
							
						
					
					
						commit
						139cca44f5
					
				@ -3,10 +3,10 @@ import React, { useState } from "react";
 | 
				
			|||||||
import Register from "./register/Register";
 | 
					import Register from "./register/Register";
 | 
				
			||||||
import Search from "./search/Search";
 | 
					import Search from "./search/Search";
 | 
				
			||||||
import "./MainForm.css";
 | 
					import "./MainForm.css";
 | 
				
			||||||
import RealtimeFaceDetection from "./realtimeFaceDetection/RealtimeFaceDetection";
 | 
					 | 
				
			||||||
import FaceLiveness from "./faceLivelinessCheck/FaceLivelinessCheck";
 | 
					import FaceLiveness from "./faceLivelinessCheck/FaceLivelinessCheck";
 | 
				
			||||||
import FaceMovementDetection from "./faceMovementDetection/FaceMovementDetection";
 | 
					import FaceMovementDetection from "./faceMovementDetection/FaceMovementDetection";
 | 
				
			||||||
import RealtimeCount from "./realtimeCount/RealtimeCount";
 | 
					import RealtimeCount from "./realtimeCount/RealtimeCount";
 | 
				
			||||||
 | 
					import RealtimeDetection from "./realtimeDetection/RealtimeDetection";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const MainForm: React.FC = () => {
 | 
					const MainForm: React.FC = () => {
 | 
				
			||||||
  const [activeTab, setActiveTab] = useState<
 | 
					  const [activeTab, setActiveTab] = useState<
 | 
				
			||||||
@ -65,7 +65,7 @@ const MainForm: React.FC = () => {
 | 
				
			|||||||
      <div className="tab-content">
 | 
					      <div className="tab-content">
 | 
				
			||||||
        {activeTab === "register" && <Register />}
 | 
					        {activeTab === "register" && <Register />}
 | 
				
			||||||
        {activeTab === "search" && <Search />}
 | 
					        {activeTab === "search" && <Search />}
 | 
				
			||||||
        {activeTab === "realtime" && <RealtimeFaceDetection />}
 | 
					        {activeTab === "realtime" && <RealtimeDetection />}
 | 
				
			||||||
        {activeTab === "liveliness" && <FaceLiveness />}
 | 
					        {activeTab === "liveliness" && <FaceLiveness />}
 | 
				
			||||||
        {activeTab === "realtime-count" && <RealtimeCount />}
 | 
					        {activeTab === "realtime-count" && <RealtimeCount />}
 | 
				
			||||||
        {activeTab === "facemovement" && <FaceMovementDetection />}
 | 
					        {activeTab === "facemovement" && <FaceMovementDetection />}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										33
									
								
								components/realtimeDetection/RealtimeDetection.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								components/realtimeDetection/RealtimeDetection.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,33 @@
 | 
				
			|||||||
 | 
					"use client";
 | 
				
			||||||
 | 
					import React, { useState } from "react";
 | 
				
			||||||
 | 
					import WebcamDetection from "./webcam/Webcam";
 | 
				
			||||||
 | 
					import RtspStream from "./rtspStream/RtspStream";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const RealtimeDetection: React.FC = () => {
 | 
				
			||||||
 | 
					  const [activeTab, setActiveTab] = useState<"webcam" | "rtsp">("webcam");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <div className="main-container">
 | 
				
			||||||
 | 
					      <div className="tabs">
 | 
				
			||||||
 | 
					        <button
 | 
				
			||||||
 | 
					          className={`tab-button ${activeTab === "webcam" ? "active" : ""}`}
 | 
				
			||||||
 | 
					          onClick={() => setActiveTab("webcam")}
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					          Webcam
 | 
				
			||||||
 | 
					        </button>
 | 
				
			||||||
 | 
					        <button
 | 
				
			||||||
 | 
					          className={`tab-button ${activeTab === "rtsp" ? "active" : ""}`}
 | 
				
			||||||
 | 
					          onClick={() => setActiveTab("rtsp")}
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					          RTSP
 | 
				
			||||||
 | 
					        </button>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					      <div className="tab-content">
 | 
				
			||||||
 | 
					        {activeTab === "webcam" && <WebcamDetection />}
 | 
				
			||||||
 | 
					        {activeTab === "rtsp" && <RtspStream />}
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default RealtimeDetection;
 | 
				
			||||||
							
								
								
									
										109
									
								
								components/realtimeDetection/rtspStream/RtspStream.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								components/realtimeDetection/rtspStream/RtspStream.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,109 @@
 | 
				
			|||||||
 | 
					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;
 | 
				
			||||||
							
								
								
									
										167
									
								
								components/realtimeDetection/webcam/Webcam.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										167
									
								
								components/realtimeDetection/webcam/Webcam.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,167 @@
 | 
				
			|||||||
 | 
					"use client";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { useEffect, useRef, useState } from "react";
 | 
				
			||||||
 | 
					import Webcam from "react-webcam";
 | 
				
			||||||
 | 
					import * as faceapi from "face-api.js";
 | 
				
			||||||
 | 
					import { Button } from "@/components/ui/button";
 | 
				
			||||||
 | 
					import { Camera } from "lucide-react";
 | 
				
			||||||
 | 
					import { useToast } from "@/hooks/use-toast";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const MODEL_URL = "https://cdn.jsdelivr.net/npm/@vladmandic/face-api/model";
 | 
				
			||||||
 | 
					const PADDING = 60;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const WebcamDetection = () => {
 | 
				
			||||||
 | 
					  const webcamRef = useRef<Webcam>(null);
 | 
				
			||||||
 | 
					  const canvasRef = useRef<HTMLCanvasElement>(null);
 | 
				
			||||||
 | 
					  const [isModelLoaded, setIsModelLoaded] = useState(false);
 | 
				
			||||||
 | 
					  const [isDetecting, setIsDetecting] = useState(false);
 | 
				
			||||||
 | 
					  const { toast } = useToast();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    const loadModels = async () => {
 | 
				
			||||||
 | 
					      try {
 | 
				
			||||||
 | 
					        await faceapi.nets.tinyFaceDetector.loadFromUri(MODEL_URL);
 | 
				
			||||||
 | 
					        await faceapi.nets.faceLandmark68Net.loadFromUri(MODEL_URL);
 | 
				
			||||||
 | 
					        await faceapi.nets.faceRecognitionNet.loadFromUri(MODEL_URL);
 | 
				
			||||||
 | 
					        setIsModelLoaded(true);
 | 
				
			||||||
 | 
					      } catch (error) {
 | 
				
			||||||
 | 
					        console.error("Error loading models:", error);
 | 
				
			||||||
 | 
					        toast({
 | 
				
			||||||
 | 
					          title: "Error",
 | 
				
			||||||
 | 
					          description: "Failed to load face detection models.",
 | 
				
			||||||
 | 
					          variant: "destructive",
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    loadModels();
 | 
				
			||||||
 | 
					  }, [toast]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const extractFaceWithPadding = (
 | 
				
			||||||
 | 
					    video: HTMLVideoElement,
 | 
				
			||||||
 | 
					    box: faceapi.Box
 | 
				
			||||||
 | 
					  ): HTMLCanvasElement => {
 | 
				
			||||||
 | 
					    const canvas = document.createElement("canvas");
 | 
				
			||||||
 | 
					    const context = canvas.getContext("2d");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Calculate padded dimensions
 | 
				
			||||||
 | 
					    const x = Math.max(0, box.x - PADDING);
 | 
				
			||||||
 | 
					    const y = Math.max(0, box.y - PADDING);
 | 
				
			||||||
 | 
					    const width = Math.min(video.videoWidth - x, box.width + 2 * PADDING);
 | 
				
			||||||
 | 
					    const height = Math.min(video.videoHeight - y, box.height + 2 * PADDING);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    canvas.width = width;
 | 
				
			||||||
 | 
					    canvas.height = height;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (context) {
 | 
				
			||||||
 | 
					      context.drawImage(video, x, y, width, height, 0, 0, width, height);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return canvas;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const detectFace = async () => {
 | 
				
			||||||
 | 
					    if (!webcamRef.current?.video || !canvasRef.current) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const video = webcamRef.current.video;
 | 
				
			||||||
 | 
					    const canvas = canvasRef.current;
 | 
				
			||||||
 | 
					    const context = canvas.getContext("2d");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!context) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    canvas.width = video.videoWidth;
 | 
				
			||||||
 | 
					    canvas.height = video.videoHeight;
 | 
				
			||||||
 | 
					    context.clearRect(0, 0, canvas.width, canvas.height);
 | 
				
			||||||
 | 
					    context.translate(canvas.width, 0);
 | 
				
			||||||
 | 
					    context.scale(-1, 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const detections = await faceapi
 | 
				
			||||||
 | 
					      .detectAllFaces(video, new faceapi.TinyFaceDetectorOptions())
 | 
				
			||||||
 | 
					      .withFaceLandmarks()
 | 
				
			||||||
 | 
					      .withFaceDescriptors();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (detections.length > 0) {
 | 
				
			||||||
 | 
					      const highConfidenceDetections = detections.filter(
 | 
				
			||||||
 | 
					        (detection) => detection.detection.score > 0.5
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      for (const detection of highConfidenceDetections) {
 | 
				
			||||||
 | 
					        const { box } = detection.detection;
 | 
				
			||||||
 | 
					        context.strokeStyle = "#00FF00";
 | 
				
			||||||
 | 
					        context.lineWidth = 2;
 | 
				
			||||||
 | 
					        context.strokeRect(box.x, box.y, box.width, box.height);
 | 
				
			||||||
 | 
					        context.save();
 | 
				
			||||||
 | 
					        context.scale(-1, 1);
 | 
				
			||||||
 | 
					        context.fillStyle = "#00FF00";
 | 
				
			||||||
 | 
					        context.font = "16px Arial";
 | 
				
			||||||
 | 
					        context.fillText(
 | 
				
			||||||
 | 
					          `Confidence: ${Math.round(detection.detection.score * 100)}%`,
 | 
				
			||||||
 | 
					          -box.x - box.width,
 | 
				
			||||||
 | 
					          box.y - 5
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        context.restore();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const faceCanvas = extractFaceWithPadding(video, box);
 | 
				
			||||||
 | 
					        faceCanvas.toBlob(
 | 
				
			||||||
 | 
					          (blob) => {
 | 
				
			||||||
 | 
					            if (blob) sendFaceDataToAPI(blob);
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          "image/jpeg",
 | 
				
			||||||
 | 
					          0.95
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const sendFaceDataToAPI = async (imageBlob: Blob) => {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const formData = new FormData();
 | 
				
			||||||
 | 
					      formData.append("image", imageBlob, "face.jpg");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const response = await fetch(
 | 
				
			||||||
 | 
					        `${process.env.NEXT_PUBLIC_BASE_URL}/search`,
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          method: "POST",
 | 
				
			||||||
 | 
					          body: formData,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const data = await response.json();
 | 
				
			||||||
 | 
					      toast({ title: data?.name, description: data.message });
 | 
				
			||||||
 | 
					    } catch (error) {
 | 
				
			||||||
 | 
					      console.error("Error sending face data:", error);
 | 
				
			||||||
 | 
					      toast({
 | 
				
			||||||
 | 
					        title: "Error",
 | 
				
			||||||
 | 
					        description: "Failed to send face data.",
 | 
				
			||||||
 | 
					        variant: "destructive",
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const startDetection = () => {
 | 
				
			||||||
 | 
					    if (!isModelLoaded) return;
 | 
				
			||||||
 | 
					    setIsDetecting(true);
 | 
				
			||||||
 | 
					    setInterval(detectFace, 1000);
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <div className="max-w-3xl mx-auto">
 | 
				
			||||||
 | 
					      <div className="relative">
 | 
				
			||||||
 | 
					        <Webcam ref={webcamRef} mirrored className="w-full rounded-lg" />
 | 
				
			||||||
 | 
					        <canvas
 | 
				
			||||||
 | 
					          ref={canvasRef}
 | 
				
			||||||
 | 
					          className="absolute top-0 left-0 w-full h-full"
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					      <div className="mt-6 flex justify-center">
 | 
				
			||||||
 | 
					        <Button
 | 
				
			||||||
 | 
					          onClick={startDetection}
 | 
				
			||||||
 | 
					          disabled={!isModelLoaded || isDetecting}
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					          <Camera className="mr-2 h-4 w-4" />
 | 
				
			||||||
 | 
					          {isDetecting ? "Detecting..." : "Start Realtime Detection"}
 | 
				
			||||||
 | 
					        </Button>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default WebcamDetection;
 | 
				
			||||||
							
								
								
									
										7
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										7
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							@ -14,6 +14,7 @@
 | 
				
			|||||||
        "clsx": "^2.1.1",
 | 
					        "clsx": "^2.1.1",
 | 
				
			||||||
        "encoding": "^0.1.13",
 | 
					        "encoding": "^0.1.13",
 | 
				
			||||||
        "face-api.js": "^0.22.2",
 | 
					        "face-api.js": "^0.22.2",
 | 
				
			||||||
 | 
					        "hls.js": "^1.0.3-0.canary.7275",
 | 
				
			||||||
        "lucide-react": "^0.474.0",
 | 
					        "lucide-react": "^0.474.0",
 | 
				
			||||||
        "next": "15.1.6",
 | 
					        "next": "15.1.6",
 | 
				
			||||||
        "react": "^18.3.1",
 | 
					        "react": "^18.3.1",
 | 
				
			||||||
@ -3883,6 +3884,12 @@
 | 
				
			|||||||
        "node": ">= 0.4"
 | 
					        "node": ">= 0.4"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/hls.js": {
 | 
				
			||||||
 | 
					      "version": "1.0.3-0.canary.7275",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/hls.js/-/hls.js-1.0.3-0.canary.7275.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-l8y7S4Hq042OpcH91BX2DgGzIslSv8dYF+BQd7Ood+wdJo6qvo0+6bHRc/c+wubSXBbd8KdJDJ0k428zmDOTIQ==",
 | 
				
			||||||
 | 
					      "license": "Apache-2.0"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "node_modules/iconv-lite": {
 | 
					    "node_modules/iconv-lite": {
 | 
				
			||||||
      "version": "0.6.3",
 | 
					      "version": "0.6.3",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
 | 
				
			||||||
 | 
				
			|||||||
@ -15,6 +15,7 @@
 | 
				
			|||||||
    "clsx": "^2.1.1",
 | 
					    "clsx": "^2.1.1",
 | 
				
			||||||
    "encoding": "^0.1.13",
 | 
					    "encoding": "^0.1.13",
 | 
				
			||||||
    "face-api.js": "^0.22.2",
 | 
					    "face-api.js": "^0.22.2",
 | 
				
			||||||
 | 
					    "hls.js": "^1.0.3-0.canary.7275",
 | 
				
			||||||
    "lucide-react": "^0.474.0",
 | 
					    "lucide-react": "^0.474.0",
 | 
				
			||||||
    "next": "15.1.6",
 | 
					    "next": "15.1.6",
 | 
				
			||||||
    "react": "^18.3.1",
 | 
					    "react": "^18.3.1",
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user