From 02adbb4c10f91caea709eb821b1391c7235de579 Mon Sep 17 00:00:00 2001 From: "santasri.pachhal" Date: Thu, 2 Jul 2026 14:23:00 +0530 Subject: [PATCH] feat: introduce custom video player for video analysis --- package-lock.json | 28 +++++++- package.json | 1 + src/app/globals.css | 9 +++ src/app/layout.tsx | 2 + src/components/video/annotatedVideoPlayer.tsx | 44 ++++++++++--- src/components/video/useDetectionPlayback.ts | 3 +- .../video/useDetectionThumbnails.ts | 66 +++++++++++++++++++ src/components/videoPlayerSection.tsx | 19 +++--- 8 files changed, 151 insertions(+), 21 deletions(-) create mode 100644 src/components/video/useDetectionThumbnails.ts diff --git a/package-lock.json b/package-lock.json index 122dc86..48011ee 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,7 @@ "@tanstack/react-query-devtools": "^5.101.0", "@tanstack/react-table": "^8.21.3", "@tanstack/react-virtual": "^3.14.3", + "@vidstack/react": "^1.15.6", "axios": "^1.13.6", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", @@ -3394,7 +3395,6 @@ "version": "19.2.14", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", - "devOptional": true, "license": "MIT", "dependencies": { "csstype": "^3.2.2" @@ -3961,6 +3961,23 @@ "win32" ] }, + "node_modules/@vidstack/react": { + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/@vidstack/react/-/react-1.15.6.tgz", + "integrity": "sha512-hO7cLld5yfxCdDnDCYqh5aS7JxmHfsmX43PmKBJWzDTwK40P3hh5qAdDQzl3qDtEJLYjuo3Sv+kr6tt/VRtJzQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.6.10", + "media-captions": "^1.0.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/react": "^18.0.0 || ^19.0.0", + "react": "^18.0.0 || ^19.0.0" + } + }, "node_modules/acorn": { "version": "8.16.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", @@ -7180,6 +7197,15 @@ "node": ">= 0.4" } }, + "node_modules/media-captions": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/media-captions/-/media-captions-1.0.4.tgz", + "integrity": "sha512-cyDNmuZvvO4H27rcBq2Eudxo9IZRDCOX/I7VEyqbxsEiD2Ei7UYUhG/Sc5fvMZjmathgz3fEK7iAKqvpY+Ux1w==", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", diff --git a/package.json b/package.json index 3e3f04d..80c37cb 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "@tanstack/react-query-devtools": "^5.101.0", "@tanstack/react-table": "^8.21.3", "@tanstack/react-virtual": "^3.14.3", + "@vidstack/react": "^1.15.6", "axios": "^1.13.6", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", diff --git a/src/app/globals.css b/src/app/globals.css index de68622..640e2ed 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -144,3 +144,12 @@ scrollbar-gutter: stable; } } + +.detection-video-player .vds-slider-preview { + display: none; +} + +.detection-video-player + .vds-slider-preview:has(.vds-slider-thumbnail img[src^='blob:']) { + display: flex; +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 2596f1f..06b8dc7 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,5 +1,7 @@ import type { Metadata } from 'next'; import { Geist_Mono, Poppins } from 'next/font/google'; +import '@vidstack/react/player/styles/default/theme.css'; +import '@vidstack/react/player/styles/default/layouts/video.css'; import './globals.css'; import { ThemeProvider } from '@/providers/ThemeProvider'; import AppInitializer from '@/providers/AppInitializer'; diff --git a/src/components/video/annotatedVideoPlayer.tsx b/src/components/video/annotatedVideoPlayer.tsx index 027d04f..386e5c4 100644 --- a/src/components/video/annotatedVideoPlayer.tsx +++ b/src/components/video/annotatedVideoPlayer.tsx @@ -2,9 +2,22 @@ import { RefObject } from 'react'; import { AlertTriangle, Loader2 } from 'lucide-react'; +import type { DetectionResultLog } from '@/types'; +import { useDetectionThumbnails } from './useDetectionThumbnails'; +import { + MediaPlayer, + MediaProvider, + Poster, + type MediaPlayerInstance, +} from '@vidstack/react'; +import { + DefaultVideoLayout, + defaultLayoutIcons, +} from '@vidstack/react/player/layouts/default'; interface AnnotatedVideoPlayerProps { - videoRef: RefObject; + videoRef: RefObject; + logs: DetectionResultLog[]; videoUrl: string | null; isLoading: boolean; isError: boolean; @@ -15,6 +28,7 @@ interface AnnotatedVideoPlayerProps { export default function AnnotatedVideoPlayer({ videoRef, + logs, videoUrl, isLoading, isError, @@ -22,18 +36,30 @@ export default function AnnotatedVideoPlayer({ onTimeUpdate, onSeeked, }: AnnotatedVideoPlayerProps) { + const thumbnails = useDetectionThumbnails(logs); + return (
{videoUrl ? ( -