[
'embed' => '',
'default_width' => 640,
'default_ratio' => 1.2994923857868, // (640 / 493)
'https_enabled' => true,
'url_regex' => [
'#archive\.org/(?:details|embed)/([\d\w\-_][^/\?\#]+)#is'
],
'id_regex' => [
'#^([\d\w\-_][^/\?\#]+)$#is'
]
],
'bambuser' => [
'embed' => '',
'default_width' => 640,
'default_ratio' => 1.2994923857868, // (640 / 493)
'https_enabled' => true,
'url_regex' => [
'#bambuser\.com/(?:v|broadcast)/([\d\w\-\+]+)(?:/\S+?)?#is'
],
'id_regex' => [
'#^([\d\w\-\+]+)$#is'
]
],
'bambuser_channel' => [
'embed' => '',
'default_width' => 640,
'default_ratio' => 1.2994923857868, // (640 / 493)
'https_enabled' => true,
'url_regex' => [
'#bambuser\.com/channel/([\d\w\-\+]+)(?:/\S+?)?#is'
],
'id_regex' => [
'#^([\d\w\-\+]+)$#is'
]
],
'beam' => [
'embed' => '',
'default_width' => 640,
'default_ratio' => 1.77777777777778, // (16 / 9)
'https_enabled' => true,
'url_regex' => [
'#beam.pro/([\d\w\-\+]+)(?:/\S+?)?#is'
],
'id_regex' => [
'#^([\d\w\-\+]+)$#is'
]
],
'disclose' => [
'embed' => '',
'default_width' => 640,
'default_ratio' => 1.77777777777778, // (640 / 360)
'https_enabled' => true,
'url_regex' => [
'#disclose.tv/embed/([\d]+)/([\w-]+)#is',
'#disclose.tv/action/viewvideo/([\d]+)/([\w-]+)/#is'
],
'id_regex' => [
'#^([\d]+)$#is'
]
],
'blip' => [
'default_width' => 640,
'default_ratio' => 1.2994923857868, // (640 / 493)
'https_enabled' => false,
'url_regex' => [
'#(http://blip\.tv/[\w\d\-]+?/[\w\d\-]+?-[\d]+)#is'
],
'oembed' => 'http://blip.tv/oembed/?url=%1$s&width=%2$d&maxwidth=%2$d'
],
'bing' => [
'embed' => '',
'default_width' => 640,
'default_ratio' => 1.77777777777778, // (16 / 9)
'https_enabled' => true,
'url_regex' => [
'#bing.com/videos/watch/video/[\w\d\-]+?/([a-zA-Z0-9]+)(?:/\S+?)?#is'
],
'id_regex' => [
'#^([a-zA-Z0-9]+)$#is'
]
],
'collegehumor' => [
'embed' => '',
'default_width' => 640,
'default_ratio' => 1.6260162601626, // (600 / 369)
'https_enabled' => true,
'url_regex' => [
'#collegehumor\.com/(?:video|e)/([\d]+)#is'
],
'id_regex' => [
'#^([\d]+)$#is'
]
],
'dailymotion' => [
'embed' => '',
'default_width' => 640,
'default_ratio' => 1.77777777777778, // (16 / 9)
'https_enabled' => true,
'url_regex' => [
'#dailymotion\.com/(?:video|embed/video)/([a-zA-Z0-9]+)(?:_\S+?)?#is'
],
'id_regex' => [
'#^([a-zA-Z0-9]+)(?:_\S+?)?#is'
]
],
'divshare' => [
'embed' => '',
'default_width' => 640,
'default_ratio' => 1.77777777777778, // (16 / 9)
'https_enabled' => true
],
'funnyordie' => [
'embed' => '',
'default_width' => 640,
'default_ratio' => 1.64102564102564, // (640 / 390)
'https_enabled' => false,
'url_regex' => [
'#funnyordie\.com/(?:videos|embed)/([a-zA-Z0-9]+)(?:/\S+?)?#is'
],
'id_regex' => [
'#^([a-zA-Z0-9]+)$#is'
]
],
'gfycat' => [
'embed' => '',
'default_width' => 640,
'https_enabled' => true,
'url_regex' => [
'#gfycat\.com/([a-zA-Z]+)#is'
],
'id_regex' => [
'#^([a-zA-Z]+)$#is'
]
],
'hitbox' => [
'embed' => '',
'default_width' => 640,
'default_ratio' => 1.77777777777778, // (16 / 9)
'https_enabled' => false,
'url_regex' => [
'#hitbox.tv/([\d\w]+)(?:/\S+?)?#is'
],
'id_regex' => [
'#^([\d\w]+)$#is'
]
],
'jwplayer' => [
'embed' => '',
'default_width' => 640,
'default_ratio' => 1.77777777777778, // (16 / 9)
'https_enabled' => true,
'url_regex' => [
'#content\.jwplatform\.com/players/([a-zA-Z0-9]+-[a-zA-Z0-9]+)(?:.)?#is'
],
'id_regex' => [
'#([a-zA-Z0-9]+-[a-zA-Z0-9]+)#is'
]
],
'kickstarter' => [
'embed' => '',
'default_width' => 640,
'default_ratio' => 1.77777777777778, // (16 / 9)
'https_enabled' => true,
'url_regex' => [
'#kickstarter\.com/projects/([\d\w-]+/[\d\w-]+)(?:/widget/video.html)?#is'
],
'id_regex' => [
'#^([\d\w-]+/[\d\w-]+)$#is'
]
],
'mediacccde' => [
'embed' => '',
'default_width' => 660,
'default_ratio' => 1.77777777777778, //(16 / 9),
'https_enabled' => true,
'url_regex' => [
'#conferences/.*?([\d\w_-]+)(?:/oembed)?.html$#is'
],
'id_regex' => [
'#^([\d\w_-]+)$#is'
]
],
'metacafe' => [
'embed' => '',
'default_width' => 640,
'default_ratio' => 1.77777777777778, // (16 / 9)
'https_enabled' => false,
'url_regex' => [
'#metacafe\.com/(?:watch|embed)/([\d]+)(?:/\S+?)?#is'
],
'id_regex' => [
'#^([\d]+)$#is'
]
],
'nico' => [
'embed' => '',
'default_width' => 640,
'default_ratio' => 1.59609120521173, // (490 / 307)
'https_enabled' => false,
'url_regex' => [
'#nicovideo\.jp/watch/((?:[a-zA-Z]{2})?[\d]+)#is'
],
'id_regex' => [
'#^((?:[a-zA-Z]{2})?[\d]+)$#is'
]
],
'rutube' => [
'embed' => '',
'default_width' => 640,
'default_ratio' => 1.77777777777778, // (16 / 9)
'https_enabled' => true,
'url_regex' => [
'#rutube\.ru/video/([a-zA-Z0-9]+)(?:/\S+?)?#is'
],
'id_regex' => [
'#^([a-zA-Z0-9]+)$#is'
]
],
'soundcloud' => [
'embed' => '',
'default_width' => 186,
'default_ratio' => 2.66666,
'https_enabled' => true,
'url_regex' => [
'#^(https://soundcloud\.com/.+?/.+?)$#is',
]
],
'teachertube' => [
'embed' => '',
'default_width' => 640,
'default_ratio' => 1.72972972972973, // (640 / 370)
'https_enabled' => false,
'url_regex' => [
'#teachertube\.com/video/(?:[\w\d\-]+?-)?([\d]+)$#is',
],
'id_regex' => [
'#^([\d]+)$#is'
]
],
'ted' => [
'embed' => '',
'default_width' => 640,
'default_ratio' => 1.77777777777778, // (16 / 9)
'https_enabled' => true,
'url_regex' => [
'#ted\.com/talks/([\d\w\-]+)(?:/\S+?)?#is',
],
'id_regex' => [
'#^([\d\w\-]+)$#is'
]
],
'tubitv' => [
'embed' => '',
'default_width' => 640,
'default_ratio' => 1.77777777777778, // (640 / 360)
'https_enabled' => true,
'url_regex' => [
'#tubitv.com/(?:video|embed)/([\d]+)(/[\w-]+)?$#is',
],
'id_regex' => [
'#^([\d]+)$#is'
]
],
'tudou' => [
'embed' => '',
'default_width' => 640,
'default_ratio' => 1.6,
'https_enabled' => false,
'url_regex' => [
'#tudou.com/listplay/([\d\w-]+)/([\d\w-]+).html#is',
'#tudou.com/listplay/([\d\w-]+).html#is'
],
'id_regex' => [
'#^([\d\w-]+)$#is'
]
],
'tvpot' => [
'embed' => '',
'default_width' => 640,
'default_ratio' => 1.77777777777778, // (16 / 9)
'https_enabled' => true,
'url_regex' => [
'#tvpot\.daum\.net/v/([\d\w-%]+)?#is'
],
'id_regex' => [
'#^([\d\w-%]+)$#is'
]
],
'twitch' => [
'embed' => '',
'default_width' => 640,
'default_ratio' => 1.64021164021164, // (620 / 378)
'https_enabled' => false,
'url_regex' => [
'#twitch\.tv/([\d\w-]+)(?:/\S+?)?#is'
],
'id_regex' => [
'#^([\d\w-]+)$#is'
]
],
'twitchvod' => [
'embed' => '',
'default_width' => 640,
'default_ratio' => 1.64021164021164, // (620 / 378)
'https_enabled' => false,
'url_regex' => [
'#twitch\.tv/([\d\w-]+)(?:/\S+?)?#is'
],
'id_regex' => [
'#^([\d\w-]+)$#is'
]
],
'videomaten' => [
'embed' => '',
'default_ratio' => 1.5, // (300 / 200)
'https_enabled' => false
],
'vimeo' => [
'embed' => '',
'default_width' => 640,
'default_ratio' => 1.77777777777778, // (640 / 360)
'https_enabled' => true,
'url_regex' => [
'#vimeo\.com/([\d]+)#is',
'#vimeo\.com/channels/[\d\w-]+/([\d]+)#is'
],
'id_regex' => [
'#^([\d]+)$#is'
],
'oembed' => '%4$s//vimeo.com/api/oembed.json?url=%1$s&width=%2$d&maxwidth=%2$d'
],
'vine' => [
'embed' => '',
'default_width' => 640,
'default_ratio' => 1, // (1 / 1)
'https_enabled' => true,
'url_regex' => [
'#vine\.co/v/([a-zA-Z0-9]+)#is'
],
'id_regex' => [
'#^([a-zA-Z0-9]+)$#is'
]
],
'yahoo' => [
'embed' => '',
'default_width' => 640,
'default_ratio' => 1.77777777777778, // (16 / 9)
'https_enabled' => true,
'url_regex' => [
'#screen\.yahoo\.com/([\w\d\-]+?-\d+).html#is'
],
'id_regex' => [
'#^([\w\d\-]+?-\d+)$#is'
]
],
'youtube' => [
'embed' => '',
'default_width' => 640,
'default_ratio' => 1.77777777777778, // (16 / 9)
'https_enabled' => true,
'url_regex' => [
'#v=([\d\w-]+)(?:&\S+?)?#is',
'#youtu\.be/([\d\w-]+)#is'
],
'id_regex' => [
'#^([\d\w-]+)$#is'
],
'oembed' => [
'http' => 'http://www.youtube.com/oembed?url=%1$s&width=%2$d&maxwidth=%2$d',
'https' => 'http://www.youtube.com/oembed?scheme=https&url=%1$s&width=%2$d&maxwidth=%2$d'
]
],
'youtubeplaylist' => [
'embed' => '',
'default_width' => 640,
'default_ratio' => 1.77777777777778, // (16 / 9)
'https_enabled' => true,
'url_regex' => [
'#list=([\d\w-]+)(?:&\S+?)?#is'
],
'id_regex' => [
'#^([\d\w-]+)$#is'
]
],
'youtubevideolist' => [
'embed' => '',
'default_width' => 640,
'default_ratio' => 1.77777777777778, // (16 / 9)
'https_enabled' => true,
'url_regex' => [
'#playlist=([\d\w-]+)(?:&\S+?)?#is'
],
'id_regex' => [
'#^([\d\w-]+)$#is'
]
],
'youku' => [
'embed' => '',
'default_width' => 640,
'default_ratio' => 1.6,
'https_enabled' => false,
'url_regex' => [
'#id_([\d\w-]+).html#is',
],
'id_regex' => [
'#^(?:id_)?([\d\w-]+)$#is'
]
]
];
/**
* Mapping of host names to services
* @var array
*/
static private $serviceHostMap = [
'archive.org' => 'archiveorg',
'embed.bambuser.com' => ['bambuser', 'bambuser_channel'],
'beam.pro' => 'beam',
'blip.tv' => 'blip',
'bing.com' => 'bing',
'collegehumor.com' => 'collegehumor',
'dailymotion.com' => 'dailymotion',
'divshare.com' => 'divshare',
'funnyordie.com' => 'funnyordie',
'gfycat.com' => 'gfycat',
'hitbox.tv' => 'hitbox',
'content.jwplatform.com' => 'jwplayer',
'kickstarter.com' => 'kickstarter',
'media.ccc.de' => 'mideacccde',
'metacafe.com' => 'metacafe',
'nicovideo.jp' => 'nico',
'rutube.ru' => 'rutube',
'teachertube.com' => 'teachertube',
'ted.com' => 'ted',
'tubitv.com' => 'tubitv',
'tudou.com' => 'todou',
'tvpot.daum.net' => 'tvpot',
'twitch.tv' => ['twitch', 'twitchvod'],
'89.160.51.62' => 'videomaten',
'vimeo.com' => 'vimeo',
'vine.co' => 'vine',
'screen.yahoo.com' => 'yahoo',
'youtube.com' => ['youtube', 'youtubeplaylist', 'youtubevideolist'],
'youku.com' => 'youku'
];
/**
* This object instance's service information.
*
* @var array
*/
private $service = [];
/**
* Video ID
*
* @var array
*/
private $id = false;
/**
* Player Width
*
* @var integer
*/
private $width = false;
/**
* Player Height
*
* @var integer
*/
private $height = false;
/**
* Description Text
*
* @var string
*/
private $description = false;
/**
* Extra IDs that some services require.
*
* @var array
*/
private $extraIDs = false;
/**
* Extra URL Arguments that may be utilized by some services.
*
* @var array
*/
private $urlArgs = false;
/**
* Main Constructor
*
* @access private
* @param string Service Name
* @return void
*/
private function __construct($service) {
$this->service = self::$services[$service];
}
/**
* Create a new object from a service name.
*
* @access public
* @param string Service Name
* @return mixed New VideoService object or false on initialization error.
*/
static public function newFromName($service) {
if (isset(self::$services[$service])) {
return new self($service);
} else {
return false;
}
}
/**
* return the service host map array
* @return array $serviceHostMap
*/
public static function getServiceHostMap() {
return self::$serviceHostMap;
}
/**
* return an array of defined services
*
* @return array $services
*/
public static function getAvailableServices() {
return array_keys(self::$services);
}
/**
* Add a service
*
* @access public
* @param string Service Name
* @param mixed args
*/
static public function addService($service, $args) {
if (isset(self::$services[$service])) {
throw new MWException("Service already already exists: $service");
}
self::$services[$service] = $args;
}
/**
* Return built HTML.
*
* @access public
* @return mixed String HTML to output or false on error.
*/
public function getHtml() {
if ($this->getVideoID() === false || $this->getWidth() === false || $this->getHeight() === false) {
return false;
}
$html = false;
if (isset($this->service['embed'])) {
// Embed can be generated locally instead of calling out to the service to get it.
$data = [
$this->service['embed'],
htmlentities($this->getVideoID(), ENT_QUOTES),
$this->getWidth(),
$this->getHeight(),
];
if ($this->getExtraIds() !== false) {
foreach ($this->getExtraIds() as $extraId) {
$data[] = htmlentities($extraId, ENT_QUOTES);
}
}
$urlArgs = $this->getUrlArgs();
if ($urlArgs !== false) {
$data[] = $urlArgs;
}
$html = call_user_func_array('sprintf', $data);
} elseif (isset($this->service['oembed'])) {
// Call out to the service to get the embed HTML.
if ($this->service['https_enabled']) {
if (stristr($this->getVideoID(), 'https:') !== false) {
$protocol = 'https:';
} else {
$protocol = 'http:';
}
}
$url = sprintf(
$this->service['oembed'],
$this->getVideoID(),
$this->getWidth(),
$this->getHeight(),
$protocol
);
$oEmbed = OEmbed::newFromRequest($url);
if ($oEmbed !== false) {
$html = $oEmbed->getHtml();
}
}
return $html;
}
/**
* Return Video ID
*
* @access public
* @return mixed Parsed Video ID or false for one that is not set.
*/
public function getVideoID() {
return $this->id;
}
/**
* Set the Video ID for this video.
*
* @access public
* @param string Video ID/URL
* @return boolean Success
*/
public function setVideoID($id) {
$id = $this->parseVideoID($id);
if ($id !== false) {
$this->id = $id;
return true;
} else {
return false;
}
}
/**
* Parse the video ID/URL provided.
*
* @access public
* @param string Video ID/URL
* @return mixed Parsed Video ID or false on failure.
*/
public function parseVideoID($id) {
$id = trim($id);
// URL regexes are put into the array first to prevent cases where the ID regexes might accidentally match an incorrect portion of the URL.
$regexes = array_merge((array) $this->service['url_regex'], (array) $this->service['id_regex']);
if (is_array($regexes) && count($regexes)) {
foreach ($regexes as $regex) {
if (preg_match($regex, $id, $matches)) {
// Get rid of the full text match.
array_shift($matches);
$id = array_shift($matches);
if (count($matches)) {
$this->extraIDs = $matches;
}
return $id;
}
}
// If nothing matches and matches are specified then return false for an invalid ID/URL.
return false;
} else {
// Service definition has not specified a sanitization/validation regex.
return $id;
}
}
/**
* Return extra IDs.
*
* @access public
* @return boolean Array of extra information or false if not set.
*/
public function getExtraIDs() {
return $this->extraIDs;
}
/**
* Return the width.
*
* @access public
* @return mixed Integer value or false for not set.
*/
public function getWidth() {
return $this->width;
}
/**
* Set the width of the player. This also will set the height automatically.
* Width will be automatically constrained to the minimum and maximum widths.
*
* @access public
* @param integer Width
* @return void
*/
public function setWidth($width = null) {
global $wgEmbedVideoMinWidth, $wgEmbedVideoMaxWidth, $wgEmbedVideoDefaultWidth;
if (!is_numeric($width)) {
if ($width === null && $this->getDefaultWidth() !== false && $wgEmbedVideoDefaultWidth < 1) {
$width = $this->getDefaultWidth();
} else {
$width = ($wgEmbedVideoDefaultWidth > 0 ? $wgEmbedVideoDefaultWidth : 640);
}
} else {
$width = intval($width);
}
if ($wgEmbedVideoMaxWidth > 0 && $width > $wgEmbedVideoMaxWidth) {
$width = $wgEmbedVideoMaxWidth;
}
if ($wgEmbedVideoMinWidth > 0 && $width < $wgEmbedVideoMinWidth) {
$width = $wgEmbedVideoMinWidth;
}
$this->width = $width;
if ($this->getHeight() === false) {
$this->setHeight();
}
}
/**
* Return the height.
*
* @access public
* @return mixed Integer value or false for not set.
*/
public function getHeight() {
return $this->height;
}
/**
* Set the height automatically by a ratio of the width or use the provided value.
*
* @access public
* @param mixed [Optional] Height Value
* @return void
*/
public function setHeight($height = null) {
if ($height !== null && $height > 0) {
$this->height = intval($height);
return;
}
$ratio = 16 / 9;
if ($this->getDefaultRatio() !== false) {
$ratio = $this->getDefaultRatio();
}
$this->height = round($this->getWidth() / $ratio);
}
/**
* Return the optional URL arguments.
*
* @access public
* @return mixed Integer value or false for not set.
*/
public function getUrlArgs() {
if ($this->urlArgs !== false) {
return http_build_query($this->urlArgs);
}
}
/**
* Set URL Arguments to optionally add to the embed URL.
*
* @access public
* @param string Raw Arguments
* @return boolean Success
*/
public function setUrlArgs($urlArgs) {
if (!$urlArgs) {
return true;
}
$urlArgs = urldecode($urlArgs);
$_args = explode('&', $urlArgs);
if (is_array($_args)) {
foreach ($_args as $rawPair) {
list($key, $value) = explode("=", $rawPair, 2);
if (empty($key) || ($value === null || $value === '')) {
return false;
}
$arguments[$key] = htmlentities($value, ENT_QUOTES);
}
} else {
return false;
}
$this->urlArgs = $arguments;
return true;
}
/**
* Is HTTPS enabled?
*
* @access public
* @return boolean
*/
public function isHttpsEnabled() {
return (bool) $this->service['https_enabled'];
}
/**
* Return default width if set.
*
* @access public
* @return mixed Integer width or false if not set.
*/
public function getDefaultWidth() {
return ($this->service['default_width'] > 0 ? $this->service['default_width'] : false);
}
/**
* Return default ratio if set.
*
* @access public
* @return mixed Integer ratio or false if not set.
*/
public function getDefaultRatio() {
return ($this->service['default_ratio'] > 0 ? $this->service['default_ratio'] : false);
}
}