Skip to main content

Quick Troubleshooting

ProblemCauseSolution
Audio not workingAudio context not initialized in user gestureCall initializeAudioContext() in click/touch handler
Avatar not loadingInvalid/expired session token (SDK Mode only)Refresh token (max 24 hours validity)
WASM MIME type errorServer misconfigurationUse Vite plugin or configure MIME type manually
WebSocket failed (SDK Mode)Network or auth issueCheck network and token
Low frame rateWebGL fallback or device limitsUse Chrome/Edge for WebGPU support

Installation Issues

Problem: Errors during npm/yarn/pnpm installSolution:
# Clear cache and reinstall
pnpm store prune
pnpm add @spatialwalk/avatarkit

# Or for npm
npm cache clean --force
npm install @spatialwalk/avatarkit
Checklist:
  • Node.js 18.0+ installed
  • Network connection stable
  • Using correct package name: @spatialwalk/avatarkit
Problem: Type errors when using SDKSolution: Update tsconfig.json:
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "lib": ["ES2020", "DOM", "DOM.Iterable"],
    "moduleResolution": "bundler"
  }
}
Requirements:
  • TypeScript 5.0+
  • Correct lib includes DOM
Problem: WASM MIME type error or 404Solution 1: Use Vite plugin (recommended)
import { avatarkitVitePlugin } from '@spatialwalk/avatarkit/vite'

export default defineConfig({
  plugins: [avatarkitVitePlugin()],
})
Solution 2: Manual configuration
export default defineConfig({
  optimizeDeps: {
    exclude: ['@spatialwalk/avatarkit'],
  },
  assetsInclude: ['**/*.wasm'],
})

Runtime Issues

Problem: AvatarSDK.initialize() throws errorChecklist:
  1. Valid App ID from SpatialReal Studio
  2. Network connection working
  3. Correct environment (Environment.cn or Environment.intl)
  4. SDK not already initialized
Debug:
try {
  await AvatarSDK.initialize('app-id', { environment: Environment.cn })
  console.log('SDK initialized:', AvatarSDK.isInitialized)
} catch (error) {
  console.error('Init failed:', error)
}
Problem: AvatarManager.shared.load() returns nullChecklist:
  1. SDK Mode only: Session token set before loading and not expired (max 24 hours)
  2. Avatar ID valid for your App ID
  3. Network connection stable
Debug:
// Ensure token is set
AvatarSDK.setSessionToken('your-token')
console.log('Token set:', AvatarSDK.sessionToken !== null)

// Load with progress tracking
const avatar = await AvatarManager.shared.load('avatar-id', (progress) => {
  console.log('Progress:', progress)
  if (progress.type === 'failed') {
    console.error('Load error:', progress.error)
  }
})
Problem: Avatar doesn’t respond to audioCause: Audio context not initialized in user gestureSolution:
// ❌ Wrong: Called outside user gesture
await controller.initializeAudioContext()  // May fail silently

// ✅ Correct: Called in click handler
button.addEventListener('click', async () => {
  await controller.initializeAudioContext()
  await controller.start()
})
Problem: Connection state shows ‘failed’Checklist:
  1. SDK Mode: Session token valid and not expired
  2. Network allows WebSocket connections
  3. No firewall blocking
Debug:
controller.onConnectionState = (state) => {
  console.log('Connection:', state)
  if (state === 'failed') {
    // Check token
    console.log('Token:', AvatarSDK.sessionToken)
    // Try reconnecting
    setTimeout(() => controller.start(), 1000)
  }
}

controller.onError = (error) => {
  console.error('Controller error:', error)
}

Performance Issues

Problem: Avatar rendering is choppyCauses:
  • WebGL fallback (WebGPU not supported)
  • Insufficient device performance
  • Too many browser tabs/processes
Solutions:
  1. Use Chrome/Edge for WebGPU support
  2. Check browser hardware acceleration:
    • Chrome: chrome://settings/system → Enable hardware acceleration
  3. Close unnecessary tabs
  4. Reduce container size if needed
// Check rendering backend in console
// SDK logs: "Using WebGPU renderer" or "Using WebGL renderer"
Problem: Memory keeps growingCause: Resources not properly cleaned upSolution:
// Always dispose when done
avatarView.controller.close()
avatarView.dispose()

// React cleanup
useEffect(() => {
  return () => {
    avatarView?.controller.close()
    avatarView?.dispose()
  }
}, [])

// Vue cleanup
onUnmounted(() => {
  avatarView?.controller.close()
  avatarView?.dispose()
})

// Full cleanup when SDK no longer needed
AvatarSDK.cleanup()
Problem: Avatar takes long to loadSolutions:
  1. Show loading progress to user:
const avatar = await AvatarManager.shared.load('avatar-id', (progress) => {
  if (progress.type === 'downloading') {
    showLoadingUI(`Loading: ${progress.progress}%`)
  }
})
  1. Preload avatars in background
  2. Avatar resources are cached after first load

Audio Format

