From 87289eaaa1ab2de63710851f00038f1b319ce596 Mon Sep 17 00:00:00 2001 From: 4gray Date: Fri, 11 Sep 2026 21:31:03 +0200 Subject: [PATCH] feat(website): add the IPTVnator vs VLC comparison MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit First comparison page that names other software. It answers the question every "IPTV on a computer" guide raises — VLC opens the playlist, so why a dedicated player — with three tables (what each can connect to, what a playlist file cannot carry, and the practical differences) and eight FAQ entries, then ends where the honest answer is: IPTVnator hands VLC the stream, with the playlist's headers and the resume position, and reads the position back. Naming another project brings rules, now recorded in the registry doc comment and a new README section: a dated ThirdPartyNote stating the other project is independent and endorses nothing, only stable publicly documented facts, no claim of a missing feature that was not checked, and no logos, brand styling or links to the other project. The compare test enforces the note and the date for every page in its NAMES_THIRD_PARTY_SOFTWARE set. Co-Authored-By: Claude Opus 5 --- CLAUDE.md | 2 +- apps/website/README.md | 30 ++- apps/website/public/llms.txt | 1 + .../components/compare/ThirdPartyNote.astro | 29 +++ apps/website/src/lib/comparisons.ts | 25 ++- .../src/pages/compare/iptvnator-vs-vlc.astro | 175 ++++++++++++++++++ tools/testing/website-compare-pages.test.mjs | 18 +- 7 files changed, 266 insertions(+), 14 deletions(-) create mode 100644 apps/website/src/components/compare/ThirdPartyNote.astro create mode 100644 apps/website/src/pages/compare/iptvnator-vs-vlc.astro diff --git a/CLAUDE.md b/CLAUDE.md index 808011503..5b745a627 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -366,7 +366,7 @@ This is an Nx monorepo with the following structure: - **apps/electron-backend-e2e** - Playwright E2E tests against the Electron app - **apps/stalker-mock-server** - Mock Stalker/Ministra portal for dev and E2E - **apps/xtream-mock-server** - Mock Xtream Codes API for dev and E2E -- **apps/website** - Astro + Tailwind landing page, blog (guides carry `faq:` frontmatter → FAQPage JSON-LD and open with `src/components/blog/ContentDisclaimer.astro`, whose `offline` variant is mandatory for posts about downloads or recordings; tags are a closed vocabulary in `src/lib/blog-tags.ts` enforced by the collection schema, each with a `/blog/tag//` hub), per-OS download landing pages (`/download/`, `/download/{windows,macos,linux}/`) plus the Docker page (`/download/docker/`) feature landing pages (`/features/`, registry in `src/lib/features.ts`) and comparison pages (`/compare/`, registry in `src/lib/comparisons.ts`, comparing IPTVnator's own options rather than other products); direct asset links are resolved at build time from the GitHub Releases API with a `package.json` fallback (`src/lib/downloads.ts`, see `apps/website/README.md`) +- **apps/website** - Astro + Tailwind landing page, blog (guides carry `faq:` frontmatter → FAQPage JSON-LD and open with `src/components/blog/ContentDisclaimer.astro`, whose `offline` variant is mandatory for posts about downloads or recordings; tags are a closed vocabulary in `src/lib/blog-tags.ts` enforced by the collection schema, each with a `/blog/tag//` hub), per-OS download landing pages (`/download/`, `/download/{windows,macos,linux}/`) plus the Docker page (`/download/docker/`) feature landing pages (`/features/`, registry in `src/lib/features.ts`) and comparison pages (`/compare/`, registry in `src/lib/comparisons.ts`; most compare IPTVnator's own options, and a page that names other software must carry a dated `ThirdPartyNote` — see "Pages that name other software" in `apps/website/README.md`); direct asset links are resolved at build time from the GitHub Releases API with a `package.json` fallback (`src/lib/downloads.ts`, see `apps/website/README.md`) - **libs/** - Shared libraries: - **epg/data-access** - EPG services, runtime bridge, program normalization - **m3u-state** - NgRx state management for M3U playlists diff --git a/apps/website/README.md b/apps/website/README.md index 1470427fd..e3be3474f 100644 --- a/apps/website/README.md +++ b/apps/website/README.md @@ -166,10 +166,10 @@ screenshots that show real channel names. ## Comparison Pages `/compare/` plus one page per decision the app asks users to make -(`m3u-vs-xtream-vs-stalker`, `playback-engines`, `desktop-vs-browser`) live in -`apps/website/src/pages/compare/`. They compare IPTVnator's own options against -each other, never other products, so every claim is checkable against this -repository; the registry is `src/lib/comparisons.ts`. +(`m3u-vs-xtream-vs-stalker`, `playback-engines`, `desktop-vs-browser`, +`iptvnator-vs-vlc`) live in `apps/website/src/pages/compare/`; the registry is +`src/lib/comparisons.ts`. Most of them compare IPTVnator's own options against +each other, so every claim is checkable against this repository. Each page opens with a one-paragraph verdict (`CompareHero`), carries at least one `ComparisonTable` (cells are `true`, `false` or a qualifying string) and @@ -178,6 +178,22 @@ emits `WebPage` / `FAQPage` / `BreadcrumbList` JSON-LD from these pages are guidance rather than a product listing, and `tools/testing/website-compare-pages.test.mjs` asserts that. -Naming a competitor on these pages is a product decision, not a technical one. -Phase 3 of `.plans/2026-09-03-marketing-landing-pages.md` covers that and is -still open. +### Pages that name other software + +Naming another project is a product decision the maintainer makes, not a +technical one. Where it has been made, the page follows stricter rules, and +`website-compare-pages.test.mjs` enforces the first two through its +`NAMES_THIRD_PARTY_SOFTWARE` set: + +- a dated `ThirdPartyNote` (`src/components/compare/ThirdPartyNote.astro`) + stating that the other project is independent and endorses nothing, +- claims carrying the month they were checked, because another project can add + a feature the day after the page is written, +- only stable, publicly documented platform and feature facts — never a claim + that something is missing unless it was actually checked, +- no third-party logos, brand styling, download links or affiliate links. + +Prefer describing what IPTVnator does and letting the difference speak. The +comparison that works best is the one where the other project is a partner +rather than a rival: `iptvnator-vs-vlc` ends on the external-player +integration, because that is the honest answer. diff --git a/apps/website/public/llms.txt b/apps/website/public/llms.txt index f623bd8fe..95dc2c465 100644 --- a/apps/website/public/llms.txt +++ b/apps/website/public/llms.txt @@ -33,6 +33,7 @@ - Compare, M3U vs Xtream Codes vs Stalker portal: https://4gray.github.io/iptvnator/compare/m3u-vs-xtream-vs-stalker/ - Compare, playback engines (built-in vs MPV/VLC vs embedded MPV): https://4gray.github.io/iptvnator/compare/playback-engines/ - Compare, desktop app vs self-hosted browser version: https://4gray.github.io/iptvnator/compare/desktop-vs-browser/ +- Compare, IPTVnator vs VLC (when a general media player is enough): https://4gray.github.io/iptvnator/compare/iptvnator-vs-vlc/ - Release Notes (v0.18): https://4gray.github.io/iptvnator/blog/v0-18-release-notes/ - Troubleshooting (macOS app damaged fix): https://4gray.github.io/iptvnator/blog/macos-arm-app-damaged-fix/ - Source Code: https://github.com/4gray/iptvnator diff --git a/apps/website/src/components/compare/ThirdPartyNote.astro b/apps/website/src/components/compare/ThirdPartyNote.astro new file mode 100644 index 000000000..3a860da9f --- /dev/null +++ b/apps/website/src/components/compare/ThirdPartyNote.astro @@ -0,0 +1,29 @@ +--- +/** + * Shown on every comparison page that names software other than IPTVnator. + * Two jobs: state that there is no affiliation, and date the claims, because + * another project can add a feature the day after this page is written. + */ +interface Props { + /** Names as their own projects spell them. */ + names: string[]; + /** Month and year the claims on the page were checked. */ + checked: string; +} + +const { names, checked } = Astro.props; +const many = names.length > 1; +const list = many ? `${names.slice(0, -1).join(', ')} and ${names.at(-1)}` : names[0]; +const copy = many + ? { verb: 'are independent projects', pronoun: 'them', possessive: 'Their names are', doing: 'they do' } + : { verb: 'is an independent project', pronoun: 'it', possessive: 'Its name is', doing: 'it does' }; +--- + +
+

+ Not affiliated.{' '} + {list} {copy.verb} with no connection to IPTVnator, and nothing here is endorsed by{' '} + {copy.pronoun}. {copy.possessive} used only to describe what {copy.doing}. Claims on this + page were checked in {checked}; software changes, so verify anything that decides your setup. +

+
diff --git a/apps/website/src/lib/comparisons.ts b/apps/website/src/lib/comparisons.ts index b2ceb092d..a71c9d9d1 100644 --- a/apps/website/src/lib/comparisons.ts +++ b/apps/website/src/lib/comparisons.ts @@ -1,14 +1,20 @@ /** - * Registry of the comparison pages under `/compare/`. These pages compare - * IPTVnator's own options against each other — connection types, playback - * engines, editions — rather than naming other products, so every claim on - * them can be checked against this repository. + * Registry of the comparison pages under `/compare/`. + * + * Most of them compare IPTVnator's own options against each other — connection + * types, playback engines, editions — so every claim can be checked against + * this repository. A page that names other software follows stricter rules: + * only platform and feature facts that are stable and publicly documented, a + * dated `ThirdPartyNote`, no logos or brand styling, no download links to the + * other project, and no claim that a feature is missing unless it was checked. + * Prefer describing what IPTVnator does and letting the difference speak. */ export type ComparisonSlug = | 'm3u-vs-xtream-vs-stalker' | 'playback-engines' - | 'desktop-vs-browser'; + | 'desktop-vs-browser' + | 'iptvnator-vs-vlc'; export interface ComparisonEntry { slug: ComparisonSlug; @@ -52,6 +58,15 @@ export const COMPARISONS: readonly ComparisonEntry[] = [ href: '/iptvnator/compare/desktop-vs-browser/', icon: 'M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z', }, + { + slug: 'iptvnator-vs-vlc', + label: 'IPTVnator vs VLC', + eyebrow: 'Compare · Other players', + question: + 'VLC opens an M3U playlist too. When is that enough, and what does a player built around IPTV add on top?', + href: '/iptvnator/compare/iptvnator-vs-vlc/', + icon: 'M7.5 21L3 16.5m0 0L7.5 12M3 16.5h13.5m0-13.5L21 7.5m0 0L16.5 12M21 7.5H7.5', + }, ]; export const COMPARE_HUB_HREF = '/iptvnator/compare/'; diff --git a/apps/website/src/pages/compare/iptvnator-vs-vlc.astro b/apps/website/src/pages/compare/iptvnator-vs-vlc.astro new file mode 100644 index 000000000..2294b7d83 --- /dev/null +++ b/apps/website/src/pages/compare/iptvnator-vs-vlc.astro @@ -0,0 +1,175 @@ +--- +import BaseLayout from '../../layouts/BaseLayout.astro'; +import FaqAccordion from '../../components/blog/FaqAccordion.astro'; +import DownloadSection from '../../components/download/DownloadSection.astro'; +import FeatureList from '../../components/download/FeatureList.astro'; +import OfficialSourcesNote from '../../components/download/OfficialSourcesNote.astro'; +import RelatedPosts from '../../components/download/RelatedPosts.astro'; +import CompareHero from '../../components/compare/CompareHero.astro'; +import ComparisonTable from '../../components/compare/ComparisonTable.astro'; +import CompareSwitcher from '../../components/compare/CompareSwitcher.astro'; +import ThirdPartyNote from '../../components/compare/ThirdPartyNote.astro'; +import type { FaqEntry } from '../../lib/download-schema'; +import { buildComparisonPageSchema } from '../../lib/comparison-schema'; +import { comparisonBySlug } from '../../lib/comparisons'; +import { absoluteUrl } from '../../lib/site'; + +const comparison = comparisonBySlug('iptvnator-vs-vlc'); +const pageUrl = absoluteUrl('/compare/iptvnator-vs-vlc/'); +const title = 'IPTVnator vs VLC – When a Media Player Is Enough for IPTV'; +const description = + 'VLC opens an M3U playlist and plays almost anything inside it. IPTVnator adds what a playlist file cannot carry: a searchable catalog, the programme guide, provider accounts, favorites and catch-up — and it can hand the stream to VLC when a codec needs it.'; + +const columns = ['VLC', 'IPTVnator']; + +const sourceRows = [ + { label: 'Opens an M3U or M3U8 playlist', cells: [true, true] }, + { label: 'Opens a single stream URL', cells: [true, true] }, + { label: 'Xtream Codes account (server, username, password)', cells: [false, true], note: 'Brings live TV, movies and series as one catalog instead of a flat list.' }, + { label: 'Stalker or Ministra portal (MAC address)', cells: [false, true] }, + { label: 'XMLTV programme guide', cells: [false, true] }, + { label: 'Several sources side by side', cells: ['One playlist at a time', true] }, +]; + +const libraryRows = [ + { label: 'Channel list with groups and search', cells: ['Flat playlist', true] }, + { label: 'Movies and series with posters and descriptions', cells: [false, true] }, + { label: 'Favorites across sources, recently watched', cells: ['General playlist and media library', true] }, + { label: 'Programme now and next, timeline, full guide', cells: [false, true] }, + { label: 'Catch-up from the guide', cells: [false, true], note: 'Where the provider offers an archive.' }, + { label: 'Resume where you stopped', cells: ['Per file in its own history', 'Per stream, across sources'] }, + { label: 'Offline downloads', cells: [false, 'Desktop app'] }, + { label: 'Phone remote control', cells: [false, 'Desktop app'] }, +]; + +const practicalRows = [ + { label: 'Plays unusual codecs and containers', cells: [true, 'Built-in players cover the common ones; MPV, VLC or the bundled mpv engine handle the rest'] }, + { label: 'Sends the provider’s User-Agent and Referer', cells: ['Set by hand per stream', 'Stored with the playlist and sent automatically'] }, + { label: 'Where it runs', cells: ['Windows, macOS, Linux, Android, iOS', 'Windows, macOS, Linux; browser version you host yourself'] }, + { label: 'Price', cells: ['Free, open source', 'Free, open source'] }, + { label: 'Account needed', cells: [false, false] }, + { label: 'Supplies channels or playlists', cells: [false, false], note: 'Neither is a content source. You bring a subscription you already have.' }, +]; + +const together = [ + { + title: 'IPTVnator can play the stream in VLC', + description: + 'Choose VLC under Settings › Playback and IPTVnator launches your own copy for playback. The catalog, the guide, favorites and the phone remote stay in IPTVnator; only the video moves to a VLC window.', + }, + { + title: 'The handover carries what VLC cannot guess', + description: + 'IPTVnator passes the playlist’s User-Agent, Referer and any custom headers with the stream, so a provider that checks them still answers. It also passes your resume position and the title for the window.', + }, + { + title: 'Progress comes back', + description: + 'IPTVnator asks VLC where it is through VLC’s own remote-control interface, so a movie you stopped in VLC resumes from the same place in the library.', + }, + { + title: 'A codec problem stops being a dead end', + description: + 'A stream that stutters or refuses in a browser engine usually plays immediately in VLC. Switching one stream does not change anything else about your setup.', + }, +]; + +const faq: FaqEntry[] = [ + { + q: 'Can VLC play an IPTV M3U playlist?', + a: 'Yes. Open the .m3u file or paste its URL into Media › Open Network Stream and VLC lists the entries in its playlist, then plays them. That is genuinely enough for a handful of channels.', + }, + { + q: 'So why use a dedicated IPTV player at all?', + a: 'Because a playlist file carries only names and stream addresses. It has no programme guide, no movie or series catalog, no account status and no catch-up. With a few hundred or a few thousand entries, a flat list also stops being something you can browse. IPTVnator is built around those things; VLC is built around playing a file.', + }, + { + q: 'Does VLC support Xtream Codes logins or Stalker portals?', + a: 'No. Those are provider APIs, not stream files: something has to log in, ask for categories, and build a catalog out of the answers. VLC opens files and URLs. If your provider gave you a server address, a username and a password, IPTVnator can use them directly.', + }, + { + q: 'Can VLC show the programme guide?', + a: 'Not for IPTV. XMLTV guide data is a separate file that has to be downloaded, parsed and matched to channels; VLC has no such feature. IPTVnator does this in the background and shows what is on now, a timeline and a full guide.', + }, + { + q: 'Which one plays more formats?', + a: 'VLC, almost certainly — decoding is what it is for. IPTVnator does not compete there: it lets you send any stream to VLC or MPV on your machine, or to the mpv engine that ships with the desktop app, so the coverage becomes theirs.', + }, + { + q: 'Do I have to choose one?', + a: 'No, and most people should not. Keeping VLC installed gives IPTVnator somewhere to send a stubborn stream. Neither installation affects the other.', + }, + { + q: 'What about watching on a phone?', + a: 'VLC has mobile apps and IPTVnator does not. The browser version of IPTVnator, which you host yourself, opens on a phone browser, and the desktop app can be controlled from a phone with its web remote — but for playback on the phone itself, a mobile player is the right tool.', + }, + { + q: 'Is either one a source of channels?', + a: 'No. Both are players. Neither provides, hosts or sells playlists, channels or subscriptions; you bring a source you already have the right to use.', + }, +]; + +const jsonLd = buildComparisonPageSchema({ comparison, pageUrl, description, faq }); +--- + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ + + +
+ +
+
+ + +
diff --git a/tools/testing/website-compare-pages.test.mjs b/tools/testing/website-compare-pages.test.mjs index 8937c8a01..13c2a1f1b 100644 --- a/tools/testing/website-compare-pages.test.mjs +++ b/tools/testing/website-compare-pages.test.mjs @@ -10,7 +10,14 @@ import assert from 'node:assert/strict'; const distRoot = new URL('../../dist/apps/website/', import.meta.url); const SITE = 'https://4gray.github.io/iptvnator'; -const COMPARISONS = ['m3u-vs-xtream-vs-stalker', 'playback-engines', 'desktop-vs-browser']; +const COMPARISONS = [ + 'm3u-vs-xtream-vs-stalker', + 'playback-engines', + 'desktop-vs-browser', + 'iptvnator-vs-vlc', +]; +/** Pages that name other software must say they are not affiliated with it, and date their claims. */ +const NAMES_THIRD_PARTY_SOFTWARE = new Set(['iptvnator-vs-vlc']); const readDist = (relativePath) => readFile(new URL(relativePath, distRoot), 'utf8'); @@ -40,6 +47,15 @@ for (const slug of COMPARISONS) { !schema.some((entry) => entry['@type'] === 'SoftwareApplication'), 'A comparison page is guidance, not a product listing.' ); + + if (NAMES_THIRD_PARTY_SOFTWARE.has(slug)) { + assert.match(html, /Not affiliated\./, 'A page naming other software must carry the disclaimer.'); + assert.match( + html, + /Claims on this page were checked in \w+ \d{4}/, + 'A page naming other software must date its claims.' + ); + } }); test(`comparison page ${slug}: links to the hub and the other comparisons`, async () => {