Bemix Notes

From SlugWiki
Revision as of 19:03, 5 February 2006 by Rob (Talk) (Philosophy and Design Goals)

Jump to: navigation, search

Bemix is a web-based media player. It supports multiple soundcards and various networked client types, allowing a group to use a centralized server to control several speakers. Commands are issued through URIs, where the resource portion of the address represents the command to be issued, while the query portion of the URI passes in parameters to the server. Multiple audio output devices are connected to a single computer (other configurations are also possible) defining an individual slave. Multiple slaves can join a single bemix server, allowing one server to manage audio playback on multiple devices on multiple computers.

Overview

The current implementation of the bemix system requires python2.3, mplayer, and an instance of MySQL. The server is designed to serve HTTP requests over port 9087. Any number of clients may gain access to the state of the bemix system and may issue commands to the server through query strings passed to the server in URIs. Communication between the server and clients is governed by the bemix client protocol.

The server acts as a central manager for any reasonable number of slaves that have joined with the server. The system supports multiple audio output devices on a single slave, and the server is capable of handling multiple slaves. Whenever a client wants to instruct a specific slave to perform a specific action, his request must be sent to the server, which then relays the request to the slave; clients never communicate directly with slaves. Communication between the server and the slaves are governed by the bemix slave protocol.

Philosophy and Design Goals

The central philosphy of the bemix system is that music should be a group experience. To this end, the bemix system was designed to support a variety of music sources and control interfaces. Fundamental to this effort is a simple and reliable message delivery system. HTTP was chosen as the method of choice for sending messages due to its relative simplicity, established dependability, and the prevalence of pre-existing HTTP libraries for a variety of environments. Communicating with any component in the bemix system is as simple as downloading or serving a webpage.

The server-slave architecture enables numerous remote machines to register themselves with a single server, thereby providing a single common interface for access to a wide variety of audio sources. Due to the simple nature of the server-slave architecture, a variety of slaves may be implemented. The base implementation utilizes mplayer for audio playback, but other slaves using xmms, winamp, or other audio players is possible as well. The only requirement for a slave is that it fully implement the bemix slave protocol.

The client-server architecture is even more modularly designed, providing a variety of output formats for different types of clients. Currently html and plaintext 'client' output formats are planned, but as time permits an RDF output format is possible as well. Clients are designed to be either human or machine controlled. For instance, while a typical command-line client exists to allow users to manually send commands to the server, a computer-controlled alarm clock could easily be implemented which would instruct a specific client to play a specific playlist after a certain amount of time has elapsed. The server has no internal notion of an "alarm clock," but provided the bemix bot sends the appropriate URI to the server, the server will respond to the command with no knowledge of where it originates or how it is generated. The switches installed in the bemix bathroom are similarly controlled; a bemix bot listens for a signal from the touch switches and, once received, generates an appropriate URI and sends it to the sever. The bemix server itself has no notion of a "touch switch."

Bemix Server

Of all the components of the bemix system, the server is the least extensible or replaceable. This is likely not a drawback, however, as the bemix system is meant to allow a variety of slave and client types to communicate well with one another across a single server. For most situations, it is counter-productive for there to be multiple bemix servers to be active at the same time, because the central design goal of bemix is to encourage a communal media experience though a single, common access point.

Source files of interest:

  • bemix/bemix-server.py - a small script that is run from the command line to begin the server processes
  • bemix/serverlib.py - a comprehensive library containing various classes and support code for running the bemix server, communicating with slaves, and formatting output for clients
  • bemix/playerlib.py - a library that contains interfaces for players and playlists, as well as the playlist implementation that is used by the server
  • bemix/bemixlib.py - a library that contains several Exceptions that are used by the server, as well as methods to generate and parse errors, and to produce plaintext 'client' results
  • crowell/browser.py - a library that deals with getting directory contents and creating relative and absolute file paths
  • crowell/dictlib.py - a library that contains a method to determine whether or not all of the required elements in a URI are valid

Configuration

The bemix server needs an open port to listen on; by default this port is 9087. It also requires read and write access to a database named 'bemix' where it stores and loads various configuration parameters. In the future an external configuration file and/or a suite of configuration utilities are planned; currently all configuration must be done manually (and at times through direct manipulation of the source code).

Root Music Directory

