Something went wrong. Try again.
A decentralized music tracking and discovery platform built on AT Protocol 🎵 rocksky.app
spotify atproto lastfm musicbrainz scrobbling listenbrainz
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236import AsyncStorage from "@react-native-async-storage/async-storage";import { browsePlyr, PLYR_ID, plyrSource } from "./plyr";import { remoteLibraryRequest } from "../../modules/rocksky-engine";
export type LibraryKind = | "navidrome" | "jellyfin" | "upnp" | "kodi" | "plex" | "plyr";export type LibrarySource = { id: string; kind: LibraryKind; name: string; baseUrl: string; username: string;};export type LibraryConnection = Omit<LibrarySource, "id"> & { id?: string; password?: string; token?: string;};export type LibraryEntry = { artistHandle?: string; id: string; title: string; kind: string; artist: string; album: string; durationMs: number; art: string | null;};export type RemoteTrackMetadata = { title: string; artist: string; album: string; albumArtist?: string; durationMs: number; albumArt: string | null; artistPicture?: string | null; artistLookupAt?: number; artworkLookupAt?: number; genre?: string; year?: number; trackNumber?: number; discNumber?: number; [key: string]: unknown;};export type LibraryPage = { entries: LibraryEntry[]; nextOffset: number | null;};export const libraryProviders = [ { kind: "plyr", title: "plyr.fm", hint: "Discover independent music and radio", placeholder: "https://api.plyr.fm", }, { kind: "navidrome", title: "Navidrome / Subsonic", hint: "Your personal music server", placeholder: "https://music.example.com", }, { kind: "jellyfin", title: "Jellyfin", hint: "Your Jellyfin music collection", placeholder: "http://192.168.1.10:8096", }, { kind: "upnp", title: "UPnP / DLNA", hint: "Find media servers on your Wi-Fi", placeholder: "http://192.168.1.10:8200/rootDesc.xml", }, { kind: "kodi", title: "Kodi", hint: "Connect through Kodi’s web server", placeholder: "http://192.168.1.10:8080", }, { kind: "plex", title: "Plex", hint: "Your Plex music libraries", placeholder: "http://192.168.1.10:32400", },] as const;export type PlaylistItem = { id: string; entryId: string; removable?: boolean; title: string; artist: string; position: number;};export type RemoteSearchEntry = LibraryEntry & { sourceId: string; sourceName: string; sourceKind: LibraryKind;};export type RemoteIndexStatus = { sourceId: string; name: string; state: "idle" | "indexing" | "ready" | "error" | "removed"; updated: number; completed: number; count: number; artworkActive?: boolean; artworkRevision?: number;};export const remoteLibraries = { search: (query: string, offset = 0, kind = "") => remoteLibraryRequest<{ entries: RemoteSearchEntry[]; nextOffset: number | null; }>({ cmd: "searchIndex", query, offset, kind }), indexStatus: () => remoteLibraryRequest<{ sources: RemoteIndexStatus[] }>({ cmd: "indexStatus", }), reindex: (sourceId: string) => remoteLibraryRequest({ cmd: "indexStart", sourceId, force: true }), playlistCapabilities: (sourceId: string) => remoteLibraryRequest<{ create: boolean; rename: boolean; delete: boolean; items: boolean; move: boolean; add: boolean; }>({ cmd: "playlistOperation", sourceId, operation: "capabilities" }), writablePlaylists: (sourceId: string, id = "", offset = 0) => remoteLibraryRequest<LibraryPage>({ cmd: "writablePlaylists", sourceId, id, offset, }), addToPlaylist: (sourceId: string, playlistId: string, id: string) => remoteLibraryRequest({ cmd: "addToPlaylist", sourceId, playlistId, id }), playlistOperation: ( sourceId: string, operation: string, params: Record<string, unknown> = {}, ) => remoteLibraryRequest<{ playlistId?: string; entries: PlaylistItem[]; nextOffset: number | null; }>({ cmd: "playlistOperation", sourceId, operation, ...params }), metadata: async ( sourceId: string, id: string, seed: Record<string, unknown>, ) => ( await remoteLibraryRequest<{ metadata: RemoteTrackMetadata }>({ cmd: "metadata", sourceId, id, seed, }) ).metadata, cacheArtwork: async ( sourceId: string, id: string, artist: string, artistUrl = "", albumUrl = "", ) => ( await remoteLibraryRequest<{ metadata: RemoteTrackMetadata }>({ cmd: "cacheArtwork", sourceId, id, artist, artistUrl, albumUrl, }) ).metadata, list: async () => { const result = await remoteLibraryRequest<{ sources: LibrarySource[] }>({ cmd: "list", }); if (await AsyncStorage.getItem("library:plyr.fm")) result.sources.push(plyrSource); return result; }, connectPlyr: () => AsyncStorage.setItem("library:plyr.fm", "true"), save: (config: LibraryConnection) => remoteLibraryRequest<{ source: LibrarySource }>({ cmd: "save", config }), remove: (sourceId: string) => sourceId === PLYR_ID ? AsyncStorage.removeItem("library:plyr.fm") : remoteLibraryRequest({ cmd: "remove", sourceId }), discover: (kind: "upnp" | "kodi") => remoteLibraryRequest<{ devices: LibrarySource[] }>({ cmd: "discover", kind, }), browse: (sourceId: string, id: string, query: string, offset: number) => sourceId === PLYR_ID ? browsePlyr(id, query, offset) : remoteLibraryRequest<LibraryPage>({ cmd: "browse", sourceId, id, query, offset, limit: 100, }), artworkFailed: (sourceId: string, entry: LibraryEntry) => sourceId === PLYR_ID ? Promise.resolve({}) : remoteLibraryRequest({ cmd: "artworkFailed", sourceId, entry }), refreshArtwork: (sourceId: string, page: LibraryPage, resume = true) => remoteLibraryRequest<LibraryPage>({ cmd: "artworkMerge", sourceId, page, resume, }), stream: async (sourceId: string, id: string) => ( await remoteLibraryRequest<{ url: string }>({ cmd: "stream", sourceId, id, }) ).url,};