Skip to content

Instantly share code, notes, and snippets.

@adammw
Last active January 1, 2016 13:09
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save adammw/bd90d308d2c59cefeefb to your computer and use it in GitHub Desktop.
Save adammw/bd90d308d2c59cefeefb to your computer and use it in GitHub Desktop.

TODOs / QUESTIONS / NOTEs:

  • Include the revision somewhere in the Playlist change events
  • Consider whether it's better to fire off multiple events (ie. one per item) or just one with an array (ie. one per revision)
  • Work out how to best expose each class to the user, as most (if not all) of them will require a reference back to the Spotify object to communicate with the server
  • Work out how items returned from metadata e.g. Artists and Albums for a Track, Tracks for a Playlist or RadioStation, get converted into their relevant objects.
  • Work out if it is feasble to no longer inherit/extend from the protobuf-generated classes, especially if all the logic is self contained within each relevant class (ie. get rid of logic from spotify.get() and spotify.playlist() and either remove them completely or make them into helper/backwards-compat stubs that forward it to the class to do). Ideally, this should separate the logic out so that the only thing that the Spotify class is really concerned about is communication and login.
  • Also, if we are moving the logic for parsing protobufs to the track class, etc. then we probably don't need the tag function (or at least not in its current form) as each class will interact directly with the sendCommand / sendProtobufRequest api.
  • Decide if the current login logic is better or if it is advantageous to decouple authentication from connection.
  • Make sure the API calls allow fn(err, result) style callbacks so we can get rid of all the exception throwing (or at least keep it to a minimum)
  • Work out how to do multi-get requests. Some sort of parent collection object? But then how would that work or how would users use it in a nice way.
  • Look at which callbacks should be optional, and which shouldn't, and document them correctly. Any functions without side-effects (e.g. getting metadata) should require a callback.
  • Should we separate Hermes/Mercury communication into a separate object/file? What advantage would this have?
  • Could the Schemas object be simplified by developing for protobufjs first and doing wrapping around protobuf? (Performance metrics probably required)
  • Work out where new features fit in the scheme of things, e.g. Inbox, Notifications, Messages, Users, Social Sharing, Suggested followers, Discover, Browse, Recently Played Artists, Last.fm scrobbling, Top lists (albums, tracks, etc.)

Spotify

Spotify.login(username, password, [fn])

Helper function, creates new Spotify instance and performs spotify.login

Class: Spotify

Event: 'connect'

Event: 'login'

Event: 'message'

function(command, args) {}

Emitted when there is a Spotify "message" from the server to the client.

By default, this is internally tied to spotify._onmessagecommand to automatically handle "do_work" and other messages.

Event: 'error'

spotify.login(username, password, [fn])

Creates the connection to the Spotify Web websocket server and logs in using the given Spotify username and password credentials.

spotify.anonymousLogin([fn])

Creates the connection to the Spotify Web websocket server and logs in using an anonymous identity.

spotify.facebookLogin(fbuid, token, [fn])

Creates the connection to the Spotify Web websocket server and logs in using the given Facebook App OAuth token and corresponding user ID.

spotify.connect([fn])

Sends the "connect" command.

Should be called once the WebSocket connection is established.

spotify.disconnect([fn])

Closes the WebSocket connection of present. This effectively ends your Spotify Web "session" (and derefs from the event-loop, so your program can exit).

spotify.get(uris, fn)

spotify.metadata(uris, fn)

Retrieves the object for one or more track, album, artist or playlist spotify uris. If multiple uris are specified then an array of objects will be returned.

(Internally calls Track.get / Album.get / Artist.get / Playlist.get depending on the uri type)

spotify.playlist(uri, [from], [length], fn)

Backwards compatibility / helper function. Creates a playlist object for the uri, requests

(QUESTION: Should this be removed ?)

spotify.rootlist(uri, [from], [length], fn)

(QUESTION: Should this be removed ?)

