mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-08-17 21:05:43 +00:00
fix(player): normalize declared stream type at ingestion
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 commit is contained in:
parent
3d52716770
commit
fe747ffc3e
7 changed files with 47 additions and 7 deletions
|
|
@ -56,6 +56,7 @@ import androidx.media3.ui.PlayerView
|
|||
import androidx.media3.ui.SubtitleView
|
||||
import androidx.media3.ui.CaptionStyleCompat
|
||||
import com.nuvio.app.R
|
||||
import com.nuvio.app.features.streams.normalizeStreamType
|
||||
import io.github.peerless2012.ass.media.widget.AssSubtitleView
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
|
@ -108,6 +109,9 @@ actual fun PlatformPlayerSurface(
|
|||
val sanitizedSourceResponseHeaders = remember(sourceResponseHeaders) {
|
||||
sanitizePlaybackResponseHeaders(sourceResponseHeaders)
|
||||
}
|
||||
val normalizedStreamType = remember(streamType) {
|
||||
normalizeStreamType(streamType)
|
||||
}
|
||||
val useLibass = playerSettings.useLibass
|
||||
val libassRenderType = runCatching {
|
||||
LibassRenderType.valueOf(playerSettings.libassRenderType)
|
||||
|
|
@ -117,7 +121,7 @@ actual fun PlatformPlayerSurface(
|
|||
sourceAudioUrl.orEmpty(),
|
||||
sanitizedSourceHeaders,
|
||||
sanitizedSourceResponseHeaders,
|
||||
streamType.orEmpty(),
|
||||
normalizedStreamType.orEmpty(),
|
||||
useYoutubeChunkedPlayback,
|
||||
)
|
||||
var subtitleDelayMs by remember(playerSourceKey) { mutableStateOf(0) }
|
||||
|
|
@ -170,7 +174,7 @@ actual fun PlatformPlayerSurface(
|
|||
sourceAudioUrl,
|
||||
sanitizedSourceHeaders,
|
||||
sanitizedSourceResponseHeaders,
|
||||
streamType,
|
||||
normalizedStreamType,
|
||||
useYoutubeChunkedPlayback,
|
||||
effectiveDecoderPriority,
|
||||
) {
|
||||
|
|
@ -235,7 +239,7 @@ actual fun PlatformPlayerSurface(
|
|||
videoMediaItem = playbackMediaItemFromUrl(
|
||||
url = sourceUrl,
|
||||
responseHeaders = sanitizedSourceResponseHeaders,
|
||||
streamType = streamType,
|
||||
streamType = normalizedStreamType,
|
||||
),
|
||||
startPositionMs = fallbackStartPositionMs,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.nuvio.app.features.details
|
|||
import com.nuvio.app.features.streams.StreamBehaviorHints
|
||||
import com.nuvio.app.features.streams.StreamItem
|
||||
import com.nuvio.app.features.streams.StreamProxyHeaders
|
||||
import com.nuvio.app.features.streams.normalizeStreamType
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonArray
|
||||
|
|
@ -288,7 +289,7 @@ internal object MetaDetailsParser {
|
|||
externalUrl = externalUrl,
|
||||
addonName = addonName,
|
||||
addonId = "embedded",
|
||||
streamType = obj.string("type"),
|
||||
streamType = normalizeStreamType(obj.string("type")),
|
||||
behaviorHints = StreamBehaviorHints(
|
||||
bingeGroup = hintsObj?.string("bingeGroup"),
|
||||
notWebReady = (hintsObj?.boolean("notWebReady") ?: false) || proxyHeaders != null,
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import com.nuvio.app.features.streams.StreamBadgeSettingsRepository
|
|||
import com.nuvio.app.features.streams.StreamItem
|
||||
import com.nuvio.app.features.streams.StreamParser
|
||||
import com.nuvio.app.features.streams.StreamsUiState
|
||||
import com.nuvio.app.features.streams.normalizeStreamType
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
|
|
@ -503,7 +504,7 @@ private fun PluginRuntimeResult.toStreamItem(scraper: PluginScraper): StreamItem
|
|||
infoHash = infoHash,
|
||||
addonName = scraper.name,
|
||||
addonId = "plugin:${scraper.id}",
|
||||
streamType = type,
|
||||
streamType = normalizeStreamType(type),
|
||||
behaviorHints = if (requestHeaders.isEmpty()) {
|
||||
com.nuvio.app.features.streams.StreamBehaviorHints()
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -90,6 +90,9 @@ data class StreamBadge(
|
|||
val borderColor: String = "",
|
||||
)
|
||||
|
||||
fun normalizeStreamType(raw: String?): String? =
|
||||
raw?.trim()?.lowercase()?.takeIf { it.isNotBlank() }
|
||||
|
||||
private fun String?.isMagnetLink(): Boolean =
|
||||
this?.trimStart()?.startsWith("magnet:", ignoreCase = true) == true
|
||||
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ object StreamParser {
|
|||
addonName = addonName,
|
||||
addonId = addonId,
|
||||
addonLogo = addonLogo,
|
||||
streamType = obj.string("type"),
|
||||
streamType = normalizeStreamType(obj.string("type")),
|
||||
clientResolve = clientResolve,
|
||||
behaviorHints = StreamBehaviorHints(
|
||||
bingeGroup = hintsObj?.string("bingeGroup"),
|
||||
|
|
|
|||
|
|
@ -915,7 +915,7 @@ private fun PluginRuntimeResult.toStreamItem(
|
|||
sourceName = scraper.name,
|
||||
addonName = addonName,
|
||||
addonId = addonId,
|
||||
streamType = type,
|
||||
streamType = normalizeStreamType(type),
|
||||
behaviorHints = if (requestHeaders.isEmpty()) {
|
||||
StreamBehaviorHints()
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -194,6 +194,37 @@ class StreamParserTest {
|
|||
assertEquals("hls", streams.single().streamType)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `parse normalizes streamType casing and whitespace`() {
|
||||
val streams = StreamParser.parse(
|
||||
payload =
|
||||
"""
|
||||
{
|
||||
"streams": [
|
||||
{
|
||||
"url": "https://cdn.example.com/playlist?token=abc",
|
||||
"name": "1080p",
|
||||
"type": " HLS "
|
||||
}
|
||||
]
|
||||
}
|
||||
""".trimIndent(),
|
||||
addonName = "Addon",
|
||||
addonId = "addon.id",
|
||||
)
|
||||
|
||||
assertEquals("hls", streams.single().streamType)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `normalizeStreamType trims lowercases and blanks to null`() {
|
||||
assertEquals("hls", normalizeStreamType(" Hls "))
|
||||
assertEquals("dash", normalizeStreamType("DASH"))
|
||||
assertEquals(null, normalizeStreamType(" "))
|
||||
assertEquals(null, normalizeStreamType(""))
|
||||
assertEquals(null, normalizeStreamType(null))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `parse leaves streamType null when addon omits it`() {
|
||||
val streams = StreamParser.parse(
|
||||
|
|
|
|||
Loading…
Reference in a new issue