Jump to content

Template parts

Modular template structure for easy maintenance and customisation.

VidPly TYPO3 Extension Logo

Location

Resources/Private/Partials/VidPly/

Available sub-templates

1. Assets.html

Registers CSS and JavaScript assets conditionally

 
<f:render partial="VidPly/Assets" arguments="{
    needsPrivacyLayer: vidply.needsPrivacyLayer,
    needsVidPlay: vidply.needsVidPlay,
    needsPlaylist: vidply.needsPlaylist,
    needsHLS: vidply.needsHLS,
    needsDASH: vidply.needsDASH
}" />
 

Arguments:

  • needsPrivacyLayer - Load PrivacyLayer.js (external services)
  • needsVidPlay - Load VidPly core (native player)
  • needsPlaylist - Load PlaylistInit.js (playlists/player initialisation)
  • needsHLS - Load hls.js (HLS streaming)
  • needsDASH - Load dash.js (DASH streaming)

Always loaded:

  • vidply.min.css - Player styling

Loaded conditionally depending on media type:

  • privacy-layer.css - Privacy layer styles (external services only)
  • PrivacyLayer.js - YouTube, Vimeo, SoundCloud
  • hls.min.js — Vendored hls.js 1.7.2 (HLS streams only)
  • dash.all.min.js — Embedded ‘dash.js’ 5.2.1 (modern UMD; DASH streams only)
  • PlaylistInit.js - Playlists or player initialisation
  • vidply/vidply.esm.min.js - Native player only (plus code-split chunks)

See Documentation/AssetLoading.md for details on optimisation.

2. VideoSources.html

Renders ‘<source>’ elements for video players

 
<f:render partial="VidPly/VideoSources" arguments="{
    videoUrl: vidply.videoUrl,
    playlistData: vidply.playlistData,
    sources: vidply.sources,
    mediaFiles: vidply.mediaFiles
}" />
 

Supports:

  • Multiple source formats with fallback order (DASH → HLS → MP4/WebM)
  • Multiple quality and format options
  • Playlist mode
  • Sources for audio descriptions

3. AudioSources.html

Renders ‘<source>’ elements for audio players

 
<f:render partial="VidPly/AudioSources" arguments="{
    playlistData: vidply.playlistData,
    tracks: vidply.tracks,
    sources: vidply.sources,
    mediaFiles: vidply.mediaFiles
}" />
 

Supported:

  • Multiple source formats with fallback order (DASH → HLS → MP3/OGG)
  • Playlist mode
  • Format fallback

4. Tracks.html

Renders subtitle and chapter<track>elements

 
<f:render partial="VidPly/Tracks" arguments="{
    captions: vidply.captions,
    chapters: vidply.chapters,
    languageSelection: vidply.languageSelection
}" />
 

Shared by video and audio players. Supports multiple languages and title types.

5. MetadataScripts.html

JSON payloads for audio description and sign language (read by the player on initialisation – not JSON-LD)

 
<f:render partial="VidPly/MetadataScripts" arguments="{
    uniqueId: vidply.uniqueId,
    audioDescriptionTracks: vidply.audioDescriptionTracks,
    audioDescription: vidply.audioDescription,
    signLanguage: vidply.signLanguage
}" />
 

Used by video players for metadata relating to audio description and sign language tracks.

 

SEO JSON-LD (VideoObject / AudioObject) is rendered separately on the page <head> via Partials/Page/StructuredDataVideo.html — see Developer Quick Start → Structured Data.

 

6. PrivacyLayer.html

GDPR consent layer for external services

 
<f:render partial="VidPly/PrivacyLayer" arguments="{
    service: 'youtube',
    videoUrl: vidply.videoUrl,
    poster: vidply.poster,
    title: data.header,
    uniqueId: vidply.uniqueId,
    privacySettings: vidply.privacySettings.youtube
}" />
 

Displays a play button with a privacy notice for YouTube, Vimeo and SoundCloud. Uses the database settings from the tx_mpcvidply_privacy_settings table, with a fallback to translations from the language file. Loads an iframe upon consent.

Structure of the privacy settings:

 
[
    'headline' => 'Optional headline',
    'intro_text' => 'Text before link',
    'outro_text' => 'Text after link',
    'policy_link' => 'https://...',
    'link_text' => 'Link text',
    'button_label' => 'Button aria-label'
]
 

7. MediaPlayer.html

The ‘<video>’ or ‘<audio>’ element itself

Player.html sets mediaKind to audio or video and renders this partial for both. mediaKind is also the element name, i.e. the tag that is used by default aria-label, the poster attribute (video only), the source partials and MetadataScripts.html (for video only) are all derived from it.

 
<f:variable name="mediaKind" value="video" />
<f:render partial="VidPly/MediaPlayer" arguments="{_all}" />
 

It replaces the previous VideoPlayer.html and AudioPlayer.html, which were identical apart from these five points. Site packages that override either of these two must MediaPlayer.html override instead.

