[FEATURE] HTML5 video poster preview image
[Packages/TYPO3.CMS.git] / typo3 / sysext / mediace / Classes / ContentObject / FlowPlayerContentObject.php
1 <?php
2 namespace TYPO3\CMS\Mediace\ContentObject;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use TYPO3\CMS\Core\Utility\ArrayUtility;
18 use TYPO3\CMS\Core\Utility\GeneralUtility;
19
20 /**
21 * Contains FlowPlayer class object.
22 */
23 class FlowPlayerContentObject extends \TYPO3\CMS\Frontend\ContentObject\AbstractContentObject {
24
25 /**
26 * File extension to mime type
27 *
28 * @var array
29 */
30 public $mimeTypes = array(
31 'aif' => array(
32 'audio' => 'audio/aiff'
33 ),
34 'au' => array(
35 'audio' => 'audio/x-au'
36 ),
37 'asf' => array(
38 'video' => 'video/x-ms-asf'
39 ),
40 'avi' => array(
41 'audio' => 'video/x-msvideo'
42 ),
43 'class' => array(
44 'audio' => 'application/java',
45 'video' => 'application/java'
46 ),
47 'dcr' => array(
48 'video' => 'application/x-director'
49 ),
50 'flac' => array(
51 'audio' => 'audio/flac'
52 ),
53 'flv' => array(
54 'video' => 'video/x-flv'
55 ),
56 'm4a' => array(
57 'audio' => 'audio/mp4a-latm'
58 ),
59 'm4v' => array(
60 'video' => 'video/x-m4v'
61 ),
62 'mov' => array(
63 'video' => 'video/quicktime'
64 ),
65 'mp3' => array(
66 'audio' => 'audio/mpeg'
67 ),
68 'mp4' => array(
69 'video' => 'video/mp4'
70 ),
71 'oga' => array(
72 'audio' => 'audio/ogg'
73 ),
74 'ogg' => array(
75 'audio' => 'audio/ogg',
76 'video' => 'video/ogg'
77 ),
78 'ogv' => array(
79 'video' => 'video/ogg'
80 ),
81 'qt' => array(
82 'video' => 'video/quicktime'
83 ),
84 'swa' => array(
85 'audio' => 'application/x-director'
86 ),
87 'swf' => array(
88 'audio' => 'application/x-shockwave-flash',
89 'video' => 'application/x-shockwave-flash'
90 ),
91 'wav' => array(
92 'audio' => 'audio/wave'
93 ),
94 'webm' => array(
95 'audio' => 'audio/webm',
96 'video' => 'video/webm'
97 ),
98 'wmv' => array(
99 'audio' => 'audio/x-ms-wmv'
100 )
101 );
102
103 /**
104 * VideoJS options
105 *
106 * @var array
107 */
108 public $videoJsOptions = array(
109 // Use the browser's controls (iPhone)
110 'useBuiltInControls',
111 // Display control bar below video vs. in front of
112 'controlsBelow',
113 // Make controls visible when page loads
114 'controlsAtStart',
115 // Hide controls when not over the video
116 'controlsHiding',
117 // Will be overridden by localStorage volume if available
118 'defaultVolume',
119 // Players and order to use them
120 'playerFallbackOrder'
121 );
122
123 /**
124 * html5 tag attributes
125 *
126 * @var array
127 */
128 public $html5TagAttributes = array(
129 'autoPlay',
130 'controls',
131 'loop',
132 'preload'
133 );
134
135 /**
136 * Flowplayer captions plugin configuration
137 *
138 * @var array
139 */
140 public $flowplayerCaptionsConfig = array(
141 'plugins' => array(
142 // The captions plugin
143 'captions' => array(
144 'url' => 'plugins/flowplayer.captions-3.2.10.swf',
145 // Pointer to a content plugin (see below)
146 'captionTarget' => 'content'
147 ),
148 // Configure a content plugin so that it looks good for showing captions
149 'content' => array(
150 'url' => 'plugins/flowplayer.content-3.2.9.swf',
151 'bottom' => 5,
152 'height' => 40,
153 'backgroundColor' => 'transparent',
154 'backgroundGradient' => 'none',
155 'border' => 0,
156 'textDecoration' => 'outline',
157 'style' => array(
158 'body' => array(
159 'fontSize' => 14,
160 'fontFamily' => 'Arial',
161 'textAlign' => 'center',
162 'color' => '#ffffff'
163 )
164 )
165 )
166 )
167 );
168
169 /**
170 * Flowplayer audio configuration
171 *
172 * @var array
173 */
174 public $flowplayerAudioConfig = array(
175 'provider' => 'audio',
176 'plugins' => array(
177 'audio' => array(
178 'url' => 'plugins/flowplayer.audio-3.2.11.swf'
179 ),
180 'controls' => array(
181 'autoHide' => FALSE,
182 'fullscreen' => FALSE
183 )
184 )
185 );
186
187 /**
188 * Flowplayer configuration for the audio description
189 *
190 * @var array
191 */
192 public $flowplayerAudioDescriptionConfig = array(
193 // The controls plugin
194 'plugins' => array(
195 'controls' => NULL
196 )
197 );
198
199 /**
200 * Rendering the cObject, SWFOBJECT
201 *
202 * @param array $conf Array of TypoScript properties
203 * @return string Output
204 */
205 public function render($conf = array()) {
206 $params = ($prefix = '');
207 if ($GLOBALS['TSFE']->baseUrl) {
208 $prefix = $GLOBALS['TSFE']->baseUrl;
209 }
210 if ($GLOBALS['TSFE']->absRefPrefix) {
211 $prefix = $GLOBALS['TSFE']->absRefPrefix;
212 }
213 // Initialize content
214 $replaceElementIdString = str_replace('.', '', uniqid('mmswf', TRUE));
215 $GLOBALS['TSFE']->register['MMSWFID'] = $replaceElementIdString;
216 $layout = isset($conf['layout.']) ? $this->cObj->stdWrap($conf['layout'], $conf['layout.']) : $conf['layout'];
217 $content = str_replace('###ID###', $replaceElementIdString, $layout);
218 $type = isset($conf['type.']) ? $this->cObj->stdWrap($conf['type'], $conf['type.']) : $conf['type'];
219 $typeConf = $conf[$type . '.'];
220 // Add Flowplayer js-file
221 $this->getPageRenderer()->addJsFile($this->getPathToLibrary('flowplayer/flowplayer-3.2.13.min.js'));
222 // Add Flowpayer css for express install
223 $this->getPageRenderer()->addCssFile($this->getPathToLibrary('flowplayer/express-install/express-install.css'));
224 // Add videoJS js-file
225 $this->getPageRenderer()->addJsFile($this->getPathToLibrary('videojs/video-js/video.js'));
226 // Add videoJS css-file
227 $this->getPageRenderer()->addCssFile($this->getPathToLibrary('videojs/video-js/video-js.css'));
228 // Add extended videoJS control bar
229 $this->getPageRenderer()->addJsFile($this->getPathToLibrary('videojs/video-js/controls/control-bar.js'));
230 $this->getPageRenderer()->addCssFile($this->getPathToLibrary('videojs/video-js/controls/control-bar.css'));
231 // Build Flash configuration
232 $player = isset($typeConf['player.']) ? $this->cObj->stdWrap($typeConf['player'], $typeConf['player.']) : $typeConf['player'];
233 if (!$player) {
234 $player = $prefix . $this->getPathToLibrary('flowplayer/flowplayer-3.2.18.swf');
235 } elseif (strpos($player, 'EXT:') === 0) {
236 $player = $prefix . $GLOBALS['TSFE']->tmpl->getFileName($player);
237 }
238 $installUrl = isset($conf['installUrl.']) ? $this->cObj->stdWrap($conf['installUrl'], $conf['installUrl.']) : $conf['installUrl'];
239 if (!$installUrl) {
240 $installUrl = $prefix . $this->getPathToLibrary('flowplayer/expressinstall.swf');
241 } elseif (strpos($installUrl, 'EXT:') === 0) {
242 $installUrl = $prefix . $GLOBALS['TSFE']->tmpl->getFileName($installUrl);
243 }
244 $flashVersion = isset($conf['flashVersion.']) ? $this->cObj->stdWrap($conf['flashVersion'], $conf['flashVersion.']) : $conf['flashVersion'];
245 if (!$flashVersion) {
246 $flashVersion = array(9, 115);
247 }
248 $flashConfiguration = array(
249 // Flowplayer component
250 'src' => $player,
251 // Express install url
252 'expressInstall' => $installUrl,
253 // Require at least this Flash version
254 'version' => $flashVersion,
255 // Older versions will see a message
256 'onFail' => '###ONFAIL###'
257 );
258 $flashDownloadUrl = 'http://www.adobe.com/go/getflashplayer';
259 $onFail = 'function() {
260 if (!(flashembed.getVersion()[0] > 0)) {
261 var message = "<p>" + "' . $GLOBALS['TSFE']->sL('LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:media.needFlashPlugin') . '" + "</p>" + "<p>" + "<a href=\\"' . $flashDownloadUrl . '\\">' . $GLOBALS['TSFE']->sL('LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:media.downloadFlash') . '</a>" + "</p>";
262 document.getElementById("' . $replaceElementIdString . '_flash_install_info").innerHTML = "<div class=\\"message\\">" + message + "</div>";
263 }
264 }';
265 $flashConfiguration = json_encode($flashConfiguration);
266 $flashConfiguration = str_replace('"###ONFAIL###"', $onFail, $flashConfiguration);
267 $filename = isset($conf['file.']) ? $this->cObj->stdWrap($conf['file'], $conf['file.']) : $conf['file'];
268 if ($filename) {
269 if (strpos($filename, '://') !== FALSE) {
270 $conf['flashvars.']['url'] = $filename;
271 } else {
272 if ($prefix) {
273 $conf['flashvars.']['url'] = $prefix . $filename;
274 } else {
275 $conf['flashvars.']['url'] = str_repeat('../', substr_count($player, '/')) . $filename;
276 }
277 }
278 }
279 if (is_array($conf['sources'])) {
280 foreach ($conf['sources'] as $key => $source) {
281 if (strpos($source, '://') === FALSE) {
282 $conf['sources'][$key] = $prefix . $source;
283 }
284 }
285 }
286 if (is_array($conf['audioSources'])) {
287 foreach ($conf['audioSources'] as $key => $source) {
288 if (strpos($source, '://') === FALSE) {
289 $conf['audioSources'][$key] = $prefix . $source;
290 }
291 }
292 }
293 if (isset($conf['audioFallback']) && strpos($conf['audioFallback'], '://') === FALSE) {
294 $conf['audioFallback'] = $prefix . $conf['audioFallback'];
295 }
296 if (isset($conf['caption']) && strpos($conf['caption'], '://') === FALSE) {
297 $conf['caption'] = $prefix . $conf['caption'];
298 }
299 // Write calculated values in conf for the hook
300 $conf['player'] = $player ?: $filename;
301 $conf['installUrl'] = $installUrl;
302 $conf['filename'] = $conf['flashvars.']['url'];
303 $conf['prefix'] = $prefix;
304 // merge with default parameters
305 $conf['flashvars.'] = array_merge((array)$typeConf['default.']['flashvars.'], (array)$conf['flashvars.']);
306 $conf['params.'] = array_merge((array)$typeConf['default.']['params.'], (array)$conf['params.']);
307 $conf['attributes.'] = array_merge((array)$typeConf['default.']['attributes.'], (array)$conf['attributes.']);
308 $conf['embedParams'] = 'flashvars, params, attributes';
309 // Hook for manipulating the conf array, it's needed for some players like flowplayer
310 if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/hooks/class.tx_cms_mediaitems.php']['swfParamTransform'])) {
311 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/hooks/class.tx_cms_mediaitems.php']['swfParamTransform'] as $classRef) {
312 GeneralUtility::callUserFunction($classRef, $conf, $this);
313 }
314 }
315 // Flowplayer config
316 $flowplayerVideoConfig = array();
317 $flowplayerAudioConfig = array();
318 if (is_array($conf['flashvars.']) && is_array($typeConf['mapping.']['flashvars.'])) {
319 ArrayUtility::remapArrayKeys($conf['flashvars.'], $typeConf['mapping.']['flashvars.']);
320 } else {
321 $conf['flashvars.'] = array();
322 }
323 $conf['videoflashvars'] = $conf['flashvars.'];
324 $conf['audioflashvars'] = $conf['flashvars.'];
325 $conf['audioflashvars']['url'] = $conf['audioFallback'];
326 // Render video sources
327 $videoSources = '';
328 if (is_array($conf['sources'])) {
329 foreach ($conf['sources'] as $source) {
330 $fileinfo = GeneralUtility::split_fileref($source);
331 $mimeType = $this->mimeTypes[$fileinfo['fileext']]['video'];
332 $videoSources .= '<source src="' . $source . '"' . ($mimeType ? ' type="' . $mimeType . '"' : '') . ' />' . LF;
333 }
334 }
335 // Render audio sources
336 $audioSources = '';
337 if (is_array($conf['audioSources'])) {
338 foreach ($conf['audioSources'] as $source) {
339 $fileinfo = GeneralUtility::split_fileref($source);
340 $mimeType = $this->mimeTypes[$fileinfo['fileext']]['audio'];
341 $audioSources .= '<source src="' . $source . '"' . ($mimeType ? ' type="' . $mimeType . '"' : '') . ' />' . LF;
342 }
343 }
344 // Configure captions
345 if ($conf['type'] === 'video' && isset($conf['caption'])) {
346 // Assemble captions track tag
347 $videoCaptions = '<track id="' . $replaceElementIdString . '_captions_track" kind="captions" src="' . $conf['caption'] . '" default>' . LF;
348 // Add videoJS extension for captions
349 $this->getPageRenderer()->addJsFile($this->getPathToLibrary('videojs/video-js/controls/captions.js'));
350 // Flowplayer captions
351 $conf['videoflashvars']['captionUrl'] = $conf['caption'];
352 // Flowplayer captions plugin configuration
353 $flowplayerVideoConfig = array_merge_recursive($flowplayerVideoConfig, $this->flowplayerCaptionsConfig);
354 }
355 // Configure flowplayer audio fallback
356 if (isset($conf['audioFallback'])) {
357 $flowplayerAudioConfig = array_merge_recursive($flowplayerAudioConfig, $this->flowplayerAudioConfig);
358 }
359 // Configure audio description
360 if ($conf['type'] == 'video') {
361 if (is_array($conf['audioSources']) && !empty($conf['audioSources'])) {
362 // Add videoJS audio description toggle
363 $this->getPageRenderer()->addJsFile($this->getPathToLibrary('videojs/video-js/controls/audio-description.js'));
364 }
365 if (isset($conf['audioFallback'])) {
366 // Audio description flowplayer config (remove controls)
367 $flowplayerAudioConfig = array_merge_recursive($flowplayerAudioConfig, $this->flowplayerAudioDescriptionConfig);
368 }
369 }
370 // Assemble Flowplayer configuration
371 if (!empty($conf['videoflashvars'])) {
372 $flowplayerVideoConfig = array_merge_recursive($flowplayerVideoConfig, array('clip' => $conf['videoflashvars']));
373 }
374 $flowplayerVideoJsonConfig = str_replace(array('"true"', '"false"'), array('true', 'false'), json_encode($flowplayerVideoConfig));
375 if (!empty($conf['audioflashvars'])) {
376 $flowplayerAudioConfig = array_merge_recursive($flowplayerAudioConfig, array('clip' => $conf['audioflashvars']));
377 }
378 $flowplayerAudioJsonConfig = str_replace(array('"true"', '"false"'), array('true', 'false'), json_encode($flowplayerAudioConfig));
379 // Assemble param tags (required?)
380 if (is_array($conf['params.']) && is_array($typeConf['mapping.']['params.'])) {
381 ArrayUtility::remapArrayKeys($conf['params.'], $typeConf['mapping.']['params.']);
382 }
383 $videoFlashParams = '';
384 if (is_array($conf['params.'])) {
385 foreach ($conf['params.'] as $name => $value) {
386 $videoFlashParams .= '<param name="' . $name . '" value="' . $value . '" />' . LF;
387 }
388 }
389 $audioFlashParams = $videoFlashParams;
390 // Required param tags
391 $videoFlashParams .= '<param name="movie" value="' . $player . '" />' . LF;
392 $videoFlashParams .= '<param name="flashvars" value=\'config=' . $flowplayerVideoJsonConfig . '\' />' . LF;
393 $audioFlashParams .= '<param name="movie" value="' . $player . '" />' . LF;
394 $audioFlashParams .= '<param name="flashvars" value=\'config=' . $flowplayerAudioJsonConfig . '\' />' . LF;
395 // Assemble audio/video tag attributes
396 $attributes = '';
397 if (is_array($conf['attributes.']) && is_array($typeConf['attributes.']['params.'])) {
398 ArrayUtility::remapArrayKeys($conf['attributes.'], $typeConf['attributes.']['params.']);
399 }
400 foreach ($this->html5TagAttributes as $attribute) {
401 if ($conf['attributes.'][$attribute] === 'true' || $conf['attributes.'][$attribute] === strtolower($attribute) || $conf['attributes.'][$attribute] === $attribute) {
402 $attributes .= strtolower($attribute) . '="' . strtolower($attribute) . '" ';
403 }
404 }
405 // Media dimensions
406 $width = isset($conf['width.']) ? $this->cObj->stdWrap($conf['width'], $conf['width.']) : $conf['width'];
407 if (!$width) {
408 $width = $conf[$type . '.']['defaultWidth'];
409 }
410 $height = isset($conf['height.']) ? $this->cObj->stdWrap($conf['height'], $conf['height.']) : $conf['height'];
411 if (!$height) {
412 $height = $conf[$type . '.']['defaultHeight'];
413 }
414 // Alternate content
415 $alternativeContent = isset($conf['alternativeContent.']) ? $this->cObj->stdWrap($conf['alternativeContent'], $conf['alternativeContent.']) : $conf['alternativeContent'];
416 // Render video
417 if ($conf['type'] === 'video') {
418 // add preview image, html5 poster attribute
419 $sourceBasePath = substr($conf['sources'][1], 0, strrpos($conf['sources'][1], '.'));
420 foreach (['.jpg', '.jpeg', '.png'] as $fileExtension) {
421 $posterFilePath = $GLOBALS['TSFE']->tmpl->getFileName($sourceBasePath . $fileExtension);
422 if (file_exists($posterFilePath)) {
423 $attributes .= ' poster="' . htmlspecialchars($posterFilePath) . '"';
424 break;
425 }
426 }
427
428 if ($conf['preferFlashOverHtml5']) {
429 // Flash with video tag fallback
430 $conf['params.']['playerFallbackOrder'] = array('flash', 'html5');
431 $flashDivContent = $videoFlashParams . LF . '<video id="' . $replaceElementIdString . '_video_js" class="video-js" ' . $attributes . 'controls="controls" mediagroup="' . $replaceElementIdString . '" width="' . $width . '" height="' . $height . '">' . LF . $videoSources . $videoCaptions . $alternativeContent . LF . '</video>' . LF;
432 $divContent = '
433 <div id="' . $replaceElementIdString . '_flash_install_info" class="flash-install-info"></div>' . LF . '<noscript>' . LF . '<object id="' . $replaceElementIdString . '_vjs_flash" type="application/x-shockwave-flash" data="' . $player . '" width="' . $width . '" height="' . $height . '">' . LF . $flashDivContent . '</object>' . LF . '</noscript>' . LF;
434 $content = str_replace('###SWFOBJECT###', '<div id="' . $replaceElementIdString . '_video" class="flashcontainer" style="width:' . $width . 'px; height:' . $height . 'px;">' . LF . $divContent . '</div>', $content);
435 } else {
436 // Video tag with Flash fallback
437 $conf['params.']['playerFallbackOrder'] = array('html5', 'flash');
438 $videoTagContent = $videoSources . $videoCaptions;
439 if (isset($conf['videoflashvars']['url'])) {
440 $videoTagContent .= '
441 <noscript>' . LF . '<object class="vjs-flash-fallback" id="' . $replaceElementIdString . '_vjs_flash_fallback" type="application/x-shockwave-flash" data="' . $player . '" width="' . $width . '" height="' . $height . '">' . LF . $videoFlashParams . LF . $alternativeContent . LF . '</object>' . LF . '</noscript>';
442 }
443 $divContent = '
444 <div id="' . $replaceElementIdString . '_flash_install_info" class="flash-install-info"></div>' . LF . '<video id="' . $replaceElementIdString . '_video_js" class="video-js" ' . $attributes . 'controls="controls" mediagroup="' . $replaceElementIdString . '" width="' . $width . '" height="' . $height . '">' . LF . $videoTagContent . '</video>';
445 $content = str_replace('###SWFOBJECT###', '<div id="' . $replaceElementIdString . '_video" class="video-js-box" style="width:' . $width . 'px; height:' . $height . 'px;">' . LF . $divContent . '</div>', $content);
446 }
447 }
448 // Render audio
449 if ($conf['type'] === 'audio' || $audioSources || isset($conf['audioFallback'])) {
450 if ($conf['preferFlashOverHtml5']) {
451 // Flash with audio tag fallback
452 $flashDivContent = $audioFlashParams . LF . '<audio id="' . $replaceElementIdString . '_audio_element"' . $attributes . ($conf['type'] === 'video' ? ' mediagroup="' . $replaceElementIdString . 'style="position:absolute;left:-10000px;"' : ' controls="controls"') . ' style="width:' . $width . 'px; height:' . $height . 'px;">' . LF . $audioSources . $alternativeContent . LF . '</audio>' . LF;
453 $divContent = ($conf['type'] === 'video' ? '' : '<div id="' . $replaceElementIdString . '_flash_install_info" class="flash-install-info"></div>' . LF) . '<noscript>' . LF . '<object id="' . $replaceElementIdString . '_audio_flash" type="application/x-shockwave-flash" data="' . $player . '" width="' . ($conf['type'] === 'video' ? 0 : $width) . '" height="' . ($conf['type'] === 'video' ? 0 : $height) . '">' . LF . $flashDivContent . '</object>' . LF . '</noscript>' . LF;
454 $audioContent = '<div id="' . $replaceElementIdString . '_audio_box" class="audio-flash-container" style="width:' . ($conf['type'] === 'video' ? 0 : $width) . 'px; height:' . ($conf['type'] === 'video' ? 0 : $height) . 'px;">' . LF . $divContent . '</div>';
455 } else {
456 // Audio tag with Flash fallback
457 $audioTagContent = $audioSources;
458 if (isset($conf['audioflashvars']['url'])) {
459 $audioTagContent .= '
460 <noscript>' . LF . '<object class="audio-flash-fallback" id="' . $replaceElementIdString . '_audio_flash" type="application/x-shockwave-flash" data="' . $player . '" width="' . $width . '" height="' . $height . '">' . LF . $audioFlashParams . LF . $alternativeContent . LF . '</object>' . LF . '</noscript>';
461 }
462 $divContent = ($conf['type'] === 'video' ? '' : '<div id="' . $replaceElementIdString . '_flash_install_info" class="flash-install-info"></div>' . LF) . '<audio id="' . $replaceElementIdString . '_audio_element" class="audio-element"' . $attributes . ($conf['type'] === 'video' ? ' mediagroup="' . $replaceElementIdString . '" style="position:absolute;left:-10000px;"' : ' controls="controls"') . '>' . LF . $audioTagContent . '</audio>' . LF . $audioSourcesEmbeddingJsScript;
463 $audioContent = '<div id="' . $replaceElementIdString . '_audio_box" class="audio-box" style="width:' . ($conf['type'] === 'video' ? 0 : $width) . 'px; height:' . ($conf['type'] === 'video' ? 0 : $height) . 'px;">' . LF . $divContent . '</div>';
464 }
465 if ($conf['type'] === 'audio') {
466 $content = str_replace('###SWFOBJECT###', $audioContent, $content);
467 } else {
468 $content .= LF . $audioContent;
469 }
470 }
471 // Assemble inline JS code
472 $videoJsSetup = '';
473 $flowplayerHandlers = '';
474 if ($conf['type'] === 'video') {
475 // Assemble videoJS options
476 $videoJsOptions = array();
477 foreach ($this->videoJsOptions as $videoJsOption) {
478 if (isset($conf['params.'][$videoJsOption])) {
479 $videoJsOptions[$videoJsOption] = $conf['params.'][$videoJsOption];
480 }
481 }
482 $videoJsOptions = !empty($videoJsOptions) ? json_encode($videoJsOptions) : '{}';
483 // videoJS setup and videoJS listeners for audio description synchronisation
484 if ($audioSources || isset($conf['audioFallback'])) {
485 $videoJsSetup = '
486 var ' . $replaceElementIdString . '_video = VideoJS.setup("' . $replaceElementIdString . '_video_js", ' . $videoJsOptions . ');
487 var ' . $replaceElementIdString . '_video_element = document.getElementById("' . $replaceElementIdString . '_video_js");
488 var ' . $replaceElementIdString . '_audio_element = document.getElementById("' . $replaceElementIdString . '_audio_element");
489 if (!!' . $replaceElementIdString . '_video_element && !!' . $replaceElementIdString . '_audio_element) {
490 ' . $replaceElementIdString . '_audio_element.muted = true;
491 VideoJS.addListener(' . $replaceElementIdString . '_video_element, "pause", function () { document.getElementById("' . $replaceElementIdString . '_audio_element").pause(); });
492 VideoJS.addListener(' . $replaceElementIdString . '_video_element, "play", function () { try {document.getElementById("' . $replaceElementIdString . '_audio_element").currentTime = document.getElementById("' . $replaceElementIdString . '_video_js").currentTime} catch(e) {}; document.getElementById("' . $replaceElementIdString . '_audio_element").play(); });
493 VideoJS.addListener(' . $replaceElementIdString . '_video_element, "seeked", function () { document.getElementById("' . $replaceElementIdString . '_audio_element").currentTime = document.getElementById("' . $replaceElementIdString . '_video_js").currentTime; });
494 VideoJS.addListener(' . $replaceElementIdString . '_video_element, "volumechange", function () { document.getElementById("' . $replaceElementIdString . '_audio_element").volume = document.getElementById("' . $replaceElementIdString . '_video_js").volume; });
495 }';
496 } else {
497 $videoJsSetup = '
498 var ' . $replaceElementIdString . '_video = VideoJS.setup("' . $replaceElementIdString . '_video_js", ' . $videoJsOptions . ');
499 ';
500 }
501 // Prefer Flash or fallback to Flash
502 $videoSourcesEmbedding = '';
503 // If we have a video file for Flash
504 if (isset($conf['filename'])) {
505 // If we prefer Flash
506 if ($conf['preferFlashOverHtml5']) {
507 $videoTagAssembly = '';
508 // Create "source" elements
509 if (is_array($conf['sources']) && !empty($conf['sources'])) {
510 foreach ($conf['sources'] as $source) {
511 $fileinfo = GeneralUtility::split_fileref($source);
512 $mimeType = $this->mimeTypes[$fileinfo['fileext']]['video'];
513 $videoTagAssembly .= '
514 ' . $replaceElementIdString . '_video_js.appendChild($f.extend(document.createElement("source"), {
515 src: "' . $source . '",
516 type: "' . $mimeType . '"
517 }));';
518 }
519 // Create "track" elements
520 if (isset($conf['caption'])) {
521 // Assemble captions track tag
522 // It will take a while before the captions are loaded and parsed...
523 $videoTagAssembly .= '
524 var track = document.createElement("track");
525 track.setAttribute("src", "' . $conf['caption'] . '");
526 track.setAttribute("id", "' . $replaceElementIdString . '_captions_track");
527 track.setAttribute("kind", "captions");
528 track.setAttribute("default", "default");
529 ' . $replaceElementIdString . '_video_js.appendChild(track);';
530 }
531 $videoTagAssembly .= '
532 $f.extend(' . $replaceElementIdString . '_video_js, {
533 id: "' . $replaceElementIdString . '_video_js",
534 className: "video-js",
535 controls: "controls",
536 mediagroup: "' . $replaceElementIdString . '",
537 preload: "none",
538 width: "' . $width . '",
539 height: "' . $height . '"
540 });
541 ' . $replaceElementIdString . '_video.appendChild(' . $replaceElementIdString . '_video_js);
542 ' . $replaceElementIdString . '_video.className = "video-js-box";';
543 $videoTagAssembly .= $videoJsSetup;
544 }
545 $videoSourcesEmbedding = '
546 var ' . $replaceElementIdString . '_video = document.getElementById("' . $replaceElementIdString . '_video");
547 var ' . $replaceElementIdString . '_video_js = document.createElement("video");
548 if (flashembed.getVersion()[0] > 0) {
549 // Flash is available
550 var videoPlayer = flowplayer("' . $replaceElementIdString . '_video", ' . $flashConfiguration . ', ' . $flowplayerVideoJsonConfig . ').load();
551 videoPlayer.onBeforeUnload(function () { return false; });
552 } else if (!!' . $replaceElementIdString . '_video_js.canPlayType) {
553 // Flash is not available: fallback to videoJS if video tag is supported
554 ' . $videoTagAssembly . '
555 } else {
556 // Neither Flash nor video is available: offer to install Flash
557 flashembed("' . $replaceElementIdString . '_video", ' . $flashConfiguration . ');
558 }';
559 } elseif (is_array($conf['sources'])) {
560 // HTML5 is the preferred rendering method
561 // Test whether the browser supports any of types of the provided sources
562 $supported = array();
563 foreach ($conf['sources'] as $source) {
564 $fileinfo = GeneralUtility::split_fileref($source);
565 $mimeType = $this->mimeTypes[$fileinfo['fileext']]['video'];
566 $supported[] = $replaceElementIdString . '_videoTag.canPlayType("' . $mimeType . '") != ""';
567 }
568 // Testing whether the browser supports the video tag with any of the provided source types
569 // If no support, embed flowplayer
570 $videoSourcesEmbedding = '
571 var ' . $replaceElementIdString . '_videoTag = document.createElement(\'video\');
572 var ' . $replaceElementIdString . '_video_box = document.getElementById("' . $replaceElementIdString . '_video");
573 if (' . $replaceElementIdString . '_video_box) {
574 if (!' . $replaceElementIdString . '_videoTag || !' . $replaceElementIdString . '_videoTag.canPlayType || !(' . (!empty($supported) ? implode(' || ', $supported) : 'false') . ')) {
575 // Avoid showing an empty video element
576 if (document.getElementById("' . $replaceElementIdString . '_video_js")) {
577 document.getElementById("' . $replaceElementIdString . '_video_js").style.display = "none";
578 }
579 if (flashembed.getVersion()[0] > 0) {
580 // Flash is available
581 var videoPlayer = flowplayer("' . $replaceElementIdString . '_video", ' . $flashConfiguration . ', ' . $flowplayerVideoJsonConfig . ').load();
582 videoPlayer.onBeforeUnload(function () { return false; });
583 } else {
584 // Neither Flash nor video is available: offer to install Flash
585 flashembed("' . $replaceElementIdString . '_video", ' . $flashConfiguration . ');
586 }
587 } else {' . $videoJsSetup . '
588 }
589 }';
590 }
591 }
592 }
593 // Audio fallback to Flash
594 $audioSourcesEmbedding = '';
595 // If we have an audio file for Flash
596 if (isset($conf['audioFallback'])) {
597 // If we prefer Flash in
598 if ($conf['preferFlashOverHtml5']) {
599 $audioTagAssembly = '';
600 // Create "source" elements
601 if (is_array($conf['audioSources']) && !empty($conf['audioSources'])) {
602 foreach ($conf['audioSources'] as $source) {
603 $fileinfo = GeneralUtility::split_fileref($source);
604 $mimeType = $this->mimeTypes[$fileinfo['fileext']]['audio'];
605 $audioTagAssembly .= '
606 ' . $replaceElementIdString . '_audio_element.appendChild($f.extend(document.createElement("source"), {
607 src: "' . $source . '",
608 type: "' . $mimeType . '"
609 }));';
610 }
611 $audioTagAssembly .= '
612 $f.extend(' . $replaceElementIdString . '_audio_element, {
613 id: "' . $replaceElementIdString . '_audio_element",
614 className: "audio-element",
615 controls: "' . ($conf['type'] === 'video' ? '' : 'controls') . '",
616 mediagroup: "' . $replaceElementIdString . '",
617 preload: "none",
618 width: "' . ($conf['type'] === 'video' ? 0 : $width) . 'px",
619 height: "' . ($conf['type'] === 'video' ? 0 : $height) . 'px"
620 });
621 ' . $replaceElementIdString . '_audio_box.appendChild(' . $replaceElementIdString . '_audio_element);
622 ' . $replaceElementIdString . '_audio_box.className = "audio-box";';
623 }
624 $audioSourcesEmbedding = '
625 var ' . $replaceElementIdString . '_audio_box = document.getElementById("' . $replaceElementIdString . '_audio_box");
626 var ' . $replaceElementIdString . '_audio_element = document.createElement("audio");
627 if (flashembed.getVersion()[0] > 0) {
628 // Flash is available
629 var audioPlayer = flowplayer("' . $replaceElementIdString . '_audio_box", ' . $flashConfiguration . ', ' . $flowplayerAudioJsonConfig . ').load();
630 audioPlayer.onBeforeUnload(function () { return false; });
631 ' . ($conf['type'] === 'video' ? 'audioPlayer.mute();' : '') . '
632 } else if (!!' . $replaceElementIdString . '_audio_element.canPlayType) {
633 // Flash is not available: fallback to audio element if audio tag is supported
634 ' . $audioTagAssembly . '
635 } else {
636 // Neither Flash nor audio is available: offer to install Flash if this is not an audio description of a video
637 ' . ($conf['type'] === 'video' ? '' : 'flashembed("' . $replaceElementIdString . '_audio_box", ' . $flashConfiguration . ');') . '
638 }';
639 } elseif (is_array($conf['audioSources'])) {
640 // HTML5 is the preferred rendering method
641 // Test whether the browser supports any of types of the provided sources
642 $supported = array();
643 foreach ($conf['audioSources'] as $source) {
644 $fileinfo = GeneralUtility::split_fileref($source);
645 $mimeType = $this->mimeTypes[$fileinfo['fileext']]['audio'];
646 $supported[] = $replaceElementIdString . '_audioTag.canPlayType("' . $mimeType . '") != ""';
647 }
648 // Testing whether the browser supports the audio tag with any of the provided source types
649 // If no support, embed flowplayer
650 $audioSourcesEmbedding = '
651 var ' . $replaceElementIdString . '_audioTag = document.createElement(\'audio\');
652 var ' . $replaceElementIdString . '_audio_box = document.getElementById("' . $replaceElementIdString . '_audio_box");
653 if (' . $replaceElementIdString . '_audio_box) {
654 if (!' . $replaceElementIdString . '_audioTag || !' . $replaceElementIdString . '_audioTag.canPlayType || !(' . (!empty($supported) ? implode(' || ', $supported) : 'false') . ')) {
655 // Avoid showing an empty audio element
656 if (document.getElementById("' . $replaceElementIdString . '_audio_element")) {
657 document.getElementById("' . $replaceElementIdString . '_audio_element").style.display = "none";
658 }
659 if (flashembed.getVersion()[0] > 0) {
660 var audioPlayer = flowplayer("' . $replaceElementIdString . '_audio_box", ' . $flashConfiguration . ', ' . $flowplayerAudioJsonConfig . ').load();
661 audioPlayer.onBeforeUnload(function () { return false; });
662 ' . ($conf['type'] === 'video' ? 'audioPlayer.mute()' : '') . '
663 } else {
664 // Neither Flash nor audio is available: offer to install Flash if this is not an audio description of a video
665 ' . ($conf['type'] === 'video' ? '' : 'flashembed("' . $replaceElementIdString . '_audio_box", ' . $flashConfiguration . ');') . '
666 }
667 }
668 }';
669 }
670 // Flowplayer eventHandlers for audio description synchronisation
671 $flowplayerHandlers = '';
672 if ($conf['type'] === 'video') {
673 $flowplayerHandlers = '
674 if (flashembed.getVersion()[0] > 0) {
675 // Flash is available
676 var videoPlayer = flowplayer("' . $replaceElementIdString . '_video");
677 if (videoPlayer) {
678 // Control audio description through video control bar
679 videoPlayer.onVolume(function (volume) { flowplayer("' . $replaceElementIdString . '_audio_box").setVolume(volume); });
680 videoPlayer.onMute(function () { flowplayer("' . $replaceElementIdString . '_audio_box").mute(); });
681 videoPlayer.onUnmute(function () { flowplayer("' . $replaceElementIdString . '_audio_box").unmute(); });
682 videoPlayer.onPause(function () { flowplayer("' . $replaceElementIdString . '_audio_box").pause(); });
683 videoPlayer.onResume(function () { flowplayer("' . $replaceElementIdString . '_audio_box").resume(); });
684 videoPlayer.onStart(function () { flowplayer("' . $replaceElementIdString . '_audio_box").play(); });
685 videoPlayer.onStop(function () { flowplayer("' . $replaceElementIdString . '_audio_box").stop(); });
686 videoPlayer.onSeek(function (clip, seconds) { flowplayer("' . $replaceElementIdString . '_audio_box").seek(seconds); });
687 // Mute audio description on start
688 flowplayer("' . $replaceElementIdString . '_audio_box").onStart(function () { this.mute()});
689 // Audio description toggle
690 var videoContainer = document.getElementById("' . $replaceElementIdString . '_video");
691 var buttonContainer = document.createElement("div");
692 $f.extend(buttonContainer, {
693 id: "' . $replaceElementIdString . '_audio_description_toggle",
694 className: "vjs-audio-description-control"
695 });
696 var button = document.createElement("div");
697 buttonContainer.appendChild(button);
698 buttonContainer.style.position = "relative";
699 buttonContainer.style.left = (parseInt(' . $width . ', 10)-27) + "px";
700 videoContainer.parentNode.insertBefore(buttonContainer, videoContainer.nextSibling);
701 VideoJS.addListener(buttonContainer, "click", function () {
702 var buttonContainer = document.getElementById("' . $replaceElementIdString . '_audio_description_toggle");
703 var state = buttonContainer.getAttribute("data-state");
704 if (state == "enabled") {
705 buttonContainer.setAttribute("data-state", "disabled");
706 flowplayer("' . $replaceElementIdString . '_audio_box").mute();
707 } else {
708 buttonContainer.setAttribute("data-state", "enabled");
709 flowplayer("' . $replaceElementIdString . '_audio_box").unmute();
710 }
711 });
712 }
713 }';
714 }
715 }
716 // Wrap up inline JS code
717 $jsInlineCode = $audioSourcesEmbedding . $videoSourcesEmbedding . $flowplayerHandlers;
718 if ($jsInlineCode) {
719 $jsInlineCode = 'VideoJS.DOMReady(function(){' . $jsInlineCode . LF . '});';
720 }
721 $this->getPageRenderer()->addJsInlineCode($replaceElementIdString, $jsInlineCode);
722 if (isset($conf['stdWrap.'])) {
723 $content = $this->cObj->stdWrap($content, $conf['stdWrap.']);
724 }
725 return $content;
726 }
727
728 /**
729 * resolves the path to the extensions' Contrib directory
730 *
731 * @param string $fileAndFolderName the file to be located
732 * @return string
733 */
734 protected function getPathToLibrary($fileAndFolderName) {
735 return $GLOBALS['TSFE']->tmpl->getFileName('EXT:mediace/Resources/Contrib/' . $fileAndFolderName);
736 }
737 }