GitHub – unhaya/vam-seek: 2D Video Seek Marker
demo-trimmed.mp4
A lightweight 2D video seek grid library for video streaming sites.
Navigate videos visually with a thumbnail grid instead of a 1D seek bar. Client-side frame extraction with smooth marker animation.
🎯 Stop blind scrubbing. See every scene at once.
| Traditional Seek Bar | VAM Seek |
|---|---|
| 1D timeline, trial-and-error | 2D grid, instant visual navigation |
| Server-generated thumbnails | Client-side canvas extraction |
| Heavy infrastructure | Zero server load, ~15KB JS |
| Complex integration | One-line setup |
Keywords: video player, video seek, thumbnail grid, video navigation, HTML5 video, JavaScript library, video streaming, media player, video controls, video thumbnails, seek bar alternative, video UX
<!-- 1. Add the script -->
<script src="https://cdn.jsdelivr.net/gh/unhaya/vam-seek/dist/vam-seek.js"></script>
<!-- 2. Connect to your video -->
<script>
VAMSeek.init({
video: document.getElementById('myVideo'),
container: document.getElementById('seekGrid'),
columns: 5,
secondsPerCell: 15
});
</script>
That’s it. See docs/INTEGRATION.md for full documentation.
- Client-side frame extraction – No server CPU usage
- LRU cache – 200 frames cached in memory
- Smooth marker animation – 60fps with requestAnimationFrame
- VAM algorithm – Precise timestamp calculation
- Framework support – React, Vue, vanilla JS examples included
Your video never leaves the browser.
Traditional thumbnail systems upload videos to a server, process with FFmpeg, store thumbnails, and serve via CDN. This costs money, takes time, and raises privacy concerns.
VAM Seek works differently:
| Traditional | VAM Seek |
|---|---|
| Video uploaded to server | Video stays in browser |
| Server-side FFmpeg processing | Client-side Canvas API |
| Thumbnails stored on disk | Frames cached in memory |
| CDN bandwidth costs | Zero server cost |
| Privacy risk | Fully private |
All frame extraction happens in the user’s browser using the Canvas API. When the page closes, everything is gone. No data is ever sent to any server.
VAM_web/
├── dist/ # Distributable files
│ └── vam-seek.js # Standalone library (1 file, ~15KB)
│
├── docs/ # Documentation
│ └── INTEGRATION.md # API integration guide
│
├── examples/ # Integration examples
│ ├── basic-integration.html # Vanilla JS example
│ ├── react-integration.jsx # React component & hook
│ └── vue-integration.vue # Vue 3 component
│
├── backend/ # FastAPI backend (for demo)
│ ├── main.py # Entry point, static file serving
│ ├── requirements.txt # Python dependencies
│ ├── core/ # Core logic
│ │ ├── grid_calc.py # VAM grid calculation
│ │ └── video_utils.py # FFmpeg video processing
│ ├── models/ # Pydantic schemas
│ │ └── schemas.py # Request/response models
│ ├── routers/ # API routers
│ │ ├── grid.py # /api/grid/* endpoints
│ │ └── video.py # /api/video/* endpoints
│ ├── uploads/ # Uploaded videos (gitignore)
│ └── thumbnails/ # Generated thumbnails (gitignore)
│
├── frontend/ # Demo frontend
│ ├── index.html # Main UI with embedded JS
│ └── assets/
│ └── marker.svg # Grid marker icon
│
├── .gitignore
└── README.md
CDN (Recommended)
<script src="https://cdn.jsdelivr.net/gh/unhaya/vam-seek/dist/vam-seek.js"></script>
npm
const vam = VAMSeek.init({
video: document.getElementById('video'),
container: document.getElementById('grid'),
columns: 5,
secondsPerCell: 15,
onSeek: (time, cell) => {
console.log(`Seeked to ${time}s`);
}
});
// API
vam.seekTo(120); // Seek to 2:00
vam.moveToCell(2, 3); // Move to column 2, row 3
vam.configure({ columns: 8 }); // Update settings
vam.destroy(); // Clean up
| Option | Type | Default | Description |
|---|---|---|---|
video |
HTMLVideoElement | required | Target video element |
container |
HTMLElement | required | Container for the grid |
columns |
number | 3 | Grid columns (3-10) |
secondsPerCell |
number | 5 | Seconds per cell |
cacheSize |
number | 200 | LRU cache size |
onSeek |
function | null | Seek callback |
- Python 3.9+
- FFmpeg (in PATH)
cd backend
pip install -r requirements.txt
python main.py
Open http://localhost:8000
| Method | Path | Description |
|---|---|---|
| GET | / |
Serve frontend |
| GET | /api/health |
Health check |
| POST | /api/grid/config |
Calculate grid dimensions |
| POST | /api/grid/position |
Calculate timestamp from position |
| POST | /api/video/upload |
Upload video (demo only) |
// 1. Create hidden video element
const video = document.createElement('video');
video.src = "https://github.com/unhaya/video.mp4";
// 2. Seek to timestamp
video.currentTime = 15.0;
// 3. Capture on seeked event
video.addEventListener('seeked', () => {
const canvas = document.createElement('canvas');
ctx.drawImage(video, 0, 0);
const dataUrl = canvas.toDataURL('image/jpeg', 0.8);
});
// 4. Cache with LRU (max 200 frames)
frameCache.put(videoSrc, timestamp, dataUrl);
// X-continuous timestamp calculation
function calculateTimestamp(x, y, gridWidth, gridHeight, duration, secondsPerCell) {
const rowIndex = Math.floor(y / gridHeight * rows);
const colContinuous = x / gridWidth * columns;
const cellIndex = rowIndex * columns + colContinuous;
return Math.min(cellIndex * secondsPerCell, duration);
}
| Key | Action |
|---|---|
Arrow Keys |
Move marker by cell |
Space |
Play/Pause |
Home |
First cell |
End |
Last cell |
- Chrome 80+
- Firefox 75+
- Safari 14+
- Edge 80+
- Mobile browsers
Free for personal, educational, and research use.
Commercial use requires a paid license. Contact: info@haasiy.jp
- FastAPI backend with modular architecture
- Client-side frame extraction (video + canvas)
- VAM-compliant marker movement (X-continuous mode)
- LRU cache with fade-in animation
- Scroll position fix
- Same-origin serving for CORS
- Standalone
vam-seek.jsfor external integration - Integration documentation
- React, Vue examples
Based on VAM Desktop application algorithms:
vam5.70/utils/video_utils.py– calculate_x_continuous_timestampvam5.70/gui/preview/core/grid_calculator.py– GridCalculator
