MacDevCenter    
 Published on MacDevCenter (http://www.macdevcenter.com/)
 See this if you're having trouble printing code examples


Build an iTunes Remote Control

by Matthew Russell
03/01/2005

Editor's note: AirPort Express is great for streaming music from your Mac, except when you have to change tracks from another room. There are commercial solutions available, but here's a great evening project using your web-enabled cell phone and the power of Mac OS X. This is actually an Apache web server tutorial disguised as an iTunes hack.

If you own an Airport Express, you know that it can be inconvenient to travel back and forth to iTunes in order to please the masses at parties. You might also find paying an additional 50 percent (of the price of your Express) to buy a remote control unacceptable. Not to worry, you can build your own remote control using your web-enabled cell phone and some of the magic built in to Mac OS X. The only catches are that it's free, has a range that reaches farther than you'll ever need, and is as customizable as you like. But wait a minute; those are good things!

Putting Together the Pieces

Building your own cell phone remote control for iTunes (or any other application for that matter) isn't difficult if you have the right tools and follow a few basic steps. Assuming you have a cell phone that can surf the web and a working Internet connection, the only other requirement is some brain power. Here's the recipe:

Setting up an iTunes remote control not only allows you to DJ parties without looking like a computer nerd, it also allows you to tap other cool possibilities like adding a wireless snooze feature to your iTunes alarm clock, or surprising family members at home with pre-recorded messages (while you sit at work and chuckle quietly).

Setting up Apache

Kevin Hemenway has already written a very good series on setting up Apache. You can get to the whole series by starting with his bio, or start directly with Part 1 of the series here. Our steps in setting up Apache are extremely minimal, but the much fuller context is there if you want it.

Personal Web Sharing in System Preferences Starting up Apache in OS X is as simple as checking a box in System Preferences

To get started, open up the "Sharing" folder in System Preferences and enable "Personal Web Sharing." After this change, you should immediately be able to type http://localhost into your browser and see one of Apache's localized startup pages being displayed from /Library/WebServer/Documents. The name localhost has the corresponding IP address of 127.0.0.1, but you could also get to this same page with the IP address assigned to your machine by your default gateway, which probably pulls double duty as your firewall and is more commonly referred to as your router. Typing ifconfig in Terminal gives you the internal IP address that identifies your machine on your home network.

Typing ifconfig in Terminal for internal IP address If you have a home network behind a firewall or router, typing "ifconfig" at a Terminal prompt gives you your IP address internal to your home network.

To be able to access your home web server from anywhere in the world (including cell phones), the firewall protecting your web server needs to allow incoming traffic on port 80, and you need to know your external IP address that identifies your server to the internet at large. For example, my 2Wire Home Portal allows me to open a control panel in my web browser to accomplish these things. Most routers follow suit; check your manual for more info or search the web for your specific model. If you have a dial-up connection, your mileage may vary depending upon your Internet Service Provider.

Once you've opened up port 80, you should be able to get the default Apache page by typing your external IP address into your browser. There are two primary considerations to address at this point: 1) security concerns and 2) IP address changes because of Dynamic Host Configuration Protocol (DHCP). For security concerns, stay on top of your updates, check out this primer, and spend some time surfing the web on web server security. You can potentially spare yourself many problems by closing the port whenever it's not being used for extended time periods.

Before you work around the potential DHCP issue, determine first whether or not it is actually a problem. It could be the case that your IP address doesn't change as often as you'd think. You can keep an eye on it using your router's control panel to see what the trend appears to be, or call your ISP and ask them about their particular policy. If you find you have a particular IP address for hours at a time and use your iTunes remote control only occasionally, you might want to look it up when you need it instead of working around it. If you decide to do a workaround, you can check here for a tutorial that shows you how. The workaround addresses a .Mac audience, but works equally as well with any constant point of entry (such as one of the many free hosting services). For our iTunes remote control, I'll assume you're just using your external IP address directly.

Getting Apache to Serve WAP Content

From looking at the earlier link to WAP, you know that the WAP protocol is a standard for serving web content to small wireless devices that have low bandwidth and small viewing areas--like our cell phones. To get Apache to serve WAP content, its configuration file must be slightly modified. Open up /etc/httpd/httpd.conf in Terminal by with root level permissions by using sudo and add the two snippets of content below. Back up this file before modifying it just in case disaster occurs. For each snippet, I've given a few lines of context to help you find a good insertion location.

Here is the first snippet that allows Apache to recognize particular MIME types associated with WAP based on file extension:

    #
    # AddType allows you to tweak mime.types without
    # actually editing it, or to
    # make certain files to be certain types.
    #
    AddType application/x-tar .tgz

####################################################
#Added 25 Feb 05 -MR
#This tells Apache to serve these mime types
#to browsers when they ask for them based on 
#file extension. Apache assumes the browser knows
#that to do with these types if it asks for them
####################################################
#WML/WAP types
AddType text/vnd.wap.wml .wml
AddType application/vnd.wap.wmlc .wmlc
AddType text/vnd.wap.wmlscript .wmls
AddType application/vnd.wap.wmlscriptc .wmlsc
AddType image/vnd.wap.wbmp .wbmp
####################################################

This second snippet is for convenience. It changes the default file Apache serves when given a request for content in the root directory.

<IfModule mod_dir.c>
	###########################################
	#Added 25 Feb 05 -MR
	#Replace index.html with /cgi-bin/index.pl
	#to change the default page served
	###########################################
    #DirectoryIndex index.html
    DirectoryIndex /cgi-bin/index.pl
</IfModule>

Important: these changes won't reflect until you restart Apache by stopping and starting "Personal Web Sharing" from System preferences.

You should now be able to verify that Apache serves WAP content. To see for yourself, save the following WML file as /Library/WebServer/Documents/hello.wml

<?xml version="1.0"?>
<!DOCTYPE wml PUBLIC
    "-//WAPFORUM//DTD WML 1.1//EN"
    "http://www.wapforum.org/DTD/wml_1.1.xml">

<wml>
    <card title="Hello World">
        <p>Hello World</p>
    </card>
</wml>

Your browser doesn't expect to receive WAP content, so you need to use your cell phone or a WAP emulator such as Wapsilon to view the content. In your cell phone or an emulator, enter http://your-ip-address/hello.wml and you should get the infamous "Hello World" message. If you try to open this page in your computer's web browser, it won't display it because it doesn't expect this MIME type.

Solicitation to Download WAP
Your Mac's web browser doesn't expect WAP content, so it defaults to a download option. Your cell phone and other WAP enabled-devices, however, do expect this type of content and happily display them.

Serving WAP via CGI (ala Perl)

You can get some very good context for running CGI on Apache here if you need an overview. For our purposes, CGI is simply a means of having a Perl script do two things: 1) provide the appropriate HTTP Response Headers for serving WAP content, and 2) pass messages to a daemon (currently lurking in the shadows) that controls iTunes. The stock installation of Apache with OS X has CGI configured to run by default.