The bemix server must have access to all the music that is available to any client on the bemix system. In other words, the server controls what music is and is not available to all the slaves connected to it. This decision ensures that a wide variety of music is available to all the slaves on the bemix system, and provides a 'barrier-free' music experience, allowing clients to queue up any song onto any player associated with any slave on the system without needing to know what files are available to the specific slave in question. Therefore, the music source must be available to both the server and all of its slaves through a network filesystem such as NFS, AFS, or CIFS. The server requires the files to live in or to be nested below a single directory on the filesystem, although this requirement can be met by providing symlinks to audio files and directories residing anywhere in the local filesystem hierarchy. Setting up the music shares is outside the scope of this document.

It is important to note that the server does not actually transfer audio files to the slaves and is not capable of providing audio files for download to clients. Each slave must be configured in a manner similar to the server; the slaves must have access to the shared audio files in their local filesystem hierarchy as well.

MySQL Table Schema

The bemix server utilizes a MySQL database to manage playlists, slaves, and other information about the configuration of the server. The following tables must be in place and accessible for the bemix-server to function properly.

Required Tables

+-----------------+
| Tables_in_bemix |
+-----------------+
| data            |
| playlist        |
| slave           |
| track           |
+-----------------+

The MySQL user for the bemix database must have both read and write access to these four tables.

Data Table

+---------------+-------------+------+-----+---------+-------+
| Field         | Type        | Null | Key | Default | Extra |
+---------------+-------------+------+-----+---------+-------+
| variable      | varchar(50) |      | PRI |         |       |
| variableValue | varchar(50) | YES  |     | NULL    |       |
+---------------+-------------+------+-----+---------+-------+

This table stores various configuration variables that are required for the bemix server to function properly. Currently, the only defined variable is "rootDir" which has the value of the absolute path to the root music directory, such as "/mnt/tunes".

Playlist Table

+------------+--------------+------+-----+---------+----------------+
| Field      | Type         | Null | Key | Default | Extra          |
+------------+--------------+------+-----+---------+----------------+
| playlistId | int(11)      |      | PRI | NULL    | auto_increment |
| name       | varchar(100) |      |     |         |                |
+------------+--------------+------+-----+---------+----------------+

This table contains a record for each playlist that is created and automatically assigns each new playlist a unique id that is used as a foreign key the the track table, described below. name stores the name of the playlist (the value should be escaped as if it were part of a URI). When a playlist is deleted, its record in this table is removed as are any associated records in the track table.

Track Table

+-------------+---------+------+-----+---------+----------------+
| Field       | Type    | Null | Key | Default | Extra          |
+-------------+---------+------+-----+---------+----------------+
| trackId     | int(11) |      | PRI | NULL    | auto_increment |
| playlistId  | int(11) |      |     | 0       |                |
| trackNumber | int(11) |      |     | 0       |                |
| file        | text    | YES  |     | NULL    |                |
+-------------+---------+------+-----+---------+----------------+

This table stores the individual tracks that make up a playlist. While each track is individually assigned a unique trackId, this value is currently not utilized by the bemix server. playlistId refers to the playlist with the same id in the playlist table, described above. The index of the song in the playlist is stored in the field trackNumber, while the file path to the actual song is stored in the file field. (This file path is relative to the rootDir defined in the data table, and should be escaped as if it were being sent in a URI.)

Slave Table

+---------+--------------+------+-----+---------+----------------+
| Field   | Type         | Null | Key | Default | Extra          |
+---------+--------------+------+-----+---------+----------------+
| slaveId | int(11)      |      | PRI | NULL    | auto_increment |
| ip      | varchar(100) | YES  |     | NULL    |                |
+---------+--------------+------+-----+---------+----------------+

This table stores information about the slaves that are allowed to join the bemix server. It does not, however, contain any information about which slaves happen to be connected to the server at any particular time. TODO: the ip field currently stores a string of the form 'http://1.2.3.4:9088/' rather than a pure IP address.

Output Formats

The bemix server is capable of exporting data in several formats. The two currently supported formats are plaintext 'client' and html (suitable for viewing through a web browser). Clients are required to understand at least one of the available formats, but do not need to understand more than one. All operations will be available to clients that understand any of the available formats.

Plaintext

