# StreamShare Documentation — Full Markdown Context
> Generated convenience export. The individual Markdown documents remain the canonical transport representation.
# @streamshare/plugin-sdk v1.0.0
## Classes
- [PluginManifestValidationError](classes/PluginManifestValidationError.md)
- [PluginCompatibilityError](classes/PluginCompatibilityError.md)
## Interfaces
- [PluginRuntimeCapabilities](interfaces/PluginRuntimeCapabilities.md)
- [PluginRuntime](interfaces/PluginRuntime.md)
- [ToastOptions](interfaces/ToastOptions.md)
- [PluginFileStat](interfaces/PluginFileStat.md)
- [PluginFileScanOptions](interfaces/PluginFileScanOptions.md)
- [PluginFileScanResult](interfaces/PluginFileScanResult.md)
- [PluginSourceCatalogItem](interfaces/PluginSourceCatalogItem.md)
- [PluginSourceCatalogVariant](interfaces/PluginSourceCatalogVariant.md)
- [PluginSourceCatalogBatch](interfaces/PluginSourceCatalogBatch.md)
- [PluginSourceCatalogAppendResult](interfaces/PluginSourceCatalogAppendResult.md)
- [PluginSourceCatalogQuery](interfaces/PluginSourceCatalogQuery.md)
- [PluginSourceCatalogResult](interfaces/PluginSourceCatalogResult.md)
- [PluginList](interfaces/PluginList.md)
- [PluginListItemInput](interfaces/PluginListItemInput.md)
- [PluginListItem](interfaces/PluginListItem.md)
- [PluginListWithItems](interfaces/PluginListWithItems.md)
- [IPluginAPI](interfaces/IPluginAPI.md)
- [NetworkPermissions](interfaces/NetworkPermissions.md)
- [BackgroundTaskConstraints](interfaces/BackgroundTaskConstraints.md)
- [PluginBackgroundTask](interfaces/PluginBackgroundTask.md)
- [SubscriptionConfig](interfaces/SubscriptionConfig.md)
- [ConfigField](interfaces/ConfigField.md)
- [ActionInput](interfaces/ActionInput.md)
- [PluginAction](interfaces/PluginAction.md)
- [PluginManifest](interfaces/PluginManifest.md)
- [SystemEventContext](interfaces/SystemEventContext.md)
- [IAutomationPlugin](interfaces/IAutomationPlugin.md)
- [SourceSearchCriteria](interfaces/SourceSearchCriteria.md)
- [SourceCapabilities](interfaces/SourceCapabilities.md)
- [SourceInfo](interfaces/SourceInfo.md)
- [SourceTag](interfaces/SourceTag.md)
- [SourceMediaItem](interfaces/SourceMediaItem.md)
- [SourceFacets](interfaces/SourceFacets.md)
- [SourcePagedResult](interfaces/SourcePagedResult.md)
- [SourcePlaybackInfo](interfaces/SourcePlaybackInfo.md)
- [ISourcePlugin](interfaces/ISourcePlugin.md)
## Type Aliases
- [PluginConfiguration](type-aliases/PluginConfiguration.md)
- [PluginRuntimePlatform](type-aliases/PluginRuntimePlatform.md)
- [PluginRuntimeMode](type-aliases/PluginRuntimeMode.md)
- [PluginRuntimeReason](type-aliases/PluginRuntimeReason.md)
- [PluginType](type-aliases/PluginType.md)
- [PluginIconAppearance](type-aliases/PluginIconAppearance.md)
- [BackgroundNetworkType](type-aliases/BackgroundNetworkType.md)
- [PluginActionParameters](type-aliases/PluginActionParameters.md)
- [StreamShareAddon](type-aliases/StreamShareAddon.md)
- [SourceMediaType](type-aliases/SourceMediaType.md)
- [SourceSortOption](type-aliases/SourceSortOption.md)
## Functions
- [isValidNetworkPermission](functions/isValidNetworkPermission.md)
- [assertPluginManifest](functions/assertPluginManifest.md)
- [parsePluginManifest](functions/parsePluginManifest.md)
- [isPluginManifest](functions/isPluginManifest.md)
- [isPluginCompatibleWithApplication](functions/isPluginCompatibleWithApplication.md)
- [assertPluginCompatibleWithApplication](functions/assertPluginCompatibleWithApplication.md)
- [registerPlugin](functions/registerPlugin.md)
# Adapt a remote catalog
A source addon can translate an authorized website or remote catalog directly into
the StreamShare source contract. This design is useful when fresh results matter
more than offline browsing and the provider already exposes bounded search,
pagination and detail pages.
It also has deliberate limits. Without a local index, the addon only knows what it
has fetched. It must avoid claiming global filters, counts or availability that
cannot be derived from the provider response.
## Audit the provider before coding
Map the provider behavior to the public source contract before selecting HTML
elements or writing parsers:
| Provider behavior | Source contract |
| --- | --- |
| Search or category result | `search()`, `browse()` or `getRecent()` |
| Result-page cursor or page number | opaque `pageToken` |
| Movie or generic video | playable logical item |
| Series page | `series` container |
| Season page or section | `season` container |
| Episode row | playable `episode` item |
| Quality, language or edition page | playback variant candidate |
| Hosting-provider link | URL returned by `resolvePlayback()` |
Record which filters are applied by the provider before pagination, whether item
IDs survive sorting and page changes, and whether variants are present on one page
or linked from several pages. Test missing fields, empty pages and changed markup;
the most complete example is rarely representative of the whole catalog.
Only adapt content and endpoints you are authorized to access and expose.
## Separate logical media from playback variants
Normal discovery should return one logical item for a movie or episode. Do not
expand every quality and hosting provider into duplicate Media Hub results.
When the user opens source selection, StreamShare sends
`isSourceSelection: true`. A source advertising `supportsPlaybackVariants` can
then fetch the selected detail page and return one item per available variant.
Prefer `targetSourceItemId` over a display title: titles may have been localized
or enriched after discovery.
This separation keeps ordinary search bounded:
1. Fetch one requested result page.
2. Parse logical media and stable detail locators.
3. Fetch variant pages only for the selected item.
4. Return distinct stable IDs for the resulting variants.
5. Resolve only the chosen variant URL in `resolvePlayback()`.
If variants are split across several linked pages, deduplicate their URLs before
fetching them and use bounded concurrency. The source methods return a complete
promise; the current contract does not emit variants progressively. Avoid opening
every detail page during discovery just to prepare a source selector that the user
may never open.
`SourcePlaybackInfo` describes a URL, optional headers and playback hints. It does
not classify URLs as direct, external or redirector links. Return the
source-supported URL; do not invent an undocumented URL category in the addon.
Resolve expiring or signed URLs only after the user selects a variant.
## Make identity globally stable
An ID must be unique within the complete source, not merely within the current
page or parent. For example, `season:1` and `episode:1` collide as soon as a second
series is opened. Include stable provider identity at every level:
```text
series:
season::
episode:::
variant::
```
When no provider ID exists, derive an opaque ID from a normalized canonical detail
locator. Do not use an array position, a title alone or a temporary image URL.
Keep any locator needed for later browsing or source selection recoverable from
the ID or from addon-owned state.
Return explicit artwork for each item when the provider owns that artwork. A
season or episode must not rely on a previously opened series remaining in the
interface. Artwork URLs must be absolute and directly retrievable: source media
items cannot attach request headers to image fields. If images require a session,
referer or authorization header, prefer host metadata enrichment or another
authorized public image URL.
## Model series as a real hierarchy
Do not flatten series into a single playable record when the provider exposes
seasons and episodes. A typical hierarchy is:
```text
series (container)
└── season (container)
└── episode (playable logical item)
```
Some providers place links to other seasons inside a season page. Parse those
links as sibling season containers rather than treating the current page as the
complete series. Include `seriesTmdbId`, `seasonNumber` and `episodeNumber` when
known so the host can enrich each level correctly.
## Advertise only filters the provider really applies
A capability flag promises that the criterion is applied to the full matching
result before pagination. If the remote endpoint supports year and type but not a
reliable genre mapping, advertise only year and type.
Facets have the same scope rule. The years or genres found on page 1 are not the
facets of a ten-page result set. Omit `facets`, or honor `noFacets`, unless the
provider returns aggregate values or the addon has inspected the complete bounded
result. Partial facets are more misleading than no facets.
Propagate the provider cursor or page number through an opaque `pageToken` and
return bounded pages. Do not scan every remote page to simulate a capability that
the provider does not expose.
## Keep preferences, scope and exclusions distinct
Configuration names must describe their actual effect:
- A **preference** changes ordering. Non-preferred but valid variants remain
available.
- A **selection** or **allowed set** deliberately limits the requested qualities
or languages.
- An **ignore list** excludes matching providers.
- A **maximum** truncates results only when the user selects a non-zero limit.
Neutral defaults are usually the least surprising: return all valid variants,
ignore no provider and apply no maximum. Sort preferred languages, release origins
and hosting providers first, preserving the provider order as the final tie-break.
Use manifest `select` fields for stable, closed vocabularies. For values controlled
by a remote provider and likely to evolve, a normalized comma-separated text field
can remain forward-compatible. Trim values, compare case-insensitively and ignore
empty entries. Unknown providers should remain available unless the user explicitly
excluded them.
## Parse defensively
Remote markup is not a versioned API. Keep transport, parsing and contract mapping
separate so each can be tested independently.
- Convert relative links and artwork to absolute URLs against the configured base
URL.
- Prefer semantic attributes and stable link patterns over visual positions.
- Accept missing optional metadata and reject records without stable identity.
- Normalize whitespace, entities, quality labels and provider names in one place.
- Deduplicate variants by their stable link identity, not only by their label.
- Preserve unknown release origins and hosting providers as displayable values.
- When player data is embedded in page scripts, associate the parsed URL with the
current media and variant; do not return the first media-looking URL in the page.
- Log bounded diagnostic context without URLs containing credentials or tokens.
Build parser tests from small, sanitized fixtures that cover each known page shape.
Include at least a changed wrapper, missing artwork, an unknown provider, several
seasons, duplicate links and a result with no playable variants.
## Understand the HTTP boundary
`api.http.get()` retrieves text through the manifest-constrained HTTP capability.
It is suitable for server-rendered pages and documented text endpoints. It is not
a programmable browser session and provides no public contract for executing page
JavaScript, solving challenges, completing interactive authentication or sharing
browser cookies.
Development-time inspection in a browser can help identify the requests and markup
used by an authorized provider, but it does not add those browser capabilities to
the addon runtime. If access depends on an interactive challenge or complex client
execution, use an authorized stable endpoint or report the source unavailable;
do not depend on private host behavior.
A user-editable base URL does not automatically extend `permissions.network`.
Declare the finite allowed hosts whenever possible. If arbitrary origins are an
essential product requirement, the current manifest requires the broad `*`
permission, which users can review and which should be explained clearly.
## Use TMDB enrichment without coupling availability to it
Return provider TMDB or IMDb identifiers when they are explicitly available. If
they are absent, retain a clean title and production year for best-effort host
matching. Do not embed host credentials or scrape a metadata service merely to
duplicate host enrichment.
Returning identifiers enables enrichment; it does not mean the addon implements
direct lookup. Advertise `supportsTmdbLookup` only when incoming target identifiers
are actually applied before pagination. See [TMDB metadata and source
addons](./tmdb-metadata.md).
## Test the complete user path
Parser tests alone cannot validate a source addon. In development mode, exercise:
1. search, browse, recent items and next-page navigation;
2. movie, generic-video and complete series hierarchies;
3. artwork with cold and repeated navigation;
4. source selection with several qualities and providers;
5. preference ordering, explicit exclusions and unlimited defaults;
6. fallback when a preferred value is absent;
7. `resolvePlayback()` for every returned variant ID;
8. source selection from a unified result, including a similarly named remote item;
9. playback headers on every playback target claimed as supported;
10. malformed responses, timeouts and provider changes;
11. remote-control operation on a supported TV device when configuration is meant
for television use.
Use [development mode](./development-mode.md) for runtime logs and reloads, and
start with the deterministic [Example Catalog Source](../examples/catalog-source-addon.md)
before adding remote transport and parsing.
# Addon compatibility
An addon declares its compatible StreamShare application range in `manifest.compatibleVersion`. The CLI can check a target version before packaging, and the host rejects installation when that range does not include the running application version.
## Current release-candidate matrix
| SDK | StreamShare application | Android mobile/TV interactive | Android headless | iOS | Web |
| --- | --- | --- | --- | --- | --- |
| `1.0.0` | `^3.13.0` (tested through `3.13.1`) | Supported | Supported on API 26+ | Not implemented | Not implemented |
The machine-readable copy is distributed as `@streamshare/plugin-sdk/compatibility.json`. Until the packages are explicitly published, this remains a tested release candidate rather than a public availability commitment.
Before packaging an addon, run its normal validation command and target only an application range listed in this matrix. The CLI verifies the declared range against the selected application version.
# Addon manifest reference
Every addon package contains a `manifest.json` file at its root. The manifest is the install-time contract between the addon, the StreamShare host and the user: it identifies the addon, selects its runtime contract, declares its capabilities and describes the configuration and actions shown by the host.
This page explains the contract in context. For the exact TypeScript shape, follow the SDK's [`PluginManifest`](../api/interfaces/PluginManifest.md) interface and its linked field types.
The manifest is validated before the addon is packaged or installed. Unknown top-level properties are tolerated for forward compatibility, but they are not public API unless they are described here and present in the SDK types.
## Complete example
```json
{
"id": "com.example.catalog",
"name": "Example catalog",
"version": "1.2.0",
"compatibleVersion": "^3.13.0",
"type": "source",
"main": "dist/index.js",
"icon": "assets/icon.svg",
"iconAppearance": "original",
"author": "Example author",
"description": "Adds an example media catalog to StreamShare.",
"adult": false,
"permissions": {
"network": ["api.example.com", "*.media.example.com"],
"extended_runtime": false
},
"subscriptions": [
{"event": "app:started"}
],
"config": [
{
"key": "region",
"type": "select",
"label": "Catalog region",
"default": "eu",
"options": [
{"label": "Europe", "value": "eu"},
{"label": "North America", "value": "na"}
]
}
],
"actions": [
{
"id": "refresh",
"label": "Refresh catalog",
"description": "Downloads the latest catalog data."
}
],
"backgroundTasks": [
{
"id": "daily-refresh",
"action": "refresh",
"intervalMinutes": 1440,
"freshnessMinutes": 1080,
"flexMinutes": 360,
"runOnInstall": true,
"runOnConfigChange": true,
"sliceDurationMinutes": 8,
"constraints": {
"network": "connected",
"requiresBatteryNotLow": true,
"requiresStorageNotLow": true,
"requiresCharging": false,
"requiresDeviceIdleOnTv": false
}
}
]
}
```
Only declare optional properties that the addon actually uses. In particular, do not copy network permissions or background tasks from this example without a functional need.
## Identity and entry point
| Property | Required | Description and rules |
| --- | --- | --- |
| `id` | Yes | Globally unique and stable reverse-domain identifier, for example `com.example.catalog`. Changing it creates a different addon rather than an update. |
| `name` | Yes | Short human-readable name displayed by StreamShare. |
| `version` | Yes | Addon package version in strict semantic-version format, such as `1.2.0` or `2.0.0-beta.1`. |
| `compatibleVersion` | Yes | Semantic-version range of compatible StreamShare application versions, such as `^3.13.0`. See [addon compatibility](../compatibility.md). |
| `type` | Yes | Runtime contract implemented by the addon. Public values are `automation` and `source`; `player` is reserved and must not be used by third-party addons yet. |
| `main` | Yes | Package-relative path of the compiled JavaScript entry point. `dist/index.js` is recommended. This is an output path, not the TypeScript source path. |
| `icon` | No | Package-relative SVG, PNG or WebP file displayed by the host. See [Declaring an icon](#declaring-an-icon). |
| `iconAppearance` | No | Rendering policy for `icon`: `auto`, `monochrome` or `original`. Defaults to `auto`. |
| `author` | Yes | Publisher or maintainer name displayed to users. |
| `description` | Yes | Concise user-facing explanation of the addon's purpose. |
| `adult` | No | Set to `true` when every catalog entry and playback source exposed by the addon is intended for adults. StreamShare can then hide the addon and its data while parental controls are locked. Omitted values default to `false`. |
Paths in `main` and `icon` must stay inside the addon package. Absolute paths, URLs, parent traversal (`..`), query strings and fragments are rejected.
The `adult` declaration applies to the complete addon. It is not a per-item rating and does not replace accurate catalog metadata. An addon must not depend on this flag for access control: the host decides whether and how protected content is displayed.
The public addon contract does not require the addon to implement a PIN, maintain
an unlocked state or provide alternate artwork. Keep the manifest icon and
description suitable for a management screen that may remain visible while
content is locked. If one integration intentionally mixes public and adult
catalogs, split them into separate addons or conservatively declare the complete
addon as adult; there is currently no per-item adult declaration.
### Addon types
- `automation` implements `IAutomationPlugin` and can expose actions or react to subscribed host events.
- `source` implements `ISourcePlugin` and exposes a browsable or searchable media source.
- `player` has no public third-party contract yet. Its presence in the type union is reserved for future compatibility, not an invitation to implement it.
## Declaring an icon
Place the icon in the addon directory, preferably under `assets/`, and reference it from `manifest.json`:
```text
my-addon/
├── assets/
│ └── icon.svg
├── src/
│ └── index.ts
├── manifest.json
└── package.json
```
```json
{
"icon": "assets/icon.svg"
}
```
The icon is optional. When present:
- its extension must be `.svg`, `.png` or `.webp`;
- its path must be relative to the package root and must not escape the package;
- the file must exist when the CLI builds the addon;
- its maximum size is 512 KiB;
- the CLI includes the `assets/` directory in the ZIP automatically. An icon declared elsewhere is included individually;
- in development mode, the same relative path is served from the development server root.
Use `iconAppearance` only when the icon's visual construction requires an
explicit rendering policy:
- `auto` lets the host adapt ordinary SVG logos to the surrounding interface;
- `monochrome` requests a host-colored silhouette and is suitable for SVG marks
whose opaque shape forms the complete logo;
- `original` preserves the asset's colors and filled regions. Use it for badges,
multicolor artwork and SVG files with an opaque background.
Raster images are always rendered with their original pixels. Prefer `auto`
when either adaptive or original rendering is acceptable.
## Permissions
`permissions` is required even when the addon needs no privileged capability.
| Property | Required | Description and rules |
| --- | --- | --- |
| `permissions.network` | Yes | Array of permitted network hosts. Use `[]` when no network access is required. Values are exact hosts, leading wildcards such as `*.example.com`, or `*`. Schemes, ports and paths are not allowed. |
| `permissions.extended_runtime` | No | When `true`, allows explicitly long interactive work to use the host's extended time limit. It is not required for ordinary actions and does not make a task durable. |
Network permissions are reviewed during installation or update. See [Network access](../guides/network-access.md) for matching and least-privilege guidance.
## Event subscriptions
`subscriptions` is an optional array of host events delivered to the addon's `onEvent` method.
| Property | Required | Description and rules |
| --- | --- | --- |
| `subscriptions[].event` | Yes | Event identifier such as `app:started`, `plugin:installed`, `media:started`, `media:progress` or `media:ended`. Duplicate subscriptions are rejected. |
Declaring a subscription does not invoke an event by itself. The addon must implement the appropriate event handler and safely ignore event data it does not understand.
## Configuration fields
`config` is an optional array. StreamShare uses it to present persistent, user-editable addon settings.
| Property | Required | Description and rules |
| --- | --- | --- |
| `config[].key` | Yes | Stable identifier, unique within `config`. The addon uses this key with `api.getConfig()`. |
| `config[].type` | Yes | Editor and value type: `text`, `password`, `number`, `boolean` or `select`. |
| `config[].label` | Yes | Human-readable label displayed by the host. |
| `config[].default` | Yes | Initial value. Its JSON type must match `type`; a scalar `select` accepts one option value and a multiple select accepts an array of option values. |
| `config[].options` | For `select` | Non-empty array of `{label, value}` choices. Values must be unique and each value is a string or finite number. |
| `config[].multiple` | No | Set to `true` on a `select` to let users choose several values. It is rejected on other field types. |
Configuration keys are persistent API identifiers. Rename one only with an explicit data-migration strategy.
`api.getConfig()` returns a read-only string map. The host serializes number and boolean settings, so addon code should parse or compare them explicitly (`config.enabled === 'true'`, for example). Multiple select values are serialized as a JSON array; parse them with `JSON.parse()` and validate every entry against the choices declared by the addon. This keeps configuration behavior identical across interactive and headless runtimes.
## User actions and inputs
`actions` is an optional array of operations the host can present to the user.
| Property | Required | Description and rules |
| --- | --- | --- |
| `actions[].id` | Yes | Stable action identifier, unique within the addon. It is passed to `onAction`. |
| `actions[].label` | Yes | Short user-facing action label. |
| `actions[].description` | No | Explanation of the action and its effect. |
| `actions[].inputs` | No | Values the host collects before invoking the action. Input keys must be unique within the action. |
| `actions[].inputs[].key` | Yes | Stable key in the `params` object passed to `onAction`. |
| `actions[].inputs[].type` | Yes | `text`, `password`, `number`, `boolean` or `select`. |
| `actions[].inputs[].label` | Yes | Human-readable input label. |
| `actions[].inputs[].default` | No | Initial value whose JSON type must match the input type. |
| `actions[].inputs[].options` | For `select` | Non-empty, unique `{label, value}` choices. A provided default must match one of them. |
Treat action and input identifiers as part of the public contract between the manifest and addon code.
## Background tasks
`backgroundTasks` is an optional array of durable Android work. Every task references an action declared in the same manifest.
| Property | Required | Description and rules |
| --- | --- | --- |
| `backgroundTasks[].id` | Yes | Stable task identifier, unique within the addon. |
| `backgroundTasks[].action` | Yes | ID of an entry in `actions`. Unknown action references are rejected. |
| `backgroundTasks[].intervalMinutes` | Yes | Integer repeat interval of at least 15 minutes. Execution remains inexact and controlled by the operating system. |
| `backgroundTasks[].freshnessMinutes` | No | Skip delivery while the last success is newer than this value. Integer from `0` through `intervalMinutes`. |
| `backgroundTasks[].flexMinutes` | No | Flexible execution window. Integer from `5` through `intervalMinutes`. |
| `backgroundTasks[].runOnInstall` | No | Schedule an initial one-time run after installation when `true`. |
| `backgroundTasks[].runOnConfigChange` | No | Schedule a one-time run after configuration is saved when `true`. |
| `backgroundTasks[].sliceDurationMinutes` | No | Cooperative execution slice from 1 to 9 minutes. The addon must checkpoint before yielding. |
| `backgroundTasks[].constraints` | No | Operating-system scheduling conditions described below. |
| `constraints.network` | No | `connected` or `unmetered`. |
| `constraints.requiresBatteryNotLow` | No | Defer while Android reports a low battery. |
| `constraints.requiresStorageNotLow` | No | Defer while Android reports low storage. |
| `constraints.requiresCharging` | No | Require the device to be charging. |
| `constraints.requiresDeviceIdleOnTv` | No | Require Android device-idle mode on television devices only. |
See [Background tasks](../guides/background-tasks.md) before using this capability and consult the [compatibility matrix](../compatibility.md) for currently implemented addon hosts.
## Validation APIs
The CLI and StreamShare host use the SDK's canonical runtime validator. Tools that read manifests should use `parsePluginManifest`, `assertPluginManifest` or `isPluginManifest` rather than maintaining another schema. See [Validate a manifest](../guides/manifest-validation.md).
# Background tasks
Background tasks are Android-only, durable requests declared in `manifest.backgroundTasks`. The current addon host is implemented for Android mobile and TV; iOS and web addon runtimes are not yet public implementations.
```json
{
"backgroundTasks": [
{
"id": "daily-refresh",
"action": "refresh",
"intervalMinutes": 1440,
"flexMinutes": 360,
"runOnInstall": true,
"constraints": {
"network": "connected",
"requiresBatteryNotLow": true
}
}
]
}
```
Scheduling is inexact. The operating system may delay work because of battery, network, idle, storage, or vendor policies. Correctness must never depend on an exact execution time.
## Cooperative continuation
Process work in bounded, idempotent batches. Save a checkpoint before asking the host to continue later:
```ts
if (api.runtime.shouldYield()) {
await saveCheckpoint();
api.runtime.requestContinuation();
return;
}
```
An operating system can terminate a process without a final callback. A task must be able to replay its last incomplete batch safely.
Check `api.runtime.capabilities.backgroundTasks` before presenting platform-specific behavior and use `api.runtime.reason` only to adapt presentation, not correctness. See the complete [runtime capability matrix](../concepts/runtime-capabilities.md).
# Build a source addon
A source addon exposes logical media to StreamShare without writing into the
application database. The host asks the addon what it supports, requests bounded
pages of media and resolves a selected logical item only when playback is needed.
## Lifecycle
One source invocation follows this sequence:
1. StreamShare creates an isolated addon instance and calls `onInit(api)`.
2. `getSourceInfo()` returns stable identity, availability and capabilities.
3. The host calls `search()`, `browse()` or `getRecent()` according to those capabilities.
4. When the user selects playable media, `resolvePlayback(sourceItemId)` returns the resource.
An addon must not perform expensive synchronization in `getSourceInfo()`. Return
`isAvailable: false` when required configuration or local state is missing.
## Advertise only implemented capabilities
[`SourceCapabilities`](../api/interfaces/SourceCapabilities.md) controls which
operations and filters the host may present. A `true` flag is a behavioral promise:
the corresponding criteria must be applied before pagination.
For example, a source that filters types and years but has no metadata-provider
lookup should report:
```ts
capabilities: {
supportsSearch: true,
supportsBrowse: true,
supportsTypeFilter: true,
supportsGenreFilter: false,
supportsYearFilter: true,
supportsTmdbLookup: false,
supportedSorts: ['relevance', 'date', 'rating', 'title'],
supportedTypes: ['movie', 'series', 'season', 'episode', 'folder', 'video'],
}
```
## Keep logical IDs stable
Every [`SourceMediaItem`](../api/interfaces/SourceMediaItem.md) needs an ID that
remains stable across searches, pages and application restarts. StreamShare passes
that ID back to the addon for browsing and playback. Do not use a page position,
translated title or temporary URL as the logical ID.
Set `isContainer: true` for folders, series and seasons that should open another
browse level. Return playable movies and episodes with `isContainer: false`.
Use `video` with `isContainer: false` for generic playable content that has no
movie or episodic TMDB identity, such as a documentary feed or television clip.
Its stable `id` follows the same `resolvePlayback()` lifecycle as other playable
items. Advertising `video` in `supportedTypes` lets the host include the source
in generic-video searches.
## Filter, sort, then paginate
Apply every supported criterion and the requested sort before slicing a page.
Treat `pageToken` as opaque at the API boundary: your addon may use an offset,
cursor or signed provider token, while the host passes it back unchanged.
Normalize `rating` to the inclusive zero-to-ten scale used by the source
contract. For example, multiply a five-star provider score by two. Omit the field
when the provider has no reliable rating instead of substituting zero.
```ts
const hasMore = offset + limit < matchingItems.length;
return {
items: matchingItems.slice(offset, offset + limit),
hasMore,
nextPageToken: hasMore ? String(offset + limit) : undefined,
};
```
Keep pages bounded even when the host omits `limit`. Facets must describe the
matching result set, not only the current page.
## Separate discovery from playback
Search and browse should return logical metadata. Resolve the final URL and any
required HTTP headers in `resolvePlayback()` only after the user selects an item.
Reject unknown IDs and containers instead of returning a guessed resource.
When source selection uses a text-query fallback, verify the returned title, year
and episodic context before attaching variants to the selected media. Never assume
that the first remote search result is the requested item. Prefer the addon's own
`targetSourceItemId`, and keep every returned ID independently resolvable after an
addon reload rather than relying on a previous in-memory search result.
Return media request headers through `SourcePlaybackInfo.headers`. StreamShare
forwards them to playback targets that support custom headers, but external targets
may differ; test each platform the addon claims to support. Resolve signed or
short-lived media URLs at playback time instead of storing them as stable IDs.
Network permissions cover requests performed by addon APIs such as `api.http`.
They do not replace the developer's responsibility to distribute only media and
credentials they are authorized to expose.
## Describe playback quality
Playable [`SourceMediaItem`](../api/interfaces/SourceMediaItem.md) records can
provide the metadata rendered in source selection:
| Field | Example | Meaning |
| --- | --- | --- |
| `resolution` | `1080p`, `2160p` | Video resolution or quality tier |
| `source` | `WEB-DL`, `BluRay` | Distribution or provider origin |
| `encoding` | `H.264`, `HEVC`, `AV1` | Video codec or encoding |
| `language` | `MULTI`, `VO`, `VOSTFR` | Audio or content-language label |
| `qualityLabel` | `1080p WEB-DL HEVC` | Composite fallback for legacy or unstructured data |
| `tags` | `[{label: 'Host A', color: '#38BDF8'}]` | Short addon-defined badges for provider-specific metadata |
Prefer the structured fields whenever they are known. StreamShare renders them as
separate badges and uses `qualityLabel` only when resolution, source and encoding
are absent.
```ts
{
id: 'provider:movie:42',
type: 'movie',
title: 'Example movie',
isContainer: false,
resolution: '2160p',
source: 'WEB-DL',
encoding: 'HEVC',
language: 'MULTI',
tags: [{label: 'Host A', color: '#38BDF8'}],
}
```
Keep `source` for the distribution origin rather than the hosting provider. Use
`tags` when a provider, edition or other source-specific value needs its own
badge. Tag colors are optional six-digit hexadecimal values. Hosts validate
colors, limit the number and length of tags, and choose readable foreground
text automatically.
For several playable variants of the same media, keep one logical record in normal
search and browse results. Advertise `supportsPlaybackVariants: true`, then return
one item per variant when `search()` receives `isSourceSelection: true`. Each
variant needs a stable distinct `id`; StreamShare passes the selected variant ID to
`resolvePlayback()`.
When the selected media already came from the receiving source,
`targetSourceItemId` contains that source's previous opaque item ID. Prefer it over
`query` to locate the logical record: host enrichment can localize or replace the
displayed title before source selection. The value is source-specific and must not
be interpreted by another source. A returned variant can copy `targetTmdbId` when
the source ID confirms that it belongs to that requested media. Do not copy the
requested metadata ID onto an unverified text-search result merely to force a
match.
For addons backed by `api.sourceCatalog`, translate `targetSourceItemId` to the
catalog query's exact `mediaId`; do not combine that exact lookup with enriched
title or year filters.
Do not advertise playback variants when every logical media item has only one
playable representation. Missing structured metadata is displayed as an unknown
quality rather than being guessed by the interface.
For rich titles, artwork and episode metadata, follow [TMDB metadata and source
addons](./tmdb-metadata.md). Returning a TMDB identifier enables host enrichment;
it does not require embedding TMDB credentials in the addon.
## Start from the reference implementation
The [Example Catalog Source](../examples/catalog-source-addon.md) demonstrates the
complete lifecycle with deterministic in-memory data, two-level pagination and no
external dependency. Its contract tests are suitable as a starting point for a
real provider adapter.
When the provider is queried on demand instead of synchronized into a local
catalog, continue with [Adapt a remote catalog](./remote-source-addons.md) for
variant loading, hierarchy, facets, configuration and HTTP limitations.
# Class: PluginCompatibilityError
Error raised when an addon does not support the running StreamShare version.
## Extends
- `Error`
## Constructors
### Constructor
> **new PluginCompatibilityError**(`applicationVersion`, `compatibleVersion`): `PluginCompatibilityError`
#### Parameters
##### applicationVersion
`string`
##### compatibleVersion
`string`
#### Returns
`PluginCompatibilityError`
#### Overrides
`Error.constructor`
## Properties
### applicationVersion
> `readonly` **applicationVersion**: `string`
Application version that was checked.
***
### compatibleVersion
> `readonly` **compatibleVersion**: `string`
Compatibility range declared by the addon.
***
### cause?
> `optional` **cause?**: `unknown`
#### Inherited from
`Error.cause`
***
### name
> **name**: `string`
#### Inherited from
`Error.name`
***
### message
> **message**: `string`
#### Inherited from
`Error.message`
***
### stack?
> `optional` **stack?**: `string`
#### Inherited from
`Error.stack`
# Class: PluginManifestValidationError
Error raised when an unknown value violates the public addon manifest contract.
## Extends
- `Error`
## Constructors
### Constructor
> **new PluginManifestValidationError**(`message`, `field?`): `PluginManifestValidationError`
#### Parameters
##### message
`string`
##### field?
`string`
#### Returns
`PluginManifestValidationError`
#### Overrides
`Error.constructor`
## Properties
### field?
> `readonly` `optional` **field?**: `string`
Dot-separated field path associated with the failure when available.
***
### cause?
> `optional` **cause?**: `unknown`
#### Inherited from
`Error.cause`
***
### name
> **name**: `string`
#### Inherited from
`Error.name`
***
### message
> **message**: `string`
#### Inherited from
`Error.message`
***
### stack?
> `optional` **stack?**: `string`
#### Inherited from
`Error.stack`
# Create your first addon
## Install the development packages
```bash
pnpm add @streamshare/plugin-sdk
pnpm add --save-dev @streamshare/plugin-cli typescript
```
## Define the manifest
Create `manifest.json` at the package root. Its supported shape is defined by the SDK's [`PluginManifest`](./api/interfaces/PluginManifest.md) interface; the [manifest reference](./concepts/manifest.md) explains how each property is used and validated.
```json
{
"id": "com.example.hello",
"name": "Hello StreamShare",
"version": "1.0.0",
"compatibleVersion": "^3.13.0",
"type": "automation",
"main": "dist/index.js",
"author": "Example author",
"description": "A minimal StreamShare addon",
"permissions": {
"network": []
},
"actions": [
{
"id": "hello",
"label": "Say hello"
}
]
}
```
Use a globally unique, stable ID. Declare only the network hosts and runtime capabilities the addon actually needs.
This minimal example has no icon. To add one, place an SVG, PNG or WebP file in `assets/` and declare `"icon": "assets/icon.svg"`.
## Implement the addon
```ts
import {
type IAutomationPlugin,
type IPluginAPI,
type PluginActionParameters,
registerPlugin,
type SystemEventContext,
} from '@streamshare/plugin-sdk';
class HelloAddon implements IAutomationPlugin {
private api!: IPluginAPI;
async onInit(api: IPluginAPI): Promise {
this.api = api;
}
async onEvent(_event: SystemEventContext): Promise {}
async onAction(_params: PluginActionParameters, actionId: string): Promise {
if (actionId === 'hello') {
this.api.toast({message: 'Hello from the addon', color: 'success'});
}
}
}
registerPlugin(new HelloAddon());
```
## Add scripts
```json
{
"scripts": {
"validate": "streamshare-plugin-cli validate --app-version 3.13.1",
"dev": "streamshare-plugin-cli dev",
"build": "streamshare-plugin-cli build"
}
}
```
Run `pnpm validate` before targeting a specific StreamShare version, `pnpm dev` while testing locally and `pnpm build` to create an installation ZIP. Follow the [development-mode guide](./guides/development-mode.md) to connect a supported StreamShare device, receive reloads and inspect logs.
## Next steps
- Review the [public addon contract](./concepts/public-contract.md).
- Read the complete [manifest reference](./concepts/manifest.md).
- Detect optional APIs through [runtime capabilities](./concepts/runtime-capabilities.md).
- Connect StreamShare using [development mode](./guides/development-mode.md).
- Give an assistant authoritative context with [AI-assisted development](./guides/ai-assisted-development.md).
- Explore the complete [Hello addon](./examples/hello-addon.md).
- Build a searchable catalog with the [Example Catalog Source](./examples/catalog-source-addon.md).
- Learn the [source-addon lifecycle](./guides/source-addons.md).
- Adapt an authorized [remote catalog without a local index](./guides/remote-source-addons.md).
- Integrate with [StreamShare TMDB enrichment](./guides/tmdb-metadata.md).
- Declare [network access](./guides/network-access.md) narrowly.
- Use [background tasks](./guides/background-tasks.md) only for durable Android work.
- Consult the [SDK API reference](./api/index.md).
# Develop addons with AI assistants
An AI assistant can accelerate contract mapping, scaffolding, parser tests and
debugging. It cannot decide whether content access is authorized, approve
permissions, protect undisclosed credentials or replace testing on a supported
StreamShare host. Treat generated code as a proposed change that remains subject
to developer review.
The safest workflow gives the assistant a small set of authoritative public
sources and requires it to distinguish documented behavior from assumptions.
## Start with the public documentation index
StreamShare publishes two AI-readable entry points:
- [`llms.txt`](https://docs.tootiapps.com/llms.txt) is a compact index of public
documentation pages with direct Markdown links and descriptions.
- [`llms-full.txt`](https://docs.tootiapps.com/llms-full.txt) concatenates the
complete public documentation for tools that work better with one context file.
Prefer `llms.txt` first. Ask the assistant to retrieve only the pages relevant to
the task, such as the public contract, manifest, compatibility matrix, source guide
and exact SDK interface. This reduces unrelated context and makes citations easier
to verify. Use `llms-full.txt` when the assistant cannot follow links or when the
task genuinely spans most of the documentation.
These files are documentation transports, not additional SDK APIs. The installed
package version, generated API reference and compatibility page remain decisive
when a model's prior knowledge conflicts with them.
## Provide enough project context
Give the assistant:
- the desired observable behavior and addon type;
- the target StreamShare application version;
- `manifest.json`, `package.json` and the relevant source files;
- current validation, typecheck or runtime errors;
- small sanitized provider responses or fixtures when parsing is involved;
- constraints such as supported platforms, latency goals and whether local state
is allowed.
Do not provide passwords, API tokens, session cookies, private user data or large
copyrighted page dumps. Replace secrets with named placeholders and reduce remote
responses to the smallest fixture that reproduces the shape under test.
## Use a contract-first prompt
A useful initial prompt is:
```text
Build a StreamShare source addon for an authorized remote catalog.
Target application version: .
Read https://docs.tootiapps.com/llms.txt first, then retrieve only the relevant
addon Markdown pages and exact SDK API declarations. Use documented public APIs
only. If a required capability is absent, identify the limitation instead of
inventing an API or depending on host internals.
Before editing code:
1. map the requested behavior to SourceCapabilities and SourceMediaItem;
2. define stable IDs and the container hierarchy;
3. separate discovery from playback variants;
4. list required manifest permissions and configuration semantics;
5. propose deterministic tests and device-level checks.
After editing, run manifest validation, typechecking, tests and the addon build.
Report assumptions, remaining limitations and every changed public permission.
```
For an existing addon, also ask the assistant to preserve unrelated changes and
to diagnose a failure from evidence before rewriting the implementation.
## Work in verifiable increments
Use the assistant for one observable slice at a time:
1. Validate the manifest and establish a minimal registered addon.
2. Implement stable logical discovery with deterministic fixtures.
3. Add pagination and only the capabilities the provider applies globally.
4. Model series, seasons and episodes as distinct IDs and containers.
5. Add source-selection variants and `resolvePlayback()`.
6. Add configuration, with preferences separated from filters and exclusions.
7. Test in development mode on every supported interaction model.
Require a test or a concrete runtime observation for each slice. Small steps make
it easier to identify whether a regression comes from parsing, identity, source
mapping, configuration or presentation.
Before asking for a fix, identify the failing boundary: remote response, addon
mapping, published SDK contract or host behavior. Give the assistant the received
criteria, returned opaque ID and resolved result when they are relevant. Browser
inspection can establish the remote page shape, but it does not prove that the
same request, image or media URL works in the addon runtime or on every playback
target.
## Ask the assistant to challenge common mistakes
An AI review should explicitly check for:
- APIs or manifest fields that do not exist in the published SDK;
- capabilities advertised without applying their criteria before pagination;
- page positions, titles or sibling-only values used as IDs;
- the first remote search result accepted without verifying media identity;
- duplicate logical results for qualities that belong in source selection;
- eager detail-page requests during ordinary search;
- language, quality or provider **preferences** implemented as filters;
- fixed select options for a provider-controlled vocabulary that can evolve;
- partial facets computed from the current page only;
- host enrichment confused with `supportsTmdbLookup`;
- image URLs that require headers the artwork contract cannot carry;
- signed playback URLs stored as durable item IDs;
- configuration values used without parsing their serialized string form;
- broad network permissions added only to hide an allowlist error;
- credentials, signed URLs or remote response bodies written to logs;
- generated documentation or host internals treated as editable public source.
When the assistant identifies a missing capability, ask it to describe a fallback
using current public contracts and to state the user-visible trade-off. Do not ask
it to reach into undocumented application behavior.
## Validate generated work
At minimum, run manifest validation and the addon build. Run the project's test
suite when it declares one:
```bash
pnpm validate
pnpm build
pnpm test # when the project defines this script
```
Then connect through [development mode](./development-mode.md) and test the real
user path. A successful typecheck does not prove correct pagination, stable
identity, remote-control operation, provider tolerance or playback resolution.
Review the final diff and answer these questions before distribution:
- Does every permission correspond to an observable requirement?
- Are configuration defaults neutral and labels semantically accurate?
- Can unknown remote values remain usable?
- Are errors bounded, understandable and free of secrets?
- Does the addon degrade safely when the provider changes or is unavailable?
- Is every external resource authorized for this use?
AI assistance changes development speed, not responsibility. A human maintainer
remains accountable for security, rights, compatibility, testing and publication.
# Development mode
Development mode runs an addon directly from your computer. The plugin CLI validates the manifest, bundles the entry point, serves the project over HTTP and reloads connected addon sandboxes when watched files change.
This workflow is currently verified with StreamShare's Android mobile and TV addon runtime. The public compatibility matrix will identify additional supported hosts as they become available.
## Start the local server
Your package scripts should include:
```json
{
"scripts": {
"validate": "streamshare-plugin-cli validate --app-version 3.13.1",
"dev": "streamshare-plugin-cli dev",
"build": "streamshare-plugin-cli build"
}
}
```
From the addon directory, run:
```bash
pnpm dev
```
The server listens on all local interfaces at port `3000` by default. Use `--port 3100` or set the `PORT` environment variable to select another port. The CLI prints the reachable `IP:port` values to enter on the device.
The CLI then:
1. validates `manifest.json`, including the declared icon;
2. finds `src/index.ts`, `src/main.ts` or `src/index.js` as the source entry point;
3. bundles it to the path declared by `manifest.main`, with an inline source map;
4. serves `manifest.json`, the compiled script and project assets over HTTP;
5. watches `src/`, `assets/` and `manifest.json`;
6. sends a reload notification over WebSocket after a successful rebuild;
7. prints `console.log`, `console.warn` and `console.error` messages forwarded by the addon runtime.
Before connecting a particular application build, validate the manifest range explicitly:
```bash
pnpm validate
```
You can confirm that the server is reachable on the development computer:
```text
http://localhost:3000/manifest.json
http://localhost:3000/dist/index.js
```
The second path must match the `main` property in your manifest.
## Connect StreamShare
The device and development computer must be able to reach each other on the local network.
1. Find the development computer's LAN address, for example `192.168.1.50`.
2. In StreamShare, open **Settings → Extensions → Developer Mode**.
3. Enable developer mode.
4. Enter the server as `IP:port`, for example `192.168.1.50:3000`. Do not enter a protocol or path.
5. Select **Connect / reload manifest** and review the requested permissions.
On a physical phone or TV, `localhost` refers to that device, not to the development computer. Use the computer's reachable LAN address and allow the selected port through its firewall when necessary.
After approval, StreamShare creates or updates a virtual addon entry marked `[DEV]`, loads the script from the CLI server and dispatches `plugin:installed` to that addon.
## Reload behavior
Changes under `src/`, `assets/` or to `manifest.json` trigger a rebuild. After a successful build, the CLI asks connected runtime sandboxes to reload.
Script and asset edits are therefore delivered automatically to an active development runtime. A manifest edit changes permissions or host-provided UI metadata, so select **Connect / reload manifest** again to make StreamShare fetch, validate and approve the new manifest.
If an action or event is not currently running, trigger it again after the rebuild to create a new runtime execution with the latest bundle.
## Icons in development mode
An icon declared as `"icon": "assets/icon.svg"` is available at:
```text
http://192.168.1.50:3000/assets/icon.svg
```
The host resolves the icon from the server root, applies the same supported-format and 512 KiB limits as an installed addon, and does not retain development icons across reconnects or rebuilds. See the [manifest icon reference](../concepts/manifest.md#declaring-an-icon).
## Permissions and local-network safety
The development server itself is allowed as the source of the manifest, bundle, icon and reload connection. Other outbound requests from the addon remain constrained by `permissions.network`.
The CLI development server binds to the local network, uses unencrypted HTTP/WebSocket connections and has no authentication. Use it only on a trusted development network, stop it when finished, and do not expose it directly to the internet.
Disabling developer mode removes virtual development addons from StreamShare. It does not delete the project or any locally built ZIP from the development computer.
## Troubleshooting
| Symptom | Check |
| --- | --- |
| StreamShare cannot connect | Confirm `pnpm dev` is still running, use the computer's LAN IP rather than `localhost`, verify the port and check the firewall. |
| Manifest rejected | Read the CLI or application error, then compare the file with the [manifest reference](../concepts/manifest.md) and [validation rules](./manifest-validation.md). |
| Script returns 404 | Ensure `manifest.main` matches the generated URL, normally `dist/index.js`. |
| Icon is missing | Confirm `icon` is relative to the project root, the file exists, and its case and extension match exactly. |
| Change does not affect permissions or actions | Reconnect the manifest; automatic reload refreshes the bundle but does not silently approve a changed contract. |
| Addon request is blocked | Add the exact required host to `permissions.network`; never add `*` merely to bypass diagnosis. |
# Example Catalog Source
The Example Catalog Source is the maintained reference for the public `source`
contract. It combines Blender Foundation open movies carrying TMDB identifiers
with a deterministic fictional series hierarchy and a generic-video record. The
addon performs no network request and depends only on the published addon
contract.
## Manifest
```json
{
"id": "com.tootiapps.streamshare.catalog-example",
"name": "Example Catalog Source",
"version": "1.0.0",
"compatibleVersion": "^3.13.0",
"type": "source",
"main": "dist/index.js",
"author": "TootiApps",
"description": "A deterministic catalog demonstrating the public source-addon contract.",
"permissions": {
"network": []
}
}
```
An empty network allowlist is intentional: the example never calls `api.http`.
StreamShare enriches movie records through its own TMDB integration and opens the
Blender media URL only after playback resolution. The fictional episode URLs under
`example.com` remain non-working placeholders.
## Source identity
`getSourceInfo()` advertises exactly the implemented behavior. In particular, the
example supports local type/year filtering and direct TMDB lookup, but does not
claim genre filtering.
```ts
getSourceInfo(): SourceInfo {
return {
id: 'example-catalog',
name: 'Example Catalog',
isAvailable: true,
capabilities: {
supportsSearch: true,
supportsBrowse: true,
supportsTypeFilter: true,
supportsGenreFilter: false,
supportsYearFilter: true,
supportsTmdbLookup: true,
supportedSorts: ['relevance', 'date', 'rating', 'title'],
supportedTypes: ['movie', 'series', 'season', 'episode', 'folder', 'video'],
},
};
}
```
## TMDB enrichment and direct lookup
The movie records intentionally omit artwork, overview, genres and rating. Their
stable TMDB IDs let StreamShare provide those fields in the current application
language:
```ts
{
id: 'movie:big-buck-bunny',
type: 'movie',
title: 'Big Buck Bunny',
isContainer: false,
tmdbId: 10378,
productionYear: 2008,
}
```
The source also advertises `supportsTmdbLookup: true` because it applies
`targetTmdbId` and `parentTmdbId` criteria before pagination. Merely returning a
`tmdbId` for host enrichment would not be enough to advertise this capability.
See [TMDB metadata and source addons](../guides/tmdb-metadata.md) for the complete
distinction.
The generic-video record intentionally has no TMDB identity. It demonstrates
that `video` is a first-class playable type when a source advertises it:
```ts
{
id: 'video:cosmos-livestream',
type: 'video',
title: 'Cosmos Livestream',
isContainer: false,
qualityLabel: '1080p live',
language: 'VO',
}
```
## Source-selection metadata
Every playable demonstration item provides structured technical metadata. These
labels are illustrative and show how the source-selection interface presents each
field independently:
```ts
{
id: 'episode:orbit:1:1',
type: 'episode',
title: 'Arrival',
isContainer: false,
seasonNumber: 1,
episodeNumber: 1,
resolution: '2160p',
source: 'WEB-DL',
encoding: 'HEVC',
language: 'MULTI',
}
```
The reference catalog exposes one playable representation per item, so it does not
advertise `supportsPlaybackVariants`. A provider with several encodes should return
one logical item during discovery and expand it into stable variant IDs only when
`isSourceSelection` is requested. See [Describe playback quality](../guides/source-addons.md#describe-playback-quality).
## Search and pagination
The reference catalog filters and sorts the complete result before applying a
bounded page. Its next-page token is a string offset, but consumers must treat it
as opaque.
```ts
async search(criteria: SourceSearchCriteria, pageToken?: string | number) {
const matchingItems = filterItems(criteria);
const result = paginate(matchingItems, pageToken, criteria.limit);
result.facets = {
years: [...new Set(matchingItems
.map((item) => item.productionYear)
.filter((year): year is number => year !== undefined))]
.sort((left, right) => right - left),
};
return result;
}
```
## Browse hierarchy
The root contains `Movies`, `Series` and `Videos` folders. Selecting the example
series navigates through a season to two playable episodes. Unknown parents
return an empty page rather than leaking an implementation error.
## Playback resolution
Only known, playable logical IDs resolve. Containers and unknown IDs fail clearly.
```ts
async resolvePlayback(sourceItemId: string): Promise {
const url = PLAYBACK_URLS[sourceItemId];
if (!url) throw new Error(`No playable media exists for '${sourceItemId}'.`);
return {url};
}
```
The movie map uses openly distributed Blender Foundation resources. Replace every
URL with media you own or are authorized to distribute when adapting the example.
The fictional episode URLs are placeholders and are not expected to play.
## Validate locally
From the example project directory:
```bash
pnpm validate
pnpm test
pnpm dev
```
The tests cover capabilities, hierarchy, filtering, sorting, pagination, recents
and playback resolution. Follow [Build a source addon](../guides/source-addons.md)
for the design rules behind each method.
# Function: assertPluginCompatibleWithApplication()
> **assertPluginCompatibleWithApplication**(`manifest`, `applicationVersion`): `void`
Rejects an addon manifest whose compatibility range excludes the application.
## Parameters
### manifest
`Pick`\<[`PluginManifest`](../interfaces/PluginManifest.md), `"compatibleVersion"`\>
### applicationVersion
`string`
## Returns
`void`
## Throws
[PluginCompatibilityError](../classes/PluginCompatibilityError.md) when the versions are incompatible.
# Function: assertPluginManifest()
> **assertPluginManifest**(`value`): `asserts value is PluginManifest`
Validates an unknown value and narrows it to the public addon manifest type.
Unknown additional object fields are retained for forward compatibility.
## Parameters
### value
`unknown`
## Returns
`asserts value is PluginManifest`
## Throws
[PluginManifestValidationError](../classes/PluginManifestValidationError.md) when validation fails.
# Function: isPluginCompatibleWithApplication()
> **isPluginCompatibleWithApplication**(`manifest`, `applicationVersion`): `boolean`
Tests whether an addon manifest supports one exact StreamShare application version.
## Parameters
### manifest
`Pick`\<[`PluginManifest`](../interfaces/PluginManifest.md), `"compatibleVersion"`\>
### applicationVersion
`string`
## Returns
`boolean`
# Function: isPluginManifest()
> **isPluginManifest**(`value`): `value is PluginManifest`
Tests whether an unknown value satisfies the public addon manifest contract.
## Parameters
### value
`unknown`
## Returns
`value is PluginManifest`
# Function: isValidNetworkPermission()
> **isValidNetworkPermission**(`value`): `value is string`
Tests whether a manifest network entry is a supported host allowlist pattern.
## Parameters
### value
`unknown`
## Returns
`value is string`
# Function: parsePluginManifest()
> **parsePluginManifest**(`value`): [`PluginManifest`](../interfaces/PluginManifest.md)
Validates and returns an unknown value as a public addon manifest.
## Parameters
### value
`unknown`
## Returns
[`PluginManifest`](../interfaces/PluginManifest.md)
# Function: registerPlugin()
> **registerPlugin**(`addon`): `void`
Registers an addon instance with the StreamShare runtime.
Call this once from the package entry point after creating the addon instance.
## Parameters
### addon
[`StreamShareAddon`](../type-aliases/StreamShareAddon.md)
## Returns
`void`
## Throws
When the bundle is executed outside a StreamShare addon runtime.
# Hello addon example
The Hello addon is the smallest complete StreamShare automation addon. It demonstrates configuration, an event subscription, a user action, and typed host registration without requesting network access.
## Manifest
```json
{
"id": "com.tootiapps.streamshare.hello",
"name": "Hello StreamShare",
"version": "1.0.0",
"compatibleVersion": "^3.13.0",
"type": "automation",
"main": "dist/index.js",
"author": "TootiApps",
"description": "A minimal addon demonstrating configuration, events and actions.",
"permissions": {
"network": []
},
"subscriptions": [
{"event": "app:started"}
],
"config": [
{
"key": "greeting",
"type": "text",
"label": "Greeting",
"default": "Hello"
}
],
"actions": [
{
"id": "say_hello",
"label": "Say hello",
"description": "Displays a greeting using the configured message.",
"inputs": [
{
"key": "recipient",
"type": "text",
"label": "Recipient",
"default": "StreamShare"
}
]
}
]
}
```
An empty network list is intentional. Addons should not request capabilities they do not need.
The example omits the optional `icon` property to keep the package minimal. See [Declaring an icon](../concepts/manifest.md#declaring-an-icon) for the recommended `assets/icon.svg` layout and packaging rules.
## Entry point
```ts
import {
type IAutomationPlugin,
type IPluginAPI,
type PluginActionParameters,
registerPlugin,
type SystemEventContext,
} from '@streamshare/plugin-sdk';
class HelloAddon implements IAutomationPlugin {
private api!: IPluginAPI;
async onInit(api: IPluginAPI): Promise {
this.api = api;
this.api.logger.info('Hello addon initialized.');
}
async onEvent(event: SystemEventContext): Promise {
if (event.eventName === 'app:started') {
this.api.logger.info('StreamShare started.');
}
}
async onAction(params: PluginActionParameters, actionId: string): Promise {
if (actionId !== 'say_hello') return;
const config = await this.api.getConfig();
const greeting = config['greeting'] || 'Hello';
const recipient = typeof params['recipient'] === 'string' ? params['recipient'] : 'StreamShare';
this.api.toast({
message: `${greeting}, ${recipient}!`,
color: 'success',
duration: 3000,
});
}
}
registerPlugin(new HelloAddon());
```
`registerPlugin` is provided by the SDK and reports a clear error if the bundle is executed outside the StreamShare addon runtime.
## Validate and package
From the example project directory:
```bash
pnpm typecheck
pnpm build
```
The build validates the manifest, bundles the entry point and creates a local installation ZIP. It does not publish the addon.
For an iterative workflow, run `pnpm dev` and follow the [development-mode guide](../guides/development-mode.md).
# Interface: ActionInput
Input requested by the host before invoking a user action.
## Properties
### key
> **key**: `string`
Stable key present in the `params` object passed to `onAction`.
***
### type
> **type**: `"number"` \| `"boolean"` \| `"text"` \| `"password"` \| `"select"`
Editor used by the host action dialog.
***
### label
> **label**: `string`
Human-readable input label.
***
### default?
> `optional` **default?**: `string` \| `number` \| `boolean`
Initial value shown when the action dialog opens.
***
### options?
> `optional` **options?**: `object`[]
Allowed choices when `type` is `select`.
#### label
> **label**: `string`
#### value
> **value**: `string` \| `number`
# Interface: BackgroundTaskConstraints
Optional operating-system constraints for a persistent Android task.
## Properties
### network?
> `optional` **network?**: [`BackgroundNetworkType`](../type-aliases/BackgroundNetworkType.md)
Required network. Defaults to connected.
***
### requiresBatteryNotLow?
> `optional` **requiresBatteryNotLow?**: `boolean`
Defer while Android reports a low battery.
***
### requiresStorageNotLow?
> `optional` **requiresStorageNotLow?**: `boolean`
Defer while Android reports low storage.
***
### requiresCharging?
> `optional` **requiresCharging?**: `boolean`
Require charging on battery-powered devices.
***
### requiresDeviceIdleOnTv?
> `optional` **requiresDeviceIdleOnTv?**: `boolean`
Require Android device-idle mode on television devices only.
# Interface: ConfigField
User-editable value stored in the addon's configuration.
## Properties
### key
> **key**: `string`
Stable key used by [IPluginAPI.getConfig](IPluginAPI.md#getconfig).
***
### type
> **type**: `"number"` \| `"boolean"` \| `"text"` \| `"password"` \| `"select"`
Editor used by the host configuration screen.
***
### label
> **label**: `string`
Human-readable field label.
***
### default
> **default**: `string` \| `number` \| `boolean` \| readonly (`string` \| `number`)[]
Value used before the user saves a custom value.
***
### options?
> `optional` **options?**: `object`[]
Allowed choices when `type` is `select`.
#### label
> **label**: `string`
#### value
> **value**: `string` \| `number`
***
### multiple?
> `optional` **multiple?**: `boolean`
Allows a select field to store more than one choice.
# Interface: IAutomationPlugin\
Contract implemented by addons whose manifest type is `automation`.
## Example
```ts
class ExampleAddon implements IAutomationPlugin {
async onInit(api: IPluginAPI) {
api.logger.info('Initialized');
}
async onEvent(event: SystemEventContext) {
// Handle subscribed events.
}
}
```
## Type Parameters
### TEventData
`TEventData` = `unknown`
## Methods
### onInit()
> **onInit**(`api`): `Promise`\<`void`\>
Called once after the host creates the addon script instance.
#### Parameters
##### api
[`IPluginAPI`](IPluginAPI.md)
#### Returns
`Promise`\<`void`\>
***
### onEvent()
> **onEvent**(`event`): `Promise`\<`void`\>
Called when an event declared in the manifest is delivered.
#### Parameters
##### event
[`SystemEventContext`](SystemEventContext.md)\<`TEventData`\>
#### Returns
`Promise`\<`void`\>
***
### onAction()?
> `optional` **onAction**(`params`, `actionId`): `Promise`\<`void`\>
Called when the user invokes an action declared in the manifest.
#### Parameters
##### params
[`PluginActionParameters`](../type-aliases/PluginActionParameters.md)
##### actionId
`string`
#### Returns
`Promise`\<`void`\>
# Interface: IPluginAPI
Capabilities exposed by StreamShare to one addon instance.
## Remarks
Availability depends on the runtime mode and platform. Addons must use
`runtime` capability fields and the public compatibility documentation rather
than assuming every method is available everywhere.
## Properties
### runtime
> **runtime**: [`PluginRuntime`](PluginRuntime.md)
Execution environment. Headless tasks expose a deadline for cooperative checkpointing.
***
### logger
> **logger**: `object`
Logger piped to the host and development tools
#### log()
> **log**(...`args`): `void`
Writes a general diagnostic message.
##### Parameters
###### args
...`unknown`[]
##### Returns
`void`
#### info()
> **info**(...`args`): `void`
Writes an informational diagnostic message.
##### Parameters
###### args
...`unknown`[]
##### Returns
`void`
#### warn()
> **warn**(...`args`): `void`
Writes a warning diagnostic message.
##### Parameters
###### args
...`unknown`[]
##### Returns
`void`
#### error()
> **error**(...`args`): `void`
Writes an error diagnostic message.
##### Parameters
###### args
...`unknown`[]
##### Returns
`void`
***
### storage
> **storage**: `object`
Isolated persistent key-value storage for this specific plugin
#### get()
> **get**(`key`): `Promise`\<`string` \| `null`\>
Retrieves a string value, or `null` when the key does not exist.
##### Parameters
###### key
`string`
##### Returns
`Promise`\<`string` \| `null`\>
#### set()
> **set**(`key`, `value`): `Promise`\<`void`\>
Creates or replaces a string value.
##### Parameters
###### key
`string`
###### value
`string`
##### Returns
`Promise`\<`void`\>
#### delete()
> **delete**(`key`): `Promise`\<`void`\>
Removes one key when it exists.
##### Parameters
###### key
`string`
##### Returns
`Promise`\<`void`\>
#### clear()
> **clear**(): `Promise`\<`void`\>
Removes every key owned by this addon.
##### Returns
`Promise`\<`void`\>
***
### files
> **files**: `object`
Isolated file storage for large plugin-owned datasets.
Paths are always relative to the plugin directory; traversal is rejected.
#### write()
> **write**(`path`, `data`, `options?`): `Promise`\<`void`\>
Creates or updates a UTF-8 file at a package-relative path.
##### Parameters
###### path
`string`
###### data
`string`
###### options?
###### append?
`boolean`
##### Returns
`Promise`\<`void`\>
#### read()
> **read**(`path`): `Promise`\<`string` \| `null`\>
Reads a UTF-8 file, or returns `null` when it does not exist.
##### Parameters
###### path
`string`
##### Returns
`Promise`\<`string` \| `null`\>
#### stat()
> **stat**(`path`): `Promise`\<[`PluginFileStat`](PluginFileStat.md)\>
Returns existence, size, and modification metadata for a file.
##### Parameters
###### path
`string`
##### Returns
`Promise`\<[`PluginFileStat`](PluginFileStat.md)\>
#### delete()
> **delete**(`path`): `Promise`\<`void`\>
Deletes a file when it exists.
##### Parameters
###### path
`string`
##### Returns
`Promise`\<`void`\>
#### move()
> **move**(`from`, `to`): `Promise`\<`void`\>
Atomically moves or renames a file within the addon's storage.
##### Parameters
###### from
`string`
###### to
`string`
##### Returns
`Promise`\<`void`\>
#### scanLines()
> **scanLines**(`path`, `options?`): `Promise`\<[`PluginFileScanResult`](PluginFileScanResult.md)\>
Streams matching lines without loading an entire large file into memory.
##### Parameters
###### path
`string`
###### options?
[`PluginFileScanOptions`](PluginFileScanOptions.md)
##### Returns
`Promise`\<[`PluginFileScanResult`](PluginFileScanResult.md)\>
***
### sourceCatalog
> **sourceCatalog**: `object`
Indexed, isolated storage for large source catalogs.
A snapshot is invisible until commitSnapshot succeeds, so interactive reads
keep using the previous snapshot while a synchronization is running.
#### beginSnapshot()
> **beginSnapshot**(`options?`): `Promise`\<`string`\>
Resume a durable unpublished snapshot when requested and available.
##### Parameters
###### options?
###### resume?
`boolean`
##### Returns
`Promise`\<`string`\>
#### append()
> **append**(`snapshotId`, `batch`): `Promise`\<[`PluginSourceCatalogAppendResult`](PluginSourceCatalogAppendResult.md)\>
Appends a bounded batch of logical media and variants to a draft snapshot.
##### Parameters
###### snapshotId
`string`
###### batch
[`PluginSourceCatalogBatch`](PluginSourceCatalogBatch.md)
##### Returns
`Promise`\<[`PluginSourceCatalogAppendResult`](PluginSourceCatalogAppendResult.md)\>
#### getSnapshotStats()
> **getSnapshotStats**(`snapshotId`): `Promise`\<[`PluginSourceCatalogAppendResult`](PluginSourceCatalogAppendResult.md)\>
Durable row counts, useful when resuming after process termination.
##### Parameters
###### snapshotId
`string`
##### Returns
`Promise`\<[`PluginSourceCatalogAppendResult`](PluginSourceCatalogAppendResult.md)\>
#### commitSnapshot()
> **commitSnapshot**(`snapshotId`): `Promise`\<`void`\>
Atomically publishes a completed snapshot for interactive queries.
##### Parameters
###### snapshotId
`string`
##### Returns
`Promise`\<`void`\>
#### abortSnapshot()
> **abortSnapshot**(`snapshotId`): `Promise`\<`void`\>
Deletes an unpublished snapshot and its temporary data.
##### Parameters
###### snapshotId
`string`
##### Returns
`Promise`\<`void`\>
#### query()
> **query**(`criteria`): `Promise`\<[`PluginSourceCatalogResult`](PluginSourceCatalogResult.md)\>
Query unique logical media before pagination.
##### Parameters
###### criteria
[`PluginSourceCatalogQuery`](PluginSourceCatalogQuery.md)
##### Returns
`Promise`\<[`PluginSourceCatalogResult`](PluginSourceCatalogResult.md)\>
#### queryVariants()
> **queryVariants**(`criteria`): `Promise`\<[`PluginSourceCatalogResult`](PluginSourceCatalogResult.md)\>
Expand matching logical media into their playable variants.
##### Parameters
###### criteria
[`PluginSourceCatalogQuery`](PluginSourceCatalogQuery.md)
##### Returns
`Promise`\<[`PluginSourceCatalogResult`](PluginSourceCatalogResult.md)\>
***
### http
> **http**: `object`
Native HTTP capability constrained by the manifest network allowlist.
#### get()
> **get**(`url`, `headers?`): `Promise`\<`string`\>
Retrieves raw text content from an allowed HTTP(S) URL.
##### Parameters
###### url
`string`
###### headers?
`Record`\<`string`, `string`\>
##### Returns
`Promise`\<`string`\>
***
### lists
> **lists**: `object`
Interactive playlist and library management capabilities.
#### getAll()
> **getAll**(): `Promise`\<[`PluginList`](PluginList.md)[]\>
Returns playlists visible to the current user.
##### Returns
`Promise`\<[`PluginList`](PluginList.md)[]\>
#### getById()
> **getById**(`id`): `Promise`\<[`PluginListWithItems`](PluginListWithItems.md) \| `null`\>
Returns one playlist and its items, or `null` when it does not exist.
##### Parameters
###### id
`number`
##### Returns
`Promise`\<[`PluginListWithItems`](PluginListWithItems.md) \| `null`\>
#### create()
> **create**(`name`): `Promise`\<`number`\>
Creates a playlist when permitted by the current account and returns its numeric identifier.
##### Parameters
###### name
`string`
##### Returns
`Promise`\<`number`\>
#### addItem()
> **addItem**(`listId`, `item`): `Promise`\<`number`\>
Adds one media item to a playlist and returns the new item identifier.
##### Parameters
###### listId
`number`
###### item
[`PluginListItemInput`](PluginListItemInput.md)
##### Returns
`Promise`\<`number`\>
#### addItems()
> **addItems**(`listId`, `items`): `Promise`\<`number`\>
Adds multiple media items atomically and returns the number accepted.
##### Parameters
###### listId
`number`
###### items
[`PluginListItemInput`](PluginListItemInput.md)[]
##### Returns
`Promise`\<`number`\>
#### removeItem()
> **removeItem**(`itemId`): `Promise`\<`void`\>
Removes one playlist item by its item identifier.
##### Parameters
###### itemId
`number`
##### Returns
`Promise`\<`void`\>
#### clear()
> **clear**(`listId`): `Promise`\<`void`\>
Removes every item from a playlist while retaining the playlist.
##### Parameters
###### listId
`number`
##### Returns
`Promise`\<`void`\>
#### enrichList()
> **enrichList**(`listId`): `Promise`\<`void`\>
Requests metadata enrichment for items in a playlist.
##### Parameters
###### listId
`number`
##### Returns
`Promise`\<`void`\>
## Methods
### getConfig()
> **getConfig**(): `Promise`\<`Readonly`\<`Record`\<`string`, `string`\>\>\>
Retrieves user configuration defined by the addon manifest.
#### Returns
`Promise`\<`Readonly`\<`Record`\<`string`, `string`\>\>\>
***
### toast()
> **toast**(`messageOrOptions`): `void`
Displays or updates a user notification.
#### Parameters
##### messageOrOptions
`string` \| [`ToastOptions`](ToastOptions.md)
#### Returns
`void`
***
### dismissToast()
> **dismissToast**(`id`): `void`
Dismisses the notification with the supplied stable ID.
#### Parameters
##### id
`string`
#### Returns
`void`
***
### setActionProgress()
> **setActionProgress**(`progress`): `void`
Sets the progress description for the currently running action.
#### Parameters
##### progress
`string`
#### Returns
`void`
# Interface: ISourcePlugin
Contract implemented by plugins whose manifest type is `source`.
## Example
```ts
class CatalogSource implements ISourcePlugin {
async onInit(api: IPluginAPI) {
api.logger.info('Catalog source initialized');
}
getSourceInfo(): SourceInfo {
return {
id: 'catalog',
name: 'Catalog',
isAvailable: true,
capabilities: {
supportsSearch: true,
supportsBrowse: true,
supportsTypeFilter: true,
supportsGenreFilter: false,
supportsYearFilter: false,
supportsTmdbLookup: false,
supportedTypes: ['movie', 'folder'],
},
};
}
async search(): Promise {
return {items: [], hasMore: false};
}
async browse(): Promise {
return {items: [], hasMore: false};
}
async getRecent(): Promise {
return [];
}
async resolvePlayback(sourceItemId: string): Promise {
return {url: `https://media.example.com/${encodeURIComponent(sourceItemId)}`};
}
}
```
## Methods
### onInit()
> **onInit**(`api`): `Promise`\<`void`\>
Receives the scoped host API before any other source method is invoked.
#### Parameters
##### api
[`IPluginAPI`](IPluginAPI.md)
#### Returns
`Promise`\<`void`\>
***
### getSourceInfo()
> **getSourceInfo**(): [`SourceInfo`](SourceInfo.md) \| `Promise`\<[`SourceInfo`](SourceInfo.md)\>
Returns stable source identity, availability, and capabilities.
#### Returns
[`SourceInfo`](SourceInfo.md) \| `Promise`\<[`SourceInfo`](SourceInfo.md)\>
***
### search()
> **search**(`criteria`, `pageToken?`): `Promise`\<[`SourcePagedResult`](SourcePagedResult.md)\>
Searches logical media and applies supported criteria before pagination.
#### Parameters
##### criteria
[`SourceSearchCriteria`](SourceSearchCriteria.md)
##### pageToken?
`string` \| `number`
#### Returns
`Promise`\<[`SourcePagedResult`](SourcePagedResult.md)\>
***
### browse()
> **browse**(`parentId?`, `pageToken?`, `criteria?`): `Promise`\<[`SourcePagedResult`](SourcePagedResult.md)\>
Browses the root or children of a source container.
#### Parameters
##### parentId?
`string`
##### pageToken?
`string` \| `number`
##### criteria?
`Partial`\<[`SourceSearchCriteria`](SourceSearchCriteria.md)\>
#### Returns
`Promise`\<[`SourcePagedResult`](SourcePagedResult.md)\>
***
### getRecent()
> **getRecent**(`limit`): `Promise`\<[`SourceMediaItem`](SourceMediaItem.md)[]\>
Returns a bounded list of recently added or updated logical items.
#### Parameters
##### limit
`number`
#### Returns
`Promise`\<[`SourceMediaItem`](SourceMediaItem.md)[]\>
***
### resolvePlayback()
> **resolvePlayback**(`sourceItemId`): `Promise`\<[`SourcePlaybackInfo`](SourcePlaybackInfo.md)\>
Resolves one logical source item to a playable resource.
#### Parameters
##### sourceItemId
`string`
#### Returns
`Promise`\<[`SourcePlaybackInfo`](SourcePlaybackInfo.md)\>
***
### onEvent()?
> `optional` **onEvent**(`event`): `Promise`\<`void`\>
Handles an event declared in the addon manifest.
#### Parameters
##### event
[`SystemEventContext`](SystemEventContext.md)
#### Returns
`Promise`\<`void`\>
***
### onAction()?
> `optional` **onAction**(`params`, `actionId`): `Promise`\<`void`\>
Handles an action declared in the addon manifest.
#### Parameters
##### params
[`PluginActionParameters`](../type-aliases/PluginActionParameters.md)
##### actionId
`string`
#### Returns
`Promise`\<`void`\>
# Interface: NetworkPermissions
Network and foreground-runtime capabilities requested by an addon.
## Properties
### network
> **network**: `string`[]
HTTP(S)/WebSocket host allowlist shared by interactive and headless runtimes.
Use an exact host, a leading subdomain wildcard (`*.site.com`), or `*` for all hosts.
Values are hosts only: do not include a scheme, port or path.
***
### extended\_runtime?
> `optional` **extended\_runtime?**: `boolean`
Extends the interactive runtime TTL for explicitly long foreground actions.
# Interface: PluginAction
User-triggered operation exposed by an addon.
## Properties
### id
> **id**: `string`
Stable action identifier passed to the addon's `onAction` handler.
***
### label
> **label**: `string`
Short label displayed by the host.
***
### description?
> `optional` **description?**: `string`
Optional explanation of the action and its effect.
***
### inputs?
> `optional` **inputs?**: [`ActionInput`](ActionInput.md)[]
Values collected before invoking the action.
# Interface: PluginBackgroundTask
A deferrable task that can run without an open interactive application session.
## Properties
### id
> **id**: `string`
Stable identifier, unique inside the plugin manifest.
***
### action
> **action**: `string`
Plugin action passed to onAction(params, actionId).
***
### intervalMinutes
> **intervalMinutes**: `number`
Repeat interval in minutes. Android's minimum is 15 minutes.
***
### freshnessMinutes?
> `optional` **freshnessMinutes?**: `number`
Skip periodic delivery while the previous successful result is newer than this many minutes.
***
### flexMinutes?
> `optional` **flexMinutes?**: `number`
Optional flexible execution window at the end of each interval.
***
### runOnInstall?
> `optional` **runOnInstall?**: `boolean`
Schedule an immediate one-time run when this plugin/version is installed.
***
### runOnConfigChange?
> `optional` **runOnConfigChange?**: `boolean`
Schedule an immediate one-time run after the user saves plugin configuration.
***
### sliceDurationMinutes?
> `optional` **sliceDurationMinutes?**: `number`
Cooperative slice budget. The plugin should checkpoint and request a continuation before it expires.
***
### constraints?
> `optional` **constraints?**: [`BackgroundTaskConstraints`](BackgroundTaskConstraints.md)
Conditions that must be satisfied before automatic delivery.
# Interface: PluginFileScanOptions
Paging and filtering options for the native line scanner.
## Properties
### cursor?
> `optional` **cursor?**: `string`
Opaque byte cursor returned by a previous scan.
***
### limit?
> `optional` **limit?**: `number`
Maximum number of matching lines to return (1..500).
***
### query?
> `optional` **query?**: `string`
Case-insensitive substring matched by the native streaming reader.
***
### queries?
> `optional` **queries?**: `string`[]
All case-insensitive substrings that a line must contain.
# Interface: PluginFileScanResult
One page returned by the native line scanner.
## Properties
### lines
> **lines**: `string`[]
Matching UTF-8 lines, without line terminators.
***
### nextCursor?
> `optional` **nextCursor?**: `string`
Opaque cursor to pass to the next scan of the same file and query.
***
### hasMore
> **hasMore**: `boolean`
Whether more matching lines may be requested.
# Interface: PluginFileStat
Metadata returned for a file in the addon's isolated storage.
## Properties
### exists
> **exists**: `boolean`
Whether the relative path currently exists.
***
### size
> **size**: `number`
File size in bytes, or `0` when the path does not exist.
***
### modifiedAt?
> `optional` **modifiedAt?**: `number`
Last modification time as a Unix timestamp in milliseconds.
# Interface: PluginList
Public metadata for a playlist visible to the current user.
## Extended by
- [`PluginListWithItems`](PluginListWithItems.md)
## Properties
### id
> **id**: `number`
Stable numeric playlist identifier.
***
### name
> **name**: `string`
User-facing playlist name.
***
### locked?
> `optional` **locked?**: `boolean`
Whether the host prevents destructive playlist changes.
***
### createdAt?
> `optional` **createdAt?**: `string`
Creation timestamp supplied by the host.
***
### updatedAt?
> `optional` **updatedAt?**: `string`
Last-update timestamp supplied by the host.
# Interface: PluginListItem
Playlist item returned by the host.
## Properties
### id
> **id**: `number`
Stable numeric playlist-item identifier.
***
### listID
> **listID**: `number`
Identifier of the containing playlist.
***
### filename?
> `optional` **filename?**: `string`
User-facing or source-provided filename when known.
***
### url
> **url**: `string`
Playable media URL.
***
### type?
> `optional` **type?**: `"movie"` \| `"tvShow"` \| `"unclassified"`
Host media classification when known.
***
### tmdbID?
> `optional` **tmdbID?**: `number`
TMDB identifier when known.
***
### season?
> `optional` **season?**: `number`
Season number for episodic content.
***
### episode?
> `optional` **episode?**: `number`
Episode number for episodic content.
***
### production\_date?
> `optional` **production\_date?**: `string`
Source-provided production date.
# Interface: PluginListItemInput
Media item accepted by the interactive playlist API.
## Properties
### filename
> **filename**: `string`
User-facing or source-provided filename.
***
### url
> **url**: `string`
Playable media URL.
***
### type
> **type**: `"movie"` \| `"tvShow"` \| `"unclassified"`
Host media classification.
***
### tmdbID?
> `optional` **tmdbID?**: `number`
TMDB identifier when known.
***
### season?
> `optional` **season?**: `number`
Season number for episodic content.
***
### episode?
> `optional` **episode?**: `number`
Episode number for episodic content.
***
### production\_date?
> `optional` **production\_date?**: `string`
Source-provided production date.
***
### genres?
> `optional` **genres?**: `number`[]
TMDB genre identifiers.
# Interface: PluginListWithItems
One playlist together with its current media items.
## Extends
- [`PluginList`](PluginList.md)
## Properties
### id
> **id**: `number`
Stable numeric playlist identifier.
#### Inherited from
[`PluginList`](PluginList.md).[`id`](PluginList.md#id)
***
### name
> **name**: `string`
User-facing playlist name.
#### Inherited from
[`PluginList`](PluginList.md).[`name`](PluginList.md#name)
***
### locked?
> `optional` **locked?**: `boolean`
Whether the host prevents destructive playlist changes.
#### Inherited from
[`PluginList`](PluginList.md).[`locked`](PluginList.md#locked)
***
### createdAt?
> `optional` **createdAt?**: `string`
Creation timestamp supplied by the host.
#### Inherited from
[`PluginList`](PluginList.md).[`createdAt`](PluginList.md#createdat)
***
### updatedAt?
> `optional` **updatedAt?**: `string`
Last-update timestamp supplied by the host.
#### Inherited from
[`PluginList`](PluginList.md).[`updatedAt`](PluginList.md#updatedat)
***
### items
> **items**: [`PluginListItem`](PluginListItem.md)[]
Media items currently stored in the playlist.
# Interface: PluginManifest
Public manifest bundled with every StreamShare addon.
## Remarks
The manifest is reviewed before installation. Keep identifiers stable and
request only the capabilities required by the current addon version.
## Properties
### id
> **id**: `string`
Globally unique, stable addon identifier such as `com.example.my-addon`.
***
### name
> **name**: `string`
Human-readable addon name.
***
### version
> **version**: `string`
Semantic version of the addon package.
***
### compatibleVersion
> **compatibleVersion**: `string`
Semantic-version range of compatible StreamShare application versions.
***
### type
> **type**: [`PluginType`](../type-aliases/PluginType.md)
Functional contract implemented by the addon entry script.
***
### main
> **main**: `string`
Package-relative path to the compiled JavaScript entry file.
***
### icon?
> `optional` **icon?**: `string`
Package-relative SVG, PNG or WebP used as the plugin's visual identity.
***
### iconAppearance?
> `optional` **iconAppearance?**: [`PluginIconAppearance`](../type-aliases/PluginIconAppearance.md)
Controls whether the host may recolor the declared icon.
`auto` lets the host choose, `monochrome` requests a host-colored
silhouette, and `original` preserves every color and filled area.
Defaults to `auto` when omitted.
***
### author
> **author**: `string`
Publisher or maintainer name shown to the user.
***
### description
> **description**: `string`
Concise user-facing description of the addon.
***
### adult?
> `optional` **adult?**: `boolean`
Declares that every catalog entry and playback source exposed by this addon
is intended for adults. The host may hide the addon and its data behind
parental controls. Defaults to `false` when omitted.
***
### permissions
> **permissions**: [`NetworkPermissions`](NetworkPermissions.md)
Capabilities that require review during installation or update.
***
### backgroundTasks?
> `optional` **backgroundTasks?**: [`PluginBackgroundTask`](PluginBackgroundTask.md)[]
Android-only persistent work. Unsupported platforms keep using interactive events.
***
### subscriptions?
> `optional` **subscriptions?**: [`SubscriptionConfig`](SubscriptionConfig.md)[]
Host events delivered to the addon's optional event handler.
***
### config?
> `optional` **config?**: [`ConfigField`](ConfigField.md)[]
Persistent values editable from the host configuration screen.
***
### actions?
> `optional` **actions?**: [`PluginAction`](PluginAction.md)[]
Operations the user can invoke from the addon's host interface.
# Interface: PluginRuntime
Execution environment and feature availability for one addon invocation.
## Properties
### platform
> **platform**: [`PluginRuntimePlatform`](../type-aliases/PluginRuntimePlatform.md)
Host platform for this invocation.
***
### mode
> **mode**: [`PluginRuntimeMode`](../type-aliases/PluginRuntimeMode.md)
Whether the invocation is attached to the interactive app or durable work.
***
### reason?
> `optional` **reason?**: [`PluginRuntimeReason`](../type-aliases/PluginRuntimeReason.md)
Why this execution started. Interactive lifecycle calls may omit it.
***
### capabilities
> **capabilities**: [`PluginRuntimeCapabilities`](PluginRuntimeCapabilities.md)
Capabilities that may safely be used during this invocation.
***
### deadline?
> `optional` **deadline?**: `number`
Unix timestamp in milliseconds before which the current slice should finish.
## Methods
### shouldYield()
> **shouldYield**(): `boolean`
True shortly before the current background slice must yield.
#### Returns
`boolean`
***
### requestContinuation()
> **requestContinuation**(): `void`
Ask the Android scheduler to continue this task in another safe slice.
#### Returns
`void`
# Interface: PluginRuntimeCapabilities
Feature availability for the current platform and invocation mode.
## Remarks
Addons should inspect these flags before using an optional host API. A method
may remain present for structural compatibility while rejecting when its
corresponding capability is unavailable.
## Properties
### configuration
> **configuration**: `boolean`
Whether [IPluginAPI.getConfig](IPluginAPI.md#getconfig) is available.
***
### notifications
> **notifications**: `boolean`
Whether notifications can be displayed or persisted.
***
### storage
> **storage**: `boolean`
Whether isolated key-value storage is available.
***
### files
> **files**: `boolean`
Whether isolated file storage is available.
***
### fileScanning
> **fileScanning**: `boolean`
Whether native streaming line scans are available.
***
### sourceCatalogWrite
> **sourceCatalogWrite**: `boolean`
Whether source catalog snapshots can be created and published.
***
### sourceCatalogQuery
> **sourceCatalogQuery**: `boolean`
Whether the published source catalog can be queried.
***
### http
> **http**: `boolean`
Whether manifest-constrained HTTP requests are available.
***
### lists
> **lists**: `boolean`
Whether interactive playlist and library APIs are available.
***
### backgroundTasks
> **backgroundTasks**: `boolean`
Whether persistent background work is supported for this invocation.
# Interface: PluginSourceCatalogAppendResult
Counts accepted from a catalog append operation or stored in a draft snapshot.
## Properties
### media
> **media**: `number`
Number of logical media rows.
***
### variants
> **variants**: `number`
Number of playable variant rows.
# Interface: PluginSourceCatalogBatch
Column-oriented catalog batch. Media and playback variants are deduplicated independently.
## Properties
### media
> **media**: [`PluginSourceCatalogItem`](PluginSourceCatalogItem.md)[]
Logical media and containers to insert into the draft snapshot.
***
### variants?
> `optional` **variants?**: [`PluginSourceCatalogVariant`](PluginSourceCatalogVariant.md)[]
Playable variants attached to logical media in the same batch.
# Interface: PluginSourceCatalogItem
A source item stored in the plugin-owned native catalog.
## Extends
- [`SourceMediaItem`](SourceMediaItem.md)
## Properties
### genreTmdbIds?
> `optional` **genreTmdbIds?**: `number`[]
TMDB genre IDs used by the native index. They are not exposed to the Media Hub item.
***
### popularity?
> `optional` **popularity?**: `number`
Optional sort value for sources which already provide popularity.
***
### id
> **id**: `string`
Stable source identifier. It is passed back to resolvePlayback.
#### Inherited from
[`SourceMediaItem`](SourceMediaItem.md).[`id`](SourceMediaItem.md#id)
***
### type
> **type**: [`SourceMediaType`](../type-aliases/SourceMediaType.md)
Shape of this item.
#### Inherited from
[`SourceMediaItem`](SourceMediaItem.md).[`type`](SourceMediaItem.md#type)
***
### title
> **title**: `string`
User-facing title.
#### Inherited from
[`SourceMediaItem`](SourceMediaItem.md).[`title`](SourceMediaItem.md#title)
***
### isContainer
> **isContainer**: `boolean`
Whether selecting the item should browse children instead of starting playback.
#### Inherited from
[`SourceMediaItem`](SourceMediaItem.md).[`isContainer`](SourceMediaItem.md#iscontainer)
***
### tmdbId?
> `optional` **tmdbId?**: `number`
Authoritative TMDB identifier for this media. The host may use it for
enrichment and exact cross-source reconciliation; omit it when only guessed.
#### Inherited from
[`SourceMediaItem`](SourceMediaItem.md).[`tmdbId`](SourceMediaItem.md#tmdbid)
***
### imdbId?
> `optional` **imdbId?**: `string`
IMDb identifier when known.
#### Inherited from
[`SourceMediaItem`](SourceMediaItem.md).[`imdbId`](SourceMediaItem.md#imdbid)
***
### hash?
> `optional` **hash?**: `string`
Optional source-provided content or deduplication hash.
#### Inherited from
[`SourceMediaItem`](SourceMediaItem.md).[`hash`](SourceMediaItem.md#hash)
***
### runTimeTicks?
> `optional` **runTimeTicks?**: `number`
Duration in 100-nanosecond ticks.
#### Inherited from
[`SourceMediaItem`](SourceMediaItem.md).[`runTimeTicks`](SourceMediaItem.md#runtimeticks)
***
### thumbnailUrl?
> `optional` **thumbnailUrl?**: `string`
Compact image suitable for rows or source-specific lists.
#### Inherited from
[`SourceMediaItem`](SourceMediaItem.md).[`thumbnailUrl`](SourceMediaItem.md#thumbnailurl)
***
### premiereDate?
> `optional` **premiereDate?**: `string`
ISO-8601 premiere or release date.
#### Inherited from
[`SourceMediaItem`](SourceMediaItem.md).[`premiereDate`](SourceMediaItem.md#premieredate)
***
### productionYear?
> `optional` **productionYear?**: `number`
Four-digit production year.
#### Inherited from
[`SourceMediaItem`](SourceMediaItem.md).[`productionYear`](SourceMediaItem.md#productionyear)
***
### originalTitle?
> `optional` **originalTitle?**: `string`
Original-language title.
#### Inherited from
[`SourceMediaItem`](SourceMediaItem.md).[`originalTitle`](SourceMediaItem.md#originaltitle)
***
### overview?
> `optional` **overview?**: `string`
Plot or content summary.
#### Inherited from
[`SourceMediaItem`](SourceMediaItem.md).[`overview`](SourceMediaItem.md#overview)
***
### rating?
> `optional` **rating?**: `number`
Source-provided rating normalized to the inclusive 0-to-10 scale.
#### Inherited from
[`SourceMediaItem`](SourceMediaItem.md).[`rating`](SourceMediaItem.md#rating)
***
### genres?
> `optional` **genres?**: `object`[]
Human-readable genres.
#### name
> **name**: `string`
#### Inherited from
[`SourceMediaItem`](SourceMediaItem.md).[`genres`](SourceMediaItem.md#genres)
***
### posterUrl?
> `optional` **posterUrl?**: `string`
Portrait artwork URL.
#### Inherited from
[`SourceMediaItem`](SourceMediaItem.md).[`posterUrl`](SourceMediaItem.md#posterurl)
***
### backdropUrl?
> `optional` **backdropUrl?**: `string`
Landscape artwork URL.
#### Inherited from
[`SourceMediaItem`](SourceMediaItem.md).[`backdropUrl`](SourceMediaItem.md#backdropurl)
***
### seasonNumber?
> `optional` **seasonNumber?**: `number`
Season number for season and episode items.
#### Inherited from
[`SourceMediaItem`](SourceMediaItem.md).[`seasonNumber`](SourceMediaItem.md#seasonnumber)
***
### episodeNumber?
> `optional` **episodeNumber?**: `number`
Episode number for episode items.
#### Inherited from
[`SourceMediaItem`](SourceMediaItem.md).[`episodeNumber`](SourceMediaItem.md#episodenumber)
***
### seriesTmdbId?
> `optional` **seriesTmdbId?**: `number`
Authoritative TMDB series identifier used to enrich and reconcile a season
or episode in its parent context; omit it when only guessed.
#### Inherited from
[`SourceMediaItem`](SourceMediaItem.md).[`seriesTmdbId`](SourceMediaItem.md#seriestmdbid)
***
### seriesTitle?
> `optional` **seriesTitle?**: `string`
Parent series title for a season or episode.
#### Inherited from
[`SourceMediaItem`](SourceMediaItem.md).[`seriesTitle`](SourceMediaItem.md#seriestitle)
***
### childrenCount?
> `optional` **childrenCount?**: `number`
Number of known direct children for a container.
#### Inherited from
[`SourceMediaItem`](SourceMediaItem.md).[`childrenCount`](SourceMediaItem.md#childrencount)
***
### qualityLabel?
> `optional` **qualityLabel?**: `string`
Composite or legacy quality label. Prefer the structured fields below when known.
#### Inherited from
[`SourceMediaItem`](SourceMediaItem.md).[`qualityLabel`](SourceMediaItem.md#qualitylabel)
***
### resolution?
> `optional` **resolution?**: `string`
Technical resolution such as `1920x1080`.
#### Inherited from
[`SourceMediaItem`](SourceMediaItem.md).[`resolution`](SourceMediaItem.md#resolution)
***
### source?
> `optional` **source?**: `string`
Human-readable distribution origin such as `WEB-DL` or `BluRay`.
#### Inherited from
[`SourceMediaItem`](SourceMediaItem.md).[`source`](SourceMediaItem.md#source)
***
### encoding?
> `optional` **encoding?**: `string`
Codec or encoding label.
#### Inherited from
[`SourceMediaItem`](SourceMediaItem.md).[`encoding`](SourceMediaItem.md#encoding)
***
### language?
> `optional` **language?**: `string`
Audio or content language label.
#### Inherited from
[`SourceMediaItem`](SourceMediaItem.md).[`language`](SourceMediaItem.md#language)
***
### tags?
> `optional` **tags?**: [`SourceTag`](SourceTag.md)[]
Addon-defined badges for metadata that does not fit the structured technical fields.
#### Inherited from
[`SourceMediaItem`](SourceMediaItem.md).[`tags`](SourceMediaItem.md#tags)
***
### addedAt?
> `optional` **addedAt?**: `string`
ISO-8601 date at which the source added this item.
#### Inherited from
[`SourceMediaItem`](SourceMediaItem.md).[`addedAt`](SourceMediaItem.md#addedat)
# Interface: PluginSourceCatalogQuery
Filters used when listing the addon's persisted source catalog.
## Extends
- `Partial`\<[`SourceSearchCriteria`](SourceSearchCriteria.md)\>
## Properties
### pageToken?
> `optional` **pageToken?**: `string` \| `number`
Opaque page token returned by the previous query.
***
### mediaId?
> `optional` **mediaId?**: `string`
Direct logical-media lookup, primarily used by resolvePlayback.
***
### query?
> `optional` **query?**: `string`
Free-text query entered by the user.
#### Inherited from
[`SourceSearchCriteria`](SourceSearchCriteria.md).[`query`](SourceSearchCriteria.md#query)
***
### types?
> `optional` **types?**: [`SourceMediaType`](../type-aliases/SourceMediaType.md)[]
Media types that may be returned.
#### Inherited from
[`SourceSearchCriteria`](SourceSearchCriteria.md).[`types`](SourceSearchCriteria.md#types)
***
### genreTmdbIds?
> `optional` **genreTmdbIds?**: `number`[]
TMDB genre identifiers that returned items should match.
#### Inherited from
[`SourceSearchCriteria`](SourceSearchCriteria.md).[`genreTmdbIds`](SourceSearchCriteria.md#genretmdbids)
***
### year?
> `optional` **year?**: `number`
Four-digit production or release year.
#### Inherited from
[`SourceSearchCriteria`](SourceSearchCriteria.md).[`year`](SourceSearchCriteria.md#year)
***
### sortBy?
> `optional` **sortBy?**: [`SourceSortOption`](../type-aliases/SourceSortOption.md)
Sort mode requested by the host.
#### Inherited from
[`SourceSearchCriteria`](SourceSearchCriteria.md).[`sortBy`](SourceSearchCriteria.md#sortby)
***
### limit?
> `optional` **limit?**: `number`
Maximum preferred number of logical items in the page.
#### Inherited from
[`SourceSearchCriteria`](SourceSearchCriteria.md).[`limit`](SourceSearchCriteria.md#limit)
***
### parentTmdbId?
> `optional` **parentTmdbId?**: `number`
TMDB identifier of the parent series or container.
#### Inherited from
[`SourceSearchCriteria`](SourceSearchCriteria.md).[`parentTmdbId`](SourceSearchCriteria.md#parenttmdbid)
***
### parentSeason?
> `optional` **parentSeason?**: `number`
Season number of the requested parent context.
#### Inherited from
[`SourceSearchCriteria`](SourceSearchCriteria.md).[`parentSeason`](SourceSearchCriteria.md#parentseason)
***
### parentEpisode?
> `optional` **parentEpisode?**: `number`
Episode number of the requested parent context.
#### Inherited from
[`SourceSearchCriteria`](SourceSearchCriteria.md).[`parentEpisode`](SourceSearchCriteria.md#parentepisode)
***
### parentTitle?
> `optional` **parentTitle?**: `string`
Human-readable parent title for sources without a stable metadata ID.
#### Inherited from
[`SourceSearchCriteria`](SourceSearchCriteria.md).[`parentTitle`](SourceSearchCriteria.md#parenttitle)
***
### targetTmdbId?
> `optional` **targetTmdbId?**: `number`
Direct TMDB lookup target.
#### Inherited from
[`SourceSearchCriteria`](SourceSearchCriteria.md).[`targetTmdbId`](SourceSearchCriteria.md#targettmdbid)
***
### targetImdbId?
> `optional` **targetImdbId?**: `string`
Direct IMDb lookup target.
#### Inherited from
[`SourceSearchCriteria`](SourceSearchCriteria.md).[`targetImdbId`](SourceSearchCriteria.md#targetimdbid)
***
### targetSourceItemId?
> `optional` **targetSourceItemId?**: `string`
Opaque item ID previously returned by this source for the selected media.
During source selection, prefer this value over an enriched or localized
title when it belongs to the receiving source.
#### Inherited from
[`SourceSearchCriteria`](SourceSearchCriteria.md).[`targetSourceItemId`](SourceSearchCriteria.md#targetsourceitemid)
***
### isSourceSelection?
> `optional` **isSourceSelection?**: `boolean`
Whether the request is selecting playback for a known item. Sources that
advertise playback variants should return one playable item per variant.
#### Inherited from
[`SourceSearchCriteria`](SourceSearchCriteria.md).[`isSourceSelection`](SourceSearchCriteria.md#issourceselection)
***
### noEnrich?
> `optional` **noEnrich?**: `boolean`
Whether the host asks the source to avoid optional metadata enrichment.
#### Inherited from
[`SourceSearchCriteria`](SourceSearchCriteria.md).[`noEnrich`](SourceSearchCriteria.md#noenrich)
***
### noFacets?
> `optional` **noFacets?**: `boolean`
Whether the source may omit expensive facet computation.
#### Inherited from
[`SourceSearchCriteria`](SourceSearchCriteria.md).[`noFacets`](SourceSearchCriteria.md#nofacets)
# Interface: PluginSourceCatalogResult
One page returned by a native source-catalog query.
## Properties
### items
> **items**: [`PluginSourceCatalogItem`](PluginSourceCatalogItem.md)[]
Logical media matching the query.
***
### nextPageToken?
> `optional` **nextPageToken?**: `string`
Opaque token to pass to the next query.
***
### hasMore
> **hasMore**: `boolean`
Whether another page may be requested.
# Interface: PluginSourceCatalogVariant
A playable representation attached to one logical catalog media item.
## Properties
### id
> **id**: `string`
Stable variant ID passed back to the plugin's resolvePlayback method.
***
### mediaId
> **mediaId**: `string`
Stable ID of the logical media item stored in the same snapshot.
***
### qualityLabel?
> `optional` **qualityLabel?**: `string`
User-facing quality label such as `1080p` or `4K`.
***
### resolution?
> `optional` **resolution?**: `string`
Technical video resolution such as `1920x1080`.
***
### source?
> `optional` **source?**: `string`
Human-readable provider or origin label.
***
### encoding?
> `optional` **encoding?**: `string`
Codec or encoding label.
***
### language?
> `optional` **language?**: `string`
Audio or content language label.
***
### addedAt?
> `optional` **addedAt?**: `string`
ISO-8601 date at which the variant was added.
# Interface: SourceCapabilities
Features implemented by a source and advertised to the host.
## Properties
### supportsSearch
> **supportsSearch**: `boolean`
Whether [ISourcePlugin.search](ISourcePlugin.md#search) is supported.
***
### supportsBrowse
> **supportsBrowse**: `boolean`
Whether [ISourcePlugin.browse](ISourcePlugin.md#browse) accepts the source's own previously
returned container IDs and returns their direct children.
***
### supportsTypeFilter
> **supportsTypeFilter**: `boolean`
Whether the source can apply the `types` criterion.
***
### supportsGenreFilter
> **supportsGenreFilter**: `boolean`
Whether the source can apply TMDB genre filters.
***
### supportsYearFilter
> **supportsYearFilter**: `boolean`
Whether the source can apply production-year filters.
***
### supportsTmdbLookup
> **supportsTmdbLookup**: `boolean`
Whether the source applies direct TMDB criteria such as `targetTmdbId` and
`parentTmdbId`. This is independent from host enrichment of returned items.
***
### supportsPlaybackVariants?
> `optional` **supportsPlaybackVariants?**: `boolean`
The source can expand one logical media item into several playback variants
when search receives `isSourceSelection: true`.
***
### supportedSorts?
> `optional` **supportedSorts?**: [`SourceSortOption`](../type-aliases/SourceSortOption.md)[]
Sort modes applied by the source before pagination.
***
### supportedTypes
> **supportedTypes**: [`SourceMediaType`](../type-aliases/SourceMediaType.md)[]
Container and media types returned by this source. Generic playable videos
use `video`, set `isContainer: false`, and are resolved like any other item.
# Interface: SourceFacets
Filter values available for the current result set.
## Properties
### genreTmdbIds?
> `optional` **genreTmdbIds?**: `number`[]
Unique TMDB genre identifiers present in matching items.
***
### years?
> `optional` **years?**: `number`[]
Unique production years present in matching items.
# Interface: SourceInfo
Identity, availability, and capabilities of one source exposed by an addon.
## Properties
### id
> **id**: `string`
Stable ID inside the plugin. The host namespaces it with the plugin ID.
***
### name
> **name**: `string`
Human-readable source name.
***
### icon?
> `optional` **icon?**: `string`
Optional source-specific icon. A package-relative asset overrides manifest.icon;
HTTPS URLs are also supported.
***
### isAvailable
> **isAvailable**: `boolean`
Whether the source is currently configured and usable.
***
### isHidden?
> `optional` **isHidden?**: `boolean`
Whether the source should be omitted from normal source pickers.
***
### capabilities
> **capabilities**: [`SourceCapabilities`](SourceCapabilities.md)
Features the host may safely invoke on this source.
# Interface: SourceMediaItem
Logical media or container returned by a source addon.
## Extended by
- [`PluginSourceCatalogItem`](PluginSourceCatalogItem.md)
## Properties
### id
> **id**: `string`
Stable source identifier. It is passed back to resolvePlayback.
***
### type
> **type**: [`SourceMediaType`](../type-aliases/SourceMediaType.md)
Shape of this item.
***
### title
> **title**: `string`
User-facing title.
***
### isContainer
> **isContainer**: `boolean`
Whether selecting the item should browse children instead of starting playback.
***
### tmdbId?
> `optional` **tmdbId?**: `number`
Authoritative TMDB identifier for this media. The host may use it for
enrichment and exact cross-source reconciliation; omit it when only guessed.
***
### imdbId?
> `optional` **imdbId?**: `string`
IMDb identifier when known.
***
### hash?
> `optional` **hash?**: `string`
Optional source-provided content or deduplication hash.
***
### runTimeTicks?
> `optional` **runTimeTicks?**: `number`
Duration in 100-nanosecond ticks.
***
### thumbnailUrl?
> `optional` **thumbnailUrl?**: `string`
Compact image suitable for rows or source-specific lists.
***
### premiereDate?
> `optional` **premiereDate?**: `string`
ISO-8601 premiere or release date.
***
### productionYear?
> `optional` **productionYear?**: `number`
Four-digit production year.
***
### originalTitle?
> `optional` **originalTitle?**: `string`
Original-language title.
***
### overview?
> `optional` **overview?**: `string`
Plot or content summary.
***
### rating?
> `optional` **rating?**: `number`
Source-provided rating normalized to the inclusive 0-to-10 scale.
***
### genres?
> `optional` **genres?**: `object`[]
Human-readable genres.
#### name
> **name**: `string`
***
### posterUrl?
> `optional` **posterUrl?**: `string`
Portrait artwork URL.
***
### backdropUrl?
> `optional` **backdropUrl?**: `string`
Landscape artwork URL.
***
### seasonNumber?
> `optional` **seasonNumber?**: `number`
Season number for season and episode items.
***
### episodeNumber?
> `optional` **episodeNumber?**: `number`
Episode number for episode items.
***
### seriesTmdbId?
> `optional` **seriesTmdbId?**: `number`
Authoritative TMDB series identifier used to enrich and reconcile a season
or episode in its parent context; omit it when only guessed.
***
### seriesTitle?
> `optional` **seriesTitle?**: `string`
Parent series title for a season or episode.
***
### childrenCount?
> `optional` **childrenCount?**: `number`
Number of known direct children for a container.
***
### qualityLabel?
> `optional` **qualityLabel?**: `string`
Composite or legacy quality label. Prefer the structured fields below when known.
***
### resolution?
> `optional` **resolution?**: `string`
Technical resolution such as `1920x1080`.
***
### source?
> `optional` **source?**: `string`
Human-readable distribution origin such as `WEB-DL` or `BluRay`.
***
### encoding?
> `optional` **encoding?**: `string`
Codec or encoding label.
***
### language?
> `optional` **language?**: `string`
Audio or content language label.
***
### tags?
> `optional` **tags?**: [`SourceTag`](SourceTag.md)[]
Addon-defined badges for metadata that does not fit the structured technical fields.
***
### addedAt?
> `optional` **addedAt?**: `string`
ISO-8601 date at which the source added this item.
# Interface: SourcePagedResult
One page of logical media returned by search or browse.
## Properties
### items
> **items**: [`SourceMediaItem`](SourceMediaItem.md)[]
Logical items in this page.
***
### nextPageToken?
> `optional` **nextPageToken?**: `string` \| `number`
Opaque token to pass unchanged when requesting the next page.
***
### hasMore
> **hasMore**: `boolean`
Whether another page can be requested.
***
### facets?
> `optional` **facets?**: [`SourceFacets`](SourceFacets.md)
Optional filters computed for the matching result set.
# Interface: SourcePlaybackInfo
Playable resource resolved from a logical source item.
## Properties
### url
> **url**: `string`
Absolute or source-supported media URL.
***
### mimeType?
> `optional` **mimeType?**: `string`
MIME type when it cannot be inferred reliably from the URL.
***
### headers?
> `optional` **headers?**: `Record`\<`string`, `string`\>
HTTP headers required when requesting the media. The host forwards them to
playback targets that support custom request headers; support can vary by
target, so test the platforms declared compatible by the addon.
***
### durationMs?
> `optional` **durationMs?**: `number`
Known media duration in milliseconds.
***
### resumePositionMs?
> `optional` **resumePositionMs?**: `number`
Suggested resume position in milliseconds.
# Interface: SourceSearchCriteria
Search, filtering, and lookup values passed to a source addon.
## Properties
### query?
> `optional` **query?**: `string`
Free-text query entered by the user.
***
### types?
> `optional` **types?**: [`SourceMediaType`](../type-aliases/SourceMediaType.md)[]
Media types that may be returned.
***
### genreTmdbIds?
> `optional` **genreTmdbIds?**: `number`[]
TMDB genre identifiers that returned items should match.
***
### year?
> `optional` **year?**: `number`
Four-digit production or release year.
***
### sortBy?
> `optional` **sortBy?**: [`SourceSortOption`](../type-aliases/SourceSortOption.md)
Sort mode requested by the host.
***
### limit?
> `optional` **limit?**: `number`
Maximum preferred number of logical items in the page.
***
### parentTmdbId?
> `optional` **parentTmdbId?**: `number`
TMDB identifier of the parent series or container.
***
### parentSeason?
> `optional` **parentSeason?**: `number`
Season number of the requested parent context.
***
### parentEpisode?
> `optional` **parentEpisode?**: `number`
Episode number of the requested parent context.
***
### parentTitle?
> `optional` **parentTitle?**: `string`
Human-readable parent title for sources without a stable metadata ID.
***
### targetTmdbId?
> `optional` **targetTmdbId?**: `number`
Direct TMDB lookup target.
***
### targetImdbId?
> `optional` **targetImdbId?**: `string`
Direct IMDb lookup target.
***
### targetSourceItemId?
> `optional` **targetSourceItemId?**: `string`
Opaque item ID previously returned by this source for the selected media.
During source selection, prefer this value over an enriched or localized
title when it belongs to the receiving source.
***
### isSourceSelection?
> `optional` **isSourceSelection?**: `boolean`
Whether the request is selecting playback for a known item. Sources that
advertise playback variants should return one playable item per variant.
***
### noEnrich?
> `optional` **noEnrich?**: `boolean`
Whether the host asks the source to avoid optional metadata enrichment.
***
### noFacets?
> `optional` **noFacets?**: `boolean`
Whether the source may omit expensive facet computation.
# Interface: SourceTag
A compact addon-defined badge displayed with a playable source.
## Properties
### label
> **label**: `string`
Short user-facing label. Hosts may truncate labels longer than 32 characters.
***
### color?
> `optional` **color?**: `` `#${string}` ``
Optional six-digit hexadecimal background color (`#RRGGBB`).
# Interface: SubscriptionConfig
Subscription to a host event delivered to the addon's `onEvent` handler.
## Properties
### event
> **event**: `string`
Public event name. Unknown future event names must be ignored safely.
# Interface: SystemEventContext\
Event delivered to an addon that subscribed through its manifest.
## Type Parameters
### TData
`TData` = `unknown`
## Properties
### eventName
> **eventName**: `string`
Public event name declared in the addon's subscriptions.
***
### timestamp
> **timestamp**: `number`
Unix timestamp in milliseconds recorded when the event was created.
***
### data?
> `optional` **data?**: `TData`
Event-specific payload. Addons must validate fields before using them.
# Interface: ToastOptions
Presentation and persistence options for an addon notification.
## Properties
### id?
> `optional` **id?**: `string`
Stable identifier used to update or dismiss an existing notification.
***
### action?
> `optional` **action?**: `"dismiss"`
Set to `dismiss` to remove the notification identified by `id`.
***
### message
> **message**: `string`
User-facing notification body.
***
### header?
> `optional` **header?**: `string`
Optional short notification title.
***
### duration?
> `optional` **duration?**: `number`
Display duration in milliseconds. Use `0` for a non-expiring update.
***
### color?
> `optional` **color?**: `"primary"` \| `"secondary"` \| `"success"` \| `"warning"` \| `"danger"` \| `"light"` \| `"medium"` \| `"dark"`
Semantic color selected from the host design system.
***
### position?
> `optional` **position?**: `"top"` \| `"bottom"` \| `"middle"`
Preferred temporary-tooltip position.
***
### showTooltip?
> `optional` **showTooltip?**: `boolean`
Display the temporary tooltip. Defaults to true.
***
### saveInCenter?
> `optional` **saveInCenter?**: `boolean`
Keep the notification in the notification center. Defaults to false.
# Network access
Every addon declares its allowed network hosts in `permissions.network`.
```json
{
"permissions": {
"network": ["api.example.com", "*.media.example.com"]
}
}
```
Entries are host names only. Do not include a scheme, port, path, query, or fragment.
- `api.example.com` grants that exact host.
- `*.example.com` grants the apex domain and its subdomains.
- `*` grants every supported HTTP(S) and WebSocket host and should be avoided unless arbitrary origins are essential to the addon.
Network access can be reviewed by the user. Redirects and development resources remain subject to the declared allowlist.
Use `api.http.get()` when the native HTTP capability is required. Handle timeouts, invalid responses, rate limits, and unavailable networks as normal runtime conditions.
A URL entered in addon configuration remains subject to this manifest allowlist;
configuration does not grant a host automatically. Use exact or wildcard-domain
entries when the possible origins are bounded. If arbitrary user-selected origins
are essential, the current contract requires `*`, so explain the broader access
and validate the configured URL before every request.
The HTTP capability returns text. It does not provide a public programmable
browser session, page-JavaScript execution, interactive authentication, challenge
solving or shared browser cookies. See [Adapt a remote
catalog](./remote-source-addons.md) before integrating a server-rendered site.
# Public addon contract
The supported addon contract consists of:
- the published `@streamshare/plugin-sdk` types;
- the manifest fields documented for the relevant SDK major version;
- the behavior explicitly described in this documentation;
- the public compatibility and versioning policy.
Anything else must be treated as an implementation detail. In particular, addons must not depend on application database layouts, native class names, private filesystem paths, undocumented globals, or the behavior of internal test packages.
The current public implementation contracts are `automation` and `source`. The manifest value `player` is reserved for a future SDK contract and is not yet available to third-party addon authors.
The SDK's runtime manifest validator is the canonical structural validation used by the host and plugin CLI. Developer tools should reuse it instead of implementing an independent schema. See [Validate a manifest](../guides/manifest-validation.md).
## Stability
Breaking contract changes require a new SDK major version. Additive fields and methods may appear in compatible minor releases. Addons should tolerate optional fields they do not use and inspect the [runtime capability matrix](./runtime-capabilities.md) before using platform- or mode-dependent APIs.
## Trust boundary
Addon code runs in a restricted environment. It receives only the capabilities granted by the host API and approved through its manifest. An addon should keep its permission set minimal and must treat user data as private.
# Runtime capabilities
`api.runtime.capabilities` is the source of truth for features available during the current invocation. Check the relevant flag before using an optional API; do not infer availability from the platform name alone.
```ts
if (api.runtime.capabilities.lists) {
const lists = await api.lists.getAll();
// Use the interactive Collection list API.
}
```
The capability indicates that the API is available, not that every list operation is authorized. The host filters visible lists and applies the current account's creation and usage rules. See [Work with Collection lists](../guides/lists.md).
Methods remain present on `IPluginAPI` so an addon has one stable structural contract across runtimes. Calling a method whose capability is `false` can reject with an unavailable-feature error.
## Current Android matrix
| Capability | Interactive | Headless | Covers |
| --- | --- | --- | --- |
| `configuration` | Yes | Yes | `getConfig()` |
| `notifications` | Yes | Yes | `toast()`, `dismissToast()` and action progress |
| `storage` | Yes | Yes | Isolated key-value storage |
| `files` | Yes | Yes | File read, write, stat, delete and move |
| `fileScanning` | Yes | No | `files.scanLines()` |
| `sourceCatalogWrite` | Yes | Yes | Snapshot creation, append, statistics, commit and abort |
| `sourceCatalogQuery` | Yes | No | `sourceCatalog.query()` and `queryVariants()` |
| `http` | Yes | Yes | Manifest-allowlisted `http.get()` |
| `lists` | Yes | No | Playlist and library operations |
| `backgroundTasks` | Host-dependent | Yes | Manifest-declared durable work |
Headless notifications are durable system notifications rather than temporary messages in an open user interface. APIs that require an interactive application session are not part of the headless SDK contract.
Other platforms and future clients can expose a different combination. Branch on capability flags and provide a useful fallback when a feature is optional:
```ts
if (!api.runtime.capabilities.fileScanning) {
api.logger.info('Streaming file scans are unavailable in this runtime.');
return;
}
const page = await api.files.scanLines('catalog.ndjson', {limit: 100});
```
# StreamShare addons
Addons extend StreamShare through a versioned, sandboxed contract. An addon declares its identity, permissions, configuration, actions, and entry script in a manifest. Its code interacts with StreamShare only through the API exposed by `@streamshare/plugin-sdk`.
Start with [your first addon](./getting-started.md), use the complete [manifest reference](./concepts/manifest.md), and connect a device through [development mode](./guides/development-mode.md). The tested [Hello addon](./examples/hello-addon.md) provides a complete minimal implementation, while the generated [SDK API reference](./api/index.md) contains exact TypeScript signatures.
When working with an AI assistant, start from the public [`llms.txt`](https://docs.tootiapps.com/llms.txt)
index and follow the [AI-assisted development guide](./guides/ai-assisted-development.md).
## Public documentation boundary
Only the capabilities described by this documentation and the published SDK form the addon contract. Addons must not rely on undocumented behavior.
# TMDB metadata and source addons
TMDB metadata enrichment belongs to the StreamShare host. A source addon normally
provides stable media identity and availability; the application can then add
localized titles, summaries, artwork, genres, ratings, dates and runtimes from its
own TMDB integration.
The addon must not copy an application TMDB credential or depend on a private
StreamShare endpoint. It can participate by returning public identifiers.
## Provide the right identity
| Returned item | Identity used for enrichment |
| --- | --- |
| Movie | `tmdbId` for the movie |
| Series | `tmdbId` for the series |
| Season | `seriesTmdbId` and `seasonNumber` |
| Episode | `seriesTmdbId`, `seasonNumber` and `episodeNumber` |
Keep the addon `id` stable as well. TMDB identity enriches and deduplicates media,
while the addon ID remains the key passed back to `resolvePlayback()`.
```ts
const item: SourceMediaItem = {
id: 'provider:movie:42',
type: 'movie',
title: 'Fallback title',
isContainer: false,
tmdbId: 10378,
};
```
The title remains a useful fallback. When no TMDB ID is known, StreamShare may try
a best-effort title and year match, but explicit identifiers are faster and avoid
ambiguous matches.
Only return an identifier that the source can assert for that media. StreamShare
may use an addon-provided TMDB, IMDb or content hash as exact cross-source identity.
A host match inferred from title and year can improve presentation, but is not used
as proof for cross-source merging or hierarchy lookup. Ambiguous records therefore
remain as separate results rather than risk combining different media.
## Host enrichment is not source lookup
Two related behaviors must not be confused:
- **Host enrichment:** returning `tmdbId` or `seriesTmdbId` allows StreamShare to
decorate results. This works without `supportsTmdbLookup`.
- **Direct source lookup:** setting `supportsTmdbLookup: true` promises that the
addon applies `targetTmdbId` and relevant `parentTmdbId` criteria before
pagination.
```ts
const matching = items.filter((item) => {
if (criteria.targetTmdbId && item.tmdbId !== criteria.targetTmdbId) return false;
if (criteria.parentTmdbId && item.seriesTmdbId !== criteria.parentTmdbId) return false;
return true;
});
```
Advertise direct lookup only when this behavior is implemented. It lets source
selection ask whether the addon has playable media for a movie, series or episode
already identified by the application.
During source selection, `targetSourceItemId` may also contain the opaque ID that
the receiving source previously returned for the selected logical item. A source
should prefer its own stable ID over the display title, because host enrichment may
replace or localize that title. This hint does not imply `supportsTmdbLookup`.
## Preserve hierarchy ownership
For a unified series or season, StreamShare expands each attached source with the
opaque container ID that source originally returned. Other active sources are
queried only when a trusted parent TMDB identity is available and they advertise
`supportsTmdbLookup`. A source that supports neither route is skipped for that
level; it is not searched by title as a fallback.
Consequently, `browse(parentId)` must treat `parentId` as an exact source-owned
container identity, and direct TMDB lookup must return children of the requested
parent only. A child carrying a different explicit parent identity can be ignored
by the host. Keeping stable container IDs across refreshes and pagination is what
allows source-native and TMDB-aware hierarchies to coexist safely.
## Decide which fields the source owns
TMDB describes media; the source describes access to it. The source should remain
authoritative for:
- its stable item ID and container hierarchy;
- availability and playback resolution;
- required playback headers;
- source, quality, encoding and language labels;
- source-specific artwork or metadata that must override a generic presentation.
Do not download TMDB artwork merely to return it unchanged. Let the host select
the appropriate image size, language and cache policy. When the request contains
`noEnrich: true`, avoid optional or expensive metadata work; the host may still
use metadata already present in its cache.
## Network permissions and credentials
Returning TMDB IDs requires no addon network permission. The application's TMDB
configuration remains host-owned. If an addon deliberately contacts another
metadata API itself, it must declare only the required domains and manage its own
authorization; that is a separate integration from StreamShare enrichment.
The [Example Catalog Source](../examples/catalog-source-addon.md) demonstrates both
host enrichment and direct TMDB lookup without embedding credentials in addon code.
# Type Alias: BackgroundNetworkType
> **BackgroundNetworkType** = `"connected"` \| `"unmetered"`
Network condition required before Android may start a background task.
# Type Alias: PluginActionParameters
> **PluginActionParameters** = `Readonly`\<`Record`\<`string`, `unknown`\>\>
Parameters supplied when a user invokes a manifest-declared addon action.
# Type Alias: PluginConfiguration
> **PluginConfiguration** = `Readonly`\<`Record`\<`string`, `string`\>\>
Configuration values resolved from the addon manifest and user settings.
# Type Alias: PluginIconAppearance
> **PluginIconAppearance** = `"auto"` \| `"monochrome"` \| `"original"`
Rendering policy applied by the host to an addon's declared icon.
# Type Alias: PluginRuntimeMode
> **PluginRuntimeMode** = `"interactive"` \| `"headless"`
Whether the addon runs with the interactive application or as durable background work.
# Type Alias: PluginRuntimePlatform
> **PluginRuntimePlatform** = `"android"` \| `"ios"` \| `"web"` \| `string` & `object`
Host platform for one addon invocation. Future platform identifiers are allowed.
# Type Alias: PluginRuntimeReason
> **PluginRuntimeReason** = `"manual"` \| `"periodic"` \| `"install"` \| `"config"`
Reason why the current addon execution started.
# Type Alias: PluginType
> **PluginType** = `"automation"` \| `"source"` \| `"player"`
Functional category declared by an addon.
`automation` and `source` have public SDK contracts. `player` is reserved for
a future contract and must not be used by third-party addons yet.
# Type Alias: SourceMediaType
> **SourceMediaType** = `"movie"` \| `"series"` \| `"season"` \| `"episode"` \| `"folder"` \| `"video"`
Media shapes understood by the public source-addon contract.
# Type Alias: SourceSortOption
> **SourceSortOption** = `"relevance"` \| `"date"` \| `"rating"` \| `"title"` \| `"popularity"`
Sort modes a source can apply before pagination.
# Type Alias: StreamShareAddon
> **StreamShareAddon** = [`IAutomationPlugin`](../interfaces/IAutomationPlugin.md) \| [`ISourcePlugin`](../interfaces/ISourcePlugin.md)
Addon contracts that can currently be registered with the StreamShare host.
# Validate a manifest
The SDK provides the canonical runtime validator for addon manifests. The StreamShare host and plugin CLI use this same implementation, so a manifest accepted locally is checked against the same structural rules when it is installed.
Use `parsePluginManifest` at an untrusted boundary such as a JSON file, HTTP response, or package archive:
```ts
import {parsePluginManifest} from '@streamshare/plugin-sdk';
const manifest = parsePluginManifest(JSON.parse(manifestJson));
console.log(manifest.id, manifest.version);
```
The function returns a typed `PluginManifest` or throws a `PluginManifestValidationError`. Its `field` property identifies the invalid manifest field when one is available.
If you already hold a value and want an assertion, use `assertPluginManifest`:
```ts
import {assertPluginManifest} from '@streamshare/plugin-sdk';
const candidate: unknown = JSON.parse(manifestJson);
assertPluginManifest(candidate);
// candidate is now typed as PluginManifest.
console.log(candidate.actions ?? []);
```
For a non-throwing check, use `isPluginManifest(candidate)`. Avoid maintaining a separate schema in an addon or developer tool: importing the SDK validator prevents rule drift as the public contract evolves.
## What is validated
The validator checks the public manifest contract, including:
- reverse-domain addon identifiers and strict semantic versions;
- compatible-version ranges, package-relative entry points and supported icon paths;
- declared permissions and network permission patterns;
- action inputs, configuration defaults and select choices;
- unique action, configuration, subscription and background-task identifiers;
- background-task references to declared actions.
Filesystem concerns remain the responsibility of the package reader. For example, the CLI also verifies that the declared icon exists and respects its size limit.
Unknown top-level fields are tolerated for forward compatibility. They are not considered supported public API unless this documentation and the SDK types describe them.
For the meaning, requirement level and constraints of each field, use the complete [addon manifest reference](../concepts/manifest.md).
# Work with Collection lists
The interactive list API lets an addon contribute playable links to the user's **Collection** source. Use it when the addon is importing or producing personal library entries. A source addon that maintains its own browsable catalog should use the [source addon API](./source-addons.md) instead.
## Check availability
List operations require an interactive execution with the `lists` runtime capability:
```ts
if (!api.runtime.capabilities.lists) {
api.logger.info('Collection lists are unavailable in this execution.');
return;
}
```
The host returns only the lists the current account may use. Creation can also be refused when the account has reached its list limit, so treat `create()` as an operation that can reject.
## Select or create a target
Prefer an existing list selected by the user. If the addon does not expose a list setting, it can fall back to the protected main list and create a dedicated list only when the account permits it:
```ts
const lists = await api.lists.getAll();
let target = lists.find(list => list.locked) ?? lists[0];
if (!target) {
const id = await api.lists.create('My addon');
target = {id, name: 'My addon'};
}
```
Do not hard-code a numeric list identifier or assume that every account can create an additional list.
## Add items
Use `addItem()` for an isolated entry or `addItems()` for a batch. Supply a playable URL, a display filename and the most accurate media type available. Include TMDB identifiers and episode coordinates when the addon already knows them.
```ts
await api.lists.addItems(target.id, [
{
filename: 'Example movie',
url: 'https://media.example/movie.m3u8',
type: 'movie',
tmdbID: 1234,
},
]);
await api.lists.enrichList(target.id);
```
`enrichList()` asks StreamShare to complete supported media information. It is useful after a batch that contains partial metadata, but it is not required when the supplied information is already sufficient.
## Maintain addon-owned entries
Use `getById()` to inspect a list, `removeItem()` to delete a specific entry and `clear()` only when the user has explicitly chosen to replace all content in that list. The SDK intentionally does not rename or delete lists: those lifecycle actions remain under user control in the Media Library.
Every operation is checked by the host. Handle rejected or unavailable operations without discarding the addon's own source data or leaving a partially prepared user workflow.
# Desktop clients
StreamShare desktop clients allow a Windows, macOS, or Linux computer to act as a playback receiver. They are separate from addons and remote catalog services: a client receives playback commands and presents media on the host computer.
Public installation instructions, release notes, and checksums will be linked here when distribution begins.
See [platform support](./platforms.md) for the planned public support matrix.
# Desktop platform support
The desktop client targets Windows, macOS, and Linux. Exact operating-system versions, CPU architectures, package formats, and signing status are release-specific and will be published with each supported build.
Only operating systems, architectures, and package formats listed for a public release are supported.
# Homepage
The homepage brings together useful shortcuts from your configured content sources. It is intended for resuming playback and discovering something quickly without opening every source separately.
The available rows depend on your playback activity, configured sources, the sources selected in the [Media Hub](./media-hub.md#choose-your-sources), and the metadata currently available for their media.
## Homepage sections
| Section | What it is for | Where to configure it |
| --- | --- | --- |
| **New episodes available** | Find new episodes from series you follow. | **Settings > Appearance > Home portal** |
| **Continue my TV shows** | Resume series that have unfinished episodes. | **Settings > Appearance > Home portal** |
| **History** | Reopen the media you played most recently. | **Settings > Appearance > Home portal** |
| **Latest movies** | Browse recently released movies from compatible sources. | **Settings > Appearance > Home portal** |
| **Latest TV shows** | Browse recently released series from compatible sources. | **Settings > Appearance > Home portal** |
| **Popular movies** and **Popular TV shows** | Discover titles currently marked as popular. | **Settings > Appearance > Home portal** |
| **Explore sources** | Open the root of a selected source and follow its own folders and categories. | Configure the source under **Settings > Content**, then select it in the Media Hub. |
| **Favorites** | Reopen Media Hub pages and locations you saved. | Add or remove a favorite from the relevant Media Hub page. |
Selecting a media item opens its available playback choices. **See more** opens the corresponding Media Hub category, including the complete playback history from the History row.
## Choose what appears
Open **Settings > Appearance > Home portal** to:
- show or hide each group of homepage sections;
- move sections up or down;
- keep the most useful rows near the top on the current device.
See [Appearance settings](./settings.md#appearance) for the complete list of controls.
## If a section is empty
Check the following in order:
1. The relevant integration is configured and enabled under [Settings > Content](./settings.md#content).
2. The source is selected in the [Media Hub source picker](./media-hub.md#choose-your-sources).
3. The source supports the media type or ordering used by that section.
4. If only artwork or descriptions are missing, see [Media information and TMDB](./media-metadata.md).
# Media Hub
The Media Hub provides one place to browse media from your collection, connected services and source extensions. When several selected sources identify the same movie or series, StreamShare can present one media entry while preserving the available playback choices.
## Main sections
| Section | What it contains |
| --- | --- |
| **Movies** | Movies supplied by the selected compatible sources. |
| **Series** | Series, seasons and episodes supplied by the selected compatible sources. |
| **Others** | Videos that do not belong to the movie or series categories. |
| **Explorer** | The folders and categories defined by each selected source. |
| **History** | Media previously started or marked as watched, with a local title search. |
Use **Explorer** when you want to follow a source's own organization. Use Movies, Series or Others when you want a unified view across several sources.
## Choose your sources
Open the source picker in the Media Hub toolbar and select the integrations that should contribute to browsing and search.
- The same selection applies to Movies, Series, Others and Explorer.
- The selection is kept on the current device.
- The free plan can select up to two sources at the same time. Premium removes this limit.
- A configured integration remains manageable under [Settings > Content](./settings.md#content) even when it is not selected in the Media Hub.
The source picker controls which sources are consulted; it does not remove or reconfigure them.
If parental control is configured, the lock button in the toolbar controls adult content for the current session. While locked, adult sources and their saved history or favorites are not shown. A public shortcut that combines several sources may remain visible, but adult results are removed when it is opened and potentially adult artwork is replaced by a neutral image. Enter the PIN to restore protected content; locking the control hides it again immediately.
## Search, filters and sorting
Open the search control in Movies, Series or Others to enter a keyword. Genre and year filters are offered when every selected source used for the current view can apply them consistently. Sort choices follow the same rule.
Explorer keeps the navigation and ordering provided by the current source, so global search and filters are not shown there. History provides its own search over saved media titles.
If a control is absent, the current source selection or section does not offer that operation consistently. You can change the selected sources or use Explorer to access a source's own browsing options.
## Open and play a media item
Selecting a media item opens its playback choices. Depending on the information supplied by each source, a choice can show details such as quality, language, format or provider.
The source selector can also indicate a source used previously for that media. Choose a source and playback target, then start playback. To skip repeated choices when suitable defaults are available, enable **Settings > Playback & Cast > Quick Play** and select a default player.
Available actions can include adding the current page to favorites, opening a trailer, or updating watched status. Actions appear only when they apply to the selected item.
## Configure content integrations
The Media Hub consumes integrations configured elsewhere:
- **Settings > Content > Media Library** manages local playlists and their media information.
- **Settings > Content > Services** connects compatible remote services.
- **Settings > Content > Jellyfin** connects Jellyfin servers.
- **Settings > Content > Extension sources** manages source addons.
Developers can use the dedicated [addon documentation](/addons/) or [service documentation](/services/) to build these integrations.
For Main List, account limits and list management, see [Media Library and lists](./media-library.md).
# Media Library and lists
The Media Library is the list manager behind the **Collection** source. Each list is displayed as a folder in **Media Hub > Explorer > Collection**, while its identified movies and series can also contribute to the corresponding unified sections.
Open **Settings > Content > Media Library** to manage these lists.
## Main List
**Main List** is created automatically and cannot be removed. It is the default destination when a supported mobile application shares a playable web address with StreamShare.
There is no separate “received links” list and no current-list setting: shared links always go to Main List. Open the list from its actions to review the links it contains.
## Account limits
The free plan can use Main List. Premium accounts can create and use additional lists.
If additional lists remain on a device after Premium access ends, their data is retained and the lists can still be managed in Settings, but only Main List is available for browsing in Collection until the account permits the additional lists again.
## Manage lists
Use **Add** to create an empty list when your account permits another one. Open a list's actions to:
- view its contents in the Media Hub;
- rename it;
- clear all of its items;
- remove it when it is not a protected list.
Clearing a list keeps the list itself. Removing a list deletes both the list and its items. Main List is protected from removal.
## Add content
Lists can be populated in two supported ways:
- share an individual playable address with StreamShare on a supported mobile device; it is added to Main List;
- use an addon that works with the public list API to add one or more items to a list authorized for the current account.
File and URL-based JSON list import, JSON export and source-URL synchronization are not part of the Media Library workflow. Addon developers can use the [list API guide](/addons/guides/lists) for maintained integrations.
## Refresh media information
Choose the Media Library scan action to look for items whose movie, series or episode information has not been identified. Review the exclusions report for items that could not be matched or that you chose to exclude.
See [Media information and TMDB](./media-metadata.md) to understand matching and refresh behavior.
# Media metadata and TMDB
StreamShare uses TMDB to present consistent information for media discovered from
the application, services and source addons. When a source provides a reliable
TMDB identity, the application can add localized titles and summaries, posters,
backdrops, genres, ratings, release dates, runtimes, seasons and episode details.
## Metadata and playback remain separate
TMDB describes a movie or series; it does not provide the playable resource.
Sources remain responsible for availability, quality information and playback
resolution. This separation lets several sources offer the same media while the
interface presents one consistent identity and set of artwork.
## Why some matches are better than others
The most reliable matches use a TMDB identifier supplied by the content source.
Movies and series each have their own identifier; seasons and episodes also use
their season or episode number.
When that identifier is unavailable, StreamShare may try to match the title and
year. This can be less precise for remakes, similarly named media or translated
titles.
## Refresh media information
StreamShare keeps recently used media information so pages can appear more
quickly. Details that are missing or out of date can be refreshed in the
background and may appear shortly afterwards.
For followed series, use **Settings > Accounts & Synchronization > TMDB
synchronization** to choose the refresh frequency or start an update manually.
To force StreamShare to retrieve selected information again, open **Settings >
System > Cache** and clear the relevant media-information category. The next
visit can take longer while the information and artwork are retrieved again.
## If information is missing or incorrect
1. Confirm that the content source is still available under [Settings > Content](./settings.md#content).
2. Refresh TMDB information for followed series, or clear only the relevant cache category.
3. Reopen the media item after the refresh completes.
4. If the wrong title keeps returning, report the affected title, year and source so its match can be checked.
Addon authors can consult [TMDB metadata and source addons](/addons/guides/tmdb-metadata)
for the technical identity and lookup contract.
# Mobile application
The mobile application lets you browse your content, start playback locally or on another screen, and control a compatible playback target from a phone or tablet.
## Find a feature
- [Homepage](./homepage.md) provides shortcuts for resuming and discovering media.
- [Remote control](./remote-control.md) controls a Kodi device, Chromecast session or StreamShare receiver.
- [Media Hub](./media-hub.md) brings together the content sources selected on this device.
- [Settings](./settings.md) connects accounts, sources and playback devices.
## Play locally or on another screen
When opening a media source, choose the phone or tablet for local playback, or select an available remote target. Configure saved devices and playback defaults under [Settings > Playback & Cast](./settings.md#playback-and-cast).
Quick Play can skip this choice when a suitable source and default target are already available. Leave it disabled when you prefer to review the available source and target each time.
## Notifications
Open **Settings > Notifications** to review saved messages and progress updates from StreamShare. You can dismiss one message or clear the list when it is no longer useful.
Supported addon and service features can vary by platform. Their developer-facing compatibility details are documented separately in [addon compatibility](/addons/compatibility) and [service compatibility](/services/compatibility).
# Remote control
Remote control lets you manage playback on another compatible screen without returning to that device. Use it after selecting a Kodi device, a Chromecast session or a StreamShare receiver.
## Prepare a playback target
- For a Kodi device, complete the [Kodi setup](#configure-kodi), then open **Settings > Playback & Cast > Devices** and add it.
- For a StreamShare receiver, make the receiver available on the same local network and authorize the controller when requested. See [Desktop clients](../clients/index.md) for Windows, macOS and Linux receivers.
- Chromecast targets are offered when a compatible receiver is available to the device running StreamShare.
## Configure Kodi
Kodi must accept remote commands from the device running StreamShare:
1. In Kodi, open **Settings**, then **System information**, and note the device's IP address.
2. Open **Settings > Services > Control**.
3. Enable remote control over HTTP and note the configured port.
4. Enable authentication and choose a username and password.
5. Allow remote control from applications on other systems.
6. In StreamShare, open **Settings > Playback & Cast > Devices**, add a device, and enter a name, the Kodi IP address, port, username and password.
The two devices must be able to reach each other on the same network. If the Kodi address or credentials change, edit the saved device in StreamShare.
## Select the device to control
1. Open **Remote control**.
2. Choose the target type at the top of the screen.
3. Select the device or active session.
StreamShare keeps the controls associated with the selected target. You can change target at any time from the same selector.
## Available controls
Controls are shown only when the selected target supports them.
| Control | Use |
| --- | --- |
| Direction pad and **OK** | Navigate the receiver interface and confirm a selection. |
| Playback timeline | View progress or move to another position. |
| Play, pause and stop | Control the current media session. |
| Back | Return to the previous screen on compatible receivers. |
| Audio and subtitles | Choose among the tracks reported by the current playback target. |
| Volume | Adjust the receiver or session volume. |
When playback information is available, the remote also displays the current media, duration and useful stream details.
## If the remote cannot connect
For configured devices, first verify **Settings > Playback & Cast > Devices**. Confirm that the controller and receiver are reachable on the same network and that the saved address and credentials are still valid.
For a StreamShare receiver, also verify that its receiving mode is enabled and that the controlling device is authorized.
# Settings
Settings is where you connect StreamShare to your accounts, content sources and playback devices. It also controls the homepage, application experience and troubleshooting tools.
The presentation differs between mobile and TV, but the main sections serve the same purpose.
## Notifications on mobile
Open **Notifications** to review saved messages and progress updates from StreamShare. Use it when a short-lived message has disappeared before you could read it. You can dismiss one message or clear the complete list.
## Accounts and synchronization
Use this section for accounts and data that can follow you across devices.
### Subscription
View the current plan and manage Premium access. Premium affects features such as the number of Media Hub sources that can be selected together.
### AllDebrid
Connect an AllDebrid account when you want StreamShare to resolve supported links through that service. Select **AllDebrid**, open the displayed link or scan its QR code, then confirm the displayed code with your AllDebrid account. The status indicator changes when authorization succeeds.
After connection, select the card again to enable, disable or reset the account. Enable automatic link resolution if you intend to use Quick Play without being asked to resolve a link first.
### Trakt
Connect Trakt to synchronize supported collection and viewing information. Select **Trakt**, open the displayed link or scan its QR code, and confirm the code with your Trakt account. After connection, select the card again to start collection synchronization, temporarily disable the integration or reset its authorization.
### Backups
Create and restore supported application data with Google Drive. Use backups before changing device or reinstalling the application.
Open **Backups**, connect Google Drive if requested, then choose **Create a backup** and select the data to include. A password is optional, but a protected backup cannot be restored if that password is forgotten. To restore data, choose an available backup, enter its password when required, and review the data to restore before confirming.
### TMDB synchronization
Choose how often StreamShare refreshes information for active series, or start an update manually. Use this when followed series are missing newly available episode information.
## Content
Use this section to decide where the [Media Hub](./media-hub.md) obtains media.
### Media Library
Create and manage the lists exposed by the Collection source. Open **Media Library** to add a list when your account permits it, review its items, rename or clear it, and refresh its media information. See [Media Library and lists](./media-library.md) for the role of Main List and the available workflows.
### Services
Add compatible remote services when content is supplied by a StreamShare service server. Choose **Add a service**, enter its address, select the authentication method required by that service, then connect and save. Link or QR-code authentication must be completed on the page displayed by the service.
An installed service can expose a catalog, browsing sections, search and playback sources according to its capabilities. Return to this section to enable, disable, edit or remove it. If its session is no longer accepted, reset the authentication and connect again.
### Jellyfin
Connect a Jellyfin server to include its libraries as StreamShare sources. Open **Jellyfin**, add a server detected on the local network or enter its address manually, then sign in with a Jellyfin user. Return here to edit the address, change user, reauthenticate or remove the server.
### Extension sources
Manage source addons that contribute content to the Media Hub. The installed list lets you enable, disable, configure, update or remove an addon when those actions are available. Use **Add** to install from a supported catalog or address. Developer instructions belong in the [addon documentation](/addons/).
After configuring a source, select it from the [Media Hub source picker](./media-hub.md#choose-your-sources) if you want it included in unified browsing.
### Parental control
An addon can declare that its content is intended for adults. When you first enable such an addon, StreamShare offers to protect adult content with a numeric PIN; protection remains optional.
When parental control is enabled and locked, adult addons and adult-only content are omitted from source lists, browsing, search, playback choices, history and favorites. The installed-addons list displays a notice with an unlock action when adult addons are currently hidden. Public shortcuts that aggregate several sources can remain visible with neutral artwork; opening them still excludes adult results. Unlocking restores protected content for the current application session. Use **Settings > Content > Parental control** to configure, change or disable the PIN.
## Playback and Cast
Use this section to choose where media plays and how StreamShare starts playback.
### StreamShare TV Client
Enable receiving on a compatible StreamShare device when another StreamShare device should be able to select it as a playback target. Choose a recognizable device name, enable receiving and approve the controller when it connects. The same page can revoke an authorized controller or forget a paired receiver.
### Devices
Add and configure Kodi devices. Enter the address and connection information used by that Kodi installation, then save it. The saved device becomes available during playback selection and in the [Remote control](./remote-control.md). See [Configure Kodi](./remote-control.md#configure-kodi) for the settings required on Kodi itself.
### Default player
Choose the playback target StreamShare should propose by default. Select the device you use most often to reduce repeated choices.
### Quick Play
Quick Play starts playback directly when StreamShare can use the saved defaults and resolve a suitable source. Leave it disabled if you prefer to review the source and target before every playback.
### Player extensions
Manage extensions that add supported playback integrations or players.
## Experience
Use this section for application-wide behavior:
- **Language** changes the application language.
- **Quick Play** controls whether suitable defaults can start playback directly.
- **Show news** controls whether release news is presented after an update.
- On mobile, **Suggest rating and sharing** can offer a sharing prompt near the end of playback; nothing is shared without confirmation, and the suggestion frequency is configurable.
### Performance profile on TV
Open **Settings > Experience > Performance profile** when a TV needs a different balance between visual quality and responsiveness:
- **Recommended by the app** uses the profile selected for the installed release.
- **Automatic** adapts to the TV's available resources.
- **Standard** keeps the complete visual presentation.
- **Lightweight** reduces visual complexity to prioritize responsiveness.
StreamShare indicates when a restart is required to apply the change throughout the TV application.
## Extensions
This section separates everyday extension management from developer tools:
- **Installed** lists extensions already available and opens their configuration or actions.
- **Add** installs an extension from a supported source.
- **Developer mode** loads and tests an addon during development. See [Addon development mode](/addons/guides/development-mode) for the technical workflow.
## Appearance
Appearance controls the [Homepage](./homepage.md):
- show or hide new episodes and series to continue;
- show or hide playback history;
- show or hide latest movies and latest series;
- show or hide popular media, source shortcuts and favorites;
- move homepage sections up or down.
These choices change the organization of the current device; they do not enable or disable the underlying content sources.
## Support
Open **Documentation** to consult the maintained StreamShare guides on the documentation website. Use **Report an issue** when you need to describe a reproducible problem or suggest an improvement; include the application version and the shortest sequence that reproduces the problem. On TV, both destinations display a QR code that can be scanned with a phone when no suitable browser is available.
## System
### Cache
View and clear cached media information. Clearing a cache can help refresh stale artwork or descriptions, but the information may need to be downloaded again.
### System logs
Enable logs when diagnosing a problem, reproduce it, then export the logs for support. Clear them when they are no longer needed.
### Information
View the installed application version and other information useful when requesting support or checking compatibility.
# StreamShare application
The application documentation follows the same four main areas as StreamShare.
Start with the screen you are using:
- [Homepage](./homepage.md) — resume watching and discover content from your sources.
- [Remote control](./remote-control.md) — choose a playback target and control it from StreamShare.
- [Media Hub](./media-hub.md) — browse, search and play media from your selected sources.
- [Settings](./settings.md) — connect accounts, configure content sources and adapt the experience.
## A typical first setup
1. Open [Settings](./settings.md#content) and configure the content sources you want to use.
2. Open the [Media Hub](./media-hub.md#choose-your-sources) and select the sources included in unified browsing and search.
3. Use the [Homepage](./homepage.md) for shortcuts and recommendations, or open a Media Hub category for a broader search.
4. If you want to play on another screen, configure a target in [Playback & Cast settings](./settings.md#playback-and-cast), then select it from the source picker or [Remote control](./remote-control.md).
## Device-specific guidance
The same features are adapted to each type of device:
- [Mobile application](./mobile.md) for touch navigation and portable control.
- [TV application](./tv.md) for directional navigation and viewing at a distance.
Media artwork and information can come from several sources. See [Media information and TMDB](./media-metadata.md) if a title, poster or episode detail is missing or incorrect.
# StreamShare applications
StreamShare brings media discovery, library access, and playback to mobile devices and televisions. This documentation describes user-visible capabilities, supported platforms, compatibility, and the public extension points available to developers.
Choose the area that matches your goal:
- [Mobile application](./application/mobile.md) for phones and tablets.
- [TV application](./application/tv.md) for television-oriented navigation and playback.
- [Desktop clients](./clients/index.md) for Windows, macOS, and Linux receivers.
- [Addon development](/addons/) to extend StreamShare inside its sandboxed addon runtime.
- [Service development](/services/) to expose a remote HTTP catalog to StreamShare.
# Support and compatibility
Each public release will identify its supported platforms and the compatible major versions of developer contracts. Experimental or private builds are not part of the public support matrix.
Compatibility information for developer integrations is maintained separately:
- [Addon compatibility](/addons/compatibility)
- [Service compatibility](/services/compatibility)
# TV application
The TV application brings StreamShare discovery and playback to a large screen. It is designed for a directional remote: move between controls, press **OK** to select, and press **Back** to return to the previous screen.
## Find a feature
- [Homepage](./homepage.md) provides shortcuts for resuming and discovering media.
- [Remote control](./remote-control.md) controls another compatible playback target.
- [Media Hub](./media-hub.md) brings together the content sources selected on this TV.
- [Settings](./settings.md) connects accounts, sources and playback devices.
When a screen contains more content than can be shown at once, keep moving in the desired direction to reveal it. The highlighted control indicates where the next action will apply.
## Play on this TV
Choose this TV as the playback target when opening a media source. Playback opens in the television presentation and can be minimized when you want to return to the application without ending the current session.
Receiving playback from another StreamShare device can be enabled under **Settings > Playback & Cast > StreamShare TV Client**. The same section manages which controllers are authorized on the local network.
## Performance profile
On television builds, open **Settings > Experience > Performance profile** to
balance visual quality and interface responsiveness for the current TV:
- **Recommended by the app** follows the default selected for the installed release.
- **Automatic** adapts the profile to the TV's available resources.
- **Standard** keeps the complete visual presentation.
- **Lightweight** prioritizes responsiveness by reducing visual complexity and
display detail where appropriate.
The selected preference is stored on the TV and does not affect mobile devices.
StreamShare displays the profile that is currently active and asks for a restart
when a change must be applied to the entire interface.
# @streamshare/service v1.0.1
## Classes
- [StreamShareProtocolError](classes/StreamShareProtocolError.md)
## Interfaces
- [APIEndpoint](interfaces/APIEndpoint.md)
- [ParamBase](interfaces/ParamBase.md)
- [ParamSelect](interfaces/ParamSelect.md)
- [ParamText](interfaces/ParamText.md)
- [BasicServiceAuthentication](interfaces/BasicServiceAuthentication.md)
- [ApiKeyServiceAuthentication](interfaces/ApiKeyServiceAuthentication.md)
- [DeviceCodeServiceAuthentication](interfaces/DeviceCodeServiceAuthentication.md)
- [ServiceDeviceAuthorization](interfaces/ServiceDeviceAuthorization.md)
- [ServiceDeviceTokenRequest](interfaces/ServiceDeviceTokenRequest.md)
- [ServiceRefreshTokenRequest](interfaces/ServiceRefreshTokenRequest.md)
- [ServiceTokenGrant](interfaces/ServiceTokenGrant.md)
- [StreamSharePagination](interfaces/StreamSharePagination.md)
- [StreamShareFacets](interfaces/StreamShareFacets.md)
- [StreamShareResponse](interfaces/StreamShareResponse.md)
- [BrowseEndpoint](interfaces/BrowseEndpoint.md)
- [PlayLinkMetadata](interfaces/PlayLinkMetadata.md)
- [PlayLink](interfaces/PlayLink.md)
- [StreamShareTmdbReference](interfaces/StreamShareTmdbReference.md)
- [StreamShareResultBase](interfaces/StreamShareResultBase.md)
- [StreamShareMediaResult](interfaces/StreamShareMediaResult.md)
- [StreamShareResultMovie](interfaces/StreamShareResultMovie.md)
- [StreamShareResultEpisode](interfaces/StreamShareResultEpisode.md)
- [StreamShareResultUnclassified](interfaces/StreamShareResultUnclassified.md)
- [StreamShareResultSeries](interfaces/StreamShareResultSeries.md)
- [StreamShareResultSeason](interfaces/StreamShareResultSeason.md)
- [StreamShareResultFolder](interfaces/StreamShareResultFolder.md)
- [StreamShareService](interfaces/StreamShareService.md)
## Type Aliases
- [ServiceEndpointKind](type-aliases/ServiceEndpointKind.md)
- [ServiceParameterRole](type-aliases/ServiceParameterRole.md)
- [Param](type-aliases/Param.md)
- [ServiceAuthentication](type-aliases/ServiceAuthentication.md)
- [ServiceDeviceTokenResponse](type-aliases/ServiceDeviceTokenResponse.md)
- [MetadataColor](type-aliases/MetadataColor.md)
- [StreamShareResult](type-aliases/StreamShareResult.md)
- [StreamShareItemType](type-aliases/StreamShareItemType.md)
## Functions
- [defineService](functions/defineService.md)
- [assertStreamShareService](functions/assertStreamShareService.md)
- [parseServiceDeviceAuthorization](functions/parseServiceDeviceAuthorization.md)
- [parseServiceTokenGrant](functions/parseServiceTokenGrant.md)
- [parseServiceDeviceTokenResponse](functions/parseServiceDeviceTokenResponse.md)
- [parseStreamShareService](functions/parseStreamShareService.md)
- [isStreamShareService](functions/isStreamShareService.md)
- [assertStreamShareResponse](functions/assertStreamShareResponse.md)
- [parseStreamShareResponse](functions/parseStreamShareResponse.md)
- [isStreamShareResponse](functions/isStreamShareResponse.md)
# Class: StreamShareProtocolError
Error thrown when a service manifest or response violates the public protocol.
## Extends
- `Error`
## Constructors
### Constructor
> **new StreamShareProtocolError**(`message`): `StreamShareProtocolError`
#### Parameters
##### message
`string`
#### Returns
`StreamShareProtocolError`
#### Overrides
`Error.constructor`
## Properties
### cause?
> `optional` **cause?**: `unknown`
#### Inherited from
`Error.cause`
***
### name
> **name**: `string`
#### Inherited from
`Error.name`
***
### message
> **message**: `string`
#### Inherited from
`Error.message`
***
### stack?
> `optional` **stack?**: `string`
#### Inherited from
`Error.stack`
# Create your first service
## Install the protocol package
```bash
npm install @streamshare/service
```
## Define the service manifest
```ts
import {defineService} from '@streamshare/service';
export const service = defineService({
protocolVersion: 1,
name: 'My service',
logoPath: '/assets/logo.svg',
api: [
{
id: 'search',
kind: 'search',
label: 'Search',
method: 'GET',
pathname: '/search',
params: [
{id: 'q', role: 'query', label: 'Query', type: 'text'},
{id: 'page', role: 'page', label: 'Page', type: 'text', hidden: true},
],
},
],
});
```
`defineService()` preserves literal type inference and returns plain JSON that can be served by any HTTP application.
## Return results
```ts
import type {StreamShareResponse} from '@streamshare/service';
const response: StreamShareResponse = {
items: [
{
type: 'movie',
title: 'Example',
tmdb: {id: 123, type: 'movie'},
links: [{href: 'https://media.example/movie.m3u8', quality: '1080p'}],
},
],
pagination: {
currentPage: 1,
itemsPerPage: 50,
totalPages: 1,
totalItems: 1,
},
};
```
Validate data received across a network boundary with the runtime parsers exported by the package. Compile-time types alone cannot validate external JSON.
Continue with the detailed [manifest guide](./guides/manifest.md), [authentication guide](./guides/authentication.md), [result and playback guide](./guides/results-and-playback.md), and [demonstration service](./example-service.md).
# Demonstration service
The demonstration service exercises the public service contract. It demonstrates:
- serving a service manifest;
- implementing search and browse endpoints;
- returning typed media results and pagination;
- validating configuration and protocol payloads;
- running integration tests against the service contract.
It also demonstrates server-side TMDB enrichment, movie and series discovery, season and episode navigation, relative artwork and media URLs, source qualities, byte-range playback, facets, pagination, and every supported service authentication method. The TMDB read access token remains on the server and is configured only through `TMDB_ACCESS_TOKEN`.
Set `DEMO_AUTH_METHOD` to `basic`, `apikey`, or `deviceCode` to protect the catalog endpoints while keeping the manifest public. Device-code mode provides a local authorization page, rotating refresh tokens, and short-lived bearer access suitable for end-to-end testing.
The demonstration service intentionally keeps device-code sessions in memory. Restarting it invalidates previously issued refresh tokens. On the next protected request, StreamShare marks the connection as requiring authorization; use **Reconnect** to obtain a new code and QR code. This makes restart recovery and explicit authentication reset testable, but production services should normally persist revocable sessions across routine restarts.
When testing a protected mode over a private-network HTTP URL, review and enable the explicit HTTP exception in the service form, then select **Connect**. Device-code mode displays the authorization code, link, and QR code after that confirmation. HTTPS remains required by default and for every non-private address.
The example is not the protocol itself. A compatible service can use any implementation and hosting environment as long as its HTTP behavior matches the published contract.
Use the examples in [Create your first service](./getting-started.md) and the [development guide](./guides/development-and-testing.md) to reproduce each behavior independently.
# Development and contract testing
The protocol is independent of the technology used to implement a service. A service only needs to expose its manifest and JSON endpoints over HTTP.
## Validate both boundaries
Use the runtime parsers in tests and immediately before returning generated payloads:
```ts
import {
parseStreamShareResponse,
parseStreamShareService,
StreamShareProtocolError,
} from '@streamshare/service';
const manifest = parseStreamShareService(untrustedManifestValue);
const response = parseStreamShareResponse(untrustedResponseValue);
```
The parsers return the original value with its TypeScript type narrowed. They throw `StreamShareProtocolError` when the value violates protocol v1. Compile-time types do not replace these checks for database, upstream API, or network data.
At minimum, contract tests should cover:
- manifest parsing and unique endpoint semantics;
- each item discriminator returned by the service;
- search roles, exact TMDB lookup, filters, and sort behavior;
- pagination at empty, first, and last pages;
- every playback URL format, including relative URLs and byte-range media responses;
- upstream authentication failures, rate limits, malformed data, and timeouts.
## HTTP behavior and errors
Use `GET` query parameters or a JSON object for `POST`, matching the endpoint manifest. Return JSON with an appropriate HTTP status. A non-2xx response is treated as a failed service request; its response body may be shown as a diagnostic message but is not a stable application API. Keep errors concise and never include credentials, tokens, stack traces, local paths, or upstream secrets.
Recommended status codes include `400` for invalid request values, `401` or `403` for authentication failures, `404` for an unknown media record, `429` for rate limiting, and `502` or `503` for unavailable upstream providers.
## Local device testing
Run the service on an address reachable from the test device. On a virtual device, `localhost` refers to that device rather than to the development computer. Use the host address exposed by the emulator; a commonly used host-loopback alias is `10.0.2.2`, so a service listening on port `4046` can be configured as:
```text
http://10.0.2.2:4046/
```
A physical device must use the development computer's LAN address instead. Allow only the local development origin and port you need, and do not expose the development server to the public internet.
When the local service is protected, StreamShare keeps HTTPS as the default even on a private network. For a loopback or private-network HTTP address, the service form presents a separate consent control. Enable it only for a service and network you trust. The approval is stored only on the current device for that service origin; a service manifest cannot enable the exception itself. HTTP addresses outside a private network remain blocked for authenticated requests.
Then verify this sequence in StreamShare:
1. Add the service root URL and confirm that its public manifest is discovered.
2. For a protected local HTTP service, review and explicitly enable the private-network exception.
3. When requested, enter the detected credential type or select **Connect** to display the link, code, and QR authorization.
4. Confirm that the service name and logo load from the manifest.
5. Search by title and, when available, by exact TMDB identity.
6. Browse series, seasons, and episodes.
7. Open the source selector and verify quality, codec, and language badges.
8. Start the bundled or test media and verify seeking or byte-range playback.
9. In device-code mode, restart a deliberately stateless test service and confirm that the old session becomes **Connection required**.
10. Select **Reconnect**, approve the new code, and verify that protected browsing works again; then repeat after **Reset authentication**.
## Security checklist
- Keep upstream tokens server-side; never return them in the manifest or endpoint responses.
- Use HTTPS by default. Reserve the explicit private-network HTTP exception for local development on a trusted network.
- Never place passwords, bearer tokens, or API keys in query parameters.
- Apply timeouts, size limits, input validation, and rate limiting at the service boundary.
- Treat playback URLs as sensitive when they are signed or user-specific.
- Do not log authorization headers or complete signed URLs.
The [demonstration service](../example-service.md) is a tested reference implementation of this workflow.
# Function: assertStreamShareResponse()
> **assertStreamShareResponse**(`value`): `asserts value is StreamShareResponse`
Validates an endpoint response and narrows its type.
## Parameters
### value
`unknown`
## Returns
`asserts value is StreamShareResponse`
## Throws
[StreamShareProtocolError](../classes/StreamShareProtocolError.md) when the response is invalid.
# Function: assertStreamShareService()
> **assertStreamShareService**(`value`): `asserts value is StreamShareService`
Validates a service manifest and narrows its type.
## Parameters
### value
`unknown`
## Returns
`asserts value is StreamShareService`
## Throws
[StreamShareProtocolError](../classes/StreamShareProtocolError.md) when the manifest is invalid.
# Function: defineService()
> **defineService**\<`T`\>(`service`): `T`
Defines a service manifest while preserving literal inference.
The returned value is plain JSON and can be returned by the service root endpoint.
## Type Parameters
### T
`T` *extends* [`StreamShareService`](../interfaces/StreamShareService.md)
## Parameters
### service
`T`
## Returns
`T`
# Function: isStreamShareResponse()
> **isStreamShareResponse**(`value`): `value is StreamShareResponse`
Tests whether an unknown value is a valid endpoint response without throwing.
## Parameters
### value
`unknown`
## Returns
`value is StreamShareResponse`
# Function: isStreamShareService()
> **isStreamShareService**(`value`): `value is StreamShareService`
Tests whether an unknown value is a valid service manifest without throwing.
## Parameters
### value
`unknown`
## Returns
`value is StreamShareService`
# Function: parseServiceDeviceAuthorization()
> **parseServiceDeviceAuthorization**(`value`): [`ServiceDeviceAuthorization`](../interfaces/ServiceDeviceAuthorization.md)
Validates a device-code challenge returned by a service.
## Parameters
### value
`unknown`
## Returns
[`ServiceDeviceAuthorization`](../interfaces/ServiceDeviceAuthorization.md)
# Function: parseServiceDeviceTokenResponse()
> **parseServiceDeviceTokenResponse**(`value`): [`ServiceDeviceTokenResponse`](../type-aliases/ServiceDeviceTokenResponse.md)
Validates a response returned while polling the token endpoint.
## Parameters
### value
`unknown`
## Returns
[`ServiceDeviceTokenResponse`](../type-aliases/ServiceDeviceTokenResponse.md)
# Function: parseServiceTokenGrant()
> **parseServiceTokenGrant**(`value`): [`ServiceTokenGrant`](../interfaces/ServiceTokenGrant.md)
Validates a successful token grant returned by a service.
## Parameters
### value
`unknown`
## Returns
[`ServiceTokenGrant`](../interfaces/ServiceTokenGrant.md)
# Function: parseStreamShareResponse()
> **parseStreamShareResponse**(`value`): [`StreamShareResponse`](../interfaces/StreamShareResponse.md)
Parses an unknown value as a validated endpoint response.
## Parameters
### value
`unknown`
## Returns
[`StreamShareResponse`](../interfaces/StreamShareResponse.md)
The original value narrowed to [StreamShareResponse](../interfaces/StreamShareResponse.md).
## Throws
[StreamShareProtocolError](../classes/StreamShareProtocolError.md) when validation fails.
# Function: parseStreamShareService()
> **parseStreamShareService**(`value`): [`StreamShareService`](../interfaces/StreamShareService.md)
Parses an unknown value as a validated service manifest.
## Parameters
### value
`unknown`
## Returns
[`StreamShareService`](../interfaces/StreamShareService.md)
The original value narrowed to [StreamShareService](../interfaces/StreamShareService.md).
## Throws
[StreamShareProtocolError](../classes/StreamShareProtocolError.md) when validation fails.
# Interface: APIEndpoint
An HTTP endpoint exposed by a StreamShare-compatible service.
## Properties
### id
> **id**: `string`
Stable endpoint identifier, unique within the service manifest.
***
### label
> **label**: `string`
Human-readable name displayed by clients.
***
### method
> **method**: `"GET"` \| `"POST"`
HTTP method used to call the endpoint.
***
### pathname
> **pathname**: `string`
Service-root-relative URL path.
***
### params
> **params**: [`Param`](../type-aliases/Param.md)[]
Parameters accepted by the endpoint.
***
### hidden?
> `optional` **hidden?**: `boolean`
Prevents clients from presenting the endpoint as a direct navigation entry.
***
### kind
> **kind**: [`ServiceEndpointKind`](../type-aliases/ServiceEndpointKind.md)
Semantic purpose used by clients to discover the endpoint.
# Interface: ApiKeyServiceAuthentication
A static user-provided key sent as a Bearer token.
## Properties
### method
> **method**: `"apikey"`
# Interface: BasicServiceAuthentication
HTTP Basic authentication using credentials entered by the user.
## Properties
### method
> **method**: `"basic"`
# Interface: BrowseEndpoint
Navigation target that requests another endpoint with optional fixed values.
## Properties
### id
> **id**: `string`
Identifier of an endpoint declared in the service manifest.
***
### paramsValues?
> `optional` **paramsValues?**: `Record`\<`string`, `string`\>
Parameter values applied when following the navigation target.
# Interface: DeviceCodeServiceAuthentication
Device-code authentication suitable for televisions and other devices where
signing in from a second device is more convenient.
## Properties
### method
> **method**: `"deviceCode"`
***
### deviceAuthorizationPath
> **deviceAuthorizationPath**: `string`
Relative path that creates a short-lived authorization challenge.
***
### tokenPath
> **tokenPath**: `string`
Relative path used both to poll authorization and refresh tokens.
# Interface: ParamBase
Fields shared by every service endpoint parameter.
## Extended by
- [`ParamSelect`](ParamSelect.md)
- [`ParamText`](ParamText.md)
## Properties
### id
> **id**: `string`
Stable parameter identifier within the endpoint.
***
### label
> **label**: `string`
Human-readable form label.
***
### description?
> `optional` **description?**: `string`
Optional usage guidance displayed by clients.
***
### hidden?
> `optional` **hidden?**: `boolean`
Hides the input from the user while allowing clients to provide a value.
***
### required?
> `optional` **required?**: `boolean`
Whether callers must provide a value.
***
### role?
> `optional` **role?**: [`ServiceParameterRole`](../type-aliases/ServiceParameterRole.md)
Lets the application map a parameter without relying on its ID.
***
### value?
> `optional` **value?**: `string`
Default or preselected value serialized as a string.
# Interface: ParamSelect
A parameter whose value is chosen from a finite list.
## Extends
- [`ParamBase`](ParamBase.md)
## Properties
### id
> **id**: `string`
Stable parameter identifier within the endpoint.
#### Inherited from
[`ParamBase`](ParamBase.md).[`id`](ParamBase.md#id)
***
### label
> **label**: `string`
Human-readable form label.
#### Inherited from
[`ParamBase`](ParamBase.md).[`label`](ParamBase.md#label)
***
### description?
> `optional` **description?**: `string`
Optional usage guidance displayed by clients.
#### Inherited from
[`ParamBase`](ParamBase.md).[`description`](ParamBase.md#description)
***
### hidden?
> `optional` **hidden?**: `boolean`
Hides the input from the user while allowing clients to provide a value.
#### Inherited from
[`ParamBase`](ParamBase.md).[`hidden`](ParamBase.md#hidden)
***
### required?
> `optional` **required?**: `boolean`
Whether callers must provide a value.
#### Inherited from
[`ParamBase`](ParamBase.md).[`required`](ParamBase.md#required)
***
### role?
> `optional` **role?**: [`ServiceParameterRole`](../type-aliases/ServiceParameterRole.md)
Lets the application map a parameter without relying on its ID.
#### Inherited from
[`ParamBase`](ParamBase.md).[`role`](ParamBase.md#role)
***
### value?
> `optional` **value?**: `string`
Default or preselected value serialized as a string.
#### Inherited from
[`ParamBase`](ParamBase.md).[`value`](ParamBase.md#value)
***
### type
> **type**: `"select"`
Discriminator for a selection parameter.
***
### multiple?
> `optional` **multiple?**: `boolean`
Whether more than one option can be selected.
***
### options
> **options**: `object`[]
Values and labels offered by the client.
#### label
> **label**: `string`
Human-readable option name.
#### value
> **value**: `string`
Value sent to the service.
# Interface: ParamText
A free-form text parameter.
## Extends
- [`ParamBase`](ParamBase.md)
## Properties
### id
> **id**: `string`
Stable parameter identifier within the endpoint.
#### Inherited from
[`ParamBase`](ParamBase.md).[`id`](ParamBase.md#id)
***
### label
> **label**: `string`
Human-readable form label.
#### Inherited from
[`ParamBase`](ParamBase.md).[`label`](ParamBase.md#label)
***
### description?
> `optional` **description?**: `string`
Optional usage guidance displayed by clients.
#### Inherited from
[`ParamBase`](ParamBase.md).[`description`](ParamBase.md#description)
***
### hidden?
> `optional` **hidden?**: `boolean`
Hides the input from the user while allowing clients to provide a value.
#### Inherited from
[`ParamBase`](ParamBase.md).[`hidden`](ParamBase.md#hidden)
***
### required?
> `optional` **required?**: `boolean`
Whether callers must provide a value.
#### Inherited from
[`ParamBase`](ParamBase.md).[`required`](ParamBase.md#required)
***
### role?
> `optional` **role?**: [`ServiceParameterRole`](../type-aliases/ServiceParameterRole.md)
Lets the application map a parameter without relying on its ID.
#### Inherited from
[`ParamBase`](ParamBase.md).[`role`](ParamBase.md#role)
***
### value?
> `optional` **value?**: `string`
Default or preselected value serialized as a string.
#### Inherited from
[`ParamBase`](ParamBase.md).[`value`](ParamBase.md#value)
***
### type
> **type**: `"text"`
Discriminator for a text parameter.
# Interface: PlayLink
A directly playable media URL and its descriptive attributes.
## Properties
### href
> **href**: `string`
Absolute or service-resolvable playback URL.
***
### title?
> `optional` **title?**: `string`
Optional display name for the variant.
***
### codec?
> `optional` **codec?**: `string`
Codec label, for example `H.264`.
***
### quality?
> `optional` **quality?**: `string`
Quality label, for example `1080p`.
***
### languages?
> `optional` **languages?**: `string`[]
Audio languages available in this variant.
***
### subtitles?
> `optional` **subtitles?**: `string`[]
Subtitle languages available in this variant.
***
### size?
> `optional` **size?**: `number`
Approximate payload size in bytes.
***
### expiresAt?
> `optional` **expiresAt?**: `string`
ISO-8601 timestamp after which the URL is no longer valid.
***
### metadata?
> `optional` **metadata?**: [`PlayLinkMetadata`](PlayLinkMetadata.md)[]
Additional structured attributes displayed by clients.
# Interface: PlayLinkMetadata
Metadata displayed next to a playable link.
## Properties
### label?
> `optional` **label?**: `string`
Optional label shown before the value.
***
### value
> **value**: `string` \| `string`[]
One or more values presented to the user.
***
### hidden?
> `optional` **hidden?**: `boolean`
Keeps the metadata available to clients without displaying it by default.
***
### view
> **view**: `object`
Presentation hint for the metadata value.
#### type
> **type**: `"text"`
Supported metadata renderer.
#### color?
> `optional` **color?**: [`MetadataColor`](../type-aliases/MetadataColor.md)
Optional semantic or explicit color.
# Interface: ServiceDeviceAuthorization
Challenge returned when a client starts device-code authentication.
## Properties
### deviceCode
> **deviceCode**: `string`
Opaque value sent only to the token endpoint.
***
### userCode
> **userCode**: `string`
Short value displayed to the user.
***
### verificationUrl
> **verificationUrl**: `string`
Page where the user can enter the code.
***
### verificationUrlComplete?
> `optional` **verificationUrlComplete?**: `string`
Optional page containing the code, suitable for links and QR codes.
***
### expiresIn
> **expiresIn**: `number`
Challenge lifetime in seconds.
***
### interval
> **interval**: `number`
Minimum polling interval in seconds.
# Interface: ServiceDeviceTokenRequest
Request used to poll a device-code authorization.
## Properties
### grantType
> **grantType**: `"urn:ietf:params:oauth:grant-type:device_code"`
***
### deviceCode
> **deviceCode**: `string`
# Interface: ServiceRefreshTokenRequest
Request used to renew an authenticated service session.
## Properties
### grantType
> **grantType**: `"refresh_token"`
***
### refreshToken
> **refreshToken**: `string`
# Interface: ServiceTokenGrant
Bearer tokens returned after authorization or renewal.
## Properties
### accessToken
> **accessToken**: `string`
***
### refreshToken
> **refreshToken**: `string`
***
### expiresIn
> **expiresIn**: `number`
Access-token lifetime in seconds.
# Interface: StreamShareFacets
Optional aggregate values clients can expose as result filters.
## Properties
### genreTmdbIds?
> `optional` **genreTmdbIds?**: `number`[]
Distinct TMDB genre identifiers present in the full result set.
***
### years?
> `optional` **years?**: `number`[]
Distinct production years present in the full result set.
# Interface: StreamShareMediaResult
Shared fields for results that contain one or more playable variants.
## Extends
- [`StreamShareResultBase`](StreamShareResultBase.md)
## Extended by
- [`StreamShareResultMovie`](StreamShareResultMovie.md)
- [`StreamShareResultEpisode`](StreamShareResultEpisode.md)
- [`StreamShareResultUnclassified`](StreamShareResultUnclassified.md)
## Properties
### title?
> `optional` **title?**: `string`
Primary display title.
#### Inherited from
[`StreamShareResultBase`](StreamShareResultBase.md).[`title`](StreamShareResultBase.md#title)
***
### tagline?
> `optional` **tagline?**: `string`
Short secondary description.
#### Inherited from
[`StreamShareResultBase`](StreamShareResultBase.md).[`tagline`](StreamShareResultBase.md#tagline)
***
### poster?
> `optional` **poster?**: `string`
Portrait artwork URL.
#### Inherited from
[`StreamShareResultBase`](StreamShareResultBase.md).[`poster`](StreamShareResultBase.md#poster)
***
### backdrop?
> `optional` **backdrop?**: `string`
Landscape artwork URL.
#### Inherited from
[`StreamShareResultBase`](StreamShareResultBase.md).[`backdrop`](StreamShareResultBase.md#backdrop)
***
### rating?
> `optional` **rating?**: `number`
Normalized rating supplied by the service.
#### Inherited from
[`StreamShareResultBase`](StreamShareResultBase.md).[`rating`](StreamShareResultBase.md#rating)
***
### year?
> `optional` **year?**: `number`
Four-digit production or release year.
#### Inherited from
[`StreamShareResultBase`](StreamShareResultBase.md).[`year`](StreamShareResultBase.md#year)
***
### popularity?
> `optional` **popularity?**: `number`
Service-defined popularity score.
#### Inherited from
[`StreamShareResultBase`](StreamShareResultBase.md).[`popularity`](StreamShareResultBase.md#popularity)
***
### voteCount?
> `optional` **voteCount?**: `number`
Number of votes used to calculate the rating.
#### Inherited from
[`StreamShareResultBase`](StreamShareResultBase.md).[`voteCount`](StreamShareResultBase.md#votecount)
***
### productionDate?
> `optional` **productionDate?**: `string`
Precise release date formatted as YYYY-MM-DD.
#### Inherited from
[`StreamShareResultBase`](StreamShareResultBase.md).[`productionDate`](StreamShareResultBase.md#productiondate)
***
### filename?
> `optional` **filename?**: `string`
Source filename when it is useful to distinguish the media.
***
### lastPlayedAt?
> `optional` **lastPlayedAt?**: `string`
ISO-8601 timestamp of the most recent playback.
***
### links
> **links**: [`PlayLink`](PlayLink.md)[]
Available playback variants.
***
### tmdb?
> `optional` **tmdb?**: [`StreamShareTmdbReference`](StreamShareTmdbReference.md)
Optional TMDB reference used for metadata enrichment.
# Interface: StreamSharePagination
Pagination metadata returned with a result page.
## Properties
### currentPage
> **currentPage**: `number`
One-based index of the current page.
***
### itemsPerPage
> **itemsPerPage**: `number`
Maximum number of items requested or returned per page.
***
### totalItems?
> `optional` **totalItems?**: `number`
Total matching item count when known.
***
### totalPages?
> `optional` **totalPages?**: `number`
Total page count when known.
# Interface: StreamShareResponse
JSON response returned by a StreamShare-compatible service endpoint.
## Properties
### items
> **items**: [`StreamShareResult`](../type-aliases/StreamShareResult.md)[]
Media, navigation, or folder results for the current page.
***
### additionalParams?
> `optional` **additionalParams?**: [`Param`](../type-aliases/Param.md)[]
Dynamic parameters clients may offer for a subsequent request.
***
### pagination?
> `optional` **pagination?**: [`StreamSharePagination`](StreamSharePagination.md)
Pagination metadata when the endpoint is paginated.
***
### facets?
> `optional` **facets?**: [`StreamShareFacets`](StreamShareFacets.md)
Aggregate filter values for the complete logical result set.
# Interface: StreamShareResultBase
Common descriptive metadata shared by every service result.
## Extended by
- [`StreamShareMediaResult`](StreamShareMediaResult.md)
- [`StreamShareResultSeries`](StreamShareResultSeries.md)
- [`StreamShareResultSeason`](StreamShareResultSeason.md)
- [`StreamShareResultFolder`](StreamShareResultFolder.md)
## Properties
### title?
> `optional` **title?**: `string`
Primary display title.
***
### tagline?
> `optional` **tagline?**: `string`
Short secondary description.
***
### poster?
> `optional` **poster?**: `string`
Portrait artwork URL.
***
### backdrop?
> `optional` **backdrop?**: `string`
Landscape artwork URL.
***
### rating?
> `optional` **rating?**: `number`
Normalized rating supplied by the service.
***
### year?
> `optional` **year?**: `number`
Four-digit production or release year.
***
### popularity?
> `optional` **popularity?**: `number`
Service-defined popularity score.
***
### voteCount?
> `optional` **voteCount?**: `number`
Number of votes used to calculate the rating.
***
### productionDate?
> `optional` **productionDate?**: `string`
Precise release date formatted as YYYY-MM-DD.
# Interface: StreamShareResultEpisode
A playable television episode result.
## Extends
- [`StreamShareMediaResult`](StreamShareMediaResult.md)
## Properties
### title?
> `optional` **title?**: `string`
Primary display title.
#### Inherited from
[`StreamShareMediaResult`](StreamShareMediaResult.md).[`title`](StreamShareMediaResult.md#title)
***
### tagline?
> `optional` **tagline?**: `string`
Short secondary description.
#### Inherited from
[`StreamShareMediaResult`](StreamShareMediaResult.md).[`tagline`](StreamShareMediaResult.md#tagline)
***
### poster?
> `optional` **poster?**: `string`
Portrait artwork URL.
#### Inherited from
[`StreamShareMediaResult`](StreamShareMediaResult.md).[`poster`](StreamShareMediaResult.md#poster)
***
### backdrop?
> `optional` **backdrop?**: `string`
Landscape artwork URL.
#### Inherited from
[`StreamShareMediaResult`](StreamShareMediaResult.md).[`backdrop`](StreamShareMediaResult.md#backdrop)
***
### rating?
> `optional` **rating?**: `number`
Normalized rating supplied by the service.
#### Inherited from
[`StreamShareMediaResult`](StreamShareMediaResult.md).[`rating`](StreamShareMediaResult.md#rating)
***
### year?
> `optional` **year?**: `number`
Four-digit production or release year.
#### Inherited from
[`StreamShareMediaResult`](StreamShareMediaResult.md).[`year`](StreamShareMediaResult.md#year)
***
### popularity?
> `optional` **popularity?**: `number`
Service-defined popularity score.
#### Inherited from
[`StreamShareMediaResult`](StreamShareMediaResult.md).[`popularity`](StreamShareMediaResult.md#popularity)
***
### voteCount?
> `optional` **voteCount?**: `number`
Number of votes used to calculate the rating.
#### Inherited from
[`StreamShareMediaResult`](StreamShareMediaResult.md).[`voteCount`](StreamShareMediaResult.md#votecount)
***
### productionDate?
> `optional` **productionDate?**: `string`
Precise release date formatted as YYYY-MM-DD.
#### Inherited from
[`StreamShareMediaResult`](StreamShareMediaResult.md).[`productionDate`](StreamShareMediaResult.md#productiondate)
***
### filename?
> `optional` **filename?**: `string`
Source filename when it is useful to distinguish the media.
#### Inherited from
[`StreamShareMediaResult`](StreamShareMediaResult.md).[`filename`](StreamShareMediaResult.md#filename)
***
### lastPlayedAt?
> `optional` **lastPlayedAt?**: `string`
ISO-8601 timestamp of the most recent playback.
#### Inherited from
[`StreamShareMediaResult`](StreamShareMediaResult.md).[`lastPlayedAt`](StreamShareMediaResult.md#lastplayedat)
***
### links
> **links**: [`PlayLink`](PlayLink.md)[]
Available playback variants.
#### Inherited from
[`StreamShareMediaResult`](StreamShareMediaResult.md).[`links`](StreamShareMediaResult.md#links)
***
### tmdb?
> `optional` **tmdb?**: [`StreamShareTmdbReference`](StreamShareTmdbReference.md)
Optional TMDB reference used for metadata enrichment.
#### Inherited from
[`StreamShareMediaResult`](StreamShareMediaResult.md).[`tmdb`](StreamShareMediaResult.md#tmdb)
***
### type
> **type**: `"episode"`
Episode discriminator.
***
### season
> **season**: `number`
One-based season number.
***
### episode
> **episode**: `number`
One-based episode number within the season.
# Interface: StreamShareResultFolder
A generic folder that leads to another service request.
## Extends
- [`StreamShareResultBase`](StreamShareResultBase.md)
## Properties
### tagline?
> `optional` **tagline?**: `string`
Short secondary description.
#### Inherited from
[`StreamShareResultBase`](StreamShareResultBase.md).[`tagline`](StreamShareResultBase.md#tagline)
***
### poster?
> `optional` **poster?**: `string`
Portrait artwork URL.
#### Inherited from
[`StreamShareResultBase`](StreamShareResultBase.md).[`poster`](StreamShareResultBase.md#poster)
***
### backdrop?
> `optional` **backdrop?**: `string`
Landscape artwork URL.
#### Inherited from
[`StreamShareResultBase`](StreamShareResultBase.md).[`backdrop`](StreamShareResultBase.md#backdrop)
***
### rating?
> `optional` **rating?**: `number`
Normalized rating supplied by the service.
#### Inherited from
[`StreamShareResultBase`](StreamShareResultBase.md).[`rating`](StreamShareResultBase.md#rating)
***
### year?
> `optional` **year?**: `number`
Four-digit production or release year.
#### Inherited from
[`StreamShareResultBase`](StreamShareResultBase.md).[`year`](StreamShareResultBase.md#year)
***
### popularity?
> `optional` **popularity?**: `number`
Service-defined popularity score.
#### Inherited from
[`StreamShareResultBase`](StreamShareResultBase.md).[`popularity`](StreamShareResultBase.md#popularity)
***
### voteCount?
> `optional` **voteCount?**: `number`
Number of votes used to calculate the rating.
#### Inherited from
[`StreamShareResultBase`](StreamShareResultBase.md).[`voteCount`](StreamShareResultBase.md#votecount)
***
### productionDate?
> `optional` **productionDate?**: `string`
Precise release date formatted as YYYY-MM-DD.
#### Inherited from
[`StreamShareResultBase`](StreamShareResultBase.md).[`productionDate`](StreamShareResultBase.md#productiondate)
***
### type
> **type**: `"folder"`
Folder discriminator.
***
### title
> **title**: `string`
Required folder title.
#### Overrides
[`StreamShareResultBase`](StreamShareResultBase.md).[`title`](StreamShareResultBase.md#title)
***
### endpoint
> **endpoint**: [`BrowseEndpoint`](BrowseEndpoint.md)
Endpoint invoked when the folder is opened.
# Interface: StreamShareResultMovie
A playable movie result.
## Extends
- [`StreamShareMediaResult`](StreamShareMediaResult.md)
## Properties
### title?
> `optional` **title?**: `string`
Primary display title.
#### Inherited from
[`StreamShareMediaResult`](StreamShareMediaResult.md).[`title`](StreamShareMediaResult.md#title)
***
### tagline?
> `optional` **tagline?**: `string`
Short secondary description.
#### Inherited from
[`StreamShareMediaResult`](StreamShareMediaResult.md).[`tagline`](StreamShareMediaResult.md#tagline)
***
### poster?
> `optional` **poster?**: `string`
Portrait artwork URL.
#### Inherited from
[`StreamShareMediaResult`](StreamShareMediaResult.md).[`poster`](StreamShareMediaResult.md#poster)
***
### backdrop?
> `optional` **backdrop?**: `string`
Landscape artwork URL.
#### Inherited from
[`StreamShareMediaResult`](StreamShareMediaResult.md).[`backdrop`](StreamShareMediaResult.md#backdrop)
***
### rating?
> `optional` **rating?**: `number`
Normalized rating supplied by the service.
#### Inherited from
[`StreamShareMediaResult`](StreamShareMediaResult.md).[`rating`](StreamShareMediaResult.md#rating)
***
### year?
> `optional` **year?**: `number`
Four-digit production or release year.
#### Inherited from
[`StreamShareMediaResult`](StreamShareMediaResult.md).[`year`](StreamShareMediaResult.md#year)
***
### popularity?
> `optional` **popularity?**: `number`
Service-defined popularity score.
#### Inherited from
[`StreamShareMediaResult`](StreamShareMediaResult.md).[`popularity`](StreamShareMediaResult.md#popularity)
***
### voteCount?
> `optional` **voteCount?**: `number`
Number of votes used to calculate the rating.
#### Inherited from
[`StreamShareMediaResult`](StreamShareMediaResult.md).[`voteCount`](StreamShareMediaResult.md#votecount)
***
### productionDate?
> `optional` **productionDate?**: `string`
Precise release date formatted as YYYY-MM-DD.
#### Inherited from
[`StreamShareMediaResult`](StreamShareMediaResult.md).[`productionDate`](StreamShareMediaResult.md#productiondate)
***
### filename?
> `optional` **filename?**: `string`
Source filename when it is useful to distinguish the media.
#### Inherited from
[`StreamShareMediaResult`](StreamShareMediaResult.md).[`filename`](StreamShareMediaResult.md#filename)
***
### lastPlayedAt?
> `optional` **lastPlayedAt?**: `string`
ISO-8601 timestamp of the most recent playback.
#### Inherited from
[`StreamShareMediaResult`](StreamShareMediaResult.md).[`lastPlayedAt`](StreamShareMediaResult.md#lastplayedat)
***
### links
> **links**: [`PlayLink`](PlayLink.md)[]
Available playback variants.
#### Inherited from
[`StreamShareMediaResult`](StreamShareMediaResult.md).[`links`](StreamShareMediaResult.md#links)
***
### tmdb?
> `optional` **tmdb?**: [`StreamShareTmdbReference`](StreamShareTmdbReference.md)
Optional TMDB reference used for metadata enrichment.
#### Inherited from
[`StreamShareMediaResult`](StreamShareMediaResult.md).[`tmdb`](StreamShareMediaResult.md#tmdb)
***
### type
> **type**: `"movie"`
Movie discriminator.
# Interface: StreamShareResultSeason
A season navigation result, normally leading to its episodes.
## Extends
- [`StreamShareResultBase`](StreamShareResultBase.md)
## Properties
### tagline?
> `optional` **tagline?**: `string`
Short secondary description.
#### Inherited from
[`StreamShareResultBase`](StreamShareResultBase.md).[`tagline`](StreamShareResultBase.md#tagline)
***
### poster?
> `optional` **poster?**: `string`
Portrait artwork URL.
#### Inherited from
[`StreamShareResultBase`](StreamShareResultBase.md).[`poster`](StreamShareResultBase.md#poster)
***
### backdrop?
> `optional` **backdrop?**: `string`
Landscape artwork URL.
#### Inherited from
[`StreamShareResultBase`](StreamShareResultBase.md).[`backdrop`](StreamShareResultBase.md#backdrop)
***
### rating?
> `optional` **rating?**: `number`
Normalized rating supplied by the service.
#### Inherited from
[`StreamShareResultBase`](StreamShareResultBase.md).[`rating`](StreamShareResultBase.md#rating)
***
### year?
> `optional` **year?**: `number`
Four-digit production or release year.
#### Inherited from
[`StreamShareResultBase`](StreamShareResultBase.md).[`year`](StreamShareResultBase.md#year)
***
### popularity?
> `optional` **popularity?**: `number`
Service-defined popularity score.
#### Inherited from
[`StreamShareResultBase`](StreamShareResultBase.md).[`popularity`](StreamShareResultBase.md#popularity)
***
### voteCount?
> `optional` **voteCount?**: `number`
Number of votes used to calculate the rating.
#### Inherited from
[`StreamShareResultBase`](StreamShareResultBase.md).[`voteCount`](StreamShareResultBase.md#votecount)
***
### productionDate?
> `optional` **productionDate?**: `string`
Precise release date formatted as YYYY-MM-DD.
#### Inherited from
[`StreamShareResultBase`](StreamShareResultBase.md).[`productionDate`](StreamShareResultBase.md#productiondate)
***
### type
> **type**: `"season"`
Season discriminator.
***
### title
> **title**: `string`
Required season title.
#### Overrides
[`StreamShareResultBase`](StreamShareResultBase.md).[`title`](StreamShareResultBase.md#title)
***
### seasonNumber
> **seasonNumber**: `number`
One-based season number.
***
### tmdb
> **tmdb**: [`StreamShareTmdbReference`](StreamShareTmdbReference.md) & `object`
TMDB reference to the parent series.
#### Type Declaration
##### type
> **type**: `"tvShow"`
***
### endpoint?
> `optional` **endpoint?**: [`BrowseEndpoint`](BrowseEndpoint.md)
Optional endpoint used to list the season contents.
# Interface: StreamShareResultSeries
A series navigation result, normally leading to its seasons.
## Extends
- [`StreamShareResultBase`](StreamShareResultBase.md)
## Properties
### tagline?
> `optional` **tagline?**: `string`
Short secondary description.
#### Inherited from
[`StreamShareResultBase`](StreamShareResultBase.md).[`tagline`](StreamShareResultBase.md#tagline)
***
### poster?
> `optional` **poster?**: `string`
Portrait artwork URL.
#### Inherited from
[`StreamShareResultBase`](StreamShareResultBase.md).[`poster`](StreamShareResultBase.md#poster)
***
### backdrop?
> `optional` **backdrop?**: `string`
Landscape artwork URL.
#### Inherited from
[`StreamShareResultBase`](StreamShareResultBase.md).[`backdrop`](StreamShareResultBase.md#backdrop)
***
### rating?
> `optional` **rating?**: `number`
Normalized rating supplied by the service.
#### Inherited from
[`StreamShareResultBase`](StreamShareResultBase.md).[`rating`](StreamShareResultBase.md#rating)
***
### year?
> `optional` **year?**: `number`
Four-digit production or release year.
#### Inherited from
[`StreamShareResultBase`](StreamShareResultBase.md).[`year`](StreamShareResultBase.md#year)
***
### popularity?
> `optional` **popularity?**: `number`
Service-defined popularity score.
#### Inherited from
[`StreamShareResultBase`](StreamShareResultBase.md).[`popularity`](StreamShareResultBase.md#popularity)
***
### voteCount?
> `optional` **voteCount?**: `number`
Number of votes used to calculate the rating.
#### Inherited from
[`StreamShareResultBase`](StreamShareResultBase.md).[`voteCount`](StreamShareResultBase.md#votecount)
***
### productionDate?
> `optional` **productionDate?**: `string`
Precise release date formatted as YYYY-MM-DD.
#### Inherited from
[`StreamShareResultBase`](StreamShareResultBase.md).[`productionDate`](StreamShareResultBase.md#productiondate)
***
### type
> **type**: `"series"`
Series discriminator.
***
### title
> **title**: `string`
Required series title.
#### Overrides
[`StreamShareResultBase`](StreamShareResultBase.md).[`title`](StreamShareResultBase.md#title)
***
### tmdb
> **tmdb**: [`StreamShareTmdbReference`](StreamShareTmdbReference.md) & `object`
TMDB series reference.
#### Type Declaration
##### type
> **type**: `"tvShow"`
***
### endpoint?
> `optional` **endpoint?**: [`BrowseEndpoint`](BrowseEndpoint.md)
Optional endpoint used to list the series contents.
# Interface: StreamShareResultUnclassified
A playable result whose media category is not known.
## Extends
- [`StreamShareMediaResult`](StreamShareMediaResult.md)
## Properties
### title?
> `optional` **title?**: `string`
Primary display title.
#### Inherited from
[`StreamShareMediaResult`](StreamShareMediaResult.md).[`title`](StreamShareMediaResult.md#title)
***
### tagline?
> `optional` **tagline?**: `string`
Short secondary description.
#### Inherited from
[`StreamShareMediaResult`](StreamShareMediaResult.md).[`tagline`](StreamShareMediaResult.md#tagline)
***
### poster?
> `optional` **poster?**: `string`
Portrait artwork URL.
#### Inherited from
[`StreamShareMediaResult`](StreamShareMediaResult.md).[`poster`](StreamShareMediaResult.md#poster)
***
### backdrop?
> `optional` **backdrop?**: `string`
Landscape artwork URL.
#### Inherited from
[`StreamShareMediaResult`](StreamShareMediaResult.md).[`backdrop`](StreamShareMediaResult.md#backdrop)
***
### rating?
> `optional` **rating?**: `number`
Normalized rating supplied by the service.
#### Inherited from
[`StreamShareMediaResult`](StreamShareMediaResult.md).[`rating`](StreamShareMediaResult.md#rating)
***
### year?
> `optional` **year?**: `number`
Four-digit production or release year.
#### Inherited from
[`StreamShareMediaResult`](StreamShareMediaResult.md).[`year`](StreamShareMediaResult.md#year)
***
### popularity?
> `optional` **popularity?**: `number`
Service-defined popularity score.
#### Inherited from
[`StreamShareMediaResult`](StreamShareMediaResult.md).[`popularity`](StreamShareMediaResult.md#popularity)
***
### voteCount?
> `optional` **voteCount?**: `number`
Number of votes used to calculate the rating.
#### Inherited from
[`StreamShareMediaResult`](StreamShareMediaResult.md).[`voteCount`](StreamShareMediaResult.md#votecount)
***
### productionDate?
> `optional` **productionDate?**: `string`
Precise release date formatted as YYYY-MM-DD.
#### Inherited from
[`StreamShareMediaResult`](StreamShareMediaResult.md).[`productionDate`](StreamShareMediaResult.md#productiondate)
***
### filename?
> `optional` **filename?**: `string`
Source filename when it is useful to distinguish the media.
#### Inherited from
[`StreamShareMediaResult`](StreamShareMediaResult.md).[`filename`](StreamShareMediaResult.md#filename)
***
### lastPlayedAt?
> `optional` **lastPlayedAt?**: `string`
ISO-8601 timestamp of the most recent playback.
#### Inherited from
[`StreamShareMediaResult`](StreamShareMediaResult.md).[`lastPlayedAt`](StreamShareMediaResult.md#lastplayedat)
***
### links
> **links**: [`PlayLink`](PlayLink.md)[]
Available playback variants.
#### Inherited from
[`StreamShareMediaResult`](StreamShareMediaResult.md).[`links`](StreamShareMediaResult.md#links)
***
### tmdb?
> `optional` **tmdb?**: [`StreamShareTmdbReference`](StreamShareTmdbReference.md)
Optional TMDB reference used for metadata enrichment.
#### Inherited from
[`StreamShareMediaResult`](StreamShareMediaResult.md).[`tmdb`](StreamShareMediaResult.md#tmdb)
***
### type
> **type**: `"unclassified"`
Unclassified-media discriminator.
# Interface: StreamShareService
Public manifest that describes a StreamShare-compatible HTTP service.
## Properties
### protocolVersion
> **protocolVersion**: `1`
Protocol version implemented by the service.
***
### name
> **name**: `string`
Human-readable service name.
***
### description?
> `optional` **description?**: `string`
Short explanation of the service and its content.
***
### logoPath?
> `optional` **logoPath?**: `string`
Absolute, service-root-relative, or data URL for the service logo.
***
### api
> **api**: [`APIEndpoint`](APIEndpoint.md)[]
Endpoints available to StreamShare clients.
***
### auth?
> `optional` **auth?**: [`ServiceAuthentication`](../type-aliases/ServiceAuthentication.md)
Authentication method required by protected API endpoints.
# Interface: StreamShareTmdbReference
Reference used by clients to enrich service results with TMDB metadata.
## Properties
### id
> **id**: `number`
TMDB movie or series identifier.
***
### type
> **type**: `"movie"` \| `"tvShow"`
Media namespace of the referenced TMDB identifier.
***
### episodeId?
> `optional` **episodeId?**: `number`
TMDB episode ID when `id` identifies the parent series.
***
### numberOfSeasons?
> `optional` **numberOfSeasons?**: `number`
Number of published or known seasons for a series.
***
### inProduction?
> `optional` **inProduction?**: `boolean`
Whether the referenced series is still in production.
# Results and playback variants
Every endpoint returns a `StreamShareResponse`. Its `items` array uses the `type` discriminator to distinguish playable media from navigation containers.
| Item type | Playable | Required identity |
| --- | --- | --- |
| `movie` | yes | At least one `links` entry. |
| `episode` | yes | `season`, `episode`, and at least one `links` entry. |
| `unclassified` | yes | At least one `links` entry. |
| `series` | no | `title` and a TV-show TMDB reference. |
| `season` | no | `title`, `seasonNumber`, and a TV-show TMDB reference. |
| `folder` | no | `title` and an endpoint navigation target. |
## Common presentation metadata
Every result may provide `title`, `tagline`, `poster`, `backdrop`, `rating`, `year`, `popularity`, `voteCount`, and `productionDate`. Artwork URLs may be absolute or relative to the configured service root. Use HTTPS for remotely hosted artwork.
The runtime validator applies these constraints:
- `rating` is between `0` and `10`;
- `year` is a four-digit integer;
- `voteCount` is a non-negative integer;
- playable items contain at least one valid link;
- season and episode numbers are one-based positive integers.
Provide `productionDate` in `YYYY-MM-DD` form. Protocol v1 describes that format, although the current runtime validator only verifies that the value is a string.
Prefer stable identity over copied presentation data. Service-provided metadata remains valuable for unmatched or provider-specific records, but stable external identifiers improve deduplication and presentation consistency.
## Use TMDB as the stable metadata reference
When a matching TMDB record exists, return its ID instead of copying all artwork and descriptive metadata. StreamShare can enrich the result consistently with the rest of the application.
```ts
const movie = {
type: 'movie' as const,
title: 'Example movie',
tmdb: {id: 603, type: 'movie' as const},
links: [{href: '/assets/demo.mp4'}],
};
```
For an episode, `tmdb.id` identifies the parent series and `tmdb.episodeId` identifies the episode. A series or season uses `type: 'tvShow'`. Service-provided `poster`, `backdrop`, `tagline`, `rating`, `year`, and `productionDate` remain useful when TMDB has no match or before enrichment completes.
## Describe each playback variant
Return one `PlayLink` for every variant the user may select. Do not hide quality decisions inside an opaque title.
```ts
const links = [
{
href: '/media/example-1080p.mp4',
title: 'French 1080p',
quality: '1080p',
codec: 'H.264',
languages: ['fr'],
subtitles: ['fr', 'en'],
size: 2_400_000_000,
},
{
href: 'https://cdn.example/media/example-2160p.m3u8',
title: 'Original 4K',
quality: '2160p',
codec: 'H.265',
languages: ['en'],
metadata: [
{label: 'HDR', value: 'Dolby Vision', view: {type: 'text', color: 'tertiary'}},
],
},
];
```
Relative media URLs are resolved against the configured service root. Custom schemes may be passed to a compatible player. `expiresAt` can describe an expiring signed URL and `size` is expressed in bytes.
Service request credentials are not automatically reusable by arbitrary playback hosts. Protected media should use a self-contained, short-lived signed URL. See [Service authentication](./authentication.md#playback-access-is-separate).
The source selector currently derives its primary badges from `quality`, `codec`, and `languages`. A parseable `filename` can provide the same information, but structured fields are preferred because they are explicit and language-independent. Extra `metadata` is preserved by the protocol for richer clients; do not rely on it as the only place for quality or codec.
## Navigation
`series`, `season`, and `folder` results can point to a manifest endpoint and prefill its parameters:
```ts
const season = {
type: 'season' as const,
title: 'Season 1',
seasonNumber: 1,
tmdb: {id: 1399, type: 'tvShow' as const},
endpoint: {
id: 'catalog-lookup',
paramsValues: {mediaType: 'episode', tmdbId: '1399', season: '1'},
},
};
```
Keys in `paramsValues` are endpoint parameter IDs, not semantic roles. Values are always strings.
## Pagination and facets
Use one-based pagination. `currentPage` and `itemsPerPage` are required when `pagination` is present; `totalItems` and `totalPages` are optional when the service cannot calculate totals. Return unique integer TMDB genre IDs and production years in `facets`.
Filtering and sorting happen before pagination. If a request combines a keyword with a genre or year, page 1 must contain the first matching items from the complete filtered result set—not the subset that happened to match inside an upstream page. When an upstream provider cannot calculate the final count cheaply, omit `totalItems` and only advertise another page while one is known to exist.
```ts
const response = {
items,
pagination: {currentPage: 1, itemsPerPage: 50, totalItems: 84, totalPages: 2},
facets: {genreTmdbIds: [12, 28], years: [2026, 2025]},
};
```
Validate the final object with `parseStreamShareResponse()` or `assertStreamShareResponse()` before it leaves the service.
## Dynamic follow-up parameters
`additionalParams` can describe extra parameters discovered from a response. Protocol v1 validates this field and requires unique parameter IDs, but current applications do not present it as a general interactive follow-up form. Do not depend on it for essential navigation or playback.
For interoperable flows, declare stable inputs in the manifest and use item `endpoint.paramsValues` to carry service-owned navigation state. Treat `additionalParams` as reserved for compatible clients that explicitly advertise support.
# Service authentication
Authentication is optional. Omit `auth` for a public service. A protected service declares one authentication method in its public manifest without placing credentials or tokens in that manifest.
| Manifest method | User experience | Request authorization |
| --- | --- | --- |
| `basic` | Enter a username and password. | `Authorization: Basic …` |
| `apikey` | Enter a service API key. | `Authorization: Bearer ` |
| `deviceCode` | Open a link or scan a QR code, approve the connection, then let the application maintain the session. | `Authorization: Bearer ` |
The service root manifest always remains available without authentication. Only the declared API endpoints require credentials.
## Device-code sessions
Device-code authentication is useful on televisions and whenever signing in from another device is more convenient. Declare two service-relative paths:
```ts
const manifest = defineService({
protocolVersion: 1,
name: 'Protected catalog',
auth: {
method: 'deviceCode',
deviceAuthorizationPath: '/auth/device',
tokenPath: '/auth/token',
},
api: [
// Endpoints omitted for brevity.
],
});
```
Both paths must begin with `/` and remain on the configured service origin.
### 1. Create a challenge
The application sends `POST deviceAuthorizationPath` with an empty JSON object. Return:
```json
{
"deviceCode": "opaque-device-code",
"userCode": "ABCD-1234",
"verificationUrl": "https://service.example/activate",
"verificationUrlComplete": "https://service.example/activate?code=ABCD-1234",
"expiresIn": 600,
"interval": 5
}
```
`deviceCode` is an opaque secret used only for polling. `userCode` is displayed to the user. `verificationUrlComplete` is optional; when provided, it is preferred for the clickable link and QR code.
### 2. Poll for authorization
After waiting at least `interval` seconds, the application sends this JSON object to `POST tokenPath`:
```json
{
"grantType": "urn:ietf:params:oauth:grant-type:device_code",
"deviceCode": "opaque-device-code"
}
```
Return one of these states with HTTP `200`:
```json
{"status": "pending"}
```
```json
{"status": "denied"}
```
```json
{"status": "expired"}
```
The pending response may provide a larger `interval`. Once approved, return a token grant:
```json
{
"status": "authorized",
"accessToken": "short-lived-access-token",
"refreshToken": "rotating-refresh-token",
"expiresIn": 3600
}
```
The application stops polling on authorization, denial, expiry, cancellation, or when the challenge lifetime elapses.
### 3. Refresh the session
Before the access token expires, the application sends:
```json
{
"grantType": "refresh_token",
"refreshToken": "rotating-refresh-token"
}
```
Return a new `ServiceTokenGrant` containing a new access token, refresh token, and access-token lifetime. Refresh tokens are rotated: invalidate the consumed refresh token when issuing its replacement. Concurrent API requests share one renewal operation, and an API request rejected with `401` is retried only once after a forced renewal.
If the refresh token is rejected, or if the retried API request still returns `401`, StreamShare discards the invalid local token session. The service then appears as **Connection required** in Settings. **Reconnect** always starts a new device-code challenge; it does not treat an existing local token as proof of a valid service session. The user can also choose **Reset authentication** before reconnecting.
This behavior is important for revoked accounts, expired server-side sessions, and service restarts that do not preserve refresh-token state. A production service should normally persist revocable refresh-token records across routine restarts. An intentionally stateless demonstration server may invalidate them on restart, but the next protected request must return `401` so the application can request a fresh connection.
## Persistence and backups
Configured usernames, passwords, API keys, access tokens, and refresh tokens are retained with the service connection. When the user includes services in a backup, those access details are included so restoring the backup also restores the connection.
Approval for private-network HTTP is device-local and is not part of the service backup. After restoring a protected HTTP service, review the current network and approve the exception again before the application can send its restored credentials.
Treat the backup as credential-bearing data. Protect the destination account, avoid sharing the file, and use the available backup encryption when the copy may leave a trusted personal account. Revoking a credential at the service remains authoritative even when an older backup contains it.
## Security rules
- Use HTTPS for the manifest, authorization endpoints, and protected API endpoints.
- Never put credentials in the manifest, URL paths, query parameters, response diagnostics, analytics, or logs.
- Give each user a revocable, least-privilege credential.
- Return `401` when a credential is absent, invalid, expired, or revoked, and `403` when a valid credential lacks permission.
- Rate-limit authorization attempts and device-code polling.
- Generate device codes, access tokens, and refresh tokens with a cryptographically secure random source.
- Store only hashed refresh tokens server-side when the original value is not required.
- Do not redirect an authenticated API request to another origin.
For local testing, a user may explicitly approve unencrypted HTTP for a service on loopback or a private-network address. The approval belongs to that origin on the current device and cannot be requested or enabled by the service manifest. It never permits authenticated HTTP to a public-network address. Treat this as a development-only exception: credentials and tokens can be observed or altered by other systems on the local network.
## Playback access is separate
Service credentials authenticate JSON API requests. They are not automatically attached to artwork, subtitle, or playback URLs.
For protected media, return a short-lived signed URL or another self-contained playback URL that the target device can open directly. Use `expiresAt` on a playback link when the URL has a known expiry.
## Test the complete boundary
Verify that:
1. the root manifest remains public while protected endpoints reject missing credentials;
2. Basic and static API-key credentials appear only in the authorization header;
3. device-code polling respects the advertised interval and expiry;
4. refresh-token rotation invalidates the previous token;
5. simultaneous expired requests trigger only one renewal;
6. a `401` causes at most one renewal and retry;
7. a rejected refresh token clears the old session and a new connection succeeds;
8. explicit authentication reset removes the local session;
9. backup and restore preserve the selected service access;
10. playback and artwork use their own safe access mechanism.
The [demonstration service](../example-service.md) provides executable examples of every supported authentication method.
# Service compatibility
Service compatibility is governed by the protocol version in the service manifest, not by the server framework or deployment platform.
The `@streamshare/service` package ships `compatibility.json` so build tools and documentation can consume the same machine-readable matrix:
| Component | Minimum | Latest tested |
| --- | --- | --- |
| Service protocol | 1 | 1 |
| `@streamshare/service` | 1.0.1 | 1.0.1 |
| StreamShare application | 3.13.0 | 3.13.1 |
Services should return explicit protocol versions and validate their outgoing payloads during tests.
No service field is currently deprecated: the first production version has not been released yet, so pre-release consumers must migrate directly to the current contract rather than rely on compatibility aliases.
Protocol v1 therefore requires `protocolVersion`, endpoint `kind`, and semantic parameter `role` values for every behavior the application supplies automatically. Pagination uses `totalPages`; no pre-release alias is accepted.
## Current feature readiness
| Capability | Status |
| --- | --- |
| Manifest discovery and runtime validation | Tested |
| Search, exact TMDB lookup, filtering, and sorting | Tested when the corresponding parameter roles are declared |
| Browse endpoints and folder, series, or season navigation | Tested |
| Recent-content endpoint | Tested |
| Playback variants and relative URLs | Tested |
| Basic authentication | Header transport, persistence, backup, and restore tested |
| API-key authentication | Bearer-header transport, persistence, backup, and restore tested |
| Device-code authentication | Challenge, polling, QR/link presentation, token renewal, rotation, retry, persistence, backup, and restore tested |
| Response `additionalParams` | Validated by the protocol but not currently presented as an interactive follow-up form |
Do not make a production service depend on a capability marked pre-release or not production-ready.
# Service manifest
The URL configured in StreamShare is the service root. An unauthenticated `GET` request to that URL must return a JSON manifest accepted by `parseStreamShareService()`. Keep this discovery document public even when its API endpoints are protected.
```ts
import {defineService} from '@streamshare/service';
export const manifest = defineService({
protocolVersion: 1,
name: 'My catalog',
description: 'Movies available from my service.',
logoPath: '/assets/logo.svg',
api: [
{
id: 'catalog-lookup',
kind: 'search',
label: 'Search',
method: 'GET',
pathname: '/v1/catalog/lookup',
params: [
{id: 'term', role: 'query', label: 'Title', type: 'text'},
{id: 'cursor-page', role: 'page', label: 'Page', type: 'text', hidden: true},
],
},
],
});
```
The `id`, `pathname`, and parameter IDs are service-owned identifiers. StreamShare discovers their meaning from `kind` and `role`; it does not infer semantics from names.
## Manifest properties
| Property | Required | Meaning |
| --- | --- | --- |
| `protocolVersion` | yes | Must be `1`. Unsupported or missing versions are rejected. |
| `name` | yes | Human-readable service name. |
| `description` | no | Concise explanation of the content or provider. |
| `logoPath` | no | Absolute URL, path resolved relative to the configured service root, or `data:` URL. Secure clients can reject assets loaded over plain HTTP, so use an embedded asset or HTTPS outside credential-free local testing. |
| `api` | yes | Non-empty list of endpoints with unique IDs. |
| `auth` | no | Credential scheme requested when a user configures the service. |
`auth.method` accepts `basic`, `apikey`, or `deviceCode`. Basic and API-key modes select the matching credential form. Device-code mode also declares `deviceAuthorizationPath` and `tokenPath`, then presents the service-provided link, code, and QR code. The manifest never contains a username, password, key, or token. Read the [authentication guide](./authentication.md) for the complete request lifecycle.
## Endpoint properties
Every endpoint declares `id`, `kind`, `label`, `method`, `pathname`, and `params`. `pathname` starts with `/`, is relative to the service root, and contains no query string or fragment. Use parameters for request values.
The optional `hidden` flag prevents an endpoint from appearing as a top-level browsing entry; it does not disable calls made through search or navigation results.
| `kind` | Purpose | Cardinality |
| --- | --- | --- |
| `search` | Search and exact TMDB lookup. | Zero or one. |
| `browse` | User-visible catalog or navigation entry. | Zero or more. |
| `recent` | Recently added or updated media. | Zero or one. |
| `details` | Service-defined detail lookup. | Zero or more. |
Non-hidden endpoints can appear as top-level navigation entries. Use `hidden: true` for endpoints intended only as navigation targets or application-supplied lookups. A `details` endpoint has no special response shape in protocol v1; it returns the same `StreamShareResponse` as every other endpoint.
### Global search versus browsing
The optional `search` endpoint is the service-wide discovery contract. When it is present, StreamShare can include the service in the unified Movies, Series, and Other sections and in global text search. The endpoint must apply every advertised semantic role to the complete matching result set before pagination. In particular, a genre, year, media type, or sort must not be applied only to the current page.
Do not advertise a role the endpoint cannot honor consistently. A service with no `search` endpoint remains browseable through its declared catalog entries, but it is not included in global search.
`browse`, `recent`, and `details` endpoints are service-owned views. Their labels, ordering, accepted parameters, and navigation targets define their behavior. StreamShare does not reinterpret a **Top rated**, folder, or similar browsing entry as a global search endpoint, and does not inject undeclared filters into it.
## Parameters and roles
A parameter is either free-form `text` or a `select` with unique `{label, value}` options. `required`, `hidden`, `description`, and a default `value` are optional. Select parameters may set `multiple`.
Each semantic role may occur at most once in an endpoint:
| Role | Value sent by StreamShare |
| --- | --- |
| `query` | User-entered search text. |
| `mediaType` | `movie`, `series`, `season`, `episode`, or another option advertised by the service. |
| `tmdbId` | Exact TMDB movie or parent-series ID. |
| `genre` | Comma-separated TMDB genre IDs. |
| `year` | Four-digit production year. |
| `season`, `episode` | One-based episode coordinates. |
| `seriesTitle` | Parent-series title when available. |
| `sort` | A supported sort option such as `date`, `title`, `rating`, or `popularity`. |
| `minimumVotes` | Minimum vote count used with rating sorts. |
| `page`, `limit` | One-based page and requested item count. |
Unknown parameter IDs remain valid for service-specific forms, but the application only supplies automatic values through declared roles.
Roles are endpoint-local declarations but search support is service-wide: the single `search` endpoint describes the combinations available to unified discovery. Keep the contract simple by omitting unsupported roles rather than returning partially filtered pages.
For `GET`, values are serialized as query parameters. For `POST`, values are sent as a JSON object. Parameter values are strings at the HTTP boundary. A multi-select value must use the representation defined by the service; comma-separated values are recommended when the role already uses that convention.
See the generated [`StreamShareService`](../api/interfaces/StreamShareService.md) and [`APIEndpoint`](../api/interfaces/APIEndpoint.md) references for the exact TypeScript contract.
# Service protocol
The service manifest describes the available HTTP endpoints, their semantic roles, accepted parameters, and presentation metadata. Search or browse endpoints return a `StreamShareResponse` containing media items, playback links, pagination, and optional facets.
## Request lifecycle
1. The user configures a service root URL.
2. StreamShare requests the public service manifest without credentials and validates it.
3. When the manifest declares authentication, StreamShare requests credentials or starts the declared device-code authorization flow.
4. The manifest endpoint kinds and parameter roles determine which search, browsing, filtering, and recent-content operations are available.
5. StreamShare renews an expiring token session when necessary, then calls the selected endpoint with the declared method, parameter IDs, and configured authorization.
6. The response is validated, relative assets are resolved against the service root, and TMDB references may enrich the presentation.
7. When playback is requested, the service-provided variants are presented to the user and the selected media URL is opened.
The service remains the authority for its catalog, access policy, availability, and playback URLs. StreamShare remains the authority for navigation, presentation, source selection, and optional metadata enrichment.
## Endpoint roles
| Kind | Intended use |
| --- | --- |
| `search` | Text search, exact media lookup, filters, and sorting. A manifest may expose at most one. |
| `browse` | A user-visible catalog, collection, or navigation entry. Several are allowed. |
| `recent` | Recently added or updated content. A manifest may expose at most one. |
| `details` | A service-defined lookup reached through a navigation target or shown as a dedicated entry. |
Endpoint IDs and parameter IDs belong to the service. Semantic `kind` and `role` values let compatible clients understand them without relying on naming conventions.
## Requirements
- Serve a valid manifest matching a supported protocol version.
- Return JSON that passes the public runtime validators.
- Use stable identifiers and URLs.
- Return every useful playback variant instead of selecting one quality on behalf of the client.
- Treat request data and media URLs according to the service's own privacy and security policy.
- Preserve backward compatibility within a supported protocol major version.
- Return signed or otherwise self-contained playback URLs when media access cannot use the service request credentials.
## Deliberate boundaries
The protocol does not prescribe a programming language, hosting provider, database, indexing strategy, or upstream metadata source. It also does not proxy media through StreamShare or provide a generic remote-code mechanism. Operators retain control of their service while clients depend only on the documented HTTP behavior.
The [API reference](./api/index.md) is authoritative for exact fields and types. This page defines the higher-level behavioral expectations.
Continue with the guides for the [service manifest](./guides/manifest.md), [authentication](./guides/authentication.md), [results and playback variants](./guides/results-and-playback.md), and [development and contract testing](./guides/development-and-testing.md).
# StreamShare services
A StreamShare service connects a remotely managed media catalog to StreamShare through a small HTTP contract. It publishes a manifest, accepts search or navigation requests, and returns media records and playback choices.
## What a service can provide
- title search and exact TMDB lookup;
- browsable catalogs, folders, series, seasons, and episodes;
- recently added or updated media;
- filters, sorting, facets, and paginated result sets;
- several playback variants with explicit quality, codec, language, subtitle, and size information;
- service-owned metadata and TMDB references for consistent enrichment;
- protected catalogs through the authentication modes defined by the protocol.
Typical uses include a personal media gateway, a shared household catalog, a hosted provider, an index backed by cloud storage, or a catalog assembled from several upstream systems.
## Why choose a service
A service is useful when data, credentials, indexing, or business rules must remain under the operator's control. Its behavior can be updated centrally without reinstalling an addon, expensive processing can stay server-side, and the same catalog can serve several compatible clients.
That flexibility comes with operational responsibilities: the service must remain reachable, validate every request and response, protect credentials, control latency and rate limits, and return playback URLs that the target device can access.
## Service or addon?
Choose according to where the work and trust boundary belong:
| Need | Service | Addon |
| --- | --- | --- |
| Centrally managed catalog or account | Recommended | Possible only through remote APIs |
| Server-held upstream secrets | Recommended | Not recommended |
| Offline or device-local behavior | Not suitable | Recommended |
| Local files and host-granted capabilities | Not available | Recommended |
| Immediate server-side updates for every user | Recommended | Requires an addon update |
The two models can complement each other, but they use separate public contracts:
- a service runs on infrastructure controlled by its operator;
- an addon runs in a restricted environment on the user's device;
- a service communicates only through the published HTTP protocol and does not receive addon capabilities.
Start with [your first service](./getting-started.md), understand the [request lifecycle](./protocol.md), and review [service authentication](./guides/authentication.md) before exposing protected content. The generated [service API reference](./api/index.md) contains the exact package signatures.
# Type Alias: MetadataColor
> **MetadataColor** = \[`string`, `string`\] \| `"danger"` \| `"dark"` \| `"light"` \| `"medium"` \| `"primary"` \| `"secondary"` \| `"success"` \| `"tertiary"` \| `"warning"`
Color accepted by clients when rendering a metadata badge.
# Type Alias: Param
> **Param** = [`ParamText`](../interfaces/ParamText.md) \| [`ParamSelect`](../interfaces/ParamSelect.md)
Endpoint parameter supported by the StreamShare service protocol.
# Type Alias: ServiceAuthentication
> **ServiceAuthentication** = [`BasicServiceAuthentication`](../interfaces/BasicServiceAuthentication.md) \| [`ApiKeyServiceAuthentication`](../interfaces/ApiKeyServiceAuthentication.md) \| [`DeviceCodeServiceAuthentication`](../interfaces/DeviceCodeServiceAuthentication.md)
Authentication supported by a StreamShare service.
# Type Alias: ServiceDeviceTokenResponse
> **ServiceDeviceTokenResponse** = \{ `status`: `"pending"`; `interval?`: `number`; \} \| \{ `status`: `"denied"` \| `"expired"`; \} \| `object` & [`ServiceTokenGrant`](../interfaces/ServiceTokenGrant.md)
Result returned while polling the token endpoint.
# Type Alias: ServiceEndpointKind
> **ServiceEndpointKind** = `"search"` \| `"browse"` \| `"recent"` \| `"details"`
Semantic operation performed by a service endpoint.
# Type Alias: ServiceParameterRole
> **ServiceParameterRole** = `"query"` \| `"mediaType"` \| `"tmdbId"` \| `"genre"` \| `"year"` \| `"season"` \| `"episode"` \| `"seriesTitle"` \| `"sort"` \| `"minimumVotes"` \| `"page"` \| `"limit"`
Semantic roles understood by the StreamShare application.
# Type Alias: StreamShareItemType
> **StreamShareItemType** = [`StreamShareResult`](StreamShareResult.md)\[`"type"`\]
Discriminator values supported by [StreamShareResult](StreamShareResult.md).
# Type Alias: StreamShareResult
> **StreamShareResult** = [`StreamShareResultMovie`](../interfaces/StreamShareResultMovie.md) \| [`StreamShareResultSeries`](../interfaces/StreamShareResultSeries.md) \| [`StreamShareResultSeason`](../interfaces/StreamShareResultSeason.md) \| [`StreamShareResultEpisode`](../interfaces/StreamShareResultEpisode.md) \| [`StreamShareResultUnclassified`](../interfaces/StreamShareResultUnclassified.md) \| [`StreamShareResultFolder`](../interfaces/StreamShareResultFolder.md)
Any result item accepted by a StreamShare-compatible client.