hdtilt

中文文档
0.7.1发布于 2026/11/04MITESM + CJS

Open-source IPTV player: M3U, Xtream Codes and XMLTV, with a TV guide. Web/PWA, desktop, CLI, TUI, MCP and API.

原始使用文档
文档目录12 节
  1. What it does
  2. Accounts
  3. Every surface
  4. Install
  5. CLI
  6. MCP
  7. API
  8. Roku
  9. Privacy and the proxy
  10. Self-hosting
  11. Development
  12. License

hdtilt

An open-source IPTV player that works like TiviMate. Bring your own M3U playlist or Xtream Codes login and get a channel list, a TV guide and catch-up. It runs in the browser (installable PWA), on the desktop, and in the terminal.

hdtilt.com · MIT licensed · no account, no tracking, no channels included

hdtilt is a player. It ships with no content. You add a playlist from a provider you already pay for, or from any source you have the right to watch.

What it does

  • Playlists: M3U / M3U8 URLs, local .m3u files, Xtream Codes (player_api.php) logins. An Xtream get.php link pasted as an M3U is detected and upgraded to the Xtream API.
  • TV guide: XMLTV (plain or .gz), taken from the playlist's url-tvg or given by hand. Channels are matched by tvg-id, then by name. Shows what's on now and next, with progress bars and a full guide grid.
  • Catch-up: Xtream timeshift, plus the M3U catchup / catchup-source / tvg-rec forms (default, append, shift, flussonic).
  • Playback through @profullstack/player: HLS (hls.js), MPEG-TS (mpegts.js) and MP4. It restarts stalled live streams and explains codec failures.
  • Movies & Shows: an Xtream line's films and series in their own tab, a category at a time (a big line has hundreds of thousands of titles, so nothing loads them all at once). Search covers every title; a show opens into seasons and episodes; Continue watching resumes where you stopped, and the next episode follows. Providers serve films as ripped, usually MKV with AC-3 / E-AC-3 / DTS sound that no browser plays, so the server converts them on the fly: the picture is copied, only the sound becomes AAC (cheap), delivered as HLS. Seeking past what is converted restarts the conversion there. ◀ / ▶ jump 10 s / 30 s, n is the next episode. A plain M3U's /movie/ and /series/ entries show up there too.
  • Subtitles from OpenSubtitles on any film or episode: the CC button (or c) lists what exists in your language, best match first. Matches use the provider's TMDB id when it has one, else the cleaned-up title and year (an episode by its show, season and episode), and a moviehash of the file itself puts subtitles made for that exact rip on top (marked THIS FILE). −½s / +½s (or ◀ / ▶ in the panel) fix timing; the pick and its timing are remembered per title. Each file is fetched from OpenSubtitles once and kept, so a film someone already watched with subtitles costs nothing.
  • One page per view: /watch/<channel>, /channels/<group>, /favorites, /recent, /guide (lists take ?q= to filter), /vod/movies/<category>, /vod/shows/<category>, /vod/show/<id>, /settings, /account. Each can be bookmarked or reloaded, and the browser's Back button (or a remote's) steps back through them.
  • A global nav with Live TV (with a filter box), Movies & Shows, TV Guide, ★ Favorites, Recent, Account and Settings. It is always on screen outside the picture; over the picture, Back or Menu brings it up, focused for the remote.
  • iPhone and iPad Safari: they have no Media Source Extensions, so they cannot play an MPEG-TS stream directly. hdtilt asks an Xtream line for its HLS version when it offers one, and otherwise the server repackages the stream as HLS (ffmpeg -c copy, no transcoding, shared by everyone watching that channel). Self-hosters need ffmpeg on the PATH, or HDTILT_FFMPEG.
  • Favorites: tap ☆ on any channel row or on the channel banner, or press F. They sit under ★ Favorites in the nav and sync with your account.
  • The TiviMate basics: ↑/↓ zaps channels, OK opens the list, ◀ opens groups, digits jump to a channel number, Back returns to the last channel. Favorites and recents are built in. Every filter (Live TV, the guide, Movies & Shows, the CLI and MCP) finds all the words you type in any order, ignoring case, accents and punctuation, so espn us finds "US| ESPN HD" and sports1 finds "Sports 1". On a TV browser (Silk on Fire TV, Android TV, webOS, Tizen) big ▲ ▼ buttons page through every list, the guide and the category rails, because a TV pointer cannot scroll a list inside the page; ?pager=on or ?pager=off turns them on or off on any device. Works with a mouse, touch, a keyboard or a TV remote (Fire TV, Android TV, webOS, Tizen keys).

Accounts

