iptv-proxy

pierre-emmanuelJGPL-3.0活跃维护

把 M3U/M3U8 播放列表和 Xtream Codes 账号转成 HLS 反向代理,方便多设备共享 IPTV 源,单个二进制或 Docker 容器即可跑起来。

IPTV reverse proxy for M3U/M3U8 playlists and Xtream Codes accounts, with HLS. One binary or Docker container.

v4.7.3发布于 2026-11-09 09:37 UTC747star208fork同步于 2026-11-07 07:34 UTC

怎么上手

由 README 提炼,执行前请确认
docker run -d --name iptv-proxy -p 8080:8080 -e M3U_URL="http://provider.example:8080/playlist.m3u" -e PROXY_HOSTNAME=192.168.1.10 -e PROXY_USER=family -e PROXY_PASSWORD=choose-a-password -e GIN_MODE=release pierro777/iptv-proxy:latest
硬件要求按 README 说明

README 未说明具体硬件要求;Docker 镜像自 v3.12.0 起支持 amd64 与 ARM(树莓派、Apple silicon)。HDHomeRun 调谐器需 ffmpeg 把 HLS 转 MPEG-TS,可用带 ffmpeg 的镜像变体。

支持 Apple Silicon有 Docker 镜像
下载:源码 zip·GitHub Releases(安装包)本站不镜像安装包

项目说明

由 README 自动翻译提炼

把 IPTV 播放列表或 Xtream 账号反代到你自己的地址,用自设账号密码对外提供频道

把 M3U/M3U8 播放列表和 Xtream Codes 账号转成 HLS 反向代理,方便多设备共享 IPTV 源,单个二进制或 Docker 容器即可跑起来。

核心能力

  • 重写 M3U 中每条流地址,隐藏供应商地址与凭据
  • 支持 M3U/M3U8 播放列表与 Xtream Codes 两种源
  • 以 HDHomeRun 调谐器形式接入 Plex/Jellyfin/Emby
  • 按正则过滤频道与分组,并可限制并发连接数
  • 单二进制或 Docker 容器运行,无需数据库和 Web 配置界面

容易踩的坑

默认用户名 usertest、密码 passwordtest 是公开的,必须改掉;USER、HOSTNAME 是系统变量,建议改用 PROXY_USER、PROXY_PASSWORD、PROXY_HOSTNAME;HDHomeRun 调谐器把 HLS 频道转 MPEG-TS 需要 ffmpeg,普通镜像不含,要换 -ffmpeg 标签镜像。

内容由模型根据 README 生成,可能不完整或不准确,以官方文档为准。看原文 README

最近发版

