moeru-ai/airi · warning · Error

Beat Sync is not available in Stage Pocket

Error message

Beat Sync is not available in Stage Pocket

What it means

Thrown by the Capacitor (Stage Pocket) install handler for the beat-sync toggle invoke event whenever a caller attempts to enable beat sync. Beat Sync relies on desktop screen/audio capture APIs that are not available on mobile, so the Pocket handler intentionally rejects any enable request while still answering state/parameter handlers as no-ops. It is a feature-availability guard, not a transient failure.

Source

Thrown at packages/stage-shared/src/beat-sync/detector.ts:256

}

let detector: BeatSyncDetector | undefined
function getDetector() {
  if (!isStageWeb())
    throw new Error('getDetector() is only available in Stage Web environment')

  if (!detector)
    detector = createBeatSyncDetector({ env: StageEnvironment.Web })

  return detector
}

let context: ReturnType<typeof createContext> | undefined

function installCapacitorHandlers(context: ReturnType<typeof createContext>) {
  defineInvokeHandler(context, beatSyncToggleInvokeEventa, async (enabled) => {
    if (enabled)
      throw new Error('Beat Sync is not available in Stage Pocket')
  })
  defineInvokeHandler(context, beatSyncGetStateInvokeEventa, async () => ({ isActive: false }))
  defineInvokeHandler(context, beatSyncUpdateParametersInvokeEventa, async () => {})
  defineInvokeHandler(context, beatSyncGetInputByteFrequencyDataInvokeEventa, async () => {
    return new Uint8Array(inputAnalyserFFTSize / 2)
  })
}

function getContext() {
  if (!context) {
    context = createContext()

    // Capacitor cannot capture system audio. Register every request handler so
    // callers receive deterministic inactive results instead of pending forever.
    if (isStageCapacitor())
      installCapacitorHandlers(context)
  }

View on GitHub (pinned to 27111382b4)

Solutions

  1. Gate the Beat Sync toggle UI on platform capability so it is hidden/disabled on Stage Pocket rather than invoked.
  2. If the invoke may still arrive, catch it on the caller side and show a 'not available on mobile' notice instead of surfacing the raw error.
  3. Do not attempt feature-detection workarounds — mobile platforms lack the required capture APIs by design.
  4. Confirm the build target: this only fires on the Pocket (Capacitor) handler, not on Web or Tamagotchi.

Example fix

// before
invokeBeatSyncToggle(true)

// after
if (isStagePocket) {
  showUser('Beat Sync is not available on Stage Pocket.')
} else {
  invokeBeatSyncToggle(true)
}
Defensive patterns

Strategy: validation

Validate before calling

const isStagePocket = Capacitor.getPlatform() !== 'web' && isMobileBuild
if (isStagePocket) {
  showUser('Beat Sync is not available on Stage Pocket.')
  return
}
invokeBeatSyncToggle(true)

Type guard

function isBeatSyncSupported(platform: string): boolean {
  return platform === 'web' || platform === 'electron'
}

Try / catch

try {
  await invokeBeatSyncToggle(true)
} catch (e) {
  if (e instanceof Error && e.message === 'Beat Sync is not available in Stage Pocket') {
    showUser('Beat Sync is not available on Stage Pocket.')
    return
  }
  throw e
}

Prevention

When it happens

Trigger: On the Stage Pocket (Capacitor mobile) build, any UI or IPC path that invokes `beatSyncToggleInvokeEventa` with `enabled = true`. The handler throws synchronously inside the invoke callback, so the IPC call rejects on the caller side.

Common situations: Sharing UI code between desktop and mobile stages where the Beat Sync toggle is still exposed; invoking the mobile handler from a settings page or quick-action without gating it on platform capability; automated tests running against the Pocket handler with enable=true.

Related errors


AI-assisted analysis of moeru-ai/airi@27111382b4 (2026-08-12). Data as JSON: /api/errors/965ebfc1aac1e61c. Report an issue: GitHub.