Cody Music is an open source package designed to help you perform Mac iTunes and Spotify Web API playback functionality
- Mac Spotify and iTunes desktop
- Spotify Web
- Fetching Spotify audio features
- Playlists (create, delete, fetch playlist tracks, replace playlist tracks)
- Genre search (Recently updated to return highest frequency Spotify Genre)
- Fetching Spotify devices
- Access token refresh retry
- Track recommendations from Spotify
- Add and remove to the Liked playlist
- Prevents adding duplicate playlists by name
- Follow or unfollow a playlist
- Spotify Recommendations
- Genre search
npm
$ npm install cody-music --save
yarn
$ yarn add cody-music
$ npm test
Load the module
import {
getRunningTrack,
Track,
PlayerType,
TrackStatus,
setConfig } from "cody-music";
...
// update the CodyMusic spotify credentials and other settings
setConfig({
spotifyAccessToken: <spotify_access_token>,
spotifyRefreshToken: <spotify_refresh_token>;
spotifyClientSecret: <spotify_client_secret>;
spotifyClientId: <spotify_client_id>;
enableItunesDesktop: <enable_itunes_desktop_track_lookup>;
enableSpotifyDesktop: <enable_spotify_desktop_track_lookup>;
enableSpotifyApi: <enable_spotify_api>;
});
const track:Track = await getRunningTrack();
if (track.state === TrackStatus.Playing) {
// track is playing
}
if (track.playerType === PlayerType.WebSpotify) {
// track running has been identified as your spotify web player
}
OR
import * as CodyMusic from "cody-music";
OR
const CodyMusic = require("cody-music");
Play a track with Music URI uri
.
Specify either "Spotify" or "iTunes" (case-insensitive).
// get the track info using get state
await CodyMusic.getRunningTrack().then((track: Track) => {
// returns the Track data
});
// play a specific spotify track
await CodyMusic.playTrack(
"spotify",
"spotify:track:2YarjDYjBJuH63dUIh9OWv"
).then((result) => {
// track is playing
});
// play an iTunes track number
await CodyMusic.playTrack("itunes", 1).then((result) => {
// track is playing
});
// handling errors
await CodyMusic.playTrack("spotify", 1000000000).then((result) => {
// result will contain the "error" attribute with the error message
if (result.error) {
console.log(`Unable to play track, error: ${result.error}`);
}
});
await CodyMusic.getRunningTrack().then((result) => {
// result will be the best effort track that is playing.
// i.e. if you have your itunes app running, it would show you that track
});
Full set of APIs
/**
* Initialize/set music credentials and settings
* @param config <CodyConfig>
*/
setConfig(config: CodyConfig)
/**
* Valid types are: album, artist, playlist, and track
* keywords: send the keywords to search against.
* Use specific filter name if you want to search against certain
* fields.
* Example searchTracks("track:what a time artist:tom")
* If you use track and artist and internally it doesn't return a match,
* it will search again with only the track part of the query.
*
* @param string
* @param limit (min of 1 and a max of 50)
*/
searchTracks(keywords: string, limit: number = 50)
/**
* Valid types are: album, artist, playlist, and track
* keywords: send the keywords to search against.
* Use specific filter name if you want to search against certain
* fields.
* Example searchTracks("track:what a time artist:tom")
*
* @param string
* @param limit (min of 1 and a max of 50)
*/
searchArtists(keywords: string, limit: number = 50)
/**
* Returns true if the user has granted Mac OS access for iTunes control
*/
isItunesAccessGranted()
/**
* Set this if you would like a general flag itunes is not supported,
* but you will also need to set isItunesDesktopSongTrackingEnabled
* to the same value if you want to ensure it's not returning itunes songs.
*
* Returns false if cody music has been configured to to disable it
* or if it's the OS is not Mac,
* otherwise it's set to true by default
*/
isItunesDesktopEnabled()
/**
* This will allow or disallow song tracking.
* Returns false if cody music has been configured to to disable it
* or if it's the OS is not Mac,
* otherwise it's set to true by default
*/
isItunesDesktopSongTrackingEnabled()
/**
* Get the Spotify accessToken provided via through the setConfig api
* @returns {string} the spotify access token string
*/
getSpotifyAccessToken()
/**
* Refresh the Spotify accessToken
* @returns {Promise<boolean>} whether or not the refresh was successful
*/
refreshSpotifyAccessToken()
/**
* Returns false if cody music has been configured to to disable it,
* otherwise it's set to true by default
*/
isSpotifyDesktopEnabled()
/**
* Checks if the Spotify desktop or web player is running or not
* @returns {Promise<boolean>}
*/
isSpotifyRunning()
/**
* Checks if the iTunes desktop player is running or not
* @returns {Promise<boolean>}
*/
isItunesRunning()
/**
* Checks if one of the specified players is running
* @param player {spotify|spotify-web|itunes}
* @returns {Promise<boolean>}
*/
isPlayerRunning(player: PlayerName)
/**
* Returns whether there's an active track,
* (spotify web, spotify desktop, or itunes desktop)
* @returns {Promise<boolean>}
*/
hasActiveTrack(): Promise<boolean>
/**
* Returns the recommended tracks for the
* @param trackIds (optional) track IDs or URIs (5 max)
* @param limit (optional) will default to 40 if not specified
* @param market (optional) will default to none if not specified
* @param min_popularity (optional) will default to a min or 20
* @param target_popularity (optional) will default to 100
* @param seed_genres (optional) the supported spotify genres (5 max)
* @param seed_genres (optional) artist IDs or URIs (5 max)
* @param features (optional) supports the tunable track attributes using min_*, max_*, and target_*
* i.e. {max_valence: 0.3, target_valence: 0.1}
*/
getRecommendationsForTracks(
trackIds: string[] = [],
limit: number = 40,
market: string = "",
min_popularity: number = 20,
target_popularity: number = 100,
seed_genres: string[] = [],
seed_artists: string[] = [],
features: any = {}
): Promise<Track[]>
/**
* Returns the currently running track.
* Spotify web, desktop, or itunes desktop.
* If it finds a spotify device but it's not playing, and mac iTunes is not playing
* or paused, then it will return the Spotify track.
* It will return an empty Track object if it's unable to
* find a running track.
* @returns {Promise<Track>}
**/
getRunningTrack(): Promise<Track>
/**
* Fetch the recently played spotify tracks
* @param limit - The maximum number of items to return. Default: 50. Minimum: 1. Maximum: 50
*/
getSpotifyRecentlyPlayedTracks(
limit: number = 50
): Promise<Track[]>
/**
* Fetch the recently played spotify tracks
* @param limit - The maximum number of items to return. Default: 50. Minimum: 1. Maximum: 50
* @param before - Returns all items before (but not including) this cursor position
*/
getSpotifyRecentlyPlayedBefore(
limit: number = 50,
before: number = 0
): Promise<Track[]>
/**
* Fetch the recently played spotify tracks
* @param limit - The maximum number of items to return. Default: 50. Minimum: 1. Maximum: 50
* @param after - Returns all items before (but not including) this cursor position
*/
getSpotifyRecentlyPlayedAfter(
limit: number = 50,
after: number = 0
): Promise<Track[]>
/**
* Fetch the spotify player context
* Info about the device, is playing state, etc.
*/
getSpotifyPlayerContext(): Promise<PlayerContext>
/**
* Returns a track by the given spotify track id
* @param id
* @param includeFullArtistData (optional - if true it will return full artist info)
* @package includeAudioFeaturesData (optional)
* @param includeGenre (optional)
*/
getSpotifyTrackById(
id: string,
includeFullArtistData: boolean = false,
includeAudioFeaturesData: boolean = false,
includeGenre: boolean = false
): Promise<Track>
/**
* Returns tracks by the given spotify track ids
* @param ids
* @param includeFullArtistData (optional - if true it will return full artist info)
* @package includeAudioFeaturesData (optional)
* @param includeGenre (optional)
*/
export async function getSpotifyTracks(
ids: string[],
includeFullArtistData: boolean = false,
includeAudioFeaturesData: boolean = false,
includeGenre: boolean = false
): Promise<Track[]>
/**
* Returns the track of a given player {spotify|spotify-web|itunes}
* - Spotify does not return a "genre"
* - duration is in milliseconds
* @param player {spotify|spotif-web|itunes}
* @returns {artist, album, genre, disc_number, duration, played_count, track_number, id, name, state}
*/
getTrack(player: PlayerName): Promise<Track>
/**
* Returns the tracks that are found for itunes
* @param player {itunes}
* @param playListName
*/
getTracksByPlaylistName(
player: PlayerName,
playListName: string
): Promise<Track[]>
/**
* Returns tracks of a Spotify Album
* @param albumId: spotify album uri or album ID
**/
getSpotifyAlbumTracks(albumId: string): Promise<Track[]>
/**
* Currently only returns Spotify Web tracks not associated with a playlist.
* @param player
* @param qsOptions
*/
getSpotifyLikedSongs(
qsOptions: any = {}
): Promise<Track[]>
/**
* Currently only returns Spotify Web tracks not associated with a playlist.
* @param player
* @param qsOptions
*/
getSavedTracks(player: PlayerName, qsOptions: any = {}): Promise<Track[]>
/**
* Returns a playlist by ID
* @param playlist_id ID is preferred, but we'll transform a URI to an ID
*/
getSpotifyPlaylist(playlist_id: string): Promise<PlaylistItem>
/**
* Returns the tracks that are found by the given playlist name
* - currently spofity-web support only
* @param player {spotify-web}
* @param playlist_id (optional)
* @param qsOptions (optional) {offset, limit}
*/
getPlaylistTracks(
player: PlayerName,
playlist_id: string,
qsOptions: any = {}
): Promise<CodyResponse>
/**
* Plays a playlist at the beginning if the starting track id is not provided.
* @param playlistId either the ID or URI of the playlist
* @param startingTrackId either the ID or URI of the track
* @param deviceId
*/
playSpotifyPlaylist(
playlistId: string,
startingTrackId: string = "",
deviceId: string = ""
)
/**
* Plays a specific track on the Spotify or iTunes desktop
* @param player
* @param params
* spotify example ["spotify:track:0R8P9KfGJCDULmlEoBagcO", "spotify:album:6ZG5lRT77aJ3btmArcykra"]
* -- provide the trackID then the album or playlist ID
* -- they can either be in either URI or ID format
* itunes example ["Let Me Down Slowly", "MostRecents"]
* -- provide the track name then the playlist name
*/
playTrackInContext(player: PlayerName, params: any[])
/**
* Mac iTunes only
* This will allow you to play a playlist starting at a specific playlist track number.
*/
playItunesTrackNumberInPlaylist(
playlistName: string,
trackNumber: number
)
/**
* Quits/closes the mac Spotify or iTunes player
* @param player
*/
quitMacPlayer(player: PlayerName)
/**
* This is only meant for Mac iTunes or Mac Spotify desktop
* @param player
* @param params
*/
playTrackInLibrary(player: PlayerName, params: any[])
/**
* Initiate and play the specified Spotify device
* @param device_id {string}
*/
playSpotifyDevice(device_id: string)
/**
* Initiate and play the specified Spotify device
* @param device_id {string}
* @param play {boolean} true to play and false to keep current play state
*/
transferSpotifyDevice(device_id: string, play: boolean)
/**
* Fetch the user's profile
*/
getUserProfile(): Promise<SpotifyUser>
/**
* Helper API to return whether or not the user is logged in to their spotify account or not.
* It's not fool proof as it only determines if there are any devices found or not.
* {oauthActivated, loggedIn}
*/
spotifyAuthState(): Promise<SpotifyAuthState>
/**
* Initiate the play command for a specific player
* @param player {spotify|spotify-web|itunes}
* @param options { uris, device_id }
* example
* -- the uris can be in either URI or ID format
* {device_id: <spotify_device_id>, uris: ["spotify:track:4iV5W9uYEdYUVa79Axb7Rh", "spotify:track:1301WleyT98MSxVHPZCA6M"], context_uri: <playlist_uri, album_uri>}
*/
play(player: PlayerName, options: any = {})
/**
* Play a specific spotify track by trackId (it can be the URI or the ID)
* @param trackId
* @param deviceId (optional)
*/
playSpotifyTrack(trackId: string, deviceId: string = "")
/**
* Initiate the play command for a given trackId for a specific player
* @param player {spotify|spotify-web|itunes}
* @param trackId {any (string|number)}
*/
playTrack(PlayerName: PlayerName, trackId: any)
/**
* Initiate the pause command for a given player
* @param player {spotify|spotify-web|itunes}
* @param options
*/
pause(player: PlayerName, options: any = {})
/**
* Initiate the play/pause command for a given player
* @param player {spotify|spotify-web|itunes}
* @param options
*/
playPause(player: PlayerName)
/**
* Initiate the next command for a given player
* @param player {spotify|spotify-web|itunes}
* @param options
*/
next(player: PlayerName, options: any = {})
/**
* Initiate the previous command for a given player
* @param player {spotify|spotify-web|itunes}
* @param options
*/
previous(player: PlayerName, options: any = {})
/**
* Repeats a playlist
* @param player
* @param deviceId
*/
setRepeatPlaylist(player: PlayerName, deviceId: string = "")
/**
* Repeats a track
* @param player
* @param deviceId
*/
setRepeatTrack(player: PlayerName, deviceId: string = "")
/**
* Turn repeat off
* @param player
* @param deviceId
*/
setRepeatOff(player: PlayerName, deviceId: string = "")
/**
* Turn on/off repeat for a given player
* @param player {spotify|spotify-web|itunes}
* @param options
*/
setRepeat(player: PlayerName, repeat: boolean)
/**
* Turn on/off shuffling for a given player
* @param player {spotify|spotify-web|itunes}
* @param shuffle (true or false)
* @param deviceId (optional)
*/
setShuffle(player: PlayerName, shuffle: boolean, deviceId: string = "")
/**
* Return whether shuffling is on or not
* @param player {spotify|spotify-web|itunes}
*/
isShuffling(player: PlayerName)
/**
* Returns whether the player is on repeat or not
* - spotify returns true or false, and itunes returns "off", "one", "all"
* @param player {spotify|spotify-web|itunes}
*/
isRepeating(player: PlayerName)
/**
* Update the players volume
* @param player {spotify|spotify-web|itunes}
* @param volume {0-100}
*/
setVolume(player: PlayerName, volume: number)
/**
* Increments the players volume by a number
* @param player {spotify|spotify-web|itunes}
*/
volumeUp(player: PlayerName)
/**
* Decrements the players volume by a number
* @param player {spotify|spotify-web|itunes}
*/
volumeDown(player: PlayerName)
/**
* Mutes the players volume
* @param player {spotify|spotify-web|itunes}
*/
mute(player: PlayerName)
/**
* Unmutes the players volume
* @param player {spotify|spotify-web|itunes}
*/
unmute(player: PlayerName)
/**
* Unmutes the players volume
* @param player {spotify|spotify-web|itunes}
*/
setItunesLoved(loved: boolean)
/**
* Save tracks to your liked playlist
* @param trackIds (i.e. ["4iV5W9uYEdYUVa79Axb7Rh", "1301WleyT98MSxVHPZCA6M"])
*/
saveToSpotifyLiked(trackIds: string[]): Promise<CodyResponse>
/**
* Remove tracks from your liked playlist
* @param trackIds (i.e. ["4iV5W9uYEdYUVa79Axb7Rh", "1301WleyT98MSxVHPZCA6M"])
*/
removeFromSpotifyLiked(
trackIds: string[]
): Promise<CodyResponse>
/**
* Returns the playlists for a given player
* @param player {spotify|spotify-web|itunes}
* @param (optional) {limit, offset, all}
*/
getPlaylists(
player: PlayerName,
qsOptions: any = {}
): Promise<PlaylistItem[]>
/**
* Get the full list of the playlist names for a given player
* @param player {spotify|spotify-web|itunes}
* @param qsOptions (optional) {limit, offset}
*/
getPlaylistNames(
player: PlayerName,
qsOptions: any = {}
): Promise<string[]>
/**
* Launches a player device
* @param playerName {spotify|spotify-web|itunes}
* @param options (spotify-web only) {playlist_id | album_id | track_id }
*/
launchPlayer(playerName: PlayerName, options: any = {})
/**
* Plays a Spotify track within a playlist.
* It will also launch Spotify if it is not already available by checking the device Ids.
* @param trackId (optional) If it's not supplied then the playlistId must be provided
* @param playlistId (optional) If it's not supplied then the trackId must be provided
* @param playerName (optional) SpotifyWeb or SpotifyDesktop
*/
launchAndPlaySpotifyTrack(trackId: string = "", playlistId: string = "", playerName: string = PlayerName.SpotifyWeb)
/**
* Plays a Spotify Mac Desktop track within a playlist.
* It will also launch Spotify if it is not already available by checking the device Ids.
* @param trackId (optional) If it's not supplied then the playlistId must be provided
* @param playlistId (optional) If it's not supplied then the trackId must be provided
*/
playSpotifyMacDesktopTrack(trackId: string = "", playlistId: string = "")
/**
* Returns available Spotify devices
* @returns {Promise<PlayerDevice[]>}
*/
getSpotifyDevices(): Promise<PlayerDevice[]>
/**
* Returns the genre for a provided arguments
* @param artist {string} is required
* @param songName {string} is optional
* @param spotifyArtistId {string} is optional (uri or id is fine)
*/
getGenre(
artist: string,
songName: string = "",
spotifyArtistId: string = ""
): Promise<string>
/**
* Returns the spotify genre for a provided arguments
* @param artist {string} is required
*/
getSpotifyGenre(artist: string): Promise<string>
/**
* Returns the highest frequency single genre from a list
**/
getHighestFrequencySpotifyGenre(genreList: string[]): string
/**
* Returns the spotify genre for a provided arguments
* @param spotifyArtistId {string} is required (uri or id is fine)
*/
getSpotifyGenreByArtistId(spotifyArtistId: string): Promise<string> {
/**
* Returns the recent top tracks Spotify for a user.
*/
getTopSpotifyTracks(): Promise<Track[]>
/**
* Returns the audio features of the given track IDs
* @param ids these are the track ids (sans spotify:track)
*/
getSpotifyAudioFeatures(
ids: string[]
): Promise<SpotifyAudioFeature[]>
/**
* Create a playlist for a Spotify user. (The playlist will be empty until you add tracks.)
* @param name the name of the playlist you want to create
* @param isPublic if the playlist will be public or private
* @param description (Optioal) displayed in Spotify Clients and in the Web API
*/
createPlaylist(
name: string,
isPublic: boolean,
description: string = ""
)
/**
* Deletes a playlist of a given playlist ID.
* @param playlist_id
*/
deletePlaylist(playlist_id: string): CodyResponse
/**
* Follow a playlist of a given playlist ID.
* @param playlist_id (uri or id)
*/
followPlaylist(playlist_id: string): CodyResponse
/**
* Replace tracks of a given playlist. This will wipe out
* the current set of tracks and add the tracks specified.
* @param playlist_id
* @param track_ids
*/
replacePlaylistTracks(
playlist_id: string,
track_ids: string[]
)
/**
* Add tracks to a given Spotify playlist.
* @param playlist_id the Spotify ID for the playlist
* @param tracks Tracks should be the uri (i.e. "spotify:track:4iV5W9uYEdYUVa79Axb7Rh")
* but if it's only the id (i.e. "4iV5W9uYEdYUVa79Axb7Rh") this will add
* the uri part "spotify:track:"
* @param position The position to insert the tracks, a zero-based index.
*/
addTracksToPlaylist(
playlist_id: string,
tracks: string[],
position: number = 0
)
/**
* Remove tracks from a given Spotify playlist.
* @param playlist_id the Spotify ID for the playlist
* @param tracks Tracks should be the uri (i.e. "spotify:track:4iV5W9uYEdYUVa79Axb7Rh")
* but if it's only the id (i.e. "4iV5W9uYEdYUVa79Axb7Rh") this will add
* the uri part "spotify:track:"
*/
removeTracksFromPlaylist(
playlist_id: string,
tracks: string[]
)
/**
* Returns whether or not the spotify access token has been provided.
* @returns <boolean>
*/
requiresSpotifyAccessInfo(): boolean
/**
* Deprecated - use "getTrack(player)"
*/
getPlayerState(player: PlayerName): Promise<Track>
/**
* Deprecated - use "getRunningTrack()" instead
*/
getCurrentlyRunningTrackState(): Promise<Track>
/**
* Deprecated - please use "getPlayerState"
*/
getState(player: PlayerName): Promise<Track>
/**
* Deprecated - please use "launchPlayer('spotify')"
**/
startSpotifyIfNotRunning()
/**
* Deprecated - please use "launchPlayer('itunes')"
*/
startItunesIfNotRunning()
/**
* Deprecated - please use "isSpotifyRunning" or "isItunesRunning"
*/
isRunning(player: PlayerName): Promise<boolean>
/**
* Deprecated - please use "setRepat(player, repeat)"
*/
repeatOn(player: PlayerName)
/**
* Deprecated - please use "setRepat(player, repeat)"
*/
repeatOff(player: PlayerName)
/**
* Deprecated - please use "unmute(player)"
*/
unMute(player: PlayerName)
/**
* Deprecated - please use "setConfig(config: CodyConfig)"
* Set Credentials (currently only supports Spotify)
* Accepted credentials: clientId, clientSecret, refreshToken, accessToken
* @param credentials
*/
setCredentials(credentials: any)
/**
* Deprecated - please use "getSpotifyAccessToken()"
* Get the accessToken provided via through the setCredentials api
* @returns {string} the access token string
*/
getAccessToken()