output=client The plaintext 'client' format is a simple document consisting of a newline-delimited list of values. The order of the list is not guaranteed, but it can be assumed that all fields that are specified will be present in the document. All fields consist of a variable name represented as a string, followed by an '=' character, followed by the value of the variable. No spaces are present between the variable name, '=' character, and value, although space characters are permitted in the value (including a leading space character). Every value is terminated by a newline '\n' character, including the final line.

The only guaranteed field is the success field, which always takes on values of either 'true' or 'false'. A value of true indicates the operation was successfully carried out; a value of false indicates the operation could not be carried out for some reason. In the event of a failure, the comment and error my optionally be included to indicate the reason for the failure. If present, the value of comment is a human-readable string indicating the reason for the failure, steps to correct it, etc. If present, the error field is a machine-readable string indicating a reason for the failure. The list of legal error codes is interspersed within this document as part of the specification of each command.

A sample plaintext error document:

comment=the slaveId is not currently connected; it was last seen 3 days ago
error=invalid-slaveId
success=false

Bemix Client Protocol

The bemix client protocol is a one-way protocol that defines the form of messages sent to the server and the form of responses sent to the client. The server is never allowed to communicate with the client unless it is in response to a client request. The client sends a command to the server by sending a GET request for a specified resource, and the server sends its reply in the body of the document that is sent in response to the client.

