-
Notifications
You must be signed in to change notification settings - Fork 1
Backend Documentation
This is a technical documentation of the current state of the application. The backend of the application is somewhat complex, so we will split it up into a few different parts: discovery, joining a jam, and jam modification.
This application is designed as a fairly simple client/server system. The app communicates between phones using HTTP, and each phone is running a basic, embedded HTTP server called NanoHTTPD. HTTP requests are sent between phones using the async HTTP client for Android LoopJ.
Jam and music library metadata are maintained in a SQLite database on each phone and each jam has a spoke-hub architecture. Any changes made to the jam or library by the clients are sent to the master and the master processes changes to the jam and library serially and propagates the changes to all of the clients in the jam.
Discovery refers to the ability of devices connected to a network to discover each other. For us this means the ability of phones to discover the IP/port pair of the server running on the master phone. This was one of the biggest technical challenges while developing the application because there is not a particularly good way of doing discovery in Android that will work on all Android devices. We initially wanted to use UDP multicast to perform discovery, but we quickly found that the Nexus 5 filters out multicast packets in the kernel. We tried using Android's Network Service Discovery API, but we realized that it didn't work well and was prone to crashing. Then we tried using a known server to perform discovery, but we ran into problems trying to provide a unique fingerprint for a wifi network.
Recently we settled on Android's newish Wifi P2P API to perform discovery. In particular, we decided to use its Wifi Direct Service Discovery API. So far it has worked fairly well, although it is not perfect, and notably, it will not work with other platforms (e.g. iOS or Windows).
In order for phones to be able to discover each other, the master phone (that creates the jam) must broadcast a local service. The master constructs a TXT record containing information such as the local IP address of the device, the port that the local server is running on, and the name of the jam session. This TXT record is registered using the API and broadcast to any phone which is looking for local jams.
Phones which want to discover jams hosted by phones on the network register a listener for TXT records. As valid jam session TXT records, the IP and port are extracted and a ping message is sent to the address to see if the jam is still alive and on the same network. If a 200 OK response is received then the jam is considered to be discovered successfully and a message is sent to the UI to display it.
Once a jam has been discovered the next step is to 'join' it. This step involves exchanging metadata about the existing library and library of the new phone.
First the client that wants to join sends a message to the master's /requestJoinJam endpoint. This prompts the master to accept or reject the new phone. If the master user accepts the request then a message is sent to the client indicating that it can join.
Next the client sends a message to request the current state of the shared library from the master. The master gets the current library from its database and serializes it as JSON, sending it back to the client. At the same time, the master sends a request to the client to get a copy of its local library as JSON. When the master gets the music from the client, it adds the music to the shared library and also forwards this music to all of the client phones that are already in the jam (sending a message to the /updateLibrary endpoint).
At this point the libraries are synced between the phones and the client is now a part of the jam, but the master continues to send messages to the client to request album art. The loading of album art occurs asynchronously, and the responses to these messages are also forwarded to other clients so that all album art is effectively exchanged between all devices.
The master phone tracks which clients that have joined the jam and are still active using a regular heartbeat. When the master phone pings a client but it does not return a 200 OK, that client is considered out of the jam and the master removes all of its music from the shared library and jam and sends a message to each client's /removeAllFrom?ip endpoint.
Any phone can modify the jam at any time and the jam stays in sync. This includes adding songs, deleting songs, and reordering songs. Requests to modify the jam are sent as messages to the master phone's server API (documented below).
When the master receives a request to modify the jam it executes a SQL statement on the relevant SQLite table. These modification are serialized by Android, so concurrent modification requests can be handled without serious problems. After the modification is made the master propagates the changes made to the jam to all clients by sending a POST request to each client's /updateJam endpoint containing the jam serialized as JSON.
The server is documented in the source file, but I will also document the API here.
/getLocalLibrary
- Returns 200 OK and the current library on the phone as a JSON object. This object has four fields: username, ip, port, and jam, as well as a json list of artists.
/getAlbumArt
- Returns 200 OK and the album art in the library as a JSON object.
/ping
- Returns 200 OK.
/isMaster
- Returns 200 if the phone is hosting a jam. Used to detect live jams when performing discovery.
/song/{path}
- Returns 200 on success and returns an mpeg/audio stream of the song. Path should be the path to the desired song.
/joinJam?port={port}&username={username}
- Indicates that a new client wants to join the jam hosted here. Returns 200 on success. The port should be the port of the server on the client requesting to join the jam.
/acceptJoinJam?port={port}&jamName={jamName}
- Indicates that a master has accepted a request made by this device to join the jam. Returns 200 on success. The port should be the port of the server on the master that has accepted the request and the jamName should be the name of the jam hosted on the master.
/rejectJoinJam
- Indicates that a master has rejected a request made by this device to join the jam. Returns 200 on success.
/getJam
- Returns 200 on success along with a JSON object representing the jam. Should be called by the client on the master to request the current state of the jam.
**/removeAllFrom?ip={ipAddr}
- Removes all songs from the phone associated with the provided IP address. Used by the master to remove users from the jam. Returns 200 on success.
/jam/add?songId={id}&addedBy={username}&jamSongId={jamId}
- Adds the song to the jam. The songId should be a unique hash of the song, the jamSongId is a timestamp that refers to when the song was added added. Returns 200 on success.
/jam/set?jamSongId={id}
- Sets the currently playing song of the jam to the song specified by the id. Returns 200 on success.
/jam/move?jamSongId={id}&to={index}
- Moves a song to the index specified. Returns 200 on success.
/jam/remove?jamSongId={id}
- Removes a song from the jam. Returns 200 on success.
/jam/start
- Starts playing the current song in the jam. Returns 200 on success.
/jam/pause
- Pauses the currently playing song in the jam. Returns 200 on success.
/jam/restart
- Restarts the currently playing song in the jam. Returns 200 on success.
/updateLibrary
- Accepts a JSON representation of the jam and returns 200 on success.
/updateAlbumArt
- Accepts a JSON list of the album art for a library. Sets the album art for each album in the list. Returns 200 on success.
/updateJam
- Accepts a JSON representation of the jam and updates the jam correspondingly. Returns 200 on success.