mirror of
https://github.com/jamiepine/voicebox.git
synced 2026-09-29 15:15:27 -07:00
Implement auto-activation of stories in StoryTrackEditor and improve playback state management
- Added useEffect to automatically activate the story when the editor is shown, ensuring the playhead is visible. - Introduced setActiveStory function in storyStore to manage story activation without playback. - Updated playback state checks to reflect the current playing status accurately. - Enhanced UI to always display the playhead for better user experience during playback.
This commit is contained in:
@@ -139,16 +139,32 @@ export function StoryTrackEditor({ storyId, items }: StoryTrackEditorProps) {
|
|||||||
// Playback state
|
// Playback state
|
||||||
const isPlaying = useStoryStore((state) => state.isPlaying);
|
const isPlaying = useStoryStore((state) => state.isPlaying);
|
||||||
const currentTimeMs = useStoryStore((state) => state.currentTimeMs);
|
const currentTimeMs = useStoryStore((state) => state.currentTimeMs);
|
||||||
const storeTotalDurationMs = useStoryStore((state) => state.totalDurationMs);
|
|
||||||
const playbackStoryId = useStoryStore((state) => state.playbackStoryId);
|
const playbackStoryId = useStoryStore((state) => state.playbackStoryId);
|
||||||
const play = useStoryStore((state) => state.play);
|
const play = useStoryStore((state) => state.play);
|
||||||
const pause = useStoryStore((state) => state.pause);
|
const pause = useStoryStore((state) => state.pause);
|
||||||
const stop = useStoryStore((state) => state.stop);
|
const stop = useStoryStore((state) => state.stop);
|
||||||
const seek = useStoryStore((state) => state.seek);
|
const seek = useStoryStore((state) => state.seek);
|
||||||
|
const setActiveStory = useStoryStore((state) => state.setActiveStory);
|
||||||
|
|
||||||
const isActiveStory = playbackStoryId === storyId;
|
const isActiveStory = playbackStoryId === storyId;
|
||||||
const isCurrentlyPlaying = isPlaying && isActiveStory;
|
const isCurrentlyPlaying = isPlaying && isActiveStory;
|
||||||
|
|
||||||
|
// Auto-activate this story when the editor is shown so playhead is visible
|
||||||
|
useEffect(() => {
|
||||||
|
if (items.length > 0 && !isActiveStory) {
|
||||||
|
const totalDuration = Math.max(
|
||||||
|
...items.map((item) => {
|
||||||
|
const trimStart = item.trim_start_ms || 0;
|
||||||
|
const trimEnd = item.trim_end_ms || 0;
|
||||||
|
const effectiveDuration = (item.duration * 1000) - trimStart - trimEnd;
|
||||||
|
return item.start_time_ms + effectiveDuration;
|
||||||
|
}),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
setActiveStory(storyId, items, totalDuration);
|
||||||
|
}
|
||||||
|
}, [storyId, items, isActiveStory, setActiveStory]);
|
||||||
|
|
||||||
// Sort items by start time for play
|
// Sort items by start time for play
|
||||||
const sortedItems = useMemo(() => {
|
const sortedItems = useMemo(() => {
|
||||||
return [...items].sort((a, b) => a.start_time_ms - b.start_time_ms);
|
return [...items].sort((a, b) => a.start_time_ms - b.start_time_ms);
|
||||||
@@ -656,11 +672,11 @@ export function StoryTrackEditor({ storyId, items }: StoryTrackEditorProps) {
|
|||||||
<Play className="h-4 w-4" />
|
<Play className="h-4 w-4" />
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="ghost" size="icon" className="h-7 w-7" onClick={handleStop} disabled={!isActiveStory}>
|
<Button variant="ghost" size="icon" className="h-7 w-7" onClick={handleStop} disabled={!isCurrentlyPlaying}>
|
||||||
<Square className="h-3 w-3" />
|
<Square className="h-3 w-3" />
|
||||||
</Button>
|
</Button>
|
||||||
<span className="text-xs text-muted-foreground tabular-nums ml-2">
|
<span className="text-xs text-muted-foreground tabular-nums ml-2">
|
||||||
{formatTime(isActiveStory ? currentTimeMs : 0)} / {formatTime(isActiveStory ? storeTotalDurationMs : 0)}
|
{formatTime(currentTimeMs)} / {formatTime(totalDurationMs)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -875,15 +891,13 @@ export function StoryTrackEditor({ storyId, items }: StoryTrackEditorProps) {
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
||||||
{/* Playhead */}
|
{/* Playhead - always visible */}
|
||||||
{isActiveStory && (
|
<div
|
||||||
<div
|
className="absolute top-0 bottom-0 w-1 bg-accent z-30 pointer-events-none rounded-full"
|
||||||
className="absolute top-0 bottom-0 w-1 bg-accent z-30 pointer-events-none rounded-full"
|
style={{ left: `${playheadLeft}px` }}
|
||||||
style={{ left: `${playheadLeft}px` }}
|
>
|
||||||
>
|
<div className="absolute -top-1 left-1/2 -translate-x-1/2 w-3 h-3 bg-accent rounded-full" />
|
||||||
<div className="absolute -top-1 left-1/2 -translate-x-1/2 w-3 h-3 bg-accent rounded-full" />
|
</div>
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ interface StoryPlaybackState {
|
|||||||
stop: () => void;
|
stop: () => void;
|
||||||
seek: (timeMs: number) => void;
|
seek: (timeMs: number) => void;
|
||||||
setPlaybackTiming: (contextTime: number, storyTime: number) => void; // Set timing anchors for Web Audio API
|
setPlaybackTiming: (contextTime: number, storyTime: number) => void; // Set timing anchors for Web Audio API
|
||||||
|
setActiveStory: (storyId: string, items: StoryItemDetail[], totalDurationMs: number) => void; // Activate story for seeking without playing
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_TRACK_EDITOR_HEIGHT = 250;
|
const DEFAULT_TRACK_EDITOR_HEIGHT = 250;
|
||||||
@@ -126,4 +127,18 @@ export const useStoryStore = create<StoryPlaybackState>((set, get) => ({
|
|||||||
playbackStartStoryTime: storyTime,
|
playbackStartStoryTime: storyTime,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
setActiveStory: (storyId, items, totalDurationMs) => {
|
||||||
|
const currentState = get();
|
||||||
|
// Only update if switching to a different story
|
||||||
|
if (currentState.playbackStoryId !== storyId) {
|
||||||
|
set({
|
||||||
|
playbackStoryId: storyId,
|
||||||
|
playbackItems: items,
|
||||||
|
totalDurationMs,
|
||||||
|
currentTimeMs: 0,
|
||||||
|
isPlaying: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
}));
|
}));
|
||||||
|
|||||||
Reference in New Issue
Block a user