From d3e0777cb5c12870dd4b00ffdad9b883dc46f231 Mon Sep 17 00:00:00 2001
From: tapframe <85391825+tapframe@users.noreply.github.com>
Date: Tue, 9 Jun 2026 21:16:31 +0530
Subject: [PATCH] feat: pause overlay for desktop player
---
.../nuvio/app/features/player/PlayerEngine.kt | 5 +
.../features/player/PlayerScreenRuntimeUi.kt | 9 +
.../player/desktop/NativePlayerController.kt | 10 ++
.../resources/player-ui/controls.css | 162 +++++++++++++++++-
.../resources/player-ui/controls.html | 11 ++
.../resources/player-ui/controls.js | 74 +++++++-
6 files changed, 269 insertions(+), 2 deletions(-)
diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerEngine.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerEngine.kt
index ec41b9f2..bc320feb 100644
--- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerEngine.kt
+++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerEngine.kt
@@ -52,6 +52,11 @@ data class PlayerControlsState(
val episodeText: String = "",
val streamTitle: String = "",
val providerName: String = "",
+ val pauseOverlayWatchingLabel: String = "You're watching",
+ val pauseOverlayLogo: String? = null,
+ val pauseOverlayEpisodeInfo: String = "",
+ val pauseOverlayEpisodeTitle: String = "",
+ val pauseOverlayDescription: String = "",
val resizeModeLabel: String = "Fit",
val playbackSpeedLabel: String = "1x",
val subtitlesLabel: String = "Subs",
diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerScreenRuntimeUi.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerScreenRuntimeUi.kt
index 66c6d5fe..ac7a07ec 100644
--- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerScreenRuntimeUi.kt
+++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerScreenRuntimeUi.kt
@@ -163,6 +163,15 @@ internal fun PlayerScreenRuntime.RenderPlayerRuntimeUi() {
episodeText = episodeText,
streamTitle = activeStreamTitle,
providerName = activeProviderName,
+ pauseOverlayWatchingLabel = stringResource(Res.string.compose_player_youre_watching),
+ pauseOverlayLogo = logo,
+ pauseOverlayEpisodeInfo = if (seasonNumber != null && episodeNumber != null) {
+ stringResource(Res.string.compose_player_episode_code_full, seasonNumber, episodeNumber)
+ } else {
+ activeProviderName
+ },
+ pauseOverlayEpisodeTitle = activeEpisodeTitle.orEmpty(),
+ pauseOverlayDescription = (pauseDescription ?: activeStreamSubtitle).orEmpty(),
resizeModeLabel = stringResource(resizeMode.labelRes),
playbackSpeedLabel = formatPlaybackSpeedLabel(playbackSnapshot.playbackSpeed),
subtitlesLabel = stringResource(Res.string.compose_player_subs),
diff --git a/composeApp/src/desktopMain/kotlin/com/nuvio/app/features/player/desktop/NativePlayerController.kt b/composeApp/src/desktopMain/kotlin/com/nuvio/app/features/player/desktop/NativePlayerController.kt
index 6665482c..e91d2283 100644
--- a/composeApp/src/desktopMain/kotlin/com/nuvio/app/features/player/desktop/NativePlayerController.kt
+++ b/composeApp/src/desktopMain/kotlin/com/nuvio/app/features/player/desktop/NativePlayerController.kt
@@ -467,6 +467,16 @@ private fun PlayerControlsState.toControlsJson(): String =
append(',')
appendJsonField("providerName", providerName)
append(',')
+ appendJsonField("pauseOverlayWatchingLabel", pauseOverlayWatchingLabel)
+ append(',')
+ appendJsonField("pauseOverlayLogo", pauseOverlayLogo.orEmpty())
+ append(',')
+ appendJsonField("pauseOverlayEpisodeInfo", pauseOverlayEpisodeInfo)
+ append(',')
+ appendJsonField("pauseOverlayEpisodeTitle", pauseOverlayEpisodeTitle)
+ append(',')
+ appendJsonField("pauseOverlayDescription", pauseOverlayDescription)
+ append(',')
appendJsonField("resizeModeLabel", resizeModeLabel)
append(',')
appendJsonField("playbackSpeedLabel", playbackSpeedLabel)
diff --git a/composeApp/src/desktopMain/resources/player-ui/controls.css b/composeApp/src/desktopMain/resources/player-ui/controls.css
index 4cd77ce5..fcb07112 100644
--- a/composeApp/src/desktopMain/resources/player-ui/controls.css
+++ b/composeApp/src/desktopMain/resources/player-ui/controls.css
@@ -177,6 +177,7 @@ button:disabled {
.player.opening-active .top-gradient,
.player.opening-active .bottom-gradient,
.player.opening-active .parental-guide,
+.player.opening-active .pause-metadata-overlay,
.player.opening-active .skip-prompt,
.player.opening-active .next-episode-card,
.player.opening-active .content {
@@ -764,6 +765,163 @@ button:disabled {
animation: center-spin 760ms linear infinite;
}
+.pause-metadata-overlay {
+ position: absolute;
+ inset: 0;
+ z-index: 6;
+ display: block;
+ pointer-events: none;
+ opacity: 0;
+ background:
+ linear-gradient(
+ 90deg,
+ rgba(0, 0, 0, .85) 0%,
+ rgba(0, 0, 0, .45) 48%,
+ rgba(0, 0, 0, 0) 100%
+ );
+ transition: opacity 180ms ease;
+}
+
+.pause-metadata-overlay.visible {
+ opacity: 1;
+ transition-duration: 220ms;
+}
+
+.pause-metadata-content {
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-end;
+ padding:
+ 40px
+ calc(var(--hp) + env(safe-area-inset-right))
+ 120px
+ calc(var(--hp) + env(safe-area-inset-left));
+}
+
+.pause-kicker {
+ color: #b8b8b8;
+ font-size: 18px;
+ line-height: 1.3;
+ font-weight: 500;
+}
+
+.pause-logo {
+ width: min(360px, 62vw);
+ height: 96px;
+ margin-top: 12px;
+ object-fit: contain;
+ object-position: left bottom;
+}
+
+.pause-logo[hidden],
+.pause-title[hidden],
+.pause-episode-info[hidden],
+.pause-episode-title[hidden],
+.pause-description[hidden] {
+ display: none;
+}
+
+.pause-title {
+ max-width: 62%;
+ margin-top: 12px;
+ color: #fff;
+ font-size: max(calc(var(--title) * 1.8), 32px);
+ line-height: 1.08;
+ font-weight: 800;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ display: -webkit-box;
+ -webkit-line-clamp: 2;
+ -webkit-box-orient: vertical;
+}
+
+.pause-episode-info {
+ margin-top: 8px;
+ color: #ccc;
+ font-size: 18px;
+ line-height: 1.3;
+}
+
+.pause-episode-title {
+ max-width: 62%;
+ margin-top: 12px;
+ color: #fff;
+ font-size: 22px;
+ line-height: 1.25;
+ font-weight: 700;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ display: -webkit-box;
+ -webkit-line-clamp: 2;
+ -webkit-box-orient: vertical;
+}
+
+.pause-description {
+ width: 62%;
+ margin-top: 16px;
+ color: #d6d6d6;
+ font-size: 18px;
+ line-height: 24px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ display: -webkit-box;
+ -webkit-line-clamp: 3;
+ -webkit-box-orient: vertical;
+}
+
+@media (max-height: 419px) {
+ .pause-metadata-content {
+ padding-top: 24px;
+ padding-bottom: 40px;
+ }
+
+ .pause-kicker,
+ .pause-episode-info {
+ font-size: 16px;
+ }
+
+ .pause-logo {
+ height: 64px;
+ margin-top: 8px;
+ }
+
+ .pause-title {
+ max-width: 82%;
+ margin-top: 8px;
+ font-size: max(calc(var(--title) * 1.35), 32px);
+ -webkit-line-clamp: 1;
+ }
+
+ .pause-episode-info {
+ margin-top: 6px;
+ }
+
+ .pause-episode-title {
+ max-width: 82%;
+ margin-top: 8px;
+ -webkit-line-clamp: 1;
+ }
+
+ .pause-description {
+ width: 82%;
+ margin-top: 10px;
+ font-size: 16px;
+ line-height: 20px;
+ -webkit-line-clamp: 2;
+ }
+}
+
+@media (max-height: 339px) {
+ .pause-metadata-content {
+ padding-bottom: 24px;
+ }
+
+ .pause-logo {
+ height: 48px;
+ }
+}
+
.opening-overlay {
position: absolute;
inset: 0;
@@ -1844,6 +2002,7 @@ button:focus-visible,
.header-actions,
.center-controls,
.center-status,
+ .pause-metadata-overlay,
.skip-prompt,
.next-episode-card,
.skip-progress-fill,
@@ -1852,6 +2011,7 @@ button:focus-visible,
.opening-progress-bar,
.opening-artwork,
.opening-logo,
+ .pause-logo,
.next-episode-thumb img,
.episode-thumb img,
.image-loading::before,
@@ -1880,4 +2040,4 @@ svg {
display: block;
fill: currentColor;
stroke: none;
-}
\ No newline at end of file
+}
diff --git a/composeApp/src/desktopMain/resources/player-ui/controls.html b/composeApp/src/desktopMain/resources/player-ui/controls.html
index 134a30ad..1c8328e9 100644
--- a/composeApp/src/desktopMain/resources/player-ui/controls.html
+++ b/composeApp/src/desktopMain/resources/player-ui/controls.html
@@ -113,6 +113,17 @@
+
+