Skip to page content

Saving Pandora Songs to Rdio

I flew to Austin the other weekend for the Austin Music Hackathon and built a quick Chrome extension called “Pandora to Rdio“. (Lame name, I know.) It adds any track you “thumbs up” in Pandora to an Rdio playlist. Take the pain out of creating playlists by letting Pandora do the heavy lifting of finding music you like. Download the extension/grab its source code on my Github page.

Motivation

Pandora is great at finding music I like, but it streams one track after another without any ability to repeat a song. Rdio is great at letting me pick and choose songs I want to listen to and repeat them as often as I like, but—despite its name—does a lousy job of finding me new music. I wanted to get a peanut butter chocolate situation going and combine the good parts of each service.

Revisiting Pandora

I created a somewhat popular Firefox extension called Harmony a few years back that automatically scrobbled songs you listened to on Pandora to Last.fm. It used an undocumented Pandora Javascript API that ended up being removed during one of Pandora’s redesigns (subsequently breaking my extension). I’d been meaning to tinker around with Pandora again since then, and what better time than at a music hackathon?

The latest version of the Pandora web player is in HTML, so hooking into it is straightforward. The Chrome Developer Tools came in handy, I simply right-clicked on the “thumbs up” button, hit “Inspect”, and had its CSS class name. I did the same for the song’s name and artist. With those, I used a few lines of jQuery to add an event listener to the “thumbs up” button and extract the values for song title and artist.

A screenshot of the Chrome Developer Tools inspecting the Pandora website

The trickier part was working with oAuth in a browser extension. The Chrome Extension documentation has a page dedicated to it, and provides a helper library that makes the oAuth dance a breeze. Well, except that if you use the code as provided, you’ll be presented with an error in your Javascript Console:

Refused to execute inline script because it violates the following Content Security Policy directive: "script-src 'self' chrome-extension-resource:". - chrome_ex_oauth.html:19

The reason for this is that the Chrome security policy doesn’t allow for HTML pages in extensions to have inline Javascript. The workaround is to move the small amount of Javascript code in chrome_ex_oauth.html into a new Javascript file. I called mine oauth_helper.js:

$(document).ready(function() {
ChromeExOAuth.initCallbackPage();
});

Here’s my updated chrome_ex_oauth.html and oauth_helper.js for reference.

Invisible Interfaces and Room for Improvement

When you install the extension, one of the things you’ll notice is that there isn’t anything to notice. Unlike most other extensions, Pandora to Rdio doesn’t add buttons or any other elements to the browser’s chrome. This was a conscious decision. The interface is Pandora itself. Like a song? Hit the thumbs up button in Pandora, the extension will silently take care of adding the song to your Rdio playlist. It’ll even create the special “Pandora Favorites” playlist automatically if it doesn’t exist. An ideal invisible interface only disturbs the user for errors the application couldn’t deal with on its own, so you won’t find any “successfully added track” type notifications. Success is the default, not something that needs to be called out every time. Unfortunately, Pandora to Rdio isn’t an ideal invisible interface and while it doesn’t disturb you with “successfully added track” type notifications, it also doesn’t call out errors properly. This is something I wanted to work on, but ended up running out of time.

Unfortunately, it’s also not packaged for distribution to non-technical users since each person needs to sign up for their own (free) Rdio Developer account and enter their Rdio API credentials in the background.js file. I wrote up step-by-step install instructions that should help make things easier.

Despite a few rough edges, I think the project is still a useful starting point for more elaborate hackathon projects. You can grab the source code on my Github page. If you end up using it to build something cool, let me know all about it, @rahims or rsonawalla@gmail.com!

Dissecting the Sonos Controller

I’ve been eyeing the Sonos devices for some time now, but never found a chance to sit down and play with them. For the folks that have never heard of Sonos, they make high-quality wireless speakers that you can set up in your home to stream music through. The interesting thing about them is that they’re fairly easy to configure—just plug in power and connect to your home network—and they play well with each other. Adding multiple speakers is as easy as plugging them in and joining them to your home network. The devices automatically find each other, and from there you can easily arrange them however you like (have all the speakers play the same song, have two playing one song in one room and another playing a different song in another room, and so on). The downside is that they’re quite expensive (the cheapest speaker costs ~$300), which made it hard for me to justify getting one. However, that all changed recently. I was in Australia for Music Hack Day Sydney this past weekend and, as a sponsor of the event, Sonos was there with a bunch of Play:5 speakers!

Realizing that this was the perfect opportunity to finally tinker with the Sonos players, I started digging through their developer documentation. What I quickly found out though, was that while there were plenty of docs (and sample code) for creating applications that stream music through the speakers, there was no documentation on how one could control the speakers themselves. After a bit or reading, I learned that the Sonos devices communicate with each other over UPnP, so controlling the speakers would simply be a matter of working with that protocol. Unfortunately, that was easier said than done. Despite being an open protocol, UPnP is lacking in easy to understand documentation and easy to use libraries. To be fair, there seem to be good UPnP libraries in C, but I didn’t want to dust off my ancient C programming skills for a weekend hackathon. I was able to find two Python libraries, BRisa and sonospy, but neither worked when I gave them a go. It was time to get creative.

Peaking Under the Hood

I had a working Sonos controller on my computer, the official Sonos application, I just needed to figure out how it did its magic. Enter Wireshark, the revealer of secrets. After a few packet sniffing sessions, I had what I needed. Sonos devices can be controlled by sending them SOAP messages. For example, to pause the currently playing track, you would make the following HTTP POST request:

POST http://[Sonos speaker’s IP address]:1400/MediaRenderer/AVTransport/Control

POST Headers:
Content-type: text/xml
SOAPACTION: "urn:schemas-upnp-org:service:AVTransport:1#Pause"

