A self-hosted web-based TV player for HDHomeRun tuners receiving UK Freeview DVB-T/T2.
Streams live TV in the browser with no plugins required. SD channels are transcoded to H.264 for browser compatibility; HD channels are passed through directly.
Created by ZeBadger.
Features
- Live TV and radio playback in the browser
- Channel list with HD/SD/Radio filtering, favourites, search, and sorting
- EPG now/next programme snippets in the channel list
- Channel visibility management (hide channels you don't want)
- Last-channel restore on reload (browser autoplay policy permitting)
- Unified Settings modal for channel, EPG, and user actions
- Admin-only access management with user/token controls and usage log
- Optional admin-only "More info" toggle for playback internals (transcode/passthrough details)
- DVB subtitle burn-in for supported channels
- Selectable SD transcode profiles (Quality, Balanced, Low bandwidth)
- HDHomeRun network tuner (tested on FLEX QUATRO)
- Docker and Docker Compose
git clone https://github.com/ZeBadger/tv-player.git
cd tv-playerEdit compose.yaml and set HDHOMERUN_HOST to your HDHomeRun's IP address:
- HDHOMERUN_HOST=192.168.0.XThen build and start:
docker compose up --build -dOpen http://localhost:8080 in your browser.
All settings are in compose.yaml:
| Variable | Default | Description |
|---|---|---|
HDHOMERUN_HOST |
(required) | IP address of your HDHomeRun device |
MAX_CONCURRENT_STREAMS |
1 |
Maximum simultaneous playback sessions served by this app. Set to your tuner capacity (for example 4 on FLEX QUATRO) |
TUNER_RELEASE_DELAY_MS |
300 |
Delay after forced session takeover before starting the next stream, giving tuner resources time to settle |
TUNER_SWITCH_COOLDOWN_MS |
350 |
Minimum gap between stream requests from the same client IP to reduce rapid-switch tuner thrash |
AUTH_INITIAL_ADMIN_USERNAME |
admin |
Username for the first admin account (only used when no auth data exists yet) |
AUTH_INITIAL_ADMIN_TOKEN |
auto-generated if unset | Optional one-time bootstrap token for first admin sign-in |
AUTH_REMEMBER_TTL_SECONDS |
2592000 |
Remember-this-device session lifetime in seconds (default 30 days) |
TRANSCODE_PRESET |
medium |
ffmpeg x264 preset (ultrafast → veryslow). Faster = lower CPU, lower quality |
TRANSCODE_SCALE |
960:-2 |
Output resolution for SD transcode. -2 preserves aspect ratio |
TRANSCODE_FPS |
25 |
Output frame rate |
TRANSCODE_VIDEO_BITRATE |
1800k |
Target video bitrate |
TRANSCODE_VIDEO_MAXRATE |
2200k |
Maximum video bitrate |
TRANSCODE_VIDEO_BUFSIZE |
4400k |
Video rate control buffer size |
TRANSCODE_AUDIO_BITRATE |
96k |
AAC audio bitrate |
TRANSCODE_QUALITY_PRESET |
slow |
Quality profile x264 preset |
TRANSCODE_QUALITY_SCALE |
1280:-2 |
Quality profile output resolution |
TRANSCODE_QUALITY_FPS |
25 |
Quality profile frame rate |
TRANSCODE_QUALITY_VIDEO_BITRATE |
2600k |
Quality profile target video bitrate |
TRANSCODE_QUALITY_VIDEO_MAXRATE |
3200k |
Quality profile max video bitrate |
TRANSCODE_QUALITY_VIDEO_BUFSIZE |
6400k |
Quality profile video buffer size |
TRANSCODE_QUALITY_AUDIO_BITRATE |
128k |
Quality profile AAC bitrate |
TRANSCODE_LOW_PRESET |
veryfast |
Low-bandwidth profile x264 preset |
TRANSCODE_LOW_SCALE |
640:-2 |
Low-bandwidth profile output resolution |
TRANSCODE_LOW_FPS |
25 |
Low-bandwidth profile frame rate |
TRANSCODE_LOW_VIDEO_BITRATE |
900k |
Low-bandwidth profile target video bitrate |
TRANSCODE_LOW_VIDEO_MAXRATE |
1200k |
Low-bandwidth profile max video bitrate |
TRANSCODE_LOW_VIDEO_BUFSIZE |
2400k |
Low-bandwidth profile video buffer size |
TRANSCODE_LOW_AUDIO_BITRATE |
64k |
Low-bandwidth profile AAC bitrate |
The Balanced profile uses the existing TRANSCODE_* settings. In the app, choose profile from Settings -> Channel Settings -> SD transcode profile.
TV Player supports token sign-in with persistent device sessions:
- On first startup, the server creates an admin user and prints an initial admin token in logs (or uses
AUTH_INITIAL_ADMIN_TOKENif set). - Open TV Player with
/?token=YOUR_TOKENor paste the token into the sign-in prompt. - The token is exchanged, then removed from the URL and saved as a secure session cookie.
Tokens are reusable and stay valid until an admin revokes them. You can still create expiring tokens explicitly if you want to.
Auth data is stored in data/auth.json (and persisted in Compose via ./data:/app/data).
If you get locked out of admin:
- Delete
data/auth.json. - Restart the container.
- The app will bootstrap a fresh admin user and print a new initial admin token in container logs.
To find the automatically created admin token, run:
docker logs tv-player-tv-player-1After signing in as admin, you can use the in-app Admin panel or call these same-origin endpoints directly:
GET /auth/admin/usersGET /auth/admin/tokens?userId=...POST /auth/admin/userswith{"username":"alice","role":"user"}POST /auth/admin/users/disablewith{"userId":"..."}POST /auth/admin/users/restorewith{"userId":"..."}POST /auth/admin/users/deletewith{"userId":"..."}(requires user to be disabled first)POST /auth/admin/tokenswith{"userId":"...","label":"Alice phone"}or includeexpiresInHoursfor an optional expiryPOST /auth/admin/tokens/revokewith{"tokenId":"..."}
POST /auth/admin/tokens returns both the token and a ready-to-share inviteUrl.
GET /diagnostics/streams(authenticated) — Returns live server stream diagnostics including session counters, cooldown activity, and recent FFmpeg errors. The same data is available from Settings -> User Settings -> Diagnostics for admins.
TV Player can display a free EPG (now/next programme information). By default, this project includes an iptv-org/epg Docker sidecar that generates guide.xml locally from epg/channels.xml.
compose.yaml includes a separate iptv-epg service:
- Input channels file:
epg/channels.xml - Generated guide URL inside compose network:
http://iptv-epg:3000/guide.xml - Refresh schedule: every 12 hours (and at startup)
Start both services:
docker compose up --build -dConfigure your country/provider:
- Open TV Player and click EPG settings
- Use the iptv-org Channel Lists filter and selectors to choose a site/provider XML list
- Click Use Selected List
- If a rebuild is not started automatically, click Rebuild Guide Now
- Wait for the Guide build status to show that the guide file is up to date
- TV Player will load the rebuilt guide automatically, or you can click Reload Current Guide
Debugging:
If you want to expose the guide outside Docker for debugging, add a port mapping to iptv-epg (for example 3000:3000) and then open http://localhost:3000/guide.xml.
To use a manual XMLTV feed URL instead of the built-in sidecar:
- Open TV Player and click EPG settings
- Select Use an external EPG server
- Paste an XMLTV feed URL into the source field
- Click Save
- Click Reload Guide to import the data
Or set EPG_SOURCE_URL in compose.yaml:
services:
tv-player:
environment:
- EPG_SOURCE_URL=https://example.com/guide.xmlThe repository ships with a starter epg/channels.xml file. This is only a default example and may not match your country/provider.
To manually replace the channel list:
- Open the iptv-org channels folder:
https://github.com/iptv-org/epg/tree/master/sites - Find your provider/country file named
*.channels.xml - Download the raw XML file
- Replace local
epg/channels.xmlwith that file - Open EPG settings and click Rebuild Guide Now
Then open TV Player and click EPG settings -> Reload Current Guide if it has not auto-loaded yet.
- Sidecar first build can take several minutes.
- Guide content refreshes automatically, but channel definitions come from
epg/channels.xml. - If channels are added/removed by your provider, repeat the replacement steps above.
- Docker control from the settings modal uses the mounted Docker socket in the default compose setup.
- EPG data is cached locally so it persists across restarts.
- If fetch fails, the last cached data is used.
-
iptv-org EPG (built-in, multi-country support, recommended)
- UK Freeview channels map:
https://raw.githubusercontent.com/iptv-org/epg/master/sites/freeview.co.uk/freeview.co.uk.channels.xml - See iptv-org for other countries
- Note: current iptv-org workflow is to generate
guide.xmllocally (or in Docker) from a channels list
- UK Freeview channels map:
-
XMLTV UK Freeview (external, manual setup)
- Visit xmltv.org
- Requires local grabber configuration
| Variable | Default | Description |
|---|---|---|
EPG_SOURCE_URL |
http://iptv-epg:3000/guide.xml |
URL to an XMLTV feed (leave blank to disable EPG) |
EPG_CACHE_DIR |
/tmp/epg |
Where to cache the downloaded guide data |
EPG_REFRESH_INTERVAL_HOURS |
12 |
How often to refresh the guide (set to 0 to disable auto-refresh) |
GET /epg/status— Returns EPG health (enabled, last fetch time, channel count)GET /epg/now-next— Returns current and next programme for each mapped channel (JSON)POST /epg/configure— Update EPG source URL at runtime (request body:{"sourceUrl": "..."})POST /epg/refresh— Trigger manual EPG refresh
- EPG data is cached locally so it persists across restarts.
- If fetch fails, the last cached data is used.
- Channel mapping from HDHomeRun to XMLTV ids is done automatically based on channel names and numbers.