---
title: MediaBlaster Player
menu_group: Front-end
menu_order: 65
tab: —
tab_order: 10
summary: Embed the MediaBlaster streaming player with Video.js, shortcode, Gutenberg block, Elementor widget, skins, and developer hooks.
---

# MediaBlaster Player

The MediaBlaster player embeds video on WordPress pages and posts. **Video.js** is the default playback engine with a safe **native HTML5 fallback**, multi-source HLS/DASH/MP4 support, caption/chapter/thumbnail tracks, subscription-aware locked states, and JavaScript events for integrations.

All embed methods (shortcode, Gutenberg block, Elementor widget) call the same PHP renderer: `Mediablaster_Player::render()`. Locked or premium content never exposes playable source URLs in HTML when access is denied.

## Using the MediaBlaster Player shortcode

```
[mediablaster_player]
```

## Legacy shortcode (still supported)

```
[tv-video-player]
```

Both shortcodes use the same renderer. New sites should prefer `[mediablaster_player]`.

Example with skin:

```
[mediablaster_player id="123" skin="streaming"]
```

## Using the MediaBlaster Player Gutenberg block

1. In the block editor, click **+** and search for **MediaBlaster Player** (category: **Media**).
2. Insert the block and open the sidebar panels: **Video Source**, **Player Display**, **Playback Options**, **Tracks**, **Advanced**.
3. Choose a **Video Post ID** (search or numeric ID). The block **imports** HLS/MP4, poster, and track URLs from that post’s MediaBlaster meta into the sidebar (editable). You can also enter direct URLs manually.
4. Publish — the frontend renders via PHP (server-side dynamic block).

Imported HLS/MP4 URLs in the sidebar are **for reference only** when a **Video Post ID** is set. The frontend always loads **fresh URLs from that video post** on each page view (same as the shortcode). This matters for Vimeo and other signed CDN URLs that expire.

**Use URL fields instead of post meta** (Advanced) applies only when **no video post** is selected (manual URL mode). The toggle is disabled when a post ID is set.

If URL fields stay empty after selecting a post, reload the editor or re-select the video — playback still works from post meta when only **Video Post ID** is set.

The editor shows a server-side preview when a source is configured. Subscription and source resolution run on the server, same as the shortcode.

## Using the MediaBlaster Player Elementor widget

