Filter non-browser-playable formats from Doom Scroll
Formats like .mkv, .avi, .wmv, .ts, .m2ts and .tiff are not natively supported by browsers and would stall silently in Doom Scroll mode. Add src/lib/browser-media.ts with BROWSER_VIDEO_EXTENSIONS (.mp4, .webm, .mov, .m4v), BROWSER_IMAGE_EXTENSIONS (.jpg, .jpeg, .png, .gif, .webp, .bmp), and an isBrowserPlayable() helper that extracts the extension without importing Node's path module. Filter doomScrollItems in MixedView, MoviesView, and TvView using this helper so only natively renderable files are passed to DoomScrollView. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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 (
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
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