Format Requirements:
PropertyValue
FormatPCM16 (16-bit signed integer)
Byte OrderLittle-endian
ChannelsMono (1 channel)
Sample RateMatch SDK config (default: 16000 Hz)
Example: 1 second at 16kHz = 16000 × 2 bytes = 32000 bytesConverting from different sources:
// From WAV file: Extract PCM data (skip header)
// From MP3: Use AudioContext.decodeAudioData() then convert

// Example: Convert AudioBuffer to PCM16
function audioBufferToPCM16(audioBuffer: AudioBuffer): ArrayBuffer {
  const samples = audioBuffer.getChannelData(0)
  const buffer = new ArrayBuffer(samples.length * 2)
  const view = new DataView(buffer)
  
  for (let i = 0; i < samples.length; i++) {
    const s = Math.max(-1, Math.min(1, samples[i]))
    view.setInt16(i * 2, s < 0 ? s * 0x8000 : s * 0x7FFF, true)
  }
  
  return buffer
}
await AvatarSDK.initialize('app-id', {
  environment: Environment.cn,
  audioFormat: {
    channelCount: 1,  // Fixed to mono
    sampleRate: 16000,  // 8000 | 16000 | 22050 | 24000 | 32000 | 44100 | 48000
  },
})
Note: All audio sent must match the configured sample rate.

Browser Compatibility

BrowserMinimum VersionNotes
Chrome90+Best WebGPU support
Edge90+Best WebGPU support
Firefox90+WebGL only
Safari14+WebGL only, limited features
Recommendation: Chrome or Edge for best performance
PlatformBrowserNotes
iOSSafari 14+WebGL renderer
AndroidChrome (Android 8+)WebGL renderer
Mobile considerations:
  • Performance may vary by device
  • Test on target devices
  • Consider smaller container sizes
Common issues:
  • Limited WebGPU support (falls back to WebGL)
  • Some animations may differ
Solutions:
  1. Update to latest Safari
  2. Enable hardware acceleration
  3. SDK handles fallback automatically

Debugging

import { LogLevel } from '@spatialwalk/avatarkit'

await AvatarSDK.initialize('app-id', {
  environment: Environment.cn,
  logLevel: LogLevel.all,  // Enable all logs
})
Log levels:
  • off - No logs (default)
  • error - Errors only
  • warning - Warnings + errors
  • all - All logs
// Connection state
controller.onConnectionState = (state) => {
  console.log('[Connection]', state)
  // 'disconnected' → 'connecting' → 'connected'
  // or 'disconnected' → 'connecting' → 'failed'
}

// Conversation state
controller.onConversationState = (state) => {
  console.log('[Conversation]', state)
  // 'idle' ↔ 'playing' ↔ 'pausing'
}

// Errors
controller.onError = (error) => {
  console.error('[Error]', error.code, error.message)
}
// Simple FPS monitor
let frameCount = 0
let lastTime = performance.now()

function checkFPS() {
  frameCount++
  const now = performance.now()
  if (now - lastTime >= 1000) {
    console.log(`FPS: ${frameCount}`)
    frameCount = 0
    lastTime = now
  }
  requestAnimationFrame(checkFPS)
}
checkFPS()

Common Code Patterns

useEffect(() => {
  let avatarView: AvatarView | null = null
  let mounted = true

  async function init() {
    await AvatarSDK.initialize(appId, { environment: Environment.cn })
    AvatarSDK.setSessionToken(sessionToken)  // SDK Mode only
    
    const avatar = await AvatarManager.shared.load(avatarId)
    if (!mounted || !avatar) return
    
    avatarView = new AvatarView(avatar, containerRef.current!)
    await avatarView.ready
  }

  init()

  return () => {
    mounted = false
    avatarView?.controller.close()
    avatarView?.dispose()
  }
}, [appId, sessionToken, avatarId])
export function useAvatar(appId: string, sessionToken: string, avatarId: string) {
  const containerRef = ref<HTMLElement | null>(null)
  const avatarView = ref<AvatarView | null>(null)
  const isReady = ref(false)

  onMounted(async () => {
    await AvatarSDK.initialize(appId, { environment: Environment.cn })
    AvatarSDK.setSessionToken(sessionToken)  // SDK Mode only
    
    const avatar = await AvatarManager.shared.load(avatarId)
    if (!avatar || !containerRef.value) return
    
    avatarView.value = new AvatarView(avatar, containerRef.value)
    await avatarView.value.ready
    isReady.value = true
  })

  onUnmounted(() => {
    avatarView.value?.controller.close()
    avatarView.value?.dispose()
  })

  return { containerRef, avatarView, isReady }
}
try {
  await AvatarSDK.initialize(appId, config)
} catch (e) {
  console.error('SDK init failed:', e)
  return
}

const avatar = await AvatarManager.shared.load(avatarId, (progress) => {
  if (progress.type === 'failed') {
    console.error('Load failed:', progress.error)
  }
})

if (!avatar) {
  console.error('Avatar is null')
  return
}

controller.onError = (error) => {
  console.error('Runtime error:', error)
  // Implement retry logic
}