To try CGI out, copy the following script into a file named index.pl, place it into /Library/WebServer/CGI-Executables, and set its permissions to be executable by Apache (user "www") with a chmod 755 index.pl in Terminal. If you know any Perl at all, you'll see that it simply writes out the content type header that the browser expects followed by each line of a file called "menu" (the body of a WML file). The file "menu" also needs to be saved to this same directory. For a quick crash course in Perl, check here.

Here's index.pl:

#!/usr/bin/perl

print "Content-type: text/vnd.wap.wml\n\n";

open MENU, "./menu" or die "'menu' not in current dir";
while (<MENU>) {print "$_";}
close MENU;

Here's the file "menu" that is simply a WML file; it looks strikingly similar to HTML content:

<?xml version="1.0"?>
<!DOCTYPE wml PUBLIC
    "-//WAPFORUM//DTD WML 1.1//EN"
    "http://www.wapforum.org/DTD/wml_1.1.xml">

<wml>
    <card title="iTunes RC">
    <p><a href="/cgi-bin/RandomTrack.pl">RandomTrack</a></p>
    <p><a href="/cgi-bin/PlayPause.pl">Play/Pause</a></p>
    <p><a href="/cgi-bin/MuteUnmute.pl">Mute/Unmute</a></p>
    <p><a href="/cgi-bin/Rewind.pl">Rewind</a></p>
    <p><a href="/cgi-bin/FastForward.pl">FastForward</a></p>
    <p><a href="/cgi-bin/VolumeUp.pl">VolumeUp</a></p>
    <p><a href="/cgi-bin/VolumeDown.pl">VolumeDown</a></p>
    </card>
</wml>

Related Reading

AppleScript: The Missing Manual
By Adam Goldstein

You should be able to open the URL http://your-ip-address in your phone or a WAP emulator and successfully have Apache serve this menu to you. Recall that the extra /cgi-bin/index.pl that fully identifies the URL is implicitly understood through one of the earlier modifications to Apache's configuration file. If you're greeted by a "Forbidden" message instead of seeing the menu, check file permissions on the Perl script; permissions often cause hang ups, but it's to protect us from ourselves, right?

The Rest of the Perl Scripts

So what about all of those other self-naming Perl scripts referenced in index.pl? The short answer is that they do what they say they do by getting a corresponding AppleScript to control iTunes. The longer answer involves a quick aside to explain security permissions.

When we run a Perl script or any other executable on our machine, we run it as the user we logged in as. Except for root level actions, our user login usually gets the job done. When Apache runs a script, however, it runs it under the special user account "www" that is limited in some ways. In short, this is a security precaution. It's impact on our design is minimal, but at the same time, sort of frustrating.