(QUESTION: If rootlist is removed, should it be replaced by some helper function to help get the rootlist? Or should that be under the playlist class as a static method)

spotify.search(opts, fn)

Executes a "search" against the Spotify music library. Note that the response is an XML data String, so you must parse it yourself.


Playlist

Playlist.get(uris)

Creates a new Playlist instance with the specified uri, or in the case of multiple uris, creates an array of new Playlist instances.

Playlist.create([attrs], [fn])

Create a new Playlist

Class: Playlist

Event: 'change'

function(modifications) { }

Fired when any part of the Playlist changes on the server

When a listener is attached to this event, a subscription is automatically created

playlist.type (getter)

Playlist type, determined from its' uri

One of 'playlist', 'rootlist', 'publishedrootlist', 'starred'

playlist.uri (getter)

Return the spotify uri of the Playlist

playlist.attributes([revision], [fn])

Retrieve the playlist attributes

Event: 'change'

function(new_attributes, old_attributes) { }

Fired when the attributes of the Playlist changes on the server

playlist.contents([revision], [offset], [length], [fn])

Retrieve the playlist contents

Event: 'add'

function(item, index) { }

Fired for each new item added

Event: 'mov'

function(item, newIndex, oldIndex) { }

Fired for each new item moved

Event: 'rem'

function(item) { }

Fired for each new item removed

Event: 'mod'

function(item, new_attributes, old_attributes) { }

Fired for each new item modified

Event: 'change'

Fired on any of the four above events

playlist.add(items, [append], [fn])

Add an item to the playlist

playlist.remove(item, [fn])

Remove an item from a playlist

playlist.modify(attrs, [fn])

Modifies the attributes of the Playlist

playlist.delete([fn])

Deletes the Playlist represented by the Playlist instance.

playlist.subscribe([fn])

Subscribe to modification notifications from the server

Required for any of the events to work correctly

When the object detects a new listener on any of the events it will automatically subscribe to notifications

playlist.unsubscribe([fn])

Unsubscribe from modification notifications from the server

When the object detects all listeners have been removed and subscribe was not called manually, then unsubscribe will be called automatically.

playlist.getCurrentRevision([fn])

Get the current revision from the server

playlist.diff(revision)

Get the differences between the latest revision and the revision specified.

playlist.publish([fn])

playlist.follow([fn])

Publishes / Follows a Playlist

This is a helper function that adds the current playlist to the user's publishedrootlist

playlist.unpublish([fn])

playlist.unfollow([fn])

Unpublishes / Unfollows a Playlist

This is a helper function that removes the current playlist to the user's publishedrootlist

PlaylistContents

Class: PlaylistContents

contents[]

Returns the Playlist Item at the offset

contents.playlist (getter)

Returns the instance of the Playlist to which the PlaylistContents instance belongs to

contents.revision

contents.length

contents.offset

PlaylistItem

PlaylistItem.create(uri, [attributes])

Class: PlaylistItem

Event: 'mov'

function(newIndex, oldIndex) { }

Fired when the item is moved within the Playlist

Event: 'mod'

function(new_attributes, old_attributes) { }

Fired when the item's attributes are modified

Event: 'rem'

function() { }

Fired when the item is removed from the Playlist

Event: 'change'

Fired on any of the four above events

item.parent (getter)

Returns the instance of the PlaylistContents to which the PlaylistItem instance belongs to

item.revision (getter)

Returns the revision of the parent PlaylistContents instance

item.attributes

Event: 'change'

function(new_attributes, old_attributes) { }

Fired when the attributes of the item changes on the server

item.uri

item.get([fn])

Retrieve the object specified by the URI.

For example, in the typical case a Playlist will contain track URIs, and therefore this function will return a Track instance

PlaylistRevision

PlaylistRevision.Parse(buffer)

Class: PlaylistRevision

revision.number

revision.hash

revision.toString()


Track

