2D Video Seek Marker for Video Streaming Sites
<script src="https://cdn.jsdelivr.net/gh/unhaya/vam-seek/dist/vam-seek.js"></script>
Connect to your existing <video> element:
VAMSeek.init({
video: document.getElementById('myVideo'),
container: document.getElementById('seekGrid')
});
<!DOCTYPE html>
<html>
<head>
<title>My Video Site</title>
<style>
.video-container {
display: flex;
gap: 20px;
}
.player {
flex: 1;
max-width: 640px;
}
.seek-grid {
flex: 1;
max-width: 500px;
max-height: 400px;
overflow-y: auto;
background: #1a1a2e;
border-radius: 8px;
padding: 10px;
}
video {
width: 100%;
border-radius: 8px;
}
</style>
</head>
<body>
<div class="video-container">
<!-- Your existing video player -->
<div class="player">
<video id="myVideo" controls>
<source src="https://your-cdn.com/video.mp4" type="video/mp4">
</video>
</div>
<!-- VAM Seek Grid Container -->
<div class="seek-grid" id="seekGrid"></div>
</div>
<!-- VAM Seek Library -->
<script src="https://cdn.jsdelivr.net/gh/unhaya/vam-seek/dist/vam-seek.js"></script>
<script>
const video = document.getElementById('myVideo');
const grid = document.getElementById('seekGrid');
// Initialize when video is ready
video.addEventListener('loadedmetadata', () => {
const vam = VAMSeek.init({
video: video,
container: grid,
columns: 5,
secondsPerCell: 15,
onSeek: (time, cell) => {
console.log(`Seeked to ${time}s (cell ${cell.index})`);
}
});
});
</script>
</body>
</html>
| Option | Type | Default | Description |
|---|---|---|---|
video |
HTMLVideoElement | required | Your video element |
container |
HTMLElement | required | Container for the grid |
columns |
number | 3 | Grid columns (3-10 recommended) |
secondsPerCell |
number | 5 | Seconds each cell represents |
thumbWidth |
number | 160 | Thumbnail width in pixels |
thumbHeight |
number | 90 | Thumbnail height in pixels |
cacheSize |
number | 200 | LRU cache size (frames per video) |
markerSvg |
string | null | Custom marker SVG HTML |
onSeek |
function | null | Callback when user seeks |
onError |
function | null | Callback on error |
autoScroll |
boolean | true | Enable auto-scroll during playback |
scrollBehavior |
string | ‘center’ | Scroll mode: ‘center’ or ‘edge’ |
Initialize the seek grid.
const instance = VAMSeek.init({
video: videoElement,
container: containerElement,
columns: 5,
secondsPerCell: 15
});
Update configuration dynamically.
instance.configure({
columns: 8,
secondsPerCell: 10
});
Programmatically seek to a time.
instance.seekTo(120); // Seek to 2:00
Move marker to specific cell.
instance.moveToCell(2, 3); // Column 2, Row 3
Get current cell information.
const cell = instance.getCurrentCell();
// Returns: { index, col, row, time, cellStartTime, cellEndTime }
Clean up and remove the grid.
instance.destroy();
Get instance for a video element.
const instance = VAMSeek.getInstance(videoElement);
| Key | Action |
|---|---|
Arrow Keys |
Move marker by one cell |
Home |
Jump to first cell |
End |
Jump to last cell |
Space |
Play/Pause |
Override default styles with CSS:
/* Grid container */
.vam-thumbnail-grid {
gap: 4px !important;
}
/* Individual cells */
.vam-cell {
border-radius: 4px !important;
background: #2a2a4e !important;
}
/* Time labels */
.vam-time {
font-family: 'Monaco', monospace !important;
font-size: 10px !important;
}
/* Marker */
.vam-marker svg circle {
stroke: #00ff00 !important;
}
The grid auto-scrolls to keep the marker visible during playback. You can customize this in scrollToMarker():
Center-following (default) - Marker stays at viewport center:
function scrollToMarker() {
const targetScroll = STATE.markerY - viewportHeight / 2;
container.scrollTo({ top: Math.max(0, targetScroll), behavior: 'smooth' });
}
Edge-trigger - Only scroll when marker reaches screen edge:
function scrollToMarker() {
const markerTop = STATE.markerY;
const scrollTop = container.scrollTop;
if (markerTop < scrollTop + 50) {
container.scrollTo({ top: Math.max(0, markerTop - 100), behavior: 'smooth' });
} else if (markerTop > scrollTop + viewportHeight - 50) {
container.scrollTo({ top: markerTop - viewportHeight + 100, behavior: 'smooth' });
}
}
Offset from center - Marker at top 1/3 of viewport:
const targetScroll = STATE.markerY - viewportHeight / 3;
Change how often auto-scroll triggers (default: 500ms):
// In timeupdate event listener
if (now - lastScrollTime > 500) { // Change this value (milliseconds)
scrollToMarker();
lastScrollTime = now;
}
Replace the default crosshair:
VAMSeek.init({
video: video,
container: grid,
markerSvg: `
<svg width="32" height="32" viewBox="0 0 32 32">
<polygon points="16,0 32,32 0,32" fill="#ff0000"/>
</svg>
`
});
| Video Duration | Recommended Columns | Seconds/Cell |
|---|---|---|
| < 5 minutes | 4-5 | 5-10 |
| 5-30 minutes | 5-6 | 15-30 |
| 30-60 minutes | 6-8 | 30-60 |
| > 60 minutes | 8-10 | 60-120 |
The library maintains an LRU cache of 200 frames by default. Increase for longer videos:
VAMSeek.init({
// ...
cacheSize: 500 // For videos > 2 hours
});
For very long videos, consider loading frames on scroll:
container.addEventListener('scroll', () => {
// Trigger frame extraction for visible cells
instance.rebuild();
});
| Browser | Support |
|---|---|
| Chrome 80+ | Full |
| Firefox 75+ | Full |
| Safari 14+ | Full |
| Edge 80+ | Full |
| Mobile Chrome | Full |
| Mobile Safari | Full |
The video source must allow canvas extraction:
Access-Control-Allow-Origin: *
Or serve the video and page from the same origin.
If you find VAM Seek useful, consider adding a link or mention:
Powered by <a href="https://github.com/unhaya/vam-seek">VAM Seek</a>
This helps others discover the library. Not required, but appreciated!
Free for personal, educational, and research use. Commercial use requires a paid license. Contact: info@haasiy.jp
GitHub Issues: https://github.com/unhaya/vam-seek/issues