On hdtilt.com an account comes first: sign up (username, email, password), confirm the email, then add your playlist. It keeps your playlists, favorites and recent channels the same on every screen you watch on. The desktop app and a self-hosted hdtilt serve need no account.

  • Username and password. Sign in with either your username or your email. A TV has no mail client and no passkey, so a password is the way in on one.
  • The email is confirmed before the account can sign in. The link signs you straight in, and "Forgot your password?" sends a reset link.
  • Sign in with CoinPay comes first on sign-in and sign-up (OAuth 2.1 code + PKCE S256 + state + nonce against coinpayportal.com). The account is bound to your CoinPay sub, never matched by email: a first CoinPay sign-in makes a new account, and an existing account connects CoinPay from its Account page while signed in. Disconnect is refused when CoinPay is the account's only way in. Server env: COINPAY_OAUTH_CLIENT_ID, COINPAY_OAUTH_CLIENT_SECRET.
  • The CLI and TUI sign in with OAuth 2.1: hdtilt login opens the browser (authorization code + PKCE, a loopback redirect), and hdtilt login --manual works over SSH by pasting a code; the sign-in page it opens offers Sign in with CoinPay too. Tokens live in ~/.config/hdtilt/auth.json (0600). hdtilt sync merges the account's playlists and favorites into the local config, and hdtilt tui syncs when it starts.
  • Provider logins inside a synced library are encrypted at rest (AES-256-GCM).

Every surface

SurfaceHow
Web / PWAhdtilt.com, or hdtilt serve on your own box
DesktopElectron app (Linux AppImage/deb, macOS dmg, Windows exe) from Releases
CLIhdtilt now, hdtilt play cnn, hdtilt guide 101 …
TUIhdtilt tui: groups, channels and what's on; Enter plays in mpv or VLC
MCPhdtilt mcp (stdio), or POST /mcp on any server; movies_shows browses and searches films and series
APIJSON under /api (POST /api/vod for Movies & Shows), plus the stream proxy at /p/ and conversions at /hls/

Install

curl -fsSL https://hdtilt.com/install.sh | sh

That installs the hdtilt command into ~/.local (no root). It uses your Node 22+ if you have one, otherwise it fetches an official Node build (checksum verified) into ~/.local/share/hdtilt. Re-run it to upgrade; sh -s -- --version X pins a release; remove it with sh ~/.local/share/hdtilt/uninstall.sh. The desktop app (AppImage, deb, dmg, exe) is on the releases page. Or install the package yourself:

npm i -g hdtilt        # or: bunx hdtilt
hdtilt add home http://provider.example/get.php?username=U&password=P&type=m3u_plus
hdtilt now             # what's on
hdtilt tui             # browse and play (needs mpv or VLC)
hdtilt serve           # the web app on http://127.0.0.1:8930

Xtream Codes:

hdtilt add home --xtream http://provider.example:8080 --user U --pass P

Playlists and logins live in ~/.config/hdtilt/config.json (mode 0600). The desktop app and hdtilt serve read the same file, so a playlist added from the CLI shows up in the window.

CLI

hdtilt add <name> <m3u-url> [--epg <xmltv-url>]
hdtilt add <name> --xtream <server> --user <u> --pass <p>
hdtilt playlists | use <name> | remove <name> | refresh [name]
hdtilt groups
hdtilt channels [query] [--group <g>] [--json]
hdtilt now [query] [--group <g>]
hdtilt guide <channel> [--hours 12] [--from -6]
hdtilt url <channel> [--at <iso-time>]      # --at = catch-up
hdtilt play <channel> [--player mpv|vlc|ffplay]
hdtilt fav <channel>
hdtilt subs --key <opensubtitles-api-key> [--user <u> --pass <p>]
hdtilt subs <title> [--year Y] [--season S --episode E] [--lang en,fr] [--tmdb id] [--get <n> -o file.vtt]
hdtilt login [--manual] | whoami | sync | logout
hdtilt tui | serve [--port] [--host] [--public] | mcp

A channel can be named by its number, its id, or part of its name.

MCP

Add it to any MCP client:

{ "mcpServers": { "hdtilt": { "command": "npx", "args": ["-y", "hdtilt", "mcp"] } } }

Tools: list_playlists, add_playlist, list_groups, list_channels, whats_on, channel_guide, stream_url (with catch-up), movies_shows, find_subtitles, fetch_subtitle (local only). Over HTTP (POST /mcp), a public server keeps no playlists, so every call passes its own source.

API

MethodPathBody / result
GET/api/health{ ok, version, public }
POST/api/load{ source } → { channels, groups, epgUrl, account }
POST/api/guide{ url, channels:[{id,name,tvgId}], from?, to? } → { programmes: { [channelId]: [...] } }
POST/api/nownext{ url, channels } → { [channelId]: { now, next } }
GET/api/playlists, /api/playlists/:namelocal installs only
POST/api/auth/register login verify resend forgot reset refresh logoutaccounts (20 tries a minute per address)
GET/api/methe signed-in account (Authorization: Bearer, a session or an OAuth token)
GET / PUT/api/librarythe synced library; PUT { data, baseVersion }, 409 if another device wrote first
GET/.well-known/oauth-authorization-serverOAuth 2.1 metadata; /oauth/token, /oauth/revoke; client hdtilt-cli
POST/oauth/device_authorizationTV sign-in (client hdtilt-tv): { device_code, user_code, verification_uri }; approve at /link, poll /oauth/token with the device_code grant
POST/api/tv/load channels vodthe Roku app's paged view of a playlist: groups, then pages of channels or titles, each with play: { url, format, fallback? }
POST/api/subtitles{ title?, year?, tmdbId?, season?, episode?, languages?, url? } → { items: [{ fileId, language, release, downloads, hashMatch, … }], moviehash }
GET/subs/<fileId>.vtt?shift=<s>one subtitle as WebVTT, cues moved by shift seconds
GET/hls/<base64url>/index.m3u8an MPEG-TS stream repackaged as HLS (for browsers without MSE)
GET/p/<base64url>/<name.ext>stream proxy; HLS playlists are rewritten so segments and keys come back through it
POST/mcpMCP JSON-RPC

