fix: video player properly reflecting the edited timeline, video start

This commit is contained in:
Hyteq
2025-06-23 09:35:32 +03:00
parent 68864c1442
commit f688c7ef5d
3 changed files with 49 additions and 36 deletions

View File

@ -11,39 +11,59 @@ import { Play, Pause } from "lucide-react";
export function PreviewPanel() { export function PreviewPanel() {
const { tracks } = useTimelineStore(); const { tracks } = useTimelineStore();
const { mediaItems } = useMediaStore(); const { mediaItems } = useMediaStore();
const { isPlaying, toggle } = usePlaybackStore(); const { isPlaying, toggle, currentTime } = usePlaybackStore();
const firstClip = tracks[0]?.clips[0]; // Find the active clip at the current playback time
const firstMediaItem = firstClip const getActiveClip = () => {
? mediaItems.find((item) => item.id === firstClip.mediaId) 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; : null;
const aspectRatio = firstMediaItem?.aspectRatio || 16 / 9; const aspectRatio = activeMediaItem?.aspectRatio || 16 / 9;
const renderContent = () => { const renderContent = () => {
if (!firstMediaItem) { if (!activeMediaItem || !activeClip) {
return ( return (
<div className="absolute inset-0 flex items-center justify-center text-muted-foreground/50"> <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> </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 ( return (
<VideoPlayer <VideoPlayer
src={firstMediaItem.url} src={activeMediaItem.url}
poster={firstMediaItem.thumbnailUrl} poster={activeMediaItem.thumbnailUrl}
className="w-full h-full" className="w-full h-full"
startTime={relativeTime}
key={`${activeClip.id}-${activeClip.trimStart}-${activeClip.trimEnd}`}
/> />
); );
} }
if (firstMediaItem.type === "image") { if (activeMediaItem.type === "image") {
return ( return (
<ImageTimelineTreatment <ImageTimelineTreatment
src={firstMediaItem.url} src={activeMediaItem.url}
alt={firstMediaItem.name} alt={activeMediaItem.name}
targetAspectRatio={aspectRatio} targetAspectRatio={aspectRatio}
className="w-full h-full" className="w-full h-full"
backgroundType="blur" backgroundType="blur"
@ -51,12 +71,12 @@ export function PreviewPanel() {
); );
} }
if (firstMediaItem.type === "audio") { if (activeMediaItem.type === "audio") {
return ( 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="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-center">
<div className="text-6xl mb-4">🎵</div> <div className="text-6xl mb-4">🎵</div>
<p className="text-muted-foreground">{firstMediaItem.name}</p> <p className="text-muted-foreground">{activeMediaItem.name}</p>
<Button <Button
variant="outline" variant="outline"
className="mt-4" className="mt-4"
@ -88,10 +108,10 @@ export function PreviewPanel() {
{renderContent()} {renderContent()}
</div> </div>
{firstMediaItem && ( {activeMediaItem && (
<div className="mt-4 text-center"> <div className="mt-4 text-center">
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">
{firstMediaItem.name} {activeMediaItem.name}
</p> </p>
<p className="text-xs text-muted-foreground/70"> <p className="text-xs text-muted-foreground/70">
{aspectRatio.toFixed(2)} {aspectRatio > 1 ? "Landscape" : aspectRatio < 1 ? "Portrait" : "Square"} {aspectRatio.toFixed(2)} {aspectRatio > 1 ? "Landscape" : aspectRatio < 1 ? "Portrait" : "Square"}

View File

@ -259,19 +259,6 @@ export function Timeline() {
> >
{/* Timeline Header */} {/* Timeline Header */}
<div className="py-3 relative bg-muted/30 border-b"> <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 */} {/* Zoom indicator */}
<div className="absolute top-1 right-2 text-xs text-muted-foreground"> <div className="absolute top-1 right-2 text-xs text-muted-foreground">
{zoomLevel.toFixed(1)}x {zoomLevel.toFixed(1)}x
@ -301,14 +288,15 @@ export function Timeline() {
)} )}
{/* Playhead for tracks area */} {/* Playhead for tracks area */}
{tracks.length > 0 && duration > 0 && ( {tracks.length > 0 && (
<div <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={{ style={{
left: `${(currentTime / (duration / zoomLevel)) * 100}%`, left: `${currentTime * 50 * zoomLevel + 128}px`,
transform: 'translateX(-50%)'
}} }}
/> >
<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>
</div> </div>
@ -321,6 +309,7 @@ function TimelineTrackComponent({ track, zoomLevel }: { track: TimelineTrack, zo
const { mediaItems } = useMediaStore(); const { mediaItems } = useMediaStore();
const { moveClipToTrack, updateClipTrim, updateClipStartTime } = useTimelineStore(); const { moveClipToTrack, updateClipTrim, updateClipStartTime } = useTimelineStore();
const [isDropping, setIsDropping] = useState(false); const [isDropping, setIsDropping] = useState(false);
const [dropPosition, setDropPosition] = useState<number | null>(null);
const [resizing, setResizing] = useState<{ const [resizing, setResizing] = useState<{
clipId: string; clipId: string;
side: 'left' | 'right'; side: 'left' | 'right';

View File

@ -9,9 +9,10 @@ interface VideoPlayerProps {
src: string; src: string;
poster?: string; poster?: string;
className?: 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 videoRef = useRef<HTMLVideoElement>(null);
const { isPlaying, currentTime, volume, play, pause, setVolume, setDuration, setCurrentTime } = usePlaybackStore(); const { isPlaying, currentTime, volume, play, pause, setVolume, setDuration, setCurrentTime } = usePlaybackStore();
@ -25,6 +26,9 @@ export function VideoPlayer({ src, poster, className = "" }: VideoPlayerProps) {
const handleLoadedMetadata = () => { const handleLoadedMetadata = () => {
setDuration(video.duration); setDuration(video.duration);
if (startTime > 0) {
video.currentTime = startTime;
}
}; };
const handleSeekEvent = (e: CustomEvent) => { const handleSeekEvent = (e: CustomEvent) => {