@bendyline/squisq-react 0.1.2 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +96 -0
- package/dist/BlockRenderer.d.ts +37 -0
- package/dist/BlockRenderer.d.ts.map +1 -0
- package/dist/BlockRenderer.js +51 -0
- package/dist/BlockRenderer.js.map +1 -0
- package/dist/CaptionOverlay.d.ts +24 -0
- package/dist/CaptionOverlay.d.ts.map +1 -0
- package/dist/CaptionOverlay.js +40 -0
- package/dist/CaptionOverlay.js.map +1 -0
- package/dist/DocControlsBottom.d.ts +28 -0
- package/dist/DocControlsBottom.d.ts.map +1 -0
- package/dist/DocControlsBottom.js +23 -0
- package/dist/DocControlsBottom.js.map +1 -0
- package/dist/DocControlsOverlay.d.ts +28 -0
- package/dist/DocControlsOverlay.d.ts.map +1 -0
- package/dist/DocControlsOverlay.js +75 -0
- package/dist/DocControlsOverlay.js.map +1 -0
- package/dist/DocControlsSidebar.d.ts +27 -0
- package/dist/DocControlsSidebar.d.ts.map +1 -0
- package/dist/DocControlsSidebar.js +6 -0
- package/dist/DocControlsSidebar.js.map +1 -0
- package/dist/DocControlsSlideshow.d.ts +20 -0
- package/dist/DocControlsSlideshow.d.ts.map +1 -0
- package/dist/DocControlsSlideshow.js +69 -0
- package/dist/DocControlsSlideshow.js.map +1 -0
- package/dist/DocPlayer.d.ts +87 -0
- package/dist/DocPlayer.d.ts.map +1 -0
- package/dist/DocPlayer.js +604 -0
- package/dist/DocPlayer.js.map +1 -0
- package/dist/DocPlayerWithSidebar.d.ts +41 -0
- package/dist/DocPlayerWithSidebar.d.ts.map +1 -0
- package/dist/DocPlayerWithSidebar.js +68 -0
- package/dist/DocPlayerWithSidebar.js.map +1 -0
- package/dist/DocProgressBar.d.ts +31 -0
- package/dist/DocProgressBar.d.ts.map +1 -0
- package/dist/DocProgressBar.js +122 -0
- package/dist/DocProgressBar.js.map +1 -0
- package/dist/LinearDocView.d.ts +47 -0
- package/dist/LinearDocView.d.ts.map +1 -0
- package/dist/LinearDocView.js +184 -0
- package/dist/LinearDocView.js.map +1 -0
- package/dist/MarkdownRenderer.d.ts +32 -0
- package/dist/MarkdownRenderer.d.ts.map +1 -0
- package/dist/MarkdownRenderer.js +133 -0
- package/dist/MarkdownRenderer.js.map +1 -0
- package/dist/__tests__/BlockRenderer.test.d.ts +2 -0
- package/dist/__tests__/BlockRenderer.test.d.ts.map +1 -0
- package/dist/__tests__/BlockRenderer.test.js +82 -0
- package/dist/__tests__/BlockRenderer.test.js.map +1 -0
- package/dist/__tests__/DocControlsSlideshow.test.d.ts +2 -0
- package/dist/__tests__/DocControlsSlideshow.test.d.ts.map +1 -0
- package/dist/__tests__/DocControlsSlideshow.test.js +88 -0
- package/dist/__tests__/DocControlsSlideshow.test.js.map +1 -0
- package/dist/__tests__/LinearDocView.test.d.ts +2 -0
- package/dist/__tests__/LinearDocView.test.d.ts.map +1 -0
- package/dist/__tests__/LinearDocView.test.js +160 -0
- package/dist/__tests__/LinearDocView.test.js.map +1 -0
- package/dist/__tests__/MarkdownRenderer.test.d.ts +2 -0
- package/dist/__tests__/MarkdownRenderer.test.d.ts.map +1 -0
- package/dist/__tests__/MarkdownRenderer.test.js +208 -0
- package/dist/__tests__/MarkdownRenderer.test.js.map +1 -0
- package/dist/__tests__/exports.test.d.ts +2 -0
- package/dist/__tests__/exports.test.d.ts.map +1 -0
- package/dist/__tests__/exports.test.js +48 -0
- package/dist/__tests__/exports.test.js.map +1 -0
- package/dist/hooks/AudioProvider.d.ts +71 -0
- package/dist/hooks/AudioProvider.d.ts.map +1 -0
- package/dist/hooks/AudioProvider.js +52 -0
- package/dist/hooks/AudioProvider.js.map +1 -0
- package/dist/hooks/MediaContext.d.ts +35 -0
- package/dist/hooks/MediaContext.d.ts.map +1 -0
- package/dist/hooks/MediaContext.js +66 -0
- package/dist/hooks/MediaContext.js.map +1 -0
- package/dist/hooks/index.d.ts +6 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +5 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/useAudioSync.d.ts +18 -0
- package/dist/hooks/useAudioSync.d.ts.map +1 -0
- package/dist/hooks/useAudioSync.js +338 -0
- package/dist/hooks/useAudioSync.js.map +1 -0
- package/dist/hooks/useDocPlayback.d.ts +47 -0
- package/dist/hooks/useDocPlayback.d.ts.map +1 -0
- package/dist/hooks/useDocPlayback.js +176 -0
- package/dist/hooks/useDocPlayback.js.map +1 -0
- package/dist/hooks/useViewportOrientation.d.ts +31 -0
- package/dist/hooks/useViewportOrientation.d.ts.map +1 -0
- package/dist/hooks/useViewportOrientation.js +92 -0
- package/dist/hooks/useViewportOrientation.js.map +1 -0
- package/dist/index.d.ts +25 -563
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +26 -3179
- package/dist/index.js.map +1 -1
- package/dist/layers/ImageLayer.d.ts +23 -0
- package/dist/layers/ImageLayer.d.ts.map +1 -0
- package/dist/layers/ImageLayer.js +104 -0
- package/dist/layers/ImageLayer.js.map +1 -0
- package/dist/layers/MapLayer.d.ts +29 -0
- package/dist/layers/MapLayer.d.ts.map +1 -0
- package/dist/layers/MapLayer.js +97 -0
- package/dist/layers/MapLayer.js.map +1 -0
- package/dist/layers/ShapeLayer.d.ts +20 -0
- package/dist/layers/ShapeLayer.d.ts.map +1 -0
- package/dist/layers/ShapeLayer.js +35 -0
- package/dist/layers/ShapeLayer.js.map +1 -0
- package/dist/layers/TextLayer.d.ts +23 -0
- package/dist/layers/TextLayer.d.ts.map +1 -0
- package/dist/layers/TextLayer.js +126 -0
- package/dist/layers/TextLayer.js.map +1 -0
- package/dist/layers/VideoLayer.d.ts +40 -0
- package/dist/layers/VideoLayer.d.ts.map +1 -0
- package/dist/layers/VideoLayer.js +105 -0
- package/dist/layers/VideoLayer.js.map +1 -0
- package/dist/layers/index.d.ts +6 -0
- package/dist/layers/index.d.ts.map +1 -0
- package/dist/layers/index.js +6 -0
- package/dist/layers/index.js.map +1 -0
- package/dist/squisq-player.global.js +1 -1
- package/dist/standalone-entry.d.ts +61 -0
- package/dist/standalone-entry.d.ts.map +1 -0
- package/dist/standalone-entry.js +169 -0
- package/dist/standalone-entry.js.map +1 -0
- package/dist/standalone-source.js +1 -1
- package/dist/types.d.ts +131 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +21 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/animationUtils.d.ts +8 -0
- package/dist/utils/animationUtils.d.ts.map +1 -0
- package/dist/utils/animationUtils.js +8 -0
- package/dist/utils/animationUtils.js.map +1 -0
- package/dist/utils/layerUtils.d.ts +17 -0
- package/dist/utils/layerUtils.d.ts.map +1 -0
- package/dist/utils/layerUtils.js +37 -0
- package/dist/utils/layerUtils.js.map +1 -0
- package/dist/utils/mapTileUtils.d.ts +105 -0
- package/dist/utils/mapTileUtils.d.ts.map +1 -0
- package/dist/utils/mapTileUtils.js +268 -0
- package/dist/utils/mapTileUtils.js.map +1 -0
- package/package.json +5 -6
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useAudioSync Hook
|
|
3
|
+
*
|
|
4
|
+
* Synchronizes playback state with an audio element. Provides current
|
|
5
|
+
* playback time, playing state, and methods to control audio playback.
|
|
6
|
+
*
|
|
7
|
+
* Handles multiple audio segments (MP3 files) by tracking which segment
|
|
8
|
+
* is currently playing and calculating the overall timeline position.
|
|
9
|
+
*
|
|
10
|
+
* This is the HTML5 Audio implementation of the AudioProvider interface.
|
|
11
|
+
* For EFB/MSFS environments, use useCompanionAudioSync instead.
|
|
12
|
+
*/
|
|
13
|
+
import { useState, useEffect, useRef, useCallback } from 'react';
|
|
14
|
+
export function useAudioSync(audioRef, audioTrack, basePath = '') {
|
|
15
|
+
const [currentTime, setCurrentTime] = useState(0);
|
|
16
|
+
const [isPlaying, setIsPlaying] = useState(false);
|
|
17
|
+
const [currentSegment, setCurrentSegment] = useState(0);
|
|
18
|
+
const [isEnded, setIsEnded] = useState(false);
|
|
19
|
+
const [isAudioReady, setIsAudioReady] = useState(false);
|
|
20
|
+
const [totalDuration, setTotalDuration] = useState(0);
|
|
21
|
+
// Calculate segment start times
|
|
22
|
+
const segmentStarts = useRef([]);
|
|
23
|
+
// Pending seek time (used when switching segments)
|
|
24
|
+
const pendingSeekTime = useRef(null);
|
|
25
|
+
const shouldPlayAfterLoad = useRef(false);
|
|
26
|
+
// Preloaded audio blob URLs (for seeking without range request support)
|
|
27
|
+
const blobUrls = useRef(new Map());
|
|
28
|
+
const loadingPromises = useRef(new Map());
|
|
29
|
+
// Fallback timer: when audio.play() is blocked (e.g., autoplay policy),
|
|
30
|
+
// advance currentTime synthetically so blocks still progress without audio.
|
|
31
|
+
const fallbackMode = useRef(false);
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
if (!audioTrack?.segments) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
let time = 0;
|
|
37
|
+
segmentStarts.current = audioTrack.segments.map((seg) => {
|
|
38
|
+
const start = time;
|
|
39
|
+
time += seg.duration;
|
|
40
|
+
return start;
|
|
41
|
+
});
|
|
42
|
+
setTotalDuration(time);
|
|
43
|
+
}, [audioTrack]);
|
|
44
|
+
// Preload audio file as blob (enables seeking without range request support)
|
|
45
|
+
const preloadAudio = useCallback(async (src) => {
|
|
46
|
+
const audioUrl = basePath ? `${basePath}/${src}` : src;
|
|
47
|
+
// Return cached blob URL if available
|
|
48
|
+
if (blobUrls.current.has(src)) {
|
|
49
|
+
return blobUrls.current.get(src);
|
|
50
|
+
}
|
|
51
|
+
// Return existing loading promise if in progress
|
|
52
|
+
if (loadingPromises.current.has(src)) {
|
|
53
|
+
return loadingPromises.current.get(src);
|
|
54
|
+
}
|
|
55
|
+
// Start loading
|
|
56
|
+
const loadPromise = (async () => {
|
|
57
|
+
try {
|
|
58
|
+
const response = await fetch(audioUrl);
|
|
59
|
+
if (!response.ok)
|
|
60
|
+
throw new Error(`HTTP ${response.status}`);
|
|
61
|
+
const blob = await response.blob();
|
|
62
|
+
const blobUrl = URL.createObjectURL(blob);
|
|
63
|
+
blobUrls.current.set(src, blobUrl);
|
|
64
|
+
return blobUrl;
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
// Fall back to direct URL if blob loading fails
|
|
68
|
+
return audioUrl;
|
|
69
|
+
}
|
|
70
|
+
finally {
|
|
71
|
+
loadingPromises.current.delete(src);
|
|
72
|
+
}
|
|
73
|
+
})();
|
|
74
|
+
loadingPromises.current.set(src, loadPromise);
|
|
75
|
+
return loadPromise;
|
|
76
|
+
}, [basePath]);
|
|
77
|
+
// Preload all audio segments on mount
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
if (!audioTrack?.segments)
|
|
80
|
+
return;
|
|
81
|
+
// Preload all segments in parallel
|
|
82
|
+
audioTrack.segments.forEach((segment) => {
|
|
83
|
+
preloadAudio(segment.src);
|
|
84
|
+
});
|
|
85
|
+
// Cleanup blob URLs on unmount
|
|
86
|
+
const currentBlobUrls = blobUrls.current;
|
|
87
|
+
return () => {
|
|
88
|
+
currentBlobUrls.forEach((url) => {
|
|
89
|
+
URL.revokeObjectURL(url);
|
|
90
|
+
});
|
|
91
|
+
currentBlobUrls.clear();
|
|
92
|
+
};
|
|
93
|
+
}, [audioTrack, preloadAudio]);
|
|
94
|
+
// Handle audio time updates
|
|
95
|
+
useEffect(() => {
|
|
96
|
+
const audio = audioRef.current;
|
|
97
|
+
if (!audio)
|
|
98
|
+
return;
|
|
99
|
+
const handleTimeUpdate = () => {
|
|
100
|
+
// Calculate overall timeline position
|
|
101
|
+
const segmentStart = segmentStarts.current[currentSegment] || 0;
|
|
102
|
+
const overallTime = segmentStart + audio.currentTime;
|
|
103
|
+
setCurrentTime(overallTime);
|
|
104
|
+
};
|
|
105
|
+
const handlePlay = () => {
|
|
106
|
+
fallbackMode.current = false;
|
|
107
|
+
setIsPlaying(true);
|
|
108
|
+
};
|
|
109
|
+
const handlePause = () => setIsPlaying(false);
|
|
110
|
+
const handleError = () => {
|
|
111
|
+
// Audio source failed to load (e.g., 404 in CI or missing files).
|
|
112
|
+
// Set ready so the UI can still render controls and progress.
|
|
113
|
+
setIsAudioReady(true);
|
|
114
|
+
};
|
|
115
|
+
const handleEnded = () => {
|
|
116
|
+
// Move to next segment or end
|
|
117
|
+
if (audioTrack && currentSegment < audioTrack.segments.length - 1) {
|
|
118
|
+
// Auto-advance to next segment
|
|
119
|
+
// Set shouldPlayAfterLoad so the next segment auto-plays after loading
|
|
120
|
+
shouldPlayAfterLoad.current = true;
|
|
121
|
+
setCurrentSegment((prev) => prev + 1);
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
setIsEnded(true);
|
|
125
|
+
setIsPlaying(false);
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
audio.addEventListener('timeupdate', handleTimeUpdate);
|
|
129
|
+
audio.addEventListener('play', handlePlay);
|
|
130
|
+
audio.addEventListener('pause', handlePause);
|
|
131
|
+
audio.addEventListener('ended', handleEnded);
|
|
132
|
+
audio.addEventListener('error', handleError);
|
|
133
|
+
return () => {
|
|
134
|
+
audio.removeEventListener('timeupdate', handleTimeUpdate);
|
|
135
|
+
audio.removeEventListener('play', handlePlay);
|
|
136
|
+
audio.removeEventListener('pause', handlePause);
|
|
137
|
+
audio.removeEventListener('ended', handleEnded);
|
|
138
|
+
audio.removeEventListener('error', handleError);
|
|
139
|
+
};
|
|
140
|
+
}, [audioRef, currentSegment, audioTrack]);
|
|
141
|
+
// Load new segment when currentSegment changes
|
|
142
|
+
useEffect(() => {
|
|
143
|
+
const audio = audioRef.current;
|
|
144
|
+
if (!audio || !audioTrack?.segments)
|
|
145
|
+
return;
|
|
146
|
+
const segment = audioTrack.segments[currentSegment];
|
|
147
|
+
if (!segment)
|
|
148
|
+
return;
|
|
149
|
+
// Apply pending seek and play state
|
|
150
|
+
const applyPendingSeek = () => {
|
|
151
|
+
if (pendingSeekTime.current !== null) {
|
|
152
|
+
const segmentStart = segmentStarts.current[currentSegment] || 0;
|
|
153
|
+
const segmentTime = pendingSeekTime.current - segmentStart;
|
|
154
|
+
audio.currentTime = Math.max(0, segmentTime);
|
|
155
|
+
setCurrentTime(pendingSeekTime.current);
|
|
156
|
+
pendingSeekTime.current = null;
|
|
157
|
+
}
|
|
158
|
+
if (shouldPlayAfterLoad.current) {
|
|
159
|
+
audio.play().catch(() => { });
|
|
160
|
+
shouldPlayAfterLoad.current = false;
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
// Check if we're already on this source (avoid unnecessary reload)
|
|
164
|
+
// For blob URLs, check by segment src key
|
|
165
|
+
const currentSrc = audio.src;
|
|
166
|
+
const cachedBlobUrl = blobUrls.current.get(segment.src);
|
|
167
|
+
const isSameSource = currentSrc && (currentSrc === cachedBlobUrl || currentSrc.endsWith(segment.src));
|
|
168
|
+
if (!isSameSource) {
|
|
169
|
+
// Need to load new source - use preloaded blob URL
|
|
170
|
+
const loadAndPlay = async () => {
|
|
171
|
+
const blobUrl = await preloadAudio(segment.src);
|
|
172
|
+
const handleCanPlay = () => {
|
|
173
|
+
setIsAudioReady(true);
|
|
174
|
+
applyPendingSeek();
|
|
175
|
+
audio.removeEventListener('canplay', handleCanPlay);
|
|
176
|
+
};
|
|
177
|
+
audio.addEventListener('canplay', handleCanPlay);
|
|
178
|
+
audio.src = blobUrl;
|
|
179
|
+
audio.load();
|
|
180
|
+
// If audio is already ready (blob is instant), canplay might not fire
|
|
181
|
+
// Check after a microtask to see if it's ready
|
|
182
|
+
await Promise.resolve();
|
|
183
|
+
if (audio.readyState >= 3) {
|
|
184
|
+
audio.removeEventListener('canplay', handleCanPlay);
|
|
185
|
+
setIsAudioReady(true);
|
|
186
|
+
applyPendingSeek();
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
loadAndPlay();
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
// Same source - apply seek directly
|
|
193
|
+
applyPendingSeek();
|
|
194
|
+
}
|
|
195
|
+
}, [audioRef, currentSegment, audioTrack, preloadAudio]);
|
|
196
|
+
const play = useCallback(() => {
|
|
197
|
+
const audio = audioRef.current;
|
|
198
|
+
if (audio) {
|
|
199
|
+
if (isEnded) {
|
|
200
|
+
// Restart from beginning
|
|
201
|
+
setCurrentSegment(0);
|
|
202
|
+
setIsEnded(false);
|
|
203
|
+
}
|
|
204
|
+
audio
|
|
205
|
+
.play()
|
|
206
|
+
.then(() => {
|
|
207
|
+
fallbackMode.current = false;
|
|
208
|
+
})
|
|
209
|
+
.catch(() => {
|
|
210
|
+
// Audio playback failed (e.g., autoplay policy or 404).
|
|
211
|
+
// Enable fallback timer so blocks progress without audio.
|
|
212
|
+
fallbackMode.current = true;
|
|
213
|
+
setIsPlaying(true);
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
}, [audioRef, isEnded]);
|
|
217
|
+
const pause = useCallback(() => {
|
|
218
|
+
const audio = audioRef.current;
|
|
219
|
+
if (audio) {
|
|
220
|
+
audio.pause();
|
|
221
|
+
}
|
|
222
|
+
// Also set state directly for cases where audio isn't loaded
|
|
223
|
+
// (pause event won't fire if audio has no valid source)
|
|
224
|
+
setIsPlaying(false);
|
|
225
|
+
}, [audioRef]);
|
|
226
|
+
const toggle = useCallback(() => {
|
|
227
|
+
const audio = audioRef.current;
|
|
228
|
+
if (!audio)
|
|
229
|
+
return;
|
|
230
|
+
// Use component state instead of audio.paused to handle cases where
|
|
231
|
+
// audio source isn't loaded (audio.paused is always true without a source)
|
|
232
|
+
if (!isPlaying) {
|
|
233
|
+
play();
|
|
234
|
+
}
|
|
235
|
+
else {
|
|
236
|
+
pause();
|
|
237
|
+
}
|
|
238
|
+
}, [audioRef, isPlaying, play, pause]);
|
|
239
|
+
const seekTo = useCallback((time) => {
|
|
240
|
+
const audio = audioRef.current;
|
|
241
|
+
if (!audio || !audioTrack?.segments)
|
|
242
|
+
return;
|
|
243
|
+
// Clamp time to valid range
|
|
244
|
+
const clampedTime = Math.max(0, Math.min(time, totalDuration));
|
|
245
|
+
// Find which segment this time falls into
|
|
246
|
+
let segmentIndex = 0;
|
|
247
|
+
let segmentStart = 0;
|
|
248
|
+
for (let i = 0; i < audioTrack.segments.length; i++) {
|
|
249
|
+
const segEnd = segmentStart + audioTrack.segments[i].duration;
|
|
250
|
+
if (clampedTime < segEnd) {
|
|
251
|
+
segmentIndex = i;
|
|
252
|
+
break;
|
|
253
|
+
}
|
|
254
|
+
segmentStart = segEnd;
|
|
255
|
+
// Handle edge case: time exactly at end goes to last segment
|
|
256
|
+
if (i === audioTrack.segments.length - 1) {
|
|
257
|
+
segmentIndex = i;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
const wasPlaying = !audio.paused;
|
|
261
|
+
setIsEnded(false);
|
|
262
|
+
// Check if we need to switch segments
|
|
263
|
+
if (segmentIndex !== currentSegment) {
|
|
264
|
+
// Store pending seek time - will be applied after segment loads
|
|
265
|
+
pendingSeekTime.current = clampedTime;
|
|
266
|
+
shouldPlayAfterLoad.current = wasPlaying;
|
|
267
|
+
setCurrentSegment(segmentIndex);
|
|
268
|
+
}
|
|
269
|
+
else {
|
|
270
|
+
// Same segment - seek directly
|
|
271
|
+
const segmentTime = clampedTime - segmentStart;
|
|
272
|
+
audio.currentTime = Math.max(0, segmentTime);
|
|
273
|
+
setCurrentTime(clampedTime);
|
|
274
|
+
}
|
|
275
|
+
}, [audioRef, audioTrack, currentSegment, totalDuration]);
|
|
276
|
+
const skipToSegment = useCallback((index) => {
|
|
277
|
+
if (!audioTrack?.segments || index < 0 || index >= audioTrack.segments.length) {
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
setCurrentSegment(index);
|
|
281
|
+
setIsEnded(false);
|
|
282
|
+
}, [audioTrack]);
|
|
283
|
+
// Restart from beginning
|
|
284
|
+
const restart = useCallback(async () => {
|
|
285
|
+
seekTo(0);
|
|
286
|
+
// Small delay to ensure seek completes before playing
|
|
287
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
288
|
+
play();
|
|
289
|
+
}, [seekTo, play]);
|
|
290
|
+
// Fallback timer: advance currentTime synthetically when audio.play() was blocked
|
|
291
|
+
// (e.g., autoplay policy or missing audio). Blocks progress at real-time pace
|
|
292
|
+
// without sound. Stops when audio actually starts playing or playback is paused.
|
|
293
|
+
useEffect(() => {
|
|
294
|
+
if (!isPlaying || !fallbackMode.current || !totalDuration)
|
|
295
|
+
return;
|
|
296
|
+
let lastTime = performance.now();
|
|
297
|
+
let raf;
|
|
298
|
+
const tick = (now) => {
|
|
299
|
+
// Audio started playing for real — stop the fallback
|
|
300
|
+
if (!fallbackMode.current)
|
|
301
|
+
return;
|
|
302
|
+
const dt = (now - lastTime) / 1000;
|
|
303
|
+
lastTime = now;
|
|
304
|
+
setCurrentTime((prev) => {
|
|
305
|
+
const next = prev + dt;
|
|
306
|
+
if (next >= totalDuration) {
|
|
307
|
+
fallbackMode.current = false;
|
|
308
|
+
setIsEnded(true);
|
|
309
|
+
setIsPlaying(false);
|
|
310
|
+
return totalDuration;
|
|
311
|
+
}
|
|
312
|
+
return next;
|
|
313
|
+
});
|
|
314
|
+
raf = requestAnimationFrame(tick);
|
|
315
|
+
};
|
|
316
|
+
raf = requestAnimationFrame(tick);
|
|
317
|
+
return () => cancelAnimationFrame(raf);
|
|
318
|
+
}, [isPlaying, totalDuration]);
|
|
319
|
+
return {
|
|
320
|
+
// State
|
|
321
|
+
currentTime,
|
|
322
|
+
isPlaying,
|
|
323
|
+
currentSegment,
|
|
324
|
+
totalDuration,
|
|
325
|
+
isEnded,
|
|
326
|
+
isReady: isAudioReady,
|
|
327
|
+
isAvailable: true, // HTML5 audio is always available in browsers
|
|
328
|
+
// Actions
|
|
329
|
+
play: async () => play(),
|
|
330
|
+
pause: async () => pause(),
|
|
331
|
+
toggle: async () => toggle(),
|
|
332
|
+
seekTo: async (time) => seekTo(time),
|
|
333
|
+
skipToSegment: async (index) => skipToSegment(index),
|
|
334
|
+
restart,
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
export default useAudioSync;
|
|
338
|
+
//# sourceMappingURL=useAudioSync.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useAudioSync.js","sourceRoot":"","sources":["../../src/hooks/useAudioSync.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useDocPlayback Hook
|
|
3
|
+
*
|
|
4
|
+
* Manages the playback state for a visual doc, including which block
|
|
5
|
+
* is currently active, transition states, and synchronization with audio.
|
|
6
|
+
*
|
|
7
|
+
* This hook provides:
|
|
8
|
+
* - Current block determination based on time
|
|
9
|
+
* - Transition tracking (entering/exiting blocks)
|
|
10
|
+
* - Manual navigation (next/prev block)
|
|
11
|
+
* - Time-based seeking
|
|
12
|
+
* - Automatic expansion of template blocks
|
|
13
|
+
*/
|
|
14
|
+
import type { Doc, Block } from '@bendyline/squisq/schemas';
|
|
15
|
+
import type { Theme } from '@bendyline/squisq/schemas';
|
|
16
|
+
import { type ViewportConfig } from '@bendyline/squisq/doc';
|
|
17
|
+
interface PlaybackState {
|
|
18
|
+
/** Currently visible block */
|
|
19
|
+
currentBlock: Block | null;
|
|
20
|
+
/** Index of current block */
|
|
21
|
+
currentBlockIndex: number;
|
|
22
|
+
/** Previous block (for transitions) */
|
|
23
|
+
previousBlock: Block | null;
|
|
24
|
+
/** Whether current block is entering */
|
|
25
|
+
isEntering: boolean;
|
|
26
|
+
/** Whether previous block is exiting */
|
|
27
|
+
isExiting: boolean;
|
|
28
|
+
/** Time relative to current block start */
|
|
29
|
+
blockTime: number;
|
|
30
|
+
/** Progress through current block (0-1) */
|
|
31
|
+
blockProgress: number;
|
|
32
|
+
/** Overall progress through doc (0-1) */
|
|
33
|
+
docProgress: number;
|
|
34
|
+
/** Expanded blocks (templates converted to full blocks with layers) */
|
|
35
|
+
blocks: Block[];
|
|
36
|
+
}
|
|
37
|
+
interface PlaybackActions {
|
|
38
|
+
/** Go to next block */
|
|
39
|
+
nextBlock: () => void;
|
|
40
|
+
/** Go to previous block */
|
|
41
|
+
prevBlock: () => void;
|
|
42
|
+
/** Go to specific block by index */
|
|
43
|
+
goToBlock: (index: number) => void;
|
|
44
|
+
}
|
|
45
|
+
export declare function useDocPlayback(script: Doc | null, currentTime: number, viewport?: ViewportConfig, renderMode?: boolean, theme?: Theme): PlaybackState & PlaybackActions;
|
|
46
|
+
export default useDocPlayback;
|
|
47
|
+
//# sourceMappingURL=useDocPlayback.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useDocPlayback.d.ts","sourceRoot":"","sources":["../../src/hooks/useDocPlayback.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,KAAK,EAAE,GAAG,EAAE,KAAK,EAAY,MAAM,2BAA2B,CAAC;AACtE,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,2BAA2B,CAAC;AAEvD,OAAO,EAIL,KAAK,cAAc,EACpB,MAAM,uBAAuB,CAAC;AAE/B,UAAU,aAAa;IACrB,8BAA8B;IAC9B,YAAY,EAAE,KAAK,GAAG,IAAI,CAAC;IAC3B,6BAA6B;IAC7B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,uCAAuC;IACvC,aAAa,EAAE,KAAK,GAAG,IAAI,CAAC;IAC5B,wCAAwC;IACxC,UAAU,EAAE,OAAO,CAAC;IACpB,wCAAwC;IACxC,SAAS,EAAE,OAAO,CAAC;IACnB,2CAA2C;IAC3C,SAAS,EAAE,MAAM,CAAC;IAClB,2CAA2C;IAC3C,aAAa,EAAE,MAAM,CAAC;IACtB,yCAAyC;IACzC,WAAW,EAAE,MAAM,CAAC;IACpB,uEAAuE;IACvE,MAAM,EAAE,KAAK,EAAE,CAAC;CACjB;AAED,UAAU,eAAe;IACvB,uBAAuB;IACvB,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,2BAA2B;IAC3B,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,oCAAoC;IACpC,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACpC;AAED,wBAAgB,cAAc,CAC5B,MAAM,EAAE,GAAG,GAAG,IAAI,EAClB,WAAW,EAAE,MAAM,EACnB,QAAQ,GAAE,cAA2C,EACrD,UAAU,GAAE,OAAe,EAC3B,KAAK,CAAC,EAAE,KAAK,GACZ,aAAa,GAAG,eAAe,CA2LjC;AAED,eAAe,cAAc,CAAC"}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useDocPlayback Hook
|
|
3
|
+
*
|
|
4
|
+
* Manages the playback state for a visual doc, including which block
|
|
5
|
+
* is currently active, transition states, and synchronization with audio.
|
|
6
|
+
*
|
|
7
|
+
* This hook provides:
|
|
8
|
+
* - Current block determination based on time
|
|
9
|
+
* - Transition tracking (entering/exiting blocks)
|
|
10
|
+
* - Manual navigation (next/prev block)
|
|
11
|
+
* - Time-based seeking
|
|
12
|
+
* - Automatic expansion of template blocks
|
|
13
|
+
*/
|
|
14
|
+
import { useState, useEffect, useMemo, useCallback, useRef } from 'react';
|
|
15
|
+
import { getBlockAtTime } from '@bendyline/squisq/schemas';
|
|
16
|
+
import { expandDocBlocks, isTemplateBlock, VIEWPORT_PRESETS, } from '@bendyline/squisq/doc';
|
|
17
|
+
export function useDocPlayback(script, currentTime, viewport = VIEWPORT_PRESETS.landscape, renderMode = false, theme) {
|
|
18
|
+
const [transitionState, setTransitionState] = useState({
|
|
19
|
+
entering: false,
|
|
20
|
+
exiting: false,
|
|
21
|
+
previousBlock: null,
|
|
22
|
+
});
|
|
23
|
+
// Expand any template blocks into full blocks
|
|
24
|
+
const blocks = useMemo(() => {
|
|
25
|
+
if (!script?.blocks) {
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
// Check if any blocks are templates
|
|
29
|
+
const hasTemplates = script.blocks.some(isTemplateBlock);
|
|
30
|
+
if (hasTemplates) {
|
|
31
|
+
// Extract audio segment timing for proper block synchronization
|
|
32
|
+
const audioSegments = script.audio?.segments?.map((seg) => ({
|
|
33
|
+
startTime: seg.startTime,
|
|
34
|
+
duration: seg.duration,
|
|
35
|
+
}));
|
|
36
|
+
// Expand template blocks with audio segment timing, viewport, and persistent layers
|
|
37
|
+
const expanded = expandDocBlocks(script.blocks, {
|
|
38
|
+
audioSegments,
|
|
39
|
+
viewport,
|
|
40
|
+
persistentLayers: script.persistentLayers,
|
|
41
|
+
theme,
|
|
42
|
+
});
|
|
43
|
+
return expanded;
|
|
44
|
+
}
|
|
45
|
+
// All raw blocks, use as-is
|
|
46
|
+
return script.blocks;
|
|
47
|
+
}, [script?.blocks, script?.audio?.segments, script?.persistentLayers, viewport, theme]);
|
|
48
|
+
// Find current block based on time
|
|
49
|
+
const currentBlock = useMemo(() => getBlockAtTime(blocks, currentTime), [blocks, currentTime]);
|
|
50
|
+
const currentBlockIndex = useMemo(() => (currentBlock ? blocks.indexOf(currentBlock) : -1), [blocks, currentBlock]);
|
|
51
|
+
// Calculate block-relative time
|
|
52
|
+
const blockTime = useMemo(() => {
|
|
53
|
+
if (!currentBlock)
|
|
54
|
+
return 0;
|
|
55
|
+
return Math.max(0, currentTime - currentBlock.startTime);
|
|
56
|
+
}, [currentBlock, currentTime]);
|
|
57
|
+
// Calculate progress values
|
|
58
|
+
const blockProgress = useMemo(() => {
|
|
59
|
+
if (!currentBlock || currentBlock.duration === 0)
|
|
60
|
+
return 0;
|
|
61
|
+
return Math.min(1, blockTime / currentBlock.duration);
|
|
62
|
+
}, [currentBlock, blockTime]);
|
|
63
|
+
const docProgress = useMemo(() => {
|
|
64
|
+
if (!script || script.duration === 0)
|
|
65
|
+
return 0;
|
|
66
|
+
return Math.min(1, currentTime / script.duration);
|
|
67
|
+
}, [script, currentTime]);
|
|
68
|
+
// Track block transitions.
|
|
69
|
+
// In render mode, transitions are computed from blockTime so they stay
|
|
70
|
+
// synchronized with the seekTo timeline. In normal mode, setTimeout drives
|
|
71
|
+
// transitions at the browser's real-time clock speed.
|
|
72
|
+
const _prevBlockRef = useMemo(() => currentBlock,
|
|
73
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- intentionally keyed on index, not block reference
|
|
74
|
+
[currentBlockIndex]);
|
|
75
|
+
useEffect(() => {
|
|
76
|
+
if (!currentBlock || renderMode)
|
|
77
|
+
return;
|
|
78
|
+
// When block changes, trigger transition (real-time mode only)
|
|
79
|
+
if (transitionState.previousBlock?.id !== currentBlock.id) {
|
|
80
|
+
const transition = currentBlock.transition;
|
|
81
|
+
const transitionDuration = transition?.duration || 0;
|
|
82
|
+
if (transitionDuration > 0) {
|
|
83
|
+
// Start transition
|
|
84
|
+
setTransitionState({
|
|
85
|
+
entering: true,
|
|
86
|
+
exiting: true,
|
|
87
|
+
previousBlock: transitionState.previousBlock,
|
|
88
|
+
});
|
|
89
|
+
// End transition after duration
|
|
90
|
+
const timer = setTimeout(() => {
|
|
91
|
+
setTransitionState({
|
|
92
|
+
entering: false,
|
|
93
|
+
exiting: false,
|
|
94
|
+
previousBlock: currentBlock,
|
|
95
|
+
});
|
|
96
|
+
}, transitionDuration * 1000);
|
|
97
|
+
return () => clearTimeout(timer);
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
// Instant cut
|
|
101
|
+
setTransitionState({
|
|
102
|
+
entering: false,
|
|
103
|
+
exiting: false,
|
|
104
|
+
previousBlock: currentBlock,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- intentionally keyed on currentBlock?.id only; reading transitionState.previousBlock without dep to avoid infinite loop
|
|
109
|
+
}, [currentBlock?.id, renderMode]);
|
|
110
|
+
// Render mode: track previous block via ref and compute transition from time
|
|
111
|
+
const renderPrevBlockRef = useRef(null);
|
|
112
|
+
useEffect(() => {
|
|
113
|
+
if (!renderMode || !currentBlock)
|
|
114
|
+
return;
|
|
115
|
+
if (transitionState.previousBlock?.id !== currentBlock.id) {
|
|
116
|
+
// Block changed — remember the old block for crossfade
|
|
117
|
+
const oldPrev = transitionState.previousBlock;
|
|
118
|
+
renderPrevBlockRef.current = oldPrev;
|
|
119
|
+
// Store current block as the "last seen" for next transition
|
|
120
|
+
setTransitionState((prev) => ({
|
|
121
|
+
...prev,
|
|
122
|
+
previousBlock: currentBlock,
|
|
123
|
+
}));
|
|
124
|
+
}
|
|
125
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- same pattern: keyed on block identity change
|
|
126
|
+
}, [currentBlock?.id, renderMode]);
|
|
127
|
+
// In render mode, derive entering/exiting from blockTime
|
|
128
|
+
const renderTransitionDuration = currentBlock?.transition?.duration || 0;
|
|
129
|
+
const renderIsEntering = renderMode && renderTransitionDuration > 0 && blockTime < renderTransitionDuration;
|
|
130
|
+
const renderIsExiting = renderIsEntering && renderPrevBlockRef.current !== null;
|
|
131
|
+
// Manual navigation
|
|
132
|
+
const goToBlock = useCallback((index) => {
|
|
133
|
+
if (!script || index < 0 || index >= blocks.length)
|
|
134
|
+
return;
|
|
135
|
+
// This would need to be coordinated with seekTo from audio sync
|
|
136
|
+
// For now, just return the target block's start time
|
|
137
|
+
const targetBlock = blocks[index];
|
|
138
|
+
if (targetBlock) {
|
|
139
|
+
// Caller should use this time with audio seekTo
|
|
140
|
+
return targetBlock.startTime;
|
|
141
|
+
}
|
|
142
|
+
}, [script, blocks]);
|
|
143
|
+
const nextBlock = useCallback(() => {
|
|
144
|
+
if (currentBlockIndex < blocks.length - 1) {
|
|
145
|
+
return goToBlock(currentBlockIndex + 1);
|
|
146
|
+
}
|
|
147
|
+
}, [currentBlockIndex, blocks.length, goToBlock]);
|
|
148
|
+
const prevBlock = useCallback(() => {
|
|
149
|
+
if (currentBlockIndex > 0) {
|
|
150
|
+
return goToBlock(currentBlockIndex - 1);
|
|
151
|
+
}
|
|
152
|
+
}, [currentBlockIndex, goToBlock]);
|
|
153
|
+
return {
|
|
154
|
+
currentBlock,
|
|
155
|
+
currentBlockIndex,
|
|
156
|
+
previousBlock: renderMode
|
|
157
|
+
? renderIsExiting
|
|
158
|
+
? renderPrevBlockRef.current
|
|
159
|
+
: null
|
|
160
|
+
: transitionState.exiting
|
|
161
|
+
? transitionState.previousBlock
|
|
162
|
+
: null,
|
|
163
|
+
isEntering: renderMode ? renderIsEntering : transitionState.entering,
|
|
164
|
+
isExiting: renderMode ? renderIsExiting : transitionState.exiting,
|
|
165
|
+
blockTime,
|
|
166
|
+
blockProgress,
|
|
167
|
+
docProgress,
|
|
168
|
+
nextBlock,
|
|
169
|
+
prevBlock,
|
|
170
|
+
goToBlock,
|
|
171
|
+
/** Expanded blocks (templates converted to full blocks with layers) */
|
|
172
|
+
blocks,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
export default useDocPlayback;
|
|
176
|
+
//# sourceMappingURL=useDocPlayback.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useDocPlayback.js","sourceRoot":"","sources":["../../src/hooks/useDocPlayback.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useViewportOrientation Hook
|
|
3
|
+
*
|
|
4
|
+
* Detects the current viewport orientation and returns the appropriate
|
|
5
|
+
* VIEWPORT_PRESET for rendering docs. Automatically updates when
|
|
6
|
+
* the window is resized.
|
|
7
|
+
*
|
|
8
|
+
* Thresholds:
|
|
9
|
+
* - Portrait: height > width * 1.2 (significantly taller than wide)
|
|
10
|
+
* - Square: width and height within 20% of each other
|
|
11
|
+
* - Landscape: width > height * 1.2 (significantly wider than tall)
|
|
12
|
+
*/
|
|
13
|
+
import { type ViewportConfig, type ViewportOrientation } from '@bendyline/squisq/doc';
|
|
14
|
+
interface UseViewportOrientationResult {
|
|
15
|
+
/** Current viewport preset configuration */
|
|
16
|
+
viewport: ViewportConfig;
|
|
17
|
+
/** Current orientation name */
|
|
18
|
+
orientation: ViewportOrientation;
|
|
19
|
+
/** Current window dimensions */
|
|
20
|
+
windowSize: {
|
|
21
|
+
width: number;
|
|
22
|
+
height: number;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Hook to detect viewport orientation and return appropriate preset.
|
|
27
|
+
* Updates automatically when window is resized.
|
|
28
|
+
*/
|
|
29
|
+
export declare function useViewportOrientation(): UseViewportOrientationResult;
|
|
30
|
+
export default useViewportOrientation;
|
|
31
|
+
//# sourceMappingURL=useViewportOrientation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useViewportOrientation.d.ts","sourceRoot":"","sources":["../../src/hooks/useViewportOrientation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,mBAAmB,EACzB,MAAM,uBAAuB,CAAC;AAE/B,UAAU,4BAA4B;IACpC,4CAA4C;IAC5C,QAAQ,EAAE,cAAc,CAAC;IACzB,+BAA+B;IAC/B,WAAW,EAAE,mBAAmB,CAAC;IACjC,gCAAgC;IAChC,UAAU,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;CAC/C;AAsCD;;;GAGG;AACH,wBAAgB,sBAAsB,IAAI,4BAA4B,CA6CrE;AAED,eAAe,sBAAsB,CAAC"}
|