The Blueprint
There’s a ton of buzz around the iPad these days and how when using it, it feels like “the future is now.” Not to leave it’s little brother in the dark, I had one of those moments this morning on the iPhone.
I’ve been working on server upgrades and moves the past few weeks and was having an issue last night with one of them. I woke up today and was on my way to work when the epiphany hit me… I needed to change the configuration of ImageMagick but didn’t have my coding laptop with me. Being Monday our client needed this site working ASAP so I pulled over fired up the TouchTerm iPhone app that I downloaded a few months ago and SSH’d in.

As I was recompiling the software FROM MY PHONE and the server responses were flying by on the screen OF MY PHONE, I felt what all designers and UI geeks prob feel while using the iPad… that I was in the future. The interface is dead simple and even gives you options to save some of your connections for fast reconnection. It is complete with arrow key, function key, and control key overlays to make quick recall and editing of commands easy. I’m sure there are nights when I will still have eyeburn from of the radiant glow of my CRT at 2:00am reflecting lines and lines of server code, but for quick on the fly Apache restarts, server status, etc… This app is worth the download and even purchase of the Pro version.

The Problem
As just about everyone knows the iPhone is incapable of playing any sort of Flash through the mobile Safari browser and generally speaking everyone has adapted and found work arounds or changed their sites accordingly as to not require Flash for certain things.
On my personal blog about music, one of the most used plugins I have installed is the 1 Pixel Out flash MP3 player. And by the number of downloads and searches I’ve seen it’s one of the most popular ways to quickly play an mp3 inline to a blog post. It’s customizable, looks great and most of all it works perfect! That is until you try to view the post from your iPhone.
The Solution
So I hacked around the plugin and created the following patch that will detect if your viewer is using an iPhone, and if so it will serve the MP3 with a clickable “Play” image. This allows your viewer to play the mp3 straight through the iPhone! Your viewers will no longer get the no-flash error but instead get this:

