CourseHub: AJAX video course player (PHP + MySQL + Bootstrap + Plyr)
Scans nested course folders for mp4s, tracks per-user resume position and per-folder progress. SPA frontend with hash routing over a JSON API; range-request streaming via stream.php. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
# CourseHub
|
||||
|
||||
Self-hosted course video player: PHP 8 + MySQL/MariaDB + Bootstrap 5 + [Plyr](https://github.com/sampotts/plyr).
|
||||
|
||||
Single-page app: `index.php` is a static shell; all navigation, login, browsing,
|
||||
playback, and scanning happen over AJAX against the JSON API in `api/`
|
||||
(`auth.php`, `library.php`, `progress.php`, `scan.php`), with hash-based routes
|
||||
(`#/`, `#/folder/N`, `#/watch/N`) so views switch without page reloads and
|
||||
browser back/forward still work. Only `stream.php` (the mp4 bytes) is a plain
|
||||
HTTP resource.
|
||||
|
||||
- Recursively scans a videos directory for `.mp4` files and mirrors the folder
|
||||
hierarchy (courses → sections → videos, any nesting depth).
|
||||
- Per-user **resume**: log in from any device and continue where you left off.
|
||||
- **Progress tracking at every level** — each folder shows completed/total and a
|
||||
percentage bar computed over its whole subtree.
|
||||
- "Continue watching" strip, auto-play next video, playback speed, keyboard
|
||||
shortcuts (Plyr), completed checkmarks, natural sorting (`Video2` before `Video10`).
|
||||
- Seekable streaming via HTTP Range requests; files are addressed by DB id only
|
||||
(no path traversal).
|
||||
|
||||
## Layout on disk
|
||||
|
||||
```
|
||||
videos/
|
||||
├── Course 1/
|
||||
│ ├── Intro/
|
||||
│ │ ├── Video1.mp4
|
||||
│ │ └── Video2.mp4
|
||||
│ └── Module 2/
|
||||
│ └── Advanced/
|
||||
│ └── lesson.mp4
|
||||
└── Course 2/
|
||||
└── ...
|
||||
```
|
||||
|
||||
Top-level folders become courses on the home page.
|
||||
|
||||
## Setup
|
||||
|
||||
1. **Database** — create the schema and an app user:
|
||||
|
||||
```bash
|
||||
sudo mysql < schema.sql
|
||||
sudo mysql -e "CREATE USER IF NOT EXISTS 'coursehub'@'localhost' IDENTIFIED BY 'coursehub';
|
||||
GRANT ALL ON coursehub.* TO 'coursehub'@'localhost';"
|
||||
```
|
||||
|
||||
2. **Configure** — edit `config.php`:
|
||||
- `VIDEOS_ROOT`: absolute path to your course folders (must be readable by
|
||||
the web server / PHP user).
|
||||
- DB credentials (change the password in production).
|
||||
- `ALLOW_REGISTRATION`: set `false` once your users have accounts.
|
||||
|
||||
3. **Run** — with the PHP built-in server for testing:
|
||||
|
||||
```bash
|
||||
php -S 0.0.0.0:8080 -t /path/to/coursehub
|
||||
```
|
||||
|
||||
or point an Apache/Nginx + PHP-FPM vhost at the directory.
|
||||
|
||||
4. **Index the library** — click **Rescan library** in the top bar, or:
|
||||
|
||||
```bash
|
||||
php scan.php
|
||||
```
|
||||
|
||||
Rescan whenever you add/remove files. Re-scans are idempotent: existing
|
||||
videos keep their IDs (and everyone's progress); files that disappeared are
|
||||
hidden, not deleted, so progress survives if they come back.
|
||||
|
||||
5. Open the site, create an account, and watch.
|
||||
|
||||
## How progress works
|
||||
|
||||
- The player POSTs `{video_id, position, duration}` to `api/progress.php`
|
||||
every 5 s while playing, plus on pause/seek, via `navigator.sendBeacon`
|
||||
when the tab is hidden or closed, and when navigating to another view
|
||||
in the SPA.
|
||||
- A video is marked **completed** at ≥ 95 % watched (`COMPLETE_THRESHOLD`).
|
||||
Completion is sticky — rewatching doesn't un-complete it.
|
||||
- Folder progress is computed over the folder's entire subtree using a path
|
||||
prefix match, with partial credit for videos in progress.
|
||||
- Video duration is captured from the browser on first play (no ffprobe
|
||||
dependency) and cached on the `videos` row.
|
||||
|
||||
## Production notes
|
||||
|
||||
- PHP streams the video in 512 KB chunks with Range support, which is fine for
|
||||
a handful of users. For heavier use, serve files with `X-Accel-Redirect`
|
||||
(Nginx) or `X-Sendfile` (Apache) instead of streaming through PHP.
|
||||
- Put the app behind HTTPS and set `session.cookie_secure=1`.
|
||||
- Deny direct web access to `includes/`, `config.php`, and `schema.sql`
|
||||
(or place the videos and includes outside the docroot).
|
||||
Reference in New Issue
Block a user