Browse Source

Add byte range chunking support to Islandora.

pull/440/head
Jordan Dukart 11 years ago
parent
commit
19bb4b3468
  1. 150
      includes/datastream.inc

150
includes/datastream.inc

@ -70,14 +70,30 @@ function islandora_view_datastream(AbstractDatastream $datastream, $download = F
islandora_view_datastream_set_cache_headers($datastream);
drupal_page_is_cacheable(FALSE);
// Try not to load the file into PHP memory!
// Close and flush ALL the output buffers!
while (@ob_end_flush()) {
};
// New content needed.
if ($cache_check === 200) {
$datastream->getContent('php://output');
// We need to see if the chunking is being requested. This will mainly
// happen with iOS video requests as they do not support any other way
// to receive content for playback.
$chunk_headers = FALSE;
if (isset($_SERVER['HTTP_RANGE'])) {
// Set headers specific to chunking.
$chunk_headers = islandora_view_datastream_set_chunk_headers($datastream);
}
// Try not to load the file into PHP memory!
// Close and flush ALL the output buffers!
while (@ob_end_flush()) {
};
if (isset($_SERVER['HTTP_RANGE'])) {
if ($chunk_headers) {
islandora_view_datastream_deliver_chunks($datastream, $chunk_headers);
}
}
else {
$datastream->getContent('php://output');
}
}
exit();
}
@ -312,7 +328,7 @@ function islandora_edit_datastream(AbstractDatastream $datastream) {
case 0:
// No edit implementations.
drupal_set_message(t('There are no edit methods specified for this datastream.'));
drupal_goto("islandora/object/{$object->id}/manage/datastreams");
drupal_goto("islandora/object/{$datastream->parent->id}/manage/datastreams");
break;
case 1:
@ -383,3 +399,125 @@ function islandora_datastream_get_view_link(AbstractDatastream $datastream) {
'datastream' => $datastream,
));
}
/**
* Set the headers for the chunking of our content.
*
* @param AbstractDatastream $datastream
* An AbstractDatastream representing a datastream on a Fedora object.
*
* @return bool
* TRUE if there are chunks to be returned, FALSE otherwise.
*/
function islandora_view_datastream_set_chunk_headers(AbstractDatastream $datastream) {
$file_uri = islandora_view_datastream_retrieve_file_uri($datastream);
// The meat of this has been taken from:
// http://mobiforge.com/design-development/content-delivery-mobile-devices.
$size = filesize($file_uri);
$length = $size;
$start = 0;
$end = $size - 1;
header("Accept-Ranges: 0-$length");
if (isset($_SERVER['HTTP_RANGE'])) {
$c_start = $start;
$c_end = $end;
// Extract the range string.
list(, $range) = explode('=', $_SERVER['HTTP_RANGE'], 2);
// Make sure the client hasn't sent us a multibyte range.
if (strpos($range, ',') !== FALSE) {
// Not a valid range, notify the client.
header('HTTP/1.1 416 Requested Range Not Satisfiable');
header("Content-Range: bytes $start-$end/$size");
exit;
}
// If the range starts with an '-' we start from the beginning. If not, we
// forward the file pointer and make sure to get the end byte if specified.
if (strpos($range, '-') === 0) {
// The n-number of the last bytes is requested.
$c_start = $size - substr($range, 1);
}
else {
$range = explode('-', $range);
$c_start = $range[0];
$c_end = (isset($range[1]) && is_numeric($range[1])) ? $range[1] : $size;
}
/* Check the range and make sure it's treated according to the specs.
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
*/
// End bytes can not be larger than $end.
$c_end = ($c_end > $end) ? $end : $c_end;
// Validate the requested range and return an error if it's not correct.
if ($c_start > $c_end || $c_start > $size - 1 || $c_end >= $size) {
header('HTTP/1.1 416 Requested Range Not Satisfiable');
header("Content-Range: bytes $start-$end/$size");
exit;
}
$start = $c_start;
$end = $c_end;
// Calculate new content length.
$length = $end - $start + 1;
header('HTTP/1.1 206 Partial Content');
}
// Notify the client the byte range we'll be outputting.
header("Content-Range: bytes $start-$end/$size");
header("Content-Length: $length");
return array(
'start' => $start,
'end' => $end,
);
}
/**
* Deliver back the specified chunks of a file.
*
* @param AbstractDatastream $datastream
* An AbstractDatastream representing a datastream on a Fedora object.
* @param array $params
* An associate array containing the start and ending chunk bytes.
*/
function islandora_view_datastream_deliver_chunks(AbstractDatastream $datastream, $params) {
$file_uri = islandora_view_datastream_retrieve_file_uri($datastream);
// The meat of this has been taken from:
// http://mobiforge.com/design-development/content-delivery-mobile-devices.
$fp = @fopen($file_uri, 'rb');
fseek($fp, $params['start']);
// Start buffered download.
$buffer = 1024 * 8;
while (!feof($fp) && ($p = ftell($fp)) <= $params['end']) {
if ($p + $buffer > $params['end']) {
// In case we're only outputting a chunk, make sure we don't read past the
// length.
$buffer = $params['end'] - $p + 1;
}
// Reset time limit for big files.
set_time_limit(0);
echo fread($fp, $buffer);
}
fclose($fp);
}
/**
* Creates/returns the file URI for the content of a datastream for chunking.
*
* @param AbstractDatastream $datastream
* An AbstractDatastream representing a datastream on a Fedora object.
*
* @return string
* The URI of the file.
*/
function islandora_view_datastream_retrieve_file_uri(AbstractDatastream $datastream) {
$mime_detect = new MimeDetect();
$extension = $mime_detect->getExtension($datastream->mimetype);
$file_uri = 'temporary://chunk_' . $datastream->parent->id . '_' . $datastream->id . '_' . $datastream->createdDate->getTimestamp() . '.' . $extension;
if (!file_exists($file_uri)) {
$file = new stdClass();
$file->uri = $file_uri;
$file->filename = drupal_basename($file_uri);
$file->filemime = $datastream->mimeType;
$file->status = 0;
$datastream->getContent($file_uri);
file_save($file);
}
return $file_uri;
}

Loading…
Cancel
Save