From 389add4b27270c83865d22b9f6ef4d10e4a618cf Mon Sep 17 00:00:00 2001 From: Jaidev1805 Date: Sun, 19 Apr 2026 21:29:19 +0530 Subject: [PATCH] fix: ensure series streams use parentId:season:episode format in addon requests --- .../player/PlayerStreamsRepository.kt | 24 +++++++++++++-- .../app/features/streams/StreamsRepository.kt | 30 +++++++++++++++++-- 2 files changed, 49 insertions(+), 5 deletions(-) diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerStreamsRepository.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerStreamsRepository.kt index 78f55bdb..aeff3d5e 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerStreamsRepository.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerStreamsRepository.kt @@ -151,6 +151,23 @@ object PlayerStreamsRepository { return } + // For series episodes the Stremio protocol requires the id in the request URL to + // be "parentId:season:episode" (e.g. tt1234567:1:2). Append if missing. + val effectiveAddonVideoId = if (type == "series" && season != null && episode != null) { + val suffix = ":$season:$episode" + if (videoId.endsWith(suffix)) videoId else "$videoId$suffix" + } else { + videoId + } + + // Strip the suffix when checking idPrefixes so addons with prefix "tt" still match + // when effectiveAddonVideoId is "tt1234567:1:2". + val baseVideoIdForPrefixCheck = if (season != null && episode != null) { + effectiveAddonVideoId.removeSuffix(":$season:$episode") + } else { + effectiveAddonVideoId + } + val installedAddons = AddonRepository.uiState.value.addons val pluginScrapers = if (AppFeaturePolicy.pluginsEnabled) { PluginRepository.initialize() @@ -174,7 +191,10 @@ object PlayerStreamsRepository { resource.name == "stream" && resource.types.contains(type) && (resource.idPrefixes.isEmpty() || - resource.idPrefixes.any { videoId.startsWith(it) }) + resource.idPrefixes.any { prefix -> + effectiveAddonVideoId.startsWith(prefix) || + baseVideoIdForPrefixCheck.startsWith(prefix) + }) } if (!supportsRequestedStream) return@mapNotNull null @@ -221,7 +241,7 @@ object PlayerStreamsRepository { manifestUrl = addon.manifest.transportUrl, resource = "stream", type = type, - id = videoId, + id = effectiveAddonVideoId, ) val displayName = addon.addonName diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/streams/StreamsRepository.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/streams/StreamsRepository.kt index d9516513..c5d82c31 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/streams/StreamsRepository.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/streams/StreamsRepository.kt @@ -93,6 +93,18 @@ object StreamsRepository { ) } + // For series episodes the Stremio protocol requires the id in the request URL to + // be "parentId:season:episode" (e.g. tt1234567:1:2). If the videoId coming from + // the addon meta doesn't already carry the suffix, append it here so that every + // addon gets a correctly-formed stream request. + val effectiveAddonVideoId = if (type == "series" && season != null && episode != null) { + val suffix = ":$season:$episode" + if (videoId.endsWith(suffix)) videoId else "$videoId$suffix" + } else { + videoId + } + log.d { "Effective addon videoId: $effectiveAddonVideoId (original: $videoId, season=$season, episode=$episode)" } + val embeddedStreams = MetaDetailsRepository.findEmbeddedStreams(videoId) if (embeddedStreams.isNotEmpty()) { log.d { "Using ${embeddedStreams.size} embedded streams for type=$type id=$videoId" } @@ -129,6 +141,15 @@ object StreamsRepository { return } + // When checking idPrefixes, also test the bare parent ID (without :season:episode) + // so that addons with idPrefixes=["tt"] still match when effectiveAddonVideoId is + // "tt1234567:1:2". + val baseVideoIdForPrefixCheck = if (season != null && episode != null) { + effectiveAddonVideoId.removeSuffix(":$season:$episode") + } else { + effectiveAddonVideoId + } + val streamAddons = installedAddons .mapNotNull { addon -> val manifest = addon.manifest ?: return@mapNotNull null @@ -136,7 +157,10 @@ object StreamsRepository { resource.name == "stream" && resource.types.contains(type) && (resource.idPrefixes.isEmpty() || - resource.idPrefixes.any { videoId.startsWith(it) }) + resource.idPrefixes.any { prefix -> + effectiveAddonVideoId.startsWith(prefix) || + baseVideoIdForPrefixCheck.startsWith(prefix) + }) } if (!supportsRequestedStream) return@mapNotNull null @@ -147,7 +171,7 @@ object StreamsRepository { ) } - log.d { "Found ${streamAddons.size} addons for stream type=$type id=$videoId" } + log.d { "Found ${streamAddons.size} addons for stream type=$type effectiveId=$effectiveAddonVideoId" } if (streamAddons.isEmpty() && pluginProviderGroups.isEmpty()) { _uiState.value = StreamsUiState( @@ -243,7 +267,7 @@ object StreamsRepository { manifestUrl = addon.manifest.transportUrl, resource = "stream", type = type, - id = videoId, + id = effectiveAddonVideoId, ) log.d { "Fetching streams from: $url" }