4grayandClaude Opus 5.5 d57da4dfec perf(web): take the Material snack-bar off the startup path (#1939)
* perf(web): take the Material snack-bar off the startup path

The seven services and the app shell created with the app injected
MatSnackBar, and snack-bar.mjs statically imports Material's button, the
icon button chunk, ripple and the CDK a11y module, so about 110 KB sat in
the initial bundle although a toast shows only after an event.

A root LazySnackBar (@iptvnator/services/lazy-snack-bar) resolves the
snack-bar from a dynamic import the way LazyMatDialog does, retrying after
a failed chunk load, and opens text toasts with MatSnackBar.open's
arguments; a failed load is logged without the message. The eager
importers open their toasts through it with unchanged messages, actions,
durations and panel classes; lazy code keeps injecting MatSnackBar. The
startup status defers its Retry button, the other eager importer of the
button chunk, with a native button as the error fallback.

Local production build: renderer.initialBytes 1,349,048 -> 1,236,918
(-112,130, -8.3%); no Material module is in the initial bundle any more.
The CDK overlay, scrolling and portal chunks (about 78 KB) stay through
the FullscreenOverlayContainer provider in app.config.ts.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(web): keep a native Retry while the startup button chunk loads

Greptile (local round 1): the failure screen had no Retry button until
the deferred Material button chunk arrived. One native button, drawn in
the flat button's shape with theme colours, is now the placeholder and
the error view of the defer block, so a retry is possible while the
chunk downloads and after a failed load. Baseline and docs follow the
re-measured value, 1,237,440 bytes.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* docs(perf): point the initialBytes evidence at the opened pull request

The baseline and the performance doc named the predicted number; the
pull request opened as #1939.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* test(web): cover the toast actions behind the lazy snack-bar

Greptile (PR round 1): the Refresh and Trust-host actions now run after
LazySnackBar.open resolves, and no test exercised them. The PWA update
toast test holds the toast promise, then resolves it and picks Refresh;
the page reload is a field the spec substitutes, as jsdom's location
cannot be spied on. The Electron test does the same for the TLS trust
toast: the confirmation opens only once the toast is up, approving it
records the host and retries the fetch.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* test(web): type the stubbed toast ref as Observable<undefined>

CI's spec typecheck rejected `typeof EMPTY` (Observable<never>) for a
stub that returns `of(undefined)`.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* perf(web): lower the initialBytes baseline to the merged measurement

Merged with #1938 the branch measures 1,231,455 bytes on a local
production build (the radio play control's fab tokens left the
stylesheet); the baseline follows it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* perf(web): lower the initialBytes baseline to the zoneless merged measurement

Merged with #1929, #1936 and #1938 the branch measures 1,178,109 bytes
on a local production build; the baseline follows it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* perf(web): re-measure initialBytes after merging #1903 and #1942

Merged with #1903 (self-hosted server storage, which raised master's
baseline by 7,245 bytes on purpose) and #1942 the branch measures
1,187,983 bytes on a local production build, below master's 1,285,523;
no Material module is in the initial bundle. The new favorites effect
spec provides the LazySnackBar stub the merged effect injects.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* perf(xtream): open favorites toasts through LazySnackBar

#1934 gave the Xtream store's favorites feature a static MatSnackBar
injection. The store exists from startup, so that one import put
Material's snack-bar, button, icon-button and ripple chunks (about
95 KB) back on the renderer's initial path. The feature now opens its
two toasts through LazySnackBar with the same messages and durations.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(perf): count statically imported chunks in renderer.initialBytes

Angular's builder emits at most ten modulepreload hints. Once the
startup graph passed ten chunks, the rest were fetched through
main.js's own imports but never counted, so the counter stopped growing
and an eager MatSnackBar import on master went unnoticed. The script now
follows the static import graph of every counted script (dynamic
import() targets stay lazy; a missing imported file is an error), which
matches the bundler's metafile closure exactly: 1,196,205 bytes for this
branch, where the old counter read 1,098,535.

The baseline follows the new measurement and its note now explains the
current limit (Greptile).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(perf): parse startup imports and count them per request URL

Codex (PR round): the regexes also matched import-shaped text in strings,
templates and comments, which could count a lazy chunk or fail on a file
that does not exist. Static declarations are now read from TypeScript's
syntax tree (top-level import/export-from statements only). Imports are
also deduplicated by request URL, like the index.html references, so a
preload of chunk.js?v=1 no longer hides an import of ./chunk.js. The
real build still measures 1,196,205 bytes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-15 22:59:31 +02:00
2019-10-06 17:34:49 +02:00
2023-01-23 22:42:23 +01:00
2020-09-27 20:33:37 +02:00
2025-10-07 18:02:13 +02:00
2020-09-27 20:33:37 +02:00
2020-09-27 20:33:37 +02:00