source is { "type": "m3u", "url": "…" } or { "type": "xtream", "server": "…", "username": "…", "password": "…" }, plus an optional "epg".

Roku

roku/ is a native SceneGraph app for hdtilt.com. On first launch it shows a code; open hdtilt.com/link on a phone, sign in and enter it. It then opens the account's playlists: Favorites, All channels, Search, Movies, Shows and every group, with ▲▼ to change channel. Streams a Roku decodes play straight from the provider; transport streams (and anything that fails) go through hdtilt's HLS remux.

Build the sideload zip with npx -p brighterscript bsc --project roku/bsconfig.json (dist-roku/hdtilt-roku.zip, also attached to every release), then upload it at http://<roku-ip> with the device in developer mode.

Privacy and the proxy

On hdtilt.com, playlists and logins are kept in your browser (IndexedDB) and sent to the server only to load the list. Nothing is stored there. Browsers can't play http:// streams on an https:// page, and most provider streams don't send CORS headers, so those streams pass through hdtilt.com's proxy. The public proxy only serves signed-in viewers (an HttpOnly media cookie set at sign-in, since a video element cannot send a token), refuses private and internal addresses, and limits each viewer to 3 concurrent streams.

If you'd rather nothing sat in between, use the desktop app or hdtilt serve. Both run the same server on your own machine, where the proxy can also reach tuners on your LAN.

Self-hosting

git clone https://github.com/profullstack/hdtilt.com && cd hdtilt.com
bun install && bun run build
PORT=8930 node bin/hdtilt.js serve --host 0.0.0.0 --public   # --public for a shared server

Environment: PORT, HOST, HDTILT_PUBLIC=1, HDTILT_JWT_SECRET (turns accounts on), DATABASE_URL (Postgres for accounts; memory without it), HDTILT_LIBRARY_KEY, RESEND_API_KEY, MAIL_FROM, SITE_URL, HDTILT_ISSUER (the CLI's account server), HDTILT_UA (the user-agent sent to providers), HDTILT_HOME / HDTILT_CACHE, HDTILT_PLAYER, OPENSUBTITLES_API_KEY (turns subtitles on; free at opensubtitles.com/consumers), OPENSUBTITLES_USERNAME / OPENSUBTITLES_PASSWORD (an account's daily download allowance instead of 5 per IP), HDTILT_SUBS_PER_DAY (new downloads per viewer per day on a public server, default 10).

Development

bun install
bun test          # core, server, proxy, MCP and CLI against a fake provider
bun run lint
bun run dev       # build the web app and serve it
bun run desktop   # the Electron window

Layout: src/core (playlists, Xtream, XMLTV, guide, config; most of it also runs in the browser), src/server (HTTP + MCP), src/cli (TUI, player launcher), web/ (the PWA), desktop/ (Electron), bin/hdtilt.js.

License

MIT © Profullstack, Inc.

版本与发布记录

hdtilt 共发布 23 个版本,首次发布于 2026/10/10;过去 12 个月发布了 23 个版本。

版本发布日期距今
0.7.1latest2026/11/04今天
0.7.02026/11/04今天
0.5.22026/10/295 天前
0.4.22026/10/268 天前
0.4.12026/10/2113 天前
0.4.02026/10/1520 天前

关于 hdtilt 的常见问题

hdtilt 是什么?

开源IPTV播放器:M3U, xstream Codes和XMLTV,带有电视指南。Web/PWA,桌面,CLI, TUI, MCP和API。

如何安装 hdtilt?

在项目目录运行 npm install hdtilt,或使用 pnpm add / yarn add / bun add。它还提供命令行工具 hdtilt,可以用 npx hdtilt 直接运行。

hdtilt 支持 TypeScript 吗?

该包的 package.json 没有声明类型入口。需要类型提示时,可以在 npm 上查找社区维护的 @types/hdtilt 是否存在。

hdtilt 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

hdtilt 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=22.6。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

hdtilt 可以商用吗?

hdtilt 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

hdtilt 还在维护吗?

最新版本 0.7.1 发布于 2026/11/04(今天);至今共发布 23 个版本,其中过去 12 个月发布了 23 个;首次发布于 2026/10/10。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。