Installation [version 2.0.4.1]
Just download this zip and go to your plugins folder for WordPress. Replace your audio-player.php file with the one provided and put the iphone_play.png image in the assets folder. That’s it!
Please note that this is based on version 2.0.4.1. The older version I have ported are listed below.Also, I haven’t tested all the different methods so you may see an issue here or there. Just hit me up if you have a problem and I’ll try to fix it. I’m hoping that 1 Pixel Out will integrate this into the release officially at some point. BTW, The user agent detection function was borrowed from WPtouch.
For you folks that want to manually do it or see what has been changed just have a look below. In the audio-player.php file you will need to make the following changes:
Add the following to line 320:
/**
* Detects if the user agent is an iPhone or iPod
* returns true or false
*/
function detectAppleMobile($query = '') {
$container = $_SERVER['HTTP_USER_AGENT'];
$useragents = array(
"iphone", // Apple iPhone
"ipod", // Apple iPod touch
"aspen", // iPhone simulator
"incognito", // Other iPhone browser
"webmate" // Other iPhone browser
);
$applemobile = false;
foreach ( $useragents as $useragent ) {
if ( eregi( $useragent, $container ) || file_exists($devfile) ) {
$applemobile = true;
}
}
return $applemobile;
}
after inserting that code around line 562 you will see:
} else {
// Not in a feed so return player widget
$playerElementID = "audioplayer_" . ++$this->playerID;
if (strlen($this->options["flashAlternate"]) > 0) {
$playerCode = str_replace(array("%playerID%", "%downloadURL%"), array($playerElementID, $actualFile), $this->options["flashAlternate"]);
} else {
$playerCode = '<p class="audioplayer_container"><span style="display:block;padding:5px;border:1px solid #dddddd;background:#f8f8f8" id="' . $playerElementID . '">' . sprintf(__('Audio clip: Adobe Flash Player (version 9 or above) is required to play this audio clip. Download the latest version <a href="%s" title="Download Adobe Flash Player">here</a>. You also need to have JavaScript enabled in your browser.', $this->textDomain), 'http://www.adobe.com/shockwave/download/download.cgi?P1_Prod_Version=ShockwaveFlash&promoid=BIOW') . '</span></p>';
}
$this->footerCode .= 'AudioPlayer.embed("' . $playerElementID . '", ' . $this->php2js($playerOptions) . ');';
$this->footerCode .= "\n";
return $playerCode;
}
which needs to be replaced with:
} else {
if($this->detectAppleMobile()){
$iphone_player = '';
foreach ( explode( ",", $source) as $afilename ) {
$afilename = trim($afilename);
$iphone_player .= '<a href="'.$afilename.'"><img src = "'.WP_CONTENT_URL.'/plugins/audio-player/assets/iphone_play.png"></a><br />';
}
return $iphone_player;
} else {
// Not in a feed so return player widget
$playerElementID = "audioplayer_" . ++$this->playerID;
if (strlen($this->options["flashAlternate"]) > 0) {
$playerCode = str_replace(array("%playerID%", "%downloadURL%"), array($playerElementID, $actualFile), $this->options["flashAlternate"]);
} else {
$playerCode = '<p class="audioplayer_container"><span style="display:block;padding:5px;border:1px solid #dddddd;background:#f8f8f8" id="' . $playerElementID . '">' . sprintf(__('Audio clip: Adobe Flash Player (version 9 or above) is required to play this audio clip. Download the latest version <a href="%s" title="Download Adobe Flash Player">here</a>. You also need to have JavaScript enabled in your browser.', $this->textDomain), 'http://www.adobe.com/shockwave/download/download.cgi?P1_Prod_Version=ShockwaveFlash&promoid=BIOW') . '</span></p>';
}
$this->footerCode .= 'AudioPlayer.embed("' . $playerElementID . '", ' . $this->php2js($playerOptions) . ');';
$this->footerCode .= "\n";
return $playerCode;
}
}
Installation [version 2.0 beta 6]
Just download this zip and go to your plugins folder for WordPress. Replace your audio-player.php file with the one provided and put the iphone_play.png image in the assets folder.
Note: I removed the old 2.0 beta 6 manual code to avoid confusion
I spent more than half a day yesterday trying to uncover why the code that delivers our Javascript and CSS suddenly quit being cached locally in the browser. Seemingly out of the blue, every page load was pulling down a new copy of the files, which is unacceptable when they are over 100kb even in a compressed state. Akira uses a variation of the Combine script to combine, compress, serve and cache all our JS and CSS. It’s a script that more people should be using due to it’s simplicity and how well it works… when it works. Let’s take a step backwards and I’ll tell you how the caching works before I get into how I fixed it, why it was broken and why PHP is to blame (sorta).
Etags
An Etag is an HTTP response header that may be returned from your web server and is used to determine change in content on a given page. What that means is that it allows you to check a page and return either a 304 Not Modified response with no content or a 200 OK response with all the page content. Or even more practically, we can check our CSS and JS pages and the server will tell us if they’ve changed or not… and if not then use the local browser cache.
You can’t just call http://example.com/style.css and have it work. You have to have PHP check and send the proper Etag headers, which is where the Combine script comes in since it does all this for you. The script checks the Last Modified Date of each CSS/JS file and uses that in the Etag. The Etag changes when the CSS file changes, thereby telling your browser to use the new content. It’s brilliant really… and I can’t figure out why more people don’t use this method other than the VERY MINIMAL overhead (low milliseconds) that is needed to check the Etag header.
Etags and PHP
Lets look at how PHP gets involved in all this. PHP has access to a server variable called $_SERVER['HTTP_IF_NONE_MATCH'] that returns NULL if the Etags do not match. Using this variable it allows us to know if any content has changed and then act on that. Let me give you a quick example of how it works:
header('Etag: "This is the Etag header content."');
if(!isset($_SERVER['HTTP_IF_NONE_MATCH'])){
echo "$_SERVER['HTTP_IF_NONE_MATCH'] is not set";
}else{
// $_SERVER['HTTP_IF_NONE_MATCH'] is set so return 304
header ("HTTP/1.0 304 Not Modified");
header ('Content-Length: 0');
}
Houston we have a PHP problem
So if the Combine script works so great why did it stop working for us? In simple terms, PHP sort of got in the way. We started off using Combine in it’s native form… just a straight call to combine.php which served the CSS/JS. However, we recently integrated it into our framework to keep things streamlined, but I never went back to check the caching. I just figured it would still work. That was my mistake and why several months later I thought it quit working “out of the blue” when in fact it had stopped as soon as we integrated it.
The root problem is that when PHP uses sessions it modifies the cache headers because of the dynamic nature of using sessions. And wouldn’t you know it.. we use sessions (surprise surprise) and our app calls in a sessions class in the overhead portion of the app. What that means is that a session was started on each page load, including these JS and CSS calls. What happens is that when session_start() is called the server variable $_SERVER['HTTP_IF_NONE_MATCH'] is removed since PHP thinks that we don’t want to serve static content. Fun times and I now see why Rasmus dislikes frameworks due to the overhead issues.
Solution
Not being a core PHP dev I can only assume there is a real reason for how PHP handles sessions that is helpful in other instances. However to get around it I can either bypass the session_start() for JS and CSS calls (changing the overhead of the framework) or I can use the following helpful bit of code that is in PHP to allow us to change the normal behavior of sessions. Before we call session_start() we simply tell PHP that we want to set the “session cache limiter” to private by calling session_cache_limiter(‘private_no_expire’).
So there you go.. a simple fix which took my half a day to track down. Hopefully I will have saved a few people the same headache when trying to use Etags and PHP sessions.
Search The Blog
Code & Projects
Categories
Archives
- April 2010
- October 2009
- September 2009
- August 2009
- June 2009
- May 2009
- April 2009
- February 2009
- December 2008
- November 2008
- October 2008
- September 2008
- August 2008
- April 2008
- March 2008
- February 2008
- July 2007
Download