YACWC
This commit is contained in:
@@ -1,9 +1,8 @@
|
||||
"use client";
|
||||
import React, { useState, useEffect, useRef, useCallback } from "react";
|
||||
import Login from "./components/Login";
|
||||
import EmbedTimeline from "./components/EmbedTimeline";
|
||||
import VideoPlayer from "./components/VideoPlayer";
|
||||
// import ModernDateRangeSelector from './components/ModernDateRangeSelector';
|
||||
import CompactDateRangePicker from "./components/CompactDateRangePicker";
|
||||
import CustomDateRangePicker from "./components/DateRangePicker";
|
||||
import "./App.css";
|
||||
import StatusesDisplayHUD from "./components/StatusDisplay";
|
||||
@@ -17,6 +16,9 @@ function App() {
|
||||
const playerRef = useRef(null);
|
||||
const [drawerOpen, setDrawerOpen] = useState(false);
|
||||
const playerInstanceRef = useRef(null);
|
||||
const [selectedCamera, setSelectedCamera] = useState("Leopards 1");
|
||||
const [selectedHighRes, setSelectedHighRes] = useState(false);
|
||||
const [videoPlaying, setVideoPlaying] = useState(true);
|
||||
// State for the values
|
||||
window.chartRef = chartRef;
|
||||
window.playerRef = playerRef;
|
||||
@@ -31,11 +33,11 @@ function App() {
|
||||
//
|
||||
|
||||
const [startRange, setStartRange] = useState(
|
||||
new Date(new Date().getTime() - 7 * 24 * 60 * 60 * 1000)
|
||||
new Date(new Date().getTime() - 1 * 24 * 60 * 60 * 1000),
|
||||
);
|
||||
const [endRange, setEndRange] = useState(new Date());
|
||||
// const [endRange, setEndRange] = useState(new Date(new Date().getTime() - 6 * 24 * 60 * 60 * 1000));
|
||||
const [queryText, setQueryText] = useState("A clouded leopard and a human");
|
||||
const [queryText, setQueryText] = useState("Two clouded leopards being aggressive");
|
||||
const [sliderValue, setSliderValue] = useState(0);
|
||||
|
||||
// State to track last submitted values
|
||||
@@ -43,6 +45,7 @@ function App() {
|
||||
startRange,
|
||||
endRange,
|
||||
queryText,
|
||||
selectedCamera,
|
||||
});
|
||||
|
||||
// // Check if any value has changed
|
||||
@@ -60,7 +63,10 @@ function App() {
|
||||
const endChanged =
|
||||
endRange.getTime() !== new Date(lastSubmitted.endRange).getTime();
|
||||
|
||||
const queryChanged = textChanged || startChanged || endChanged;
|
||||
const cameraChanged = selectedCamera !== lastSubmitted.selectedCamera;
|
||||
|
||||
const queryChanged =
|
||||
textChanged || startChanged || endChanged || cameraChanged;
|
||||
|
||||
const streamComputeStatus = () => {
|
||||
fetch("api/return_status")
|
||||
@@ -109,14 +115,17 @@ function App() {
|
||||
};
|
||||
// Function to resubmit fetch
|
||||
const handleResubmit = (doTestMode = false) => {
|
||||
setVideoPlaying(false);
|
||||
let startRangeUse;
|
||||
let endRangeUse;
|
||||
setDrawerOpen(false);
|
||||
|
||||
if (doTestMode == true) {
|
||||
startRangeUse = new Date(
|
||||
new Date().getTime() - 2 * 24 * 60 * 60 * 1000
|
||||
new Date().getTime() - 2 * 24 * 60 * 60 * 1000,
|
||||
);
|
||||
endRangeUse = new Date(
|
||||
new Date().getTime() - 1 * 24 * 60 * 60 * 1000
|
||||
new Date().getTime() - 1 * 24 * 60 * 60 * 1000,
|
||||
);
|
||||
} else {
|
||||
startRangeUse = startRange;
|
||||
@@ -131,9 +140,10 @@ function App() {
|
||||
params.append("endRange", endRangeUse.toISOString());
|
||||
params.append("threshold", 0.0);
|
||||
params.append("query", queryText);
|
||||
params.append("camera", selectedCamera);
|
||||
setDataResults({ videos: [], breaks: [] });
|
||||
|
||||
fetch("api/videos.json?" + params.toString())
|
||||
authenticatedFetch("api/videos.json?" + params.toString())
|
||||
.then((res) => res.json())
|
||||
.then((data) => {
|
||||
streamComputeStatus();
|
||||
@@ -145,18 +155,36 @@ function App() {
|
||||
startRange: startRangeUse,
|
||||
endRange: endRangeUse,
|
||||
queryText,
|
||||
selectedCamera,
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
function selectHighResFunc(selectedHighRes, setSelectedHighRes, toggleCheckbox = true) {
|
||||
console.log(selectedHighRes);
|
||||
const params = new URLSearchParams();
|
||||
params.append("do_high_res", !selectedHighRes);
|
||||
authenticatedFetch("api/set_parameter?" + params.toString())
|
||||
.then((res) => res.json())
|
||||
.then((data) => {
|
||||
if (toggleCheckbox) {
|
||||
setSelectedHighRes(!selectedHighRes);
|
||||
}
|
||||
});
|
||||
}
|
||||
function updateDataAndValue(newValue) {
|
||||
const floatValue = parseFloat(newValue);
|
||||
setSliderValue(floatValue);
|
||||
var newData = JSON.parse(JSON.stringify(original_data.current));
|
||||
newData["videos"] = newData["videos"].filter(
|
||||
(vid) => vid["embed_scores"]["score"][1] >= floatValue
|
||||
(vid) => vid["embed_scores"]["score"][1] >= floatValue,
|
||||
);
|
||||
newData['threshold'] = floatValue;
|
||||
console.log(newData['threshold'])
|
||||
newData["threshold"] = floatValue;
|
||||
setDataResults(newData);
|
||||
}
|
||||
|
||||
@@ -199,7 +227,7 @@ function App() {
|
||||
series: [{}, { markLine: { data: [vv_new] } }],
|
||||
},
|
||||
false,
|
||||
["series.markLine"]
|
||||
["series.markLine"],
|
||||
);
|
||||
} else {
|
||||
chart.setOption(
|
||||
@@ -207,67 +235,115 @@ function App() {
|
||||
series: [{}, { markLine: markLine }],
|
||||
},
|
||||
false,
|
||||
["series.markLine"]
|
||||
["series.markLine"],
|
||||
);
|
||||
}
|
||||
}
|
||||
const handleTimelineClick = useCallback(
|
||||
(path, timeoffset) => {
|
||||
const handleTimelineClick = useCallback((path, timeoffset) => {
|
||||
if (playerRef.current && playerInstanceRef.current) {
|
||||
const player = playerInstanceRef.current;
|
||||
|
||||
console.log("Setting video source:", "api/" + path);
|
||||
console.log("Target time offset:", timeoffset);
|
||||
|
||||
// Clear any existing source first
|
||||
const token = localStorage.getItem("access_token");
|
||||
|
||||
const authenticatedUrl = `api/${path}?token=${token}`;
|
||||
player.reset();
|
||||
|
||||
|
||||
// Configure player for range request support
|
||||
const videoElement = player.el().querySelector("video");
|
||||
if (videoElement) {
|
||||
videoElement.crossOrigin = "anonymous";
|
||||
videoElement.preload = "metadata";
|
||||
}
|
||||
|
||||
player.src({
|
||||
src: "api/" + path,
|
||||
src: authenticatedUrl,
|
||||
type: "video/mp4",
|
||||
withCredentials: false,
|
||||
});
|
||||
|
||||
setVideoPlaying(true);
|
||||
// Ensure range headers are sent for seeking
|
||||
player.ready(() => {
|
||||
const tech = player.tech();
|
||||
if (tech && tech.el_) {
|
||||
tech.el_.addEventListener("loadstart", () => {
|
||||
// Force range request capability
|
||||
if (
|
||||
tech.el_.seekable &&
|
||||
tech.el_.seekable.length === 0
|
||||
) {
|
||||
console.log(
|
||||
"Video doesn't support seeking - range headers may be needed",
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
player.load();
|
||||
|
||||
|
||||
// Add multiple event listeners for debugging
|
||||
player.one("loadedmetadata", () => {
|
||||
console.log("Video metadata loaded");
|
||||
console.log("Video duration:", player.duration());
|
||||
player.currentTime(timeoffset);
|
||||
|
||||
// Try to play after setting time
|
||||
const playPromise = player.play();
|
||||
if (playPromise !== undefined) {
|
||||
playPromise.then(() => {
|
||||
console.log("Video started playing");
|
||||
}).catch(error => {
|
||||
console.error("Error playing video:", error);
|
||||
});
|
||||
}
|
||||
console.log("Time offset:", timeoffset);
|
||||
});
|
||||
|
||||
|
||||
// Wait for the video to be ready for seeking
|
||||
player.one("canplay", () => {
|
||||
console.log("Video can start playing - setting time");
|
||||
|
||||
const duration = player.duration();
|
||||
|
||||
// Ensure timeoffset is valid
|
||||
const seekTime = Math.max(
|
||||
0,
|
||||
Math.min(timeoffset, duration - 0.1),
|
||||
);
|
||||
|
||||
console.log("Seeking to:", seekTime, "of", duration);
|
||||
player.currentTime(seekTime);
|
||||
|
||||
// Wait a bit before playing to ensure seek completed
|
||||
setTimeout(() => {
|
||||
const playPromise = player.play();
|
||||
if (playPromise !== undefined) {
|
||||
playPromise
|
||||
.then(() => {
|
||||
console.log(
|
||||
"Video started playing at time:",
|
||||
player.currentTime(),
|
||||
);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Error playing video:", error);
|
||||
});
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
|
||||
player.one("error", (e) => {
|
||||
console.error("Video error:", e);
|
||||
console.error("Player error:", player.error());
|
||||
});
|
||||
|
||||
|
||||
player.one("loadstart", () => {
|
||||
console.log("Load started");
|
||||
});
|
||||
|
||||
|
||||
player.one("canplay", () => {
|
||||
console.log("Video can start playing");
|
||||
});
|
||||
} else {
|
||||
console.error("Player ref not available");
|
||||
}
|
||||
},
|
||||
[]
|
||||
);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
|
||||
selectHighResFunc(
|
||||
selectedHighRes,
|
||||
setSelectedHighRes,
|
||||
false
|
||||
)
|
||||
handleResubmit(params.get("test_mode") === "true");
|
||||
}, []);
|
||||
|
||||
@@ -279,7 +355,7 @@ const handleTimelineClick = useCallback(
|
||||
|
||||
function pollForResult() {
|
||||
const poll = () => {
|
||||
fetch("api/videos_result.json")
|
||||
authenticatedFetch("api/videos_result.json")
|
||||
.then((res) => res.json())
|
||||
.then((data) => {
|
||||
if (data.status === "processing") {
|
||||
@@ -287,22 +363,254 @@ const handleTimelineClick = useCallback(
|
||||
} else {
|
||||
const max_value = Math.max(
|
||||
...data["videos"].map(
|
||||
(vid) => vid["embed_scores"]["score"][1]
|
||||
)
|
||||
(vid) => vid["embed_scores"]["score"][1],
|
||||
),
|
||||
);
|
||||
setSliderMax(max_value);
|
||||
|
||||
original_data.current = data;
|
||||
setDataResults(data);
|
||||
updateDataAndValue(sliderValue)
|
||||
}
|
||||
});
|
||||
};
|
||||
poll();
|
||||
}
|
||||
|
||||
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
||||
const [checkingAuth, setCheckingAuth] = useState(true);
|
||||
|
||||
// Check authentication on load
|
||||
useEffect(() => {
|
||||
const token = localStorage.getItem("access_token");
|
||||
if (token) {
|
||||
// Verify token is still valid
|
||||
fetch("/api/videos_result.json", {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.ok) {
|
||||
setIsAuthenticated(true);
|
||||
} else {
|
||||
localStorage.removeItem("access_token");
|
||||
setIsAuthenticated(false);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
localStorage.removeItem("access_token");
|
||||
setIsAuthenticated(false);
|
||||
})
|
||||
.finally(() => {
|
||||
setCheckingAuth(false);
|
||||
});
|
||||
} else {
|
||||
setCheckingAuth(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Add authentication header to all API calls
|
||||
const authenticatedFetch = useCallback((url, options = {}) => {
|
||||
const token = localStorage.getItem("access_token");
|
||||
const headers = {
|
||||
...options.headers,
|
||||
Authorization: `Bearer ${token}`,
|
||||
};
|
||||
return fetch(url, { ...options, headers });
|
||||
}, []);
|
||||
|
||||
const handleLoginSuccess = () => {
|
||||
setIsAuthenticated(true);
|
||||
handleResubmit(params.get("test_mode") === "true");
|
||||
};
|
||||
|
||||
const handleLogout = () => {
|
||||
localStorage.removeItem("access_token");
|
||||
setIsAuthenticated(false);
|
||||
};
|
||||
|
||||
if (checkingAuth) {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
height: "100vh",
|
||||
background: "#181a20",
|
||||
color: "#fff",
|
||||
}}
|
||||
>
|
||||
Loading...
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!isAuthenticated) {
|
||||
return <Login onLoginSuccess={handleLoginSuccess} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="app-container">
|
||||
{/* {drawerOpen && (
|
||||
<div
|
||||
className="drawer-backdrop"
|
||||
onClick={() => setDrawerOpen(false)}
|
||||
style={{
|
||||
position: "fixed",
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: "100vw",
|
||||
height: "100vh",
|
||||
background: "rgba(0, 0, 0, 0.5)",
|
||||
zIndex: 1400,
|
||||
opacity: drawerOpen ? 1 : 0,
|
||||
transition: "opacity 0.3s ease",
|
||||
}}
|
||||
/>
|
||||
)} */}
|
||||
|
||||
{drawerOpen && (
|
||||
<div
|
||||
className={`controls-section ${
|
||||
drawerOpen ? "drawer-open" : ""
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
className="control-group"
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "20px",
|
||||
justifyContent: "flex-start",
|
||||
}}
|
||||
>
|
||||
<button
|
||||
onClick={handleLogout}
|
||||
style={{
|
||||
flexShrink: 0,
|
||||
width: "auto",
|
||||
minWidth: "80px",
|
||||
marginLeft: "20px",
|
||||
}}
|
||||
>
|
||||
Logout
|
||||
</button>
|
||||
<div
|
||||
className="radio-group"
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "8px",
|
||||
flexShrink: 0,
|
||||
width: "auto",
|
||||
border: "2px solid #4a5568",
|
||||
borderRadius: "8px",
|
||||
padding: "12px",
|
||||
backgroundColor: "#2d3748",
|
||||
boxShadow: "0 2px 4px rgba(0,0,0,0.3)",
|
||||
}}
|
||||
>
|
||||
<label
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "6px",
|
||||
color: "white",
|
||||
justifyContent: "flex-start",
|
||||
cursor: "pointer",
|
||||
}}
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
name="camera_choice"
|
||||
value="Leopards 1"
|
||||
checked={selectedCamera === "Leopards 1"}
|
||||
onChange={(e) =>
|
||||
setSelectedCamera(e.target.value)
|
||||
}
|
||||
/>
|
||||
Leopards 1
|
||||
</label>
|
||||
<label
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "6px",
|
||||
color: "white",
|
||||
justifyContent: "flex-start",
|
||||
cursor: "pointer",
|
||||
}}
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
name="camera_choice"
|
||||
value="Leopards 2"
|
||||
checked={selectedCamera === "Leopards 2"}
|
||||
onChange={(e) =>
|
||||
setSelectedCamera(e.target.value)
|
||||
}
|
||||
/>
|
||||
Leopards 2
|
||||
</label>
|
||||
<label
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "6px",
|
||||
color: "white",
|
||||
justifyContent: "flex-start",
|
||||
cursor: "pointer",
|
||||
}}
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
name="camera_choice"
|
||||
value="Leopards 3"
|
||||
checked={selectedCamera === "Leopards 3"}
|
||||
onChange={(e) =>
|
||||
setSelectedCamera(e.target.value)
|
||||
}
|
||||
/>
|
||||
Leopards 3
|
||||
</label>
|
||||
<label
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "6px",
|
||||
color: "white",
|
||||
justifyContent: "flex-start",
|
||||
cursor: "pointer",
|
||||
}}
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
name="camera_choice"
|
||||
value="Leopards 4"
|
||||
checked={selectedCamera === "Leopards 4"}
|
||||
onChange={(e) =>
|
||||
setSelectedCamera(e.target.value)
|
||||
}
|
||||
/>
|
||||
Leopards 4
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="controls-section">
|
||||
<div className="control-group">
|
||||
<button
|
||||
className="drawer-toggle"
|
||||
onClick={() => setDrawerOpen(!drawerOpen)}
|
||||
>
|
||||
{drawerOpen ? "✕" : "Options"}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="control-group">
|
||||
<CustomDateRangePicker
|
||||
startDate={startRange}
|
||||
@@ -321,21 +629,36 @@ const handleTimelineClick = useCallback(
|
||||
if (e.key === "Enter") handleResubmit();
|
||||
}}
|
||||
style={{
|
||||
// padding: "8px",
|
||||
// borderRadius: "4px",
|
||||
// border: "1px solid #343a40",
|
||||
// color: "#fff",
|
||||
// backgroundColor: "#23272f",
|
||||
// width: "100%",
|
||||
// minWidth: 0,
|
||||
// boxSizing: "border-box",
|
||||
// padding: "8px",
|
||||
// borderRadius: "4px",
|
||||
// border: "1px solid #343a40",
|
||||
// color: "#fff",
|
||||
// backgroundColor: "#23272f",
|
||||
// width: "100%",
|
||||
// minWidth: 0,
|
||||
// boxSizing: "border-box",
|
||||
fontSize: "1.1em",
|
||||
// transition: "width 0.2s",
|
||||
// transition: "width 0.2s",
|
||||
}}
|
||||
ref={inputRef}
|
||||
size={Math.max(queryText.length, 25)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="control-group">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="do_high_res"
|
||||
checked={selectedHighRes}
|
||||
onChange={(e) =>
|
||||
selectHighResFunc(
|
||||
selectedHighRes,
|
||||
setSelectedHighRes,
|
||||
)
|
||||
}
|
||||
/>
|
||||
HD Video
|
||||
</div>
|
||||
<div
|
||||
className="control-group"
|
||||
style={{ visibility: queryChanged ? "hidden" : "visible" }}
|
||||
@@ -371,7 +694,9 @@ const handleTimelineClick = useCallback(
|
||||
className="control-group"
|
||||
style={{ visibility: queryChanged ? "hidden" : "visible" }}
|
||||
>
|
||||
<span style={{ color: "#fff" }}>{sliderValue.toFixed(2)}</span>
|
||||
<span style={{ color: "#fff" }}>
|
||||
{sliderValue.toFixed(2)}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className="control-group"
|
||||
@@ -389,6 +714,16 @@ const handleTimelineClick = useCallback(
|
||||
Resubmit
|
||||
</button>
|
||||
</div>
|
||||
{videoPlaying && (
|
||||
<div className="control-group">
|
||||
<button onClick={() => window.open("api/media_download/low")}>Download Low-Res</button>
|
||||
</div>
|
||||
)}
|
||||
{videoPlaying && (
|
||||
<div className="control-group">
|
||||
<button onClick={() => window.open("api/media_download/high")}>Download High-Res</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="status-section">
|
||||
@@ -400,14 +735,16 @@ const handleTimelineClick = useCallback(
|
||||
chartRef={chartRef}
|
||||
data_in={dataResults}
|
||||
onTimelineClick={handleTimelineClick}
|
||||
authenticatedFetch={authenticatedFetch}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="video-section vjs-16-9 vjs-fluid">
|
||||
<VideoPlayer
|
||||
videoRef={playerRef}
|
||||
playerInstanceRef={playerInstanceRef}
|
||||
setMarkerTimeFunc={setMarkerValueNonReactive}
|
||||
authenticatedFetch={authenticatedFetch}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user