|
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):
-
-
- safe = resafe.sub('',
self.path)
- blank, action,
var = tuple(safe.split('/'))
-
-
-
if not action
in allowed: return
-
-
-
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
-
-
-
if len(var) > 0:
reply = (pydcop.DCOPMethod("amarok",
"player", action))(int(var))
-
else: reply = (pydcop.DCOPMethod("amarok",
"player", action))()
-
-
-
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()
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.
Source: http://www.jsharkey.org/blog/2008/08/20/amarok-14-remote-in-android-using-dcoppython/
 |