Files
coursehub/README.md
T
Harwinder Sekhon 3ae6c1ae2e 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.
2026-07-11 10:25:35 -04:00

96 lines
3.6 KiB
Markdown

# 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).