Implemented comprehensive multi-file audio import system: - Created ImportTrackDialog component with drag-and-drop and file browser support - Updated TrackList to integrate import functionality with dedicated buttons - Added multi-track-demo page to test and demonstrate import features - Sequential file processing with automatic track naming from filenames - Error handling for non-audio files 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
128 lines
3.5 KiB
TypeScript
128 lines
3.5 KiB
TypeScript
'use client';
|
|
|
|
import * as React from 'react';
|
|
import { TrackList } from '@/components/tracks/TrackList';
|
|
import { useMultiTrack } from '@/lib/hooks/useMultiTrack';
|
|
import { useMultiTrackPlayer } from '@/lib/hooks/useMultiTrackPlayer';
|
|
import { Button } from '@/components/ui/Button';
|
|
import { Play, Pause, Square, SkipBack } from 'lucide-react';
|
|
import { formatDuration } from '@/lib/audio/decoder';
|
|
|
|
export default function MultiTrackDemoPage() {
|
|
const {
|
|
tracks,
|
|
addTrack,
|
|
addTrackFromBuffer,
|
|
removeTrack,
|
|
updateTrack,
|
|
clearTracks,
|
|
} = useMultiTrack();
|
|
|
|
const {
|
|
isPlaying,
|
|
currentTime,
|
|
duration,
|
|
play,
|
|
pause,
|
|
stop,
|
|
seek,
|
|
togglePlayPause,
|
|
} = useMultiTrackPlayer(tracks);
|
|
|
|
const [zoom, setZoom] = React.useState(1);
|
|
|
|
return (
|
|
<div className="h-screen flex flex-col bg-background">
|
|
{/* Header */}
|
|
<div className="h-14 border-b border-border bg-card flex items-center justify-between px-4">
|
|
<h1 className="text-lg font-semibold">Multi-Track Demo</h1>
|
|
<div className="flex items-center gap-2">
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
onClick={clearTracks}
|
|
disabled={tracks.length === 0}
|
|
>
|
|
Clear All Tracks
|
|
</Button>
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
onClick={() => setZoom((z) => Math.max(0.5, z - 0.5))}
|
|
>
|
|
Zoom Out
|
|
</Button>
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
onClick={() => setZoom((z) => Math.min(10, z + 0.5))}
|
|
>
|
|
Zoom In
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Track List */}
|
|
<div className="flex-1 overflow-hidden">
|
|
<TrackList
|
|
tracks={tracks}
|
|
zoom={zoom}
|
|
currentTime={currentTime}
|
|
duration={duration}
|
|
onAddTrack={() => addTrack()}
|
|
onImportTrack={(buffer, name) => addTrackFromBuffer(buffer, name)}
|
|
onRemoveTrack={removeTrack}
|
|
onUpdateTrack={updateTrack}
|
|
onSeek={seek}
|
|
/>
|
|
</div>
|
|
|
|
{/* Playback Controls */}
|
|
<div className="h-16 border-t border-border bg-card flex items-center justify-between px-4">
|
|
<div className="flex items-center gap-2">
|
|
<Button
|
|
variant="outline"
|
|
size="icon"
|
|
onClick={stop}
|
|
disabled={!duration}
|
|
title="Stop"
|
|
>
|
|
<Square className="h-4 w-4" />
|
|
</Button>
|
|
<Button
|
|
variant="outline"
|
|
size="icon"
|
|
onClick={togglePlayPause}
|
|
disabled={!duration}
|
|
title={isPlaying ? 'Pause' : 'Play'}
|
|
>
|
|
{isPlaying ? (
|
|
<Pause className="h-4 w-4" />
|
|
) : (
|
|
<Play className="h-4 w-4" />
|
|
)}
|
|
</Button>
|
|
<Button
|
|
variant="outline"
|
|
size="icon"
|
|
onClick={() => seek(0)}
|
|
disabled={!duration}
|
|
title="Skip to Start"
|
|
>
|
|
<SkipBack className="h-4 w-4" />
|
|
</Button>
|
|
</div>
|
|
|
|
<div className="flex items-center gap-4">
|
|
<span className="text-sm font-mono">
|
|
{formatDuration(currentTime)} / {formatDuration(duration)}
|
|
</span>
|
|
<span className="text-sm text-muted-foreground">
|
|
{tracks.length} {tracks.length === 1 ? 'track' : 'tracks'}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|