miriad-viz 0.4.0 → 0.4.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.
@@ -2102,6 +2102,103 @@ var textStyle2 = {
2102
2102
  textAlign: "center",
2103
2103
  maxWidth: "100%"
2104
2104
  };
2105
+
2106
+ // src/viewer/audio-position.ts
2107
+ function progressToAudioPosition(progress, timingFile) {
2108
+ if (timingFile.lines.length === 0) return null;
2109
+ if (progress < 0 || progress > 1) return null;
2110
+ for (let i = 0; i < timingFile.lines.length; i++) {
2111
+ const line = timingFile.lines[i];
2112
+ if (progress >= line.progressStart && progress <= line.progressEnd) {
2113
+ const progressRange = line.progressEnd - line.progressStart;
2114
+ if (progressRange <= 0) {
2115
+ return {
2116
+ clipId: line.id,
2117
+ offsetSec: 0,
2118
+ audioFile: line.audioFile,
2119
+ lineIndex: i
2120
+ };
2121
+ }
2122
+ const fraction = (progress - line.progressStart) / progressRange;
2123
+ const offsetSec = fraction * line.durationSec;
2124
+ return {
2125
+ clipId: line.id,
2126
+ offsetSec,
2127
+ audioFile: line.audioFile,
2128
+ lineIndex: i
2129
+ };
2130
+ }
2131
+ }
2132
+ return null;
2133
+ }
2134
+
2135
+ // src/viewer/useAudioPlayback.ts
2136
+ var SEEK_TOLERANCE_SEC = 0.15;
2137
+ function useAudioPlayback({
2138
+ progress,
2139
+ playing,
2140
+ timingFile,
2141
+ audioBaseUrl
2142
+ }) {
2143
+ const audioCache = react.useRef(/* @__PURE__ */ new Map());
2144
+ const activeClipRef = react.useRef(null);
2145
+ const baseUrl = audioBaseUrl.endsWith("/") ? audioBaseUrl : `${audioBaseUrl}/`;
2146
+ const getAudio = react.useCallback(
2147
+ (clipId, audioFile) => {
2148
+ const cached = audioCache.current.get(clipId);
2149
+ if (cached) return cached;
2150
+ const audio = new Audio(`${baseUrl}${audioFile}`);
2151
+ audio.preload = "auto";
2152
+ audioCache.current.set(clipId, audio);
2153
+ return audio;
2154
+ },
2155
+ [baseUrl]
2156
+ );
2157
+ const stopAll = react.useCallback(() => {
2158
+ for (const audio of audioCache.current.values()) {
2159
+ audio.pause();
2160
+ }
2161
+ activeClipRef.current = null;
2162
+ }, []);
2163
+ react.useEffect(() => {
2164
+ return () => {
2165
+ for (const audio of audioCache.current.values()) {
2166
+ audio.pause();
2167
+ audio.src = "";
2168
+ }
2169
+ audioCache.current.clear();
2170
+ };
2171
+ }, []);
2172
+ react.useEffect(() => {
2173
+ if (!timingFile || !playing) {
2174
+ stopAll();
2175
+ return;
2176
+ }
2177
+ const position = progressToAudioPosition(progress, timingFile);
2178
+ if (!position) {
2179
+ stopAll();
2180
+ return;
2181
+ }
2182
+ const { clipId, offsetSec, audioFile } = position;
2183
+ const audio = getAudio(clipId, audioFile);
2184
+ if (activeClipRef.current !== clipId) {
2185
+ stopAll();
2186
+ activeClipRef.current = clipId;
2187
+ audio.currentTime = offsetSec;
2188
+ audio.play().catch(() => {
2189
+ });
2190
+ } else {
2191
+ const drift = Math.abs(audio.currentTime - offsetSec);
2192
+ if (drift > SEEK_TOLERANCE_SEC) {
2193
+ audio.currentTime = offsetSec;
2194
+ }
2195
+ if (audio.paused) {
2196
+ audio.play().catch(() => {
2197
+ });
2198
+ }
2199
+ }
2200
+ }, [progress, playing, timingFile, getAudio, stopAll]);
2201
+ }
2105
2202
  var FALLBACK_DURATION = 120;
2106
2203
  function usePlayback(durationSeconds = FALLBACK_DURATION) {
2107
2204
  const [progress, setProgress] = react.useState(0);
@@ -2236,9 +2333,12 @@ function SceneContents({ devMode, progress }) {
2236
2333
  ] });
2237
2334
  }
2238
2335
  var VIZ_DATA_URL = "./data/viz-data.json";
2336
+ var TIMING_URL = "./data/timing.json";
2337
+ var AUDIO_BASE_URL = "./audio/";
2239
2338
  var activeTestScene = getTestScene();
2240
2339
  function App() {
2241
2340
  const [vizData, setVizData] = react.useState(null);
2341
+ const [timingFile, setTimingFile] = react.useState(null);
2242
2342
  const [error, setError] = react.useState(null);
2243
2343
  const [devMode, setDevMode] = react.useState(getInitialDevMode);
2244
2344
  const viewportOverride = useViewportOverride();
@@ -2324,6 +2424,20 @@ function App() {
2324
2424
  chunkLJG3H4FA_cjs.useVizStore.getState().setDataSummary(extractDataSummary(enriched));
2325
2425
  }).catch((err) => setError(err instanceof Error ? err.message : String(err)));
2326
2426
  }, []);
2427
+ react.useEffect(() => {
2428
+ if (activeTestScene) return;
2429
+ fetch(TIMING_URL).then((res) => {
2430
+ if (!res.ok) return null;
2431
+ return res.json();
2432
+ }).then((data4) => setTimingFile(data4)).catch(() => {
2433
+ });
2434
+ }, []);
2435
+ useAudioPlayback({
2436
+ progress: playback.progress,
2437
+ playing: playback.playing,
2438
+ timingFile,
2439
+ audioBaseUrl: AUDIO_BASE_URL
2440
+ });
2327
2441
  react.useMemo(() => {
2328
2442
  chunkLJG3H4FA_cjs.useVizStore.getState().setPlaying(playback.playing);
2329
2443
  }, [playback.playing]);
@@ -2522,103 +2636,6 @@ var errorStyle = {
2522
2636
  fontFamily: "monospace"
2523
2637
  };
2524
2638
 
2525
- // src/viewer/audio-position.ts
2526
- function progressToAudioPosition(progress, timingFile) {
2527
- if (timingFile.lines.length === 0) return null;
2528
- if (progress < 0 || progress > 1) return null;
2529
- for (let i = 0; i < timingFile.lines.length; i++) {
2530
- const line = timingFile.lines[i];
2531
- if (progress >= line.progressStart && progress <= line.progressEnd) {
2532
- const progressRange = line.progressEnd - line.progressStart;
2533
- if (progressRange <= 0) {
2534
- return {
2535
- clipId: line.id,
2536
- offsetSec: 0,
2537
- audioFile: line.audioFile,
2538
- lineIndex: i
2539
- };
2540
- }
2541
- const fraction = (progress - line.progressStart) / progressRange;
2542
- const offsetSec = fraction * line.durationSec;
2543
- return {
2544
- clipId: line.id,
2545
- offsetSec,
2546
- audioFile: line.audioFile,
2547
- lineIndex: i
2548
- };
2549
- }
2550
- }
2551
- return null;
2552
- }
2553
-
2554
- // src/viewer/useAudioPlayback.ts
2555
- var SEEK_TOLERANCE_SEC = 0.15;
2556
- function useAudioPlayback({
2557
- progress,
2558
- playing,
2559
- timingFile,
2560
- audioBaseUrl
2561
- }) {
2562
- const audioCache = react.useRef(/* @__PURE__ */ new Map());
2563
- const activeClipRef = react.useRef(null);
2564
- const baseUrl = audioBaseUrl.endsWith("/") ? audioBaseUrl : `${audioBaseUrl}/`;
2565
- const getAudio = react.useCallback(
2566
- (clipId, audioFile) => {
2567
- const cached = audioCache.current.get(clipId);
2568
- if (cached) return cached;
2569
- const audio = new Audio(`${baseUrl}${audioFile}`);
2570
- audio.preload = "auto";
2571
- audioCache.current.set(clipId, audio);
2572
- return audio;
2573
- },
2574
- [baseUrl]
2575
- );
2576
- const stopAll = react.useCallback(() => {
2577
- for (const audio of audioCache.current.values()) {
2578
- audio.pause();
2579
- }
2580
- activeClipRef.current = null;
2581
- }, []);
2582
- react.useEffect(() => {
2583
- return () => {
2584
- for (const audio of audioCache.current.values()) {
2585
- audio.pause();
2586
- audio.src = "";
2587
- }
2588
- audioCache.current.clear();
2589
- };
2590
- }, []);
2591
- react.useEffect(() => {
2592
- if (!timingFile || !playing) {
2593
- stopAll();
2594
- return;
2595
- }
2596
- const position = progressToAudioPosition(progress, timingFile);
2597
- if (!position) {
2598
- stopAll();
2599
- return;
2600
- }
2601
- const { clipId, offsetSec, audioFile } = position;
2602
- const audio = getAudio(clipId, audioFile);
2603
- if (activeClipRef.current !== clipId) {
2604
- stopAll();
2605
- activeClipRef.current = clipId;
2606
- audio.currentTime = offsetSec;
2607
- audio.play().catch(() => {
2608
- });
2609
- } else {
2610
- const drift = Math.abs(audio.currentTime - offsetSec);
2611
- if (drift > SEEK_TOLERANCE_SEC) {
2612
- audio.currentTime = offsetSec;
2613
- }
2614
- if (audio.paused) {
2615
- audio.play().catch(() => {
2616
- });
2617
- }
2618
- }
2619
- }, [progress, playing, timingFile, getAudio, stopAll]);
2620
- }
2621
-
2622
2639
  Object.defineProperty(exports, "SIM_HOURS_PER_SECOND", {
2623
2640
  enumerable: true,
2624
2641
  get: function () { return chunkIHAAQH6X_cjs.SIM_HOURS_PER_SECOND; }