diff options
Diffstat (limited to 'apps/web/src')
| -rw-r--r-- | apps/web/src/components/editor/preview-panel.tsx | 54 | ||||
| -rw-r--r-- | apps/web/src/components/editor/timeline.tsx | 25 | ||||
| -rw-r--r-- | apps/web/src/components/ui/video-player.tsx | 6 |
3 files changed, 49 insertions, 36 deletions
diff --git a/apps/web/src/components/editor/preview-panel.tsx b/apps/web/src/components/editor/preview-panel.tsx index 593758e1..74bf5f98 100644 --- a/apps/web/src/components/editor/preview-panel.tsx +++ b/apps/web/src/components/editor/preview-panel.tsx @@ -11,39 +11,59 @@ import { Play, Pause } from "lucide-react"; export function PreviewPanel() {
const { tracks } = useTimelineStore();
const { mediaItems } = useMediaStore();
- const { isPlaying, toggle } = usePlaybackStore();
+ const { isPlaying, toggle, currentTime } = usePlaybackStore();
- const firstClip = tracks[0]?.clips[0];
- const firstMediaItem = firstClip
- ? mediaItems.find((item) => item.id === firstClip.mediaId)
+ // Find the active clip at the current playback time
+ const getActiveClip = () => {
+ for (const track of tracks) {
+ for (const clip of track.clips) {
+ const clipStart = clip.startTime;
+ const clipEnd = clip.startTime + (clip.duration - clip.trimStart - clip.trimEnd);
+
+ if (currentTime >= clipStart && currentTime < clipEnd) {
+ return clip;
+ }
+ }
+ }
+ return null;
+ };
+
+ const activeClip = getActiveClip();
+ const activeMediaItem = activeClip
+ ? mediaItems.find((item) => item.id === activeClip.mediaId)
: null;
- const aspectRatio = firstMediaItem?.aspectRatio || 16 / 9;
+ const aspectRatio = activeMediaItem?.aspectRatio || 16 / 9;
const renderContent = () => {
- if (!firstMediaItem) {
+ if (!activeMediaItem || !activeClip) {
return (
<div className="absolute inset-0 flex items-center justify-center text-muted-foreground/50">
- Drop media to start editing
+ {tracks.length === 0 ? "Drop media to start editing" : "No clip at current time"}
</div>
);
}
- if (firstMediaItem.type === "video") {
+ // Calculate the relative time within the clip (accounting for trim)
+ const relativeTime = Math.max(0, currentTime - activeClip.startTime + activeClip.trimStart);
+
+ if (activeMediaItem.type === "video") {
return (
<VideoPlayer
- src={firstMediaItem.url}
- poster={firstMediaItem.thumbnailUrl}
+ src={activeMediaItem.url}
+ poster={activeMediaItem.thumbnailUrl}
className="w-full h-full"
+ startTime={relativeTime}
+ key={`${activeClip.id}-${activeClip.trimStart}-${activeClip.trimEnd}`}
/>
);
}
- if (firstMediaItem.type === "image") {
+ if (activeMediaItem.type === "image") {
return (
<ImageTimelineTreatment
- src={firstMediaItem.url}
- alt={firstMediaItem.name}
+ src={activeMediaItem.url}
+ alt={activeMediaItem.name}
targetAspectRatio={aspectRatio}
className="w-full h-full"
backgroundType="blur"
@@ -51,12 +71,12 @@ export function PreviewPanel() { );
}
- if (firstMediaItem.type === "audio") {
+ if (activeMediaItem.type === "audio") {
return (
<div className="absolute inset-0 flex items-center justify-center bg-gradient-to-br from-green-500/20 to-emerald-500/20">
<div className="text-center">
<div className="text-6xl mb-4">🎵</div>
- <p className="text-muted-foreground">{firstMediaItem.name}</p>
+ <p className="text-muted-foreground">{activeMediaItem.name}</p>
<Button
variant="outline"
className="mt-4"
@@ -88,10 +108,10 @@ export function PreviewPanel() { {renderContent()}
</div>
- {firstMediaItem && (
+ {activeMediaItem && (
<div className="mt-4 text-center">
<p className="text-sm text-muted-foreground">
- {firstMediaItem.name}
+ {activeMediaItem.name}
</p>
<p className="text-xs text-muted-foreground/70">
{aspectRatio.toFixed(2)} • {aspectRatio > 1 ? "Landscape" : aspectRatio < 1 ? "Portrait" : "Square"}
diff --git a/apps/web/src/components/editor/timeline.tsx b/apps/web/src/components/editor/timeline.tsx index ed9128dc..be532991 100644 --- a/apps/web/src/components/editor/timeline.tsx +++ b/apps/web/src/components/editor/timeline.tsx @@ -259,19 +259,6 @@ export function Timeline() { >
{/* Timeline Header */}
<div className="py-3 relative bg-muted/30 border-b">
- {/* Playhead */}
- {duration > 0 && (
- <div
- className="absolute top-0 bottom-0 w-0.5 bg-red-500 pointer-events-none z-10"
- style={{
- left: `${(currentTime / (duration / zoomLevel)) * 100}%`,
- transform: 'translateX(-50%)'
- }}
- >
- <div className="absolute -top-1 left-1/2 transform -translate-x-1/2 w-3 h-3 bg-red-500 rounded-full border-2 border-white shadow-sm" />
- </div>
- )}
-
{/* Zoom indicator */}
<div className="absolute top-1 right-2 text-xs text-muted-foreground">
{zoomLevel.toFixed(1)}x
@@ -301,14 +288,15 @@ export function Timeline() { )}
{/* Playhead for tracks area */}
- {tracks.length > 0 && duration > 0 && (
+ {tracks.length > 0 && (
<div
- className="absolute top-0 bottom-0 w-0.5 bg-red-500/80 pointer-events-none z-10"
+ className="absolute top-0 bottom-0 w-0.5 bg-red-500 pointer-events-none z-20"
style={{
- left: `${(currentTime / (duration / zoomLevel)) * 100}%`,
- transform: 'translateX(-50%)'
+ left: `${currentTime * 50 * zoomLevel + 128}px`,
}}
- />
+ >
+ <div className="absolute -top-8 left-1/2 transform -translate-x-1/2 w-3 h-3 bg-red-500 rounded-full border-2 border-white shadow-sm" />
+ </div>
)}
</div>
</div>
@@ -321,6 +309,7 @@ function TimelineTrackComponent({ track, zoomLevel }: { track: TimelineTrack, zo const { mediaItems } = useMediaStore();
const { moveClipToTrack, updateClipTrim, updateClipStartTime } = useTimelineStore();
const [isDropping, setIsDropping] = useState(false);
+ const [dropPosition, setDropPosition] = useState<number | null>(null);
const [resizing, setResizing] = useState<{
clipId: string;
side: 'left' | 'right';
diff --git a/apps/web/src/components/ui/video-player.tsx b/apps/web/src/components/ui/video-player.tsx index 127cc5db..d31f7113 100644 --- a/apps/web/src/components/ui/video-player.tsx +++ b/apps/web/src/components/ui/video-player.tsx @@ -9,9 +9,10 @@ interface VideoPlayerProps { src: string; poster?: string; className?: string; + startTime?: number; } -export function VideoPlayer({ src, poster, className = "" }: VideoPlayerProps) { +export function VideoPlayer({ src, poster, className = "", startTime = 0 }: VideoPlayerProps) { const videoRef = useRef<HTMLVideoElement>(null); const { isPlaying, currentTime, volume, play, pause, setVolume, setDuration, setCurrentTime } = usePlaybackStore(); @@ -25,6 +26,9 @@ export function VideoPlayer({ src, poster, className = "" }: VideoPlayerProps) { const handleLoadedMetadata = () => { setDuration(video.duration); + if (startTime > 0) { + video.currentTime = startTime; + } }; const handleSeekEvent = (e: CustomEvent) => { |
