From c4d4f59db0869a7a7ebcb0ca12edcba248c17d30 Mon Sep 17 00:00:00 2001 From: tapframe <85391825+tapframe@users.noreply.github.com> Date: Wed, 8 Apr 2026 14:40:20 +0530 Subject: [PATCH] adding behaviour hint parity with bingegroup --- .../commonMain/kotlin/com/nuvio/app/App.kt | 3 + .../app/features/details/MetaDetailsParser.kt | 2 +- .../nuvio/app/features/player/PlayerScreen.kt | 2 + .../player/PlayerStreamsRepository.kt | 1 + .../streams/StreamLinkCacheRepository.kt | 3 + .../app/features/streams/StreamParser.kt | 2 +- .../app/features/streams/StreamsRepository.kt | 1 + .../app/features/streams/StreamParserTest.kt | 91 +++++++++++++++++++ 8 files changed, 103 insertions(+), 2 deletions(-) create mode 100644 composeApp/src/commonTest/kotlin/com/nuvio/app/features/streams/StreamParserTest.kt diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/App.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/App.kt index f523d8d20..fd77bd3d0 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/App.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/App.kt @@ -910,6 +910,7 @@ private fun MainAppContent( episodeThumbnail = route.episodeThumbnail, streamTitle = cached.streamName, streamSubtitle = null, + bingeGroup = cached.bingeGroup, pauseDescription = pauseDescription, providerName = cached.addonName, providerAddonId = cached.addonId, @@ -948,6 +949,7 @@ private fun MainAppContent( addonId = stream.addonId, filename = stream.behaviorHints.filename, videoSize = stream.behaviorHints.videoSize, + bingeGroup = stream.behaviorHints.bingeGroup, ) } val launchId = PlayerLaunchStore.put( @@ -1024,6 +1026,7 @@ private fun MainAppContent( addonId = stream.addonId, filename = stream.behaviorHints.filename, videoSize = stream.behaviorHints.videoSize, + bingeGroup = stream.behaviorHints.bingeGroup, ) } val launchId = PlayerLaunchStore.put( diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/MetaDetailsParser.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/MetaDetailsParser.kt index 0e115b049..6dbf3cfa5 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/MetaDetailsParser.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/MetaDetailsParser.kt @@ -285,7 +285,7 @@ internal object MetaDetailsParser { addonId = "embedded", behaviorHints = StreamBehaviorHints( bingeGroup = hintsObj?.string("bingeGroup"), - notWebReady = hintsObj?.boolean("notWebReady") ?: false, + notWebReady = (hintsObj?.boolean("notWebReady") ?: false) || proxyHeaders != null, videoSize = hintsObj?.long("videoSize"), filename = hintsObj?.string("filename"), proxyHeaders = proxyHeaders, diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerScreen.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerScreen.kt index 276ebd708..ef443a3cc 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerScreen.kt @@ -524,6 +524,7 @@ fun PlayerScreen( addonId = stream.addonId, filename = stream.behaviorHints.filename, videoSize = stream.behaviorHints.videoSize, + bingeGroup = stream.behaviorHints.bingeGroup, ) } activeSourceUrl = url @@ -578,6 +579,7 @@ fun PlayerScreen( addonId = stream.addonId, filename = stream.behaviorHints.filename, videoSize = stream.behaviorHints.videoSize, + bingeGroup = stream.behaviorHints.bingeGroup, ) } activeSourceUrl = url 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 8cc76938c..fc779c77d 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 @@ -319,6 +319,7 @@ private fun PluginRuntimeResult.toStreamItem(scraper: PluginScraper): StreamItem com.nuvio.app.features.streams.StreamBehaviorHints() } else { com.nuvio.app.features.streams.StreamBehaviorHints( + notWebReady = true, proxyHeaders = com.nuvio.app.features.streams.StreamProxyHeaders(request = requestHeaders), ) }, diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/streams/StreamLinkCacheRepository.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/streams/StreamLinkCacheRepository.kt index 5cad000ad..5490df9f8 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/streams/StreamLinkCacheRepository.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/streams/StreamLinkCacheRepository.kt @@ -12,6 +12,7 @@ data class CachedStreamLink( val cachedAtMs: Long, val filename: String? = null, val videoSize: Long? = null, + val bingeGroup: String? = null, ) internal expect fun epochMs(): Long @@ -30,6 +31,7 @@ object StreamLinkCacheRepository { addonId: String, filename: String? = null, videoSize: Long? = null, + bingeGroup: String? = null, ) { val entry = CachedStreamLink( url = url, @@ -39,6 +41,7 @@ object StreamLinkCacheRepository { cachedAtMs = epochMs(), filename = filename, videoSize = videoSize, + bingeGroup = bingeGroup, ) val payload = json.encodeToString(CachedStreamLink.serializer(), entry) StreamLinkCacheStorage.saveEntry(hashedKey(contentKey), payload) diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/streams/StreamParser.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/streams/StreamParser.kt index 10d87635e..1f6e4e922 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/streams/StreamParser.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/streams/StreamParser.kt @@ -45,7 +45,7 @@ object StreamParser { addonId = addonId, behaviorHints = StreamBehaviorHints( bingeGroup = hintsObj?.string("bingeGroup"), - notWebReady = hintsObj?.boolean("notWebReady") ?: false, + notWebReady = (hintsObj?.boolean("notWebReady") ?: false) || proxyHeaders != null, videoSize = hintsObj?.long("videoSize"), filename = hintsObj?.string("filename"), proxyHeaders = proxyHeaders, 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 246c4e4d5..278782832 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 @@ -521,6 +521,7 @@ private fun PluginRuntimeResult.toStreamItem( StreamBehaviorHints() } else { StreamBehaviorHints( + notWebReady = true, proxyHeaders = StreamProxyHeaders(request = requestHeaders), ) }, diff --git a/composeApp/src/commonTest/kotlin/com/nuvio/app/features/streams/StreamParserTest.kt b/composeApp/src/commonTest/kotlin/com/nuvio/app/features/streams/StreamParserTest.kt new file mode 100644 index 000000000..d1a554b72 --- /dev/null +++ b/composeApp/src/commonTest/kotlin/com/nuvio/app/features/streams/StreamParserTest.kt @@ -0,0 +1,91 @@ +package com.nuvio.app.features.streams + +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertNotNull +import kotlin.test.assertTrue + +class StreamParserTest { + + @Test + fun `parse keeps bingeGroup and explicit notWebReady`() { + val streams = StreamParser.parse( + payload = + """ + { + "streams": [ + { + "url": "https://example.com/video.mp4", + "name": "1080p", + "behaviorHints": { + "bingeGroup": "addon-1080p", + "notWebReady": true + } + } + ] + } + """.trimIndent(), + addonName = "Addon", + addonId = "addon.id", + ) + + val stream = streams.single() + assertEquals("addon-1080p", stream.behaviorHints.bingeGroup) + assertTrue(stream.behaviorHints.notWebReady) + } + + @Test + fun `parse forces notWebReady when proxyHeaders are provided`() { + val streams = StreamParser.parse( + payload = + """ + { + "streams": [ + { + "url": "https://example.com/video.m3u8", + "name": "Proxy stream", + "behaviorHints": { + "proxyHeaders": { + "request": { + "User-Agent": "Stremio" + } + } + } + } + ] + } + """.trimIndent(), + addonName = "Addon", + addonId = "addon.id", + ) + + val stream = streams.single() + assertTrue(stream.behaviorHints.notWebReady) + val requestHeaders = stream.behaviorHints.proxyHeaders?.request + assertNotNull(requestHeaders) + assertEquals("Stremio", requestHeaders["User-Agent"]) + } + + @Test + fun `parse keeps notWebReady false when no proxyHeaders and no hint`() { + val streams = StreamParser.parse( + payload = + """ + { + "streams": [ + { + "url": "https://example.com/video.mp4", + "name": "Direct" + } + ] + } + """.trimIndent(), + addonName = "Addon", + addonId = "addon.id", + ) + + val stream = streams.single() + assertFalse(stream.behaviorHints.notWebReady) + } +}