IPTVnator - IPTV Player Application

IPTVnator icon

Release CI status Releases Codecov Telegram Bluesky

🌐 Website | Telegram channel for discussions | Buy me a coffee | GitHub Sponsors

IPTVnator is a video player application that provides support for IPTV playlist playback (m3u, m3u8). The application allows users to import playlists using remote URLs or by uploading files from the local file system. Additionally, it supports EPG information in XMLTV format which can be provided via URL.

The application is a cross-platform, open-source project built with Electron and Angular.

⚠️ Note: IPTVnator does not provide any playlists or other digital content. The channels and pictures in the screenshots are for demonstration purposes only.

Important

Official sources only. IPTVnator is a free, open-source player — it never sells IPTV subscriptions, channels, or playlists. Websites offering "IPTVnator subscriptions/channels/premium/activated" builds are not affiliated with this project. Get the app only from the official website or GitHub Releases. See Beware of unofficial IPTVnator websites and IPTV services for details.

IPTVnator: Channels list, player and epg list

Features

Playlists & sources

  • M3U / M3U8 playlists from local files or remote URLs 📂, with automatic updates on startup
  • Xtream Codes (XC) and Stalker / Ministra (STB) portal support
  • Custom "User-Agent" header per playlist

Playback

  • Built-in HTML5 player (HLS.js or Video.js) with a resizable, resumable inline view
  • Optional unified IPTVnator controls for HTML5, Video.js, and ArtPlayer, enabled in Settings → Playback (experimental)
  • External players — MPV, VLC, and IINA on macOS (mpv.app / VLC.app bundle paths supported) (desktop)
  • Embedded MPV — native mpv rendered inside the app window on macOS, Windows & Linux 🖥️ (experimental · desktop)
  • Dedicated radio player for radio="true" streams 📻

Live TV & EPG

  • EPG / XMLTV TV guide with a live timeline ribbon and multi-channel grid (desktop)
  • TV archive / catch-up / timeshift (desktop)
  • Group-based channel list, channel-number selection, and search 🔍

Movies & series (VOD)

  • Redesigned two-state detail pages (browse ↔ watch) with season tabs and resume positions
  • Download manager for offline movies & episodes ⬇️ (desktop)
  • "Recently added" feeds and category grids with sorting & pagination

Discovery & metadata

  • Global search across live TV, movies, and series (desktop)
  • TMDB enrichment (opt-in, requires your own TMDB API key) — plots, cast & crew, trailers, ratings, artwork, a "Similar" rail, clickable actor pages, and a trending dashboard rail (trending rail: desktop)
  • Dashboard with recently watched & continue-watching

Organization

  • Per-playlist and global favorites, aggregated across all playlists ⭐
  • Recently viewed / watch history
  • Command palette (Ctrl/Cmd+K) that also finds and opens individual settings
  • Settings search from the header search box on the Settings page

Platform

  • Cross-platform desktop (Electron) and installable PWA
  • Desktop auto-updater and mobile remote control (desktop)
  • Docker self-hosting for the PWA + web backend
  • 19 languages (translation files), light & dark themes, and keyboard shortcuts

Keyboard shortcuts

Press ? or Shift+/ in the workspace to open the in-app shortcuts list.

