Fix stale preview reels when switching between series
Episode tiles were keyed by season/episode index only, so switching series reused the DOM: text bindings updated but <video> elements were kept, and browsers ignore patched <source> children without load() — the previous series' reels kept playing. - Key SeriesFullView by item id so a series switch fully remounts it. - Include a seriesIdentity (id, else root_id+title) in episode tile keys so tiles/videos remount even on an in-place series swap. - Reset episode cursor, audio owner and preview startup timers when the series identity changes, and re-gate video mounting.
This commit is contained in:
@@ -2,6 +2,7 @@
|
|||||||
<!-- Full screen view for series -->
|
<!-- Full screen view for series -->
|
||||||
<SeriesFullView
|
<SeriesFullView
|
||||||
v-if="item.type === 'series'"
|
v-if="item.type === 'series'"
|
||||||
|
:key="item.id"
|
||||||
:series="item.data as Series"
|
:series="item.data as Series"
|
||||||
:all-movies="allMovies"
|
:all-movies="allMovies"
|
||||||
:focus-episode="focusEpisode"
|
:focus-episode="focusEpisode"
|
||||||
|
|||||||
@@ -112,7 +112,7 @@
|
|||||||
<div class="episodes-grid">
|
<div class="episodes-grid">
|
||||||
<div
|
<div
|
||||||
v-for="(episode, eIndex) in selectedSeason?.episodes || []"
|
v-for="(episode, eIndex) in selectedSeason?.episodes || []"
|
||||||
:key="`${selectedSeasonIndex}-${episode.episode_number}`"
|
:key="`${seriesIdentity}-${selectedSeasonIndex}-${episode.episode_number}`"
|
||||||
class="episode-tile"
|
class="episode-tile"
|
||||||
:class="{ 'episode-tile--ahead': isEpisodeAhead(eIndex) }"
|
:class="{ 'episode-tile--ahead': isEpisodeAhead(eIndex) }"
|
||||||
v-bind="getEpisodeNavAttrs(selectedSeasonIndex, eIndex)"
|
v-bind="getEpisodeNavAttrs(selectedSeasonIndex, eIndex)"
|
||||||
@@ -258,7 +258,7 @@ import EpisodeReleaseMenu from "./EpisodeReleaseMenu.vue"
|
|||||||
import { sortTorrentsByPreference } from "../composables/useSettings"
|
import { sortTorrentsByPreference } from "../composables/useSettings"
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
series: Series & { root_id?: string | null }
|
series: Series & { id?: string; root_id?: string | null }
|
||||||
allMovies: MovieUi[]
|
allMovies: MovieUi[]
|
||||||
focusEpisode?: { seasonNumber: number; episodeNumber: number } | null
|
focusEpisode?: { seasonNumber: number; episodeNumber: number } | null
|
||||||
resumePoint?: SeriesResumePoint | null
|
resumePoint?: SeriesResumePoint | null
|
||||||
@@ -297,6 +297,15 @@ const selectedSeason = computed<Season | null>(
|
|||||||
() => props.series.seasons[selectedSeasonIndex.value] || null,
|
() => props.series.seasons[selectedSeasonIndex.value] || null,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Stable identity of the displayed series. Episode tiles carry it in their
|
||||||
|
// :key so swapping to another series remounts the tiles (and their <video>
|
||||||
|
// elements) instead of patching <source> children — which browsers ignore,
|
||||||
|
// leaving the previous series' preview reels playing.
|
||||||
|
const seriesIdentity = computed(() => {
|
||||||
|
const s = props.series
|
||||||
|
return s.id ?? `${s.root_id ?? ""}:${s.title ?? ""}`
|
||||||
|
})
|
||||||
|
|
||||||
function selectSeason(index: number) {
|
function selectSeason(index: number) {
|
||||||
if (index < 0 || index >= props.series.seasons.length) return
|
if (index < 0 || index >= props.series.seasons.length) return
|
||||||
if (selectedSeasonIndex.value === index) return
|
if (selectedSeasonIndex.value === index) return
|
||||||
@@ -1409,6 +1418,17 @@ watch(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Series swapped under a reused component instance: tiles remount via the
|
||||||
|
// :key, but per-episode state keyed by episode index alone (audio owner,
|
||||||
|
// cursor, startup timers) survives — reset it so the old series' playback
|
||||||
|
// and audio don't bleed into the new one.
|
||||||
|
watch(seriesIdentity, () => {
|
||||||
|
episodeCursorIndex.value = null
|
||||||
|
setAudioOwner(null)
|
||||||
|
stopEpisodePreviews()
|
||||||
|
scheduleEpisodeMediaReady()
|
||||||
|
})
|
||||||
|
|
||||||
// Episodes past the spoiler threshold (cursor or continue point) are faded;
|
// Episodes past the spoiler threshold (cursor or continue point) are faded;
|
||||||
// stop their playback too.
|
// stop their playback too.
|
||||||
watch([episodeCursorIndex, resumePointGlobalIndex], () => {
|
watch([episodeCursorIndex, resumePointGlobalIndex], () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user