| Current File : /home/jvzmxxx/wiki/extensions/TimedMediaHandler/TimedMediaTransformOutput.php |
<?php
class TimedMediaTransformOutput extends MediaTransformOutput {
private static $serial = 0;
// Video file sources object lazy init in getSources()
// TODO these vars should probably be private
public $sources = null;
public $textTracks = null;
public $hashTime = null;
public $textHandler = null; // lazy init in getTextHandler
public $disablecontrols = null;
public $start;
public $end;
public $fillwindow;
protected $playerClass;
// The prefix for player ids
const PLAYER_ID_PREFIX = 'mwe_player_';
function __construct( $conf ) {
$options = [ 'file', 'dstPath', 'sources', 'thumbUrl', 'start', 'end',
'width', 'height', 'length', 'offset', 'isVideo', 'path', 'fillwindow',
'sources', 'disablecontrols', 'playerClass' ];
foreach ( $options as $key ) {
if ( isset( $conf[ $key ] ) ) {
$this->$key = $conf[$key];
} else {
$this->$key = false;
}
}
}
/**
* @return TextHandler
*/
function getTextHandler() {
if ( !$this->textHandler ) {
// Init an associated textHandler
$this->textHandler = new TextHandler( $this->file );
}
return $this->textHandler;
}
/**
* Get the media transform thumbnail
* @return string
*/
function getUrl( $sizeOverride = false ) {
global $wgVersion, $wgResourceBasePath, $wgStylePath;
// Needs to be 1.24c because version_compare() works in confusing ways
if ( version_compare( $wgVersion, '1.24c', '>=' ) ) {
$url = "$wgResourceBasePath/resources/assets/file-type-icons/fileicon-ogg.png";
} else {
$url = "$wgStylePath/common/images/icons/fileicon-ogg.png";
}
if ( $this->isVideo ) {
if ( $this->thumbUrl ) {
$url = $this->thumbUrl;
}
// Update the $posterUrl to $sizeOverride ( if not an old file )
if ( !$this->file->isOld() && $sizeOverride &&
$sizeOverride[0] && intval( $sizeOverride[0] ) != intval( $this->width ) ) {
$apiUrl = $this->getPoster( $sizeOverride[0] );
if ( $apiUrl ) {
$url = $apiUrl;
}
}
}
return $url;
}
/**
* TODO get the local path
* @return mixed
*/
function getPath() {
return $this->dstPath;
}
/**
* @return int
*/
function getPlayerHeight() {
// Check if "video" tag output:
if ( $this->isVideo ) {
return intval( $this->height );
} else {
// Give sound files a height of 23px
return 23;
}
}
/**
* @return int
*/
function getPlayerWidth() {
// Check if "video" tag output:
if ( $this->isVideo ) {
return intval( $this->width );
} else {
// Give sound files a width of 300px ( if unsized )
if ( $this->width == 0 ) {
return 300;
}
// else give the target size down to 35 px wide
return ( $this->width < 35 ) ? 35 : intval( $this->width );
}
}
/**
* @return string
*/
function getTagName() {
return ( $this->isVideo ) ? 'video' : 'audio';
}
/**
* @param $options array
* @return string
* @throws Exception
*/
function toHtml( $options = [] ) {
if ( count( func_get_args() ) == 2 ) {
throw new Exception( __METHOD__ .' called in the old style' );
}
$oldHeight = $this->height;
$oldWidth = $this->width;
if ( isset( $options['override-height'] ) ) {
$this->height = $options['override-height'];
}
if ( isset( $options['override-width'] ) ) {
$this->width = $options['override-width'];
}
if ( $this->useImagePopUp() && TimedMediaHandlerHooks::activePlayerMode() === 'mwembed' ) {
$res = $this->getImagePopUp();
} else {
$res = $this->getHtmlMediaTagOutput();
}
$this->width = $oldWidth;
$this->height = $oldHeight;
return $res;
}
/**
* Helper to determine if to use pop up dialog for videos
*
* @return boolean
*/
private function useImagePopUp() {
global $wgMinimumVideoPlayerSize;
// Check if the video is too small to play inline ( instead do a pop-up dialog )
// If we're filling the window (e.g. during an iframe embed) one probably doesn't want the pop up.
// Also the pop up is broken in that case.
return $this->isVideo
&& !$this->fillwindow
&& $this->getPlayerWidth() < $wgMinimumVideoPlayerSize
// Do not do pop-up if its going to be the same size as inline player anyways
&& $this->getPlayerWidth() < $this->getPopupPlayerWidth();
}
/**
* XXX migrate this to the mediawiki Html class as 'tagSet' helper function
* @param $tagName
* @param $tagSet
* @return string
*/
static function htmlTagSet( $tagName, $tagSet ) {
if ( empty( $tagSet ) ) {
return '';
}
$s = '';
foreach ( $tagSet as $attr ) {
$s .= Html::element( $tagName, $attr );
}
return $s;
}
/**
* @return string
*/
function getImagePopUp() {
// pop up videos set the autoplay attribute to true:
$autoPlay = true;
return Xml::tags( 'div', [
'id' => self::PLAYER_ID_PREFIX . TimedMediaTransformOutput::$serial++,
'class' => 'PopUpMediaTransform',
'style' => "width:" . $this->getPlayerWidth() . "px;",
'videopayload' => $this->getHtmlMediaTagOutput( $this->getPopupPlayerSize(), $autoPlay ),
],
Xml::tags( 'img', [
'alt' => $this->file->getTitle(),
'style' => "width:" . $this->getPlayerWidth() . "px;height:" .
$this->getPlayerHeight() . "px",
'src' => $this->getUrl(),
], '' )
.
// For javascript disabled browsers provide a link to the asset:
Xml::tags( 'a', [
'href'=> $this->file->getUrl(),
'title' => wfMessage( 'timedmedia-play-media' )->escaped(),
'target' => 'new'
],
Xml::tags( 'span', [
'class' => 'play-btn-large'
],
// Have some sort of text for lynx & screen readers.
Html::element(
'span',
[ 'class' => 'mw-tmh-playtext' ],
wfMessage( 'timedmedia-play-media' )->text()
)
)
)
);
}
/**
* Get target popup player size
*/
function getPopupPlayerSize() {
// Get the max width from the enabled transcode settings:
$maxImageSize = WebVideoTranscode::getMaxSizeWebStream();
return WebVideoTranscode::getMaxSizeTransform( $this->file, $maxImageSize );
}
/**
* Helper function to get pop up width
*
* Silly function because array index operations aren't allowed
* on function calls before php 5.4
*/
private function getPopupPlayerWidth() {
list( $popUpWidth ) = $this->getPopupPlayerSize();
return $popUpWidth;
}
/**
* Sort media by bandwidth, but with things not wide enough at end
*
* The list should be in preferred source order, so we want the file
* with the lowest bitrate (to save bandwidth) first, but we also want
* appropriate resolution files before the 160p transcodes.
*/
private function sortMediaByBandwidth( $a, $b ) {
$width = $this->getPlayerWidth();
$maxWidth = $this->getPopupPlayerWidth();
if ( $this->useImagePopUp() || $width > $maxWidth ) {
// If its a pop-up player than we should use the pop up player size
// if its a normal player, but has a bigger width than the pop-up
// player, then we use the pop-up players width as the target width
// as that is equivalent to the max transcode size. Otherwise this
// will suggest the original file as the best source, which seems like
// a potentially bad idea, as it could be anything size wise.
$width = $maxWidth;
}
if ( $a['width'] < $width && $b['width'] >= $width ) {
// $a is not wide enough but $b is
// so we consider $a > $b as we want $b before $a
return 1;
}
if ( $a['width'] >= $width && $b['width'] < $width ) {
// $b not wide enough, so $a must be preferred.
return -1;
}
if ( $a['width'] < $width && $b['width'] < $width && $a['width'] != $b['width'] ) {
// both are too small. Go with the one closer to the target width
return ( $a['width'] < $b['width'] ) ? -1 : 1;
}
// Both are big enough, or both equally too small. Go with the one
// that has a lower bit-rate (as it will be faster to download).
if ( isset( $a['bandwidth'] ) && isset( $b['bandwidth'] ) ) {
return ( $a['bandwidth'] < $b['bandwidth'] ) ? -1 : 1;
}
// We have no firm basis for a comparison, so consider them equal.
return 0;
}
/**
* Call mediaWiki xml helper class to build media tag output from
* supplied arrays.
*
* This function is also called by the Score extension, in which case
* there is no connection to a file object.
*
* @param $sizeOverride array
* @param $autoPlay boolean sets the autoplay attribute
* @return string
*/
function getHtmlMediaTagOutput( $sizeOverride = [], $autoPlay = false ) {
// Try to get the first source src attribute ( usually this should be the source file )
$mediaSources = $this->getMediaSources();
reset( $mediaSources ); // do not rely on auto-resetting of arrays under HHVM
$firstSource = current( $mediaSources );
if ( !$firstSource['src'] ) {
// XXX media handlers don't seem to work with exceptions..
return 'Error missing media source';
};
// Sort sources by bandwidth least to greatest ( so default selection on resource constrained
// browsers ( without js? ) go with minimal source.
usort( $mediaSources, [ $this, 'sortMediaByBandwidth' ] );
// We prefix some source attributes with data- to pass along to the javascript player
$prefixedSourceAttr = [
'width',
'height',
'title',
'shorttitle',
'bandwidth',
'framerate',
'disablecontrols',
'transcodekey',
'label',
'res',
];
foreach ( $mediaSources as &$source ) {
foreach ( $source as $attr => $val ) {
if ( in_array( $attr, $prefixedSourceAttr ) ) {
$source[ 'data-' . $attr ] = $val;
unset( $source[ $attr ] );
}
}
}
$mediaTracks = $this->file ? $this->getTextHandler()->getTracks() : [];
foreach ( $mediaTracks as &$track ) {
foreach ( $track as $attr => $val ) {
if ( $attr === 'title' || $attr === 'provider' ) {
$track[ 'data-mw' . $attr ] = $val;
unset( $track[ $attr ] );
} elseif ( $attr === 'dir' ) {
$track[ 'data-' . $attr ] = $val;
unset( $track[ $attr ] );
}
}
}
$width = $sizeOverride ? $sizeOverride[0] : $this->getPlayerWidth();
if ( $this->fillwindow ) {
$width = '100%';
} else {
$width .= 'px';
}
// Build the video tag output:
$s = Html::rawElement( $this->getTagName(), $this->getMediaAttr( $sizeOverride, $autoPlay ),
// The set of media sources:
self::htmlTagSet( 'source', $mediaSources ) .
// Timed text:
self::htmlTagSet( 'track', $mediaTracks )
);
if ( TimedMediaHandlerHooks::activePlayerMode() === 'videojs' ) {
return $s;
} // else mwEmbed player
// Build the video tag output:
return Xml::tags( 'div', [
'class' => 'mediaContainer',
'style' => 'width:'. $width
], $s );
}
/**
* Get poster.
* @param $width Integer width of poster. Should not equal $this->width.
* @throws Exception If $width is same as $this->width.
* @return String|bool url for poster or false
*/
function getPoster( $width ) {
if ( intval( $width ) === intval( $this->width ) ) {
// Prevent potential loop
throw new Exception( "Asked for poster in current size. Potential loop." );
}
$params = [ "width" => intval( $width ) ];
$mto = $this->file->transform( $params );
if ( $mto ) {
return $mto->getUrl();
} else {
return false;
}
}
/**
* Get the media attributes
* @param $sizeOverride Array|bool of width and height
* @return array
*/
function getMediaAttr( $sizeOverride = false, $autoPlay = false ) {
global $wgVideoPlayerSkin;
// Normalize values
$length = floatval( $this->length );
$offset = floatval( $this->offset );
$width = $sizeOverride ? $sizeOverride[0] : $this->getPlayerWidth();
$height = $sizeOverride ? $sizeOverride[1]: $this->getPlayerHeight();
if ( $this->fillwindow ) {
$width = '100%';
$height = '100%';
} else {
$width .= 'px';
$height .= 'px';
}
$mediaAttr = [
'id' => self::PLAYER_ID_PREFIX . TimedMediaTransformOutput::$serial++,
// Get the correct size:
'poster' => $this->getUrl( $sizeOverride ),
// Note we set controls to true ( for no-js players ) when mwEmbed rewrites the interface
// it updates the controls attribute of the embed video
'controls'=> 'true',
// Since we will reload the item with javascript,
// tell browser to not load the video before
'preload'=>'none',
];
if ( $autoPlay === true ) {
$mediaAttr['autoplay'] = 'true';
}
if ( !$this->isVideo ) {
// audio element doesn't have poster attribute
unset( $mediaAttr[ 'poster' ] );
}
if ( TimedMediaHandlerHooks::activePlayerMode() === 'videojs' ) {
// Note: do not add 'video-js' class before the runtime transform!
$mediaAttr['class'] = $wgVideoPlayerSkin;
$mediaAttr['width'] = $this->fillwindow ? '100%' : intval( $width );
if ( $this->isVideo ) {
$mediaAttr['height'] = $this->fillwindow ? '100%' : intval( $height );
} else {
unset( $mediaAttr['height'] );
}
if ( $this->fillwindow ) {
$mediaAttr[ 'class' ] .= ' vjs-fluid';
$mediaAttr[ 'data-player' ] = 'fillwindow';
}
} else {
$mediaAttr['style'] = "width:{$width}";
if ( $this->isVideo ) {
$mediaAttr['style'] .= ";height:{$height}";
}
// MediaWiki uses the kSkin class
$mediaAttr['class'] = 'kskin';
}
// Used by Score extension and to disable specific controls from wikicode
if ( $this->disablecontrols ) {
$mediaAttr[ 'data-disablecontrols' ] = $this->disablecontrols;
}
// Additional class-name provided by Transform caller
if ( $this->playerClass ) {
$mediaAttr[ 'class' ] .= ' ' . $this->playerClass;
}
if ( $this->file ) {
// Custom data-attributes
$mediaAttr += [
'data-durationhint' => $length,
'data-startoffset' => $offset,
'data-mwtitle' => $this->file->getTitle()->getDBkey()
];
// Add api provider:
if ( $this->file->isLocal() ) {
$apiProviderName = 'local';
} else {
// Set the api provider name to "wikimediacommons" for shared ( instant commons convention )
// (provider names should have identified the provider instead of the provider type "shared")
$apiProviderName = $this->file->getRepoName();
if ( $apiProviderName == 'shared' ) {
$apiProviderName = 'wikimediacommons';
}
}
// XXX Note: will probably migrate mwprovider to an escaped api url.
$mediaAttr[ 'data-mwprovider' ] = $apiProviderName;
} else {
if ( $length ) {
$mediaAttr[ 'data-durationhint' ] = $length;
}
if ( $offset ) {
$mediaAttr[ 'data-startoffset' ] = $offset;
}
}
return $mediaAttr;
}
/**
* @return null
*/
function getMediaSources() {
if ( !$this->sources ) {
// Generate transcode jobs ( and get sources that are already transcoded)
// At a minimum this should return the source video file.
$this->sources = WebVideoTranscode::getSources( $this->file );
// Check if we have "start or end" times and append the temporal url fragment hash
foreach ( $this->sources as &$source ) {
$source['src'] .= $this->getTemporalUrlHash();
}
}
return $this->sources;
}
function getTemporalUrlHash() {
if ( $this->hashTime ) {
return $this->hashTime;
}
$hash = '';
if ( $this->start ) {
$startSec = TimedMediaHandler::parseTimeString( $this->start );
if ( $startSec !== false ) {
$hash .= '#t=' . TimedMediaHandler::seconds2npt( $startSec );
}
}
if ( $this->end ) {
if ( $hash == '' ) {
$hash .= '#t=0';
}
$endSec = TimedMediaHandler::parseTimeString( $this->end );
if ( $endSec !== false ) {
$hash .= ',' . TimedMediaHandler::seconds2npt( $endSec );
}
}
$this->hashTime = $hash;
return $this->hashTime;
}
public static function resetSerialForTest() {
self::$serial = 1;
}
}