mirror of
https://github.com/jamiepine/voicebox.git
synced 2026-09-26 13:45:16 -07:00
Enhance story item management with track editing functionality
- Introduced StoryTrackEditor component for managing story item positions and tracks. - Updated StoriesTab to conditionally render the track editor based on selected story. - Implemented moveStoryItem API endpoint to handle item repositioning and track changes. - Enhanced story item data model to include track information. - Improved audio playback management to support multiple tracks using Web Audio API. - Added hooks for moving story items and managing playback timing.
This commit is contained in:
@@ -12,13 +12,16 @@ interface StoryPlaybackState {
|
||||
totalDurationMs: number;
|
||||
playbackStoryId: string | null;
|
||||
playbackItems: StoryItemDetail[] | null;
|
||||
// Web Audio API timing (null when not playing)
|
||||
playbackStartContextTime: number | null; // AudioContext.currentTime when playback started
|
||||
playbackStartStoryTime: number | null; // Story time (ms) when playback started
|
||||
|
||||
// Actions
|
||||
play: (storyId: string, items: StoryItemDetail[]) => void;
|
||||
pause: () => void;
|
||||
stop: () => void;
|
||||
seek: (timeMs: number) => void;
|
||||
tick: (deltaMs: number) => void; // Called by animation frame
|
||||
setPlaybackTiming: (contextTime: number, storyTime: number) => void; // Set timing anchors for Web Audio API
|
||||
}
|
||||
|
||||
export const useStoryStore = create<StoryPlaybackState>((set, get) => ({
|
||||
@@ -32,6 +35,8 @@ export const useStoryStore = create<StoryPlaybackState>((set, get) => ({
|
||||
totalDurationMs: 0,
|
||||
playbackStoryId: null,
|
||||
playbackItems: null,
|
||||
playbackStartContextTime: null,
|
||||
playbackStartStoryTime: null,
|
||||
|
||||
// Actions
|
||||
play: (storyId, items) => {
|
||||
@@ -72,7 +77,10 @@ export const useStoryStore = create<StoryPlaybackState>((set, get) => ({
|
||||
},
|
||||
|
||||
pause: () => {
|
||||
set({ isPlaying: false });
|
||||
set({
|
||||
isPlaying: false,
|
||||
// Keep timing anchors so we can resume from same position
|
||||
});
|
||||
},
|
||||
|
||||
stop: () => {
|
||||
@@ -82,32 +90,26 @@ export const useStoryStore = create<StoryPlaybackState>((set, get) => ({
|
||||
playbackStoryId: null,
|
||||
playbackItems: null,
|
||||
totalDurationMs: 0,
|
||||
playbackStartContextTime: null,
|
||||
playbackStartStoryTime: null,
|
||||
});
|
||||
},
|
||||
|
||||
seek: (timeMs) => {
|
||||
const state = get();
|
||||
const clampedTime = Math.max(0, Math.min(timeMs, state.totalDurationMs));
|
||||
set({ currentTimeMs: clampedTime });
|
||||
set({
|
||||
currentTimeMs: clampedTime,
|
||||
// Reset timing anchors - will be set by hook when playback resumes
|
||||
playbackStartContextTime: null,
|
||||
playbackStartStoryTime: null,
|
||||
});
|
||||
},
|
||||
|
||||
tick: (deltaMs) => {
|
||||
const state = get();
|
||||
if (!state.isPlaying || !state.playbackItems) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newTime = state.currentTimeMs + deltaMs;
|
||||
const clampedTime = Math.min(newTime, state.totalDurationMs);
|
||||
|
||||
// Auto-stop when reaching the end
|
||||
if (clampedTime >= state.totalDurationMs) {
|
||||
set({
|
||||
currentTimeMs: state.totalDurationMs,
|
||||
isPlaying: false,
|
||||
});
|
||||
} else {
|
||||
set({ currentTimeMs: clampedTime });
|
||||
}
|
||||
setPlaybackTiming: (contextTime, storyTime) => {
|
||||
set({
|
||||
playbackStartContextTime: contextTime,
|
||||
playbackStartStoryTime: storyTime,
|
||||
});
|
||||
},
|
||||
}));
|
||||
|
||||
Reference in New Issue
Block a user