clean-up #15

Merged
gpatti merged 3 commits from clean-up into main 2026-04-11 01:33:11 +00:00
4 changed files with 26 additions and 4 deletions
Showing only changes of commit 6c2443fa2c - Show all commits

View File

@@ -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 (

View File

@@ -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,

View File

@@ -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,

19
src/lib/browser-media.ts Normal file
View 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)
}