Skip to main content
← Back to Table of Contents

Summary

Custom video player with optional YouTube embed, skip controls, PiP, fullscreen, and compact control layout.

Basic usage

HTML5 video with poster

YouTube embed from state


State & validation

Stored value

State string = direct video URL or YouTube/Vimeo URL.

Validation rules

Built-in validation rejects malformed values, unsafe schemes, credentials in URLs and protocol-relative URLs. It checks the URL format; it does not verify that a remote file exists or that the browser can decode it.

Configuration API

All methods accept Closure unless noted.

ratio()

Supports standard aspect ratios. Invalid strings throw an InvalidArgumentException.

controlsLayout()

Use compact for the Video.js-style control layout. Players at least 512px wide show one bottom bar; narrower players place playback, volume, seeking and settings in a bottom bar, with PiP/fullscreen and remote playback in a separate upper corner group. Below 672px the inset is 8px; larger players use 12px. Time labels hide when their section becomes narrower than 256px. Click the right time value to switch between remaining time and total duration:
On a direct HTML5 video, move the pointer along the seek bar to preview a frame from that time without changing playback. The bar keeps its height while you hover or drag; the thumb and preview animate independently. Frame previews load on demand, and their first appearance may wait for the video host to deliver the requested part of the file. YouTube and Vimeo embeds retain their provider’s preview capabilities.

Real-world examples

Background video (Hero)

YouTube training video


Playground

/admin/flex-fields/video-field See Playground for setup.

Player appearance


Performance

Settings, playback speed and quality

The settings menu is shown when it has at least two playback speeds or two direct-video sources to choose from. It slides between views, animates its dimensions and follows pointer or keyboard focus with a moving highlight. Reduced-motion preferences are respected.
Speed and quality lists accept at most 64 entries. Playback speeds must be finite and greater than zero. Quality sources must be safe HTTP(S) URLs or paths starting with a single /. A quality switch preserves playback position and speed when the new source is ready. Use equivalent versions of the same recording. The source configured with src() is played initially; use the matching default entry for its initial quality label. YouTube playback speeds are limited to the speeds supported by that video. Custom quality selection applies to direct video; Vimeo uses its embedded player controls. Casting, fullscreen, Picture-in-Picture and autoplay also depend on the browser, device and user permissions.

Keyboard and touch controls

Focus the player to use Space/K for play and pause, M for mute, F for fullscreen, I for Picture-in-Picture, arrow keys for five-second seeking or volume adjustment, and Escape to dismiss menus. Editable fields retain their normal keyboard behavior. In settings, Up/Down and Home/End move focus, Enter selects an option, and Escape closes the menu and returns focus to its trigger. Submenu navigation follows the page’s writing direction. On touch devices, double-tap the left or right side to skip by skipSeconds(); double-tap the center for fullscreen.

Size, corners and icons

Use size('sm'), size('md') or size('lg') to change control dimensions. rounding() accepts the shared FLEX rounding values. Set fullWidth() to fill the available column and ratio() to preserve a chosen aspect ratio. The custom player uses the original Video.js SVG icons, bundled locally and rendered inline. Volume has high, low and muted states; fullscreen, Picture-in-Picture and remote playback have separate entry and exit icons. No external icon request is needed. Icon methods accept an icon identifier, enum, renderable icon or closure: playIcon(), pauseIcon(), volumeIcon(), volumeLowIcon(), muteIcon(), fullscreenIcon(), exitFullscreenIcon(), placeholderIcon(), settingsIcon(), speedIcon(), qualityIcon(), castIcon(), exitCastIcon(), airPlayIcon(), exitAirPlayIcon(), pictureInPictureIcon() and exitPictureInPictureIcon(). If you override volumeIcon(), castIcon() or airPlayIcon(), that icon also applies to the corresponding low-volume or connected state unless you supply a separate state icon:

Efficient frame previews

By default, native video previews seek a separate video decoder only when you move along the progress bar. For large videos, supply either a lightweight preview video or pre-generated thumbnail images. The player does not generate these files: prepare them in your media-processing workflow.

Lightweight preview video

Use the same timeline as the original film, with a lower resolution and bitrate:

Individual thumbnails

start is inclusive and end is exclusive, in seconds. Entries are sorted by start time and must not overlap. Only the image for the hovered time loads:

Storyboard sprite

Several frames may share one image. Add the pixel coordinates of each frame:
The image cache holds at most eight images and eight megapixels in total. Images that exceed the budget, fail to load, or leave gaps in the timeline fall back to previewSource() or the original video. Keep storyboard sheets small; splitting long timelines into multiple sheets limits decoded memory. A successful image preview does not start the secondary video decoder. Frame images are cached, and moving within the same frame interval does not repaint it. For JSON custom fields, use preview_source and preview_thumbnails with the same values. URL checks, interval ordering, overlap checks and crop validation also apply to stored configuration. PHP closures are evaluated for the current field; stored configuration contains data only. Paused players do not run a playback clock. While custom controls are hidden, the clock uses a 500ms timer; revealing them resumes animation-frame updates. Hidden browser tabs and inactive form surfaces suspend that clock.
Last modified on October 16, 2026