Make your service look right in PhiPTV.
Your customers bring their own login to PhiPTV. This page explains exactly what the app reads from an Xtream Codes panel or an M3U + XMLTV setup, how to organise it, and what PhiPTV does not do — so nobody promises a feature that is not there.
PhiPTV is a media player. It does not provide, host, list, recommend or endorse any channel, stream or service, and this guidance is not an endorsement of anyone who follows it. It is published so that operators can make a source their customers already have work well in the app. Operators are responsible for the rights to whatever they distribute.
Thirteen things that make the biggest difference
Each one is something PhiPTV reads and shows. Skip one and the customer sees a grey tile, an empty guide row or a mystery error.
- Fill every logo and poster
stream_iconon live and VOD,coveron series,tvg-logoin M3U. An empty value shows a generated placeholder — PhiPTV never guesses a logo. - Match guide ids exactly
epg_channel_id(Xtream) ortvg-id(M3U) must equal the XMLTVchannel="…"id byte for byte. There is no name fallback and no normalisation. No id, no guide row. - Serve the guide at
/xmltv.phpXtream users get it automatically, unless they set their own guide URL, which is tried first. Gzip is supported and there is no size cap. M3U users must paste a guide URL by hand, so publish it clearly.
- Put a
±HHMMoffset on every timestampWrite it after a space, as in
20260901180000 +0100. A timestamp without one, or with the offset joined to the digits, is treated as UTC and your evening schedule lands in the afternoon. - Order categories and streams the way you want them shown
PhiPTV starts from the order your API returns and never sorts channels or titles alphabetically. Customers can hide, move and pin categories on their own TV; that is stored against the category name, so renaming a category resets it for them.
- Order streams inside each category the way they should be numbered
PhiPTV numbers channels by their position in the list on screen; every category starts at 1, and the remote's digits dial that position.
numis not shown. Keepstream_idstable: favourites, history and reminders are stored against it. - Serve live streams as raw
.tsand as.m3u8PhiPTV rewrites a live link ending in
.m3u8under/live/to.tsand tunes that first. It switches to the.m3u8for the rest of the session only after a 401, 403, 404, 410 or DNS failure on the.ts, which costs the viewer a reconnect. - Flag adult content with
is_adultIt is the only signal the parental filter and Kids mode use. There is no keyword matching on names or categories; an unflagged adult category is visible to a filtered profile.
- Tag radio with
stream_type: "radio_streams"Or
radio="true"in M3U. Tagged stations appear in the Radio section and stay in their category in Live TV. Without a tag PhiPTV guesses from the group and station name. - Fill the detail endpoints
get_vod_infoandget_series_infofeed the detail pages: plot, cast, director, genre, release date, duration, backdrop, trailer, and per-episode stills. List endpoints alone give a bare card. - Reject over-limit connections with 429 or 509
Those, and 5xx errors, are retried quietly with backoff. A 401, 403, 404 or 410 marks the channel dead and prompts the customer to refresh their playlist.
- Mark the channels you archive
Set
tv_archiveto1andtv_archive_durationto a whole number of days. PhiPTV then offers catch-up for those channels: replay from the guide, and start the programme on now over. See Catch-up. - Do not filter by User-Agent
The API and M3U file are fetched with a MAG250 string; catalogue downloads also send an
X-MACheader and amac=…cookie. Streams, movies and catch-up replays play withVLC/3.0.20 LibVLC/3.0.20. The XMLTV guide and logo or poster requests send the platform default (Dart/… (dart:io)) and no other headers.
Downloadable versions: the compatibility checklist, an M3U template and an XMLTV sample are on the Downloads page.
What PhiPTV asks your panel, and which fields it keeps
Catalogue calls go to player_api.php with the customer's username and password; the guide comes from xmltv.php and catch-up from /timeshift/. Fields not listed here are ignored.
| Call | Fields used | Where it shows |
|---|---|---|
| Sign-in (no action) | user_info.auth (a number; 0 means a wrong login), max_connections, username; server_info.time_now, server_info.timestamp_now | max_connections sets the "Auto" stream limit; the server clock fields set the time in catch-up links. status, exp_date and active_cons are read but not shown. server_info.timezone is not read. |
get_live_categoriesget_vod_categoriesget_series_categories | category_id, category_name — in array order | The category rails and filters, in your order. |
get_live_streams | stream_id, name, stream_icon, category_id, epg_channel_id, stream_type, is_adult, tv_archive, tv_archive_duration | Live TV list, guide matching, radio section, parental filter, catch-up. num is stored but not shown. |
get_vod_streams | stream_id, name, container_extension, stream_icon, rating, category_id, year, genre, is_adult | Movies grid, genre chips, poster. |
get_series | series_id, name, cover, category_id, year, genre, rating (0–10), last_modified, is_adult | Series grid; last_modified drives new-episode notifications. |
get_vod_info | info: name, plot, cast, director, genre, releasedate, duration/duration_secs, backdrop_path, movie_image or cover_big, rating, country, tmdb_id, youtube_trailer; movie_data: stream_id, name, category_id, container_extension (description, actors and release_date are accepted as fallbacks; duration is used before duration_secs) | Movie detail page and trailer. |
get_series_info | info: name, cover, plot, cast, director, genre, releaseDate (or release_date), rating (0–10; rating_5based only as a fallback, shown as if out of 10), backdrop_path, youtube_trailer. episodes: an object keyed by season number; each episode with id, title, episode_num, container_extension, added, info.plot, info.duration_secs (or info.duration), info.releasedate, info.rating, info.movie_image | Seasons, episode list, episode stills; added dates new-episode alerts. The top-level seasons array is ignored. |
xmltv.php | The XMLTV guide, see below | Programme guide, now-and-next, replay marks, reminders. |
Values that must be in a specific shape
Small things that silently break when they are not.
is_adult—1,trueor"1". The string"true"does not count.stream_type— exactlyradio_streamsfor radio; anything else is treated as live TV.rating— 0 to 10. Zero or above ten is shown as unrated.auth— send0as a number for a wrong login; the string"0"shows a generic server error instead.last_modified,exp_date— epoch seconds, not milliseconds.tv_archive,tv_archive_duration—1,trueor"1"; the duration as a whole number of days.episodes— an object keyed by season number, not an array.- Usernames and passwords — written into stream paths as typed; avoid
/ ? # %and spaces. container_extension— used verbatim in the movie and episode URL; defaults tomp4when missing.backdrop_path— an array (first entry used) or a single string; both work.youtube_trailer— a bare YouTube id or a full URL.- Timeouts — 30 seconds to connect and to wait for data on catalogue calls, 60 seconds while waiting for guide data. A slow live list fails the refresh; a slow movie or series list keeps the previous one.
How refresh works
PhiPTV downloads the whole catalogue; there is no delta API.
- Automatic refresh once a day while PhiPTV is open: it checks five minutes after start and then hourly, and runs once the customer's hour has passed (04:00 by default) and the last refresh is more than 22 hours old.
- If the fields PhiPTV keeps are unchanged (same items, same order, same category order), the database rewrite is skipped, so a stable catalogue is cheap for everyone.
- A live list that comes back empty is rejected rather than wiping the customer's channels — a panel outage does not empty their TV. Empty movie or series lists keep the previous ones. Only after the customer changes the server or login may an empty answer clear them.
- Stream URLs are built as
/live/user/pass/id.m3u8,/movie/user/pass/id.extand/series/user/pass/id.ext.allowed_output_formatsis not consulted.
Catch-up (replay)
Xtream only. What makes the replay mark appear, and how the link is built.
- A channel can be replayed when
tv_archiveis set andtv_archive_durationis a whole number of days above 0. In the guide, a programme that has ended and started within that many days carries a replay mark; OK plays it from the start. The guide reaches 12 hours back. - In fullscreen live on a TV, LEFT replays the programme on now from its start (the last hour when there is no guide), a minute behind live, with a Go live button.
- The link is built from the channel's live link and the guide's start and stop, trying in order:
/timeshift/USER/PASS/MINUTES/YYYY-MM-DD:HH-MM/ID.m3u8, the same ending.ts, then/streaming/timeshift.php?username=…&password=…&stream=ID&start=…&duration=MINUTES. The first that works is remembered for your server. - The start is in your server's local time, taken from
server_info.time_nowandtimestamp_now; without them it is UTC. Accurate programme times matter. - Each shape is checked with a short request (MAG250 User-Agent, closed after the first kilobyte) and must answer 2xx with an HLS playlist, MPEG-TS, MP4 or FLV. The replay itself plays with the VLC User-Agent and leaves no Continue Watching entry.
A correctly formed M3U, line by line
An M3U gives a customer Live TV and Radio. Movies and series are not detected in an M3U — offer the Xtream API to customers who want them.
The shape PhiPTV expects
Two lines per entry. That is the whole contract.
- Line 1 of the file starts with
#EXTM3U, or nothing is imported. - Each entry is one
#EXTINFline followed by the URL line. - No
#EXTVLCOPT,#KODIPROPor#EXTGRPline in between. An entry with an extra line in the middle is dropped. This is the single most common reason a channel is missing. - The channel name is the text after the last comma.
tvg-nameis not read. - Channel numbers are positions inside each category, starting at 1, so the order of lines within a
group-titleis the order customers dial;tvg-chnois ignored. Categories appear in the order eachgroup-titlefirst occurs. - Each entry is identified by its position in the file. Inserting, removing or reordering lines moves customers' favourites and history to other channels, so add new channels at the end.
- Do not put
"or'inside an attribute value; the value stops at the first quote. Do not append|User-Agent=…to URLs.
#EXTM3U #EXTINF:-1 tvg-id="news1.example" tvg-logo="https://cdn.example.com/logos/news1.png" group-title="News" tvg-country="GB" tvg-language="English",News Channel 1 http://stream.example.com/live/USER/PASS/1001.ts #EXTINF:-1 tvg-id="sport1.example" tvg-logo="https://cdn.example.com/logos/sport1.png" group-title="Sport",Sport Channel 1 HD http://stream.example.com/live/USER/PASS/2001.ts #EXTINF:-1 tvg-logo="https://cdn.example.com/logos/radio1.png" group-title="Radio" radio="true",Radio Station 1 http://stream.example.com/radio/station1.mp3
Attributes that are read
Everything else on the line is ignored.
| Attribute | Used for |
|---|---|
tvg-id | Guide matching. Must equal the XMLTV channel id exactly. |
tvg-logo | Channel logo. http or https; no headers are sent. |
group-title | Category. Spell it identically on every line. |
radio | radio="true" puts the entry in the Radio section; it also stays in its Live TV category. |
tvg-country, tvg-language | Stored; not used yet. |
tvg-name, tvg-chno, catchup*, url-tvg, x-tvg-url | Not read (there is no catch-up for M3U). |
Guide for M3U customers: the header's url-tvg is not read, so publish the XMLTV URL where customers can copy it. They paste it into the playlist's EPG field in PhiPTV, once.
Guide data that lands on the right channel at the right time
What is parsed
Only <programme> elements. <channel> blocks, their display names and icons, are ignored — names and logos come from the playlist.
start,stopandchannelare all required.- The first
<title>,<desc>,<category>and<icon>in each programme are used.langattributes are not inspected, so put the language you want shown first. - Timestamps are
YYYYMMDDHHMMSS ±HHMMwith a space before the offset. Without an offset the time is treated as UTC. - Entities:
& < > " '. Numeric references such aséare not decoded — write the character in UTF-8. CDATA sections are not unwrapped. - On import PhiPTV keeps programmes that end after 24 hours ago and start within the next 48 hours, for the channel ids in the customer's playlist. The guide shows 12 hours back and 24 hours ahead. Gzip bodies and gzip transport are both fine; there is no size cap.
- The guide is refreshed every 12 hours by default (the customer can choose 4, 6, 12 or 24) while Live TV or the guide is open, an hour after a failed attempt, and on demand. It is fetched without a custom User-Agent, and its start and stop times also drive reminders and catch-up links.
<?xml version="1.0" encoding="UTF-8"?> <tv> <programme start="20260901180000 +0100" stop="20260901183000 +0100" channel="news1.example"> <title lang="en">Evening News</title> <desc lang="en">The day's headlines, weather and sport.</desc> <category lang="en">News</category> <icon src="https://cdn.example.com/stills/evening-news.jpg"/> </programme> </tv>
channel="news1.example" must equal the stream's epg_channel_id or tvg-id exactly.
What the player accepts
Playback runs on the platform's hardware decoders. On TV boxes, video is decoded in hardware only — a codec the box cannot decode in hardware will not play.
| Item | Support | Notes |
|---|---|---|
HLS (.m3u8) | Yes | Live and VOD. Live joins at the live edge. |
Raw MPEG-TS over HTTP (.ts) | Yes | Preferred for live: fastest zap. Serve it alongside HLS. |
DASH (.mpd) | Yes | Without DRM. |
| MP4, MKV, WebM, MP3, AAC | Yes | Progressive files for movies, episodes and radio. |
| Audio and subtitle tracks | Embedded only | Tag tracks with an ISO 639 language and a clear label. The label is shown first; und or empty languages fall back to it. PhiPTV remembers the choice per series and per movie. |
External subtitle files (.srt, .vtt) | No | |
| RTSP, RTMP, Smooth Streaming | No | |
| http (cleartext) | Yes | http and https both work; http↔https redirects are followed. https needs a publicly trusted certificate. |
| Custom headers from the playlist | No | User-Agent is fixed (see checklist). No referrer, cookie or token headers can be set per stream. |
| Stream ends early | Auto-reopen | If a live session is cut by the server, PhiPTV reopens it up to three times before showing an error. |
| Connection limit reached | Warned | PhiPTV counts devices signed in to the same PhiPTV account that stream from the same server and username. It stops a new stream with a message (live TV: pressing again within 10 seconds goes ahead) before exceeding max_connections. Reject with 429/509 so the retry logic applies. |
| Catch-up (Xtream archive) | Yes | See Catch-up. Not available for M3U. |
| DRM-protected streams | No | No Widevine, PlayReady or ClearKey settings are read. |
A layout that reads well on a TV
PhiPTV shows your structure as it is. These conventions make it look intentional.
Categories
Shown in the order you return them, spelled exactly as you return them. Customers can hide, move and pin categories; that is stored by category name.
- Pick one prefix style and keep it:
UK | Sporteverywhere, neverUK| Sporton some rows. Every spelling variant becomes a separate category. - Put the categories customers open most at the top of the array. Uncategorised or unlisted names fall to the bottom.
- Keep names short enough for a rail label: about 24 characters.
- Give radio its own category and tag the streams. Tagged stations appear in the Radio section and stay in their category in Live TV. Untagged channels whose group contains the word "radio", or whose name reads like a station (Radio, FM, راديو…) with no TV mark, are also listed under Radio.
- Adult categories: flag every stream in them with
is_adult; the category name alone hides nothing.
Channels
Listed in the order you return them; the number is the position in the list on screen.
- Put the channels customers want on low numbers at the top of each category; digit entry dials positions within the category on screen.
- Name the channel, then the quality:
Sport Channel 1 HD. In M3U names, doubled suffixes likeHD HDare collapsed; Xtream names are shown as sent. - One logo per channel, square or nearly square, around 256 px, PNG or JPG, on a host that does not check the User-Agent or Referer.
- Give every channel an
epg_channel_id, even if a few have no guide yet — a blank id leaves an empty guide row with nothing to focus and nothing to replay.
Movies and series
Posters, genres and detail pages come straight from your metadata.
- Portrait posters in
stream_icon/cover; a widebackdrop_pathfor the detail page. genreas a comma-separated list; PhiPTV builds its genre chips from it. If it is empty the app falls back to keyword guessing from the title.- Series: one
episodesmap keyed by season number, episodes numbered withepisode_num, each with its own still ininfo.movie_image. - Update
last_modifiedwhen a new episode is added, and fill each episode'sadded— they drive the new-episode alert for customers who favourited the series, after their next catalogue refresh. - Ratings on a 0–10 scale. Durations in seconds (
duration_secs) are safest.
Accounts and limits
What PhiPTV shows the customer about their account.
- PhiPTV shows the customer their Xtream username.
status, the expiry date andactive_consare not shown, and an expired account is only noticed when streams fail. - PhiPTV's stream-limit guard uses
max_connectionsby default and warns the customer before another of their own devices pushes them over it. - Sessions you close from the server side are reopened automatically up to three times; a 429 or 509 is retried with backoff; 401/403/404/410 is treated as a dead link.
What PhiPTV does not do
So that nobody sells a feature the app does not have.
| Feature | Status | Detail |
|---|---|---|
Catch-up from an M3U (catchup, catchup-days, catchup-source) | Not supported | Catch-up works with the Xtream archive only. |
| Stalker / MAG portals | Not supported | PhiPTV accepts an M3U URL or an Xtream Codes login only. |
| Movies & Series in an M3U | Not detected | Every M3U entry is a live channel or a radio station. Use the Xtream API for VOD. |
| External subtitle files | Not supported | Only tracks embedded in the container. |
Per-stream headers (#EXTVLCOPT, #KODIPROP) | Not supported | Worse: such lines cause the entry to be dropped. Do not emit them. |
| RTSP, RTMP, Smooth Streaming | Not supported | HLS, DASH, MPEG-TS and progressive files only. |
XMLTV <channel> data | Ignored | Display names and icons come from the playlist, not the guide. |
| Multi-language guide entries | First only | The first title and description in each programme are shown regardless of lang. |
| DRM-protected streams | Not supported | No licence settings are read. |
tvg-chno, url-tvg, allowed_output_formats, server_info.timezone | Ignored | Channel numbers are positions in the list; formats are fixed as described above. |
This page is kept in step with what the app does. When a feature ships, it moves out of this table.
Content responsibility
PHIPTV is a media player only. It does not provide, host, bundle, or endorse any channels, streams, or other content.
You supply your own playlists and sources, and you are solely responsible for ensuring you have the legal right to access any content you add.
Shown and accepted on the first launch of the app, before anything else.