clean-up #15
@@ -7,6 +7,7 @@ import ImageLightbox from './ImageLightbox'
|
|||||||
import TagSelector from '@/components/tags/TagSelector'
|
import TagSelector from '@/components/tags/TagSelector'
|
||||||
import FilterPanel from '@/components/FilterPanel'
|
import FilterPanel from '@/components/FilterPanel'
|
||||||
import DoomScrollView, { type DoomScrollItem } from '@/components/DoomScrollView'
|
import DoomScrollView, { type DoomScrollItem } from '@/components/DoomScrollView'
|
||||||
|
import { isBrowserPlayable } from '@/lib/browser-media'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
libraryId: string
|
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 filters are active, doom scroll uses filteredEntries (already filtered by search/tags).
|
||||||
// When no filters, doom scroll uses the full recursiveEntries.
|
// When no filters, doom scroll uses the full recursiveEntries.
|
||||||
const doomScrollItems: DoomScrollItem[] = (filtersActive ? filteredEntries : 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' }))
|
.map((e) => ({ url: e.url!, name: e.name, mediaType: e.mediaType as 'video' | 'image' }))
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import type { Movie } from '@/types'
|
|||||||
import MovieDetailModal from './MovieDetailModal'
|
import MovieDetailModal from './MovieDetailModal'
|
||||||
import FilterPanel from '@/components/FilterPanel'
|
import FilterPanel from '@/components/FilterPanel'
|
||||||
import DoomScrollView, { type DoomScrollItem } from '@/components/DoomScrollView'
|
import DoomScrollView, { type DoomScrollItem } from '@/components/DoomScrollView'
|
||||||
|
import { isBrowserPlayable } from '@/lib/browser-media'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
libraryId: string
|
libraryId: string
|
||||||
@@ -74,7 +75,7 @@ export default function MoviesView({ libraryId }: Props) {
|
|||||||
|
|
||||||
const handleDoomScroll = () => {
|
const handleDoomScroll = () => {
|
||||||
// Use filtered movies — respects any active search/tag filters automatically
|
// 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)}`,
|
url: `/api/file?libraryId=${encodeURIComponent(libraryId)}&path=${encodeURIComponent(m.videoPath)}`,
|
||||||
name: m.title,
|
name: m.title,
|
||||||
mediaType: 'video' as const,
|
mediaType: 'video' as const,
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import VideoPlayerModal from '@/components/mixed/VideoPlayerModal'
|
|||||||
import TagSelector from '@/components/tags/TagSelector'
|
import TagSelector from '@/components/tags/TagSelector'
|
||||||
import EpisodeCard from './EpisodeCard'
|
import EpisodeCard from './EpisodeCard'
|
||||||
import DoomScrollView, { type DoomScrollItem } from '@/components/DoomScrollView'
|
import DoomScrollView, { type DoomScrollItem } from '@/components/DoomScrollView'
|
||||||
|
import { isBrowserPlayable } from '@/lib/browser-media'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
libraryId: string
|
libraryId: string
|
||||||
@@ -184,7 +185,7 @@ export default function TvView({ libraryId }: Props) {
|
|||||||
return seasonEps.flat()
|
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)}`,
|
url: `/api/file?libraryId=${encodeURIComponent(libraryId)}&path=${encodeURIComponent(ep.videoPath)}`,
|
||||||
name: ep.title,
|
name: ep.title,
|
||||||
mediaType: 'video' as const,
|
mediaType: 'video' as const,
|
||||||
@@ -209,7 +210,7 @@ export default function TvView({ libraryId }: Props) {
|
|||||||
return seasonEps.flat()
|
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)}`,
|
url: `/api/file?libraryId=${encodeURIComponent(libraryId)}&path=${encodeURIComponent(ep.videoPath)}`,
|
||||||
name: ep.title,
|
name: ep.title,
|
||||||
mediaType: 'video' as const,
|
mediaType: 'video' as const,
|
||||||
|
|||||||
19
src/lib/browser-media.ts
Normal file
19
src/lib/browser-media.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
/**
|
||||||
|
* Browser-native media formats safe for use in <video> and <img> elements.
|
||||||
|
* Kept separate from the broader scanner extension sets (media-utils.ts, files.ts)
|
||||||
|
* which include server-side-only formats like .mkv, .avi, .tiff, etc.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const BROWSER_VIDEO_EXTENSIONS = new Set(['.mp4', '.webm', '.mov', '.m4v'])
|
||||||
|
export const BROWSER_IMAGE_EXTENSIONS = new Set(['.jpg', '.jpeg', '.png', '.gif', '.webp', '.bmp'])
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the file at `filename` (or path) has a browser-playable extension.
|
||||||
|
* Uses lastIndexOf to avoid importing the Node `path` module in client components.
|
||||||
|
*/
|
||||||
|
export function isBrowserPlayable(filename: string): boolean {
|
||||||
|
const dot = filename.lastIndexOf('.')
|
||||||
|
if (dot === -1) return false
|
||||||
|
const ext = filename.slice(dot).toLowerCase()
|
||||||
|
return BROWSER_VIDEO_EXTENSIONS.has(ext) || BROWSER_IMAGE_EXTENSIONS.has(ext)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user