Using Xibo 1.8.9 under docker
I’m trying to customize a video widget to show an html title above it in it’s contained region. Basically adding an image title in html, to match our existing layout from another signage software.
I read the docs, https://xibo.org.uk/manual/en/advanced_modules.html and other posts to help me figure out what was needed and where
Since the current video module doesn’t allow you to add custom HTML or CSS, I thought creating my own would be easy. Same twig files for this new module, almost same code except for the getRessource to render the html and voilà.
But no.
If I set the module to be regionSpecific, it raises an error when addding a widget from this module because no media are assigned to it at first :
An exception has been thrown during the rendering of a template ("No file to return").
#0 /var/www/cms/cache/ae/ae1fe01a0a7a5af7485225790a509767c029f5f7f4b7402cd948bcad760b42b6.php(75): Twig_Template->displayBlock('formHtml', Array, Array)
#1 /var/www/cms/vendor/twig/twig/lib/Twig/Template.php(215): __TwigTemplate_707e05763b5ad99e40a51a7d48fbad2f482756c82a210020828a39fbdd9f00be->block___internal_f313ffacb312194ed23810ebbdecab442b2114a9ff3d4e76067d65ff9016ff38(Array, Array)
#2 /var/www/cms/vendor/twig/twig/lib/Twig/Template.php(279): Twig_Template->displayBlock('__internal_f313...', Array, Array, true)
#3 /var/www/cms/cache/ae/ae1fe01a0a7a5af7485225790a509767c029f5f7f4b7402cd948bcad760b42b6.php(37): Twig_Template->renderBlock('__internal_f313...', Array, Array)
#4 /var/www/cms/vendor/twig/twig/lib/Twig/Template.php(432): __TwigTemplate_707e05763b5ad99e40a51a7d48fbad2f482756c82a210020828a39fbdd9f00be->doDisplay(Array, Array)
#5 /var/www/cms/vendor/twig/twig/lib/Twig/Template.php(403): Twig_Template->displayWithErrorHandling(Array, Array)
#6 /var/www/cms/cache/d9/d9a0be7befa7d7655bed656724d72dfe030b2b6de47ea04bd467d57b31d9328b.php(31): Twig_Template->display(Array, Array)
#7 /var/www/cms/vendor/twig/twig/lib/Twig/Template.php(432): __TwigTemplate_576d02ca6d755ceeba5dc763ea2b3554215869a4996f7ea45be2f9fac1427e31->doDisplay(Array, Array)
#8 /var/www/cms/vendor/twig/twig/lib/Twig/Template.php(403): Twig_Template->displayWithErrorHandling(Array, Array)
#9 /var/www/cms/vendor/twig/twig/lib/Twig/Template.php(411): Twig_Template->display(Array)
#10 /var/www/cms/vendor/slim/views/Twig.php(91): Twig_Template->render(Array)
#11 /var/www/cms/lib/Controller/Base.php(410): Slim\Views\Twig->render('etsvideo-form-a...', Array)
#12 /var/www/cms/lib/Controller/Base.php(323): Xibo\Controller\Base->renderTwigAjaxReturn(Array, Object(RKA\Slim), Object(Xibo\Helper\ApplicationState))
#13 /var/www/cms/lib/Middleware/State.php(117): Xibo\Controller\Base->render()
#14 [internal function]: Xibo\Middleware\State->Xibo\Middleware\{closure}()
#15 /var/www/cms/vendor/slim/slim/Slim/Slim.php(1208): call_user_func_array(Object(Closure), Array)
#16 /var/www/cms/vendor/slim/slim/Slim/Slim.php(1356): Slim\Slim->applyHook('slim.after.disp...')
#17 /var/www/cms/vendor/slim/slim/Slim/Middleware/Flash.php(85): Slim\Slim->call()
#18 /var/www/cms/vendor/slim/slim/Slim/Middleware/MethodOverride.php(92): Slim\Middleware\Flash->call()
#19 /var/www/cms/lib/Middleware/Actions.php(160): Slim\Middleware\MethodOverride->call()
#20 /var/www/cms/lib/Middleware/Theme.php(36): Xibo\Middleware\Actions->call()
#21 /var/www/cms/lib/Middleware/WebAuthentication.php(131): Xibo\Middleware\Theme->call()
#22 /var/www/cms/lib/Middleware/CsrfGuard.php(63): Xibo\Middleware\WebAuthentication->call()
#23 /var/www/cms/lib/Middleware/State.php(121): Xibo\Middleware\CsrfGuard->call()
#24 /var/www/cms/lib/Middleware/Storage.php(47): Xibo\Middleware\State->call()
#25 /var/www/cms/lib/Middleware/Xmr.php(37): Xibo\Middleware\Storage->call()
#26 /var/www/cms/vendor/slim/slim/Slim/Slim.php(1300): Xibo\Middleware\Xmr->call()
#27 /var/www/cms/web/index.php(124): Slim\Slim->run()
#28 {main}
If I set the module to be not regionSpecific, it uses the LibraryManager form to assign a media item to it, and creates a module video and not my own module.
Crawling through the code, I see that the module created from the library is determined by the media extensions of enabled modules. Ok, i’ll just disable the the video module then since I only need my own, but now when I try to add my module and assign a video media to it through the library,
- It still creates the default video module despite being disabled
- Media Library can’t filter video type (extension is disabled) and my module doesn’t list any mpeg/mp4 files or any other extensions (I assigned the same valid extensions than the video )
Also - worth mentionning, I kept getting twig error message at first when I tried to add a widget from my module with regionSpecific enabled. The message "No file was found’ was confusing since I thought it couldn’t find a twig template when the exception originated from having no mediaID when trying to show the custom video-add-form.
10783 2018-08-22 20:53 WEB GET DEBUG /playlist/widget/form/edit/28 Getting first primary media for Widget: 28 Media: [] audio []
Code
custom\ETSVideo.json
{
"title": "ETS video",
"author": "Ecole de technologie supérieure - Jean-Sebastien Gervais",
"description": "Module vidéo a l'intérieur du gabarit ETS avec titre",
"name": "ETSVideo",
"class": "Xibo\\Custom\\ETSVideo\\ETSVideo"
}
custom\ETSvideo\ETSvideo.php
<?php
/*
* École de technologie supérieure
* Jean-Sébastien Gervais
*
*/
namespace Xibo\Custom\ETSVideo;
use Respect\Validation\Validator as v;
use Xibo\Exception\InvalidArgumentException;
use Xibo\Exception\XiboException;
use Xibo\Factory\ModuleFactory;
use Xibo\Widget\ModuleWidget;
/**
* Class Hls
* @package Xibo\Custom
*/
class ETSVideo extends ModuleWidget
{
public $codeSchemaVersion = 1;
/**
* Form for updating the module settings
*/
public function settingsForm()
{
// Return the name of the TWIG file to render the settings form
return 'ETSVideo-form-settings';
}
/**
* Process any module settings
* TODO
*/
public function settings()
{
// Process any module settings you asked for.
$this->module->settings['defaultMute'] = $this->getSanitizer()->getCheckbox('defaultMute');
if ($this->getModule()->defaultDuration !== 0)
throw new \InvalidArgumentException(__('The Video Module must have a default duration of 0 to detect the end of videos.'));
// Return an array of the processed settings.
return $this->module->settings;
}
/** @inheritdoc
public function init()
{
// Initialise extra validation rules
v::with('Xibo\\Validation\\Rules\\');
}
*/
/**
* Install or Update this module
* @param ModuleFactory $moduleFactory
*/
public function installOrUpdate($moduleFactory)
{
if ($this->module == null) {
// Install
$module = $moduleFactory->createEmpty();
$module->name = 'ETSVideo';
$module->type = 'ETSVideo';
$module->class = 'Xibo\Custom\ETSVideo\ETSVideo';
$module->description = 'Module vidéo, gabarit ÉTS avec titre "Youtube"';
$module->imageUri = 'forms/library.gif';
$module->enabled = 1;
$module->previewEnabled = 1;
$module->assignable = 1;
$module->regionSpecific = 0;
$module->renderAs = 'html';
$module->schemaVersion = $this->codeSchemaVersion;
$module->defaultDuration = 60;
$module->settings = [];
$module->viewPath = '../custom/ETSVideo';
$this->setModule($module);
$this->installModule();
}
// Check we are all installed
$this->installFiles();
}
/**
* Install Files
*/
public function installFiles()
{
$this->mediaFactory->createModuleSystemFile(PROJECT_ROOT . '/modules/vendor/jquery-1.11.1.min.js')->save();
$this->mediaFactory->createModuleSystemFile(PROJECT_ROOT . '/modules/xibo-layout-scaler.js')->save();
// Install files from a folder
$folder = PROJECT_ROOT . '/custom/ETSVideo/resources';
foreach ($this->mediaFactory->createModuleFileFromFolder($folder) as $media) {
$media->save();
}
}
/**
* Override previewAsClient
* @param float $width
* @param float $height
* @param int $scaleOverride
* @return string
*/
public function previewAsClient($width, $height, $scaleOverride = 0)
{
return $this->previewIcon();
}
/**
* Determine duration
* @param $fileName
* @return int
*/
public function determineDuration($fileName = null)
{
// If we don't have a file name, then we use the default duration of 0 (end-detect)
if ($fileName === null)
return 0;
$this->getLog()->debug('Determine Duration from %s', $fileName);
$info = new \getID3();
$file = $info->analyze($fileName);
return intval($this->getSanitizer()->getDouble('playtime_seconds', 0, $file));
}
/**
* Template for Layout Designer JavaScript (adds javascript in module add/edit)
* @return string
*/
//public function layoutDesignerJavaScript()
//{
// return 'my-module-javascript';
//}
public function add()
{
//$this->setCommonOptions();
//$this->validate();
// Save the widget
$this->saveWidget();
}
/**
* Edit Media
*/
public function edit()
{
$this->setCommonOptions();
$this->validate();
// Save the widget
$this->saveWidget();
}
/**
* Set common options
*/
private function setCommonOptions()
{
$this->setDuration($this->getSanitizer()->getInt('duration', $this->getDuration()));
$this->setUseDuration($this->getSanitizer()->getCheckbox('useDuration'));
$this->setOption('name', $this->getSanitizer()->getString('name'));
$this->setOption('uri', urlencode($this->getSanitizer()->getString('uri')));
$this->setOption('mute', $this->getSanitizer()->getCheckbox('mute'));
// Only loop if the duration is > 0
if ($this->getUseDuration() == 0 || $this->getDuration() == 0)
$this->setOption('loop', 0);
else
$this->setOption('loop', $this->getSanitizer()->getCheckbox('loop'));
// This causes some android devices to switch to a hardware accellerated web view
$this->setOption('transparency', 0);
}
/**
* Validate
* @throws XiboException
*/
private function validate()
{
if ($this->getUseDuration() == 1 && $this->getDuration() == 0)
throw new InvalidArgumentException(__('Please enter a duration'), 'duration');
if (!v::url()->notEmpty()->validate(urldecode($this->getOption('uri'))))
throw new InvalidArgumentException(__('Please enter a link'), 'uri');
}
/**
* Set default widget options
*/
public function setDefaultWidgetOptions()
{
parent::setDefaultWidgetOptions();
$this->setOption('mute', $this->getSetting('defaultMute', 0));
}
/**
* @inheritdoc
*/
public function isValid()
{
// Using the information you have in your module calculate whether it is valid or not.
// 0 = Invalid
// 1 = Valid
// 2 = Unknown
return 1;
}
/**
* GetResource
* Return the rendered resource to be used by the client (or a preview) for displaying this content.
* @param integer $displayId If this comes from a real client, this will be the display id.
* @return mixed
*/
public function getResource($displayId = 0)
{
// Ensure we have the necessary files linked up
$media = $this->mediaFactory->createModuleFile(PROJECT_ROOT . '/modules/vendor/hls/hls.min.js');
$media->save();
$this->assignMedia($media->mediaId);
$this->setOption('hlsId', $media->mediaId);
$media = $this->mediaFactory->createModuleFile(PROJECT_ROOT . '/modules/vendor/hls/hls-1px-transparent.png');
$media->save();
$this->assignMedia($media->mediaId);
$this->setOption('posterId', $media->mediaId);
$arrow = $this->getResourceUrl('ets_arrow.png');
$ytlogo = $this->getResourceUrl('youtube-logo.png');
// Render and output HTML
$this
->initialiseGetResource()
->appendViewPortWidth($this->region->width)
->appendJavaScriptFile('vendor/jquery-1.11.1.min.js')
->appendJavaScriptFile('vendor/hls/hls.min.js')
->appendJavaScript('
$(document).ready(function() {
if(Hls.isSupported()) {
var video = document.getElementById("video");
var hls = new Hls({
autoStartLoad: true,
startPosition : -1,
capLevelToPlayerSize: false,
debug: false,
defaultAudioCodec: undefined,
enableWorker: true
});
hls.loadSource("' . urldecode($this->getOption('uri')) . '");
hls.attachMedia(video);
hls.on(Hls.Events.MANIFEST_PARSED, function() {
video.play();
});
hls.on(Hls.Events.ERROR, function (event, data) {
if (data.fatal) {
switch(data.type) {
case Hls.ErrorTypes.NETWORK_ERROR:
// try to recover network error
//console.log("fatal network error encountered, try to recover");
hls.startLoad();
break;
case Hls.ErrorTypes.MEDIA_ERROR:
//console.log("fatal media error encountered, try to recover");
hls.recoverMediaError();
break;
default:
// cannot recover
hls.destroy();
break;
}
}
});
}
});
')
->appendBody('
<img src="' . $this->getResourceUrl('custom/ETSVideo/resources/ets_arrow.png') . '"/>
<img src="' . $this->getResourceUrl('custom/ETSVideo/resources/youtube-logo.png') . '"/>
<video id="video" poster="' . $this->getResourceUrl('vendor/hls/hls-1px-transparent.png') . '" ' . (($this->getOption('mute', 0) == 1) ? 'muted' : '') . '>
</video>
')
->appendCss('
video {
width: 100%;
height: 100%;
}
')
;
return $this->finaliseGetResource();
}
/** @inheritdoc */
public function getCacheDuration()
{
return 3600;
}
}
custom\ETSvideo\etsvideo-form-add.twig
custom\ETSvideo\etsvideo-form-edit.twig
custom\ETSvideo\etsvideo-form-settings.twig
same as the default video***.twig module in modules\ folder
So my question is :
Is it possible to override the default video module to use your own? or is there something I missed ?