Skip to content

Commit

Permalink
add video content elements
Browse files Browse the repository at this point in the history
  • Loading branch information
m-vo committed Jun 11, 2022
1 parent 3273a11 commit a74f96e
Show file tree
Hide file tree
Showing 12 changed files with 393 additions and 5 deletions.
4 changes: 4 additions & 0 deletions UPGRADE.md
Expand Up @@ -31,6 +31,8 @@ Twig-only templates:

- `image` (`ce_image``content_element/image`)
- `gallery` (`ce_gallery``content_element/gallery`)
- `youtube` (`ce_youtube``content_element/youtube`)
- `vimeo` (`ce_vimeo``content_element/vimeo`)

The legacy content elements and their templates are still around and will only be dropped in Contao 6.
If you want to use them instead of the new ones, you can opt in on a per-element basis by adding the
Expand All @@ -48,6 +50,8 @@ $GLOBALS['TL_CTE']['links']['hyperlink'] = \Contao\ContentHyperlink::class;
$GLOBALS['TL_CTE']['links']['toplink'] = \Contao\ContentToplink::class;
$GLOBALS['TL_CTE']['media']['image'] = \Contao\ContentImage::class;
$GLOBALS['TL_CTE']['media']['gallery'] = \Contao\ContentGallery::class;
$GLOBALS['TL_CTE']['media']['youtube'] = \Contao\ContentYouTube::class;
$GLOBALS['TL_CTE']['media']['vimeo'] = \Contao\ContentVimeo::class;
```

### Show to guests only
Expand Down
159 changes: 159 additions & 0 deletions core-bundle/src/Controller/ContentElement/VideoController.php
@@ -0,0 +1,159 @@
<?php

declare(strict_types=1);

/*
* This file is part of Contao.
*
* (c) Leo Feyer
*
* @license LGPL-3.0-or-later
*/

namespace Contao\CoreBundle\Controller\ContentElement;

use Contao\ContentModel;
use Contao\CoreBundle\Image\Studio\Studio;
use Contao\CoreBundle\ServiceAnnotation\ContentElement;
use Contao\CoreBundle\Twig\FragmentTemplate;
use Contao\StringUtil;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

/**
* @ContentElement("vimeo", category="media")
* @ContentElement("youtube", category="media")
*
* @phpstan-type VideoSourceParameters array{
* provider: 'vimeo'|'youtube',
* video_id: string,
* options: array<string, string>,
* base_url: string,
* query: string,
* url: string
* }
*/
class VideoController extends AbstractContentElementController
{
public function __construct(private readonly Studio $studio)
{
}

protected function getResponse(FragmentTemplate $template, ContentModel $model, Request $request): Response
{
// Video source data, size and aspect ratio
$sourceParameters = match ($type = $template->get('type')) {
'vimeo' => $this->getVimeoSourceParameters($model),
'youtube' => $this->getYoutubeSourceParameters($model, $request->getLocale()),
default => throw new \InvalidArgumentException(sprintf('Unknown video provider "%s".', $type))
};

$template->set('source', $sourceParameters);

$size = StringUtil::deserialize($model->playerSize, true);

$template->set('width', $size[0] ?? 640);
$template->set('height', $size[1] ?? 360);

$template->set('aspect_ratio', $model->playerAspect);

// Meta data
$template->set('caption', $model->playerCaption);

// Splash image
$figure = !$model->splashImage ? null : $this->studio
->createFigureBuilder()
->fromUuid($model->singleSRC ?: '')
->setSize($model->size)
->buildIfResourceExists()
;

$template->set('splash_image', $figure);

return $template->getResponse();
}

/**
* @return array<string, string|array<string, string>>
*
* @phpstan-return VideoSourceParameters
*/
private function getVimeoSourceParameters(ContentModel $model): array
{
$options = [];

foreach (StringUtil::deserialize($model->vimeoOptions, true) as $option) {
[$option, $value] = match ($option) {
'vimeo_portrait', 'vimeo_title', 'vimeo_byline' => [substr($option, 6), '0'],
default => [substr($option, 6), '1'],
};

$options[$option] = $value;
}

if ($color = $model->playerColor) {
$options['color'] = $color;
}

$query = http_build_query($options);

if (($start = (int) $model->playerStart) > 0) {
$options['start'] = $start;
$query .= "#t={$start}s";
}

return [
'provider' => 'vimeo',
'video_id' => $videoId = $model->vimeo,
'options' => $options,
'base_url' => $baseUrl = "https://player.vimeo.com/video/$videoId",
'query' => $query,
'url' => empty($query) ? $baseUrl : "$baseUrl?$query",
];
}

/**
* @return array<string, string|array<string, string>>
*
* @phpstan-return VideoSourceParameters
*/
private function getYoutubeSourceParameters(ContentModel $model, string $locale): array
{
$options = [];
$domain = 'https://www.youtube.com';

foreach (StringUtil::deserialize($model->youtubeOptions, true) as $option) {
if ('youtube_nocookie' === $option) {
$domain = 'https://www.youtube-nocookie.com';

continue;
}

[$option, $value] = match ($option) {
'youtube_fs', 'youtube_rel', 'youtube_controls' => [substr($option, 8), '0'],
'youtube_hl' => [substr($option, 8), \Locale::parseLocale($locale)[\Locale::LANG_TAG] ?? ''],
'youtube_iv_load_policy' => [substr($option, 8), '3'],
default => [substr($option, 8), '1'],
};

$options[$option] = $value;
}

if (($start = (int) $model->playerStart) > 0) {
$options['start'] = $start;
}

if (($end = (int) $model->playerStop) > 0) {
$options['end'] = $end;
}

return [
'provider' => 'youtube',
'video_id' => $videoId = $model->youtube,
'options' => $options,
'base_url' => $baseUrl = "$domain/embed/$videoId",
'query' => $query = http_build_query($options),
'url' => empty($query) ? $baseUrl : "$baseUrl?$query",
];
}
}
4 changes: 4 additions & 0 deletions core-bundle/src/Resources/config/controller.yml
Expand Up @@ -86,6 +86,10 @@ services:

Contao\CoreBundle\Controller\ContentElement\ToplinkController: ~

Contao\CoreBundle\Controller\ContentElement\VideoController:
arguments:
- '@contao.image.studio'

Contao\CoreBundle\Controller\FaviconController:
arguments:
- '@contao.framework'
Expand Down
4 changes: 0 additions & 4 deletions core-bundle/src/Resources/contao/config/config.php
Expand Up @@ -26,8 +26,6 @@
use Contao\ContentSliderStart;
use Contao\ContentSliderStop;
use Contao\ContentTeaser;
use Contao\ContentVimeo;
use Contao\ContentYouTube;
use Contao\CoreBundle\Controller\BackendCsvImportController;
use Contao\Crawl;
use Contao\FilesModel;
Expand Down Expand Up @@ -282,8 +280,6 @@
'media' => array
(
'player' => ContentPlayer::class,
'youtube' => ContentYouTube::class,
'vimeo' => ContentVimeo::class
),
'files' => array
(
Expand Down
3 changes: 3 additions & 0 deletions core-bundle/src/Resources/contao/languages/en/default.xlf
Expand Up @@ -2045,6 +2045,9 @@
<trans-unit id="MSC.manual">
<source>Manual</source>
</trans-unit>
<trans-unit id="MSC.splashScreen">
<source>Please click here to load the content.</source>
</trans-unit>
<trans-unit id="UNITS.0">
<source>Byte</source>
</trans-unit>
Expand Down
Expand Up @@ -93,7 +93,7 @@
{% endif %}

{% if randomize_order or add_pagination or limit_elements %}
{% block script %}
{% block list_script %}
{% add "list_script" to body %}
<script type="application/javascript">
{# Shuffle lists #}
Expand Down
@@ -0,0 +1,60 @@
{#
This component outputs a splash screen button. A client side script will
replace it with content from a template tag when it gets clicked.
<button data-splash>
<p>Click to load the iframe.</p>
<template>
<div class="my-content">
<iframe src="…"></iframe>
</div>
</template>
</button>
After activation the HTML of the above example will look like this:
<div class="my-content">
<iframe src="…"></iframe>
</div>
Optional variables:
@var bool randomize_order
@var int limit
@var int items_per_page
@var string tag_name
@var \Contao\CoreBundle\String\HtmlAttributes splash_button_attributes
#}

{% trans_default_domain "contao_default" %}

{% block splash_screen_component %}
{% set splash_button_attributes = attrs(splash_button_attributes|default)
.set('data-splash-screen')
%}
<button{{ splash_button_attributes }}>
{% block splash_screen_button_content %}
{# Render your splash screen's button content here. #}
{{ ('MSC.splashScreen')|trans }}
{% endblock %}
<template>
{% block splash_screen_content %}
{# Render the actual content (present after activation) here. #}
{% endblock %}
</template>
</button>

{% block splash_screen_script %}
{% if not as_editor_view %}
{% add "splash_screen_script" to body %}
<script>
document.querySelectorAll('*[data-splash-screen]').forEach(button => {
button.addEventListener('click', () => {
button.insertAdjacentHTML('afterend', button.querySelector('template').innerHTML);
button.remove();
})
})
</script>
{% endadd %}
{% endif %}
{% endblock %}
{% endblock %}
@@ -0,0 +1,44 @@
{% trans_default_domain "contao_default" %}
{% extends "@Contao/content_element/_base.html.twig" %}
{% use "@Contao/component/_splash_screen.html.twig" %}
{% use "@Contao/component/_picture.html.twig" %}

{% block content %}
{% set video_attributes = attrs(video_attributes|default)
.addClass('aspect aspect--' ~ aspect_ratio, aspect_ratio)
%}
<figure{{ video_attributes }}>
{% if splash_image %}
{{ block('splash_screen_component') }}
{% else %}
{% block iframe %}
{% set iframe_attrs = attrs({width, height, src: source.url, allowfullscreen: true})
.mergeWith(iframe_attrs|default)
%}
<iframe{{ iframe_attrs }}></iframe>
{% endblock %}
{% endif %}

{% block video_caption %}
{% if caption %}
<figcaption{{ attrs(video_caption_attributes|default) }}>
{{- caption|insert_tag_raw -}}
</figcaption>
{% endif %}
{% endblock %}
</figure>
{% endblock %}

{% block splash_screen_button_content %}
{# Preview image #}
{% with {figure: splash_image} %}{{ block('picture_component') }}{% endwith %}

{# Textual note #}
{% block splash_screen_text %}
<p>{{ ('MSC.splashScreen')|trans }}</p>
{% endblock %}
{% endblock %}

{% block splash_screen_content %}
{{ block('iframe') }}
{% endblock %}
@@ -0,0 +1 @@
{% extends "@Contao/content_element/_video.html.twig" %}
@@ -0,0 +1,5 @@
{% extends "@Contao/content_element/_video.html.twig" %}

{% set iframe_attrs = attrs(iframe_attrs|default)
.set('allow', 'autoplay; encrypted-media; picture-in-picture; fullscreen')
%}
Expand Up @@ -134,6 +134,7 @@ static function () use ($modelData): Metadata|null {

$controller->setFragmentOptions([
'template' => $template ?? "content_element/{$modelData['type']}",
'type' => $modelData['type'],
]);

$response = $controller(new Request(), $model, 'main');
Expand Down

0 comments on commit a74f96e

Please sign in to comment.