Skip to content

feat(stremio): add Stremio addon support - #119

Open
bilipp wants to merge 5 commits into
mainfrom
feat/stremio
Open

feat(stremio): add Stremio addon support#119
bilipp wants to merge 5 commits into
mainfrom
feat/stremio

Conversation

@bilipp

@bilipp bilipp commented Jul 6, 2026

Copy link
Copy Markdown
Owner

Summary

Adds Stremio addons as a first-class content source alongside Xtream, M3U and Stalker (#97). Users add any addon by its manifest.json URL (or stremio:// install link) through the existing Add Playlist flow; the addon's movie, series and TV catalogs surface through the same Home rails, browse views, search, favorites and enrichment as every other source, and streams play through the existing engine stack (KSPlayer → VLCKit → AVPlayer).

Implementation

Follows the Stalker-portal pattern rather than a new StremioAddon model: PlaylistSourceType.stremio on the existing Playlist (manifest URL in serverURL), which gets iCloud config sync, deletion, browse scoping and auto-sync for free with no CloudKit schema change.

  • StremioClient + DTOs (Services/Network/): manifest / catalog / meta / stream / subtitles endpoints with retry+backoff; lenient decoding for real-world addon drift (string-or-number imdbRating, title/name and episode/number aliases, mixed string/object resources), verified against the protocol docs and a live Cinemeta manifest.
  • ContentSyncManager+Stremio: maps browsable catalogs (those without required extras like search-only) onto Category/Movie/Series/LiveStream with the standard playlist-UUID id prefixes; ids hashed via M3UIdentity; pages catalogs via skip capped at 500 items each (Cinemeta's are endless); IMDb ids stored in imdbId so OMDb/TMDB enrichment and Trakt keep working. Series episodes load lazily on the detail screen from the full meta (new Series.stremioId optional field, lightweight migration).
    • Paging never trusts the protocol's 100-item page size or the manifest's skip declaration — AIOStreams serves 99-item pages with no extras declared, Cinemeta 45–50 behind a redirect. walkStremioCatalog probes skip regardless and stops when a page yields no new ids (one extra request on skip-ignoring addons).
    • Cinemeta companion catalogs: stream-capable addons get Cinemeta's Popular/Featured movie+series catalogs attached at sync (cinemeta:-prefixed category apiIds), mirroring the Stremio app where pre-installed Cinemeta fills Discover. Without this, an AIOStreams playlist browses only its ~99-title JustWatch service lists. Gated on supportsStreams(forType:idPrefix: "tt") so companion items are always playable; skipped when the playlist is Cinemeta itself; a companion failure never fails the sync.
    • Cinemeta meta fallback: stream-focused addons (AIOStreams, Torrentio) serve no meta resource, so episode fetches fall back to Cinemeta for IMDb ids — series from such addons otherwise show no episodes.
  • Playback: catalog rows store a lumestremio:// placeholder (mirroring lumestalker://); the new StremioStreamResolver resolves /stream/{type}/{id}.json at playback time — torrent (infoHash), YouTube and external-browser streams are filtered out. A small DeferredStreamLink front door now dispatches Stalker/Stremio resolution in the player. External-player handoff refuses placeholders (they'd reach Infuse/VLC unresolved).
  • Stream picker: addons return dozens of candidates per title (quality/size/provider encoded in name/description + behaviorHints), so VOD playback shows an in-player source picker (StremioStreamPickerView, platform-adaptive incl. tvOS focus). Auto-picks without asking when there is a single candidate, when the new "Choose Stremio Stream" toggle (Settings → Playback) is off, or when a candidate matches the session's last-picked bingeGroup — so binge auto-advance stays on the chosen release. Live channels keep first-playable resolution for fast zapping.
  • UI: fourth segment in the add-playlist picker (iOS/macOS/tvOS) with manifest-URL validation via a live manifest fetch (addon name auto-fills the playlist name); PlaylistDetailView handles the credential-less source. LoginView's per-source form sections moved to LoginView+Sections.swift to stay within lint size caps.
  • i18n: 11 new keys translated for all 9 locales.

Not in scope (noted for follow-ups): subtitle-addon wiring into the player UI (client endpoint implemented), search-extra catalogs (online search), per-genre Discover browsing, cross-playlist stream aggregation, behaviorHints.proxyHeaders, torrent streams, and remembering the picked stream across player sessions. Downloads are disabled for Stremio sources since stream URLs are resolved on demand.

Testing

  • Acceptance criteria met
  • Tests pass (xcodebuild test -scheme Lume -destination 'platform=iOS Simulator,name=iPhone 17 Pro' — full LumeTests suite; single failure is the pre-existing RecommendationEngine parallel-run flake, unrelated)
  • SwiftLint clean (vendored SwiftFormat + SwiftLint --strict on changed files)
  • Tests added — 51 cases across six suites covering manifest-URL normalization, resource-URL/extras encoding, placeholder round-trip, lenient DTO decoding (incl. stream behaviorHints), catalog-paging termination, stream-support scoping, and picker auto-pick rules

Platforms

  • iOS / iPadOS
  • tvOS
  • macOS
  • visionOS

Related

Closes #97

@bilipp
bilipp force-pushed the feat/stremio branch 2 times, most recently from cf2d3b9 to b753bc9 Compare July 31, 2026 08:47
bilipp added 5 commits August 7, 2026 22:05
Tested against a real AIOStreams manifest, three gaps kept such addons
from working:

- Catalog paging trusted the protocol's 100-item page size and the
  manifest's skip declaration; AIOStreams serves 99-item pages with no
  extras declared (and Cinemeta 45-50 behind a redirect), so sync took
  one page and stopped. Paging now probes skip regardless and ends when
  a page yields no new ids.
- Stream-focused addons serve no meta resource, so series never got
  episodes. Episode fetches now fall back to Cinemeta for IMDb ids,
  like the Stremio app.
- Addons return dozens of streams per title (quality/size/provider);
  Lume silently played the first. VOD playback now shows an in-player
  source picker, auto-picking for single candidates, a matching
  bingeGroup (binge continuation on the same release), or when the new
  "Choose Stremio Stream" toggle is off. Live channels keep
  first-playable for fast zapping.
- Stream-capable addons additionally get Cinemeta's Popular/Featured
  catalogs attached at sync, mirroring the app where pre-installed
  Cinemeta fills Discover - an AIOStreams playlist otherwise browses
  only its ~99-title service lists.

Also: external-player handoff refuses lumestremio:// placeholders,
stream behaviorHints (bingeGroup/filename/videoSize) decoded, audio
session helpers split out to respect the file-length cap, and an
os_log autoclosure self-capture build breaker fixed via a local copy
(swiftformat keeps stripping explicit self).

New strings localized in all 9 locales; 51 unit tests across six
Stremio suites pass.
Close the gaps that kept popular addon categories from working:

- proxyHeaders: streams carrying behaviorHints.proxyHeaders.request now
  flow those headers via PlayableMedia.httpHeaders into every engine
  (KSPlayer appendHeader, AVPlayer header-fields asset option, LumeEngine
  demuxer.httpHeaders, VLC best-effort UA/Referer) — unblocks
  MediaFusion's media proxy and header-guarded live-TV addons.
- Non-standard catalog types (Anime Kitsu's "anime"): such catalogs are
  prefetched once and their items routed to the movie/series/live
  buckets by each item's own declared type.
- Subtitle addons: Stremio VOD resolution fetches the subtitles resource
  from the owning addon, other subtitle-capable Stremio playlists and
  the OpenSubtitles v3 companion (pre-installed in the official app),
  and offers the tracks in the KSPlayer and VLC subtitle menus.
- Cross-playlist aggregation, mirroring the app's installed-addon set:
  stream resolution falls back to other Stremio playlists that declare
  matching stream support (makes catalog-only addons like Kitsu, TMDB
  and Trakt lists playable), and the series-meta fallback asks Cinemeta
  then other meta-capable playlists.

FullScreenPlayerView, ContentSyncManager+Stremio and
VLCPlayerCoordinator were split to stay within the file-length cap.
Merging main brought the OpenSubtitles integration onto this branch, and
both features had independently introduced an `ExternalSubtitle`: this
branch's in `PlayableMedia.swift` (a remote track a Stremio subtitle addon
offers, keyed by language) and OpenSubtitles' in `ExternalSubtitleLoading.swift`
(a file downloaded from the search sheet, carrying its own label). They live
in different files, so git merged both without a conflict — and left the
module with an invalid redeclaration that would not compile.

Collapse them into one value type next to `PlayableMedia`, holding the URL
the engine reads from, the menu label, and an optional language used only to
group same-language tracks. An `addonTrack(id:url:language:)` factory keeps
the localized-language-name labelling the addon path had as a computed
`displayName`, so addon menus read exactly as before.

With one type, LumeEngine can finally offer addon subtitles too: its sidecar
slot held a single track because only the OpenSubtitles pick ever reached it,
so Stremio tracks appeared on KSPlayer and VLC but silently nowhere else.
Widen it to a list — addon tracks are on offer from the first frame and parse
only when picked, and a mid-playback search result joins them.

Also settle the container-choice switch this branch had open: Stremio addons
resolve a stream on demand at playback time, so like Stalker there is nothing
for the viewer to pick.

Verified: iOS and tvOS builds, SwiftFormat and SwiftLint (vendored 0.61.1 /
0.63.2), and 520 tests across every affected suite. The full-suite run also
reports ~151 failures at 0.000s, all collateral from WatchProgressBufferTests
trapping on an out-of-range index and killing its shared test process — a
known pre-existing flake in files neither feature touches.
@bilipp

bilipp commented Aug 25, 2026

Copy link
Copy Markdown
Owner Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: Support for Stremio Addons

1 participant