Skip to content

Sync Controllers

Sync controllers provide local snapshot mirroring for UI-only contexts or offline playback state management.

The sync module exports two factory functions that create controllers for maintaining a local PlaybackSnapshot that stays in sync with the native runtime:

  • createLegatoSync — uses the full Legato facade
  • createAudioPlayerSync — uses only the audioPlayer surface

Both return a LegatoSyncController with identical behavior.

Configuration options for sync controller creation.

interface LegatoSyncOptions {
/** Optional playback client override; defaults to the exported `Legato` facade. */
client?: AudioPlayerApi;
/** Optional callback invoked when local snapshot state changes. */
onSnapshot?: (snapshot: PlaybackSnapshot) => void;
/** Optional callback invoked for each received event payload. */
onEvent?: <E extends LegatoEventName>(eventName: E, payload: LegatoEventPayloadMap[E]) => void;
}

Audio-player-specific options (extends LegatoSyncOptions).

interface AudioPlayerSyncOptions extends LegatoSyncOptions {
/** Optional playback client override constrained to the audio-player surface. */
client?: AudioPlayerApi;
}

Controller API for maintaining a local playback snapshot mirror.

interface LegatoSyncController {
/** Starts sync and returns initial snapshot. */
start(): Promise<PlaybackSnapshot>;
/** Resyncs from native state and returns updated snapshot. */
resync(): Promise<PlaybackSnapshot>;
/** Returns the current local snapshot, or null if sync not started. */
getCurrent(): PlaybackSnapshot | null;
/** Stops sync and removes all listeners. */
stop(): Promise<void>;
}

Creates a sync controller using the full Legato facade.

import { createLegatoSync } from '@ddgutierrezc/legato-capacitor';
const sync = createLegatoSync({
onSnapshot: (snapshot) => {
console.log('Update UI:', snapshot.state, snapshot.position);
},
onEvent: (eventName, payload) => {
console.log('Event:', eventName);
},
});
await sync.start();

Creates a sync controller using only the audio-player surface.

import { createAudioPlayerSync } from '@ddgutierrezc/legato-capacitor';
const sync = createAudioPlayerSync({
onSnapshot: (snapshot) => {
updateProgressBar(snapshot.position, snapshot.duration);
},
});
await sync.start();
const sync = createLegatoSync();
const snapshot = await sync.start();
// sync.getCurrent() now returns snapshot
setInterval(() => {
const current = sync.getCurrent();
if (current) {
console.log(`Now playing: ${current.currentTrack?.title} at ${current.position}s`);
}
}, 1000);
// Stop sync when done
await sync.stop();
const sync = createLegatoSync();
await sync.start();
// Force resync (e.g., after app returns to foreground)
document.addEventListener('visibilitychange', async () => {
if (document.visibilityState === 'visible') {
await sync.resync();
}
});

The sync controller processes these events to update the local snapshot:

EventSnapshot update
playback-state-changedUpdates state field
playback-active-track-changedUpdates currentTrack, currentIndex, duration, and queue currentIndex
playback-queue-changedReplaces entire queue
playback-progressUpdates position, duration, bufferedPosition
playback-endedReplaces snapshot with terminal snapshot
playback-error, remote eventsNo snapshot update (event delivered via onEvent callback only)