Dashboard

Player Events & Callbacks

The player exposes a callback for every meaningful state change. Wire them at construct time:

const player = new AnsdevPlayer({
  container: '#player',
  videoId: 'vid_abc123def456',
  playbackToken: 'pt_...',
  onReady:     () => console.log('Ready'),
  onPlay:      () => console.log('Play'),
  onPause:     () => console.log('Pause'),
  onEnded:     () => console.log('End'),
  onError:     (e) => console.error(e),
  onProgress:  ({ time }) => savePosition(time),
});

All callbacks

CallbackSignatureFires when
onReady(player) => voidPlayer is mounted and ready for commands
onPlay() => voidPlayback starts (first play or resume)
onPause() => voidPlayback pauses
onSeek(time: number) => voidAfter a user-initiated seek completes
onTimeUpdate(time: number) => voidThrottled to ~250 ms — current playhead
onProgress({ time, duration }) => voidEvery 5 s — durable progress (debounced)
onQualityChange(quality: string) => voidAdaptive bitrate switches rendition
onEnded() => voidPlayhead reaches end of media
onError({ code, message }) => voidRecoverable or fatal error
onUrlExpiring() => voidPlayback token < 30 s from expiry
onChatSend(message: string) => voidUser sends a chat message (chat add-on)
getProgress() => { time, duration }Returns the latest saved position

Resume playback

let lastPosition = Number(localStorage.getItem(`pos:${videoId}`)) || 0;

const player = new AnsdevPlayer({
  container: '#player',
  videoId,
  playbackToken,
  startAt: lastPosition,
  onProgress: ({ time }) => {
    localStorage.setItem(`pos:${videoId}`, String(Math.floor(time)));
  },
});

onProgress fires every 5 seconds (not every frame), so writing to storage on every event is safe.

Handle token expiry

Playback tokens last 5 minutes. For long-form content, refresh the token before it expires so playback continues uninterrupted:

const player = new AnsdevPlayer({
  container: '#player',
  videoId,
  playbackToken: initialToken,
  onUrlExpiring: async () => {
    const { token } = await fetch(`/api/play/${videoId}`).then((r) => r.json());
    player.setPlaybackToken(token);
  },
});

Error handling

error.codeMeaningRecommended action
TOKEN_EXPIREDPlayback token > 5 min oldFetch a fresh token and call setPlaybackToken()
NETWORK_ERRORTransient connection lossSDK retries up to 3× automatically
MEDIA_ERRORDecoder hiccupSDK attempts recovery; surface error if it fails again
NOT_FOUNDVideo deleted or unknown IDShow a friendly empty state
FORBIDDENToken rejected (revoked key, wrong video)Re-authenticate
onError: ({ code, message }) => {
  if (code === 'TOKEN_EXPIRED') {
    refreshToken();
  } else {
    showErrorBanner(message);
  }
}

Analytics example

Track watch time and ABR behaviour:

let watchedSeconds = 0;
const switches: { from: string; to: string; t: number }[] = [];
let currentQuality = 'auto';

const player = new AnsdevPlayer({
  container: '#player',
  videoId,
  playbackToken,
  onTimeUpdate: (t) => { watchedSeconds = t; },
  onQualityChange: (q) => {
    switches.push({ from: currentQuality, to: q, t: watchedSeconds });
    currentQuality = q;
  },
  onEnded: () => {
    analytics.track('video_complete', { videoId, watchedSeconds, switches });
  },
});

💡 Use onProgress, not onTimeUpdate

For "save and resume" features, prefer onProgress over onTimeUpdate. It fires every 5 s instead of 4×/second, so you won't hammer storage or send a flood of analytics events.