WP Manifestindependent plugin directory
manifest / media / xroad-videos

XRV (Xroad Video) releases

Privacy-first WordPress video gallery by Crossroad Media. A click-to-load YouTube facade that eliminates the consent-banner overlays, black/blank players, and pre-consent Google requests that Smash Balloon YouTube Feed plus a cookie manager produce. Preoads nothing from YouTube. Local thumbnails + self-generating VideoObject schema.

by Crossroad Media · github.com/crossroad-media/xroad-videos · website

2stars
1release downloads
0forks

Install

The author publishes release zips, so WP-CLI can install straight from GitHub:

wp plugin install https://github.com/crossroad-media/xroad-videos/releases/download/v2.8.0/xroad-videos-v2.8.0.zip

Readme

Crossroad Videos (xroad-videos)

A single-file WordPress plugin by Crossroad Media. A privacy-first, click-to-load video gallery and a drop-in alternative to Smash Balloon YouTube Feed for any site running a cookie/consent manager. Supports YouTube, Vimeo, Wistia, Loom, Dailymotion, and self-hosted MP4 / WebM behind one facade, with geo-aware GDPR consent modes, a built-in bulk importer, a site-wide settings page, and multiple layouts.

Why it exists

Smash Balloon YouTube Feed (and standard YouTube embeds) fire requests to youtube.com / i.ytimg.com / google.com on page load, before consent. When a consent manager (CookieYes, Osano, Cookiebot, Complianz, etc.) is present, it tries to intercept those requests, and that interception is what produces the symptoms operators keep reporting:

  • a consent warning overlay sitting on top of the player,
  • a black or blank player that never loads,
  • cascade JavaScript failures when the CMP and the feed's consent shim collide,
  • and corrupted GA4 attribution when a page-refresh "fix" is used to force the player to load.

Every one of those has the same root cause: third-party requests firing before a deliberate user action.

This plugin inverts that. Every card starts as a local poster image + a play button: pure first-party HTML/CSS, zero requests to any Google domain, zero cookies, zero localStorage. Because nothing third-party fires before interaction, the consent manager has nothing to block, so no banner, overlay, or black player can appear. Only on click does it inject a youtube-nocookie.com iframe (the click is the consent) and push a video_play event to the dataLayer. Storing thumbnails locally hardens the guarantee: even the poster makes no call to i.ytimg.com.

This is the "facade" pattern.

What it does

  • Curated CPT xroad_video. Editors paste a video URL from any supported host and drag to reorder; the provider is auto-detected and no API key is required.
  • Built-in bulk importer (Videos → Import). Paste a list of URLs, point at a channel or playlist (optionally with a free API key for durations and upload dates), or upload a JSON file carrying full metadata and taxonomy terms. A dry-run preview shows new vs. already-in-library before anything is written, then the import runs in batched AJAX with a progress bar.
  • Local thumbnail sideload on save, so the grid references /wp-content/uploads/ and never calls the host's image CDN. YouTube uses its predictable poster URLs (maxres with hqdefault fallback, checked by HTTP status); every other host supplies its poster, and for all but YouTube its duration, from its own no-key oEmbed endpoint, which also prefills the title on first save.
  • Layouts. A featured carousel, a browse grid, or both in one shortcode (layout="library"), with lightbox or inline playback. Filtering (dropdown selects or chips for Series / Audience / Topic), keyword search, and sort (including shortest or longest by duration) are pure client-side toggles, so they run instantly with no network round-trip. Paged browse adds a Load More button and an optional Subscribe button, and the grid steps 3 to 2 to 1 columns on smaller screens.
  • Geo-aware consent modes. Global (recommended), Strict GDPR, or No consent integration. Global shows a dismissible opt-in prompt only to EU/UK/EEA/CH visitors (resolved from an edge country header) and stays frictionless for everyone else, including US / CCPA, since the facade shares no data with YouTube until a click. Whenever a prompt is required the plugin makes zero contact with any Google domain until the visitor accepts. See Privacy, consent & GDPR below.
  • Site-wide settings page (Videos → Settings). Set the consent mode, privacy URL, filter style, per-page counts, Subscribe URL, and YouTube Data API key once; every gallery inherits them, and any shortcode or block attribute still overrides.
  • Self-generating VideoObject JSON-LD inside a CollectionPage / ItemList, merging with the site's Organization node via the xrv_org_id filter. Single-video pages emit a standalone VideoObject with transcript and key-moment Clips for rich-result and AI-citation eligibility.
  • Shortcode, block, and block sidebar controls under the collision-proof xroad namespace. The Gutenberg block exposes Layout, Browse, Privacy, and pre-filter panels through InspectorControls, with no build step.
  • Three REST-exposed taxonomies (xrv_series, xrv_audience, xrv_topic); sites define their own terms.
  • Multi-source. One _xrv_provider switch routes YouTube, Vimeo, Wistia, Loom, Dailymotion, and self-hosted files, auto-detected from the pasted URL. Hosted videos load on click as the host's privacy-enhanced iframe (Vimeo with dnt=1); self-hosted files play in a native <video> element with zero third-party contact ever. Vimeo unlisted and domain-private videos keep their privacy hash, and galleries can mix sources in a single grid.