Track.get(uris, [getMetadata], fn)

Creates a new Track instance with the specified uri, or in the case of multiple uris, creates an array of new Track instances.

Instances will only contain a URI and will not have metadata populated.

Class: Track

Track(uri) (constructor)

Equivalent to Track.get(uri)

Track(obj) (constructor)

???

track.uri (getter / setter)

Converts the internal GID to the Track URI

track.id (getter / setter)

Converts the internal GID to the Track Hex ID

track.audioUrl([format], [transport], fn)

Gets the audio URL for the given Track object.

Format can be one of 'MP3_96' (30 second preview) or 'MP3_160' (default).

Transport can be one of 'http' (default) or 'rtmp'.

track.get([refresh], [fn])

track.metadata([refresh], [fn])

Returns the metadata for the Track to the specified callback.

If refresh is specified and truthy, then a new request will be made for the data regardless if the metadata has already been loaded.

track.similar([fn])

Retrieve suggested similar tracks to the given track URI

track.isAvailable([country])

Checks if the given Track object is "available" for playback, taking account for the allowed/forbidden countries, the user's current country, the user's account type (free/paid), etc.

track.recurseAlternatives([country], fn)

Checks if the given "track" is "available". If yes, returns the "track" untouched. If no, then the "alternative" tracks array on the "track" instance is searched until one of them is "available", and then returns that "track". If none of the alternative tracks are "available", returns null.

track.play([dontSendEventsAutomatically], [fn])

Begins playing the track

Callback signature: function(err, stream) { }

If callback is not specified return a PassThrough stream, listeners should listen for the error event on it as well.

(QUESTION: Should we add a 'start'/'success'/'play' event to the PassThrough stream as well so the stream can be piped once we know it is valid? Or just tell people to use the callback-API?)

track.preview([dontSendEventsAutomatically], [fn])

Begins playing the track preview

Callback signature: function(err, stream) { }

track.sendTrackEnd()

track.sendTrackEvent()

track.sendTrackProgress()

(These are for if a client really needs to do it manually, I'm hoping these events can be automatically sent according to AudioStream, even if it's not entirely correct as we don't know the stream source.)

AudioStream

Class: AudioStream

stream._res

Access the underlying response object

stream.format

The format of the stream (e.g. MP3 160k or MP3 96k)

stream.pause()

Pauses the stream and sends the track paused event

stream.play()

Pauses the stream and sends the track play event

stream.stop()

Stops the stream and sends the track end event


Album

Album.get(uri)

Creates a new Album instance with the specified uri, or in the case of multiple uris, creates an array of new Album instances.

Note that this function doesn't actually trigger a request to the server, and just creates an empty instance. Use track.get() to actually request data.

Class: Album

album.uri (getter)

album.get([fn])

album.metadata([fn])

Returns the metadata for the Album


Artist

Artist.get(uri)

Creates a new Artist instance with the specified uri, or in the case of multiple uris, creates an array of new Artist instances.

Note that this function doesn't actually trigger a request to the server, and just creates an empty instance. Use track.get() to actually request data.

Class: Artist

artist.uri (getter)

artist.get([fn])

artist.metadata([fn])

Returns the metadata for the Artist


Image


RadioStation

RadioStation.get(uri)

Returns a RadioStation instance for the specified URI (Station ID)

RadioStation.create(seeds, attrs, [fn])

Creates a new RadioStation given the Seed URIs and attributes

Class: RadioStation

station.getTracks([lastTracks], [length], fn)

station.sendFeedback(uri, type, [fn])


HermesRequest

Class: HermesRequest

HermesRequest(args)

request.addSubrequest(request)

request.setRequestSchmea(schema)

request.setResponseSchema(schema)

request.send([data], fn)


HermesResponse

Class: HermesResponse

response.contentType

response.statusCode

response.isSuccess

response.isRedirect

response.isClientError

response.isServerError

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment