Skip to content

Commit 93ade18

Browse files
committed
fix: run Desktop player inside Nuvio production theme
1 parent 950fe96 commit 93ade18

1 file changed

Lines changed: 67 additions & 36 deletions

File tree

scripts/augment_native_desktop_player.py

Lines changed: 67 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
IMPORTS = """import androidx.compose.foundation.layout.fillMaxSize
2121
import androidx.compose.ui.Modifier
2222
import androidx.compose.ui.awt.ComposePanel
23+
import com.nuvio.app.core.ui.NuvioTheme
2324
import com.nuvio.app.features.player.PlatformPlayerSurface
2425
import com.nuvio.app.features.player.PlayerControlsState
2526
import com.nuvio.app.features.player.PlayerPlaybackSnapshot
@@ -45,8 +46,32 @@
4546
val failureStage: String,
4647
val durationSeconds: Double?,
4748
val positionSeconds: Double?,
49+
val exceptionChain: String = "",
4850
)
4951
52+
private fun desktopThrowableChain(error: Throwable): Pair<Throwable, String> {
53+
var current = error
54+
val parts = mutableListOf<String>()
55+
repeat(8) {
56+
val name = current::class.qualifiedName.orEmpty().ifBlank { current.javaClass.name }
57+
val message = current.message.orEmpty()
58+
.replace(Regex("https?://\\S+", RegexOption.IGNORE_CASE), "<url>")
59+
.replace(Regex("(?i)(authorization|cookie|token|secret)\\s*[:=]\\s*\\S+"), "$1=<redacted>")
60+
.replace(Regex("\\s+"), " ")
61+
.take(180)
62+
parts += if (message.isBlank()) name else "$name:$message"
63+
val next = when (current) {
64+
is java.lang.reflect.InvocationTargetException -> current.targetException ?: current.cause
65+
else -> current.cause
66+
}
67+
if (next == null || next === current) {
68+
return current to parts.joinToString(" -> ").take(420)
69+
}
70+
current = next
71+
}
72+
return current to parts.joinToString(" -> ").take(420)
73+
}
74+
5075
private fun captureDesktopPhase(phase: String, fixtureSlug: String) {
5176
val safe = phase.replace(Regex("[^A-Za-z0-9_.-]+"), "-").trim('-').ifBlank { "phase" }
5277
val dir = File(workspace, "native-evidence/desktop/${System.getProperty("os.name").lowercase().replace(Regex("[^a-z0-9]+"), "-")}/$fixtureSlug")
@@ -83,37 +108,41 @@
83108
frame.defaultCloseOperation = JFrame.DISPOSE_ON_CLOSE
84109
frameRef.set(frame)
85110
panel.setContent {
86-
// Production entry point. Provider output enters Nuvio unchanged;
87-
// Nuvio's own production surface decides sanitation, settings,
88-
// native bridge and playback behavior exactly as the app does.
89-
PlatformPlayerSurface(
90-
sourceUrl = url,
91-
sourceHeaders = headers.orEmpty(),
92-
sourceResponseHeaders = emptyMap(),
93-
externalSubtitles = emptyList(),
94-
streamType = streamType,
95-
useYoutubeChunkedPlayback = false,
96-
modifier = Modifier.fillMaxSize(),
97-
playWhenReady = true,
98-
initialPositionMs = 0L,
99-
initialPositionRequestKey = "niakvio-native-reader",
100-
resizeMode = PlayerResizeMode.Fit,
101-
useNativeController = true,
102-
playerControlsState = PlayerControlsState(),
103-
onControllerReady = { _ -> },
104-
onSnapshot = { snapshot ->
105-
latestSnapshot.set(snapshot)
106-
if (snapshot.isEnded || (!snapshot.isLoading && (snapshot.isPlaying || snapshot.positionMs > 0L || snapshot.durationMs > 0L))) {
107-
terminal.countDown()
108-
}
109-
},
110-
onError = { message ->
111-
if (!message.isNullOrBlank()) {
112-
errorRef.compareAndSet(null, message)
113-
terminal.countDown()
114-
}
115-
},
116-
)
111+
// PlatformPlayerSurface assumes the same production composition
112+
// locals as a normal Nuvio app screen. In particular the current
113+
// Desktop implementation reads LocalNuvioPlatformDensity, whose
114+
// deliberate default throws outside NuvioTheme. Keep the real
115+
// theme around the real surface instead of inventing test values.
116+
NuvioTheme {
117+
PlatformPlayerSurface(
118+
sourceUrl = url,
119+
sourceHeaders = headers.orEmpty(),
120+
sourceResponseHeaders = emptyMap(),
121+
externalSubtitles = emptyList(),
122+
streamType = streamType,
123+
useYoutubeChunkedPlayback = false,
124+
modifier = Modifier.fillMaxSize(),
125+
playWhenReady = true,
126+
initialPositionMs = 0L,
127+
initialPositionRequestKey = "niakvio-native-reader",
128+
resizeMode = PlayerResizeMode.Fit,
129+
useNativeController = true,
130+
playerControlsState = PlayerControlsState(),
131+
onControllerReady = { _ -> },
132+
onSnapshot = { snapshot ->
133+
latestSnapshot.set(snapshot)
134+
if (snapshot.isEnded || (!snapshot.isLoading && (snapshot.isPlaying || snapshot.positionMs > 0L || snapshot.durationMs > 0L))) {
135+
terminal.countDown()
136+
}
137+
},
138+
onError = { message ->
139+
if (!message.isNullOrBlank()) {
140+
errorRef.compareAndSet(null, message)
141+
terminal.countDown()
142+
}
143+
},
144+
)
145+
}
117146
}
118147
frame.isVisible = true
119148
}
@@ -145,10 +174,12 @@
145174
}
146175
return DesktopNativePlayerProbe("timeout", "NuvioDesktopProductionPlayer", "READER_TIMEOUT", 0, "timeout", durationSeconds, positionSeconds)
147176
} catch (error: Throwable) {
177+
val (root, chain) = desktopThrowableChain(error)
178+
val rootMessage = root.message.orEmpty().ifBlank { "PLAYER_SETUP" }
148179
return DesktopNativePlayerProbe(
149-
"error", error::class.qualifiedName.orEmpty(),
150-
(error.message ?: "PLAYER_SETUP").replace(Regex("\\s+"), "_").take(160),
151-
0, "player_setup", null, null,
180+
"error", root::class.qualifiedName.orEmpty().ifBlank { root.javaClass.name },
181+
rootMessage.replace(Regex("\\s+"), "_").take(160),
182+
0, "player_setup", null, null, chain,
152183
)
153184
} finally {
154185
runCatching { SwingUtilities.invokeAndWait { frameRef.getAndSet(null)?.dispose() } }
@@ -198,7 +229,7 @@ def augment(path: Path, expected_minutes: int, stream_scope: str) -> None:
198229
emit("FIELD_NATIVE_PLAYER_BEGIN client=desktop fixture=$fixtureSlug provider64=${{b64(provider.id)}} request_type=$requestMediaType route_mode=$routeMode index=$index entry=PlatformPlayerSurface")
199230
captureDesktopPhase("player-start", fixtureSlug)
200231
val reader = probeDesktopProductionPlayer(row.url, row.headers, row.type, {expected_minutes})
201-
emit("FIELD_NATIVE_PLAYER client=desktop fixture=$fixtureSlug provider64=${{b64(provider.id)}} request_type=$requestMediaType route_mode=$routeMode index=$index state=${{reader.state}} engine=nuvio-production-desktop http_status=${{reader.httpStatus}} failure_stage=${{reader.failureStage}} duration_seconds=${{reader.durationSeconds ?: 0.0}} host64=${{b64(hostOnly(row.url))}} error_class64=${{b64(reader.errorClass)}} error_code64=${{b64(reader.errorCode)}} exception_chain64=${{b64("")}} response_header_names64=${{b64("")}} load_bytes=0 load_duration_ms=0 media_data_type=-1 track_type=-1")
232+
emit("FIELD_NATIVE_PLAYER client=desktop fixture=$fixtureSlug provider64=${{b64(provider.id)}} request_type=$requestMediaType route_mode=$routeMode index=$index state=${{reader.state}} engine=nuvio-production-desktop http_status=${{reader.httpStatus}} failure_stage=${{reader.failureStage}} duration_seconds=${{reader.durationSeconds ?: 0.0}} host64=${{b64(hostOnly(row.url))}} error_class64=${{b64(reader.errorClass)}} error_code64=${{b64(reader.errorCode)}} exception_chain64=${{b64(reader.exceptionChain)}} response_header_names64=${{b64("")}} load_bytes=0 load_duration_ms=0 media_data_type=-1 track_type=-1")
202233
captureDesktopPhase("player-result", fixtureSlug)
203234
// Independent transport diagnostics run only after the production
204235
// player has reached a terminal observation for this source.
@@ -222,7 +253,7 @@ def augment(path: Path, expected_minutes: int, stream_scope: str) -> None:
222253
print(
223254
f"FIELD_NATIVE_DESKTOP_READER_AUGMENTED source={path} streams={stream_scope} "
224255
f"expected_minutes={expected_minutes} timeout_ms={reader_timeout_ms} entry=PlatformPlayerSurface "
225-
f"ci_mode={'pr-bounded' if pr_bounded else 'deep'}"
256+
f"ci_mode={'pr-bounded' if pr_bounded else 'deep'} theme=NuvioTheme"
226257
)
227258

228259

0 commit comments

Comments
 (0)