mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-05-11 00:02:38 +02:00
feat: render video files with native controls and seeking
This commit is contained in:
parent
0d9cf71947
commit
b24113b78e
2 changed files with 58 additions and 0 deletions
|
|
@ -15,6 +15,7 @@ import { GraphView, type GraphEdge, type GraphNode } from '@/components/graph-vi
|
|||
import { BasesView, type BaseConfig, DEFAULT_BASE_CONFIG } from '@/components/bases-view';
|
||||
import { HtmlFileViewer } from '@/components/html-file-viewer';
|
||||
import { ImageFileViewer } from '@/components/image-file-viewer';
|
||||
import { VideoFileViewer } from '@/components/video-file-viewer';
|
||||
import { useDebounce } from './hooks/use-debounce';
|
||||
import { SidebarContentPanel } from '@/components/sidebar-content';
|
||||
import { SuggestedTopicsView } from '@/components/suggested-topics-view';
|
||||
|
|
@ -4836,6 +4837,10 @@ function App() {
|
|||
<div className="flex-1 min-h-0 overflow-hidden">
|
||||
<ImageFileViewer path={selectedPath} />
|
||||
</div>
|
||||
) : selectedPath && /\.(mp4|mov|webm|m4v)$/i.test(selectedPath) ? (
|
||||
<div className="flex-1 min-h-0 overflow-hidden">
|
||||
<VideoFileViewer path={selectedPath} />
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex-1 overflow-auto p-4">
|
||||
<pre className="text-sm font-mono text-foreground whitespace-pre-wrap">
|
||||
|
|
|
|||
53
apps/x/apps/renderer/src/components/video-file-viewer.tsx
Normal file
53
apps/x/apps/renderer/src/components/video-file-viewer.tsx
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
import { useEffect, useState } from 'react'
|
||||
import { ExternalLinkIcon, FileVideoIcon } from 'lucide-react'
|
||||
|
||||
interface VideoFileViewerProps {
|
||||
path: string
|
||||
}
|
||||
|
||||
type State = 'loading' | 'ready' | 'error'
|
||||
|
||||
export function VideoFileViewer({ path }: VideoFileViewerProps) {
|
||||
const [state, setState] = useState<State>('loading')
|
||||
|
||||
useEffect(() => {
|
||||
setState('loading')
|
||||
}, [path])
|
||||
|
||||
const src = `app://workspace/${path.split('/').map(encodeURIComponent).join('/')}`
|
||||
|
||||
if (state === 'error') {
|
||||
return (
|
||||
<div className="flex h-full w-full flex-col items-center justify-center gap-3 px-6 text-center text-muted-foreground">
|
||||
<FileVideoIcon className="size-6" />
|
||||
<p className="text-sm font-medium text-foreground">Cannot play this video</p>
|
||||
<p className="max-w-md text-xs">
|
||||
The codec or container format isn't supported by Chromium (e.g. WMV, AVI, or some MKV files).
|
||||
</p>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
void window.ipc.invoke('shell:openPath', { path })
|
||||
}}
|
||||
className="inline-flex items-center gap-1.5 rounded-md border border-border bg-background px-3 py-1.5 text-xs font-medium text-foreground hover:bg-accent"
|
||||
>
|
||||
<ExternalLinkIcon className="size-3.5" />
|
||||
Open in system
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-full items-center justify-center bg-black">
|
||||
<video
|
||||
key={path}
|
||||
src={src}
|
||||
controls
|
||||
className="max-h-full max-w-full"
|
||||
onLoadedMetadata={() => setState('ready')}
|
||||
onError={() => setState('error')}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue