{"version":3,"file":"pausedMediaPreviewScheduler.mjs","names":[],"sources":["../../../../src/hooks/playback/internal/pausedMediaPreviewScheduler.ts"],"sourcesContent":["import type { MediaSynchronizationQueue } from '#react/hooks/playback/internal/mediaSynchronizationQueue';\nimport { toMediaError, withMediaCauseMessage } from '#react/hooks/playback/mediaError';\nimport { TimelineMediaError } from '@techsquidtv/canvas-timeline-core';\nimport type {\n  ActiveLayerSelector,\n  TimelineEngine,\n  TimelineMediaSyncAdapter,\n} from '@techsquidtv/canvas-timeline-core';\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nclass PausedMediaPreviewScheduler {\n  private frame: number | null = null;\n  private generation = 0;\n\n  cancel() {\n    this.generation += 1;\n    if (this.frame !== null) {\n      cancelAnimationFrame(this.frame);\n      this.frame = null;\n    }\n  }\n\n  isCurrent(generation: number) {\n    return this.generation === generation;\n  }\n\n  schedule(run: (generation: number) => void) {\n    if (this.frame !== null) {\n      return;\n    }\n\n    this.frame = requestAnimationFrame(() => {\n      this.frame = null;\n      this.generation += 1;\n      run(this.generation);\n    });\n  }\n}\n\ninterface UsePausedMediaPreviewOptions<LayerName extends string> {\n  engine: TimelineEngine;\n  adapter: TimelineMediaSyncAdapter<LayerName>;\n  adapterIdentity: object | undefined;\n  layers: Record<LayerName, ActiveLayerSelector>;\n  ready: boolean;\n  playing: boolean;\n  hasPendingPlaybackStart: () => boolean;\n  operationQueue: MediaSynchronizationQueue;\n  onError?: (error: TimelineMediaError) => void;\n}\n\ninterface PausedMediaPreviewSynchronization {\n  cancel: () => void;\n  schedule: () => void;\n  resetForAdapter: (shouldPrime: boolean) => void;\n}\n\nexport function usePausedMediaPreviewSynchronization<LayerName extends string>({\n  engine,\n  adapter,\n  adapterIdentity,\n  layers,\n  ready,\n  playing,\n  hasPendingPlaybackStart,\n  operationQueue,\n  onError,\n}: UsePausedMediaPreviewOptions<LayerName>): PausedMediaPreviewSynchronization {\n  const adapterRef = useRef(adapter);\n  const adapterIdentityRef = useRef(adapterIdentity);\n  const layersRef = useRef(layers);\n  const readyRef = useRef(ready);\n  const playingRef = useRef(playing);\n  const onErrorRef = useRef(onError);\n  const primedRef = useRef(false);\n  const layerSelectionRef = useRef<{ engine: TimelineEngine; signature: string } | null>(null);\n  const [scheduler] = useState(() => new PausedMediaPreviewScheduler());\n\n  useEffect(() => {\n    adapterRef.current = adapter;\n    adapterIdentityRef.current = adapterIdentity;\n    layersRef.current = layers;\n    readyRef.current = ready;\n    playingRef.current = playing;\n    onErrorRef.current = onError;\n  }, [adapter, adapterIdentity, layers, onError, playing, ready]);\n\n  const cancel = useCallback(() => {\n    scheduler.cancel();\n  }, [scheduler]);\n\n  const canSeek = useCallback(\n    () =>\n      !hasPendingPlaybackStart() &&\n      readyRef.current &&\n      !playingRef.current &&\n      adapterRef.current.seek !== undefined,\n    [hasPendingPlaybackStart]\n  );\n\n  const schedule = useCallback(() => {\n    if (!canSeek()) {\n      return;\n    }\n    scheduler.schedule((generation) => {\n      if (!canSeek()) {\n        return;\n      }\n\n      const timelineTime = engine.getTime();\n      const activeLayers = engine.media.getActiveLayers({\n        time: timelineTime,\n        layers: layersRef.current,\n      });\n      const operationToken = operationQueue.capture(adapterIdentityRef.current);\n      void operationQueue.enqueue(async () => {\n        if (\n          !scheduler.isCurrent(generation) ||\n          !operationQueue.isCurrent(operationToken, adapterIdentityRef.current) ||\n          !canSeek()\n        ) {\n          return;\n        }\n        const currentAdapter = adapterRef.current;\n        try {\n          await currentAdapter.seek?.(timelineTime, activeLayers);\n        } catch (seekError) {\n          if (\n            !scheduler.isCurrent(generation) ||\n            !operationQueue.isCurrent(operationToken, adapterIdentityRef.current) ||\n            !canSeek()\n          ) {\n            return;\n          }\n          const cause = toMediaError(seekError);\n          onErrorRef.current?.(\n            new TimelineMediaError(\n              'seek-failed',\n              withMediaCauseMessage('Media seek failed.', cause),\n              { cause }\n            )\n          );\n        }\n      });\n    });\n  }, [canSeek, engine, operationQueue, scheduler]);\n\n  useEffect(() => {\n    if (!canSeek()) {\n      return;\n    }\n    const activeLayers = engine.media.getActiveLayers({ layers });\n    const signature = JSON.stringify(\n      (Object.keys(activeLayers.layers) as LayerName[]).map((name) => [\n        name,\n        activeLayers.layers[name].map((entry) => entry.syncKey),\n      ])\n    );\n    const previous = layerSelectionRef.current;\n    layerSelectionRef.current = { engine, signature };\n    if (previous && (previous.engine !== engine || previous.signature !== signature)) {\n      schedule();\n    }\n  }, [canSeek, engine, layers, playing, ready, schedule]);\n\n  useEffect(() => {\n    const unsubscribers = [\n      engine.on('playhead:scrub', schedule),\n      engine.on('content:change', schedule),\n      engine.on('playback:state', (nextPlaying) => {\n        playingRef.current = nextPlaying;\n        if (nextPlaying) {\n          cancel();\n        } else {\n          schedule();\n        }\n      }),\n    ];\n\n    return () => {\n      cancel();\n      for (const unsubscribe of unsubscribers) {\n        unsubscribe();\n      }\n    };\n  }, [cancel, engine, schedule]);\n\n  useEffect(() => {\n    if (!ready || adapter.seek === undefined) {\n      primedRef.current = false;\n      return;\n    }\n    if (primedRef.current) {\n      return;\n    }\n    primedRef.current = true;\n    schedule();\n  }, [adapter.seek, ready, schedule]);\n\n  const resetForAdapter = useCallback(\n    (shouldPrime: boolean) => {\n      primedRef.current = shouldPrime;\n      cancel();\n      if (shouldPrime && !engine.getState().playing) {\n        schedule();\n      }\n    },\n    [cancel, engine, schedule]\n  );\n\n  return useMemo(\n    () => ({ cancel, schedule, resetForAdapter }),\n    [cancel, resetForAdapter, schedule]\n  );\n}\n"],"mappings":"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"}