moeru-ai/airi · error · Error

The MAGIC Live2D driver is not playing.

Error message

The MAGIC Live2D driver is not playing.

What it means

`replace()` swaps the state generator of a driver that is currently animating. If no animation is running (the internal `generator` is undefined), there is nothing to replace and the driver throws instead of silently starting. Use `start()` to begin playback from idle.

Source

Thrown at packages/model-driver-magic-live2d/src/driver.ts:122

  }

  function start(nextGenerator: Generator<TState>) {
    if (generator)
      throw new Error('The MAGIC Live2D driver is already playing.')

    generator = nextGenerator
    accumulatedMs = 0
    lastFrameAt = now()
    filter.reset()
    outputFrame = undefined
    options.onOutput?.(undefined)
    applyPose(generatePose())
    animationFrame = requestFrame(generationFrame)
  }

  function replace(nextGenerator: Generator<TState>) {
    if (!generator)
      throw new Error('The MAGIC Live2D driver is not playing.')

    generator = nextGenerator
    accumulatedMs = 0
    lastFrameAt = now()
  }

  function stop() {
    if (!generator)
      return

    if (animationFrame !== undefined)
      cancelFrame(animationFrame)
    generator = undefined
    animationFrame = undefined
    accumulatedMs = 0
    filter.reset()
    outputFrame = undefined
    options.onOutput?.(undefined)

View on GitHub (pinned to 9c213115f8)

Solutions

  1. Call `driver.start(generator)` instead of `replace()` when the driver is idle.
  2. Track playing state and branch: `playing ? replace(g) : start(g)`.
  3. Ensure `start()` is invoked in the mount/lifecycle path before any `replace()` calls can fire.
  4. Check that a prior `stop()` (explicit or from an error handler) did not retire the driver unexpectedly.

Example fix

// before
driver.replace(generator)
// after
if (driver.isPlaying())
  driver.replace(generator)
else
  driver.start(generator)
Defensive patterns

Strategy: validation

Validate before calling

function canReplace(driver: MagicLive2dDriver): boolean {
  return driver.isPlaying()
}

Try / catch

try {
  driver.replace(generator)
} catch (error) {
  if (error instanceof Error && error.message.includes('not playing')) {
    driver.start(generator)
  } else {
    throw error
  }
}

Prevention

When it happens

Trigger: Calling `driver.replace(gen)` before any `start()` call, or after `stop()` has cleared the generator. E.g. calling `replace` during component setup before the animation loop was ever started.

Common situations: Calling replace in an init path that runs before start; calling replace after an error path already stopped the driver; race where a stop happened between user intent and replace.

Related errors


AI-assisted analysis of moeru-ai/airi@9c213115f8 (2026-09-02). Data as JSON: /api/errors/391fceb339e07804. Report an issue: GitHub.