Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 57 additions & 5 deletions core/components/com_resources/helpers/html.php
Original file line number Diff line number Diff line change
Expand Up @@ -630,6 +630,38 @@ public static function citation($option, $cite, $id, $citations, $type, $rev='')
return $html;
}

/**
* Determine if a legacy media type is actually holding a playable video file
*
* Video attachments predating the 'video' resource type are typed
* 'quicktime' or 'player'. The router knows neither, so they fall through
* to a download link even though they are plain video files. Route those to
* the HTML5 player instead - but only where the player can handle them: the
* file has to be one of the formats the manifest builder collects, and it
* has to live in the resource's own filespace.
*
* @param object $item Child resource
* @return boolean
*/
public static function isLegacyPlayableVideo($item)
{
if (!in_array($item->type->alias, array('quicktime', 'player')))
{
return false;
}

// Externally hosted or absolute paths have no filespace to build a manifest from
if (strstr($item->path, 'http')
|| substr($item->path, 0, 3) == 'mms'
|| substr($item->path, 0, 1) == '/')
{
return false;
}

// Keep in sync with the extensions buildVideoManifestForResource() globs for
return (bool) preg_match('/\.(mp4|ogv|webm)$/i', $item->path);
}

/**
* Determine the final URL for the primary resource child
*
Expand All @@ -649,6 +681,14 @@ public static function processPath($option, $item, $pid=0, $action=0)
{
$type = $item->type->alias;

// Legacy media types still carry plain video files. Send those to the
// player too, unless the caller explicitly asked for the file itself
// ($action 3 - the OAI-PMH miner harvests direct file URLs).
if ($action != 3 && self::isLegacyPlayableVideo($item))
{
return Route::url('index.php?option=' . $option . '&id=' . $pid . '&resid=' . $item->id . '&task=video');
}

switch ($type)
{
case 'ilink':
Expand Down Expand Up @@ -1034,6 +1074,16 @@ public static function primary_child($option, $resource, $firstChild, $xact='')
$childParams = $firstChild->params;
$linkAction = intval($childParams->get('link_action', $linkAction));

$isVideo = ($firstChild->type->alias == 'video' || self::isLegacyPlayableVideo($firstChild));

// A playable video always goes to the player. The generic linkAction
// hint the type carries - legacy logical types such as
// 'Podcast (video)' are commonly set to 'download' - must not win.
if ($isVideo)
{
$linkAction = 0;
}

$url = self::processPath($option, $firstChild, $resource->id, $linkAction);

switch ($linkAction)
Expand Down Expand Up @@ -1077,6 +1127,13 @@ public static function primary_child($option, $resource, $firstChild, $xact='')
//$rt = new \Components\Resources\Tables\Type($database);
//$rt->load($firstChild->type);

// resources.js keys the inline embed off this class
if ($isVideo)
{
$class = 'video';
$mesg = Lang::txt('COM_RESOURCES_VIEW_PRESENTATION');
}

//if we are a hubpresenter resource type, do not show file type in button
if ($firstChild->type->alias == 'hubpresenter')
{
Expand All @@ -1089,11 +1146,6 @@ public static function primary_child($option, $resource, $firstChild, $xact='')
$mesg .= ' ' . self::getFileAttribs($firstChild->path);
}

if ($firstChild->type->alias == 'video')
{
$class = 'video';
}

if ($resource->type->alias == 'databases')
{
$mesg = "View Data";
Expand Down
86 changes: 42 additions & 44 deletions core/components/com_resources/site/controllers/resources.php
Original file line number Diff line number Diff line change
Expand Up @@ -1083,22 +1083,33 @@ public function videoTask()
App::abort(403, Lang::txt('COM_CONTRIBUTE_NOT_AUTH'));
}

// Check to see if we have a manifest
if (!$this->videoManifestExistsForResource($activechild))
// Load the manifest, rebuilding it from the filespace when it is missing or unusable
$manifest = null;
$manifestPath = PATH_APP . $this->getVideoManifestForResource($activechild);

if (is_file($manifestPath))
{
$this->createVideoManifestForResource($activechild);
$manifest = json_decode(file_get_contents($manifestPath));
}

// Get manifest
$manifest = $this->getVideoManifestForResource($activechild);
if (!isset($manifest->presentation))
{
$manifest = $this->buildVideoManifestForResource($activechild);

if (!file_exists(PATH_APP . $manifest))
// Cache it for next time, but do not require the write to succeed - an
// unwritable filespace should not cost the user the player. An empty
// manifest is never cached, or it would mask files landing later.
if (count($manifest->presentation->media) > 0)
{
\Filesystem::write($manifestPath, json_encode($manifest, JSON_PRETTY_PRINT));
}
}

if (empty($manifest->presentation->media))
{
App::abort(404, Lang::txt('COM_RESOURCES_RESOURCE_NOT_FOUND'));
}

$manifest = json_decode(file_get_contents(PATH_APP . $manifest));

// Media tracking object
require_once dirname(dirname(__DIR__)) . DS . 'models' . DS . 'mediatracking.php';

Expand Down Expand Up @@ -1168,12 +1179,17 @@ public function videoTask()
}

/**
* Get Video Manifest for resource
* Get the filespace holding a resource's video, relative to PATH_APP
*
* @param object $resource HUB Resource
* @return boolean
* Both the reader and the writer go through here. filespace() is not
* interchangeable with it: that derives the directory from the created date
* and a zero-padded id, which does not always agree with the path actually
* stored on the attachment.
*
* @param object $resource HUB Resource
* @return string
*/
private function getVideoManifestForResource($resource)
private function getVideoPathForResource($resource)
{
// Base url for the resource
$base = DS . trim($this->config->get('uploadpath'), DS);
Expand All @@ -1188,41 +1204,32 @@ private function getVideoManifestForResource($resource)
// Build the rest of the resource path and combine with base
$path = $path ? $path : $resource->relativepath();

// Get manifests
$manifests = \Filesystem::files(PATH_APP . DS . $base . $path, '.json');

// Return path to manifest if we have one
return (count($manifests) > 0) ? $base . $path . DS . $manifests[0] : array();
return $base . $path;
}

