Trying to come up with a good playlist for a party can be a real challenge. Are the guests going to be more into Metallica or Rise Against? Is everybody going to be bored when the third Tool song comes on in a row, or are they feeling it?
Well, what if the guests could vote American Idol style for the next song that they want to hear? This way the music of the party could be set by the guests and match their current mood. Everyone would be given the choice between three songs as to what they want to hear next. The song that receives the most votes is played and three new song choices are picked for the guests to vote on.
Web Sockets
To build the project, I’m going to use a fairly new technology called web sockets. Web sockets allow for two way, asynchronous communication between the browser and the server. This is great news for our program because this means that the browser can notify the server of any votes that the user casts and the server can notify the browser when the song choices have changed.
Using the python tornado web socket server, the basic set up of a program looks like this:
class WSHandler(tornado.websocket.WebSocketHandler):
def open(self):
print 'new connection'
def on_message(self, msg):
print 'message received'
def on_close(self):
print 'connection closed'
application = tornado.web.Application([
(r'/ws', WSHandler)
])
if __name__ == "__main__":
http_server = tornado.httpserver.HTTPServer(application)
http_server.listen(8888)
tornado.ioloop.IOLoop.instance().start()
The basic program is pretty self- explanatory. We start by defining a class that is going to respond to the various events that are going to occur: open a new connection, receive a message, and close a connection. Next we specify the location that we will be listening to “/ws” and the port that we will be listening on (8888). Finally, we go into an infinite loop, waiting for one of the events to happen.
On the browser, we can connect to the server with a little bit of javascript:
var host = "ws://192.168.1.108:8888/ws";
var socket = new WebSocket(host);
socket.onopen = function() {
alert('Connection Opened');
}
socket.onmessage = function(msg) {
alert(‘Message Received’)
}
socket.onclose = function() {
alert('Connection Closed');
}
This code is fairly similar to the server code. We start by specifying the location of the server and then we handle the various events that can happen: opening a new connection, receiving a message, and closing a connection.
Sending a message from the server is pretty easy (from within the WSHandler class):
self.write_message(‘message’)
Sending a message from the client is also really easy:
socket.send(‘message’);
Sending Song Choices
The first step is that we need to determine what songs are in our library. To do this, we take in a path and then read all of the files in that directory and call that our list of songs:
class Song(object):
def __init__(self, path):
self.path = path
self.name = os.path.splitext(os.path.basename(path))[0]
class SongLibrary(object):
def __init__(self, path):
self.songs = {}
for file in os.listdir(path):
song = Song(path + '/' + file)
self.songs[song.name] = song
songLibrary = SongLibrary('/home/pi/Music')
Now we can randomly select 3 songs and send them out to the clients:
def set_choices(self, choices = 3):
self.votes = {}
self.voted = {}
names = list(self.songs.keys())
while len(self.votes) < choices:
idx = random.randint(0, len(self.songs) - 1)
name = names[idx]
if not name in self.votes:
self.votes[name] = 0
for client in self.clients:
client.send_songs()
Then on the client, we can receive and display those songs:
socket.onmessage = function(msg) {
var data = JSON.parse(msg.data);
switch (data.type) {
case 'update':
displaySongs(data.songs);
break;
default:
displayHeader('Unknown result from server');
break;
}
}
function displaySongs(songs) {
displayHeader('Which song would you like to hear next?');
$.each(songs, function(idx, song) {
$('<button>' + song + '</button><br/>').click(function() { vote(song); }).appendTo('#info');
});
}
function displayHeader(header) {
$('#info').empty();
$('#header').html('<h1>' + header + '</h1>');
}
Which will look like this:
Voting for a Song
Next we need to handle when someone votes for a song. To do this, we will add some javascript that will notify the server when the user clicks on one of the buttons:
function vote(song) {
displayHeader('You voted for: ' + song);
socket.send(song);
}
We already tied this into the click event of the button in the displaySongs method above.
Then on the server, we need to handle a client voting:
class WSHandler(tornado.websocket.WebSocketHandler):
def send_songs(self):
songs = ','.join(['"%s"' % song for song in self.library.get_choices()])
self.write_message('{ "type" : "update", "songs" : [ %s ] }' % songs)
def send_results(self):
results = ','.join(['{ "name" : "%s", "count" : "%s"}' % (name, count) for (name, count) in self.library.get_results()])
self.write_message('{ "type" : "vote", "results" : [ %s ] }' % results)
def vote(self, song):
ip = self.request.remote_ip
response = self.library.vote(song, ip)
if response:
self.send_error(response)
else:
self.send_results()
def on_message(self, msg):
self.vote(msg)
class SongLibrary (object):
def vote(self, song, ip):
if song in self.votes:
if ip in self.voted:
return 'You have already voted'
else:
self.voted[ip] = song
self.votes[song] += 1
return ""
else:
return 'Unknown song - %s' % song
def get_results(self):
return self.votes.items()
This will tally up the votes that are received from the client. Also, it will send the current results to the client, so they can see how their favorite song is doing:
Playing the Song
The last problem is actually playing the most popular song. This is pretty easy to accomplish with the pygame library:
class SongLibrary(object):
def get_next_song(self):
return max(self.votes.iterkeys(), key=(lambda key: self.votes[key]))
def play_song(self, song):
print 'Playing: ' + song
pygame.mixer.music.load(song)
pygame.mixer.music.play()
while pygame.mixer.music.get_busy():
pygame.time.delay(100)
def main_loop(self):
pygame.init()
pygame.mixer.init()
self.set_choices()
while 1:
song = self.get_next_song()
self.set_choices()
self.play_song(self.songs[song].path)
The full code is attached.
Summary
We created an automated DJ for our next party that allows the guests to vote on their favorite song and determine what is going to be played next. The website can be accessed by any browser that supports web sockets.