Index: audio.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/audio/audio.module,v
retrieving revision 1.105.2.12
diff -u -p -r1.105.2.12 audio.module
--- audio.module	18 Dec 2007 00:10:13 -0000	1.105.2.12
+++ audio.module	25 Apr 2008 19:59:12 -0000
@@ -1528,10 +1528,8 @@ function audio_play($nid = FALSE) {
  * Variation on Drupal's file_transfer() function. The only difference
  * is that set_time_limit() is called to allow for large files.
  *
- * @param $source
- *   String specifying the file to transfer.
- * @param $headers
- *   An array of http headers to send along with file.
+ * @param $source File to transfer.
+ * @param $headers An array of http headers to send along with file.
  */
 function audio_file_transfer($source, $headers) {
   ob_end_clean();
@@ -1541,20 +1539,124 @@ function audio_file_transfer($source, $h
     // not followed by a space or a tab.
     // See http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
     $header = preg_replace('/\r?\n(?!\t| )/', '', $header);
-    header($header);
+    drupal_set_header($header);
   }
 
   $source = file_create_path($source);
 
-  // Transfer file in 1024 byte chunks to save memory usage.
   if ($fd = fopen($source, 'rb')) {
     if (!ini_get('safe_mode')){
       set_time_limit(0);
     }
-    while (!feof($fd)) {
-      print fread($fd, 1024);
-      ob_flush();
-      flush();
+    // Get file size.
+    $filesize = filesize($source);
+    $end_file = $filesize - 1;
+    // Set Last-Modified and ETag headers.
+    $filemtime = filemtime($source);
+    drupal_set_header('Last-Modified: '. gmdate('D, d M Y H:i:s', $filemtime) .' GMT');
+    drupal_set_header('ETag: '. dechex(fileinode($source)) .'-'. dechex($filemtime) .'-'. dechex($filesize));
+    // Tell client that we accept byte ranges.
+    drupal_set_header('Accept-Ranges: bytes');
+    $valid = FALSE;
+    $satisfiable = FALSE;
+    // Is this a partial request?
+    if (isset($_SERVER['HTTP_RANGE'])) {
+      preg_match('/^bytes=(\S+)$/i', $_SERVER['HTTP_RANGE'], $ranges);
+      $ranges = explode(',', array_pop($ranges));
+      // Check for valid ranges.
+      foreach ($ranges as $key => $range) {
+        if (substr_count($range, '-') == 1) {
+          $valid = TRUE;
+          if (substr($range, 0, 1) == '-') {
+            $satisfiable = TRUE;
+            $ranges[$key] = array('from' => max(0, $end_file + $range), 'to' => $end_file);
+          }
+          else {
+            $range = explode('-', $range);
+            if ($range[0] <= $end_file) {
+              if ($range[1] === '') {
+                $satisfiable = TRUE;
+                $ranges[$key] = array('from' => $range[0], 'to' => $end_file);
+              }
+              elseif ($range[0] <= $range[1]) {
+                $satisfiable = TRUE;
+                $ranges[$key] = array('from' => $range[0], 'to' => min($range[1], $end_file));
+              }
+            }
+          }
+        }
+        if (!$valid || !$satisfiable) {
+          unset($ranges[$key]);
+        }
+      }
+      // Check for overlapping ranges.
+      $cranges = $ranges;
+      foreach ($ranges as $key => $range) {
+        foreach ($cranges as $ckey => $crange) {
+          // Don't compare a range to itself.
+          if ($key == $ckey) {
+            unset($cranges[$ckey]);
+          }
+          else {
+            $overlap = FALSE;
+            // The beginning of this range is in another range.
+            if ($range['from'] >= $crange['from'] && $range['from'] <= $crange['to'] + 1) {
+              $ranges[$key]['from'] = $range['from'] = $crange['from'];
+              $overlap = TRUE;
+            }
+            // The end of this range is in another range.
+            if ($range['to'] <= $crange['to'] && $range['to'] >= $crange['from'] - 1) {
+              $ranges[$key]['to'] = $range['to'] = $crange['to'];
+              $overlap = TRUE;
+            }
+            if ($overlap) {
+              unset($ranges[$ckey], $cranges[$ckey]);
+            }
+          }
+        }
+      }
+    }
+    if ($satisfiable) {
+      drupal_set_header('HTTP/1.1 206 Partial Content');
+      if (count($ranges) == 1) {
+        $range = array_shift($ranges);
+        drupal_set_header(sprintf('Content-Range: bytes %d-%d/%d', $range['from'], $range['to'], $filesize));
+        drupal_set_header(sprintf('Content-Length: %d', $range['to'] + 1 - $range['from']));
+        _audio_file_transfer_range($fd, $range['to'] + 1 - $range['from'], $range['from']);
+      }
+      else {
+        $type = '';
+        foreach ($headers as $header) {
+          $header = trim($header);
+          if (stripos($header, 'Content-Type') === 0) {
+            $type = "$header\n";
+            break;
+          }
+        }
+        $boundary = md5(rand());
+        drupal_set_header("Content-Type: multipart/byteranges; boundary=$boundary");
+        foreach ($ranges as $range) {
+          print "--$boundary\n";
+          print $type;
+          print sprintf('Content-Range: bytes %d-%d/%d', $range['from'], $range['to'], $filesize) ."\n";
+          _audio_file_transfer_range($fd, $range['to'] + 1 - $range['from'], $range['from']);
+          print "\n";
+        }
+        print "--$boundary--";
+      }
+    }
+    else {
+      if ($valid && !isset($_SERVER['If-Range'])) {
+        drupal_set_header('HTTP/1.1 416 Requested Range Not Satisfiable');
+        drupal_set_header('Content-Range: *');
+      }
+      else {
+        $length = $filesize;
+      }
+    }
+    if (isset($length)) {
+      drupal_set_header("Content-Length: $length");
+      _audio_file_transfer_range($fd, $length);
     }
     fclose($fd);
   }
@@ -1565,6 +1667,22 @@ function audio_file_transfer($source, $h
 }
 
 /**
+ * Transfer a byte-range in 1024 byte chunks to save memory usage.
+ * @param $fd A file system pointer resource
+ * @param $length Number of bytes to transfer
+ * @param $byte Starting byte for this range
+ */
+function _audio_file_transfer_range($fd, $length, $byte = 0) {
+  // Move file pointer to starting byte.
+  fseek($fd, $byte);
+  while ($length > 0) {
+    $read = min(1024, $length);
+    print fread($fd, $read);
+    $length -= $read;
+  }
+}
+
+/**
  * Create audio nodes from a file.
  *
  * Function for other modules to use to create a audio node from a file. Once
