Dev scripts, Add support for rendering videos for browser sessions (#3507)

This commit is contained in:
Shuchang Zheng
2025-09-23 22:47:24 -04:00
committed by GitHub
parent 18127e588a
commit b1cc417086
2 changed files with 177 additions and 8 deletions

View File

@@ -6,10 +6,12 @@ import { getClient } from "@/api/AxiosClient";
import { BrowserStream } from "@/components/BrowserStream";
import { LogoMinimized } from "@/components/LogoMinimized";
import { useCredentialGetter } from "@/hooks/useCredentialGetter";
import { BrowserSessionVideo } from "./BrowserSessionVideo";
function BrowserSession() {
const { browserSessionId } = useParams();
const [hasBrowserSession, setHasBrowserSession] = useState(false);
const [activeTab, setActiveTab] = useState<"stream" | "videos">("stream");
const credentialGetter = useCredentialGetter();
@@ -19,12 +21,14 @@ function BrowserSession() {
const client = await getClient(credentialGetter, "sans-api-v1");
try {
await client.get(`/browser_sessions/${browserSessionId}`);
const response = await client.get(
`/browser_sessions/${browserSessionId}`,
);
setHasBrowserSession(true);
return true;
return response.data;
} catch (error) {
setHasBrowserSession(false);
return false;
return null;
}
},
});
@@ -60,12 +64,41 @@ function BrowserSession() {
<div className="text-xl">browser session</div>
</div>
</div>
{/* Tab Navigation */}
<div className="flex w-full border-b">
<button
className={`border-b-2 px-4 py-2 text-sm font-medium transition-colors ${
activeTab === "stream"
? "border-blue-500 text-blue-600"
: "border-transparent text-gray-500 hover:text-gray-700"
}`}
onClick={() => setActiveTab("stream")}
>
Live Stream
</button>
<button
className={`border-b-2 px-4 py-2 text-sm font-medium transition-colors ${
activeTab === "videos"
? "border-blue-500 text-blue-600"
: "border-transparent text-gray-500 hover:text-gray-700"
}`}
onClick={() => setActiveTab("videos")}
>
Recordings
</button>
</div>
{/* Tab Content */}
<div className="min-h-0 w-full flex-1 rounded-lg border p-4">
<BrowserStream
browserSessionId={browserSessionId}
interactive={false}
showControlButtons={true}
/>
{activeTab === "stream" && (
<BrowserStream
browserSessionId={browserSessionId}
interactive={false}
showControlButtons={true}
/>
)}
{activeTab === "videos" && <BrowserSessionVideo />}
</div>
</div>
</div>

View File

@@ -0,0 +1,136 @@
import { useQuery } from "@tanstack/react-query";
import { useParams } from "react-router-dom";
import { getClient } from "@/api/AxiosClient";
import { useCredentialGetter } from "@/hooks/useCredentialGetter";
interface Recording {
url: string;
checksum: string;
filename: string;
modified_at: string;
}
function BrowserSessionVideo() {
const { browserSessionId } = useParams();
const credentialGetter = useCredentialGetter();
const {
data: browserSession,
isLoading,
error,
} = useQuery({
queryKey: ["browserSession", browserSessionId],
queryFn: async () => {
const client = await getClient(credentialGetter, "sans-api-v1");
const response = await client.get(
`/browser_sessions/${browserSessionId}`,
);
return response.data;
},
enabled: !!browserSessionId,
});
// Extract recordings from browser session data
const recordings = browserSession?.recordings || [];
if (isLoading) {
return (
<div className="flex h-full w-full items-center justify-center">
<div className="text-lg">Loading videos...</div>
</div>
);
}
if (error) {
return (
<div className="flex h-full w-full items-center justify-center">
<div className="text-lg text-red-500">
Error loading videos: {error.message}
</div>
</div>
);
}
if (!recordings || recordings.length === 0) {
return (
<div className="flex h-full w-full items-center justify-center">
<div className="text-center">
<div className="mb-2 text-lg text-gray-500">
No recordings available
</div>
<div className="text-sm text-gray-400">
Video recordings will appear here when the browser session is active
and recording
</div>
</div>
</div>
);
}
return (
<div className="h-full w-full p-4">
<div className="mb-4">
<h2 className="text-xl font-semibold">Browser Session Videos</h2>
<p className="text-sm text-gray-500">
Recorded videos from this browser session
</p>
</div>
<div className="grid gap-4">
{recordings.map((recording: Recording, index: number) => (
<div
key={recording.checksum || index}
className="rounded-lg border p-4"
>
<div className="mb-2">
<h3 className="font-medium">
{recording.filename || `Recording ${index + 1}`}
{recording.modified_at && (
<span className="ml-2 text-sm text-gray-500">
({new Date(recording.modified_at).toLocaleString()})
</span>
)}
</h3>
</div>
{recording.url ? (
<div className="w-full">
<video
controls
className="w-full max-w-4xl rounded-lg"
src={recording.url}
preload="metadata"
>
Your browser does not support the video tag.
</video>
<div className="mt-2 text-xs text-gray-500">
<a
href={recording.url}
target="_blank"
rel="noopener noreferrer"
className="text-blue-600 hover:text-blue-800"
>
Download video
</a>
</div>
</div>
) : (
<div className="text-gray-500">
Video URL not available - video may still be processing
</div>
)}
{recording.checksum && (
<div className="mt-2 text-sm text-gray-600">
Checksum: {recording.checksum}
</div>
)}
</div>
))}
</div>
</div>
);
}
export { BrowserSessionVideo };