POST Body:
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><s:Body><u:Pause xmlns:u="urn:schemas-upnp-org:service:AVTransport:1"><InstanceID>0</InstanceID><Speed>1</Speed></u:Pause></s:Body></s:Envelope>

About Sonos MenubarFinding out the Sonos speaker’s IP address is done programmaticaly through the Simple Service Discovery Protocol (SSDP). I wasn’t able to write the code to do dynamic discovery over the weekend, so I took the easier route. If you have the official Sonos application installed, you can get a report of all the speakers’ IPs by clicking on “About My Sonos System” in the Sonos menu.

It’ll give you an output similar to the one below.

Associated ZP: 10.0.0.103
———————————
BRIDGE: BRIDGE
Serial Number: 00-0E-58-4F-87-AC:F
Version: 3.7 (build 17551200)
Hardware Version: 1.5.0.0-2
IP Address: 10.0.0.100
———————————
PLAY:5: HACK CHILL ROOM1
Serial Number: 00-0E-58-5D-15-32:E
Version: 3.7 (build 17551200e)
Hardware Version: 1.16.4.1-2
IP Address: 10.0.0.102
OTP: 1.1.1(1-16-4-zp5s-0.5)
———————————

I was able to implement the other basic controller functionality, like play, stop, next track, previous track, and get information about the currently playing track, by sending similar SOAP messages. A few hours into the weekend, and I was able to control the hackathon’s Sonos speakers through my terminal! (This got pretty annoying for the other hackers at the event since those were the sames speakers that were playing the music for the weekend.)

Spicing Things Up

A terminal prompt makes for a lousy hackathon demo, so I decided to code up some simple applications on top of my base Sonos controller library. The first was a web app version of the Sonos controller. To spice things up, I used the Rovi API to bring in high-quality cover art and editorial album reviews. I also used the Rdio API to add in a “favorite” button. Push it and the currently playing track gets added to an Rdio playlist.

Screenshot of SoCo web app

For my second example application, I used the Twilio API to let me control the Sonos speaker through SMS. This was particularly cool because the official Sonos application for the iPhone only works when you’re in your home network. With SMS, I could control the speakers even when I was out. Ran out of the house, but forgot to turn off your music? Just send your speaker a text message.

The Payoff

Sonos Play:3 and BridgeAll three demos went over well with the audience at the hackathon during my presentation on Sunday night, and I ended up winning a Sonos Play:3 and bridge of my very own!

The Real Payoff

While winning some hardware of my own is awesome, the real win for me is the potential for great things in the near future. All of my code for the weekend and the sample applications is available on my Github page. With the basic groundwork laid, I hope developers at future hackdays will be able to create all kinds of interesting takes on controlling Sonos devices. Bring on the Kinect and OpenCV hacks! If you build something cool, please let me know about it through e-mail or twitter.

SSH tunneling with Tomato

Eric Butler released Firesheep yesterday and the Internet forums have already started debating the ethics of it. I’m not sure what kind of impact it’ll have on other people, but it convinced me to take action and secure my computers.

There are a few ways to secure your computers, but after reviewing the HN thread, it looks like the quickest and cheapest (free) way is to set up an SSH tunnel and route all wireless traffic through it.

These instructions assume you’re moderately tech savvy (find a nerdy friend), and that your home router runs Tomato.

Set up the SSH daemon

The first thing you’ll need to do is turn on Tomato’s built-in SSH daemon.

  1. Open up a web browser and navigate to http://192.168.1.1
  2. Type in your router’s username and password
  3. Click on the “Administration” link in the lefthand menu
  4. Check “Enable at Startup” and “Remote Access” (so that you can create an SSH tunnel to your router even when you’re out and about)
  5. Enter “2222″ for the remote port. (Pick another port number if you like.)
  6. Uncheck “Allow Password Login.” (We’ll enter in authorized keys in the next section.)

Set up each computer

Next, you’ll need to create SSH keys for each of the computers you plan on using.

  1. Open up Terminal and type ssh-keygen -d to create a new key
  2. Accept all the defaults
  3. Type in a passphrase of your choosing
  4. Using a text editor, open up the newly created “id_dsa.pub” file. (Found under ~/.ssh/id_dsa.pub by default.)
  5. Copy and paste the contents of the file into the “Authorized keys” section in Tomato. (Add multiple keys by pasting them one after the other in the “Authorized keys” section.)

Connecting securely

  1. Create a new text file and paste in the following:

    #!/bin/sh

    ssh -fND 8887 -p 2222 root@[router's external IP address]

  2. Save the file as “setup_tunnel.sh”
  3. Make the file executable by running chmod +x setup_tunnel.sh in Terminal

Now whenever you want to create an SSH tunnel to your router, just open up Terminal and run ./setup_tunnel.sh.

Route traffic through the tunnel

Once you’ve got a secure tunnel running on your computer, you’ll need to route traffic through it.

OS X

  1. System Preferences → Network
  2. Select “AirPort” in the lefthand list
  3. Click on the “Advanced” button
  4. Click on the “Proxies” tab
  5. Check “SOCKS Proxy” and enter “localhost” for the host and “8887″ for the port

Ubuntu

  1. System → Preferences → Network Proxy
  2. Check “Manual proxy configuration”
  3. Under “Socks host” type “localhost” and “8887″ for the port
  4. Click “Apply System-Wide…”

Secure Firefox

By default, Firefox doesn’t route DNS through the proxy, so do the following to fix that.

  1. Open up Firefox and type “about:config” in the address bar
  2. Click “I’ll be careful, I promise”
  3. Type “network.proxy.socks_remote_dns” in the filter.
  4. Toggle the value to “true” by double clicking on it

And that’s it, a free way to secure your computers’ Wi-Fi connections!