index ↓
player
client.player Playback state and transport for a webapp. onSnapshot delivers the full PlayerState after
every change. stateGet, queueGet, and targetsGet return the same shapes on demand.
requests
you ask, the daemon answers. await the tagged result and check .ok
stateGet(): Promise<TypedRequestResult<PlayerStateReply, never>>
push form: onStateReply (subscribe instead of awaiting)
const res = await client.player.stateGet();
if (res.ok) {
console.log(res.response.state);
} type Result =
| { ok: true; response: PlayerStateReply }
| { ok: false; kind: 'domain'; error: never }
| { ok: false; kind: 'protocol'; error: WireError }; queueGet(): Promise<TypedRequestResult<PlayerQueueReply, never>>
push form: onQueueReply (subscribe instead of awaiting)
const res = await client.player.queueGet();
if (res.ok) {
console.log(res.response.current);
} type Result =
| { ok: true; response: PlayerQueueReply }
| { ok: false; kind: 'domain'; error: never }
| { ok: false; kind: 'protocol'; error: WireError }; targetsGet(): Promise<TypedRequestResult<PlayerTargetsReply, never>>
push form: onTargetsReply (subscribe instead of awaiting)
Returns the playback targets the connected companion app can reach.
const res = await client.player.targetsGet();
if (res.ok) {
console.log(res.response.targets);
} type Result =
| { ok: true; response: PlayerTargetsReply }
| { ok: false; kind: 'domain'; error: never }
| { ok: false; kind: 'protocol'; error: WireError }; commands
fire-and-forget. the promise resolves once the daemon has taken the message
play(PlayUri): Promise<void>
Starts playback of a uri, optionally within a context.
await client.player.play({ uri: 'spotify:track:...' }); queue(QueueUri): Promise<void>
await client.player.queue({ uri: 'spotify:track:...', position: { /* QueuePosition */ } }); pause(): Promise<void>
await client.player.pause(); resume(): Promise<void>
await client.player.resume(); skipNext(): Promise<void>
await client.player.skipNext(); skipToIndex(SkipToIndex): Promise<void>
await client.player.skipToIndex({ index: 0 }); setShuffle(SetShuffle): Promise<void>
await client.player.setShuffle({ on: true }); setSpeed(SetSpeed): Promise<void>
Changes playback speed. The connected companion app must support rate control.
await client.player.setSpeed({ speed: 0 }); setCrossfade(SetCrossfade): Promise<void>
await client.player.setCrossfade({ durationMs: 0 }); transferTo(TransferTo): Promise<void>
await client.player.transferTo({ targetId: '...' }); events
the daemon pushes these unprompted. subscribing returns an unsubscribe function
onSnapshot(handler: (PlayerStateReply) => void): () => void
const off = client.player.onSnapshot((reply) => {
console.log(reply.state);
});
// call off() to unsubscribe onDelta(handler: (NowPlayingUpdate) => void): () => void
const off = client.player.onDelta((update) => {
console.log(update.mediaItem);
});
// call off() to unsubscribe onQueueChanged(handler: (PlayerQueueReply) => void): () => void
const off = client.player.onQueueChanged((reply) => {
console.log(reply.current);
});
// call off() to unsubscribe onTargetsChanged(handler: (PlayerTargetsReply) => void): () => void
const off = client.player.onTargetsChanged((reply) => {
console.log(reply.targets);
});
// call off() to unsubscribe onErrorEvent(handler: (PlayerErrorReply) => void): () => void
const off = client.player.onErrorEvent((reply) => {
console.log(reply.error);
});
// call off() to unsubscribe types
shapes referenced above, as the sdk types them
type PlayerStateReply = {
state: PlayerState;
activeApp?: CurrentlyActiveApplication; // The app driving playback, when the phone reports it.
}; type NowPlayingUpdate = {
mediaItem?: MediaItemUpdate;
playback?: PlaybackUpdate;
}; type PlayerQueueReply = {
current?: QueueItem; // The now-playing track, when one is loaded.
items: QueueItem[]; // Upcoming tracks in queue order.
previous: QueueItem[]; // Recently-played history.
}; type PlayerTargetsReply = {
targets: PlaybackTarget[];
}; type PlayerErrorReply = {
error: PlayerError;
}; type PlayUri = {
uri: string;
context?: PlayContext;
}; type QueueUri = {
uri: string;
position: QueuePosition;
}; type SkipPrev = {
allowSeeking: boolean; // `true` restarts the current track once it is past the restart threshold.
}; type SkipToIndex = {
index: number;
}; type SeekTo = {
positionMs: number;
}; type SetShuffle = {
on: boolean;
}; type SetRepeat = {
mode: RepeatMode;
}; type SetSpeed = {
speed: number;
}; type SetCrossfade = {
durationMs?: number;
}; type TransferTo = {
targetId: string;
}; onSnapshot delivers it, and the SDK merges later deltas into it.
type PlayerState = {
track?: MediaItem;
playback: Playback;
queue: QueueItem[];
options: PlayerOptions;
context?: PlaybackContext;
target?: PlaybackTarget;
}; The app driving playback on the phone.
type CurrentlyActiveApplication = {
id: string; // Bundle identifier, for example `com.spotify.client`.
name: string;
}; Attributes that change when the track changes.
type MediaItemUpdate = {
persistentId?: string;
title?: string;
album?: string;
albumUri?: string;
albumArtist?: string;
artist?: string;
artistUri?: string;
liked?: boolean;
artworkId?: string; // Opaque artwork asset id. Pass it to `asset.get` for the bytes.
durationMs?: number;
mediaTypes?: MediaType[];
trackNumber?: number;
trackCount?: number;
isLikeSupported?: boolean;
isBanSupported?: boolean;
isBanned?: boolean;
isResidentOnDevice?: boolean;
chapterCount?: number;
}; Attributes that change without the track changing.
type PlaybackUpdate = {
playing?: boolean;
positionMs?: number;
shuffle?: boolean;
shuffleMode?: ShuffleMode;
repeat?: RepeatMode;
appBundle?: string; // For example `com.spotify.client`. Null on Android.
appDisplayName?: string;
queueIndex?: number;
queueCount?: number;
queueChapterIndex?: number;
playbackSpeed?: number;
setElapsedTimeAvailable?: boolean; // False when the app refuses absolute seeks. Null means no signal yet.
queueListAvail?: boolean;
appleMusicRadioAd?: boolean;
appleMusicRadioStationName?: string;
}; type QueueItem = {
uri: string;
title?: string;
artist?: string;
artistUri?: string;
album?: string;
albumUri?: string;
artworkId?: string; // Opaque artwork asset id. Pass it to `asset.get` for the bytes.
durationMs?: number;
persistentId?: string; // Opaque. Null when the source has none.
queued: boolean;
}; transferTo moves playback here. Check available.playbackTargets in Capabilities first.
type PlaybackTarget = {
id: string; // Opaque endpoint id. Pass it to `transferTo`.
name: string;
kind: PlaybackTargetKind;
isActive: boolean; // True when this endpoint is the one playing.
volumePercent?: number; // Null when the endpoint reports no volume.
}; type PlayerError =
| 'schemeUnclaimed' // No connected phone claims the uri's scheme.
| 'playFailed' // The phone accepted the command and then failed to carry it out.
| 'noGateway' // No phone is connected.
| 'notInQueue' // `skipToIndex` named an index outside the queue.
| 'unknownTarget' // `transferTo` named an endpoint outside the current target list.; contextUri is the album, playlist, or show the track plays from. It sets what skipNext does.
type PlayContext = {
contextUri: string;
}; Where a queue call puts the item.
type QueuePosition =
| { type: 'append' }
| { type: 'next' } // Directly after the current track.
| { type: 'index'; data: u32 } // At a 0-based slot in the upcoming list.; type RepeatMode = 'off' | 'all' | 'one'; A field stays null until the phone reports it.
type MediaItem = {
uri?: string;
persistentId?: string;
title?: string;
album?: string;
albumUri?: string;
albumArtist?: string;
artist?: string;
artistUri?: string;
liked?: boolean;
artworkId?: string; // Opaque artwork asset id. Pass it to `asset.get` for the bytes.
durationMs?: number;
mediaTypes?: MediaType[];
trackNumber?: number;
trackCount?: number;
isLikeSupported?: boolean;
isBanSupported?: boolean;
isBanned?: boolean;
chapterCount?: number;
}; positionMs is the playhead when the state was taken. Advance it locally while state is
playing.
type Playback = {
state: PlaybackState;
positionMs: number;
positionAgeMs?: number;
shuffle: boolean;
shuffleMode?: ShuffleMode;
repeat: RepeatMode;
queueIndex?: number;
queueCount?: number;
queueChapterIndex?: number;
setElapsedTimeAvailable?: boolean; // False when the app refuses absolute seeks. Null means no signal yet.
queueListAvail?: boolean;
appleMusicRadioAd?: boolean;
}; type PlayerOptions = {
speed: number;
crossfadeMs?: number; // 0 turns crossfade off. Null when the source carries no crossfade setting.
}; What the current track plays from, such as a playlist, an album, or a show.
type PlaybackContext = {
uri: string;
name?: string; // Null until the phone resolves the name.
}; An item can carry more than one kind.
type MediaType = 'music' | 'podcast' | 'audioBook'; A phone that does not separate track from album shuffle reports songs while shuffle is on.
type ShuffleMode = 'off' | 'songs' | 'albums'; An endpoint the source does not classify reports unknown.
type PlaybackTargetKind =
| 'unknown'
| 'phone'
| 'tablet'
| 'computer'
| 'speaker'
| 'tv'
| 'gameConsole'
| 'automobile'
| 'wearable'; type PlaybackState =
| 'stopped' // No track is loaded.
| 'paused' // A track is loaded and its position is held.
| 'playing';