=== Pellio ===
Contributors: scalemath
Tags: video player, youtube, vimeo, video hosting, lms
Requires at least: 6.5
Tested up to: 7.1
Requires PHP: 8.1
Stable tag: 0.6.17
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

A fast, private, brandable video player for YouTube, Vimeo and Media Library videos. Connect Pellio to host your videos without YouTube.

== Description ==

Pellio gives your videos a clean, fast player in your own colours, whether they live on YouTube, Vimeo, your Media Library or Pellio.

= Free: the Pellio player =

No account needed.

* **YouTube, Vimeo and Media Library videos.** Add a Pellio Video block and paste a link, or choose a video from the Media Library. Or use `[pellio url="…"]`.
* **Your branding.** The play button and progress bar use your colour, with your corner radius. YouTube's title bar, logo, "More videos" and end-screen suggestions stay out of view.
* **Private until play.** Pages show a thumbnail stored on your own site. Visitors' browsers don't contact YouTube or Vimeo until they press play, and then only through youtube-nocookie.com and Vimeo's "do not track" mode.
* **Fast.** No player code from YouTube or Vimeo loads with the page; the player script is small and deferred.
* **Existing embeds too.** Optionally play the YouTube and Vimeo embed blocks already in your posts in the Pellio player.
* **Accessible controls.** Keyboard shortcuts (space, arrows, M, F), playback speed, full screen, and picture-in-picture for Media Library videos.
* **Google Analytics.** Optionally send video_start, video_progress and video_complete to GA4 or Tag Manager.
* **Consent managers.** Works with the WP Consent API, Complianz, CookieYes, Real Cookie Banner and Borlabs Cookie: YouTube and Vimeo videos wait for marketing consent (or a click on "Play video" in the player's notice).

= With a Pellio account: host your videos on Pellio =

Upload videos to Pellio instead of YouTube, straight from the block editor. Files go directly to Pellio's storage, not through your server. Pellio-hosted videos add:

* No ads, no YouTube logo and no other channels' videos.
* **Private and domain-locked videos.** Every embed is authorised by a short-lived token signed by your site, so videos can't be hot-linked elsewhere.
* **Email capture, calls to action and annotation cards** in the player, with leads sent to Pellio.
* **Chapters, captions and transcripts**, plus VideoObject structured data (with key moments) and a video sitemap for search engines, with Yoast SEO and Rank Math support.
* **Viewer analytics**: who watched, how much, and from which page.
* **Playlist and Lightbox blocks.**
* **LifterLMS.** Require students to watch a percentage of a lesson video (verified in Pellio: skipping ahead doesn't count) before "Mark Complete", auto-complete lessons, lock seeking, resume where students left off, and see video progress in LifterLMS reports.
* **Migration.** Tools → Pellio migration finds Vimeo, Wistia and YouTube embeds across posts, pages and course fields, replaces the ones already in Pellio (with preview and undo), and moves Media Library videos to Pellio.
* **Page builders and custom fields.** An Elementor widget and a Bricks element (with dynamic data, e.g. a custom field holding a YouTube link), and an ACF "Pellio Video" field type that returns the player, the video id or its data.
* **Automations.** When a logged-in user starts or completes a video (verified by Pellio for hosted videos), apply WP Fusion or FluentCRM tags, or run Uncanny Automator recipes. Developers get PHP actions (pellio_video_played, pellio_video_progress, pellio_video_completed, pellio_lead_captured) and DOM events (pellio:play, pellio:progress, pellio:complete, pellio:lead).
* **Roles, not seats.** Choose which WordPress roles may upload and manage videos. They use the site's connection, so they don't need their own Pellio accounts.
* **Your library in WordPress.** Pellio → Videos: upload, search and filter by folder, edit titles, descriptions, privacy, thumbnails, chapters and captions, replace a video's file, and restore from Trash. Each video's analytics (plays, retention, where it was watched, and which site members watched how much) are there too, with a dashboard widget for your top videos.
* **Developers.** `pellio_video( $video )` template function, `[pellio id="…"]` shortcode, oEmbed (paste a Pellio link), and WP-CLI: `wp pellio status|scan|migrate|rollback|offload`.

Connect your account in Pellio → Settings. No API keys to copy.

== Installation ==

1. Install and activate the plugin.
2. Add a **Pellio Video** block to a post and paste a YouTube or Vimeo link, or choose a Media Library video. Set your player colour in **Pellio → Settings**.
3. To host videos on Pellio: in **Pellio → Settings**, click **Connect to Pellio**, sign in (or create an account) and approve the site. Then drop a video file on a Pellio Video block to upload it.

== Frequently Asked Questions ==

= Do I need a Pellio account? =

No. The player works for YouTube, Vimeo and Media Library videos without one. An account is for hosting videos on Pellio (uploads, private videos, email capture, analytics and the other features listed above).

= Does it work with my LMS or membership plugin? =

Course lessons: LifterLMS, LearnDash, Tutor LMS and Sensei LMS can require a Pellio video to be watched (counted in unique seconds and verified with Pellio, so skipping ahead never counts) before a lesson is completed, and can complete it automatically. Members-only videos: MemberPress, Paid Memberships Pro, Restrict Content (Pro), WooCommerce Memberships and LifterLMS memberships. Pick the plans in the video block's Access panel; everyone else sees a "for members" message and is never issued a playback token.

= Does it work with cookie consent plugins? =

Yes. With the WP Consent API, Complianz, CookieYes, Real Cookie Banner or Borlabs Cookie active:

* Pellio-hosted videos play without consent: the player sets no cookies. Until the visitor allows statistics, views are counted anonymously and no viewer id is kept on their device.
* YouTube and Vimeo videos need marketing consent. Without it, pressing play first explains that the video loads from YouTube or Vimeo, and a second click plays that one video.
* The player's storage is declared through the WP Consent API for generated cookie policies.

= Does it work with page caching and optimization plugins? =

Yes. Embed tokens on cached pages are refreshed automatically, and Pellio's scripts opt out of "delay JavaScript" features in WP Rocket, LiteSpeed Cache, Autoptimize and Cloudflare. With Perfmatters or FlyingPress, add `/plugins/pellio/assets/js/` to their delay exclusions. Pellio → Settings → Status checks your setup.

= Why does a Vimeo video show Vimeo's own controls? =

Vimeo only lets embeds hide its controls for videos owned by paid Vimeo accounts. On other Vimeo videos its controls may appear as well.

= Can the player hide everything from YouTube? =

It hides YouTube's title bar, logo, pause-screen and end-screen suggestions. YouTube still serves the video, so its terms apply, and any ads the video's owner has turned on can still play. To avoid YouTube entirely, host the video on Pellio.

= Does the video file pass through my server? =

Not for uploads from the editor: the browser sends the file straight to Pellio's storage. The Media Library offload is the exception: those files are already on your server, so it sends them to Pellio from there, in 16 MB chunks.

= Does the player set cookies? =

No. Pellio's player is cookieless. It keeps an anonymous visitor id in the player's own local storage for view statistics.

= Can I undo a migration? =

Yes. Every post changed by the migration is backed up first. Use **Undo** on the Tools → Pellio migration screen or `wp pellio rollback`.

== External services ==

**YouTube and Vimeo (the player, no account needed).** When you add a YouTube or Vimeo link, your server asks the provider's oEmbed endpoint for the video's title, size and thumbnail (https://www.youtube.com/oembed or https://vimeo.com/api/oembed.json), and downloads the thumbnail (from i.ytimg.com or i.vimeocdn.com) into your uploads folder once. Visitors' browsers contact the provider only when they press play: the video then loads from youtube-nocookie.com or player.vimeo.com, which receive the visitor's IP address and browser details like any embed.
YouTube: terms https://www.youtube.com/t/terms, privacy https://policies.google.com/privacy.
Vimeo: terms https://vimeo.com/terms, privacy https://vimeo.com/privacy.

**Pellio (only when you connect a Pellio account).** Pellio (https://pellio.io) is a video hosting service operated by ScaleMath. Nothing is sent to Pellio until you connect.

* **Connecting your site** (Pellio → Settings): you are sent to pellio.io to sign in and approve the site. Your site URL is sent to Pellio, and Pellio returns a site token and a signing secret, stored in your WordPress database.
* **Uploading and managing videos**: the video's file name, size, type and title are sent to the Pellio API (api.pellio.io) to create the upload. The file itself is sent from the browser (editor uploads) or from your server (Media Library offload) to Pellio's storage.
* **Playing videos**: visitors' browsers load the Pellio player from Pellio (play.pellio.io). The player records anonymous viewing statistics (which parts of the video were watched, the address of the page it is embedded on, device and browser type). When a logged-in WordPress user watches, their WordPress user id is included so you can see per-student progress, and their email address if you turn on "Identify signed-in users by email" in Pellio → Settings (off by default). If you turn on "Viewer watermark", their display name is included too, so the player can show it over the video. Viewers who fill in an email-capture form in the player send the details they enter to Pellio. No cookies are set.
* **Search structured data**: to describe each embedded Pellio video to search engines, your server fetches the video's title, description, duration, chapters and (when it has captions) its transcript from the Pellio API. Results are cached for 12 hours. Turn this off with the `pellio_output_schema` filter, or just the transcript with `pellio_schema_transcript`.
* **Course progress and evidence** (LifterLMS, connected sites only): lesson progress boxes, completion checks, the version check when a student opens a lesson, and watch-evidence exports fetch per-student watch data (by WordPress user id) and the video's current version from the Pellio API. Nothing new is sent.
* **Learner log** (connected sites with LifterLMS, LearnDash, Tutor LMS or Sensei LMS): for lessons with a Pellio video, when a student’s watch is verified, the lesson is completed or reopened, or they sign an attestation, your server sends Pellio the event with the student’s WordPress user id and display name, the course and lesson titles, and their email address only if “Identify signed-in users by email” is on. Pellio keeps these in a tamper-evident learner log for your records.
* **Linked YouTube and Vimeo videos** (connected sites only): the first time a YouTube or Vimeo video is shown in the Pellio player, its id, title and thumbnail address are sent to Pellio to list it in your library. While it plays, the player sends Pellio which seconds were watched, the page address and, for logged-in users, their WordPress user id, signed by your site.
* **Automations and member analytics**: when a logged-in user plays a Pellio-hosted video, your server asks Pellio how much of it that user watched (by their WordPress user id), to verify progress before firing automations; Pellio → Videos shows each video's statistics from Pellio.
* **Status checks** (Pellio → Settings → Status, and Tools → Site Health): your server sends Pellio a test token signed with this site's secret, and Pellio returns whether it is valid, its clock time and your workspace's allowed domains.
* **Migration**: the platform ids of Vimeo/Wistia videos found on your site are sent to Pellio to find the matching imported videos.
* **LifterLMS**: when a student reaches a lesson's watch threshold, the student's WordPress user id and the video id are sent to Pellio to confirm their progress.

Pellio terms of service: https://pellio.io/terms
Pellio privacy policy: https://pellio.io/privacy

== Screenshots ==

1. The Pellio Video block: paste a YouTube or Vimeo link, choose a Media Library video, or (with a Pellio account) upload.
2. The player: your colours, with no YouTube title bar or suggested videos.
3. Tools → Pellio migration: find and replace Vimeo, Wistia and YouTube embeds.
4. LifterLMS lesson settings: require watching before completion.

== Changelog ==

= Unreleased =

= 0.6.17 =
* New: move from Presto Player. Pellio → Migration (and `wp pellio migrate`) switches Presto Player blocks, reusable Media Hub items and `[presto_player]` shortcodes to the Pellio player: YouTube, Vimeo and Media Library videos, or the Pellio-hosted copy once imported. Backed up and undoable like other migrations.

= 0.6.16 =
* New: viewer watermark (Pellio → Settings): signed-in users see their own name, faint and moving, over Pellio-hosted videos, to discourage screen recording.

= 0.6.15 =
* New: watch evidence CSV and retraining for LearnDash, Tutor LMS and Sensei LMS (previously LifterLMS only). Download per course from the course screen, Pellio → Settings → Courses, or `wp pellio evidence <course>`; when a lesson’s video is replaced and Pellio requires the latest version, learners who completed the old one are asked to rewatch.

= 0.6.14 =
* New: LifterLMS engagement triggers: “Student watches a lesson’s Pellio video (verified)” and “Student has watched every Pellio video in a course”, for achievements, certificates and emails. Developers: `pellio_llms_lesson_video_watched` and `pellio_llms_course_videos_watched` actions.
* New: Pellio videos in LifterLMS quiz questions play in the signed Pellio player (private videos work).
* New: the LifterLMS course builder’s Pellio panel can pick the lesson video from your Pellio library, and set up an attestation.
* New: cross-device resume: a signed-in student picks up a lesson video where they got to, on any device.
* Improved: the embed migration also moves LifterLMS quiz question videos and Sensei lesson videos to Pellio.

= 0.6.13 =
* New: attestation after a lesson video (LifterLMS, LearnDash, Tutor LMS, Sensei LMS). Once Pellio verifies the watch, learners confirm your statement (optionally typing their name, or their name and password) before the lesson completes. Each attestation is recorded in the learner log and in the LifterLMS watch evidence CSV.

= 0.6.12 =
* New: learner log. Verified watches, lesson completions and reopened lessons (LifterLMS, LearnDash, Tutor LMS, Sensei LMS) are recorded in Pellio’s tamper-evident learner log alongside the watch milestones Pellio records itself. Events that can’t be delivered are retried hourly.

= 0.6.11 =
* Fixed: Media Library videos without a thumbnail could start playing on their own (or show “Press play to start”) when the page loaded; they now wait for play.

= 0.6.10 =
* Fixed: LifterLMS lessons: a video picked in the Pellio box wasn’t saved when the lesson’s Video Embed URL field was empty.

= 0.6.9 =
* Improved: locked videos (logged-in or members only) show a styled panel with a lock icon instead of plain text.

= 0.6.8 =
* Fixed: a Pellio video without a title set in the block now labels its play button with the video’s own title.

= 0.6.7 =
* Fixed: Pellio videos embedded by shortcode (or blocks saved without a size) now use the video's own shape instead of 16:9, so wide films don't get black bars.

= 0.6.6 =
* New: members-only videos. In the block's Access panel (or `[pellio … visibility="members" members="pmpro:2,memberpress:12"]`), limit a video to members of chosen plans from MemberPress, Paid Memberships Pro, Restrict Content (Pro), WooCommerce Memberships or LifterLMS. Everyone else sees a "for members" message and is never issued a playback token, including by the cached-page token refresh. Filters: `pellio_member_can_watch`, `pellio_members_locked_html`.
* New: watch-to-complete for LearnDash, Tutor LMS and Sensei LMS. With a Pellio video in a lesson, require it to be watched (unique seconds, verified with Pellio) before the lesson can be completed, complete it automatically, and stop skipping ahead; the player resumes where the student left off. Set it site-wide in Pellio → Settings → Courses or per lesson.

= 0.6.5 =
* Fixed: embedding a linked YouTube or Vimeo video by its Pellio id (shortcode, block or library) showed a "This video is on YouTube" message instead of playing it.
* New: LifterLMS watch evidence export. Download a CSV per course (course edit screen, or `wp pellio evidence <course>`) with each student's unique seconds watched, sessions, first/last viewed, video version, lesson completion time and best quiz grade.
* New: LifterLMS retraining. When a lesson's video is replaced and Pellio requires completion on the latest version, students who completed the old version are asked to rewatch it.

= 0.6.4 =
* New: captions button for YouTube videos in the Pellio player (the video's own captions).
* Improved: the player colour setting uses the WordPress colour picker, with a hex field.

= 0.6.3 =
* New: with a Pellio account connected, YouTube and Vimeo videos played in the Pellio player appear in your Pellio library as linked videos, with viewing analytics per viewer (including which logged-in users watched and how much).
* Improved: Pellio-hosted videos show the same play button as the player (in your workspace colour, with the duration) before they load.
* Improved: Access settings list roles in a tidy grid.

= 0.6.2 =
* Fixed: in Safari (which blocks YouTube and Vimeo from starting with sound), pressing play no longer leaves a spinner or a zoomed YouTube thumbnail: the next click starts the video, with a “Press play to start” hint; Vimeo falls back to its own play button.
* The Pellio menu icon, block and Automator integration use the Pellio logo.

= 0.6.1 =
* Wording: “Thumbnail” instead of “Poster” in the block and Pellio → Videos.

= 0.6.0 =
* New: automations: WP Fusion and FluentCRM tags and Uncanny Automator triggers when a logged-in user starts or completes a video or submits their email; PHP actions and DOM events for developers.
* New: Access settings: choose which roles can upload and manage videos without their own Pellio accounts.
* New: per-video analytics in Pellio → Videos, including which site members watched, and a dashboard widget.
* New: Pellio menu in the dashboard: Videos (upload, search, folders, edit details, thumbnail, chapters, captions, replace the file, Trash), Migration and Settings.
* Improved: the Pellio Video block's setup screen, styled consistently in any theme; "Connect Pellio" opens in a new tab so the post stays open.
* New: Status panel in Pellio → Settings and a Site Health test: connection, embed signing, server clock, allowed domain, Content-Security-Policy, REST API reachability and caching plugins.
* New: embeds on pages served from a page cache for longer than 12 hours get fresh tokens automatically, so private and members-only videos keep playing.
* New: Elementor widget, Bricks element and ACF "Pellio Video" field type.
* New: pellio_video( $video, $atts ) template function: a Pellio id or link, a YouTube/Vimeo link, a video file URL or a Media Library attachment id.
* New: consent-manager support (WP Consent API, Complianz, CookieYes, Real Cookie Banner, Borlabs Cookie): YouTube and Vimeo wait for marketing consent; Pellio-hosted views stay anonymous until statistics consent.
* Improved: compatibility with WP Rocket, LiteSpeed Cache, Autoptimize, SiteGround Optimizer and Cloudflare Rocket Loader: Pellio's scripts opt out of JavaScript delay and combining, so the first click on play always works.

= 0.5.0 =
* New: the Pellio player works without a Pellio account, for YouTube, Vimeo and Media Library videos: paste a link in the Pellio Video block, or use [pellio url="…"].
* New: player branding in Pellio → Settings: colour and corner radius.
* New: privacy by default: thumbnails are stored on your site, and visitors don't contact YouTube or Vimeo until they press play (youtube-nocookie.com, Vimeo do-not-track).
* New: YouTube's title bar, logo and suggested videos stay hidden; the thumbnail covers the end screen.
* New: optionally play existing YouTube and Vimeo embed blocks in the Pellio player.
* New: speed menu, keyboard shortcuts, full screen, and picture-in-picture and optional download for Media Library videos.
* Google Analytics video events now cover YouTube, Vimeo and Media Library videos too.

= 0.4.0 =
* New: optionally send video_start, video_progress and video_complete to Google Analytics 4 / Tag Manager (Pellio → Settings).
* Improved: analytics see the address of the page a video is watched on, for page and UTM reports in Pellio.
* New: Pellio Playlist block: a Pellio playlist (kept in sync) or hand-picked videos, as a player with a list (auto-advance) or a grid that opens in a lightbox.
* New: Pellio Lightbox block: a thumbnail or button that opens a video in a lightbox.
* New: video sitemap (/pellio-video-sitemap.xml) listing the posts that embed Pellio videos, added to robots.txt.
* New: search structured data now includes the transcript (from captions), chapters as key moments, and SeekToAction for videos without chapters.
* New: key-moment links (?pellio_t=SECONDS) start the video at that time.
* New: with Yoast SEO or Rank Math, the video sitemap is listed in their sitemap index and each VideoObject joins their schema graph (linked to the page) instead of a separate script.
* Chapters, transcripts, downloads and other new player features work in existing embeds automatically.

= 0.3.0 =
* Migration: scan, preview, replace and undo Vimeo/Wistia/YouTube embeds; Media Library offload; WP-CLI commands.
* LifterLMS: server-verified completion, seek lock, resume, lesson video picker, course builder panel, reporting.
* Scripts are enqueued files; offload uses the WordPress HTTP API with multipart uploads.

= 0.2.0 =
* Connect flow, signed embeds, zero-JS facade, block uploads with processing status and library picker, shortcode, oEmbed, VideoObject structured data.

= 0.1.0 =
* First version.
