多视频同步播放 Web 应用
Multi-video synchronized playback web application
SyncPlayer is a web application that loads and plays multiple local video files simultaneously with synchronized playback controls. Designed for motion analysis, video comparison, and multi-angle viewing scenarios.
SyncPlayer 是一个支持同时加载并同步播放多个本地视频文件的 Web 应用,适用于动作分析、视频对比、多角度回放等场景。
- Synchronized Playback — Play, pause, and seek across all videos simultaneously
- Frame-level Control — Step forward/backward by single frame (at 30 fps) for precise analysis
- Unified Progress Bar — Single slider with seek-on-commit mode controls all videos
- Local File API — All videos processed client-side via Blob URLs; nothing uploaded to servers
- Adaptive Grid Layout — CSS Grid auto-arranges videos maintaining original aspect ratios
- Append Mode — Add more videos dynamically with no hard limit on count
- Performance Warning — Alert shown when exceeding 9 videos
- Fault Tolerance — Individual video load failures show an error placeholder without blocking others
- Static Video Server — Built-in FastAPI backend serves test videos with Range Request support
- Node.js 18+ or Bun
- Python 3.11+
- uv (Python package manager)
- Modern browser (Chrome, Firefox, Edge, Safari)
# Clone the repository
git clone git@github.com:hy592070616/SyncPlayer.git
cd SyncPlayer
# Install frontend dependencies
npm install
# Install backend dependencies
cd backend
uv sync
cd ..
# Start both frontend and backend
npm run dev:full- Frontend: http://localhost:5173
- Backend API: http://localhost:8000
- API Docs: http://localhost:8000/docs
- Click Select Videos to choose local video files
- Use the Play/Pause button to control all videos synchronously
- Drag the progress bar to seek to any point in time
- Use frame step buttons (forward/backward) for frame-accurate navigation
- Click Add More Videos to append additional files
All videos play muted by default (required by browser autoplay policy). The progress bar reflects the longest video's duration; shorter videos freeze on their last frame when exceeded.
| Layer | Technology |
|---|---|
| Frontend | React 18 + TypeScript + Vite 5 |
| Backend | FastAPI (Python 3.11+) |
| Package (Frontend) | Bun / npm |
| Package (Backend) | uv |
SyncPlayer/
├── src/ # Frontend source
│ ├── components/ # React components
│ │ ├── MultiVideoPlayer.tsx # Main orchestrator
│ │ ├── FileSelector.tsx # File picker
│ │ ├── VideoGrid.tsx # Grid layout
│ │ ├── ControlPanel.tsx # Playback controls
│ │ ├── ProgressBar.tsx # Unified progress slider
│ │ └── __tests__/ # Component tests
│ ├── hooks/ # Custom React hooks
│ │ ├── useVideoSync.ts # Sync logic
│ │ ├── useVideoRefs.ts # DOM ref management
│ │ └── __tests__/ # Hook tests
│ ├── types/ # TypeScript definitions
│ ├── App.tsx # App entry
│ └── main.tsx # React root
├── backend/ # Python backend
│ ├── main.py # FastAPI app
│ ├── pyproject.toml # Python dependencies
│ └── static/videos/ # Sample videos
├── assets/ # Static assets (screenshots)
├── start.bat # Windows launcher
├── start.sh # Unix launcher
└── vite.config.ts # Vite configuration
| Endpoint | Method | Description |
|---|---|---|
/ |
GET | Service status |
/health |
GET | Health check |
/videos/{filename} |
GET | Serve video files (supports Range Requests) |
/docs |
GET | Swagger UI documentation |
Data flow: File selection → Blob URL creation → React state → useVideoSync hook → Video elements
Sync mechanism:
- Calls
play()/pause()on all<video>elements simultaneously - Seek is applied to every video's
currentTime; shorter videos freeze at their last frame - Frame stepping uses a fixed interval of 1/30 s
- Only the master video (first in the list) drives progress bar updates via
timeupdate
Contributions are welcome! Please open an issue or submit a pull request.
- Fork the repository
- Create a feature branch (
git checkout -b feature/awesome-feature) - Commit your changes (
git commit -m 'Add awesome feature') - Push to the branch (
git push origin feature/awesome-feature) - Open a Pull Request
Distributed under the GPL v2.1 License. See LICENSE for more information.
