Skip to content

fix(player): honor declared stream type for HLS without .m3u8 URLs#1326

Merged
tapframe merged 2 commits into
NuvioMedia:cmp-rewritefrom
anikettuli:fix/android-player-playback-bundle
Jun 12, 2026
Merged

fix(player): honor declared stream type for HLS without .m3u8 URLs#1326
tapframe merged 2 commits into
NuvioMedia:cmp-rewritefrom
anikettuli:fix/android-player-playback-bundle

Conversation

@anikettuli

Copy link
Copy Markdown
Contributor

Summary

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 the field, 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.

PR type

  • Behavior bug/regression fix

Why

Three pieces, all on the same data path:

  • Capture the declaration. StreamItem gains a streamType field, populated from stream.type in StreamParser, MetaDetailsParser embedded streams, and both PluginRuntimeResult.toStreamItem conversions (StreamsRepository and PlayerStreamsRepository). 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.
  • Carry it to the player. The value is plumbed 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, and streamType participates 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.
  • Use it. 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, so an addon using type for other purposes cannot force a wrong MIME.

The PlatformPlayerSurface expect/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

  • No UI change
  • Behavior changed only to fix a documented bug/regression

Policy check

  • I have read and understood CONTRIBUTING.md.
  • This PR is small, focused, and limited to one problem.
  • This PR is not cosmetic-only.
  • Any UI change fixes a linked glitch/bug and includes visual proof, or this PR has no UI change.
  • Any behavior change fixes a linked bug/regression or has explicit approval, or this PR has no behavior change.
  • This PR does not bundle unrelated refactors, cleanups, formatting, or drive-by changes.
  • This PR does not add dependencies, architecture changes, migrations, or product-direction changes without explicit approval.
  • I listed the testing performed below.

Scope boundaries

  • [Bug]: Android player overrides stream-provided User-Agent headers #1246 (User-Agent override) is intentionally NOT bundled — it is already fixed in the current PlayerPlaybackNetworking.createHttpDataSourceFactory, which skips setUserAgent when caller-supplied headers declare one.
  • The audio track of a merged video+audio source keeps URL-based inference (the declared type describes the video manifest).
  • Downloads, cloud files, and P2P playback paths are untouched beyond resetting the type to null on switch — local files and the P2P engine do their own format detection.
  • iOS/desktop player engines receive but do not consume the new parameter; wiring AVPlayer-side hints is out of scope.

Testing

./gradlew :composeApp:compileFullDebugKotlinAndroid passes clean. ./gradlew :composeApp:testFullDebugUnitTest passes. 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 changed PlatformPlayerSurface.

New StreamParserTest cases: stream.type round-trips from JSON into StreamItem.streamType, and stays null when the addon omits it.

Manual (please verify during review — needs a device and a plugin/addon emitting type: "hls"):

  • Play a declared-HLS stream whose URL does not contain .m3u8: playback starts (previously errored/stalled).
  • Switch sources mid-playback from an HLS stream to a progressive one and back: each source plays with its own format.
  • With Reuse Last Link enabled, replay a declared-HLS stream from cache: still plays.
  • Play a normal .mp4/.m3u8 URL 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

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>
Copilot AI review requested due to automatic review settings June 11, 2026 23:45

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 streamType to StreamItem, cached link models, and player launch/args/runtime state.
  • Parse/persist streamType from addon payloads (type) and propagate it through repositories and UI/runtime switching.
  • Use streamType on Android to infer Media3 mimeType (HLS/DASH/SmoothStreaming) when building MediaItems.

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.

Comment thread composeApp/src/commonMain/kotlin/com/nuvio/app/features/streams/StreamParser.kt Outdated
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>
@tapframe tapframe merged commit e897ec2 into NuvioMedia:cmp-rewrite Jun 12, 2026
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.

[Bug]: Android player fails to start addon-declared HLS streams without .m3u8 URLs

3 participants