mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-05-11 16:22:40 +02:00
feat: render audio files with native player
This commit is contained in:
parent
b3519433eb
commit
a4cd6abb3a
2 changed files with 66 additions and 1 deletions
|
|
@ -17,6 +17,7 @@ import { HtmlFileViewer } from '@/components/html-file-viewer';
|
||||||
import { ImageFileViewer } from '@/components/image-file-viewer';
|
import { ImageFileViewer } from '@/components/image-file-viewer';
|
||||||
import { VideoFileViewer } from '@/components/video-file-viewer';
|
import { VideoFileViewer } from '@/components/video-file-viewer';
|
||||||
import { PdfFileViewer } from '@/components/pdf-file-viewer';
|
import { PdfFileViewer } from '@/components/pdf-file-viewer';
|
||||||
|
import { AudioFileViewer } from '@/components/audio-file-viewer';
|
||||||
import { useDebounce } from './hooks/use-debounce';
|
import { useDebounce } from './hooks/use-debounce';
|
||||||
import { SidebarContentPanel } from '@/components/sidebar-content';
|
import { SidebarContentPanel } from '@/components/sidebar-content';
|
||||||
import { SuggestedTopicsView } from '@/components/suggested-topics-view';
|
import { SuggestedTopicsView } from '@/components/suggested-topics-view';
|
||||||
|
|
@ -1431,7 +1432,7 @@ function App() {
|
||||||
// Media viewers (HTML, image, video, PDF) self-load via app:// protocol.
|
// Media viewers (HTML, image, video, PDF) self-load via app:// protocol.
|
||||||
// Skip the generic UTF-8 loader so we don't trash fileContent with binary
|
// Skip the generic UTF-8 loader so we don't trash fileContent with binary
|
||||||
// bytes or double-fetch large files.
|
// bytes or double-fetch large files.
|
||||||
if (/\.(html?|png|jpe?g|webp|gif|svg|avif|bmp|ico|mp4|mov|webm|m4v|pdf)$/i.test(pathToLoad)) {
|
if (/\.(html?|png|jpe?g|webp|gif|svg|avif|bmp|ico|mp4|mov|webm|m4v|pdf|mp3|wav|m4a|ogg|flac|aac)$/i.test(pathToLoad)) {
|
||||||
setFileContent('')
|
setFileContent('')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -4846,6 +4847,10 @@ function App() {
|
||||||
<div className="flex-1 min-h-0 overflow-hidden">
|
<div className="flex-1 min-h-0 overflow-hidden">
|
||||||
<PdfFileViewer path={selectedPath} />
|
<PdfFileViewer path={selectedPath} />
|
||||||
</div>
|
</div>
|
||||||
|
) : selectedPath && /\.(mp3|wav|m4a|ogg|flac|aac)$/i.test(selectedPath) ? (
|
||||||
|
<div className="flex-1 min-h-0 overflow-hidden">
|
||||||
|
<AudioFileViewer path={selectedPath} />
|
||||||
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex-1 overflow-auto p-4">
|
<div className="flex-1 overflow-auto p-4">
|
||||||
<pre className="text-sm font-mono text-foreground whitespace-pre-wrap">
|
<pre className="text-sm font-mono text-foreground whitespace-pre-wrap">
|
||||||
|
|
|
||||||
60
apps/x/apps/renderer/src/components/audio-file-viewer.tsx
Normal file
60
apps/x/apps/renderer/src/components/audio-file-viewer.tsx
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import { ExternalLinkIcon, FileAudioIcon } from 'lucide-react'
|
||||||
|
|
||||||
|
interface AudioFileViewerProps {
|
||||||
|
path: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type State = 'loading' | 'ready' | 'error'
|
||||||
|
|
||||||
|
function basename(path: string): string {
|
||||||
|
const idx = path.lastIndexOf('/')
|
||||||
|
return idx >= 0 ? path.slice(idx + 1) : path
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AudioFileViewer({ path }: AudioFileViewerProps) {
|
||||||
|
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">
|
||||||
|
<FileAudioIcon className="size-6" />
|
||||||
|
<p className="text-sm font-medium text-foreground">Cannot play this audio file</p>
|
||||||
|
<p className="max-w-md text-xs">The codec or container format isn't supported.</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 flex-col items-center justify-center gap-4 bg-muted/30 px-6">
|
||||||
|
<FileAudioIcon className="size-10 text-muted-foreground" />
|
||||||
|
<p className="max-w-md truncate text-sm font-medium text-foreground" title={path}>
|
||||||
|
{basename(path)}
|
||||||
|
</p>
|
||||||
|
<audio
|
||||||
|
key={path}
|
||||||
|
src={src}
|
||||||
|
controls
|
||||||
|
className="w-full max-w-lg"
|
||||||
|
onLoadedMetadata={() => setState('ready')}
|
||||||
|
onError={() => setState('error')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue