Skip to content

Technical The service

Kepler75 edited this page Nov 5, 2026 · 4 revisions

Home › Technical reference

The service

StremioK's service (service/index.js) does on the TV what a web page can't do on its own: YouTube, USB drive listings, subtitle files and downloads, the phone page, AIOManager, and repacking a live channel's choppy sound.

Runtime and ports

  • One file, Node core modules only, ES5 (the 2017 TV's Node is 4.x).
  • Sideloaded app: a bundled <tizen:service> on 127.0.0.1:8099, which the app (js/ytplay.js) starts when it needs it.
  • TizenBrew: TizenBrew runs the same file inside its own Node service, on 127.0.0.1:8098, and starts it when it launches StremioK. Since it then shares TizenBrew's process, every request is fenced off: a failure ends that one request, never the service. Separate ports let both installs live on one TV.
  • The phone page listens on a second port, the service's plus 10 (8109, or 8108 under TizenBrew), on the TV's home-network address only, and only while a page is open.
  • The home bar preview is a separate small service, service/preview.js (the sideloaded app only; see Home bar preview).

Who it answers

These checks keep web pages out (any page running in the TV's browser engine can reach a local port). They are restrictions on requests, not proof of who is calling.

  • Every endpoint but /health: the request must name the service by its local address (127.0.0.1 or localhost with its port, so a page reaching it through a DNS-rebound name is refused) and carry no Origin header. StremioK's pages and the TV's player send none, while a web page's request always does. Under TizenBrew, TizenBrew's own pages (http://127.0.0.1:8081) may send theirs, and every TizenBrew module shares that address. A browser's pre-check (preflight) carries an Origin, so it's refused too. GET only.
  • USB, files and downloads (/fs…, /fsdl/…) also need a token. The service keeps it in StremioK's private folder (wgt-private), and the app reads it and sends it. Where the app can't read it (on Tizen 6 the service keeps it in a folder of its own), an X-StremioK header is accepted instead, which a web page can't add without a pre-check. Under TizenBrew there's no private folder and so no token: these endpoints stay closed. Their answers carry no CORS header.
  • The phone page's controls, AIOManager and opening a live relay (/entry/…, /aiom/addons, /live/open) need the token or the X-StremioK header. Under TizenBrew, where there's no token, the header is what's sent. The app sends the header only when it has no token: on the 2017 TV's engine an extra header might make it send an Origin.

Endpoints

Each endpoint's parameters are described at the top of service/index.js.

Family Endpoints What it does Needs
Health /health "ok": the app waits for it before playing nothing
YouTube /yt, /ytinfo, /ytsubs, /ytsb, /ytlist A video's HLS master; its audio languages, end screen, cards and chapters; its captions as WebVTT; SponsorBlock sections and DeArrow; searches, channels and playlists local address, no Origin
USB drives /fslist, /fswalk, /fssub, /fshash, /fsfile A folder's listing; every video and subtitle file under a folder (the index); a subtitle file as UTF-8; a video's OpenSubtitles hash; a file served with ranges + token or header
Downloads /fsdl/add, list, cancel, clear, space Downloads to a USB drive, and a drive's free space + token or header
Phone page /entry/open, poll, close Opens, polls and closes the one-time page + token or header
AIOManager /aiom/addons Reads an account's addons + token or header
Xtream films and series /xtvod/list, /xtvod/find An account's newest titles and Search; a title's copies + token or header
Stalker /stalker/list, /stalker/link, /stalker/genres, /stalker/vod, /stalker/play A portal's channels as an M3U playlist; a channel's link to play; its genres; its films and series; a film's or episode's link, redirected to + token or header (play: or the guide ticket)
TV guide /epg/load, /epg/get Reads a playlist's guide for your channels; their programs + token or header
DRM /drm/license (a POST) Posts a Widevine channel's license request to its license server + token or the guide ticket
Live relay /live/open, then /live/<id>/… Checks a channel's sound and captions; relays it repacked, captions as subtitles open: + token or header

YouTube

  • Resolving: a YouTube page is resolved through YouTube's InnerTube API, from the TV, as the YouTube website does (no key, no account), and its HLS master served at /yt.
  • Quality: playback first asks for 1080p when that resolution is available (in a wider picture, like a 2:1 trailer's 1920×960, the rung at or below it). The complete master is loaded separately to fill the Video quality menu, each quality named by its class, wide pictures too (1920×960 is 1080p, 3840×1920 2160p). Picking a quality reopens the stream with a fresh YouTube session and a single-resolution master; Auto restores the full ladder. The player is stopped and given a moment before it reopens, since the TV's player often fails an open straight after closing a playing stream; if that first open still fails, it's retried once quietly ("Switching to 720p…"), then steps down a rung, then goes to Auto.
  • Tizen 6 headers: YouTube's watch page often comes with more headers than a Tizen 6 TV's Node reads (an experiment's 7 KB header, 12 KB in all, over its 8 KB). The service then reads that answer itself, straight off the connection, and asks again for anything else it can't read (twice at most). That reader accepts only a complete answer: every chunk and its end, or its declared length.
  • Audio: many videos carry AI-dubbed audio (every dub listed, none marked default, a dub often first), and switching tracks on such a stream right after it starts hangs the 2017 TV's player. The service keeps only the original audio, as the default (a stream with no track marked original is passed on as it is), and the app doesn't switch a YouTube video's audio on its own. Another language is another stream: &lang=<code> keeps that dub instead.
  • /ytinfo gives the video's audio languages, the links on its end screen and cards (links to videos only) and its chapters, all read from the page already fetched. /ytsubs gives a caption track as WebVTT.
  • /ytlist lists a search (src=search:<words>, {term} filled from q; kind videos / channels / auto / live, sort relevance / date / views / rating), a channel (@handle, link or UC… ID) or a playlist (link or PL… ID), about 20–30 items a page, with a next token for page=. Shorts are left out. meta=1 adds the channel's or playlist's name, description and picture (and, for a channel, what it's streaming now) and joins up to 4 pages (120 videos). orig=1 gives titles in their own language: the same request is made again in Zulu (YouTube doesn't translate titles into it) and only the titles are taken from it. brand=1 adds DeArrow's titles, asked per listed video, in parallel, waiting at most 4 seconds (the rest come next time).
  • /ytsb asks sponsor.ajay.app for a video's SponsorBlock sections and DeArrow title and thumbnail time by the first 4 characters of its id's SHA-256, as SponsorBlock's own extensions do: the answer covers every video sharing them, so the server isn't told which one.
  • Hosts: YouTube's pages and API and googlevideo.com (the masters), over HTTPS, with every redirect checked against those hosts; sponsor.ajay.app for SponsorBlock and DeArrow. An answer other than 200 is an error.
  • Limits: these YouTube and SponsorBlock requests go through one gate: 6 at a time, 40 waiting (more are told it's busy), at most 600 started a minute, identical ones sharing one download (up to 16 waiting on it), each given 30 seconds from start to finish (and 15 s of silence). Answers are capped at 8 MB downloaded and 10 MB unpacked (no gzip bombs). Resolving a page: at most 2 at a time, a few waiting, 30 fresh a minute, kept 4 minutes. Lists: 60 fresh a minute, kept 10 minutes. SponsorBlock: 300 a minute, kept an hour. A request given up (screen left, player closed) drops its waiting work and a shared download nobody else waits for; a lookup already running finishes and is kept.

USB drives and files

  • Only folders under /opt/media/<drive>/: a path is resolved to where it really is and must stay inside the drive it was asked under. Links in a folder are listed as links and never followed or walked, and subtitle files, hashes and /fsfile's files are read only through that check (a file is opened once, and served only if it's a regular file).
  • A drive must be mounted (listed in /proc/mounts). Its free space is read with df, run directly with the drive as its argument, never through a shell, and without holding up the service.
  • /fssub reads subtitle files up to 5 MB: SRT and WebVTT as they are, ASS / SSA turned into SRT, Windows-1252 and UTF-16 converted. /fshash reads a video's size and its first and last 64 KB. /fsfile serves a file with ranges, for the 2017 TV's player, which can't read past 2 GB of a file straight from the drive.
  • Bounded: 12 file lookups at a time for the whole service, 4 listings at once, and one drive walk at a time (the same drive asked again shares it; another is told the service is busy). A walk covers up to 5,000 folders and 20,000 files, 12 levels deep, and starts nothing new after 90 s.

Downloads

  • One at a time, up to 50 in the list, into a ….part file renamed when complete.
  • A dropped connection (or 60 s of silence) is picked up where it stopped, with a range request where the server allows it, up to 5 times, waiting 5 s, 15 s, 30 s, 1 min, then 2 min.
  • A resumed answer must start exactly where the partial file ends (its Content-Range), for the same file (If-Range, with the ETag or date the download began with); otherwise it starts over. The finished file must be the declared size.
  • That ETag or date is kept in StremioK's own folder (the last 64 downloads), never on the drive, where a prepared file could lead elsewhere. The partial file's size is read only from a regular file, not through a link.
  • Checked before it starts: the drive's free space (with 50 MB to spare) and, on a FAT32 drive, the 4 GB file limit. The partial file and the finished one must be regular files, not links.
  • A streamed playlist (HLS or DASH, by its type or its first bytes) isn't a file: refused.
  • Links over HTTP(S) only, never to the TV itself: loopback, unspecified and link-local addresses however they're written (localhost, 127.…, 2130706433, 0x7f…, ::1, ::ffff:127.0.0.1, 169.254.…), checked again on the address a name resolves to when connecting. Home-network addresses (a NAS) are allowed. HTTPS checked against the bundled service/cacert.pem.
  • The list is kept in the service's memory; the app keeps its own copy and adds again what the service no longer knows after a restart. One that had already finished (its file on the drive at its size, no partial file left) is taken as done, not downloaded a second time.

The phone page

The TV asks the service to open a page (/entry/open?what=…), polls it (/entry/poll?t=…) and closes it (/entry/close?t=…). At most 2 pages are open at a time (another closes the oldest) and 12 opened a minute. Each page has a random 128-bit code in its address, takes one entry (8 KB at most), and closes after 5 minutes, or 30 seconds without the TV polling it. Its port takes at most 8 connections, and each visit is given 20 seconds (10 s of silence). See Typing, or sending from your phone.

AIOManager

/aiom/addons reads an account's addons with AIOManager's Hydra API (GET <address>/hydra/addons, the API key sent as X-API-Key), up to 2 MB, within 20 seconds in all, and keeps at most 500. It follows up to 4 redirects on the same server only (HTTP to HTTPS allowed, never the other way): the key never goes anywhere else. One cancel stops the whole chain. It then registers StremioK (POST <address>/hydra/register, best effort), which is its card in AIOManager's Connections. Nothing else is written to AIOManager. It's done in the service because an instance only answers the web addresses it's set to. The same host rule as downloads applies.

Live relay

/live/open looks at the start of one of a channel's segments (up to 1.5 MB) to see whether its AAC sound is cut mid-frame. If it is, it returns a relay link that passes the picture as it is and repacks the sound into whole frames, each with its own time. At most 4 relays at a time, each dropped after 5 minutes unused; playlists up to 1 MB and segments up to 32 MB, 20 seconds each. The channel's User-Agent, Referer and Cookie are passed on, and the same host rule as downloads applies.

HLS through StremioK's service (Settings › Playback › Seeking & buffering, or hold OK on a source › Play through StremioK's service): an HLS source opens the relay without the look (&force=1&fix=0), so every playlist and piece is fetched with its headers. Sources that need headers: those with their own User-Agent, Referer or Cookie; All HLS sources: any .m3u8. Not YouTube, DRM or USB files. A film's (&film=1) keeps every link it named (a live channel's keeps its last 400), relays its keys, init sections and encrypted pieces too, asks a byte range as one, and is dropped after 3 hours unused. If the service doesn't answer, the source plays as it is.

Closed captions (CEA-608 in H.264, HEVC or MPEG-2 pictures) at a segment's start: the look reads the whole segment, up to 8 MB. Asked for (&cc=CC1-en; &fix=0: sound as it is), the relay adds a WebVTT subtitle track per channel; a plain playlist goes under a quality list of its own.

  • One download per segment for picture and captions (kept 30 s for the other, two at a time, up to 8 MB); captions kept for the latest 30 segments.
  • Cues in showing order: a pop-on caption from when it shows; roll-up, one per finished line with the lines above; one still on at a segment's end goes on in the next.
  • Timing: the picture's own clock (X-TIMESTAMP-MAP).

Every request

  • An answer within 60 seconds (504 otherwise), except the USB and download endpoints, which have bounds of their own.
  • At most 64 connections at once.
  • A request line up to 2 KB (8 KB for adding a download, whose link can be long).
  • Answers say Cache-Control: no-store and X-Content-Type-Options: nosniff.

Clone this wiki locally