remove ffmpeg video transcode, back to native-only playback
Drop the /transcode (and dead /transcode-audio) routes and the frontend mkv/avi transcode wiring. Only browser-native formats (mp4, webm, mov, m4v, ogv) are classified as video now; other containers fall through to the generic file view. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { useMemo, useRef } from 'react';
|
||||
import { Loader2, FolderArchive } from 'lucide-react';
|
||||
import { getLang, getRawUrl, getTranscodeUrl, needsTranscode, getArchiveBaseName } from './file-types';
|
||||
import { getLang, getRawUrl, getArchiveBaseName } from './file-types';
|
||||
import { useFileViewer } from './FileViewerContext';
|
||||
import { PdfRenderer } from './renderers/PdfRenderer';
|
||||
import { ImageRenderer } from './renderers/ImageRenderer';
|
||||
@@ -14,11 +14,11 @@ import { EditorRenderer } from './renderers/EditorRenderer';
|
||||
import { JsonEditorRenderer } from './renderers/JsonEditorRenderer';
|
||||
|
||||
export const FileViewerBody = () => {
|
||||
const { filePath, fileName, root, fileType, content, loading, error, autoPlay, editing, setContent } = useFileViewer();
|
||||
const { filePath, fileName, root, fileType, content, loading, error, autoPlay, editing, setContent } =
|
||||
useFileViewer();
|
||||
const scrollRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const videoSrc =
|
||||
fileType === 'video' ? (needsTranscode(fileName) ? getTranscodeUrl(filePath, root) : getRawUrl(filePath, root)) : '';
|
||||
const videoSrc = fileType === 'video' ? getRawUrl(filePath, root) : '';
|
||||
|
||||
const isJson = useMemo(() => {
|
||||
if (!content) return false;
|
||||
@@ -52,11 +52,7 @@ export const FileViewerBody = () => {
|
||||
) : fileType === 'image' ? (
|
||||
<ImageRenderer src={getRawUrl(filePath, root)} fileName={fileName} />
|
||||
) : fileType === 'video' ? (
|
||||
<VideoRenderer
|
||||
src={videoSrc}
|
||||
fileName={fileName}
|
||||
fallbackSrc={needsTranscode(fileName) ? undefined : getTranscodeUrl(filePath, root)}
|
||||
/>
|
||||
<VideoRenderer src={videoSrc} fileName={fileName} />
|
||||
) : fileType === 'audio' ? (
|
||||
<AudioRenderer src={getRawUrl(filePath, root)} fileName={fileName} autoPlay={autoPlay} />
|
||||
) : content !== null && editing && isJson ? (
|
||||
|
||||
@@ -7,24 +7,6 @@ export type FileType = 'markdown' | 'audio' | 'video' | 'image' | 'pdf' | 'code'
|
||||
export const ARCHIVE_EXTS = ['zip', 'tar', 'gz', 'tgz', 'bz2', 'tbz2', 'xz', 'txz', 'zst', '7z', 'rar'];
|
||||
export const AUDIO_EXTS = ['mp3', 'wav', 'flac', 'ogg', 'oga', 'opus', 'aac', 'm4a', 'wma'];
|
||||
export const NATIVE_VIDEO_EXTS = ['mp4', 'm4v', 'webm', 'ogv', 'mov'];
|
||||
export const TRANSCODE_VIDEO_EXTS = [
|
||||
'mkv',
|
||||
'avi',
|
||||
'wmv',
|
||||
'flv',
|
||||
'ts',
|
||||
'mts',
|
||||
'm2ts',
|
||||
'3gp',
|
||||
'3g2',
|
||||
'vob',
|
||||
'divx',
|
||||
'asf',
|
||||
'f4v',
|
||||
'rm',
|
||||
'rmvb',
|
||||
];
|
||||
export const ALL_VIDEO_EXTS = [...NATIVE_VIDEO_EXTS, ...TRANSCODE_VIDEO_EXTS];
|
||||
export const IMAGE_EXTS = [
|
||||
'jpg',
|
||||
'jpeg',
|
||||
@@ -107,9 +89,15 @@ export function getFileType(name: string): FileType {
|
||||
if (ext === 'pdf') return 'pdf';
|
||||
if (EXT_TO_LANG[ext]) return 'code';
|
||||
if (AUDIO_EXTS.includes(ext)) return 'audio';
|
||||
if (ALL_VIDEO_EXTS.includes(ext)) return 'video';
|
||||
if (NATIVE_VIDEO_EXTS.includes(ext)) return 'video';
|
||||
if (IMAGE_EXTS.includes(ext)) return 'image';
|
||||
if (baseName.endsWith('.tar.gz') || baseName.endsWith('.tar.bz2') || baseName.endsWith('.tar.xz') || baseName.endsWith('.tar.zst')) return 'archive';
|
||||
if (
|
||||
baseName.endsWith('.tar.gz') ||
|
||||
baseName.endsWith('.tar.bz2') ||
|
||||
baseName.endsWith('.tar.xz') ||
|
||||
baseName.endsWith('.tar.zst')
|
||||
)
|
||||
return 'archive';
|
||||
if (ARCHIVE_EXTS.includes(ext)) return 'archive';
|
||||
if (baseName === 'dockerfile' || baseName === 'makefile') return 'code';
|
||||
return 'text';
|
||||
@@ -127,10 +115,6 @@ export function getExt(name: string): string {
|
||||
return name.split('.').pop()?.toLowerCase() ?? '';
|
||||
}
|
||||
|
||||
export function needsTranscode(name: string): boolean {
|
||||
return TRANSCODE_VIDEO_EXTS.includes(getExt(name));
|
||||
}
|
||||
|
||||
export function formatTime(s: number): string {
|
||||
if (!isFinite(s) || isNaN(s)) return '0:00';
|
||||
const h = Math.floor(s / 3600);
|
||||
@@ -147,20 +131,6 @@ export function getRawUrl(filePath: string, root?: string): string {
|
||||
return `${API_URL}/file-browser/raw?path=${encodeURIComponent(filePath)}&token=${encodeURIComponent(token)}${rootParam}`;
|
||||
}
|
||||
|
||||
export function getTranscodeUrl(filePath: string, root?: string): string {
|
||||
const headers = getHeaders();
|
||||
const token = headers['Authorization']?.replace('Bearer ', '') ?? '';
|
||||
const rootParam = root && root !== 'home' ? `&root=${encodeURIComponent(root)}` : '';
|
||||
return `${API_URL}/file-browser/transcode?path=${encodeURIComponent(filePath)}&token=${encodeURIComponent(token)}${rootParam}`;
|
||||
}
|
||||
|
||||
export function getTranscodeAudioUrl(filePath: string, root?: string): string {
|
||||
const headers = getHeaders();
|
||||
const token = headers['Authorization']?.replace('Bearer ', '') ?? '';
|
||||
const rootParam = root && root !== 'home' ? `&root=${encodeURIComponent(root)}` : '';
|
||||
return `${API_URL}/file-browser/transcode-audio?path=${encodeURIComponent(filePath)}&token=${encodeURIComponent(token)}${rootParam}`;
|
||||
}
|
||||
|
||||
export function getArchiveBaseName(name: string): string {
|
||||
const lower = name.toLowerCase();
|
||||
for (const compound of ['.tar.gz', '.tar.bz2', '.tar.xz', '.tar.zst']) {
|
||||
|
||||
@@ -6,10 +6,9 @@ import { useSeekBar, SeekBar } from './SeekBar';
|
||||
type VideoRendererProps = {
|
||||
src: string;
|
||||
fileName: string;
|
||||
fallbackSrc?: string;
|
||||
};
|
||||
|
||||
export const VideoRenderer = ({ src, fileName, fallbackSrc }: VideoRendererProps) => {
|
||||
export const VideoRenderer = ({ src, fileName }: VideoRendererProps) => {
|
||||
const videoRef = useRef<HTMLVideoElement>(null);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const hideTimer = useRef<ReturnType<typeof setTimeout>>(null);
|
||||
@@ -27,7 +26,6 @@ export const VideoRenderer = ({ src, fileName, fallbackSrc }: VideoRendererProps
|
||||
useEffect(() => {
|
||||
const v = videoRef.current;
|
||||
if (!v) return;
|
||||
let blobUrl: string | null = null;
|
||||
const onLoaded = () => {
|
||||
setDuration(v.duration);
|
||||
setLoaded(true);
|
||||
@@ -36,34 +34,7 @@ export const VideoRenderer = ({ src, fileName, fallbackSrc }: VideoRendererProps
|
||||
const onPlay = () => setPlaying(true);
|
||||
const onPause = () => setPlaying(false);
|
||||
const onEnded = () => setPlaying(false);
|
||||
let fetching = false;
|
||||
let fetchDone = false;
|
||||
const onError = () => {
|
||||
if (fetching) return;
|
||||
if (fetchDone) {
|
||||
setError(true);
|
||||
return;
|
||||
}
|
||||
fetching = true;
|
||||
const fetchUrl = fallbackSrc || src;
|
||||
fetch(fetchUrl)
|
||||
.then((res) => {
|
||||
if (!res.ok) throw new Error();
|
||||
return res.blob();
|
||||
})
|
||||
.then((blob) => {
|
||||
fetching = false;
|
||||
fetchDone = true;
|
||||
blobUrl = URL.createObjectURL(blob);
|
||||
v.src = blobUrl;
|
||||
v.load();
|
||||
})
|
||||
.catch(() => {
|
||||
fetching = false;
|
||||
fetchDone = true;
|
||||
setError(true);
|
||||
});
|
||||
};
|
||||
const onError = () => setError(true);
|
||||
v.addEventListener('loadedmetadata', onLoaded);
|
||||
v.addEventListener('timeupdate', onTime);
|
||||
v.addEventListener('play', onPlay);
|
||||
@@ -77,7 +48,6 @@ export const VideoRenderer = ({ src, fileName, fallbackSrc }: VideoRendererProps
|
||||
v.removeEventListener('pause', onPause);
|
||||
v.removeEventListener('ended', onEnded);
|
||||
v.removeEventListener('error', onError);
|
||||
if (blobUrl) URL.revokeObjectURL(blobUrl);
|
||||
};
|
||||
}, []);
|
||||
|
||||
@@ -147,7 +117,14 @@ export const VideoRenderer = ({ src, fileName, fallbackSrc }: VideoRendererProps
|
||||
if (playing) setShowControls(false);
|
||||
}}
|
||||
>
|
||||
<video ref={videoRef} src={src} preload="metadata" playsInline className="max-w-full max-h-full" onClick={togglePlay} />
|
||||
<video
|
||||
ref={videoRef}
|
||||
src={src}
|
||||
preload="metadata"
|
||||
playsInline
|
||||
className="max-w-full max-h-full"
|
||||
onClick={togglePlay}
|
||||
/>
|
||||
|
||||
{loaded && !playing && (
|
||||
<button onClick={togglePlay} className="absolute inset-0 flex items-center justify-center cursor-pointer">
|
||||
@@ -183,7 +160,10 @@ export const VideoRenderer = ({ src, fileName, fallbackSrc }: VideoRendererProps
|
||||
</button>
|
||||
|
||||
<div className="flex items-center gap-1.5">
|
||||
<button onClick={toggleMute} className="p-1 text-white/70 hover:text-white transition-colors cursor-pointer">
|
||||
<button
|
||||
onClick={toggleMute}
|
||||
className="p-1 text-white/70 hover:text-white transition-colors cursor-pointer"
|
||||
>
|
||||
{muted ? <VolumeX className="h-4 w-4" /> : <Volume2 className="h-4 w-4" />}
|
||||
</button>
|
||||
<input
|
||||
@@ -205,7 +185,10 @@ export const VideoRenderer = ({ src, fileName, fallbackSrc }: VideoRendererProps
|
||||
|
||||
<span className="text-[9px] font-mono text-white/30 uppercase tracking-wider">{getExt(fileName)}</span>
|
||||
|
||||
<button onClick={toggleFullscreen} className="p-1 text-white/70 hover:text-white transition-colors cursor-pointer">
|
||||
<button
|
||||
onClick={toggleFullscreen}
|
||||
className="p-1 text-white/70 hover:text-white transition-colors cursor-pointer"
|
||||
>
|
||||
{isFullscreen ? <Minimize2 className="h-4 w-4" /> : <Maximize2 className="h-4 w-4" />}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user