{"record":{"id":"20f101b530bacab9","repo":"moeru-ai/airi","slug":"the-magic-live2d-driver-is-already-playing","errorCode":null,"errorMessage":"The MAGIC Live2D driver is already playing.","messagePattern":"The MAGIC Live2D driver is already playing\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/model-driver-magic-live2d/src/driver.ts","lineNumber":108,"sourceCode":"\n    const frameIntervalMs = 1000 / generator.sampleRateHz\n    accumulatedMs += Math.min(250, Math.max(0, timestamp - lastFrameAt))\n    lastFrameAt = timestamp\n\n    let nextPose: Pose | undefined\n    while (accumulatedMs >= frameIntervalMs) {\n      nextPose = generatePose()\n      accumulatedMs -= frameIntervalMs\n    }\n    if (nextPose)\n      applyPose(nextPose)\n\n    animationFrame = requestFrame(generationFrame)\n  }\n\n  function start(nextGenerator: Generator<TState>) {\n    if (generator)\n      throw new Error('The MAGIC Live2D driver is already playing.')\n\n    generator = nextGenerator\n    accumulatedMs = 0\n    lastFrameAt = now()\n    filter.reset()\n    outputFrame = undefined\n    options.onOutput?.(undefined)\n    applyPose(generatePose())\n    animationFrame = requestFrame(generationFrame)\n  }\n\n  function replace(nextGenerator: Generator<TState>) {\n    if (!generator)\n      throw new Error('The MAGIC Live2D driver is not playing.')\n\n    generator = nextGenerator\n    accumulatedMs = 0\n    lastFrameAt = now()","sourceCodeStart":90,"sourceCodeEnd":126,"githubUrl":"https://github.com/moeru-ai/airi/blob/9c213115f8bd0fff9e6eabab02b077ac32da21be/packages/model-driver-magic-live2d/src/driver.ts#L90-L126","documentation":"The MAGIC Live2D driver's `start()` only accepts a new generator when idle. If a previous `start()` was called without a matching `stop()`, the internal `generator` slot is occupied and calling `start()` again throws. It guards against clobbering an active animation loop mid-generation.","triggerScenarios":"Calling `driver.start(gen)` while the driver is already playing (i.e. after a previous `start()` with no `stop()` in between). Common when wiring a second MAGIC session or re-entrant event handler that calls `start` on each event instead of on lifecycle transitions.","commonSituations":"Double-mounting a Vue component so two `onMounted` handlers both call start; a reconnect or model-reload path calling start without stopping the old driver first; a button that triggers start on every click.","solutions":["Call `driver.stop()` before `driver.start(newGenerator)` when the driver may already be playing.","Check for a playing/generator state (or track it in your own code) and skip the redundant `start()`.","Use `replace(nextGenerator)` instead of `start()` when you intend to swap generators in-place on a playing driver.","Guard against duplicate component mounts by aborting/stopping in `onUnmounted`/cleanup."],"exampleFix":"// before\ndriver.start(newGenerator)\n// after\nif (driver.isPlaying())\n  driver.replace(newGenerator)\nelse\n  driver.start(newGenerator)","handlingStrategy":"try-catch","validationCode":"function canStart(driver: MagicLive2dDriver): boolean {\n  return !driver.isPlaying()\n}","typeGuard":null,"tryCatchPattern":"try {\n  driver.start(generator)\n} catch (error) {\n  if (error instanceof Error && error.message.includes('already playing')) {\n    driver.replace(generator)\n  } else {\n    throw error\n  }\n}","preventionTips":["Always pair start() with stop() in component lifecycle (onMounted/onUnmounted).","Track playing state in your own store before calling start().","Prefer replace() when swapping generators on a live driver.","Guard re-entrant event handlers with an idempotency flag."],"tags":["lifecycle","state-error","live2d","animation"],"backgroundTag":"already-started-driver","analyzedSha":"9c213115f8bd0fff9e6eabab02b077ac32da21be","analyzedAt":"2026-09-02T04:27:24.639Z","contentChangedAt":"2026-09-02T04:27:24.639Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}