This reference covers the current Video Tracking runtime: module bootstrap, canonical storage, AJAX/REST entry points, Bricks integration, and the services that track watch state.
Overview
The Video Tracking runtime lives under src/Modules/VideoWatch/. It adds tracked video playback to BRM content, hosted video selection, and a native brm-video Bricks element.
- Admin surface —
BricksMembers → Video Trackingfor global defaults and host connections - Editor surface — BRM meta box fields for
brm_video_url,brm_video_duration,brm_video_image, and per-post watch policy overrides - Frontend surface —
BRM VideoBricks element plus watch-state AJAX sync and dynamic tags
Core Services
- VideoWatchSystem — Module bootstrap. Registers cross-module hooks and BRM Video element refresh controls.
- VideoConfigResolver — Resolves effective config for a post by merging global defaults, per-post policy, and the resolved video source.
- VideoWatchService — Canonical read/write service for user watch state. Exposes
get_watch_state(),get_effective_watch_state(),sync(), and related context helpers. - VideoSourceSync — Keeps canonical
_brm_video_sourcein sync with legacy fields such asbrm_video_url,brm_video_image, andbrm_video_duration, stores an explicit provider hint for opaque values, and lazily backfills legacy rows on read. - VideoProviderRegistry — Detects the provider from raw URL/ID input and normalizes provider-specific source data.
- VideoPickerService — Lists videos and groups from connected providers for admin picker flows and exposes
get_connected_providers()for editor surfaces. - VideoRefreshService — Adds threshold-reached refresh controls to the
brm-videoelement and enqueues the frontend refresh handler when needed.
Canonical Storage
Video Tracking uses a mix of options, post meta, and a dedicated watch-state table.
brm_video_watch_settings— Global defaults such as required percent, completion mode, gating, resume mode, seek policy, and thumbnail settingsbrm_video_host_credentials— Provider credentials and access tokens for the video pickerbrm_video_host_enabled— Per-provider admin enable flags_brm_video_source— Canonical normalized source payload for a post_brm_video_source_provider— Optional provider hint stored alongside legacybrm_video_urlvalues so opaque IDs can resolve deterministically_brm_video_watch_policy— Per-post overrides for required percent, completion mode, and next-navigation gatingbrm_video_url,brm_video_duration,brm_video_image— Legacy-compatible editor fields kept in sync with the canonical source{prefix}brm_user_video_watch— Per-user/per-post watch state including coverage, watched buckets, threshold flags, completion flags, duration, and last/max positions
VideoWatchService treats the watch table as the canonical runtime store for learner watch progress. The current row shape includes values such as coverage_map, watched_buckets, watched_percent, threshold_percent, threshold_reached, fully_watched, last_position_seconds, max_position_seconds, duration_seconds, and source_hash.
Effective Watch State vs Post Watch State
VideoWatchService::get_effective_watch_state() is the preferred read path for frontend logic. It can aggregate related child video posts when a lesson/template context should behave like a single tracked lesson rather than an isolated child video post.
$state = \BaselMedia\BricksMembers\Modules\VideoWatch\VideoWatchService::get_instance()
->get_effective_watch_state( get_current_user_id(), get_the_ID() );
if ( ! empty( $state['threshold_reached'] ) ) {
// Reveal dependent UI or allow a next-step action.
}
Use get_watch_state() only when you explicitly need the raw row for a single post/video source.
AJAX Handlers
The main frontend/admin entry points are registered in src/Ajax/VideoWatchActions.php.
wp_ajax_brm_get_video_watch_state— Returns the raw watch-state row for the requested post plusrequired_percentafter access/context validation. This handler does not callget_effective_watch_state().wp_ajax_brm_sync_video_watch_state— Syncs client watch progress into the canonical watch tablewp_ajax_brm_test_video_host_connection— Admin-only connection test for configured hosts
The sync payload keys are source_hash, duration_seconds, current_time_seconds, max_position_seconds, and bucket_indexes. The service rejects stale payloads if the source hash no longer matches the canonical post source.
REST Endpoints
Hosted video browsing is exposed through the REST controller under /wp-json/bricksmembers/v1/video-picker/.
GET /wp-json/bricksmembers/v1/video-picker/{provider}— List videos for one providerGET /wp-json/bricksmembers/v1/video-picker/{provider}/groups— List groups/folders for providers that support them
The current picker supports mux, wistia, gumlet, bunny, youtube, and vimeo. The routes are registered whenever the Video Tracking module is active. Requests require a logged-in user who can edit_posts plus a valid WordPress REST nonce. Successful responses still depend on the provider being enabled and connected. YouTube is considered connected when BRM has either a current access token or a refresh-token stack (refresh_token + client_id + client_secret). Vimeo requires a saved access token, and private libraries need the public and private scopes.
Bricks Integration
The frontend element is brm-video (BRM Video in the builder). It renders the canonical video source for the current post and handles tracked playback.
- Display modes —
direct_embed,click_to_load,lightbox - Threshold refresh control — Injected through
brm/elements/brm-video/controlsand backed byVideoRefreshService - Asset hook —
brm/elements/brm-video/enqueue_assets - Threshold event name —
brm:video:threshold-reached
The threshold refresh controls support no-op, partial AJAX refresh, or full page refresh with scroll/highlight restoration. This is the hook point that lets the video threshold reveal quizzes, refresh progress bars, or update wrapped BRM UI on the page.
Dynamic Tags
Video watch tags are registered through VideoWatchDynamicTags. These tags use VideoWatchService::get_effective_watch_state(), not the raw AJAX state handler, so lesson-level aggregate contexts can resolve differently from wp_ajax_brm_get_video_watch_state. Current tags:
{brm_video_watch:percent}{brm_video_watch:required_percent}{brm_video_watch:remaining_percent}{brm_video_watch:threshold_reached}{brm_video_watch:completed}{brm_video_watch:last_position}{brm_video_watch:resume_available}
Separate from the watch-state tags, the core BRM dynamic tags also expose the resolved video metadata for the current post. That includes {brm_video:url}, {brm_video:source}, {brm_video:image}, and {brm_video:duration}. {brm_video:source} returns the resolved provider key such as gumlet, vimeo, youtube, or html5.
Provider Normalization
VideoProviderRegistry::normalize_from_raw() is the normalization boundary between editor input and runtime playback. The canonical source payload contains provider-specific normalized data such as provider key, source value, source ID, embed URL, thumbnail ID, duration, and privacy mode.
Provider implementations also declare whether they support resume and seek-guard behavior. That allows the runtime to adapt playback UX without treating all video sources as identical.
Related
- User post: Video Tracking and the BRM Video Element