Template structure

 
VidPly.html (Main)
├── layout = card|episodes → VidPly/EpisodeLayout.html
│   ├── VidPly/EpisodeCard.html (cover, episode number, title, date, duration, description)
│   └── VidPly/Player.html
└── VidPly/Player.html (wrapper + renderMode switch)
    ├── privacy → PrivacyLayer.html
    ├── mixedPlaylist → MixedPlaylistPlayer.html
    └── audio / video → MediaPlayer.html (mediaKind = audio|video)
        ├── VideoSources.html / AudioSources.html
        ├── Tracks.html
        └── MetadataScripts.html (video only)
    └── Assets.html (conditional JS/CSS)
 

At page level: Partials/Page/StructuredDataVideo.html (JSON-LD in <head>).

Customisation

Overriding in the site package

TypoScript:

 
tt_content.mpc_vidply {
    partialRootPaths.100 = EXT:your_sitepackage/Resources/Private/Partials/
}
 

Create a custom partial:

 
your_sitepackage/
└── Resources/Private/Partials/VidPly/
    └── VideoSources.html  ← Your customization
 

Other partials remain unchanged.

List view and detail templates

Location: Resources/Private/Partials/Listview/ and Resources/Private/Templates/Listview.html / Detail.html

Partial / TemplateRole
Listview/RowA shelf or grid row: Heading, Sorting <select>, optional pagination wrapper
Listview/RowListBodyShared <ul> for grid or shelf (can be used with or without pagination)
Listview/CardIndividual media card (poster, title, artist, category chips, link)
Listview/ShelfArrows‘Back/Next’ controls for the horizontal shelf

Overwrite in the same way using partialRootPaths for mpc_vidply List view TypoScript, if your site package requires a different markup; retain data-* attributes if you use Listview.js (shelf, sorting, pagination).

Sub-templates for episode cards

These are used when the ‘Layout’ field of the content element (tx_mpcvidply_layout) is set to ‘Episode card’ or ‘Episode card with episode list’; default renders the simple player exactly as before.

PartRole
VidPly/EpisodeLayoutLayout wrapper, loads episode.min.css + EpisodeInit.min.js, renders the map or the episode list as well as the player
VidPly/EpisodeListThe <ol> episode rows, which are rendered either separately or within the pager container
VidPly/EpisodeCoverSquare thumbnail image of an episode
VidPly/EpisodeCardA round play button next to the episode number, title, release date, duration, description, download link and the icon for viewing the detailed description

EpisodeCard requires an titleLevel argument (3 for the single card, 4 for list rows ) to ensure the order of the headings below the content element’s header remains unbroken.

The two layouts

Both start with the same card: mpc-episode-cover alongside mpc-episode-main, which stacks the card text onto the player. The episode card ends at this point and displays vidply.episodethe first entry.

The episode card with episode list adds a mpc-episode-list-section , which changes to a full-width line below it and displays each record in vidply.episodes as <ol> row mpc-episode-item rows. The card then acts as a ‘Now Playing’ header: EpisodeInit.js Whenever the title changes, the selected row is copied into this card, so that the cover, title, date, duration, categories and description always correspond to the current title in the player. Because of this copying, the markup rendered by the server remains the sole source for the episode data – nothing is reformatted in JavaScript.

As the list is the title overview, the processor disables the player’s own playlist window (showPanel: false) and hides its toggle button (playlistToggleButton: false) – otherwise, the same data records would be displayed twice by two different formatters.

The episode data comes from EpisodeListBuilder::build(), and is layout-specific, so that the episode card does not query for posters and categories from records that it will never display. Dates, running times and download sizes are already formatted in PHP for the website’s language, so that templates output ready-made strings.

Only records from which a playable track has been created are included in vidply.episodes, which ensures that episode.index remains usable as a track index in the playlist: a record whose source cannot be resolved is immediately removed from both lists.

Sorting and pagination of the episode list

The list has its own sort drop-down menu and, from a configurable page size onwards, a pager – modelled on the list view (Listview.js), which, however, in EpisodeInit.js.

Both only affect the list. episode.index The playlist’s title index remains unchanged, regardless of how the rows are sorted or which ones are hidden, so that the player retains the editor’s order for ‘Next/Previous’ and automatic fast-forwarding. This also means that the ‘Now Playing’ card is the track with the index 0 (EpisodeListBuilder::resolveLeadEpisode()), not as the first array entry.

Field (tt_content)Effect
tx_mpcvidply_episode_sortDefault order: sorting (order of media elements), date_desc, date_asc, title_asc. Applied on the server side and inserted into the <select>
tx_mpcvidply_episode_paginationEnables pagination; this only becomes visible once there are more episodes than can fit on a single page
tx_mpcvidply_episode_per_pageEpisodes per page, limited to 1–200 (default 10)

Episodes without a publication date are sorted last in both date modes – both on the server and in the browser – so that an incomplete record never appears at the top of a ‘Newest first’ list. In the event of a tie, the title index is used.

Hidden rows carry the hidden attribute, which removes them from the tab order and the accessibility tree; the heading retains the full episode count, whilst the page counter indicates the current page. When changing pages, the focus jumps to the first row of the new page, and when the title changes, the page with the active row is displayed – this ensures the list is carried along when scrolling automatically past the end of a page.

