diff --git a/mollom.captcha.tpl.php b/mollom.captcha.tpl.php
new file mode 100644
index 0000000..26f29e7
--- /dev/null
+++ b/mollom.captcha.tpl.php
@@ -0,0 +1,140 @@
+<?php
+/**
+ * @file
+ * Provide the HTML output for the audio CAPTCHA playback.
+ *
+ * Available variables:
+ * - captcha_type: The type of CAPTCHA to render: one of 'audio' or 'image'.
+ *   Default value is 'image'.
+ * - $captcha_url:  The CAPTCHA image or audio to present based on type.
+ * - $flash_fallback_player: The URL to the Flash plugin to use as a fallback
+ *   player when HTML5 audio is unsupported for MP3.
+ * - $audio_enabled: boolean indicates if audio CAPTCHAs are enabled
+ *   for this site.
+ *
+ * Assumptions:
+ * - SWFObject is already included if it is available.
+ *
+ * @see http://www.html5rocks.com/en/tutorials/audio/quick/
+ */
+
+$flash_url = url($flash_fallback_player, array(
+  'query' => array('url' => $captcha_url),
+  'external' => TRUE,
+));
+$verify_audio = $audio_enabled ? t('Switch to audio verification') : '';
+$verify_image = t('Switch to image verification');
+$instructions_image = t("Type the characters you see in the picture; if you can't read them, submit the form and a new image will be generated. Not case sensitive.");
+$instructions_audio = t('Enter only the first letter of each word you hear.  If you are having trouble listening in your browser, you can <a href="@captcha-url" id="mollom_captcha_download" class="swfNext-mollom_captcha_verify">download the audio</a> to listen on your device.', array(
+  '@captcha-url' => $captcha_url,
+));
+$unsupported = t('Your system does not support our audio playback verification.  Please <a href="@captcha-url" id="mollom_captcha_download" class="swfNext-mollom_captcha_verify">download this verification</a> to listen on your device.', array(
+  '@captcha-url' => $captcha_url,
+));
+$image_alt_text = t('Type the characters you see in this picture.');
+?>
+
+<?php if($captcha_type === 'audio') { ?>
+  <script type="text/javascript">
+    function embedFallbackPlayer() {
+      var embedDiv = document.getElementById("mollom_captcha_fallback_player");
+      var audioDiv = document.getElementById("mollom_captcha_audio");
+      var unsupportedDiv = document.getElementById("mollom_captcha_unsupported");
+      var movie = '<?php print $flash_url; ?>';
+
+      function embedComplete(e) {
+        if (e.success) {
+          e.ref.focus();
+        } else {
+          jQuery(unsupportedDiv).show();
+        }
+      }
+
+      var flashvars = {},
+        params = {
+          wmode:  "opaque"
+        },
+        attributes = {
+          "class": "swfPrev-mollom_captcha_download swfNext-mollom_captcha_verify mollom_captcha_flash_player",
+        },
+        playerWidth = 110,
+        playerHeight = 50;
+      if (typeof swfobject !== 'undefined') {
+        swfobject.embedSWF(movie, embedDiv, playerWidth, playerHeight, "10.0", null, flashvars, params, attributes, embedComplete);
+      } else {
+        var embed = '<object id="mollom_captcha_fallback_player" name="mollom_captcha_fallback_player" ';
+        embed += 'classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" ';
+        embed += 'type="application/x-shockwave-flash" ';
+        embed += 'data="' + movie + '" '
+        for (var attr in attributes) {
+          embed += attr + '="' + attributes[attr] + '" ';
+        }
+        embed += 'width="' + playerWidth + 'px" height="' + playerHeight + 'px">';
+        embed += '<param name="movie" value="' + movie + '" />';
+        for (var param in params) {
+          embed += '<param name="' + param + '" value="' + params[param] + '" />';
+        }
+        var flashVarsArray = [];
+        for(var varname in flashvars) {
+          flashVarsArray.append(varname + '=' + encodeURI(flashvars[varname]));
+        }
+        flashVarsString = flashVarsArray.join('&');
+        if (flashVarsString.length > 0) {
+          embed += '<param name="flashVars" value="' + flashVarsString + '" />';
+        }
+        embed += '<embed src="' + movie + '" width="' + playerWidth + '" height="' + playerHeight + '" ';
+        for (var attr in attributes) {
+         embed += attr + '="' + attributes[attr] + '" ';
+        }
+        for (var param in params) {
+          embed += param + '="' + params[param] + '" ';
+        }
+        if (flashVarsString.length > 0) {
+          embed += 'flashVars="' + flashVarsString + '"';
+        }
+        embed += '/>';
+        embed += '</object>';
+        jQuery(embedDiv).replaceWith(embed);
+        jQuery("#mollom_captcha_fallback_player").focus();
+      }
+      jQuery(audioDiv).hide();
+    }
+  </script>
+
+<div class="mollom-captcha-content mollom-audio-captcha">
+  <div class="mollom-audio-catcha-instructions"><?php print $instructions_audio; ?></div>
+
+  <!--- HTML5 Audio playback -->
+  <audio id="mollom_captcha_audio" controls tabindex="0">
+    <source src="<?php print $captcha_url; ?>" type="audio/mpeg" />
+    <!-- Displays if HTML5 audio is unsupported and JavaScript player embed is unsupported -->
+    <p><?php print $unsupported; ?></p>
+  </audio>
+
+  <!-- Fallback for browsers not supporting HTML5 audio or not MP3 format -->
+  <div id="mollom_captcha_fallback">
+    <div id="mollom_captcha_fallback_player"></div>
+    <script>
+      var audioTest = document.createElement('audio');
+      if (!audioTest.canPlayType || !audioTest.canPlayType('audio/mpeg')) {
+        embedFallbackPlayer();
+      }
+    </script>
+  </div>
+
+  <!-- Text to show when neither HTML5 audio or SWFs are supported -->
+  <div id="mollom_captcha_unsupported" style="display:none;">
+    <p><?php print $unsupported; ?></p>
+  </div>
+
+  <div class="mollom-audio-captcha-switch"><a href="#" class="mollom-switch-captcha mollom-image-captcha swfPrev-mollom_captcha_download" id="mollom_captcha_verify"><?php print $verify_image; ?></a>.</div>
+</div>
+
+<? }
+else {
+  $captcha_output = theme('image', array('path' => $captcha_url, 'alt' => $image_alt_text, 'getsize' => FALSE));
+?>
+<span class="mollom-captcha-content mollom-image-captcha"><? print $captcha_output; ?></span>
+<div class="mollom-image-captcha-instructions"><?php print $instructions_image ?><?php if ($audio_enabled) { ?>&nbsp;&nbsp;<a href="#" class="mollom-switch-captcha mollom-audio-captcha"><?php print $verify_audio; ?></a>.<?php } ?></div>
+</div>
+<?php } ?>
diff --git a/mollom.css b/mollom.css
index cef4eaf..e57d4f0 100644
--- a/mollom.css
+++ b/mollom.css
@@ -8,3 +8,13 @@
 #simpletest-result-form table td {
   white-space: pre-wrap;
 }