Requires [Elementor](https://elementor.com/) (Elementor Pro not required).

1. Edit a page with Elementor.
2. Search the widget panel for **MediaBlaster Player** (category **MediaBlaster**).
3. Configure **Source mode**:
   - **Current Post** — uses the post ID of the page being viewed. On **Theme Builder** templates (e.g. single `movies`), set the sample post under the template **Preview Settings** (gear → Preview Settings → choose a movie); the editor canvas uses that preview post, not the `elementor_library` template post itself.
   - **Select Post** — pick a video from the dropdown or enter a Post ID.
   - **Direct URLs** — HLS/DASH/MP4 fields only (optional Post ID for poster/meta fallback).
4. Adjust display, playback, and track settings in the remaining sections.

**Editor preview:** The Elementor canvas shows the **same live player** as the frontend (PHP render + Video.js), not a text-only placeholder. If no source is configured yet, a dashed hint box explains what to set. Subscription-locked videos show the locked state in the editor too.

Example: drag **MediaBlaster Player** onto the page, set **Current Post** on a single movie template, or **Select Post** with a specific video ID.

## Choosing a source

| Method | Best for |
|--------|----------|
| Post ID (shortcode `id`, block **Video Post ID**, Elementor **Select Post**) | Videos with MediaBlaster meta (`rovidx_smarttv_URL`, format, captions, BIF) |
| Direct URLs | External streams, landing pages without a video CPT post |
| Current post context | `[mediablaster_player]` with no `id` on a movie/episode singular template |

URL priority when overrides are set: `hls` → `dash` → `mp4_1080`–`360` → `url` → `mp4` → post meta. See [Source resolution](#source-resolution) below.

Enabled video post types depend on **MediaBlaster → General Settings** (movies, short videos, episodes). The block editor searches these types via AJAX; Elementor lists up to 50 recent published videos per type.

## Selecting a player skin

| Skin | Use case |
|------|----------|
| `default` | General site embeds |
| `streaming` | TV-style layouts |
| `creator` | Creator-focused pages |

Set via shortcode `skin="streaming"`, block **Player Display → Skin**, or Elementor **Player Display → Skin**.

## Adding captions

- **From post meta:** use default behavior (`captions="true"` on shortcode, or leave caption URL empty in block/widget).
- **Override with WebVTT:** `caption="https://…/en.vtt"` plus `caption_label` and `caption_lang`.
- **Subtitles track:** `subtitles` URL with `subtitles_label` / `subtitles_lang`.

## Adding chapters

- **Boolean (default):** `chapters="true"` loads chapter behavior from post meta when available.
- **WebVTT URL:** `chapters="https://…/chapters.vtt"` with optional `chapters_label` and `chapters_lang`.

Configure in the block/widget **Tracks** panel or shortcode attributes.

## Adding thumbnail/storyboard tracks

- **BIF from meta:** `thumbnails="true"` (default) loads `rovidx_smarttv_bif` when present.
- **WebVTT storyboard:** `thumbnails="https://…/thumbnails.vtt"`.

BIF scrubber UI on the progress bar is not implemented yet; metadata is stored for future use.

## Autoplay and muted behavior

Most browsers **block autoplay with sound**. For reliable autoplay, enable **Muted** as well as **Autoplay**.

The Gutenberg block shows a warning in the sidebar when autoplay is on and muted is off. Elementor includes the same guidance in control help text.

## Native fallback mode

Force native HTML5 (skip Video.js):

```
[mediablaster_player player_engine="native"]
```

Or set **Player engine → Native HTML5** in the block or Elementor widget. If Video.js is unavailable, the player falls back automatically.

## Basic examples

Current post context:

```
[mediablaster_player]
```

Specific video post:

```
[mediablaster_player id="123"]
```

Direct MP4 URL:

```
[mediablaster_player mp4="https://example.com/video.mp4"]
```

HLS stream:

```
[mediablaster_player hls="https://example.com/master.m3u8"]
```

DASH stream:

```
[mediablaster_player dash="https://example.com/manifest.mpd"]
```

Multiple MP4 qualities:

```
[mediablaster_player mp4_1080="https://example.com/video-1080.mp4" mp4_720="https://example.com/video-720.mp4" mp4_480="https://example.com/video-480.mp4"]
```

Poster override:

```
[mediablaster_player id="123" poster="https://example.com/poster.jpg"]
```

Autoplay (requires muted for most browsers):

```
[mediablaster_player id="123" autoplay="true" muted="true"]
```

Vertical video:

```
[mediablaster_player id="123" aspect_ratio="9:16"]
```

Captions (WebVTT):

```
[mediablaster_player caption="https://example.com/captions.vtt" caption_label="English" caption_lang="en"]
```

Subtitles:

```
[mediablaster_player subtitles="https://example.com/es.vtt" subtitles_label="Spanish" subtitles_lang="es"]
```

Chapters (WebVTT):

```
[mediablaster_player chapters="https://example.com/chapters.vtt" chapters_label="Chapters" chapters_lang="en"]
```

Thumbnail/storyboard VTT:

```
[mediablaster_player thumbnails="https://example.com/thumbnails.vtt"]
```

Legacy BIF trickplay URL (stored for future scrubber UI):

```
[tv-video-player bif="https://example.com/storyboard.bif"]
```

Player skins:

```
[mediablaster_player skin="streaming"]
[mediablaster_player skin="creator"]
```

Native HTML5 fallback (skip Video.js):

```
[mediablaster_player player_engine="native"]
```

## Player engine

Default engine: **videojs**.

- Video.js loads only when a player is rendered on the page (not globally).
- If bundled Video.js files are missing, or another plugin already registered Video.js, the player adapts automatically.
- If Video.js is unavailable, playback falls back to native HTML5 without breaking the page.

Bundled assets live at `public/vendor/videojs/` inside the plugin. See that folder’s README for upgrade instructions.

## Supported attributes

| Attribute | Default | Description |
|-----------|---------|-------------|
| `id` | current post | Video post ID |
| `player_engine` | `videojs` | `videojs` or `native` |
| `hls` | — | HLS master URL (`.m3u8`) |
| `dash` | — | DASH manifest URL (`.mpd`) |
| `mp4_1080` | — | 1080p MP4 URL |
| `mp4_720` | — | 720p MP4 URL |
| `mp4_480` | — | 480p MP4 URL |
| `mp4_360` | — | 360p MP4 URL |
| `url` | — | Generic video URL override |
| `mp4` | — | MP4 URL override |
| `type` | auto | Force format: `hls`, `mp4`, `dash`, etc. |
| `poster` | featured image | Poster image URL |
| `max_width` | `1920px` | CSS max-width |
| `aspect_ratio` | `16:9` | `16:9`, `4:3`, `1:1`, `9:16`, `21:9` |
| `skin` | `default` | `default`, `streaming`, `creator` |
| `controls` | `true` | Show player controls |
| `preload` | `metadata` | `none`, `metadata`, `auto` |
| `autoplay` | `false` | Autoplay when allowed |
| `loop` | `false` | Loop playback |
| `muted` | `false` | Mute audio |
| `playsinline` | `true` | Mobile inline playback |
| `start_time` | `0` | Seek to seconds on load |
| `class` | — | Extra CSS classes |
| `bif` | — | Legacy BIF URL or VTT storyboard alias |
| `captions` | `true` | Load caption tracks from post meta |
| `caption` | — | Single captions VTT URL |
| `caption_label` | English | Captions track label |
| `caption_lang` | `en` | Captions track language |
| `subtitles` | — | Single subtitles VTT URL |
| `subtitles_label` | English | Subtitles track label |
| `subtitles_lang` | `en` | Subtitles track language |
| `captions_json` | — | JSON array of multiple caption/subtitle tracks |
| `chapters` | `true` | Boolean flag, or chapters VTT URL |
| `chapters_label` | Chapters | Chapters track label |
| `chapters_lang` | `en` | Chapters track language |
| `thumbnails` | `true` | Boolean flag for BIF meta, or thumbnails VTT URL |
| `thumbnail_label` | Thumbnails | Thumbnail metadata track label |
| `resume` | `false` | Reserved for future resume watching |
| `autoplay_next` | `false` | Reserved for future playlist autoplay |
| `locked_behavior` | `overlay` | `overlay` or `message` when access denied |

### Dual-mode attributes

- **`thumbnails`**: `true`/`false` controls BIF meta loading; a URL value sets a WebVTT metadata track.
- **`chapters`**: `true`/`false` sets the chapters feature flag; a URL value adds a WebVTT chapters track.
- **`captions`**: `false` disables all caption/subtitle output including post meta.

## Source resolution

URL priority:

1. Shortcode `hls`
2. Shortcode `dash`
3. Shortcode `mp4_1080`, `mp4_720`, `mp4_480`, `mp4_360`
4. Shortcode `url`
5. Shortcode `mp4`
6. Post meta `rovidx_smarttv_URL`
7. Fire Creator add-on MP4 meta (when active)

Format detection uses explicit args, post meta `rovidx_smarttv_format`, or URL extension (`.m3u8`, `.mpd`, `.mp4`).

For HLS (`.m3u8`), the player follows master-playlist redirects server-side before rendering (common for Vimeo and other CDNs). This ensures variant playlists and segments load from the CDN origin with proper CORS instead of failing with a perpetual spinner.

Captions load from post meta `rovidx_smarttv_cc` when `captions="true"`.

BIF trickplay loads from post meta `rovidx_smarttv_bif` when `thumbnails="true"` (boolean mode).

## Subscription access

When subscriptions are enabled, the player checks entitlements before outputting playable sources.

- **Show locked** — Branded overlay with poster; no video URL in markup
- **Hide completely** — Empty output (no player rendered)
- Use `locked_behavior="message"` for legacy plain-text locked message

See [Subscription Access Metabox](metabox-subscription-access.md).

## Headless configuration

For apps or custom PHP integrations, use:

```php
$config = Mediablaster_Player::get_config( $post_id, $args );
$html   = Mediablaster_Player::render( $post_id, Mediablaster_Player::normalize_args( $args ) );
```

`get_config()` returns a sanitized array (sources, tracks, poster, engine, locked state, etc.) without HTML. Premium sources are omitted when access is denied.

`normalize_args()` maps block/Elementor keys (`postId`, `className`) to player args and applies the same sanitization as shortcodes.

## Developer hooks

```php
apply_filters( 'mediablaster_player_args', $args, $post_id );
apply_filters( 'mediablaster_player_engine', $engine, $post_id, $args );
apply_filters( 'mediablaster_player_use_videojs', $use_videojs, $post_id, $args );
apply_filters( 'mediablaster_player_source_args', $source_args, $post_id, $args );
apply_filters( 'mediablaster_player_hls_url', $url );
apply_filters( 'mediablaster_player_resolved_hls_url', $resolved, $original_url );
apply_filters( 'mediablaster_player_sources', $sources, $post_id, $args );
apply_filters( 'mediablaster_player_poster', $poster, $post_id, $args );
apply_filters( 'mediablaster_player_caption_tracks', $tracks, $post_id, $args );
apply_filters( 'mediablaster_player_tracks', $tracks, $post_id, $args );
apply_filters( 'mediablaster_player_thumbnail_track', $track, $post_id, $args );
apply_filters( 'mediablaster_player_resolved', $resolved, $post_id, $args );
apply_filters( 'mediablaster_player_config', $config, $post_id, $args );
apply_filters( 'mediablaster_player_markup', $html, $resolved, $args );
apply_filters( 'mediablaster_player_locked_title', $title, $post_id, $args );
apply_filters( 'mediablaster_player_locked_message', $message, $post_id, $args );
apply_filters( 'mediablaster_player_locked_button_url', $url, $post_id, $args );
apply_filters( 'mediablaster_player_locked_button_text', $text, $post_id, $args );
apply_filters( 'rovidx_wpstv_player', $html, $args ); // legacy
apply_filters( 'rovidx_wpstv_video_url', $url, $post_id ); // legacy
```

Actions:

```php
do_action( 'mediablaster_player_rendered', $resolved, $args );
do_action( 'mediablaster_player_enqueue_assets' );
```

## JavaScript events

The player dispatches custom events on the wrapper element:

- `mediablaster:player-ready`
- `mediablaster:play`
- `mediablaster:pause`
- `mediablaster:ended`
- `mediablaster:error`
- `mediablaster:timeupdate`
- `mediablaster:loadedmetadata`

Event detail:

```js
{
  postId,
  engine,      // "videojs" or "native"
  wrapper,
  video,
  player,      // Video.js instance or wrapper
  currentTime,
  duration
}
```

## Troubleshooting

### Block shows poster but no play button or controls (shortcode works)

**Typical signs:** HLS or segments may load in the Network tab; no JavaScript errors; Video.js poster visible; no big play icon or control bar.

**Cause:** Video.js can end up in `vjs-controls-disabled` when ingesting a `<video class="video-js">` element without a native `controls` attribute, even when `data-controls="true"` is on the wrapper.

**Check in browser DevTools:**

```js
const w = document.querySelector('[data-mediablaster-player]');
const vjs = w?.querySelector('.video-js');
console.log({
  dataControls: w?.getAttribute('data-controls'),
  controlsDisabled: vjs?.classList.contains('vjs-controls-disabled'),
  bigPlayDisplay: getComputedStyle(w.querySelector('.vjs-big-play-button')).display
});
```

**Expect when healthy (paused):** `dataControls` is `"true"`, `controlsDisabled` is `false`, `bigPlayDisplay` is `"block"`.

**Fix in plugin:** `public/js/mediablaster-player.js` must call `player.controls(true)` after Video.js `ready` when controls are enabled (`applyVideoJsControls`). Do not remove this when refactoring init.

**Also verify:** hard-refresh the page so `mediablaster-player.js` is not cached; block has a valid **Video Post ID**; compare with `[mediablaster_player id="SAME_ID"]` on a test page.

### Block does not play but Network shows HLS requests

1. Confirm **Video Post ID** points to a published video with `rovidx_smarttv_URL` (or overrides in manual URL mode).
2. Do not rely on stale HLS URLs saved in block JSON for post-backed videos — playback uses post meta, not expired sidebar URLs.
3. See [Video.js controls](#block-shows-poster-but-no-play-button-or-controls-shortcode-works) above if the stream loads but the UI is missing.

### Player assets missing (no init at all)

- Dynamic block renders after `wp_head`; assets enqueue via `has_block()`, `view_script_handles`, and `render_block` → `Mediablaster_Player_Assets::enqueue()`.
- Scripts: bundled `video.min.js` then `mediablaster-player.js` (dependency order matters).
- Init retries until `window.videojs` exists and runs again on `window.load` for deferred block scripts.

### Editor: Controls toggle looks off for new blocks

The block defaults **Controls** to on. If the sidebar showed Controls off before saving, older posts may have `controls: false` in block JSON — re-enable **Controls** under **Playback Options** and update the post.

### Subscription locked

No playable `<source>` in HTML when access is denied — expected. See [Subscription access](#subscription-access).

## Implementation notes (developers)

| Topic | Rule |
|-------|------|
| Single renderer | Block/Elementor must use `Mediablaster_Player::render()` — no parallel player logic in `blocks/player/index.js` on the frontend |
| Post-backed URLs | `Mediablaster_Player_Integration::args_from_block_attributes()` strips playback URL overrides when `postId > 0` |
| Block booleans | Only pass `controls`, `autoplay`, etc. when set in block attributes; never `null` placeholders |
| `parse_bool` | Empty string is false; use defaults in `sanitize_args()` for null/missing bool keys |
| Video.js UI | Always `applyVideoJsControls()` after init/`ready` when `data-controls="true"` |

Cursor skill for agents: **`wp-smart-tv-player`** (`.cursor/skills/wp-smart-tv-player/SKILL.md`).

## Not implemented yet

Do not expect these yet:

- BIF scrubber UI on the progress bar
- Resume watching / watch progress storage
- Continue Watching shelf
- Analytics dashboard
- Autoplay next episode logic
- Chromecast / AirPlay / VAST ads
- Full REST player config endpoint (use `get_config()` in PHP for now)

## Related guides

- [Shortcodes](shortcodes.md)
- [Podcast Audio Player](audio-player.md) — separate player for `podcast_episode` audio (not Video.js)
- [Video Data metabox](metabox-video-data.md)
- [Closed Captions & Trickplay](metabox-captions-trickplay-ads.md)
