If you have ever opened an IPTV player app, typed in a server URL, username and password, and watched thousands of channels and a full TV guide appear a few seconds later, you have used one of the most widely adopted, yet least documented, interfaces in consumer streaming. Behind almost every iptv subscription sit the same three building blocks: a playlist format, a JSON API and an XML programme guide. This article explains how they fit together, with working examples you can use to build or debug a simple player.
The three formats every IPTV player speaks
| Format | What it describes | Typical use |
|---|---|---|
| M3U / M3U8 playlist | A list of channels and their stream URLs | Simple players, quick imports |
| Xtream Codes API | JSON endpoints for accounts, categories, live, VOD and series | Full-featured apps with libraries and catch-up |
| XMLTV | The electronic programme guide (EPG) | The "now and next" grid in the app |
The video itself is usually delivered as HLS (HTTP Live Streaming) or as an MPEG-TS stream over HTTP. The formats above simply tell the player what exists and where to find it.
M3U playlists: the simplest format
An extended M3U file is plain text. Each channel is an #EXTINF line with metadata, followed by the stream URL:
#EXTM3U #EXTINF:-1 tvg-id="news.example" tvg-name="Example News" tvg-logo="https://example.com/logos/news.png" group-title="News",Example News https://example.com/live/demo/demo/1001.m3u8 #EXTINF:-1 tvg-id="sport1.example" tvg-name="Example Sport 1" group-title="Sport",Example Sport 1 https://example.com/live/demo/demo/1002.m3u8
The attributes matter. group-title creates the categories you see in the app, tvg-logo supplies the channel icon, and tvg-id links the channel to its programme guide data. A minimal parser in JavaScript looks like this:
function parseM3U(text) {
const lines = text.split(/\r?\n/);
const channels = [];
for (let i = 0; i < lines.length; i++) {
if (!lines[i].startsWith('#EXTINF')) continue;
const attrs = Object.fromEntries(
[...lines[i].matchAll(/([\w-]+)="([^"]*)"/g)].map((m) => [m[1], m[2]])
);
const name = lines[i].split(',').pop().trim();
const url = (lines[i + 1] || '').trim();
channels.push({ name, url, group: attrs['group-title'], epgId: attrs['tvg-id'], logo: attrs['tvg-logo'] });
}
return channels;
}
M3U is easy to generate and import, but it has limits: it is one large file, it has no structure for films and series, and every change means downloading the whole list again.
The Xtream Codes API: structured data over JSON
Most modern IPTV apps prefer the Xtream Codes style API, because it returns structured JSON that can be loaded in pieces. A player signs in with three values: the server URL, a username and a password. The main endpoint is player_api.php:
Account and server information
GET https://example.com/player_api.php?username=demo&password=demo
Live TV categories and channels
GET https://example.com/player_api.php?username=demo&password=demo&action=get_live_categories
GET https://example.com/player_api.php?username=demo&password=demo&action=get_live_streams
Video on demand and series
GET https://example.com/player_api.php?username=demo&password=demo&action=get_vod_streams
GET https://example.com/player_api.php?username=demo&password=demo&action=get_series
Short programme guide for one channel
GET https://example.com/player_api.php?username=demo&password=demo&action=get_short_epg&stream_id=1001
The first call returns a user_info object, with fields such as the account status, expiry date and the number of allowed connections, and a server_info object with the URL, port and time zone. Players use these to show account details and to build stream URLs, which usually follow this pattern:
- https://example.com/live/{username}/{password}/{stream_id}.m3u8
- https://example.com/movie/{username}/{password}/{stream_id}.{container_extension}
- https://example.com/series/{username}/{password}/{episode_id}.{container_extension}
A small Node.js script that lists live categories might look like this:
const base = 'https://example.com/player_api.php';
const auth = 'username=demo&password=demo';
async function listCategories() {
const res = await fetch(`${base}?${auth}&action=get_live_categories`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const categories = await res.json();
for (const c of categories) console.log(c.category_id, c.category_name);
}
listCategories().catch(console.error);
Because the data arrives in sections, a player can show live categories instantly and load the much larger film and series libraries in the background. That is why Xtream-based apps feel faster than M3U imports on big catalogues.
XMLTV: where the TV guide comes from
The programme guide is usually an XMLTV document, often available from an endpoint such as xmltv.php. It contains <channel> elements and <programme> elements with start and stop times:
<tv>
<channel id="news.example">
<display-name>Example News</display-name>
</channel>
<programme start="20261024180000 +0100" stop="20261024190000 +0100" channel="news.example">
<title>Evening Bulletin</title>
</programme>
</tv>
The channel attribute must match the playlist's tvg-id. Most "empty TV guide" bugs come down to three causes: mismatched IDs, time zone offsets being ignored, or an XML file so large that a low-powered device times out while parsing it. Parse it in a streaming fashion, cache the result and only keep the next day or two in memory.
Playing the stream in a browser with hls.js
Safari plays HLS natively, while most other browsers need a library. hls.js is the common choice:
<video id="player" controls muted playsinline></video>
<script src="https://cdn.jsdelivr.net/npm/hls.js@1"></script>
<script>
const video = document.getElementById('player');
const src = 'https://example.com/live/demo/demo/1001.m3u8';
if (Hls.isSupported()) {
const hls = new Hls({ lowLatencyMode: true });
hls.loadSource(src);
hls.attachMedia(video);
hls.on(Hls.Events.ERROR, (event, data) => console.warn(data.type, data.details));
} else if (video.canPlayType('application/vnd.apple.mpegurl')) {
video.src = src;
}
</script>
Two practical notes. Browsers block mixed content, so an HTTPS page cannot load an HTTP stream. And the stream server must send CORS headers, or the browser will refuse the playlist even when the URL works in a native app.
Debugging streams with ffprobe
When a channel will not play, check the stream itself before blaming the player. ffprobe, part of FFmpeg, shows the codecs, resolution and bitrate:
ffprobe -hide_banner -show_streams -select_streams v:0 \ "https://example.com/live/demo/demo/1001.m3u8"
Look at codec_name (H.264 or HEVC), width and height. A device that cannot decode HEVC will show a black screen with sound, which is one of the most common support tickets in IPTV.
What real-world services add on top
The formats are only the interface. What separates a good service from a poor one is the infrastructure behind it: enough server capacity for peak events such as a big football match, load balancing across regions, accurate EPG data, and support for apps on Fire TV, Android TV, smart TVs and phones. UK services such as Telemo also bundle features like a built-in VPN, because some internet providers throttle video traffic at busy times. For developers, the lesson is that player quality depends as much on handling slow APIs, partial data and network errors gracefully as on decoding video.
A note on legality
M3U, Xtream-style APIs, XMLTV and HLS are neutral technologies used by broadcasters, hotels, universities and commercial TV services. Whether a particular stream is legal depends on whether the content is properly licensed, not on the format. If you build or test a player, use your own content or streams you have the rights to.
Key takeaways
- M3U is a simple list of channels and URLs; Xtream Codes is a structured JSON API; XMLTV supplies the guide.
- Load Xtream data in sections so the app feels fast, and cache everything you can.
- Match
tvg-idto the XMLTV channel ID, and respect time zone offsets. - In the browser, use hls.js, serve over HTTPS and make sure the stream sends CORS headers.
- Use ffprobe to check codecs before debugging the player.
