Jeff Sharkey



Amarok 1.4 remote in Android using DCOP+Python

We’ve all seen the Apple app that lets you control iTunes remotely, and there totally needs to be something like this for Android. I’m an avid fan of Amarok, so I’ve whipped together a simple remote control that runs on Android. It only took about 3 hours tonight, and I’m releasing everything GPLv3 here. First some details on the architecture:

Amarok can easily be controlled via DCOP, including fetching currently playing information and album art. DCOP is a local, safe IPC architecture that usually comes already enabled with KDE applications. Just a reminder that DCOP is being phased out in KDE 4 with D-Bus taking its place. Speaking of the D-Bus future, there is an awesome standard called Media Player Remote Interface Specification (MPRIS) that is being put together by the folks at XMMS, VLC, Amarok, and others. It doesn’t seem to be in stable releases yet, but will be soon. Back to the present, I’m going to just focus on getting Amarok 1.4 working with older DCOP calls.

First, I built a Python bridge that offers a simple HTTP REST-like API that will help us relay pre-approved DCOP commands from Android to Amarok. The Python is pretty simple:

from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
import pydcop, re

port = 8484
allowed = ["status", "trackCurrentTime", "trackTotalTime", "album", "artist",
	"title", "next", "playPause", "prev", "volumeDown", "volumeUp",
	"coverImage", "seek"]
resafe = re.compile("[^A-Za-z0-9/]")

class AmarokHandler(BaseHTTPRequestHandler):
	def do_GET(self):
		
		# pull out action and simple variable
		safe = resafe.sub('', self.path)
		blank, action, var = tuple(safe.split('/'))
		
		# skip if action has not been approved
		if not action in allowed: return
		
		# check if image request
		if action == "coverImage":
			self.send_response(200)
			self.send_header('Content-type', 'image/jpeg')
			self.end_headers()
			
			cover = open((pydcop.DCOPMethod("amarok", "player", "coverImage"))())
			self.wfile.write(cover.read())
			cover.close()
			return
		
		# make dcop call over to amarok
		if len(var) > 0: reply = (pydcop.DCOPMethod("amarok", "player", action))(int(var))
		else: reply = (pydcop.DCOPMethod("amarok", "player", action))()
		
		# write back any dcop response
		self.send_response(200)
		self.send_header('Content-type', 'text/plain')
		self.end_headers()
		self.wfile.write(reply)
		
		return


try:
	server = HTTPServer(('', port), AmarokHandler)
	print 'started amarokremote server on port %s' % (port)
	server.serve_forever()
except KeyboardInterrupt:
	server.socket.close()

Essentially we are using a URL of the form http://ipaddress:port/command/variable. For example, in the Android emulator you could call http://10.0.2.2:8282/volumeUp/ to increase the volume.

Carefully note that we are screening the commands against an “allowed” list before running off to Amarok with them. We’re also scrubbing the incoming calls to prevent any buffer overflows. Usually we are just calling the Amarok DCOP method and returning any result. One exception is for coverImage requests, because Amarok just returns a local path. We help that image over the Python bridge by sending the local JPEG as the response.

On the Android side of things, we have a simple screen layout and are hooking up the various API calls to buttons. There’s also a background thread that keeps Android in-sync with what Amarok is currently playing. Finally, we’re using some simple preferences to store the server string and update interval. (Tap the menu button to change these settings.)

Here’s a tarball of the Eclipse project, along with the APK that’s ready to run on the new 0.9 SDK. Start the Python server above on your the computer running Amarok, change the IP address if needed, and you should be ready to go.

Also, a reminder that 10.0.2.2 is magic on the Android emulator because it points to the loopback adapter of the parent computer. So, if you’re running the emulator and Amarok on the same computer, this IP address will work perfectly.