+
+/* Allow CAPTCHA rendering to take full form width. */
+.form-item-mollom-captcha label[for="edit-mollom-captcha"] {
+    float: none;
+}
+
+/* Accessibilty focus helper for audio CAPTCHA. */
+.mollom_captcha_flash_player:focus{
+    border: yellow 2px solid;
+}
diff --git a/mollom.js b/mollom.js
index 7a4ee61..61f3c7b 100644
--- a/mollom.js
+++ b/mollom.js
@@ -59,7 +59,15 @@ function getMollomCaptcha() {
       // Add an onclick-event handler for the new link.
       Drupal.attachBehaviors(context);
       // Focus on the CAPTCHA input.
-      $('input[name="mollom[captcha]"]', context).focus();
+      if (newCaptchaType == 'image') {
+          $('input[name="mollom[captcha]"]', context).focus();
+      } else {
+         // Focus on audio player.
+         // Fallback player code is responsible for setting focus upon embed.
+         if ($('#mollom_captcha_audio').is(":visible")) {
+             $('#mollom_captcha_audio').focus();
+         }
+      }
     }
   });
   return false;
diff --git a/mollom.module b/mollom.module
index b861f01..4403e8b 100644
--- a/mollom.module
+++ b/mollom.module
@@ -457,7 +457,7 @@ function mollom_data_load($entity, $id) {
  * @param $entity
  *   The entity type to retrieve data for.
  *
- * @returns
+ * @return array
  *   The matching Mollom data as an array keyed by entity id.
  */
 function mollom_entity_type_load($type) {
@@ -1435,7 +1435,7 @@ function _mollom_get_openid($account) {
  * @param $bundle
  *   An array of bundle names to check.
  * 
- * @returns
+ * @return array
  *   An array of protected bundles for this entity type.
  */
 function _mollom_get_entity_forms_protected($type, $bundles = array()) {
@@ -1662,11 +1662,21 @@ function mollom_element_info() {
  * Implements hook_theme().
  */
 function mollom_theme() {
+  $base_path = base_path() . drupal_get_path('module', 'mollom');
   return array(
     'mollom_admin_blacklist_form' => array(
       'render element' => 'form',
       'file' => 'mollom.admin.inc',
     ),
+    'mollom_captcha' => array(
+      'variables' => array(
+        'captcha_type' => 'image',
+        'captcha_url' => NULL,
+        'flash_fallback_player' => $base_path . '/mollom-captcha-player.swf',
+      ),
+      'render element' => 'element',
+      'template' => 'mollom.captcha',
+    ),
   );
 }
 
@@ -1738,7 +1748,7 @@ function mollom_library() {
  *   The type of the entity to check.
  * @param $entity
  *   The entity to check.  This can be either the entity object or an id.
- * @return boolean
+ * @return bool
  *   True if reporting is available and false if not available.
  */
 function _mollom_flag_access($type, $entity) {
@@ -1894,11 +1904,11 @@ function mollom_process_mollom($element, &$form_state, $complete_form) {
   // assigned as Boolean #solved = TRUE element property when solved correctly.
   $element['captcha'] = array(
     '#type' => 'textfield',
-    '#title' => t('Word verification'),
+    '#title' => t('Verification'),
     '#size' => 10,
     '#default_value' => '',
-    '#description' => t("Type the characters you see in the picture above; if you can't read them, submit the form and a new image will be generated. Not case sensitive."),
   );
+
   // Disable browser autocompletion, unless testing mode is enabled, in which
   // case autocompletion for 'correct' and 'incorrect' is handy.
   if (!variable_get('mollom_testing_mode', 0)) {
@@ -2014,6 +2024,8 @@ function mollom_form_add_captcha(&$element, &$form_state) {
   if (!empty($captcha)) {
     $element['captcha']['#access'] = TRUE;
     $element['captcha']['#field_prefix'] = $captcha;
+    $element['captcha']['#attributes'] = array('title' => t('Enter the characters from the verification above.'));
+    _mollom_attach_captcha_script($element['captcha']);
 
     // Ensure that the latest CAPTCHA ID is output as value.
     $element['captchaId']['#value'] = $form_state['mollom']['response']['captcha']['id'];
@@ -2777,7 +2789,7 @@ function _mollom_send_feedback($data, $reason = 'spam', $type = 'moderate', $sou
  *   A boolean if TRUE, will force the statistics to be re-fetched and stored
  *   in the cache.
  *
- * @return
+ * @return array
  *   An array of statistics.
  */
 function mollom_get_statistics($refresh = FALSE) {
@@ -2860,7 +2872,7 @@ function mollom_field_extra_fields() {
  * @param $form_state
  *   The current state of a form.
  *
- * @return
+ * @return string
  *   The markup of the CAPTCHA HTML.
  */
 function mollom_get_captcha(&$form_state) {
@@ -2889,37 +2901,77 @@ function mollom_get_captcha(&$form_state) {
     return '';
   }
 
-  // @todo Convert these to actual theme functions?
-  $output = '';
-  switch ($form_state['mollom']['captcha_type']) {
-    case 'audio':
-      $source = url(base_path() . drupal_get_path('module', 'mollom') . '/mollom-captcha-player.swf', array(
-        'query' => array('url' => $url),
-        'external' => TRUE,
-      ));
-      $output = '<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" codebase="//download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=9,0,0,0" width="110" height="50">';
-      $output .= '<param name="allowFullScreen" value="false" />';
-      $output .= '<param name="movie" value="' . $source . '" />';
-      $output .= '<param name="loop" value="false" />';
-      $output .= '<param name="menu" value="false" />';
-      $output .= '<param name="quality" value="high" />';
-      $output .= '<param name="wmode" value="transparent" />';
-      $output .= '<param name="bgcolor" value="#ffffff" />';
-      $output .= '<embed src="' . $source . '" loop="false" menu="false" quality="high" wmode="transparent" bgcolor="#ffffff" width="110" height="50" align="baseline" allowScriptAccess="sameDomain" allowFullScreen="false" type="application/x-shockwave-flash" pluginspage="http://www.adobe.com/go/getflashplayer_de" />';
-      $output .= '</object>';
-
-      $output = '<span class="mollom-captcha-content mollom-audio-captcha">' . $output . '</span>';
-      $output .= ' (<a href="#" class="mollom-switch-captcha mollom-image-captcha">' . t('verify using image') . '</a>)';
-      break;
-
-    case 'image':
-      $captcha = theme('image', array('path' => $url, 'alt' => t('Type the characters you see in this picture.'), 'getsize' => FALSE));
-      $output = '<span class="mollom-captcha-content mollom-image-captcha">' . $captcha . '</span>';
-      $output .= ' (<a href="#" class="mollom-switch-captcha mollom-audio-captcha">' . t('verify using audio') . '</a>)';
-      break;
-  }
-
-  return $output;
+  return theme('mollom_captcha', array(
+    'captcha_type' => $form_state['mollom']['captcha_type'],
+    'captcha_url' => $url,
+    'audio_enabled' => variable_get('mollom_audio_captcha_enabled', 1),
+  ));
+}
+
+/**
+ * Attach SWFObject script to render element when available.
+ *
+ * @param $element
+ *   A render element to attach the script to.
+ *
+ * @return bool
+ *   True if the library can be found, false otherwise.
+ */
+function _mollom_attach_captcha_script(&$element = NULL) {
+  $libraries = &drupal_static(__FUNCTION__);
+  if (empty($libraries['swfobject'])) {
+    $lib = array(
+      'found' => FALSE,
+    );
+
+    // Try to load via libraries module if enabled.
+    if (module_exists('libraries')) {
+      if (($library = libraries_detect('swfoject')) && !empty($library['installed'])) {
+        $lib = array(
+          'found' => TRUE,
+          'libraries' => TRUE,
+        );
+      }
+    }
+    if (!$lib['found']) {
+      // Check for SWFObject in standard library locations.
+      $profile = drupal_get_path('profile', drupal_get_profile());
+      $config = conf_path();
+      $search = array(
+        'libraries',
+        "$profile/libraries",
+        "sites/all/libraries",
+        "$config/libraries",
+      );
+      foreach ($search as $dir) {
+        if (is_dir($dir) && (
+          file_exists("$dir/swfobject.js") || file_exists("$dir/swfobject/swfobject.js")
+          )) {
+          $lib = array(
+            'found' => TRUE,
+            'libraries' => FALSE,
+            'path' => file_exists("$dir/swfobject.js") ? "$dir/swfobject.js" : "$dir/swfobject/swfobject.js",
+          );
+          break;
+        }
+      }
+    }
+    $libraries['swfobject'] = $lib;
+  }
+  if ($libraries['swfobject']['found']) {
+    if (isset($element)) {
+      if ($libraries['swfobject']['libraries']) {
+        $element['#attached']['libraries_load'][] = array('swfobject');
+      }
+      else {
+        $element['#attached'] = array(
+          'js' => array($libraries['swfobject']['path']),
+        );
+      }
+    }
+    return TRUE;
+  }
+  return FALSE;
 }
 
 /**
diff --git a/tests/mollom.test b/tests/mollom.test
index 4014608..ea9787e 100644
--- a/tests/mollom.test
+++ b/tests/mollom.test
@@ -553,7 +553,7 @@ class MollomWebTestCase extends DrupalWebTestCase {
     ));
     $this->assert(!empty($inputs[0]) && !empty($labels[0]), 'Required CAPTCHA field found.');
 
-    $image = $this->xpath('//img[@alt=:alt]', array(':alt' => t('Type the characters you see in this picture.')));
+    $image = $this->xpath('//img[@alt=:alt]', array(':alt' => t("Type the characters you see in this picture.")));
     $this->assert(!empty($image), 'CAPTCHA image found.');
   }
 
@@ -564,7 +564,7 @@ class MollomWebTestCase extends DrupalWebTestCase {
     $this->assertNoText($this->unsure_message);
     $this->assertNoText($this->incorrect_message);
     $this->assertNoFieldByXPath('//input[@type="text"][@name="mollom[captcha]"]', '', 'CAPTCHA field not found.');
-    $image = $this->xpath('//img[@alt=:alt]', array(':alt' => t('Type the characters you see in this picture.')));
+    $image = $this->xpath('//img[@alt=:alt]', array(':alt' => t("Type the characters you see in this picture.")));
     $this->assert(empty($image), 'CAPTCHA image not found.');
   }
 
@@ -4859,6 +4859,20 @@ class MollomCaptchaTestCase extends MollomWebTestCase {
     $response = drupal_json_decode($out);
     $this->assertTrue($response['captchaId']);
   }
+
+  /**
+   * Tests the CAPTCHA audio enable/disable functionality.
+   */
+  function subTestCAPTCHAAudioEnable() {
+    // Default should be enabled audio.
+    $this->drupalGet('mollom-test/form');
+    $this->assertLink(t('Switch to audio verification'));
+
+    // Verify that CAPTCHA cannot be switched when audio is disabled.
+    variable_set('mollom_audio_captcha_enabled', 0);
+    $this->drupalGet('mollom-test/form');
+    $this->assertNoLink(t('Switch to audio verification'));
+  }
 }
 
 /**