5 个版本v4.7.3
  1. v4.7.3最新正式版2026-11-09 · 2026-11-09 09:37 UTC

    Fixed

    • Providers that gzip their Xtream API answers whatever the request asks: the proxy read the compressed bytes as JSON. Players got raw gzip without a Content-Encoding header ("expected value at line 1 column 1"), and the answers the proxy rewrites (login, get_vod_info) went out unscrubbed. The API client (API, playlists, guides, logos) now asks for gzip and reads the answers uncompressed; streams are unchanged. Providers that honour gzip now also send much less. Thanks to @mzoookle for the report and the diagnosis (#205, fixed in #207).

    Upgrading

    Nothing to change in the settings.

    Images

    docker pull pierro777/iptv-proxy:v4.7.3
    docker pull pierro777/iptv-proxy:v4.7.3-ffmpeg
    

    Also tagged v4.7, v4 and latest (with ffmpeg: v4.7-ffmpeg, v4-ffmpeg and latest-ffmpeg), and published as ghcr.io/pierre-emmanuelj/iptv-proxy:v4.7.3 and ghcr.io/pierre-emmanuelj/iptv-proxy:v4.7.3-ffmpeg; linux/amd64 and linux/arm64.

  2. v4.7.22026-11-09 · 2026-11-09 09:28 UTC
    查看更新说明

    Fixed

    • A player that stops reading a stream without closing its connection kept its stream, and its place in its user's MAX_CONNECTIONS limit, until the proxy restarted. Every chunk of a stream (live and files/VOD, HLS, sources, the HDHomeRun tuner, the guide download) now has a one-minute write deadline: a client that takes nothing for a minute is dropped and its place freed. Thanks to @mackid1993 for the fix (#206).

    Upgrading

    Nothing to change in the settings. A VOD paused for more than about a minute (plus what the player has buffered) loses its connection; players that resume with an HTTP Range request (VLC, Kodi, ExoPlayer-based apps, AVPlayer) carry on as before.

    Images

    docker pull pierro777/iptv-proxy:v4.7.2
    docker pull pierro777/iptv-proxy:v4.7.2-ffmpeg
    

    Also tagged v4.7, v4 and latest (with ffmpeg: v4.7-ffmpeg, v4-ffmpeg and latest-ffmpeg), and published as ghcr.io/pierre-emmanuelj/iptv-proxy:v4.7.2 and ghcr.io/pierre-emmanuelj/iptv-proxy:v4.7.2-ffmpeg; linux/amd64 and linux/arm64.

  3. v4.7.12026-10-05 · 2026-10-05 10:12 UTC
    查看更新说明

    Fixed

    • The HDHomeRun tuner with several sources announced the connections of the first account only. It now announces those of every source together (a source's max-connections, else what its account allows, one for an account that does not say): a media server can watch as many channels at once as the sources allow.

    Upgrading

    Nothing to change in the settings. With several sources, a media server now sees more tuners: if it cached the tuner count, rescan the tuner in it.

    Images

    docker pull pierro777/iptv-proxy:v4.7.1
    docker pull pierro777/iptv-proxy:v4.7.1-ffmpeg
    

    Also tagged v4.7, v4 and latest (with ffmpeg: v4.7-ffmpeg, v4-ffmpeg and latest-ffmpeg), and published as ghcr.io/pierre-emmanuelj/iptv-proxy:v4.7.1 and ghcr.io/pierre-emmanuelj/iptv-proxy:v4.7.1-ffmpeg; linux/amd64 and linux/arm64.

  4. v4.7.02026-10-05 · 2026-10-05 09:40 UTC
    查看更新说明

    Added

    • HLS channels on the HDHomeRun tuner, with ffmpeg. Media servers expect MPEG-TS; a channel the provider has only as HLS (an M3U playlist of .m3u8 addresses, or an Xtream account whose allowed_output_formats has no ts, which is then asked for HLS) is read by ffmpeg and written as MPEG-TS, without encoding it again. Viewers of a channel share one ffmpeg; when ffmpeg fails on an address, the channel's next one is tried. ffmpeg's messages are logged without addresses.
    • -ffmpeg images: each Docker tag has one with ffmpeg (latest-ffmpeg, v4-ffmpeg, v4.7.0-ffmpeg...), for amd64 and ARM. The usual images do not change.
    • --ffmpeg (FFMPEG): the ffmpeg to use. By default ffmpeg, used when it is found; none goes without.

    Upgrading

    Nothing to change in the settings. Take an -ffmpeg image only if the HDHomeRun tuner must serve channels that the provider has only as HLS; a provider that serves MPEG-TS needs none, and the usual images stay as they were. When ffmpeg is installed, --ffmpeg none (FFMPEG=none) turns it off.

    Images

    docker pull pierro777/iptv-proxy:v4.7.0
    docker pull pierro777/iptv-proxy:v4.7.0-ffmpeg
    

    Also tagged v4.7, v4 and latest (with ffmpeg: v4.7-ffmpeg, v4-ffmpeg and latest-ffmpeg), and published as ghcr.io/pierre-emmanuelj/iptv-proxy:v4.7.0 and ghcr.io/pierre-emmanuelj/iptv-proxy:v4.7.0-ffmpeg; linux/amd64 and linux/arm64.

  5. v4.6.02026-10-05 · 2026-10-05 08:42 UTC
    查看更新说明

    Added

    • Channel logos in Plex's guide. Plex's apps are served over HTTPS and refuse a logo at a plain http:// address, which many providers have only: the guide showed no logos. When the proxy's own address is HTTPS, the tuner's guide now gives those logos through the proxy (/logo/<token>/<name>, as --proxy-logos does for players).
    • --proxy-logos covers the guides too: the images of xmltv.php (and of an M3U playlist's guide) and of the tuner's guide come through the proxy.

    Upgrading

    Nothing to change in the settings. In Plex, reload the DVR's guide (or wait for its daily refresh) so it takes the new logo addresses. The logos come through the proxy only when its address is HTTPS (--https), and that address must be reachable by the Plex apps.

    Images

    docker pull pierro777/iptv-proxy:v4.6.0
    

    Also tagged v4.6, v4 and latest, and published as ghcr.io/pierre-emmanuelj/iptv-proxy:v4.6.0; linux/amd64 and linux/arm64.

用户点评

登录用户的真实使用记录,可按版本和硬件筛选
共 0 条

    还没有人点评。第一个说出真实体验的人最有价值。

    README 原文

    未翻译

    iptv-proxy

    CI Release Docker pulls

    iptv-proxy is a reverse proxy for IPTV: give it an M3U/M3U8 playlist or an Xtream Codes account, and it serves the same channels, movies and series from your own address, with a user and a password you choose.

    Your players (TiviMate, IPTV Smarters, VLC, Kodi...) talk to the proxy and never see the provider's address or credentials. Media servers (Plex, Jellyfin, Emby, Channels DVR) can see the live channels as an HDHomeRun TV tuner, with their guide. Use it to share IPTV at home without handing out the provider's login, to put a provider behind your own domain, HTTPS or VPN, or to give every device one address that does not change.

    It is one binary or one container, set up with a few flags or environment variables. There is no database and nothing to configure in a web interface; a read-only status page shows what is being watched.

    Quick start

    In the examples, 192.168.1.10 is the machine running the proxy, as your players reach it, and family / choose-a-password are the credentials you give to your players. Replace them with your own.

    Docker, with an M3U playlist

    docker run -d --name iptv-proxy -p 8080:8080 \
      -e M3U_URL="http://provider.example:8080/playlist.m3u" \
      -e PROXY_HOSTNAME=192.168.1.10 \
      -e PROXY_USER=family \
      -e PROXY_PASSWORD=choose-a-password \
      -e GIN_MODE=release \
      pierro777/iptv-proxy:latest
    

    Give this address to your player:

    http://192.168.1.10:8080/iptv.m3u?username=family&password=choose-a-password
    

    To check it from a terminal:

    curl "http://192.168.1.10:8080/iptv.m3u?username=family&password=choose-a-password"
    

    The playlist can also be a local file: mount it and give its path.

    docker run -d --name iptv-proxy -p 8080:8080 \
      -v "$PWD/iptv.m3u:/iptv.m3u:ro" \
      -e M3U_URL=/iptv.m3u \
      -e PROXY_HOSTNAME=192.168.1.10 \
      -e PROXY_USER=family \
      -e PROXY_PASSWORD=choose-a-password \
      -e GIN_MODE=release \
      pierro777/iptv-proxy:latest
    

    Docker, with an Xtream Codes account

    docker run -d --name iptv-proxy -p 8080:8080 \
      -e XTREAM_BASE_URL="http://provider.example:8080" \
      -e XTREAM_USER=xtream_user \
      -e XTREAM_PASSWORD=xtream_password \
      -e PROXY_HOSTNAME=192.168.1.10 \
      -e PROXY_USER=family \
      -e PROXY_PASSWORD=choose-a-password \
      -e GIN_MODE=release \
      pierro777/iptv-proxy:latest
    

    In your player, choose the Xtream Codes login and enter:

    Server:   http://192.168.1.10:8080
    Username: family
    Password: choose-a-password
    

    A player that only takes a playlist can use the usual Xtream addresses:

    Playlist: http://192.168.1.10:8080/get.php?username=family&password=choose-a-password&type=m3u_plus&output=ts
    Guide:    http://192.168.1.10:8080/xmltv.php?username=family&password=choose-a-password
    

    Docker images

    RegistryImage
    Docker Hubpierro777/iptv-proxy
    GitHubghcr.io/pierre-emmanuelj/iptv-proxy

    Tags: latest, and for a given version v4.7.1, v4.7 or v4 (v3 for the 3.x versions). Since v3.12.0 they work on amd64 and on ARM (Raspberry Pi, Apple silicon): Docker pulls the image of your machine. The -amd64 and -arm64 tags (pierro777/iptv-proxy:latest-arm64) name one architecture, as before.

    Each tag also has an image with ffmpeg, about 140 MB larger: latest-ffmpeg, v4-ffmpeg, v4.7.0-ffmpeg... Take it if the HDHomeRun tuner serves channels your provider has only as HLS (see Plex and other media servers); nothing else needs it.

    Docker Compose

    services:
      iptv-proxy:
        image: pierro777/iptv-proxy:latest
        restart: unless-stopped
        ports:
          # the same port as PORT below
          - 8080:8080
        environment:
          # Either an M3U playlist (an address, or a file mounted in the container)...
          M3U_URL: "http://provider.example:8080/playlist.m3u"
          # ...or an Xtream Codes account (remove M3U_URL then)
          # XTREAM_BASE_URL: "http://provider.example:8080"
          # XTREAM_USER: xtream_user
          # XTREAM_PASSWORD: xtream_password
          PORT: 8080
          # Name or IP your players reach this machine at
          PROXY_HOSTNAME: 192.168.1.10
          # What your players log in with
          PROXY_USER: family
          PROXY_PASSWORD: choose-a-password
          GIN_MODE: release
    
    docker compose up -d
    

    The docker-compose.yml of this repository does the same with an Xtream Codes account, building the image from the sources.

    Binary

    Download the archive for your system (Linux, macOS, Windows; amd64 and arm64; also .deb and .rpm) from the releases, or build it with Go 1.27 or later:

    git clone https://github.com/pierre-emmanuelJ/iptv-proxy.git
    cd iptv-proxy
    go build -o iptv-proxy .
    
    GIN_MODE=release ./iptv-proxy \
      --m3u-url "http://provider.example:8080/playlist.m3u" \
      --hostname 192.168.1.10 \
      --user family \
      --password choose-a-password
    

    Quote the playlist address: it usually contains ? and &.

    Options

    Every option is a flag, an environment variable (the flag's name in upper case, with _ instead of -) or a line of the configuration file. A flag wins over the variable, and the variable over the file.

    FlagEnvironment variableDefaultWhat it does
    --m3u-url, -uM3U_URLProvider playlist: an http(s) address or a local file.
    --xtream-base-urlXTREAM_BASE_URLProvider's Xtream Codes address, e.g. http://provider.example:8080.
    --xtream-userXTREAM_USERProvider's Xtream Codes user.
    --xtream-passwordXTREAM_PASSWORDProvider's Xtream Codes password.
    --xtream-passthroughXTREAM_PASSTHROUGHfalsePlayers log in with their own account of the provider. See Each player with its own provider account.
    --hostnamePROXY_HOSTNAME (or HOSTNAME)Name or IP your players reach the proxy at. It is written in every address the proxy gives out.
    --portPORT8080Port the proxy listens on.
    --listen-addressLISTEN_ADDRESSevery interfaceIP address the proxy listens on, e.g. 127.0.0.1 behind a reverse proxy on the same machine.
    --advertised-portADVERTISED_PORTvalue of --portPort written in the addresses the proxy gives out, when it differs from the listening port (behind a reverse proxy, or a different published Docker port).
    --httpsHTTPSfalseWrite https:// instead of http:// in the addresses the proxy gives out. The proxy itself always listens in plain HTTP: put a reverse proxy in front for TLS.
    --userPROXY_USER (or USER)usertestUser your players log in with.
    --passwordPROXY_PASSWORD (or PASSWORD)passwordtestPassword your players log in with.
    --max-connectionsMAX_CONNECTIONS0 (no limit)Streams that user may watch at once. See Several users.
    --m3u-file-nameM3U_FILE_NAMEiptv.m3uName of the playlist the proxy serves: http://host:port/iptv.m3u.
    --custom-endpointCUSTOM_ENDPOINTPrefix put before every path: http://host:port/<custom-endpoint>/iptv.m3u.
    --custom-idCUSTOM_IDderived from your settingsFirst path element of M3U track addresses. The same settings give the same addresses, restart after restart.
    --m3u-cache-expirationM3U_CACHE_EXPIRATION1Hours a playlist is kept before reading it again from the provider (M3U playlists included since v3.13.0).
    --xmltv-urlXMLTV_URLthe guide the playlist namesGuide (XMLTV) of an M3U playlist: an http(s) address or a local file. It is served at /xmltv.php.
    --xtream-api-getXTREAM_API_GETfalseBuild the get.php playlist (live channels) from the provider's API, for providers that disabled get.php.
    --xtream-api-get-moviesXTREAM_API_GET_MOVIESfalseAdd the provider's movies to that generated playlist. Off by default: a catalogue of tens of thousands of movies makes a playlist some players cannot load. Series are not added (see below).
    --user-agentUSER_AGENTUser-Agent sent to the provider instead of the player's. Some providers only answer known players.
    --no-stream-sharingNO_STREAM_SHARINGfalseOpen one provider connection per player for a live stream, instead of sharing one between the players watching it.
    --status-passwordSTATUS_PASSWORDPassword of a read-only status page at /status. See Status page.
    --proxy-logosPROXY_LOGOSfalseServe the logos and covers of playlists, of the Xtream API and of the guides through the proxy too, so that players reach no other host.
    --hdhomerun-portHDHOMERUN_PORTPort of an HDHomeRun tuner for Plex, Emby, Jellyfin or Channels DVR. Local network only. See Plex and other media servers.
    --hdhomerun-group-regex, --hdhomerun-channel-regex, --hdhomerun-group-exclude-regex, --hdhomerun-channel-exclude-regexHDHOMERUN_GROUP_REGEX...Filters of the tuner alone, applied after the proxy's: the media server gets fewer channels than your players.
    --hdhomerun-tunersHDHOMERUN_TUNERSthe account's connections (the sources' together), else 2Number of tuners announced: how many channels a media server plays at once.
    --ffmpegFFMPEGffmpegffmpeg the HDHomeRun tuner turns HLS channels into MPEG-TS with, when it is found (the -ffmpeg images have it). none goes without.
    --group-regexGROUP_REGEXKeep only the live channels whose group matches this regular expression. See Filtering channels.
    --channel-regexCHANNEL_REGEXKeep only the live channels whose name matches this regular expression.
    --group-exclude-regexGROUP_EXCLUDE_REGEXLeave out the live channels whose group matches this regular expression.
    --channel-exclude-regexCHANNEL_EXCLUDE_REGEXLeave out the live channels whose name matches this regular expression.
    --iptv-proxy-configIPTV_PROXY_CONFIG.iptv-proxy.yaml in the home or current directoryYAML file holding the same options, named as the flags (m3u-url: ...), the users and the sources.
    --versionPrints the version and exits.

    Good to know:

    • Change --user and --password: the defaults are public.
    • USER and HOSTNAME are also ordinary system variables: a shell sets USER to your login name, and Docker sets HOSTNAME to the container's ID. Since v3.11.0, prefer PROXY_USER, PROXY_PASSWORD and PROXY_HOSTNAME: they win over the old names, which keep working. Since v3.12.0, user, password and hostname in the configuration file also win over the old names.
    • GIN_MODE is not an option of the proxy but of its web framework. Since v3.10.0 the proxy runs in release mode unless you set it. Before that, set GIN_MODE=release as the examples do: the debug mode lists the routes at startup, and they contain your user and password. The access log masks them in both modes.

    M3U playlists

    The proxy reads the provider's playlist at startup and serves it at /iptv.m3u with every track address replaced by its own. All other lines (names, logos, groups, guide IDs, player options) are kept as the provider wrote them. When a player asks for it and it is older than --m3u-cache-expiration (one hour by default), the playlist is read again: no restart is needed to pick up the provider's changes. If the provider fails, the previous playlist is kept.

    A track's address on the proxy is derived from its address at the provider: it stays the same when the playlist is read again, wherever the track moves in it, and from one start of the proxy to the next.

    The provider's playlist:

    #EXTM3U
    #EXTINF:-1 tvg-id="news.example" tvg-name="News" tvg-logo="http://provider.example/logos/news.png" group-title="News",News HD
    http://provider.example:8080/live/news/1.ts
    #EXTINF:-1 tvg-id="sport.example" tvg-name="Sport" tvg-logo="http://provider.example/logos/sport.png" group-title="Sport",Sport HD
    http://provider.example:8080/live/sport/2.m3u8?token=abc
    

    What your players get:

    #EXTM3U
    #EXTINF:-1 tvg-id="news.example" tvg-name="News" tvg-logo="http://provider.example/logos/news.png" group-title="News",News HD
    http://192.168.1.10:8080/e3c0c308/family/choose-a-password/4f2a9c1d7b21e0aa/1.ts
    #EXTINF:-1 tvg-id="sport.example" tvg-name="Sport" tvg-logo="http://provider.example/logos/sport.png" group-title="Sport",Sport HD
    http://192.168.1.10:8080/e3c0c308/family/choose-a-password/9b03e5c2d8a17f46/2.m3u8
    

    e3c0c308 is the --custom-id, and 4f2a9c1d7b21e0aa names the track. Tokens and other query parameters of the provider's addresses stay on the proxy.

    When the playlist names a guide (url-tvg or x-tvg-url in its first line), or --xmltv-url gives one, the proxy serves it at /xmltv.php?username=...&password=..., and the playlist names that address instead. Players load the guide through the proxy, and the filters apply to it.

    Some playlists name the provider's credentials outside of the track addresses too: a guide address in url-tvg, a catch-up address in catchup-source, a player option. Such an attribute or line is removed, so that the credentials never reach a player. (In an Xtream Codes playlist, they point to the proxy instead: see below.)

    Xtream Codes

    The proxy answers the Xtream Codes client API like the provider does: live, movies, series, catch-up and the guide. Your players log in with the proxy's address and credentials, the proxy asks the provider with the real ones.

    ProviderProxy
    Serverhttp://provider.example:8080http://192.168.1.10:8080
    Userxtream_userfamily
    Passwordxtream_passwordchoose-a-password

    The provider's answers are passed on as they are, so the fields and quirks of any provider reach the player. Only what names the provider is rewritten: the account and server of the login answer, and the addresses holding its credentials. In the get.php playlist, the guide (url-tvg) and catch-up (catchup-source) addresses of the provider become the proxy's, and work through it.

    Endpoints served:

    EndpointWhat it is
    /player_api.phpThe client API (login, categories, streams, movie and series details, short guide).
    /get.phpThe M3U playlist, with the same parameters as the provider's (type, output).
    /xmltv.phpThe full guide (XMLTV), streamed from the provider.
    /apigetA playlist of the live channels built from the API.
    /<user>/<password>/<id>, /live/..., /movie/..., /series/..., /timeshift/...The streams.

    /player_api.php, /get.php, /xmltv.php and /apiget take username=...&password=....

    If you give --m3u-url the provider's get.php address (http://provider.example:8080/get.php?username=xtream_user&password=xtream_password&type=m3u_plus&output=ts), the Xtream options are read from it: everything above is served, and that playlist is also available at /iptv.m3u.

    With the Xtream options alone, /iptv.m3u is the account's playlist too (the same as /get.php).

    The playlist built from the API (/apiget, or /get.php with --xtream-api-get) holds the live channels, and the movies with --xtream-api-get-movies. Series are not in it: the API gives the episodes of one series at a time, so listing them all would take one request per series. Players that speak the Xtream API get movies and series from it directly.

    HLS

    HLS streams (.m3u8) work in both modes, whatever the provider. Every address an HLS playlist names (variants, segments, keys, audio tracks) is replaced by an address of the proxy, /hls/<token>/<name>, so the whole stream goes through the proxy, on any host and after any redirect. The token is the provider's address, encrypted: a player never sees the provider's host, credentials or session tokens. Nothing is stored, and tokens stay valid across restarts.

    Several users

    One user and password is all most homes need, and the options above give exactly that. To give each person or device their own login, list the users in the configuration file (--iptv-proxy-config, or IPTV_PROXY_CONFIG in Docker):

    # /config/iptv-proxy.yaml
    users:
      - name: family
        password: choose-a-password
        max-connections: 2
      - name: kids
        password: another-password
        max-connections: 1
        group-regex: "(?i)kids|cartoon"
      - name: grandma
        password: a-third-one
        group-regex: "^FR "
        channel-exclude-regex: "(?i)adult"
    
    docker run -d --name iptv-proxy -p 8080:8080 \
      -v "$PWD/iptv-proxy.yaml:/config/iptv-proxy.yaml:ro" \
      -e IPTV_PROXY_CONFIG=/config/iptv-proxy.yaml \
      -e XTREAM_BASE_URL="http://provider.example:8080" \
      -e XTREAM_USER=xtream_user \
      -e XTREAM_PASSWORD=xtream_password \
      -e PROXY_HOSTNAME=192.168.1.10 \
      pierro777/iptv-proxy:latest
    
    • When users is there, those users replace --user and --password. The other options (provider, address, filters) stay where they are: flags, variables or the same file.
    • Each user logs in with their own name and password, and their playlists and stream addresses hold their own credentials.
    • max-connections is how many streams the user watches at once (none means no limit). At the limit, a new stream from the same device (the same address) replaces that device's oldest one: zapping keeps working. A stream from another device is refused until one stops. The login answer gives players the user's limit and the streams they watch. HLS segments are not counted: a live HLS channel is many short requests.
    • Filters (group-regex, channel-regex, group-exclude-regex, channel-exclude-regex) apply to that user on top of the proxy's own, with the same rules (see Filtering channels): a live channel left out does not play, even asked by its id. Movies and series are not filtered.
    • Names must not be hls, logo, live, movie, series, timeshift or play: they are the first elements of the proxy's own addresses.
    • --max-connections (MAX_CONNECTIONS) sets the limit of the one user of --user and --password, without a file.

    Each player with its own provider account

    When everyone in the house has their own account with the same provider, the proxy does not need one: with --xtream-passthrough, players log in to the proxy with their provider account, and the proxy only gives the provider's address.

    docker run -d --name iptv-proxy -p 8080:8080 \
      -e XTREAM_BASE_URL="http://provider.example:8080" \
      -e XTREAM_PASSTHROUGH=true \
      -e PROXY_HOSTNAME=192.168.1.10 \
      pierro777/iptv-proxy:latest
    
    • Players are set up with the proxy's address and their own provider user and password. Their playlists, guide and stream addresses name the proxy and hold their own account; the provider's address is left out.
    • The proxy asks the provider whether it accepts an account at the first request, then again every 10 minutes. An account refused by the provider gets a 401. An account it accepted keeps playing while the provider cannot answer, with the last good answers.
    • Each account is held to the streams the provider allows it (max_connections of its login answer), with the same rule as several users: zapping on the same device works, another device waits. --max-connections sets another limit for every account.
    • The proxy's filters apply to every account. Live streams are shared between the players of the same account only.
    • There are no proxy users: --xtream-user, --xtream-password, users in the configuration file, --m3u-url and the HDHomeRun tuner do not go with it, and the proxy says so at startup. --user and --password are not used.

    Several sources

    A second account, at the same provider or another, can back up the first: list it under sources in the configuration file. The proxy then serves both accounts as one catalogue.

    # /config/iptv-proxy.yaml
    sources:
      - name: backup
        xtream-base-url: http://other-provider.example:8080
        xtream-user: other_user
        xtream-password: other_password
        max-connections: 1   # optional: the account's own limit by default
    

    The Xtream options (XTREAM_BASE_URL, XTREAM_USER, XTREAM_PASSWORD) stay the first source; without them, the first of sources is.

    • Ids. The first source keeps its ids: the players set up before see the same channels under the same numbers. The second source's ids are shown as 100000000 + id, the third's as 200000000 + id, and so on.
    • Categories of the same name are one, with the first source's id.
    • A live channel several sources have is shown once, from the first of them. Channels are recognised by their guide id (epg_channel_id): a channel without one is shown from each source.
    • Failover. When a live channel fails at its source (an error, an HTTP error), the proxy opens it at the next source that has it. A source already holding as many streams as its account allows is passed over: its channels play from another source while its connections are busy.
    • Movies and series of every source are listed, each from its own source.
    • The guide is the first source's, plus the channels only the other sources have. The playlist (get.php, iptv.m3u) is built from the merged catalogue, as with --xtream-api-get.
    • A source that does not answer is left out of the lists for that request; the last complete answer is served instead when there is one.
    • Sources are Xtream accounts: they do not go with --m3u-url or with --xtream-passthrough.

    Plex and other media servers

    With --hdhomerun-port, the proxy also answers as an HDHomeRun network tuner on that port. Plex, Emby, Jellyfin and Channels DVR then see your live channels as a TV tuner, with a guide matched to them, and can record.

    docker run -d --name iptv-proxy -p 8080:8080 -p 192.168.1.10:5004:5004 \
      -e XTREAM_BASE_URL="http://provider.example:8080" \
      -e XTREAM_USER=xtream_user \
      -e XTREAM_PASSWORD=xtream_password \
      -e PROXY_HOSTNAME=192.168.1.10 \
      -e PROXY_USER=family \
      -e PROXY_PASSWORD=choose-a-password \
      -e HDHOMERUN_PORT=5004 \
      -e HDHOMERUN_GROUP_REGEX='^(FR|UK) ' \
      pierro777/iptv-proxy:latest
    

    In Plex: Settings > Live TV & DVR > Set up Plex DVR, then "Don't see your HDHomeRun device? Enter its network address manually": 192.168.1.10:5004. When Plex asks for the guide, choose the XMLTV option and give http://192.168.1.10:5004/guide.xml.

    When Plex runs in Docker on the same machine, put both containers on one network and give Plex iptv-proxy:5004 and http://iptv-proxy:5004/guide.xml instead: the tuner's port then needs no publishing at all.

    • A tuner has no login: anyone who reaches its port can watch. Keep it on your local network: do not publish it on the Internet or behind your reverse proxy. In exchange, nothing the tuner answers holds your proxy's or your provider's credentials.
    • The tuner serves the live channels the filters keep, and only those. Use them: a media server is not made for 10,000 channels. Besides the proxy's filters, the tuner has its own (--hdhomerun-group-regex, --hdhomerun-channel-regex, --hdhomerun-group-exclude-regex, --hdhomerun-channel-exclude-regex), applied after them: your players keep every channel while the media server gets a few groups.
    • Plex takes no more than about 400 channels with a guide for a tuner: past that, adding the DVR fails at its last step ("try again later"). The tuner helps: a channel the provider lists several times with the same guide id (another quality, a backup) is one tuner channel, the first of the list; the others are its fallbacks, played when it fails. If Plex still refuses, narrow the tuner's filters.
    • With an Xtream account, a channel's number is its id at the provider: it does not change when the provider reorders its list, so recordings stay on their channel. With an M3U playlist, it is the track's tvg-chno, else its place in the playlist.
    • The guide at /guide.xml names each channel by its tuner number, which is how media servers match a guide to channels.
    • Channel logos in Plex. Plex's apps are served over HTTPS and refuse a logo at a plain http:// address; many providers have only those. When the proxy's own address is HTTPS (--https, see Behind a reverse proxy), the tuner's guide gives such logos through that address, where the Plex apps load them and the proxy fetches them. With --proxy-logos, all of them come through it.
    • The tuner announces as many tuners as your Xtream account allows connections, or, with several sources, as their accounts allow together (--hdhomerun-tuners to change it): a media server never opens more streams than that.
    • Media servers expect MPEG-TS. With an Xtream account, the tuner asks the provider for MPEG-TS. A channel the provider has only as HLS (an M3U playlist of .m3u8 addresses, or an account whose allowed_output_formats has no ts) plays when ffmpeg is at hand: take an -ffmpeg image (see Docker images), or have ffmpeg in the PATH of the binary. ffmpeg then reads the HLS stream and writes it as MPEG-TS, without encoding it again; viewers of a channel share one ffmpeg. Without ffmpeg, such a channel is passed on as a playlist, which media servers do not play.
    • Plex finds a tuner by its address, entered by hand: automatic discovery on the network is not supported.

    Status page

    With --status-password (STATUS_PASSWORD), the proxy serves a read-only page at http://host:port/status. It asks for that password; any user name does.

    It shows:

    • the streams being watched: by whom, from which address, for how long;
    • the users and their limits;
    • how many live streams are open at the provider, shared between their viewers;
    • the sources and the streams each holds, when there are several;
    • the HDHomeRun tuner's channels;
    • the answers kept for when the provider fails.

    The page refreshes itself every 10 seconds; /status.json gives the same in JSON. It changes nothing, and shows no password: stream addresses are masked, sources are named by their host. iptv-proxy --version prints the version.

    When the provider fails

    Providers fail now and then: an error for a minute, a maintenance page, a guide that will not generate. The proxy keeps the last answer the provider gave in full for the lists players load (the login, categories, channels, movies, series, details, playlists and the guide), compressed in memory. When the provider fails, players get that answer instead of an error, and the log says so. Streams themselves are not kept: when the provider is down, nothing plays.

    A refusal (wrong account, expired subscription) is passed on: it says something you need to know.

    Filtering channels

    Four options keep the live channels you want and leave out the others. Each takes a regular expression matched against a channel's group (group-title in a playlist, the category in the Xtream API) or its name:

    • --group-regex, --channel-regex: keep only what matches.
    • --group-exclude-regex, --channel-exclude-regex: leave out what matches, even if it was kept by the first two.

    A channel is kept when it passes all of them. The expressions are case sensitive; start one with (?i) to ignore case.

      -e GROUP_REGEX='^(FR|UK) ' \
      -e GROUP_EXCLUDE_REGEX='(?i)adult|xxx' \
      -e CHANNEL_EXCLUDE_REGEX='(?i)backup|test' \
    

    The filters apply to:

    • the M3U playlist (/iptv.m3u) and the Xtream playlists (/get.php, /apiget): a channel left out is not in it, and in M3U mode it has no address on the proxy;
    • the live categories and channels of the Xtream API;
    • the guide (/xmltv.php): only the channels kept, and their programmes, are in it. A guide of thousands of channels shrinks to the ones you watch.

    Movies and series are not filtered. In a get.php playlist that holds movies, the same rules apply to them, by their group and name.

    The filters also hold for streams: a live channel left out does not play, even asked by its id, and the provider is not asked for it. A channel the provider just added plays as soon as it passes the filters: when a stream asks for an id the list does not have, the list is read again (at most once a minute).

    Live streams: one connection to the provider

    An IPTV account allows a few connections at a time, often a single one. When several players watch the same live channel, the proxy opens one connection to the provider and shares it: the first player opens the stream, the next ones join it where it is, and the connection is closed when the last one leaves.

    If the provider drops the stream, or leaves it silent for 20 seconds, the proxy opens it again while someone is watching, so the player does not have to reconnect. After a few attempts that fail, the stream ends.

    Movies, series and catch-up are files: each player reads its own, from where it wants. --no-stream-sharing gives every player its own connection for live streams too.

    Behind a reverse proxy, with HTTPS

    The proxy listens in plain HTTP. To serve it on your own domain with HTTPS, put a reverse proxy (Traefik, Caddy, nginx...) in front, and tell iptv-proxy which addresses to give out:

        environment:
          PORT: 8080             # where iptv-proxy listens
          PROXY_HOSTNAME: iptv.example.com
          ADVERTISED_PORT: 443   # the port your players use
          HTTPS: 1               # addresses given out start with https://
    

    Your players then use https://iptv.example.com:443/iptv.m3u?username=...&password=....

    The proxy's user and password travel in every address, so use HTTPS as soon as the proxy is reachable from the Internet.

    A complete example with Traefik and Let's Encrypt is in the traefik folder. From the root of the repository:

    cp -r ./traefik/* .
    mkdir -p Traefik/etc/traefik Traefik/log
    

    Then replace iptv.proxyexample.xyz with your domain in docker-compose.yml, set your e-mail address in Traefik/traefik.yaml, and start it:

    docker compose up -d
    

    What it does not do

    • It does not provide channels or streams: you need a playlist or an account from a provider.
    • It does not edit the catalogue: no channel editor, no renaming, no guide mapping. Players get what the provider sends.
    • It does not transcode, record or cache streams: they are passed on as they come. The one exception is the tuner's HLS channels, which ffmpeg turns into MPEG-TS, without encoding them again.
    • It has no web interface to change anything: the status page only shows.

    Roadmap

    Nothing is planned beyond fixes: what comes next depends on what users ask for. Open an issue for a bug or an idea, or a discussion for a question.

    See the changelog for what each release brought.

    Contributing

    Bug reports, ideas and pull requests are welcome: see CONTRIBUTING.md. Questions go to the discussions, vulnerabilities to SECURITY.md.

    License

    GPL-3.0.

    Built with cobra and gin.

    If the project is useful to you, you can buy me a beer:

    paypal