performance-stability #14
@@ -73,9 +73,20 @@ export default function DoomScrollView({ items, videoContext = 'mixed', onClose
|
||||
setTimeout(() => { cooldownRef.current = false }, 300)
|
||||
}, [goNext, goPrev])
|
||||
|
||||
// Reset pause when switching items
|
||||
// On navigation to a new item: reset pause state and start playing.
|
||||
// Merging the reset + play() into one effect prevents the old isPaused=true
|
||||
// value from calling pause() on the freshly-mounted video element before the
|
||||
// reset fires. If autoplay is blocked by browser policy (common when unmuted),
|
||||
// fall back to muted and retry — the user can unmute manually afterward.
|
||||
useEffect(() => {
|
||||
setIsPaused(false)
|
||||
if (!videoRef.current) return
|
||||
videoRef.current.play().catch(() => {
|
||||
if (!videoRef.current) return
|
||||
videoRef.current.muted = true
|
||||
setLocalMuted(true)
|
||||
videoRef.current.play().catch(() => {})
|
||||
})
|
||||
}, [current?.url])
|
||||
|
||||
// Sync muted imperatively — React's muted prop is not reliable
|
||||
@@ -83,7 +94,8 @@ export default function DoomScrollView({ items, videoContext = 'mixed', onClose
|
||||
if (videoRef.current) videoRef.current.muted = localMuted
|
||||
}, [localMuted, current?.url])
|
||||
|
||||
// Sync play/pause imperatively
|
||||
// Sync play/pause imperatively for user-initiated pause/unpause only.
|
||||
// current?.url is intentionally excluded: navigation is handled above.
|
||||
useEffect(() => {
|
||||
if (!videoRef.current) return
|
||||
if (isPaused) {
|
||||
@@ -91,7 +103,7 @@ export default function DoomScrollView({ items, videoContext = 'mixed', onClose
|
||||
} else {
|
||||
videoRef.current.play().catch(() => {})
|
||||
}
|
||||
}, [isPaused, current?.url])
|
||||
}, [isPaused])
|
||||
|
||||
// Auto-play timer — resets on each new item, pause, enable/disable, or interval change
|
||||
useEffect(() => {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { useEffect, useRef, useState, useCallback } from 'react'
|
||||
import type { TvSeries, TvSeason, TvEpisode } from '@/types'
|
||||
import type { DirectoryListing } from '@/types'
|
||||
|
||||
import FilterPanel from '@/components/FilterPanel'
|
||||
import VideoPlayerModal from '@/components/mixed/VideoPlayerModal'
|
||||
import TagSelector from '@/components/tags/TagSelector'
|
||||
@@ -190,13 +190,30 @@ export default function TvView({ libraryId }: Props) {
|
||||
mediaType: 'video' as const,
|
||||
}))
|
||||
} else {
|
||||
// No filters — use full recursive browse
|
||||
const data: DirectoryListing = await fetch(
|
||||
`/api/browse?libraryId=${encodeURIComponent(libraryId)}&path=&recursive=true`
|
||||
// No filters — fetch all episodes via the TV API hierarchy
|
||||
const allSeries: TvSeries[] = await fetch(
|
||||
`/api/tv?libraryId=${encodeURIComponent(libraryId)}`
|
||||
).then((r) => r.json())
|
||||
items = data.entries
|
||||
.filter((e) => e.type === 'file' && e.mediaType === 'video' && e.url)
|
||||
.map((e) => ({ url: e.url!, name: e.name, mediaType: 'video' as const }))
|
||||
const episodeLists = await Promise.all(
|
||||
allSeries.map(async (s) => {
|
||||
const seasons: TvSeason[] = await fetch(
|
||||
`/api/tv?libraryId=${encodeURIComponent(libraryId)}&seriesId=${encodeURIComponent(s.id)}`
|
||||
).then((r) => r.json())
|
||||
const seasonEps = await Promise.all(
|
||||
seasons.map((season) =>
|
||||
fetch(
|
||||
`/api/tv?libraryId=${encodeURIComponent(libraryId)}&seriesId=${encodeURIComponent(s.id)}&seasonId=${encodeURIComponent(season.id)}`
|
||||
).then((r) => r.json() as Promise<TvEpisode[]>)
|
||||
)
|
||||
)
|
||||
return seasonEps.flat()
|
||||
})
|
||||
)
|
||||
items = episodeLists.flat().map((ep) => ({
|
||||
url: `/api/file?libraryId=${encodeURIComponent(libraryId)}&path=${encodeURIComponent(ep.videoPath)}`,
|
||||
name: ep.title,
|
||||
mediaType: 'video' as const,
|
||||
}))
|
||||
}
|
||||
setDoomScrollItems(items)
|
||||
setDoomScrollActive(true)
|
||||
|
||||
Reference in New Issue
Block a user