No page builder, ACF, jQuery, or build step. The inline-asset architecture (CSS, JS, and SVG emitted once per request) survives a performance plugin's unused-CSS pass and moves between themes unchanged.

Usage

[xroad-videos]
[xroad-videos layout="library" per_page="9" subscribe_url="https://youtube.com/@yourchannel"]
[xroad-videos series="webinars" columns="3" filter_ui="chips" consent_notice="geo" privacy_url="/privacy-policy/"]

Block: xroad/videos (a PHP-rendered dynamic block that shares the shortcode's render path). Every attribute below is also a block sidebar control, and anything left blank inherits the site-wide default from Videos → Settings, which a shortcode or block attribute always overrides.

Sources

Editors never pick a provider by hand: paste a URL and the host is detected automatically (the Provider selector defaults to Auto-detect). Supported sources:

Source Paste Metadata without an API key
YouTube watch, youtu.be, Shorts, or embed URL title, poster
Vimeo vimeo.com/... (including unlisted vimeo.com/{id}/{hash}) title, poster, duration, description
Wistia a wistia.com/medias/... URL title, poster, duration
Loom loom.com/share/... title, poster, duration
Dailymotion dailymotion.com/video/... or dai.ly/... title, poster
TikTok a tiktok.com/@user/video/... link (vertical, rendered 9:16) title, poster
Self-hosted file a direct .mp4 or .webm URL (pick Self-hosted file) none; set the title and upload a poster yourself

Hosted videos load on click as the host's privacy-enhanced player (Vimeo uses dnt=1 with no title or byline chrome); self-hosted files play in a native <video> element, so they make no third-party contact at any point. Galleries can mix sources freely in one grid. The bulk importer and channel sync are currently YouTube-only; extending bulk import to every provider is a planned roadmap item.

Content and filtering

Attribute Default What it does
series, audience, topic all Pre-filter to one or more taxonomy term slugs (comma-separated).
limit all Cap how many videos render.
filter_ui select Facet filter style: select (dropdowns) or chips (clickable rows).
controls true false hides the search / sort / filter bar.

Layout

Attribute Default What it does
layout grid grid, carousel, or library (a featured carousel above a browse grid).
columns responsive Fixed column count; blank gives responsive columns (masonry for grid, 3 for library/carousel).
featured_limit 6 How many videos feed the featured carousel.
heading none Optional centered section title above a grid or carousel.
playback lightbox lightbox pops the video into a centered overlay; inline plays it in the card.
per_page 9 Cards shown before a Load More button appears.
load_more 3 How many more cards each Load More click reveals.
subscribe_url, subscribe_label off Show a Subscribe button under the grid when subscribe_url is set; subscribe_label sets its text.

Privacy and consent

Attribute Default What it does
consent_notice off geo = Global (opt-in prompt for EU/UK/EEA/CH; one-click + facade elsewhere, including US / CCPA), strict = Strict GDPR (opt-in prompt for everyone), off = no consent layer. (light = legacy on-video caption, still honored.) See Privacy, consent & GDPR.
consent_text built-in Body text of the consent prompt.
consent_button Load video Accept-button label.
consent_decline No thanks Decline-button label.
privacy_url WP privacy page Privacy-policy link shown in the prompt.

Install

Single-file plugin. Either upload xroad-videos.php to wp-content/plugins/xroad-videos/, or zip the folder (xroad-videos/xroad-videos.php) and upload via Plugins → Add New → Upload. Activate; the CPT and taxonomies register on activation.

Configuration filters

Filter Purpose
xrv_org_id Pin the publisher @id to your SEO plugin's exact Organization @id so the two schema nodes merge instead of competing.
xrv_seed_terms Pre-seed taxonomy terms on activation: return [ taxonomy => [ slug => name ] ]. Idempotent.
xrv_synonym_map Extend the keyword index: return [ term-slug => 'extra search aliases' ].
xrv_video_schema Modify a single VideoObject node (add transcript, regionsAllowed, about, etc.).
xrv_list_name Override the CollectionPage / ItemList name.
xrv_consent_required Override the geo consent decision for the current request (force a region, plug in MaxMind, defer to your CMP, and so on). Return true to require the consent prompt, false to allow one-click play.

Example, merging schema with Yoast/Rank Math's Organization node:

add_filter( 'xrv_org_id', fn() => 'https://example.com/#organization' );

Verify the privacy guarantee

Open a page using the gallery in DevTools (Network tab, cache disabled) and confirm zero requests to youtube.com / youtube-nocookie.com / google.com / i.ytimg.com before any click, and zero YouTube cookies / localStorage. Repeat with your consent manager active and confirm no banner, overlay, or black screen. If any Google-domain request fires pre-click, that's a bug; open an issue.

Read the full README on GitHub →

Releases

TagPublishedAssetDownloads
v2.8.0 Jul 4, 2026 xroad-videos-v2.8.0.zip 0
v2.7.1 Jul 4, 2026 xroad-videos-v2.7.1.zip 0
v2.7.0 Jul 4, 2026 xroad-videos-v2.7.0.zip 0
v2.6.0 Jul 4, 2026 xroad-videos-v2.6.0.zip 0
v2.4.0 Jul 4, 2026 xroad-videos-v2.4.0.zip 0
v2.2.0 Jul 4, 2026 xroad-videos-v2.2.0.zip 0
v2.1.1 Jul 4, 2026 xroad-videos-v2.1.1.zip 0
v2.1.0 Jul 4, 2026 xroad-videos-v2.1.0.zip 0
v2.0.1.beta Jul 4, 2026 xroad-videos-v2.0.1.beta.zip 0
v2.0.0 Jul 4, 2026 xroad-videos-v2.0.0.zip 0
v1.0.9 Jul 4, 2026 xroad-videos-v1.0.9.zip 0
v1.0.8 Jul 4, 2026 xroad-videos-v1.0.8.zip 0
v1.0.7 Jul 4, 2026 xroad-videos-v1.0.7.zip 0
v1.0.6 Jul 4, 2026 xroad-videos-v1.0.6.zip 0
v1.0.5 Jul 4, 2026 xroad-videos-v1.0.5.zip 0
v1.0.4 Jul 4, 2026 xroad-videos-v1.0.4.zip 0
v1.0.3 Jul 4, 2026 xroad-videos-v1.0.3.zip 0
v1.0.2 Jul 4, 2026 xroad-videos-v1.0.2.zip 0
v1.0.1 Jul 4, 2026 xroad-videos-v1.0.1.zip 0
v1.0.0 Jul 4, 2026 xroad-videos-v1.0.0.zip 0
v2.10.0 Jul 4, 2026 xroad-videos.zip 0
v2.5.1 Jun 19, 2026 xroad-videos.zip 0
v2.5.0 Jun 19, 2026 xroad-videos.zip 0
v2.3.0 Jun 19, 2026 xroad-videos.zip 0
v2.2.2 Jun 19, 2026 xroad-videos.zip 1