fix(player): honor declared stream type for HLS without .m3u8 URLs#1326
Merged
tapframe merged 2 commits intoJun 12, 2026
Merged
Conversation
Plugin scrapers (and some non-standard addons) declare the manifest
format of their links via a "type" field ("hls", "dash") on the
stream object. PluginRuntime already parses it into
PluginRuntimeResult.type, but both toStreamItem conversions dropped
it, and PlayerEngine.android.kt built MediaItems with no MIME type,
leaving ExoPlayer to infer the format from the URL extension. For
HLS links whose URL hides the .m3u8 behind tokens or rewriters
(https://cdn.example.com/playlist?token=…), inference fails and
playback errors or stalls.
This change:
- Adds a streamType field to StreamItem, populated from stream.type
in StreamParser, MetaDetailsParser embedded streams, and both
PluginRuntimeResult.toStreamItem conversions (StreamsRepository and
PlayerStreamsRepository).
- Plumbs the value through PlayerLaunch / PlayerScreen /
PlatformPlayerSurface and the in-player source-switch paths.
Switches to P2P sentinels and downloaded local files reset the
value to null so a stale "hls" can never mislabel the next source.
streamType participates in the player rebuild keys so a source
change always rebuilds with the right MIME.
- Persists streamType in the Reuse Last Link cache (defaulted
serializable field, old payloads stay decodable) so a typed HLS
stream that played once keeps working through link reuse.
- In playbackMediaItemFromUrl, the declared type takes precedence
over response-header and URL-path inference, mapping "hls"/"m3u8" →
APPLICATION_M3U8, "dash"/"mpd" → APPLICATION_MPD,
"ss"/"smoothstreaming" → APPLICATION_SS. Unrecognized values
(including Stremio's content-type semantics like "movie"/"series")
fall through to the existing inference chain unchanged.
The official Stremio stream spec defines no top-level type field, so
the Stremio-addon parsing is best-effort; the reliable emitters are
plugin scrapers, whose results previously lost the field entirely.
Scopes down to NuvioMedia#1244 only. NuvioMedia#1246 (User-Agent override) is already
fixed in the current PlayerPlaybackNetworking.createHttpDataSourceFactory,
which skips setUserAgent when caller-supplied headers declare one.
Fixes NuvioMedia#1244
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Adds an optional streamType metadata field to stream models and propagates it through playback launch/surface plumbing so Android can infer the correct Media3 MIME type (e.g., HLS/DASH) even when URLs/headers are ambiguous.
Changes:
- Add
streamTypetoStreamItem, cached link models, and player launch/args/runtime state. - Parse/persist
streamTypefrom addon payloads (type) and propagate it through repositories and UI/runtime switching. - Use
streamTypeon Android to infer Media3mimeType(HLS/DASH/SmoothStreaming) when buildingMediaItems.
Reviewed changes
Copilot reviewed 20 out of 20 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| composeApp/src/iosMain/kotlin/com/nuvio/app/features/player/PlayerEngine.ios.kt | Adds streamType to iOS player surface signature for parity with common expect. |
| composeApp/src/desktopMain/kotlin/com/nuvio/app/features/player/PlayerEngine.desktop.kt | Adds streamType to Desktop player surface signature for parity with common expect. |
| composeApp/src/commonTest/kotlin/com/nuvio/app/features/streams/StreamParserTest.kt | Adds tests ensuring addon-declared type is preserved as streamType. |
| composeApp/src/commonMain/kotlin/com/nuvio/app/features/streams/StreamsRepository.kt | Propagates plugin type into StreamItem.streamType. |
| composeApp/src/commonMain/kotlin/com/nuvio/app/features/streams/StreamParser.kt | Parses JSON type into StreamItem.streamType. |
| composeApp/src/commonMain/kotlin/com/nuvio/app/features/streams/StreamModels.kt | Adds streamType field to StreamItem. |
| composeApp/src/commonMain/kotlin/com/nuvio/app/features/streams/StreamLinkCacheRepository.kt | Adds streamType to cached link model and persistence. |
| composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerStreamsRepository.kt | Propagates plugin type into StreamItem.streamType in player module. |
| composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerScreenRuntimeUi.kt | Passes activeStreamType into PlatformPlayerSurface. |
| composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerScreenRuntimeState.kt | Stores streamType in args and runtime activeStreamType. |
| composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerScreenRuntimeSourceActions.kt | Updates source switching to set/clear activeStreamType and cache it for reuse. |
| composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerScreenRuntimeEffects.kt | Preserves streamType when refreshing credentialed sources. |
| composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerScreenArgs.kt | Adds streamType to player screen args. |
| composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerScreen.kt | Adds streamType parameter and wires it into args. |
| composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerModels.kt | Adds streamType to PlayerLaunch. |
| composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerEngine.kt | Adds streamType to expected PlatformPlayerSurface API. |
| composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/MetaDetailsParser.kt | Parses embedded stream type into StreamItem.streamType. |
| composeApp/src/commonMain/kotlin/com/nuvio/app/App.kt | Threads streamType through player launch paths and cache usage. |
| composeApp/src/androidMain/kotlin/com/nuvio/app/features/player/PlayerEngine.android.kt | Plumbs streamType into Android playback pipeline and media item creation. |
| composeApp/src/androidMain/kotlin/com/nuvio/app/features/player/PlaybackMediaItems.android.kt | Adds streamType-based MIME inference for Media3 MediaItem. |
streamType is now trimmed, lowercased, and blanked to null by a shared normalizeStreamType helper at all four ingestion sites, and the player derives a single normalized value for both its rebuild keys and MIME inference, so values like " HLS " cannot cause key churn or missed mappings. The helper lives in commonMain and is covered by commonTest together with a parser round-trip for a padded uppercase type. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Plugin scrapers (and some non-standard addons) declare the manifest format of their links via a
typefield ("hls","dash") on the stream object.PluginRuntimealready parses it intoPluginRuntimeResult.type, but bothtoStreamItemconversions dropped the field, andPlayerEngine.android.ktbuilt MediaItems with no MIME type — leaving ExoPlayer to infer the format from the URL extension. For HLS links whose URL hides the.m3u8behind tokens or rewriters (https://cdn.example.com/playlist?token=…), inference fails and playback errors or stalls.PR type
Why
Three pieces, all on the same data path:
StreamItemgains astreamTypefield, populated fromstream.typeinStreamParser,MetaDetailsParserembedded streams, and bothPluginRuntimeResult.toStreamItemconversions (StreamsRepositoryandPlayerStreamsRepository). The official Stremio stream spec defines no top-leveltypefield, so the Stremio-addon parsing is best-effort; the reliable emitters are plugin scrapers, whose results previously lost the field entirely.PlayerLaunch/PlayerScreen/PlatformPlayerSurfaceand the in-player source-switch paths. Switches to P2P sentinels and downloaded local files reset the value to null so a stale"hls"can never mislabel the next source, andstreamTypeparticipates in the player rebuild keys. It is also persisted in the Reuse Last Link cache (defaulted serializable field — old cached payloads stay decodable) so a typed HLS stream that played once keeps working through link reuse.playbackMediaItemFromUrl, the declared type takes precedence over response-header and URL-path inference, mapping"hls"/"m3u8"→APPLICATION_M3U8,"dash"/"mpd"→APPLICATION_MPD,"ss"/"smoothstreaming"→APPLICATION_SS. Unrecognized values — including Stremio's content-type semantics like"movie"/"series"— fall through to the existing inference chain unchanged, so an addon usingtypefor other purposes cannot force a wrong MIME.The
PlatformPlayerSurfaceexpect/actual signature gains one default-null parameter; the iOS and desktop actuals accept it without acting on it (AVPlayer and the desktop native player do their own format detection).Issue or approval
Fixes #1244
UI / behavior impact
Policy check
CONTRIBUTING.md.Scope boundaries
PlayerPlaybackNetworking.createHttpDataSourceFactory, which skipssetUserAgentwhen caller-supplied headers declare one.Testing
./gradlew :composeApp:compileFullDebugKotlinAndroidpasses clean../gradlew :composeApp:testFullDebugUnitTestpasses. The desktop target currently fails upstream on cmp-rewrite for unrelated pre-existing expect/actual mismatches (DebridSettingsStorage.desktop.kt,StreamBadgeSettingsStorage.desktop.kt— reproducible on a clean checkout); that compilation gets far enough to validate expect/actual signatures and reports no error for the changedPlatformPlayerSurface.New
StreamParserTestcases:stream.typeround-trips from JSON intoStreamItem.streamType, and stays null when the addon omits it.Manual (please verify during review — needs a device and a plugin/addon emitting
type: "hls"):.m3u8: playback starts (previously errored/stalled)..mp4/.m3u8URL with no declared type: behavior unchanged (existing inference).Screenshots / Video (UI changes only)
Not a UI change.
Breaking changes
None. The Reuse Last Link cache entry gains a defaulted field; existing cached payloads decode unchanged.
Linked issues
Fixes #1244