All commands that are sent to the server by a bemix client are encoded in URIs with additional information contained in the query string. In the following documentation, all URIs are relative to the base server address (in our case http://bemix.mit.edu:9087/), and all URIs have an output=[html, client] element in their query string. An example URI that will instruct player 2 on slave 3 to begin playing the current playlist is http://bemix.mit.edu:9087/play?output=client&slaveId=3&playerId=2. The commands that may be sent to the server include getting the slaves that are currently connected, creating a new playlist, and instructing a specific player to mute itself.

In the following examples, a relative URI is listed indicating the form of the URI that is sent to the bemix server in order to accomplish a specific function. Listed after the URI are zero or more error codes along with a brief description of what can cause them. Following the error codes is sample bemix server output for a successful action in the plaintext 'client' output format. The query string indicates the type of the value of the variable as either int representing an integer or string representing an escaped sequence of utf-8 characters. At the end of every command is a paragraph explaining in detail what the command does.

Player

A player represents a single audio output device connected to a slave. There is one player for every audio output device that the bemix server knows about. Before anything can be played on a player, a playlist must be created, filled with songs, and loaded on the player. When a player has finished playing a song, it should automatically play the next song sequentially in the current playlist. If the song that just finished was the last song in the playlist, the player should begin playing the first song in the current playlist. A client never communicates directly with a slave or player; all commands for a specific player are sent via the bemix server which behaves much like a proxy, forwarding the request to the specified slave, and then forwarding the slave's response back to the client.

Play

play?slaveId=int&playerId=int

  • no-playlist-loaded - there is no playlist loaded for the current player
  • illegal-playlistId - the playlist that is currently loaded is no longer valid
  • invalid-slaveId - the slaveId that was specified is not valid
  • invalid-playerId - the playerId that was specified is not valid
success=true

Instructs a player (referred to by its playerId) that is associated with a specific slave (referred to by its slaveId) to begin playing. If there is a playlist already loaded on the player, and the player is currently stopped, the player should begin playing from the beginning of the song that is stopped. If the player is paused, playback should be resumed. If the player is playing, the command should be ignored. The illegal-playlistId error arises if the playlist that is currently loaded on the player has subsequently been deleted and therefore cannot be played.

Pause

pause?slaveId=int&playerId=int

  • no-playlist-loaded - there is no playlist loaded for the current player
  • illegal-playlistId - the playlist that is currently loaded is no longer valid
  • invalid-slaveId - the slaveId that was specified is not valid
  • invalid-playerId - the playerId that was specified is not valid
success=true

Instructs a player to pause or resume the current song. If the player is currently playing then the song is paused. If the player is currently paused then the song is resumed ans begins playing from the position at which it was previously paused. If the player is stopped the command is ignored.

Stop

stop?slaveId=int&playerId=x

  • no-playlist-loaded - there is no playlist loaded for the current player
  • illegal-playlistId - the playlist that is currently loaded is no longer valid
  • invalid-slaveId - the slaveId that was specified is not valid
  • invalid-playerId - the playerId that was specified is not valid
success=true

Instructs a player to stop playing. If the player is currently playing or paused then playback stops and the position in the current song is reset to 0 so that if it is later played, playback begins from the beginning of the song. If the player is currently stopped then the command is ignored.

Toggle

toggleStatus?slaveId=int&playerId=int

  • no-playlist-loaded - there is no playlist loaded for the current player
  • illegal-playlistId - the playlist that is currently loaded is no longer valid
  • invalid-slaveId - the slaveId that was specified is not valid
  • invalid-playerId - the playerId that was specified is not valid
success=true

Instructs a player to toggle its current status. If the player is playing, playback will stop. If the player is stopped, it will begin playing. If the player is paused, it will be resumed.

Next

next?slaveId=int&playerId=int

  • no-playlist-loaded - there is no playlist loaded for the current player
  • illegal-playlistId - the playlist that is currently loaded is no longer valid
  • invalid-slaveId - the slaveId that was specified is not valid
  • invalid-playerId - the playerId that was specified is not valid
success=true

Instructs a player to begin playing the next song. As long as a playlist is loaded, the next song in the playlist begins to play immediately. If the current song is the last song in the playlist, the first song in the playlist begins playing.

Previous

previous?slaveId=int&playerId=int

  • no-playlist-loaded - there is no playlist loaded for the current player
  • illegal-playlistId - the playlist that is currently loaded is no longer valid
  • invalid-slaveId - the slaveId that was specified is not valid
  • invalid-playerId - the playerId that was specified is not valid
success=true

Instructs a player to begin playing the previous song. As long as a playlist is loaded, the previous song in the playlist begins to play immediately. If the current song is the first song in the playlist, the last song in the playlist begins playing.

Load

load?slaveId=int&playerId=int&playlistId=int

  • invalid-playlistId - the playlist that was specified is not valid
  • invalid-slaveId - the slaveId that was specified is not valid
  • invalid-playerId - the playerId that was specified is not valid
success=true

Instructs a player to load a specified playlist. Regardless of the state of the player, playback stops, the playlist is loaded, and the first song in the playlist begins playing immediately. A 'play' command does not have to be sent in order for the player to begin playing when a playlist is loaded.

Unload

unload?slaveId=int&playerId=int

  • invalid-slaveId - the slaveId that was specified is not valid
  • invalid-playerId - the playerId that was specified is not valid
success=true

Instructs a player to unload a playlist if one is currently loaded. This returns the player to the state it is in after it first joins the server. If the player is currently unloaded then the command is ignored. Playback immediately stops for the current player.

Seek

seek?slaveId=int&playerId=int&position=int

  • no-playlist-loaded - there is no playlist loaded for the current player
  • illegal-playlistId - the playlist that is currently loaded is no longer valid
  • invalid-slaveId - the slaveId that was specified is not valid
  • invalid-playerId - the playerId that was specified is not valid
success=true

Instructs a player to play the current song at the indicated position (represented as seconds). As long as a playlist is loaded, the player immediately begins playing the current song at the indiciated position. If the position is less than 0, playback begins at the start of the song. If the position is larger than the length of the song the command is equivalent to a 'next' command.

Set Volume

setVolume?slaveId=int&playerId=int&level=int

  • invalid-slaveId - the slaveId that was specified is not valid
  • invalid-playerId - the playerId that was specified is not valid
success=true

Instructs a player to set its internal volume to a specified level. The level should be between 0 and 100. If level is below 0, it should be set to 0. If level is larger than 100, it should be set to 100.

Adjust Volume

setVolume?slaveId=int&playerId=int&amount=int

  • invalid-slaveId - the slaveId that was specified is not valid
  • invalid-playerId - the playerId that was specified is not valid
success=true

Instructs a player to adjust its internal volume by a specified amount. The amount should be between 0 and 100. If amount is below 0, it should be set to 0. If amount is larger than 100, it should be set to 100. The amound may be negative to instruct the player to reduce the current volume by a certain amount.

Status

player?slaveId=int&slaveId=int

  • invalid-slaveId - the slaveId that was specified is not valid
  • invalid-playerId - the playerId that was specified is not valid
name=string
playlistId=int
index=int
volume=int
secondsElapsed=float
secondsTotal=float
status=int
success=true

Gets the current status of the specified player. name represents the name of the player. playlistId represents the id of the currently loaded playlist, or -1 if there is no playlist loaded. index represents the index number of the currently selected track, or -1 if there is no playlist loaded. volume indicates the current volume level from 0-100. secondsElapsed represents the current position in the current song in seconds. secondsTotal represents the length of the current song in seconds. status represents the status of the player; -1 indicates no playlist loaded, 0 indicates playing, 1 indicates paused, 2 indicates stopped.

Playlist

A playlist consists of a name and music files that are associated with it. The songs in a playlist are ordered with indices starting at 0. Before anything can be played, a playlist must be created and loaded onto a specific player.

Available Playlists

playlists

playlistIds=int,int,int,...,int,
success=true

Gets a list of all the valid playlistIds. This list does not include playlists that have been deleted.

Create

create?name=string

playlistId=int
success=true

Instructs the server to create a new playlist and returns the playlistId of the newly created playlist as playlistId. The newly generated playlistId must be unique and new (no other playlists have the same playlistId, and no playlists may have ever had the same playlistId, even those that have been deleted).

Delete

delete?playlistId=int

  • invalid-playlistId - the playlist that was specified is not valid
success=true

Instructs the server to remove a specified playlist from the list. Once the playlist has been removed it cannot be restored.

Add Track

add?playlistId=int&song=string

  • invalid-playlistId - the playlist that was specified is not valid
  • invalid-song - the song is not valid
index=int
success=true

Adds a specified song to the end of the indicated playlist. The song name should be relative to the rootDir defined by the server; i.e. the client should indicate the song relative to '/'. index indicates the position of the newly added song in the playlist.

Remove Track

remove?playlistId=int&index=int

  • invalid-playlistId - the playlist that was specified is not valid
  • invalid-index - the index specified is not valid for the playlist
success=true

Removes a specified song from a playlist. The song is referred to by its index in the playlist rather than by its name.

Playlist Contents

playlist?playlistId=int

  • invalid-playlistId - the playlist that was specified is not valid
name=string
size=int
song0=string
song1=string
...
success=true

Gets the status of a specified playlist. name indicates the name of the playlist. size indicates the number of songs in the playlist. songN indicates the relative path of the song in the playlist. If the size of the playlist is N, there should be values for song0, song1, ... song(N-1).

Browsing

The server contains a list of all the music files available to any slave connected to it. Therefore the server can handle any requests for file browsing regardless of the slaves that are connected to it at any given time. All operations on files and directories are relative to the server's root music directory, so /A/Air may actually refer to /mnt/tunes/A/Air on the server.

Directory Contents

browse?dir=string

  • invalid-directory - the directory is not accessible by the server
dir0=string
dir1=string
...
file0=string
file1=string
...
success=true

Instructs the server to list the contents of the directory specified by dir. This directory should be specified relative to the server's root music directory. dirN represents the complete relative path of a subdirectory of the directory. fileN represents the complete relative path of a file contained in the directory.

Slaves

A single bemix server can control multiple slaves that are connected to it.

Available Slaves

slaves

slaveIds=int,int,int,...,int,
success=true

Gets a list of the slaveIds for all slaves that are currently connected to the server.

Slave Information

slave?slaveId=int

  • invalid-slaveId - the slaveId is not valid
name=string
playerIds=int,int,int,...int,
success=true

Gets information about the specified slave. playerIds indicates a list of all the valid players that are connected to this slave. Once the slave has joined, the list of playerIds may not be modified unless the slave disconnects and then rejoins the server.

Results

Each command returns a data structure through HTTP that defines variable names and values. A (variable name, value) combination is referred to as a field. Every command contains at least one success field, whose value may be either true or false. If any tag is false, it should be assumed that the entire command failed. Each command may optionally return a comment field as well, whose value gives some indication of why an operation may have failed.

In addition, most commands return additional information about the state of a particular aspect of the bemix server.

Slave Commands

Slave commands are, in general, identical in form to their "server" counterparts defined above. However, there are additional commands which are defined for the bemix server to communicate with its slaves. The slave server understands all commands understood by the server that are listed under "Player", as well as the "players" command.

Additional Server Commands

The bemix server understands additional commands that are meant to be used only by bemix slaves. Clients should not use these commands themselves.

  • playlistSong?playlistId=x&index=y (gets the name of the song at a specified index in a given playlist)

Client Types

Bemix is capable of dumping data in multiple of formats. Currently only the "client" and "html" formats are planned, but XML/RDF formats are possibility in the future. The format of the export is indicated in the URL by the mandatory output parameter.

Client

The "client" output format returns data in a simple newline-delimited list. String values are not delimited by quote characters, and list items are delimited by commas (including a comma after the final element). The format of each line is name=value, where name is the name of the variable being returned, and value is the value of the variable. No whitespace characters are inserted between name, =, and value.

The order in which the fields are returned is not guaranteed, and it should not be assumed that all tags will be present or in any particular order.

The MIME type of the client output is 'text/plain'.

The result of the browse command http://slugwiki.mit.edu:8080/browse?output=client&dir=/P/Porcupine%20Tree

dir0=/P/Porcupine Tree/Coma Divine
dir1=/P/Porcupine Tree/Spiral Circus
dir2=/P/Porcupine Tree/In Absentia
dir3=/P/Porcupine Tree/Insignificance
dir4=/P/Porcupine Tree/Lightbulb Sun
dir5=/P/Porcupine Tree/Metanoia
dir6=/P/Porcupine Tree/On the Sunday of Life
dir7=/P/Porcupine Tree/Recordings
file0=/P/Porcupine Tree/Chloroform (Instrumental Rough Mix).mp3
file1=/P/Porcupine Tree/Disappear (Demo Version 2.0).mp3
file2=/P/Porcupine Tree/Is Not.mp3
file3=/P/Porcupine Tree/2003 Intro Music.mp3
file4=/P/Porcupine Tree/Even Less, Part 2.mp3
file5=/P/Porcupine Tree/London.mp3
success=true

The result of the playlists command http://slugwiki.mit.edu:8080/playlists?output=client

ids=32,30,31,
success=true

The result of the players command with an invalid playerId http://slugwiki.mit.edu:8080/player?output=client&playerId=2

success=false
comment=playerId not found
success=true

HTML

When dumping data as HTML, bemix creates pages that can also be used to issue commands to the server. For instance, a browse page generates a link for each directory that is being displayed that points to the browse page for that subdirectory.

HTML output is meant to be used through a webbrowser. If you're interested in manipulating the bemix server through some other means, use the "client" output format described above.

Todo

The code base needs to be cleaned up substantially before the 1.0 release of the software. Currently an alpha version, the code works, but is messy.

Beta Version

The beta version will contain various improvements over the current version. This version will add support for slave clients, and will permit slaves to drop in and out at will.

bemix-server

  • The handler for the bemix server parses the incoming url into an action, an output type, and a dictionary of variables. These are then given to manager.ServerManager.
  • Supports bemix-slaves from dropping in and out dynamically, so computers can be turned off and on without restarting the bemix system
  • Deals with browsing and playlist administration as well

manager.ServerManager

  • Maintains a list of available bemix-slave instances, their available players, and the available playlists
  • Contains a single method performAction(action, output="client", vars={}) that calls the appropriate method in the many command classes

bemixlib

  • Contains methods that are used by manager.ServerManager to perform actions that bemix-server responds to (methods accept an output type and the vars dictionary)

bemix-slave

  • All slaves must have access to the identical music shares (achieved externally via samba, afs, etc)
  • A running http server that responds to messages from the bemix-server it is registered with only (done via IP address checking)
  • Maintains internal state of volume, secondsElapsed, secondsTotal, status, index, and playlistId
  • Must query bemix-server to get the filename and index of the next song it is to play (performed through queries such as playlist-next?playerId=0&slaveManagerId=2&output=slave which returns something like "song=/P/Porcupine Tree/London.mp3\nindex=5\n")

Overall Improvements

  • create a SlaveManager to drop-in for the normal manager to be used in the slaves
  • replace the current dispatching in bemix-server with a model that passes in all of the variables
  • find a python module to parse the query string in a URL (rather than the current homebrew method)
  • replace the current server+players & slaveServer+players with server+slavePlayers, where slavePlayers are created on both the local and remote machines -- might create too many open ports; could create a slave manager that has the open port and works like current server (controls the various local players itself), doesn't have to be done over http?

See Also

  • Bemix - Overview information about the Bemix music system