To illustrate the design impact, consider the following scenario: If I execute a Perl script as user "matthew" that contains a line of code executing an AppleScript, the AppleScript executes just fine under the veil of user account "matthew" as well.

User "matthew" runs the Perl script, so user "matthew" inadvertently runs the AppleScript, which then sends Apple events to an application like iTunes (an application also running under user account "matthew"). This scenario isn't possible for our iTunes remote control because the Perl scripts and embedded AppleScripts are executed by Apache under user account "www", and user "www" can't send Apple events to applications (not to mention iTunes running under the different user account "matthew").

For our specific purpose, we can circumvent the inability of Apache to run AppleScripts the way we'd like by having the CGI script write a particular command to a file that a background daemon periodically checks and acts on. If this daemon process is running under our normal user account, the daemon can execute AppleScripts and consequently send Apple events to iTunes. This the long way around, but it does get the job done and is reasonably secure. If you ever need to do something fairly complex involving CGI and AppleScript, have a look at ACGI Dispatcher. It might save you some time at the cost of a few meals.

Let's get rolling again with some more code. Consider the following Perl script, PlayPause.pl, that Apache executes when you click on the corresponding link in index.pl:

#!/usr/bin/perl -w

system "echo 'PlayPause' > /tmp/iTunesRemoteControl";
system "chmod uga+rw /tmp/iTunesRemoteControl";

print "Content-type: text/vnd.wap.wml\n\n";

open MENU, "./menu" or die "'menu' not in current dir";
while (<MENU>) {print "$_";}
close MENU;

This Perl script writes the command "PlayPause" to a file in a temporary directory where everyone has write permissions, changes its permissions so that the daemon (that's our next step) can modify it, and then redisplays the main menu.

The Daemon Glue

Without further adieu, here is the daemon that completes the process:

#!/bin/bash
##########################################################
#Matthew Russell -25 Feb 05
#Usage: prompt$./iTunesRemoteControlDaemon.scr &
#
#This simply reads a file and executes an AppleScript that 
#controls iTunes based on its content.
#
#kill the daemon using the companion script killDaemon.scr
##########################################################

############
#User Prefs
############
scriptDir="/Library/WebServer/CGI-Executables"
remoteCommand="/tmp/iTunesRemoteControl"
delay="1.0"


#############
#begin script
#############
while [ 1 ]; do

status=`cat $remoteCommand`

case $status in
    "RandomTrack") osascript $scriptDir/RandomTrack.scpt;;
    "Rewind") osascript $scriptDir/Rewind.scpt;;
    "FastForward") osascript $scriptDir/FastForward.scpt;;
    "MuteUnmute") osascript $scriptDir/MuteUnmute.scpt;;
    "PlayPause") osascript $scriptDir/PlayPause.scpt;;
    "VolumeDown") osascript $scriptDir/VolumeDown.scpt;;
    "VolumeUp") osascript $scriptDir/VolumeUp.scpt;;
esac

echo "" > $remoteCommand
sleep $delay

done

So as you can see, it's simply a matter of having a daemon script pick up a message from a file and execute a corresponding AppleScript. This would be a good time to check out the man pages for the osascript command and take a crash course in Bash scripting.

I've conveniently bundled all of the scripts here for you along with some troubleshooting help in a README file. Take this archive and extract its contents to your /Library/WebServer/CGI-Executables directory. You must give all of the Perl scripts proper permissions with chmod 755 *.pl. Also, give your daemon executable permissions from your user account with chmod u+x. When all else fails, check and recheck file permissions.

All of the AppleScripts provided can be inspected in the Script Editor via Terminal with open [ScriptName]. All of them are too simple to merit explanation. For example, consider PlayPause.scpt:

tell application "iTunes"
	playpause
end tell

Any questions? If you want some more information on AppleScript, a good place to start is here on Apple's site.

Final Thoughts

The scripts provided do most of the common things you'd want to be able to do from an iTunes remote control. If you want more customization, adapt some of the existing AppleScripts out on the web to fit your needs. Doug's AppleScripts for iTunes is a good starting point. If you plan to keep your remote control up for extended time periods, you'll want to design a simple login system that requires some form of authentication. You can easily do this with another short CGI script. What could be worse than an unknowing web surfer waking you up at night with random music?

Following the design pattern for this remote control, you can control a lot more than iTunes. Stay on top of your web server's security, and have fun.

Matthew Russell is a computer scientist from middle Tennessee; and serves Digital Reasoning Systems as the Director of Advanced Technology. Hacking and writing are two activities essential to his renaissance man regimen.


Return to MacDevCenter.com.

Copyright © 2009 O'Reilly Media, Inc.