StreamHall
Self-hosted live stream hall - public list, player, admin panel, analytics & push notifications
English · 简体中文
- ✨ Features
- 🚀 Quick Start
- 💻 Local Development
- ⚙️ Configuration
- 🐳 Services
- 🔌 Stream Push
- 📡 API
- 🤝 Contributing
- 📝 License
✨ Features
- Public stream list - Live and archive tabs, password-protected streams, custom site branding, bilingual UI (Chinese / English) with per-language site description
- Player - HLS, FLV, MPEG-DASH playback via ArtPlayer; AES-128 key override and DASH ClearKey support; Widevine and FairPlay DRM playback via Shaka Player (multi-DRM configs per source, Android Telegram WebView detection)
- Admin panel - Add, edit, reorder, enable/disable streams; manage sources with per-source labels and proxy mode selection
- Viewer analytics - Session tracking, unique visitors, peak concurrent viewers, average watch duration, device / browser / OS / geography breakdown, real-time dashboard, CSV export
- Telegram notifications - Per-stream push messages on stream start and stop; each source going live fires its own notification, simultaneous go-lives within a configurable window are merged into one message, and brief RTMP reconnects within a grace period are suppressed to avoid spurious stop/start pairs
- Stream push - Local file browser with per-file and per-folder RTMP push management; multi-file folder push with independent stream keys; inline push status and detail modal; remote RTMP push config for external encoders; hidden HLS route proxy (
/h/<slug>) so real stream keys are never exposed publicly - VOD / file serving - Signed
/video/URLs with HTTP Range support (seek-capable); publish any local video file or folder as an archive stream directly from the file browser - HLS proxy modes - Per-source direct, full proxy, or manifest-only proxy modes for balancing source URL exposure, CORS compatibility, and server bandwidth; full proxy supports upstream cookie forwarding for cookie-authenticated CDNs (e.g. CloudFront signed cookies), with the cookie stored server-side and never exposed in playback URLs
- API key auth - Generate per-key tokens in the admin panel for programmatic access to all admin and analytics endpoints
- Mobile responsive - Admin panel sidebar, source editor, file browser rows, and push directory sidebar all collapse gracefully on narrow screens
🚀 Quick Start
1. Download and configure
curl -LO https://git.stdm.moe/Stardream/StreamHall/raw/branch/main/docker-compose.yml
curl -LO https://git.stdm.moe/Stardream/StreamHall/raw/branch/main/nginx-hls.conf
Important
Open
docker-compose.ymland changeSECRET_KEYandPOSTGRES_PASSWORDto strong random values before starting.
2. Start
docker compose up -d
3. Find the initial admin password
docker logs streamhall
Look for:
StreamHall initial admin password: <random-password>
4. Access
| URL | Description |
|---|---|
http://HOST:8085/ |
Public stream list |
http://HOST:8085/admin |
Admin panel |
http://HOST:18088/ |
SRS HTTP playback |
http://HOST:8889/ |
Nginx HLS proxy |
Tip
After first login, change your password under Site Settings → Security. The initial password is printed once and not stored in plaintext.
Updating to a newer version
docker compose pull && docker compose up -d
Resetting a forgotten admin password
docker exec streamhall-postgres psql -U streamhall -d streamhall \
-c "DELETE FROM site_settings WHERE key='admin_password_hash';"
docker restart streamhall
docker logs streamhall
Deleting the hash causes StreamHall to generate a new random password on the next startup and print it to the log.
💻 Local Development
Prerequisites: Python 3.12+, PostgreSQL 14+
# 1. Clone the repo
git clone https://git.stdm.moe/Stardream/StreamHall.git
cd StreamHall
# 2. Install the single runtime dependency
pip install -r requirements.txt
# 3. Export required environment variables
export SECRET_KEY=dev-secret
export DATABASE_URL=postgresql://user:pass@localhost:5432/streamhall
# 4. Start the server
python server.py
The app listens on http://localhost:8080 by default. On first startup it creates the database schema and prints a one-time admin password to stdout.
⚙️ Configuration
Set these environment variables in docker-compose.yml:
| Variable | Default | Required | Description |
|---|---|---|---|
SECRET_KEY |
REPLACE_ME |
Yes | HMAC signing key for sessions and stream routes. Generate with openssl rand -hex 32 |
DATABASE_URL |
see compose file | Yes | PostgreSQL connection string |
POSTGRES_PASSWORD |
see compose file | Yes | PostgreSQL password |
TZ |
UTC |
No | Container timezone, e.g. Asia/Shanghai |
SRS_HTTP_ORIGIN |
http://srs:8080 |
No | SRS HTTP playback base URL |
STREAM_PROBE_TIMEOUT |
4 |
No | Seconds before aborting a stream URL probe |
HLS_PROXY_TIMEOUT |
15 |
No | Seconds before aborting an upstream HLS manifest/segment proxy request |
STREAM_MONITOR_INTERVAL |
10 |
No | Seconds between stream liveness checks |
TELEGRAM_TIMEOUT |
6 |
No | Seconds before aborting a Telegram API call |
TG_RECONNECT_GRACE_SECS |
60 |
No | Grace period before sending a stop notification; absorbs brief RTMP reconnects (0 disables) |
TG_START_MERGE_SECS |
30 |
No | Window for merging simultaneous link-online events into one start notification (0 disables) |
RTMP_HOST |
srs |
No | Hostname of the SRS container used for local push jobs |
VIDEOS_DIRS |
(unset) | No | Comma-separated list of directories exposed in the file browser. Optionally prefix each path with a label: label:/app/path. Multiple entries: movies:/app/movies,shows:/app/shows |
Warning
Always change
SECRET_KEYandPOSTGRES_PASSWORDbefore exposing StreamHall to a network. The defaults are intentionally weak placeholders.
Mounting video directories for Local Push
Method 1 — single base directory, multiple sources as subdirectories:
Mount multiple host paths under one container base path. The file browser shows them as subdirectories.
environment:
VIDEOS_DIRS: "/app/videos"
volumes:
- ./videos:/app/videos/local
- /your/media/path:/app/videos/external
Method 2 — multiple labeled top-level directories:
Each path is listed as a separate labeled root entry in the file browser.
environment:
VIDEOS_DIRS: "/app/videos,external:/app/media/external"
volumes:
- ./videos:/app/videos
- /your/media/path:/app/media/external
HLS proxy modes
Each stream source can choose how external HLS URLs are exposed to viewers:
| Mode | Behavior | Bandwidth impact |
|---|---|---|
Auto |
Backward-compatible default; external HLS uses the full proxy | StreamHall carries manifest and segment traffic |
Direct |
Player uses the source URL directly | Viewer traffic goes to the source server |
Full proxy |
Manifest, segments, maps, and keys are routed through /proxy/hls/ |
StreamHall carries all HLS media traffic |
Manifest only |
Only the playlist uses StreamHall; segment/key/map URLs are absolute source URLs | Low StreamHall bandwidth; final media URLs remain visible in browser network tools |
In Full proxy mode, a source can also set an upstream cookie for CDNs that require cookie-based authentication (e.g. CloudFront signed cookies). StreamHall forwards the cookie on every manifest and segment request. The cookie is stored server-side and referenced via a signed opaque token in proxy URLs, so it is never derivable from a playback or segment URL. Upstream proxy requests reuse pooled HTTP connections to avoid per-request TLS handshake overhead.
🐳 Services
The default compose stack starts four containers:
| Container | Image | Port(s) | Purpose |
|---|---|---|---|
streamhall |
local build | 8085→8080 |
Python app + static frontend |
streamhall-postgres |
postgres:16-alpine |
- | PostgreSQL persistent database |
streamhall-srs |
ossrs/srs:6 |
1935, 18088→8080 |
RTMP publishing + HTTP playback |
streamhall-nginx-hls |
nginx:alpine |
8889→80 |
Clean public HLS route proxy |
Persistent data is stored in ./postgres-data/.
🔌 Stream Push
SRS handles RTMP publishing on port 1935. Configure your encoder as follows:
RTMP server: rtmp://HOST:1935/live
Stream key: your-stream-key
The admin panel's Local Push section provides a file browser for your media directory. Select any video file to push it via RTMP with a custom stream key, or select a folder to push all contained videos simultaneously with independent stream keys. Push status is shown inline per file; a detail modal gives real-time duration, copy addresses, and stop controls.
The hidden public HLS URL (/h/<slug>/...) routes through nginx on port 8889, keeping the real stream key out of the public URL.
Note
The RTMP host field accepts an optional port, e.g.
live.example.com:1935. Do not hard-code:1935if you use a non-standard port.
📡 API
Authentication
- Browser session - cookie set by
POST /api?action=login - API key - send
Authorization: Bearer <token>header. Keys are managed in the admin panel under Site Settings → API Keys.
Public Endpoints
| Method | Endpoint | Description |
|---|---|---|
GET |
/api?action=public_list |
Stream list for the public homepage |
GET |
/api?action=site_settings |
Site title, description, branding |
GET |
/api?action=get_player_data&id=<public_id> |
Player sources and metadata |
POST |
/api?action=verify_password |
Verify a stream password |
GET |
/video/<token>/<payload> |
Signed VOD endpoint with HTTP Range support |
Admin Endpoints
| Method | Endpoint | Description |
|---|---|---|
POST |
/api?action=login |
Authenticate and start a session |
GET |
/api?action=logout |
End the current session |
GET |
/api?action=list_admin |
Full stream list |
POST |
/api?action=add |
Add a stream |
POST |
/api?action=update |
Update a stream |
POST |
/api?action=delete |
Delete a stream |
POST |
/api?action=set_stream_enabled |
Toggle stream visibility |
POST |
/api?action=reorder_streams |
Reorder within a label group |
POST |
/api?action=update_site_settings |
Save site settings |
GET |
/api?action=telegram_settings |
Read Telegram bot config |
POST |
/api?action=update_telegram_settings |
Save Telegram bot config |
POST |
/api?action=update_admin_password |
Change admin password |
GET |
/api?action=list_api_keys |
List API keys (tokens not returned) |
POST |
/api?action=create_api_key |
Create a key - token returned once |
POST |
/api?action=delete_api_key |
Revoke a key by id |
GET |
/api?action=list_pushes |
List active push jobs |
POST |
/api?action=start_push |
Start an RTMP push job for a file |
POST |
/api?action=stop_push |
Stop a push job by stream key |
GET |
/api?action=list_folder_videos |
List playable files in a directory (with signed URLs) |
Analytics Endpoints
| Method | Endpoint | Description |
|---|---|---|
GET |
/api?action=stats_overview |
Aggregated totals |
GET |
/api?action=stats_streams |
Per-stream stats table |
GET |
/api?action=stats_timeseries |
Bucketed view counts |
GET |
/api?action=stats_geo |
Country-level visitor counts |
GET |
/api?action=stats_stream_detail&id=<id> |
Single-stream detail |
GET |
/api?action=stats_sessions_page&id=<id> |
Paginated session list |
GET |
/api?action=stats_export_csv&range=<range> |
Export sessions as CSV (range: today, 7d, 30d (default), all) |
💡 Acknowledgements
StreamHall was inspired by AniLive, a live stream listing site that is not open-source. This project is an independent reimplementation built from scratch, sharing no code with the original site.
🤝 Contributing
Issues and pull requests are welcome. Please open an issue first for major changes so the direction can be discussed.
📝 License
Copyright © 2026 Stardream. This project is licensed under the GNU Affero General Public License v3.0.
Permissions
| ✅ | Commercial use |
| ✅ | Modification |
| ✅ | Distribution |
| ✅ | Private use |
Conditions
| ⚠️ | Disclose source - modified versions must also be open-source |
| ⚠️ | Same license - derivative works must use AGPL-3.0 |
| ⚠️ | Network use = distribution - if you run a modified version as a network service, you must make the source available to users |
| ⚠️ | State changes - modifications must be documented |
In short: you are free to use, modify, and self-host StreamHall. If you distribute or offer a modified version as a hosted service, you must publish the full source under AGPL-3.0.