diff --git a/public/css/main.css b/public/css/main.css index e54b969..dedba28 100644 --- a/public/css/main.css +++ b/public/css/main.css @@ -3779,19 +3779,20 @@ kbd { /* Next Episode Panel */ .watch-next-episode { position: absolute; - bottom: 140px; + bottom: 160px; right: var(--space-lg); display: flex; align-items: center; gap: var(--space-md); padding: var(--space-md) var(--space-lg); - background: var(--glass-bg); + background: rgba(0, 0, 0, 0.85); backdrop-filter: blur(16px); - border: 1px solid var(--glass-border); + border: 1px solid rgba(255, 255, 255, 0.2); border-radius: var(--radius-lg); - box-shadow: var(--shadow-lg); + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5); animation: slideInRight 0.3s ease; - z-index: 20; + z-index: 50; + pointer-events: auto; } .watch-next-episode.hidden { @@ -4088,7 +4089,7 @@ kbd { .watch-next-episode { left: var(--space-md); right: var(--space-md); - bottom: 100px; + bottom: calc(120px + env(safe-area-inset-bottom, 0px)); flex-wrap: wrap; } @@ -4100,11 +4101,6 @@ kbd { max-width: none; } - /* iOS Safari safe area adjustments for mobile */ - .watch-next-episode { - bottom: calc(100px + env(safe-area-inset-bottom, 0px)); - } - .watch-bottom-bar { padding-bottom: calc(var(--space-lg) + env(safe-area-inset-bottom, 0px)); } diff --git a/public/js/pages/WatchPage.js b/public/js/pages/WatchPage.js index 7a6528b..b85ca63 100644 --- a/public/js/pages/WatchPage.js +++ b/public/js/pages/WatchPage.js @@ -88,6 +88,8 @@ class WatchPage { this.nextEpisodeTimeout = null; this.nextEpisodeCountdown = 10; this.nextEpisodeInterval = null; + this.nextEpisodeShowing = false; + this.nextEpisodeDismissed = false; // Watch history this.historyInterval = null; @@ -258,6 +260,7 @@ class WatchPage { // Reset state this.cancelNextEpisode(); + this.nextEpisodeDismissed = false; // Navigate to watch page this.app.navigateTo('watch', true); @@ -760,6 +763,28 @@ class WatchPage { const percent = (this.video.currentTime / this.video.duration) * 100; this.progressSlider.value = percent; this.timeCurrent.textContent = this.formatTime(this.video.currentTime); + + // Show "Up Next" panel early for series (like streaming services do during credits) + // Only show if auto-play next episode is enabled + const autoPlayEnabled = this.app?.player?.settings?.autoPlayNextEpisode; + if (autoPlayEnabled && this.contentType === 'series' && this.seriesInfo && !this.nextEpisodeShowing && !this.nextEpisodeDismissed) { + const duration = this.video.duration; + const currentTime = this.video.currentTime; + + // Only proceed if we have reliable duration data + if (isFinite(duration) && duration >= 180 && currentTime >= 120) { + const timeRemaining = duration - currentTime; + const creditsThreshold = 10; // seconds before end to show "Up Next" + + if (timeRemaining <= creditsThreshold && timeRemaining > 0) { + const nextEp = this.getNextEpisode(); + if (nextEp) { + this.nextEpisodeShowing = true; + this.showNextEpisodePanel(nextEp); + } + } + } + } } onMetadataLoaded() { @@ -805,10 +830,12 @@ class WatchPage { } onEnded() { - // For series, show next episode panel - if (this.contentType === 'series' && this.seriesInfo) { + // For series, show next episode panel if not already showing and auto-play is enabled + const autoPlayEnabled = this.app?.player?.settings?.autoPlayNextEpisode; + if (autoPlayEnabled && this.contentType === 'series' && this.seriesInfo && !this.nextEpisodeShowing) { const nextEp = this.getNextEpisode(); if (nextEp) { + this.nextEpisodeShowing = true; this.showNextEpisodePanel(nextEp); } } @@ -1326,6 +1353,8 @@ class WatchPage { cancelNextEpisode() { clearInterval(this.nextEpisodeInterval); this.nextEpisodePanel?.classList.add('hidden'); + this.nextEpisodeShowing = false; + this.nextEpisodeDismissed = true; // Prevent re-triggering if (this.nextEpisodePanel) { this.nextEpisodePanel.nextEpisodeData = null; }