AttributePurpose
data-mpc-episode-default-sortFor the section: the order pre-selected by the editor, which is applied <select> when loading
data-mpc-episode-sortThe sort order <select>
data-mpc-episode-listThe <ol> which <li> children are sorted and paginated
data-mpc-episode-date / data-mpc-episode-sort-titleSort key per row (Y-m-d, empty if not specified)
data-mpc-episode-paginatePager container; contains data-mpc-episode-per-page as well as the pager labels
data-mpc-episode-pager-navEmpty <nav> The buttons appear in

‘Downloads’ in the episode list

episode.downloadUrl and episode.downloadInfo (“MP3, 7.4 MB”) are populated for entries with the ‘Allow download’ option – but only in the episode list layout of a playlist. As every episode is displayed on the page there, one link per line allows visitors to save any episode without having to select it first.

In any other combination, the download is initiated via the button in the player’s control bar, which retrieves the file from the selected track in the playlist (track.downloadUrl / downloadFormat / downloadFileSizesee DownloadResolver::enrichTrack()). This is also why the episode card does not display a link: it shows a specific episode, whilst the player may already be on a different one; therefore, a link rendered by the server would be out of date there.

In any case, exactly one option is offered: buildPlaylistData() only groups downloadButton: true outside the episode list layout.

The size is measured in terms of storage space required (ResourceStorage::getFileInfo()), not based on sys_file.size: A file that has been replaced without re-indexing would otherwise be displayed with the size of its predecessor. By passing the same number of bytes to the player, both labels remain consistent, and the HEAD request that the button would otherwise send. If the storage cannot respond, the line label relies on the format alone and the player measures the file itself.

Within a line, the link position: relative and a z-index above the stretched overlay of the play button, as otherwise the full-line-width play target would swallow up clicks on it.

The detailed description of a media item

long_description is the medium’s RTE text. The details page displays it in full; the map and each list row instead present it as a drop-down field, so that a long list of episodes remains clear and organised. The button appears for records where the field is filled in – there is no editor toggle for it.

Both halves are rendered by the server: the panel is rendered using the hidden attribute and EpisodeInit.js only expands hidden, aria-expanded and the button label. The panel contains no id and the button contains no aria-controls: The ‘Currently Playing’ card is a clone of a list row, so an ID would appear twice in the document as soon as a track is selected. aria-expanded A button immediately followed by its panel expresses the same relationship without this risk, which is why the panel is also searched for within the mpc-episode-body rather than via a reference.

AttributePurpose
data-mpc-episode-longdesc-toggleThe button for showing and hiding. Contains data-mpc-episode-label-more / -less for both states and data-mpc-episode-title for the accessible name
data-mpc-episode-longdescThe panel controlled by the hidden attribute

Within a single line, the button and the area require a z-index above the ‘Play’ overlay. Without this, the line-wide ‘Play’ target will capture the clicks, and the expanded text cannot be selected or clicked as a link.

If you override the card, retain these hooks – EpisodeInit.js relies on them:

AttributePurpose
data-mpc-episode-playIdentifies the play button. Clicks are delegated so that a cloned button works without reinitialisation. Within a row ::after spans the entire row – the same trick as with Bootstrap .stretched-link in the teasers, which is why the line position: relative
data-mpc-episode-indexplaylist title selected by the button (playlistManager.play(index))
data-mpc-episode-itemrow wrapper. Remains aria-current="true" as long as its title is selected and serves as the cloning source for the card
data-mpc-episode-currentAt the root: an index of the title currently displayed by the card. Is rendered as 0 , so that nothing needs to be replaced on first render

Class names follow the Bootstrap pattern (mpc-episode, mpc-episode-cover, mpc-episode-body, mpc-episode-play, …) with the layout as a modifier on the root element (mpc-episode-layout-card, mpc-episode-layout-episodes). The image assets are located in mpc-episode-cover and everything else in mpc-episode-main, so that the player is aligned with the play button rather than the image. List items reduce the size of the cover and the play bubble by using --mpc-vidply-episode-cover and --mpc-vidply-episode-play-size, which is why a row cloned into the card is displayed at full size.

The heading levels differ between the two positions – titleLevel 3 for the card, 4 for rows — so the clone takes on the tag of the title it replaces, rather than extending the row <h4> up to the top edge of the element.

Two things to bear in mind when customising the design of a row: the play button must not have a transform, as a transformed element becomes the enclosing block of its own overlay and the clickable area would jump back to the bubble on hover (instead, the hover and :focus-within states of the row take over the affordance instead). And the overlay covers the text of the row, so it is not possible to select a row’s short description with the mouse – the same compromise that the teasers also make. The expanded long description is the exception: it is raised above the overlay and remains selectable.

Advantages

  • Single point of responsibility – Each sub-element has a clear purpose
  • Reusability – Tracks sub-elements shared by video and audio
  • Easy to overwrite – Adjust only what you need
  • Maintainability – Changes are limited to specific files
  • Readability – The main template has been reduced from 250 to 130 lines

No changes that break compatibility

The refactoring leaves the HTML output and functionality unchanged. Existing configurations will continue to work without any changes.

Share page