Protocol v1 / JavaScript reference client
BossTunnel App SDK
Native player integration for combined libraries, remote metadata discovery and on-demand playback.
For app developers: keep your existing Xtream browser and playback integration. Add the optional Boss extension to search authorized sources for titles that are not yet in the downloaded IPTV catalogue.
Use an existing Xtream login
The JavaScript module has no dependencies and uses Fetch. It works in modern browsers and Node.js 20+. Download it into the player's codebase and import it locally.
import { BossClient, BossError } from './boss-client.mjs';
// Use the server URL and credentials the user already entered.
const boss = await BossClient.fromXtream(
xtreamServer, username, password
);
const page = await boss.search('John Q', {
type: 'movie', skip: 0, limit: 100
});
for (const item of page.items) {
// item.id: canonical UUID; item.xtreamId: stable numeric ID.
showSearchResult(item);
}The server URL includes its configured base path, for example https://bosstunnel.com/xtream. Do not append player_api.php when constructing the SDK client.
Discovery uses POST {server}/boss_api with the existing username and password. No Boss management token is needed. A 404 from a non-Boss server means the extension is unavailable; a 401 means the credentials were rejected.
Connect a native Boss addon or M3U playlist
Building a source addon instead of an app? Use the Addon Author SDK.
// M3U mode: reads only the header, then discovers the Boss API.
const boss = await BossClient.fromM3u(userProvidedPlaylistUrl);
const results = await boss.search('John Q', { type: 'movie' });
// Native mode: the same search, media and playback methods.
const native = await BossClient.fromAddon(userProvidedAddonUrl); // https://host/addon.boss
// File from an input with accept=".boss". Ask the user to trust the host first.
const imported = await BossClient.fromFile(selectedFile, {
trustedOrigin: "https://your-addon.example",
token: userProvidedAccessToken
});Boss playlists contain boss-addon-url=".../addon.boss" on their first #EXTM3U line. The SDK reads at most 8 KB of the header and cancels the remaining download. Missing discovery metadata means ordinary M3U behavior only. Discovery URLs must have the same origin as the playlist.
With a Boss-aware app, all three connection modes expose the same library, canonical IDs, extended search and lazy playback API. Their legacy transport formats are not lossless equivalents: ordinary M3U/Xtream clients still lack remote search, and optional subtitles, guides and archives depend on source capabilities.
const boss = await BossClient.fromAddon(userProvidedAddonUrl);
// For a private author-hosted addon, pass its addon token, never a management token:
// const boss = await BossClient.fromAddon(url, { token: addonAccessToken });
// Native API resources must stay on the configured origin; redirects are rejected.
const page = await boss.catalogue({ type: 'movie', limit: 100 });
const next = page.next
? await boss.catalogue({ type: 'movie', after: page.next })
: null;Boss-native addon URLs end in /addon.boss. Existing /addon links remain supported. The descriptor declares format: "boss-media-addon", version: 1, capabilities and resource URLs. The wire encoding is UTF-8 JSON; a native Boss addon is not the external compatibility addon format.
A .boss file is a UTF-8 JSON descriptor, not executable code or a video container. File imports accept File/Blob, UTF-8 bytes or text, up to 8 MiB, and require an explicitly trusted origin before sending credentials. Private files contain access links: handle them as secrets. Apps must register their own .boss file picker or operating-system association; the SDK does not register one automatically. Imported files use the same search, metadata and playback methods as hosted links. Do not construct resource paths from assumptions. Follow the descriptor's resource URLs. Unsupported optional resources are omitted.
Catch-up playback
When the descriptor declares catchup, channel records include channel.catchupDays. Call boss.catchup(channel.id, { start, end, signal }) with UTC millisecond timestamps. The same method works through native, Xtream and M3U-discovered SDK connections. The response contains protected playback resources, just like ordinary playback. Native HTTP uses {catchup}?start=...&end=...; the Xtream extension uses action catchup with the same fields.
Intervals must be positive, no longer than 24 hours, and not end more than one minute in the future. The provider must offer that channel and interval within its retained archive window. An advertised window is not a guarantee that every recording exists. Missing, expired or unauthorized archives return 422 at playback. This is provider catch-up, not a Boss recording service or a live timeshift buffer.
Expanded search
- Debounce text input and cancel obsolete requests with an AbortController.
- Call
boss.search(query, { type, skip, limit, signal }). Use 1 to 200 characters. - Merge remote results with local results by canonical
idor stablextreamId, never by title alone. - Use
nextOffsetfor the next search page. Usenextonly for ordinary catalogue browsing. - On selection, request playback immediately before starting the player.
let pending;
async function searchMovies(query) {
pending?.abort();
const controller = pending = new AbortController();
try {
const page = await boss.search(query, {
type: 'movie', skip: 0, limit: 100,
signal: controller.signal
});
if (pending === controller) renderResults(page.items);
} catch (error) {
if (error.name !== 'AbortError') showSearchError(error);
}
}Search imports metadata into the shared graph. The discovered title can subsequently appear in the same library's Xtream catalogue. It does not start video resolution or guarantee a playable resource exists.
Discovery is bounded: up to 200 records per source page, five pages per catalogue/request and 256 searchable catalogues per source, with a 15-second source deadline and shared queue limits. Feeds are visited in breadth-first rounds, and a failed feed does not prevent other feeds in the source from being searched. Completed feed progress is cached briefly; failed feeds may retry on a later search. Results can be partial when sources fail or the discovery bound is reached. Deep jumps can require subsequent requests. Source relevance ordering is not currently retained. This is not an unlimited global movie search.
Identity and playback
const { media } = await boss.media(item.id);
const { resources } = await boss.playback(item.id);
if (!resources.length) return openSeriesOrSeason(media);
// Prepare the selected upstream request; the player handles the media.
const request = playbackRequest(resources[0], { supportsHeaders: true });
player.open(request.url, { headers: request.headers });Use canonical UUIDs for Boss calls. xtreamId identifies the same item in standard Xtream actions: movie details use vod_id, series details use series_id, and episode playback uses the series route. IDs persist through restarts; never derive them from titles or replace one placeholder item's identity on each search.
Episodes include seriesId, seasonNumber and episodeNumber. The graph preserves external identities independently of the playback source. Multiple authorized sources can attach to one item.
playbackState: "UNRESOLVED" is intentional metadata, not a failed status. A series or season has no direct playback resource. HTTP 422 during playback means no usable resource was provided. Surface this to the user; do not silently retry forever.
Native BOSS playback returns supplied HTTP/HLS resources directly without downloading media, rewriting HLS, remuxing or transcoding. Follow response types and redirects, not URL extensions. Check player capabilities and report unsupported formats instead of expecting Boss to convert them. Universal player certification is not claimed.
HTTP contract
For native mode, use the descriptor's URLs. For Xtream mode, send form-encoded POST requests to {server}/boss_api with username, password, action and action parameters.
| Action | Parameters | Response |
|---|---|---|
| describe | None | Versioned Boss descriptor |
| catalogue | type, after, limit | items, next |
| search | search, type, skip, limit | items, nextOffset |
| media | id | media |
| playback | id | id, resources |
| subtitles | id | subtitles |
The reference client limits metadata responses to 8 MiB, checking both declared length and streamed decoded bytes. Oversized responses raise BossError with status 413; malformed response objects are rejected. Cancellation while downloading a response remains an AbortError. M3U discovery is limited to 8192 UTF-8 bytes. Page size is 1 to 200. Default is 100. Type values are movie, series, season, episode, channel and event where declared. Do not combine a nonzero cursor and offset.
{
"items": [{
"id": "canonical-uuid",
"xtreamId": 48291,
"type": "movie",
"title": "Example title",
"identities": { "imdb": "tt1234567" },
"artwork": {},
"playbackState": "UNRESOLVED"
}],
"next": null,
"nextOffset": null
}Fields such as year, description, genres, runtimeSeconds, rating, certification and releaseDate may be absent or null. Treat missing metadata as unknown, not as an empty or unavailable library.
| Status | Player handling |
|---|---|
| 400 | Invalid parameters; correct the request |
| 401 / 403 | Credentials rejected or resource access revoked |
| 404 | Unsupported endpoint, missing item or removed library |
| 422 | No usable playback resource or unsupported capability |
| 502 / 503 | Source failure or capacity limit; bounded retry with backoff |
Security requirements
- Use only user-authorized sources and HTTPS in production.
- Store credentials in the platform's secure credential store. Never request the Boss management token.
- Treat native addon and playback URLs as private access links. Redact paths, query strings and credentials from logs and crash reports.
- Do not persist temporary stream URLs indefinitely. Obtain a fresh playback resource when opening a title.
- Respect capability declarations, expiry and revocation. Do not bypass DRM, subscriptions or authentication.
- No torrent engines, magnet links, torrent uploads or torrent payload playback. Already-resolved authorized HTTP media is supported.
Player acceptance checklist
- Boss extension discovery succeeds using existing Xtream credentials; ordinary servers retain normal behavior.
- A title absent from the downloaded catalogue appears through remote search without a stream request.
- The same title retains its canonical and Xtream IDs after refresh and restart.
- Search cancellation does not let older results overwrite newer results.
- Two sources for one reliable identity appear as one title, with authorized playback fallback.
- Series/episode navigation preserves identities and does not try to play a series record.
- Expired links, text-file responses, removed libraries and unavailable sources display actionable errors.
- Actual media, subtitles, seeking and live playback are verified on every supported player platform.
Boss server contract tests and the reference client are provided. App integration and platform certification must still be performed by the player team.
Player protocol negotiation
When the descriptor advertises playbackNegotiation, call boss.playback(id, { protocols: ['http', 'hls'], signal }) with only protocols your player supports. The same option works through native, Xtream-extension and M3U-discovered connections, and with catchup. Omit it for legacy defaults.
Allowed values are http, hls and dash, where advertised. Empty, duplicate or invalid lists are rejected. Older servers without negotiation support produce an SDK error rather than silently ignoring preferences. The HTTP parameter is boss_protocols, a single comma-separated list used when resolving choices. It is not appended to direct provider URLs.
This selects compatible resources; it does not transcode, remove DRM or guarantee codec support. Filename extensions are not container guarantees. DASH coverage currently includes local static audio/video fixtures. Dynamic feeds, adaptive switching and individual player platforms need further verification.
Player capability limits
When descriptor.playbackCapabilities.version === 1, provide the optional capabilities object to playback or catchup. The same options work through native Boss, the Xtream extension and M3U discovery. Omit the object for existing behavior. This extension is separate from transport-protocol negotiation.
const capabilities = {
codecs: ['h264'], maxHeight: 1080,
hdr: false, strictCapabilities: true, language: 'en'
};
const { resources } = await boss.playback(item.id, { capabilities, signal });
const request = playbackRequest(resources[0], { supportsHeaders: true });
player.open(request.url, { headers: request.headers });| SDK field | HTTP parameter | Meaning |
|---|---|---|
| codecs | boss_codecs | Nonempty unique list from the advertised codecs; comma-separated on the wire |
| maxHeight | boss_max_height | Whole-number maximum from 0 to the advertised maximumHeight; 0 adds no player height limit |
| hdr | boss_hdr | false excludes streams declared as HDR |
| strictCapabilities | boss_strict | true rejects missing codec metadata when a codec list is requested |
| language | boss_language | Preferred audio language, such as en or en-US; ranking preference, not a hard exclusion |
Boolean wire values are exactly true or false. Duplicate fields, duplicate codecs and malformed values return 400. The SDK checks advertisement and refuses unsupported requests with 422 before sending them. Do not silently remove a player's required limits to get a successful response.
Boss intersects codec lists and height limits with the saved library profile. Either side can prohibit HDR or require known codec metadata. Disjoint codec lists return 422. A supplied language preference overrides the library's default language preference. Player options filter the returned direct choices. BOSS cannot revoke or recheck provider URLs already handed to a player; request fresh choices immediately before playback. A policy change during resolution returns 409; request fresh playback details.
Limits rely on source-provided metadata. Unknown height or HDR metadata cannot establish compatibility; the player must still handle decode failures. These options do not inspect, remux, transcode or alter media bytes. A legacy IPTV player gains no new behavior unless it implements the Boss extension; direct Boss-hosted Xtream playback URLs also accept these HTTP parameters.
Source categories
Detect capabilities.categories and resources.categories before showing a category browser. This optional v1 extension uses the same category IDs through native Boss, the Xtream extension and M3U-discovered Boss connections. Legacy clients keep their existing behavior.
const groups = await boss.categories({ type: 'movie', limit: 100 });
const nextGroups = groups.next
? await boss.categories({ type: 'movie', after: groups.next, limit: 100 })
: null;
const films = await boss.catalogue({
type: 'movie', categoryId: groups.categories[0].id, limit: 100
});Native HTTP follows resources.categories; Xtream-extension POST uses action categories. Supply type movie, series, channel or event. Responses contain categories: [{ id, name, type }] and a nullable next cursor. Page size is 1 to 200. Category IDs are opaque strings for app storage and equal the corresponding Xtream category IDs. Keep category and media cursors separate.
Pass categoryId to catalogue or search. An unfiltered media item includes its primary category; a filtered item includes the selected category. The primary category is not an exhaustive membership list: a merged title remains discoverable in every authorized source category without duplicating its canonical ID. Other-source labels use Source Name | Category. Category 1, Boss Media, contains uncategorized items only. Omit categoryId to browse all items. Removed or unrelated source categories do not expose their titles.
Older players can ignore the new fields. Player integrations must explicitly consume this resource and preserve category assignments; updating the server alone does not add category navigation to an installed app.
WebDAV libraries
A configured WebDAV source becomes the same canonical catalogue used by other Boss sources. Apps use the normal catalogue, search, media, playback and subtitle methods; native direct playback returns the required WebDAV authorization header to the authorized player. This header is a credential and must be scoped to its resource origin. Apps do not implement WebDAV catalogue traversal themselves.
const shows = await boss.catalogue({ type: 'series', limit: 100 });
const episodes = await boss.catalogue({
type: 'episode', seriesId: shows.items[0].id, limit: 100
});
const details = await boss.media(episodes.items[0].id);
const playback = await boss.playback(episodes.items[0].id);
const captions = await boss.subtitles(episodes.items[0].id);
Follow pagination for both series and episodes. Native playback URLs are exact upstream resources with required headers. Fetch them from the player, not through BOSS. MP4, MKV and other supported containers are not converted. Range requests and seeking depend on the source and player. Optional codec limits require trustworthy source metadata.
Boss scans folders in the background, identifies common season/episode filenames and reads local NFO metadata, artwork and matching SRT/VTT/ASS/SSA files. IMDb/TMDB/TVDB IDs in filenames, folders or NFOs support reliable identity merging. Missing metadata may be enriched on a detail request using the existing metadata provider; ambiguous or unidentified names remain local rather than being guessed. Only files actually discovered on WebDAV become playable episode records.
Native Boss, authenticated Xtream extension and M3U-discovered Boss connections share these APIs. Ordinary Xtream clients browse VOD and series/season/episode lists; ordinary M3U clients receive file entries and groups. External subtitle discovery and expanded search require the Boss extension or another output protocol that supports them. WebDAV file libraries do not advertise live television, EPG, catch-up or timeshift.
Connection authentication supports Basic username/password, Bearer access token, or anonymous access. Use HTTPS. Initial listing takes time; a completed catalogue is not a guarantee that every file is online or decodable. Stable file paths retain their IDs across rescans/restarts; renamed files require reliable identities to merge with the old title. Multi-episode files can appear as multiple episodes but play the original complete file without invented chapter offsets.
Live programme guide
Check descriptor.capabilities.epg and descriptor.resources.guide. When present, boss.guide({ signal }) returns a Fetch Response containing XMLTV. Stream response.body into the app's XMLTV parser and persistent guide database; do not load a large merged guide into one in-memory array. Close or cancel the body when finished.
if (boss.descriptor.capabilities.epg) {
const response = await boss.guide({ signal });
await programmeDatabase.importXmltv(response.body, { signal });
}
// Match XMLTV programme/@channel to the channel record's channel.epgId.
// programmeDatabase is supplied by the app, not the Boss SDK.
The same method works for native .boss files/links, Boss-aware M3U discovery and Xtream SDK connections. Native mode uses the advertised guide URL; Xtream mode sends credentials in a POST body to {server}/xmltv.php. Ordinary M3U clients use x-tvg-url; ordinary Xtream clients use XMLTV or their short-guide API. Channel IDs remain stable across outputs and restarts. XMLTV timestamps include a UTC offset; convert them for the viewer's timezone.
Guides are merged only from the library's enabled, authorized sources. Xtream sources automatically check their authenticated default XMLTV endpoint. M3U sources discover x-tvg-url or url-tvg in the playlist header. An explicitly configured XMLTV URL takes precedence. Missing or denied provider guides do not become invented programme data. Movies, series and WebDAV file libraries have metadata, not live schedules. Rendering now/next and the guide grid remains the app's responsibility.
Native direct resources
Import playbackRequest from the app SDK. Render every returned choice with its source, quality label, tags and expiry. A non-null resolution contains both numeric width and height; inferred: true marks nominal quality-tier dimensions, not measured pixels. Unknown dimensions remain null.
The native playback, catch-up and subtitle APIs return exact authorized provider URLs and required headers. The helper prepares a player request without fetching media. Never copy addon-login credentials to provider requests. Scope media headers to headerOrigin, do not forward them across unrelated redirects, and handle provider failures in the player. BOSS does not probe these resources and cannot revoke links already handed off.
Legacy playback routes always issue a zero-body 307 to the exact selected upstream URL. HTTP redirects cannot carry provider request headers. Unmodified players therefore work with self-contained or signed URLs; a header-protected URL requires native header support and may be rejected by the provider when opened through a legacy route. BOSS does not insert credentials into or otherwise alter an upstream URL.
This local security branch is unreleased. Its native responses are direct, but compatibility streams and experimental server outputs still need reconciliation with the deployed direct-only release. Do not deploy this branch or claim every route is direct until that work is verified.
Enabled source inventory (v1)
The optional descriptor.sourceAvailability reports only enabled, currently authorized sources in this library. It is additive to the existing aggregate capabilities. Native addon, Xtream describe and M3U-discovered connections share this descriptor. No source URL, credentials, name or raw upstream error is included; opaque source IDs match playback choice source IDs.
import { sourceAvailability } from './boss-client.mjs';
const inventory = sourceAvailability(boss.descriptor); // or boss.sourceAvailability()
if (inventory?.effective.features.live === 'unsupported') {
// Do not refresh Live for this descriptor snapshot.
}
// null (older server) or unknown: retain legacy discovery behavior.
// Obtain a fresh descriptor to observe changed source membership.
Version 1 uses semantics: "enabled-authorized-not-health-verified". Each sources item has id, protocol, availability: "enabled", contentTypes, contentTypesComplete, features and health. Protocol is xtream, plex, emby, jellyfin, boss, other, m3u, webdav, catalogue or unknown. Content types use the existing canonical types. Missing/empty declarations set completeness false, not evidence of absent upstream content.
features.live, features.epg and features.catchup are supported, unsupported or unknown. They derive from adapter declarations, not protocol guesses or stream probes. Live/EPG require channel support; catchup also requires streams. The effective object combines the same content and feature fields across authorized sources: any supported wins, otherwise any unknown remains unknown, otherwise unsupported. Only a complete content-type inventory may exclude an unlisted type.
Health is {state: "unknown", basis: "not-probed"} or {state: "degraded", basis: "last-sync-failed"}. Enabled is not healthy, a sync failure does not remove capabilities, and a completed catalogue sync does not certify playback. Disabled, removed and unauthorized sources are omitted. This is the gateway's configured source inventory, not enumeration of hidden providers inside a third-party addon.
The helper returns null only for an absent extension; malformed or unsupported versions throw BossError. Authorization and vault checks are unchanged. HTTP 401/403/423 during discovery remain errors, never an empty inventory. A locked vault (423) requires the owner to sign in/unlock; do not interpret it as missing Live or permanently disable refresh. No health probe or authorization bypass is performed by this extension.
Stremio addons to media-server connections
In the workspace choose Stremio addon and enter its manifest URL. Add a catalogue or metadata source to the same combined library when your addon supplies streams only. Choose Jellyfin server or Emby server under Player format, then set up the connection. Save its separate server URL, username and password in your player.
This is an experimental direct-play adapter, not a complete replacement for every Jellyfin or Emby server API. HTTP/debrid streams are supported; torrent acquisition and transcoding are not. The player must decode the original format. Native Silo output is not implemented.
Download the media-server SDK. Provision connections only from a trusted backend. Never bundle an administrator key in a public player.
import { BossMediaServer } from './boss-media-server.mjs';
const boss = new BossMediaServer({ baseUrl: 'https://bosstunnel.com', adminToken });
const { addon } = await boss.addStremio({ name: 'My addon', manifestUrl });
const connection = await boss.provision(addon.id, 'jellyfin');
// Save connection.password now. It cannot be retrieved later.
// Use 'emby' to create a separate Emby-scoped connection.
Saint TV customer API keys
Saint TV connection details include a separate Jellyfin API key alongside the server URL, username and password. Apps that ask for an API key should use that key, with the supplied username if requested. This key accesses only the customer's library; it is not a BOSS administrator key. General workspace provisioning continues to issue username/password credentials.
// Player-side: use the customer's key from their private connection details.
const player = new BossMediaServer({ baseUrl: 'https://bosstunnel.com', apiKey });
const user = await player.me();
const movies = await player.items({ type: 'Movie' });
const playback = await player.playback({ itemId: movies.Items[0].Id });
// Select a MediaSource and use its DirectStreamUrl and RequiredHttpHeaders.
For direct HTTP integrations send X-Emby-Token: <customer API key>. The api_key and ApiKey query forms also work, but prefer a header to keep credentials out of URLs. GET /jellyfin/Users returns only the authenticated user; /jellyfin/Users/Me returns that user directly. Login with username/password remains supported and returns a separate session AccessToken.
The customer key persists across server restarts and follows the validated subscription expiry. Disabled or expired access is denied. Logout using the key revokes it; fetching fresh Saint TV connection details then issues a replacement. Password rotation also revokes keys. Keep keys private and never send a customer's key to management APIs. Native app compatibility must still be checked per app.
Resolve a new title through Jellyfin
An app can discover an IMDb title outside the initial BOSS catalogue, select a movie or episode, and resolve its available sources through the Jellyfin API.
This flow is supported by the deployed BOSS Jellyfin adapter. A successful title lookup means metadata was found; playback still depends on an authorized addon returning a usable source. Looking up a title does not submit a Saint TV content request, acquire media or start a download. If a separate request is still being fulfilled, show that state separately and retry playback when it becomes available.
Structure the integration
- Connect once. Use the customer's Jellyfin server URL and API key, or authenticate with their username/password and retain the returned AccessToken. Native apps use
https://bosstunnel.com/jellyfin; the JavaScript SDK'sbaseUrlishttps://bosstunnel.com, because its methods add the protocol path. - Identify the requested title. Obtain its verified IMDb ID and type from the app's metadata/search provider.
SearchTermsearches the existing library; it does not trigger external discovery. New-title discovery currently needs an IMDb ID and a supporting authorized source; addon resource types and idPrefixes are respected. A TMDB-only or TVDB-only lookup does not perform that discovery. Resolve those IDs to IMDb before calling this flow. - Ask Jellyfin for the item. Use
AnyProviderIdEquals=imdb.<id>andIncludeItemTypes=MovieorSeries. BOSS checks enabled, authorized sources and verified metadata, then returns a Jellyfin item ID. Prefer one title per user action; discovery accepts at most five unique IMDb IDs in one request. - Select the episode when required. For series, call
/Shows/{seriesId}/Episodeswith the season, follow pagination and match the returned season/episode numbers. Pass the episode's returnedIdto playback. Do not send the series ID, IMDb ID or a constructedtt…:season:episodestring as the Jellyfin playback item ID. - Resolve and play. Call
POST /Items/{itemId}/PlaybackInfoimmediately before playback. Present the returned MediaSources, select one the player can decode and open its exact DirectStreamUrl with RequiredHttpHeaders. Keep the ItemId, MediaSourceId and PlaySessionId with that playback session.
Copyable JavaScript flow
Download boss-media-server.mjs. Store the following helper beside it in your application. Inputs below come from the user selection and private connection details; no real account credentials are embedded in these examples.
import { BossMediaServer } from './boss-media-server.mjs';
import { resolveRequestedTitle } from './resolve-request.mjs';
const player = new BossMediaServer({
baseUrl: 'https://bosstunnel.com', apiKey: customerApiKey,
});
const result = await resolveRequestedTitle(player, {
imdbId: selectedTitle.imdbId, type: selectedTitle.type,
// For a series, also supply the selected season and episode:
season: selectedSeason, episode: selectedEpisode,
profile: { DeviceProfile: actualPlayerDeviceProfile },
});
// Render result.state. For ready, offer result.sources as playback choices.
// Open the selected source.DirectStreamUrl with source.RequiredHttpHeaders.
// Pass a BossMediaServer instance configured with the customer's API key,
// or pass token from login().AccessToken for username/password sessions.
export async function resolveRequestedTitle(player, {
imdbId, type, season, episode, token, profile = {},
}) {
if (!/^tt\d{7,12}$/.test(imdbId) || !['Movie', 'Series'].includes(type)) {
throw new TypeError('Supply an IMDb ID and Movie or Series');
}
if (type === 'Series' && (!Number.isInteger(season) || season < 0 ||
!Number.isInteger(episode) || episode < 1)) {
throw new TypeError('Supply a season and episode number');
}
const query = new URLSearchParams({
AnyProviderIdEquals: `imdb.${imdbId}`,
IncludeItemTypes: type,
Fields: 'ProviderIds', Recursive: 'true', Limit: '5',
});
const found = await player.request(`/jellyfin/Items?${query}`, { token });
let item = found.Items.find(row =>
row.Type === type && row.ProviderIds?.Imdb === imdbId);
if (!item) return { state: 'title-unavailable' };
if (type === 'Series') {
const seriesId = item.Id;
item = null;
// Follow pagination; episode numbers need not be contiguous.
for (let start = 0; ; start += 100) {
const query = new URLSearchParams({
Season: String(season), StartIndex: String(start), Limit: '100',
SortBy: 'ParentIndexNumber,IndexNumber',
EnableTotalRecordCount: 'true',
});
const page = await player.request(
`/jellyfin/Shows/${encodeURIComponent(seriesId)}/Episodes?${query}`,
{ token },
);
item = page.Items.find(row => row.ParentIndexNumber === season &&
row.IndexNumber === episode);
if (item || page.Items.length === 0 ||
start + page.Items.length >= page.TotalRecordCount) break;
}
if (!item) return { state: 'episode-unavailable' };
}
// Resolve just before playback. Let the caller supply its real DeviceProfile.
const playback = await player.playback({ token, itemId: item.Id, profile });
const sources = (playback.MediaSources || []).filter(source =>
source.SupportsDirectPlay && source.DirectStreamUrl);
if (!sources.length) return {
state: 'no-compatible-source', itemId: item.Id,
errorCode: playback.ErrorCode,
};
return {
state: 'ready', itemId: item.Id,
playSessionId: playback.PlaySessionId, sources,
};
}
For password login, construct the SDK without apiKey, call player.login({ username, password, deviceId }), and pass its AccessToken as token to the helper. Use a stable, nonempty deviceId. Authentication failures remain errors; do not replace them with an empty source list.
Equivalent HTTP sequence
All paths below are relative to the customer's Jellyfin server URL. Authenticate each request with X-Emby-Token: <API key or AccessToken>. The IDs in braces are values returned by the preceding requests.
GET /Users/Me
GET /Items?AnyProviderIdEquals=imdb.{imdbId}&IncludeItemTypes=Movie&Fields=ProviderIds
# For a series instead:
GET /Items?AnyProviderIdEquals=imdb.{imdbId}&IncludeItemTypes=Series&Fields=ProviderIds
GET /Shows/{seriesId}/Episodes?Season=1&StartIndex=0&Limit=100
POST /Items/{movieOrEpisodeId}/PlaybackInfo
Content-Type: application/json
{}
The empty playback body uses the saved library policy. In the helper, profile is the playback request body: { DeviceProfile: actualPlayerDeviceProfile }. Supply a truthful DeviceProfile when the player knows its codec/container limits; do not advertise codecs it cannot decode. Keep all returned source choices and their real metadata. A successful response does not certify decoding, and missing codec metadata does not establish compatibility. BOSS provides original direct streams; it does not transcode or remux them.
Catalogue records may omit MediaSources. A narrow identity query with Fields=MediaSources or Fields=MediaStreams can enrich records, but this path can return an empty source list after a provider error. Use PlaybackInfo as the explicit resolution step so failures can be handled correctly. Refresh it after a stale URL/session failure; do not store stream URLs as permanent catalogue entries. Send only RequiredHttpHeaders to the provider, never the Jellyfin key/password; do not forward provider headers to an unrelated redirect origin. A legacy 307 redirect cannot attach those headers for the player.
Handle waiting and failure states
| Result | App behaviour |
|---|---|
| Title or episode unavailable | Show an honest unavailable state. Check the identity and episode selection. Missing metadata is negatively cached for up to 60 seconds; avoid rapid repeated lookups. |
| No compatible source | Keep the title visible and explain that no usable source is available now. Do not play the series container, invent a URL or claim a separate content request has completed. |
| 401 / 403 | Refresh login/details or show the access problem. Do not retry indefinitely or label it “no sources.” |
| 400 / 422 | Correct the input, unsupported query or player profile. Preserve the user's actual decoder requirements. |
| 409 | Reload the title and request fresh playback information after a library/source change. |
| 429 | Back off; respect Retry-After when provided. Coalesce duplicate taps and stop cancelled work. |
| 502 / 503 / 504 or network failure | Show a temporary resolution problem. Offer retry; use a small bounded retry budget with backoff rather than a fixed one-minute delay. Cold metadata/provider initialization can add latency. |
| Decode failure after media starts | Try another compatible returned source or report the unsupported codec/container. Do not repeatedly reopen the same undecodable media. |
Keep metadata lookup, episode selection, source resolution and actual playback as separate UI states. Test a movie outside the initial catalogue, a series episode outside it, missing metadata, unavailable sources, expired authentication and required provider headers. SDK/server tests verify those contracts; native app playback must also be checked on the target device.