index ↓
library
client.library The music library on the connected phone. browse, search, and favoritesList read it,
favoritesToggle and favoritesSet edit saved state, and onFavoriteChanged reports every change.
requests
you ask, the daemon answers. await the tagged result and check .ok
browse(LibraryBrowse): Promise<TypedRequestResult<LibraryBrowseReply, LibraryErrorReply>>
push form: onBrowseReply (subscribe instead of awaiting)
Pages through one folder of the library tree, or the root menu. Root results are held for 5 minutes.
const res = await client.library.browse({ limit: 0, offset: 0 });
if (res.ok) {
console.log(res.response.result);
} else {
console.warn(res.kind, res.error);
} type Result =
| { ok: true; response: LibraryBrowseReply }
| { ok: false; kind: 'domain'; error: LibraryErrorReply }
| { ok: false; kind: 'protocol'; error: WireError }; search(LibrarySearch): Promise<TypedRequestResult<LibrarySearchReply, LibraryErrorReply>>
push form: onSearchReply (subscribe instead of awaiting)
const res = await client.library.search({ query: '...', limit: 0, offset: 0 });
if (res.ok) {
console.log(res.response.result);
} else {
console.warn(res.kind, res.error);
} type Result =
| { ok: true; response: LibrarySearchReply }
| { ok: false; kind: 'domain'; error: LibraryErrorReply }
| { ok: false; kind: 'protocol'; error: WireError }; recommendations(LibraryRecommendations): Promise<TypedRequestResult<LibraryRecommendationsReply, LibraryErrorReply>>
push form: onRecommendationsReply (subscribe instead of awaiting)
const res = await client.library.recommendations({ seeds: [], limit: 0, offset: 0 });
if (res.ok) {
console.log(res.response.result);
} else {
console.warn(res.kind, res.error);
} type Result =
| { ok: true; response: LibraryRecommendationsReply }
| { ok: false; kind: 'domain'; error: LibraryErrorReply }
| { ok: false; kind: 'protocol'; error: WireError }; resolveContext(LibraryResolveContext): Promise<TypedRequestResult<LibraryResolveContextReply, LibraryErrorReply>>
push form: onResolveContextReply (subscribe instead of awaiting)
Resolves a context uri, such as PlayerState.context.uri, into a name and an artwork id.
const res = await client.library.resolveContext({ uri: 'spotify:track:...' });
if (res.ok) {
console.log(res.response.name);
} else {
console.warn(res.kind, res.error);
} type Result =
| { ok: true; response: LibraryResolveContextReply }
| { ok: false; kind: 'domain'; error: LibraryErrorReply }
| { ok: false; kind: 'protocol'; error: WireError }; favoritesList(LibraryFavoritesList): Promise<TypedRequestResult<LibraryFavoritesListReply, LibraryErrorReply>>
push form: onFavoritesListReply (subscribe instead of awaiting)
Pages the user's saved items, mixed across kinds.
const res = await client.library.favoritesList({ limit: 0, offset: 0 });
if (res.ok) {
console.log(res.response.page);
} else {
console.warn(res.kind, res.error);
} type Result =
| { ok: true; response: LibraryFavoritesListReply }
| { ok: false; kind: 'domain'; error: LibraryErrorReply }
| { ok: false; kind: 'protocol'; error: WireError }; favoritesContains(LibraryFavoritesContains): Promise<TypedRequestResult<LibraryFavoritesContainsReply, LibraryErrorReply>>
push form: onFavoritesContainsReply (subscribe instead of awaiting)
Checks which uris the user has saved. The reply's liked lines up with uris.
const res = await client.library.favoritesContains({ uris: [] });
if (res.ok) {
console.log(res.response.liked);
} else {
console.warn(res.kind, res.error);
} type Result =
| { ok: true; response: LibraryFavoritesContainsReply }
| { ok: false; kind: 'domain'; error: LibraryErrorReply }
| { ok: false; kind: 'protocol'; error: WireError }; commands
fire-and-forget. the promise resolves once the daemon has taken the message
favoritesToggle(FavoritesToggle): Promise<void>
await client.library.favoritesToggle({ item: { /* ItemRef */ } }); favoritesSet(FavoritesSet): Promise<void>
await client.library.favoritesSet({ item: { /* ItemRef */ }, liked: true }); favoritesSetMany(FavoritesSetMany): Promise<void>
await client.library.favoritesSetMany({ entries: [] }); events
the daemon pushes these unprompted. subscribing returns an unsubscribe function
onFavoriteChanged(handler: (FavoriteChanged) => void): () => void
const off = client.library.onFavoriteChanged((favoriteChanged) => {
console.log(favoriteChanged.uri);
});
// call off() to unsubscribe onErrorEvent(handler: (LibraryErrorReply) => void): () => void
A favoritesToggle, favoritesSet, or favoritesSetMany command failed.
const off = client.library.onErrorEvent((reply) => {
console.log(reply.error);
});
// call off() to unsubscribe types
shapes referenced above, as the sdk types them
type FavoriteChanged = {
uri: string;
liked: boolean;
}; type LibraryErrorReply = {
error: LibraryError;
}; Pages through one folder of the library tree, or the root menu. Root results are held for 5 minutes.
type LibraryBrowse = {
nodeId?: string; // A `nodeId` from an earlier result. Null browses the root.
limit: number; // Capped at 100.
offset: number;
sections?: number; // Root only. Null returns every folder.
preview?: number; // Root only. Preview children per folder; `0` returns ids and titles only.
}; type LibraryBrowseReply = {
result: BrowseResult;
}; type LibrarySearch = {
query: string;
kinds?: ItemKind[]; // Null searches every kind.
limit: number; // Capped at 100.
offset: number;
}; type LibrarySearchReply = {
result: SearchResult;
}; type LibraryRecommendations = {
seeds: ItemRef[]; // Only the first 5 are used.
kind?: ItemKind; // Null lets the companion app choose from the seeds.
limit: number; // Capped at 100.
offset: number;
}; type LibraryRecommendationsReply = {
result: RecommendationsResult;
}; Resolves a context uri, such as PlayerState.context.uri, into a name and an artwork id.
type LibraryResolveContext = {
uri: string;
}; Fields are null when the companion app cannot name the uri.
type LibraryResolveContextReply = {
name?: string;
artworkId?: string;
subtitle?: string;
}; Pages the user's saved items, mixed across kinds.
type LibraryFavoritesList = {
limit: number; // Capped at 100.
offset: number;
}; type LibraryFavoritesListReply = {
page: FavoritesPage;
}; Checks which uris the user has saved. The reply's liked lines up with uris.
type LibraryFavoritesContains = {
uris: string[]; // Only the first 50 are used.
}; type LibraryFavoritesContainsReply = {
liked: boolean[]; // Lines up with the `uris` you sent.
}; type FavoritesToggle = {
item: ItemRef;
}; type FavoritesSet = {
item: ItemRef;
liked: boolean;
}; type FavoritesSetMany = {
entries: FavoritesSet[];
}; type LibraryError =
| 'notFound' // The uri or node id names nothing in the library.
| 'notSupported' // The music source offers no such operation.
| 'unauthorized' // The signed-in account permits no such operation.
| 'noGateway' // No phone is connected.; Page by raising offset while hasMore is true.
type BrowseResult = {
entries: BrowseEntry[];
total?: number;
hasMore: boolean;
}; search and recommendations take a list of these to constrain their results.
type ItemKind =
| 'track'
| 'album'
| 'playlist'
| 'podcastEpisode'
| 'show'
| 'artist'
| 'station'; Ranked best first. Page by raising offset while hasMore is true.
type SearchResult = {
items: LibraryItem[];
kinds: ItemKind[]; // The kinds the search honored. Compare it against the kinds you asked for.
total?: number;
hasMore: boolean;
}; A reference to a library item. Pass it to favoritesToggle, or pass its uri to player.play.
type ItemRef = {
uri: string;
kind: ItemKind;
persistentId?: string; // Opaque. Null when the source has none.
}; Page by raising offset while hasMore is true.
type RecommendationsResult = {
items: LibraryItem[];
total?: number;
hasMore: boolean;
}; Mixed kind. Page by raising offset while hasMore is true.
type FavoritesPage = {
items: LibraryItem[];
total?: number;
hasMore: boolean;
}; A folder can be browsed further; an item can be played.
type BrowseEntry =
| { type: 'folder'; data: BrowseFolder }
| { type: 'item'; data: LibraryItem }; Branch on type to read the payload.
type LibraryItem =
| { type: 'track'; data: Track }
| { type: 'album'; data: Album }
| { type: 'playlist'; data: Playlist }
| { type: 'podcastEpisode'; data: PodcastEpisode }
| { type: 'show'; data: Show }
| { type: 'artist'; data: Artist }
| { type: 'station'; data: Station }; Pass its nodeId to browse to descend into it.
type BrowseFolder = {
nodeId: string;
title: string;
subtitle?: string;
artworkId?: string;
total?: number; // Null when the source reports no count.
previewChildren?: BrowseEntry[]; // The first few children, when the source returns them alongside the folder.
}; A track with its album and artist resolved. Library search and browse return these.
type Track = {
id: string;
name: string;
album: Album;
artist: Artist; // Primary credited artist.
artists: Artist[]; // All credited artists, in order.
durationMs: number;
imageId: string; // Opaque artwork asset id. Pass it to `asset.get` for the bytes.
saved: boolean;
}; type Album = {
id: string;
name: string;
artworkId?: string; // Opaque artwork asset id. Pass it to `asset.get` for the bytes.
}; type Playlist = {
uri: string;
name: string;
ownerName?: string; // Who the source credits, such as an owner or a curator.
trackCount?: number; // Null when the source reports no count.
artworkId?: string;
}; type PodcastEpisode = {
uri: string;
name: string;
showName?: string;
durationMs?: number;
publishedAtUnixS?: number; // Null when the source reports none.
artworkId?: string;
}; type Show = {
uri: string;
name: string;
publisher?: string;
episodeCount?: number; // Null when the source reports no count.
artworkId?: string;
}; type Artist = {
id: string;
name: string;
artworkId?: string; // Opaque artwork asset id. Pass it to `asset.get` for the bytes.
}; type Station = {
uri: string;
name: string;
seed?: string; // URI the station was built from, such as an artist or a track.
artworkId?: string;
};