Area Shortcut Action
Global Ctrl/Cmd+K Open command palette
Global Ctrl/Cmd+F Open global search (desktop); on Settings, search settings
Global Ctrl/Cmd+R Open recently viewed in the desktop app
Global Enter in workspace search Submit the search; on Settings, open the best match
Global F11 Toggle app window fullscreen in the desktop app
Navigation Alt+← / Cmd+[ (macOS) Go back, like the top bar's Back and the mouse Back button
Navigation Ctrl/Cmd+B Toggle the live sidebar
Navigation 0-9 Select an M3U channel by number
Playback Space / K Play or pause playback
Playback F Toggle player fullscreen
Playback ArrowLeft / ArrowRight Seek VOD playback by 5 seconds
Playback ArrowUp / ArrowDown Adjust volume by 5%
Playback M Mute audio
Dialogs and lists ArrowUp / ArrowDown Move command palette selection
Dialogs and lists Enter Run the selected command or open a focused item
Dialogs and lists Escape Close dialogs and dismiss overlays

The desktop app can also open at its last size, maximized, or fullscreen on every launch (Settings → General → "Window on startup"), and iptvnator --fullscreen forces a single fullscreen launch for TV or HTPC autostart scripts without changing that setting.

Screenshots:

Dashboard with recently watched content Live channels with inline player and EPG
Dashboard with recently watched content Live channels with inline player and EPG
Add playlist dialog for M3U, Xtream, and Stalker Live category channel list
Add playlist dialog for M3U, Xtream, and Stalker Live category channel list
Global search across live TV, movies, and series Manage visible live categories
Global search across live TV, movies, and series Manage visible live categories
Movie category grid with sorting and pagination Recently added movies and series
Movie category grid with sorting and pagination Recently added movies and series
VOD details with playback and download actions Download manager
VOD details with playback and download actions Download manager
Multi-channel EPG grid External MPV player support
Multi-channel EPG grid External MPV player support
Radio playback with dedicated audio player Light theme
Radio playback with dedicated audio player Light theme
Application settings
Application settings

Note: First version of the application which was developed as a PWA is available in an extra git branch.

Self-hosted PWA

The Docker setup builds the Angular PWA and the monorepo web backend into one image. The backend handles remote M3U parsing plus Xtream and Stalker proxy requests under /api, so a separate 4gray/iptvnator-backend container is not required for the default self-hosted flow.

docker compose -f docker/docker-compose.yml up --build -d

The application is available at http://localhost:4333. See docker/docker-compose.yml for the ready-to-run compose file and docker/README.md for environment variables, reverse proxy notes, PWA limitations, and build details.

The image keeps playlists, favorites and watch progress on the server, so every browser that opens it shares one library. The first browser that opens an updated instance offers to move the playlists it already has.

The self-hosted image runs the browser PWA rather than the Electron desktop app: EPG/XMLTV panels, Embedded MPV, managed MPV/VLC launching, the download manager, and Electron remote-control features are not available there. If browser playback fails, copy the stream URL and open it manually in an external player such as MPV, VLC, or IINA.

Download

Download the latest version of the application for macOS, Windows, and Linux from the release page.

Alternatively, you can install the application using one of the following package managers:

Nightly builds

Every merge into master is published as a prerelease in 4gray/iptvnator-nightly. The desktop app can follow them: Settings → About → Update channel → Nightly offers each new build through the built-in updater (macOS, Windows, and the Linux AppImage). Nightly builds are untested snapshots — they may break, and their database changes are permanent, so switching back to Stable keeps the installed nightly until the next stable release is newer. Back up your playlists first, and mention the version and commit shown in About when reporting a bug from a nightly.

Homebrew

$ brew install iptvnator

Snap

$ sudo snap install iptvnator

Arch

Also available as an Arch PKG, iptvnator-bin, in the AUR (using your favourite AUR-helper, .e.g. yay)

$ yay -S iptvnator-bin

Gentoo

You can install IPTVnator from the gentoo-zh overlay

sudo eselect repository enable gentoo-zh
sudo emerge --sync gentoo-zh
sudo emerge iptvnator-bin

Linux Embedded MPV Support

Embedded MPV on Linux is experimental and currently supports x64 desktop sessions where IPTVnator runs under X11 or Xwayland. Native Wayland embedding is not supported yet. Linux package launchers request X11 with --ozone-platform=x11, so Wayland desktops still need Xwayland available.

The Linux backend starts a system mpv executable with --wid, so mpv must be installed and available on PATH. CI validates the Linux native addon and standard packages on Ubuntu 22.04, with Flatpak packaging built on Ubuntu 24.04. Expected user targets are Ubuntu/Debian .deb, Arch/Manjaro pacman, RPM distributions, and AppImage on x64 systems with X11/Xwayland plus mpv installed. Flatpak and Snap builds remain available, but embedded MPV is not announced as supported there yet because those sandboxed formats do not expose the host mpv executable to the embedded backend by default.

Get it from the Snap Store

Sponsor on GitHub Support on Ko-fi

Troubleshooting

macOS: "App is damaged and can't be opened"

Older unsigned macOS builds may require removing the quarantine flag from the downloaded application:

xattr -c /Applications/IPTVnator.app

Alternatively, if the app is located in a different directory:

xattr -c ~/Downloads/IPTVnator.app

Linux: chrome-sandbox Issues

If you encounter the following error when launching IPTVnator:

The SUID sandbox helper binary was found, but is not configured correctly.
Rather than run without sandboxing I'm aborting now.
You need to make sure that chrome-sandbox is owned by root and has mode 4755.

Solution 1: Fix chrome-sandbox permissions (Recommended for .deb/.rpm installations)

Navigate to the IPTVnator installation directory and run:

sudo chown root:root chrome-sandbox
sudo chmod 4755 chrome-sandbox

Solution 2: Launch with --no-sandbox flag

Edit the desktop launcher file to add the --no-sandbox flag:

  1. Find your desktop file location:

    • Ubuntu/Debian: ~/.local/share/applications/iptvnator.desktop
    • System-wide: /usr/share/applications/iptvnator.desktop
  2. Edit the file and modify the Exec line:

    Exec=iptvnator --no-sandbox %U
    
  3. Save the file and relaunch the application from your application menu.

Alternatively, you can launch IPTVnator from the terminal with the flag:

iptvnator --no-sandbox

GNU/Linux: Wayland startup failure

If IPTVnator exits on GNU/Linux with errors about failing to connect to Wayland or initialize the Ozone platform, force X11/XWayland instead:

iptvnator --ozone-platform=x11

This workaround is mainly for older or problematic Linux graphics stacks. The Snap package already includes this X11 override by default. For AppImage, direct binaries, and other Linux package formats, pass the flag manually when needed.

How to Build and Develop

Requirements:

  • Node.js 22.22.3 or newer within 22.x, or 24.15.0 or newer within 24.x
  • pnpm 10.33.0 (via Corepack)

The repository's .nvmrc pins the Node.js version used by CI. With nvm, run nvm install and nvm use from the repository root to use the same version for local development.

  1. Clone this repository and install project dependencies:

    $ nvm install
    $ nvm use
    $ corepack enable
    $ pnpm install
    
  2. Start the application:

    $ pnpm run serve:backend
    

This will open the Electron app in a separate window, while the Angular dev server will run at http://localhost:4200.

The equivalent Nx command is:

$ nx serve electron-backend

To start Electron with an empty, isolated data directory instead of your normal ~/.iptvnator folder, set IPTVNATOR_E2E_DATA_DIR for that run:

$ rm -rf .tmp/iptvnator-empty && mkdir -p .tmp/iptvnator-empty
$ IPTVNATOR_E2E_DATA_DIR="$PWD/.tmp/iptvnator-empty" pnpm run serve:backend

This redirects the SQLite database, Electron user data, and local config under the given directory. Delete that directory whenever you want a fresh empty state.

If you need startup diagnostics for a white screen or a frozen route, you can also turn on opt-in Electron tracing. These logs are written to the Electron terminal output so they still help when the renderer DevTools never open:

$ IPTVNATOR_TRACE_STARTUP=1 pnpm run serve:backend

Nx equivalent:

$ IPTVNATOR_TRACE_STARTUP=1 nx serve electron-backend

Useful narrower flags:

  • IPTVNATOR_TRACE_IPC=1 logs renderer window.electron.* calls reaching the Electron bridge
  • IPTVNATOR_TRACE_DB=1 logs DB worker requests and request-scoped DB events
  • IPTVNATOR_TRACE_SQL=1 logs SQLite statements in both the main connection and DB worker connection
  • IPTVNATOR_TRACE_WINDOW=1 logs BrowserWindow load, navigation, and unresponsive events
  • IPTVNATOR_TRACE_RENDERER_CONSOLE=1 mirrors renderer console messages into the Electron terminal output

Security-sensitive network compatibility flags are opt-in:

  • IPTVNATOR_ALLOW_PRIVATE_NETWORK_URLS=1 permits strict EPG fetches from playlist metadata (x-tvg-url, url-tvg, or tvg-url) to resolve to localhost, LAN, or other private addresses. Directly configured Xtream/Stalker portals and private playlist servers remain supported without this flag. Prefer the in-app source-scoped “Allow source” action for a trusted EPG URL.
  • IPTVNATOR_ALLOW_INSECURE_TLS=1 disables certificate validation for remote playlist imports and refreshes for the whole Electron process. Prefer the in-app host-scoped trust action for a trusted provider with a self-signed or otherwise invalid certificate.

If the local Nx daemon gets into a bad state before rerunning Electron, reset it:

$ pnpm nx reset

To run only the Angular app without Electron, use:

$ pnpm run serve:frontend

To see how many bytes the built web app puts on the initial load path (the number the CI ratchet guards), build it and run the measurement:

$ pnpm nx build web
$ pnpm run perf:initial-bytes

The contract behind that number is in docs/architecture/performance-journeys.md.

To benchmark the "launch to usable", "open a source" and "start playback" journeys (fresh Electron processes on a seeded profile against the local Xtream mock, exact renderer counters plus wall-clock), run:

$ pnpm run perf:journeys

The journeys, their counters and the summary written under dist/performance/journeys/ are described in docs/architecture/performance-journeys.md. pnpm run perf:compositing measures the renderer's tile memory and composited layers on the dashboard, the detail pages, Live TV and settings (the "compositing budget" section of the same document).

Disclaimer

IPTVnator doesn't provide any playlists or other digital content.

Trademark

The name "IPTVnator" and the IPTVnator logo are unregistered trademarks of the project owner. The MIT license covers the source code only — it does not grant rights to the name or logo. Forks and redistributions (including app-store submissions) must use a different name and their own icon. See TRADEMARK.md for details.

All Contributors

Developer and agent documentation

Start with the task context map to find the authoritative contract and validation for your area. Common agent rules are in AGENTS.md; Claude Code imports that same file. Development and documentation-maintenance conventions live in the agent workflow.

Languages
TypeScript 82.2%
JavaScript 8.6%
HTML 3.1%
SCSS 2.7%
Astro 1.1%
Other 2.1%