mfd / adaptive-video
Delivers FAL videos as adaptive HLS streams for a fast start and smooth playback on any connection. Videos are encoded asynchronously with ffmpeg in a background worker; until a stream is ready, the original file is played. Works with <f:media> and the Text & Media content element, uses native HLS o
Package info
github.com/marketing-factory/adaptive_video
Language:JavaScript
Type:typo3-cms-extension
pkg:composer/mfd/adaptive-video
Requires
- php: ^8.2
- b13/poster-image: ^2.0
- typo3/cms-core: ^13.4 || ^14.0
Requires (Dev)
- phpunit/phpunit: ^11.0
- typo3/cms-filemetadata: ^13.4 || ^14.0
- typo3/testing-framework: ^9.3
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
Delivers FAL videos as adaptive HLS streams. Videos are encoded asynchronously with ffmpeg in a Symfony Messenger worker; until a stream is ready, and in browsers without JavaScript, the original file is delivered.
- TYPO3 13.4 and 14, PHP 8.2+
- Works with all output via
<f:media>/ theRendererRegistry(e.g. the Text & Media content element). Templates with their own<video>markup are not covered yet. - Native HLS where available (Safari, iOS), otherwise hls.js (light build, loaded only when needed).
- Poster images from
b13/poster-imageare supported.
How it works
- Scan (
adaptive-video:scan, scheduled): finds supported FAL files with at least one active file reference that have no version for their current content (sys_file.sha1) and encoding profile. For each it creates apendingversion intx_adaptivevideo_versionand queues a message. - Worker (
messenger:consume adaptive_video): analyzes the source with ffprobe, encodes all renditions with ffmpeg into a working directory, validates the result, moves it to<artifactDirectory>/<fileUid>/<versionUid>/and marks the versionready. Then the page caches showing the file are flushed. - Rendering: the renderer delivers the newest
readyversion whose content fingerprint matches the current file. If the file was replaced, the original is delivered until the new version is ready.
Short videos (below minimumDuration) and videos above the configured limits
are marked skipped and keep using the original. Errors mark a version
failed; there is no automatic retry.
A file is only processed once it is referenced, not on upload. References saved, copied or localized in the backend are queued right away by a DataHandler hook; everything else (imports, migrations, direct database changes) is picked up by the next scan.
Installation
composer require mfd/adaptive-video vendor/bin/typo3 extension:setup
- Install
ffmpegandffprobeon the host that runs the worker. - Add the site set Adaptive Video (
mfd/adaptive-video) to the sites that should deliver streams. - Enable processing in exactly the environments that encode (see Configuration), schedule the scan and run the worker (see Operation).
- Allow
blob:in your Content Security Policy (see Content Security Policy).
Configuration
Extension configuration
Set in Admin Tools > Settings > Extension Configuration or in
$GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['adaptive_video'].
| Setting | Default | Description |
|---|---|---|
processingEnabled |
0 |
Create versions and dispatch encoding jobs in this environment. Existing ready versions are delivered regardless. |
deliveryEnabled |
1 |
Deliver ready versions as HLS. Disable to encode first and switch the frontend later. Flush the page cache after enabling. |
defaultProfile |
default |
Identifier of the active encoding profile. |
supportedFileExtensions |
mp4,mov,m4v,webm |
Video file extensions that are processed. |
minimumDuration |
10 |
Shorter videos (seconds) are skipped. |
ffmpegPath, ffprobePath |
ffmpeg, ffprobe |
Binaries, absolute or looked up in PATH. |
commandPrefix |
Arguments placed before the binaries. | |
ffmpegOptions |
Additional output options for all encoders. All options except -threads are part of the profile fingerprint: changing them re-encodes all videos. |
|
maxRuntime |
10800 |
ffmpeg is aborted after this time (seconds) and the version marked failed. A version of a crashed worker can be taken over after this time. |
redeliverTimeout |
14400 |
A job not acknowledged within this time is handed out again (crashed worker). Must be higher than maxRuntime. |
maxSourceFileSize |
0 |
Larger files (MB) are skipped. 0 disables the limit. |
maxSourceDuration |
0 |
Longer videos (seconds) are skipped. 0 disables the limit. |
artifactDirectory |
_videos |
Directory for the streams, relative to the public web root. |
publicUrlPrefix |
/_videos/ |
URL path of the artifact directory. |
temporaryDirectory |
var/transient/adaptive_video |
Working directory; should be on the same filesystem as the artifact directory. |
rendererPriority |
20 |
Must be higher than the renderers it replaces (b13/poster-image: 10, Core: 1). |
Encoding profile
The default profile encodes 1080p (5000 kbit/s), 720p (2800), 480p (1400) and 360p (800) with H.264, AAC 128 kbit/s, at most 30 fps and 4 second fMP4 segments. The height refers to the shorter side, so portrait videos get the same quality steps. Renditions larger than the source are left out; a source smaller than all of them is encoded once in its own size.
Profiles can be added or overridden:
$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adaptive_video']['profiles']['default'] = [ 'renditions' => [ ['height' => 1080, 'videoBitrate' => 4500], ['height' => 720, 'videoBitrate' => 2500], ['height' => 360, 'videoBitrate' => 800], ], 'audioBitrate' => 128, 'audioSampleRate' => 48000, 'maxFrameRate' => 30, 'segmentDuration' => 4, ];
Every change of the active profile creates new versions on the next scan. The previous versions stay active until the new ones are ready.
Site settings
| Setting | Default | Description |
|---|---|---|
adaptiveVideo.enabled |
true |
Deliver streams on this site. |
adaptiveVideo.preload |
metadata |
Used if the template sets no preload. With none and metadata, segments are loaded on play only. |
adaptiveVideo.capLevelToPlayerSize |
true |
hls.js does not load renditions larger than the player. |
Operation
Scan
Run adaptive-video:scan regularly, e.g. every 10 minutes as scheduler task
Execute console commands or as cron job. It only queues jobs and returns
quickly. References saved in the backend do not wait for it, they are queued
on save.
vendor/bin/typo3 adaptive-video:scan --dry-run # list files that would be queued vendor/bin/typo3 adaptive-video:scan -v # queue and list them
Worker
The worker encodes one video at a time. Run exactly one worker per environment; encodings are CPU heavy and long videos take hours.
Recommended is a process manager such as Supervisor or systemd. The TYPO3 core stops a worker after one hour (after the current job), the process manager restarts it, so new code is loaded regularly.
[program:adaptive_video_worker] command=/path/to/vendor/bin/typo3 messenger:consume adaptive_video autorestart=true numprocs=1 ; Lets a running encoding finish on stop stopwaitsecs=10800
Without a process manager, cron with flock works as well. Calls fail on the
lock while a worker is running:
* * * * * flock -n /tmp/adaptive_video.lock /path/to/vendor/bin/typo3 messenger:consume adaptive_video
Restart the worker after a deployment. An encoding aborted by a restart is
handed out again after redeliverTimeout and continues from the start.
-v, -vv and -vvv print the progress of the worker: -vv shows when a
video is started, encoded and ready.
Status
vendor/bin/typo3 adaptive-video:status # versions per status, running and failed jobs vendor/bin/typo3 adaptive-video:status -v # additionally skipped versions vendor/bin/typo3 adaptive-video:status <fileUid> # versions of a file, active version and playlist URL
Retry and reprocess
- Retry a failed or skipped version: delete its row from
tx_adaptivevideo_version; the next scan queues the file again. - Reprocess everything (e.g. after a broken profile): empty the artifact
directory and the table
tx_adaptivevideo_version, then run the scan. The originals are delivered until the new versions are ready.
Old versions and their files are not removed automatically.
Logging
Entries use the channel Mfd.AdaptiveVideo.* with file and version uid as
context. By default TYPO3 only writes warnings and errors to
var/log/typo3_*.log. To log every finished video to its own file:
$GLOBALS['TYPO3_CONF_VARS']['LOG']['Mfd']['AdaptiveVideo']['writerConfiguration'] = [ \Psr\Log\LogLevel::INFO => [ \TYPO3\CMS\Core\Log\Writer\FileWriter::class => ['logFileInfix' => 'adaptive_video'], ], ];
Environments and deployment
Database and artifact directory belong together: a ready version without its
files results in a broken player (the renderer does not check the files). When
copying a database between environments, copy the artifact directory as well.
A typical rollout:
- Deploy with
processingEnabled = 1anddeliveryEnabled = 0on production. Videos are encoded, the frontend still delivers the originals. - Copy database and artifact directory to a test environment with
deliveryEnabled = 1and test there. - Enable delivery on production and flush the page cache.
Web server and CDN
The artifact directory is served as static files. Make sure that
-
.m3u8is delivered asapplication/vnd.apple.mpegurland.m4sasvideo/iso.segment(orvideo/mp4), e.g. for Apache:AddType application/vnd.apple.mpegurl .m3u8 AddType video/iso.segment .m4s
-
the files may be cached for a long time: every version has its own directory, so the content behind a URL never changes.
-
streams are served from the same host as the page. Otherwise the host has to be allowed in the CSP and send CORS headers.
Content Security Policy
The extension does not change the CSP. hls.js needs blob: for the media
source and its web worker:
media-src 'self' blob:;
worker-src 'self' blob:;
Without it, streams do not play in browsers without native HLS (Chrome, Firefox, Edge). Check the browser console for CSP violations when testing.
Limitations
- Only output through
<f:media>/ theRendererRegistry. - One active encoding profile for all sites.
- No automatic cleanup of old versions.