diff --git a/src/components/mixed/MixedView.tsx b/src/components/mixed/MixedView.tsx index 783bfe4..7f6e9b6 100644 --- a/src/components/mixed/MixedView.tsx +++ b/src/components/mixed/MixedView.tsx @@ -7,6 +7,7 @@ import ImageLightbox from './ImageLightbox' import TagSelector from '@/components/tags/TagSelector' import FilterPanel from '@/components/FilterPanel' import DoomScrollView, { type DoomScrollItem } from '@/components/DoomScrollView' +import { isBrowserPlayable } from '@/lib/browser-media' interface Props { libraryId: string @@ -200,7 +201,7 @@ export default function MixedView({ libraryId, initialPath }: Props) { // When filters are active, doom scroll uses filteredEntries (already filtered by search/tags). // When no filters, doom scroll uses the full recursiveEntries. const doomScrollItems: DoomScrollItem[] = (filtersActive ? filteredEntries : recursiveEntries) - .filter((e) => e.type === 'file' && (e.mediaType === 'video' || e.mediaType === 'image') && e.url) + .filter((e) => e.type === 'file' && (e.mediaType === 'video' || e.mediaType === 'image') && e.url && isBrowserPlayable(e.name)) .map((e) => ({ url: e.url!, name: e.name, mediaType: e.mediaType as 'video' | 'image' })) return ( diff --git a/src/components/movies/MoviesView.tsx b/src/components/movies/MoviesView.tsx index bb28d68..cc446d1 100644 --- a/src/components/movies/MoviesView.tsx +++ b/src/components/movies/MoviesView.tsx @@ -5,6 +5,7 @@ import type { Movie } from '@/types' import MovieDetailModal from './MovieDetailModal' import FilterPanel from '@/components/FilterPanel' import DoomScrollView, { type DoomScrollItem } from '@/components/DoomScrollView' +import { isBrowserPlayable } from '@/lib/browser-media' interface Props { libraryId: string @@ -74,7 +75,7 @@ export default function MoviesView({ libraryId }: Props) { const handleDoomScroll = () => { // Use filtered movies — respects any active search/tag filters automatically - const items: DoomScrollItem[] = filtered.map((m) => ({ + const items: DoomScrollItem[] = filtered.filter((m) => isBrowserPlayable(m.videoPath)).map((m) => ({ url: `/api/file?libraryId=${encodeURIComponent(libraryId)}&path=${encodeURIComponent(m.videoPath)}`, name: m.title, mediaType: 'video' as const, diff --git a/src/components/tv/TvView.tsx b/src/components/tv/TvView.tsx index 87cfafe..9a95816 100644 --- a/src/components/tv/TvView.tsx +++ b/src/components/tv/TvView.tsx @@ -8,6 +8,7 @@ import VideoPlayerModal from '@/components/mixed/VideoPlayerModal' import TagSelector from '@/components/tags/TagSelector' import EpisodeCard from './EpisodeCard' import DoomScrollView, { type DoomScrollItem } from '@/components/DoomScrollView' +import { isBrowserPlayable } from '@/lib/browser-media' interface Props { libraryId: string @@ -184,7 +185,7 @@ export default function TvView({ libraryId }: Props) { return seasonEps.flat() }) ) - items = episodeLists.flat().map((ep) => ({ + items = episodeLists.flat().filter((ep) => isBrowserPlayable(ep.videoPath)).map((ep) => ({ url: `/api/file?libraryId=${encodeURIComponent(libraryId)}&path=${encodeURIComponent(ep.videoPath)}`, name: ep.title, mediaType: 'video' as const, @@ -209,7 +210,7 @@ export default function TvView({ libraryId }: Props) { return seasonEps.flat() }) ) - items = episodeLists.flat().map((ep) => ({ + items = episodeLists.flat().filter((ep) => isBrowserPlayable(ep.videoPath)).map((ep) => ({ url: `/api/file?libraryId=${encodeURIComponent(libraryId)}&path=${encodeURIComponent(ep.videoPath)}`, name: ep.title, mediaType: 'video' as const, diff --git a/src/lib/browser-media.ts b/src/lib/browser-media.ts new file mode 100644 index 0000000..99d87cb --- /dev/null +++ b/src/lib/browser-media.ts @@ -0,0 +1,19 @@ +/** + * Browser-native media formats safe for use in