/**
* Check for Video manifest file
* Get the path to a resource's video manifest, relative to PATH_APP
*
* Named explicitly rather than taken as "the first .json in the directory" -
* an unrelated sidecar would otherwise be loaded as the manifest.
*
* @param object $resource HUB Resource
* @return boolean
* @param object $resource HUB Resource
* @return string
*/
private function videoManifestExistsForResource($resource)
private function getVideoManifestForResource($resource)
{
// Get video manifest
$manifest = $this->getVideoManifestForResource($resource);

// Do we have a manifest already?
return (!is_array($manifest) || count($manifest) < 1) ? false : true;
return $this->getVideoPathForResource($resource) . DS . 'presentation.json';
}

/**
* Create manifest file for video
* Build a video manifest for a resource from the files in its filespace
*
* @param object $resource HUB Resource
* @return boolean
* @return object
*/
private function createVideoManifestForResource($resource)
private function buildVideoManifestForResource($resource)
{
//base url for the resource
//$base = DS . trim($this->config->get('uploadpath'), DS);

//build the rest of the resource path and combine with base
$path = $resource->filespace();
$path = PATH_APP . $this->getVideoPathForResource($resource);

//instantiate params object then parse resource attributes
$attributes = $resource->attribs;
Expand Down Expand Up @@ -1302,16 +1309,7 @@ private function createVideoManifestForResource($resource)
$manifest->presentation->media = array_values($manifest->presentation->media);
$manifest->presentation->subtitles = array_values($manifest->presentation->subtitles);

// json encode manifest
$manifest = json_encode($manifest, JSON_PRETTY_PRINT);

// attempt to create manifest file
if (!\Filesystem::write($path . DS . 'presentation.json', $manifest))
{
return false;
}

return true;
return $manifest;
}

/**
Expand Down