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
| Callback | Signature | Fires when |
|---|---|---|
onReady | (player) => void | Player is mounted and ready for commands |
onPlay | () => void | Playback starts (first play or resume) |
onPause | () => void | Playback pauses |
onSeek | (time: number) => void | After a user-initiated seek completes |
onTimeUpdate | (time: number) => void | Throttled to ~250 ms — current playhead |
onProgress | ({ time, duration }) => void | Every 5 s — durable progress (debounced) |
onQualityChange | (quality: string) => void | Adaptive bitrate switches rendition |
onEnded | () => void | Playhead reaches end of media |
onError | ({ code, message }) => void | Recoverable or fatal error |
onUrlExpiring | () => void | Playback token < 30 s from expiry |
onChatSend | (message: string) => void | User 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.code | Meaning | Recommended action |
|---|---|---|
TOKEN_EXPIRED | Playback token > 5 min old | Fetch a fresh token and call setPlaybackToken() |
NETWORK_ERROR | Transient connection loss | SDK retries up to 3× automatically |
MEDIA_ERROR | Decoder hiccup | SDK attempts recovery; surface error if it fails again |
NOT_FOUND | Video deleted or unknown ID | Show a friendly empty state |
FORBIDDEN | Token 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.