# Brightcove Player

> Style and fully customize the Brightcove video player UI — control bar, play button, progress bar, volume, captions, playlists, responsive sizing, and skins. Use this skill whenever the user mentions Brightcove, video-js player styling, customizing a Brightcove player, changing player colors/layout/controls, embedding a Brightcove player, making it responsive, player skins or themes, Brightcove S…

- **Type:** Skill
- **Install:** `agentstack add skill-mikemai2awesome-agent-skills-brightcove-player`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [mikemai2awesome](https://agentstack.voostack.com/s/mikemai2awesome)
- **Installs:** 0
- **Category:** [AI & ML](https://agentstack.voostack.com/c/ai-and-ml)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [mikemai2awesome](https://github.com/mikemai2awesome)
- **Source:** https://github.com/mikemai2awesome/agent-skills/tree/main/skills/brightcove-player

## Install

```sh
agentstack add skill-mikemai2awesome-agent-skills-brightcove-player
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# Brightcove Player Customization

Brightcove players are built on Video.js. Every visual element is targetable via `.vjs-*` CSS classes. The tricky parts are specificity (the player ships with its own stylesheet) and the iframe vs. in-page embed split (iframe players block inline CSS entirely).

**Always use physical CSS properties** (`width`, `height`, `max-width`, `top`, `left`, etc.) — never logical properties (`inline-size`, `block-size`, `inset-inline-start`, etc.). Video.js itself uses physical properties throughout, and mixing logical properties into overrides creates inconsistency and can cause specificity surprises.

## Player script URL and ID terminology

Brightcove uses three distinct IDs that are easy to confuse:

| Term                 | What it is                                                         | Example               |
| -------------------- | ------------------------------------------------------------------ | --------------------- |
| **Account ID**       | Numeric Brightcove account identifier                              | `1752604059001`       |
| **Player config ID** | The player configuration created in Studio                         | `default` (or a UUID) |
| **HTML element id**  | The `id` attribute on `` — used by `videojs.getPlayer()` | `myPlayer`            |

The CDN script URL is built from the **account ID** and **player config ID** — not the HTML element id:

```
https://players.brightcove.net/{account_id}/{player_config_id}_default/index.min.js
```

So if the account is `1752604059001` and the Studio player config is named `default`, the script URL is:

```html

```

The `` element's `id` attribute (`myPlayer`, `heroPlayer`, etc.) is separate — it's just a DOM handle for `videojs.getPlayer()`:

```html

```

When a user says "player id myPlayer", they almost always mean the element id, not a Studio player config named `myPlayer`. If no Brightcove player config ID is specified, default to `data-player="default"` and the `default_default` script URL.

**Demo / preview pages** — when producing a self-contained demo HTML file, always use these known-good values so the player actually loads:

- Account: `1752604059001`
- Player config: `default`
- Video: `4825279519001`
- Script: `https://players.brightcove.net/1752604059001/default_default/index.min.js`

Never leave placeholder text like `ACCOUNT_ID` or `PLAYER_ID` in a demo file — it will produce a blank page with console errors.

---

## Embed type — decide first

| Embed type             | Where CSS lives                                    | JS access |
| ---------------------- | -------------------------------------------------- | --------- |
| **Advanced (in-page)** | `` tag on the page OR Studio stylesheet     | Full      |
| **Standard (iframe)**  | Studio stylesheet only — page `` won't work | Limited   |

For iframe players, upload a CSS file to a public URL and add it in **Studio → Players → Plugins → Stylesheets**, then republish.

For in-page embeds, a `` block on the same page is the fastest approach.

---

## Beating Brightcove's stylesheet

The player's own stylesheet is loaded late and carries high specificity. The recommended approach is to use both techniques together:

**Unnamed cascade layer** — CSS layers declared with a name come before unnamed layers. Putting your overrides in an unnamed `@layer` block makes them beat everything, including Brightcove's injected stylesheet.

**`!important`** — even inside an unnamed layer, add `!important` on every property. Brightcove occasionally injects inline styles at runtime, and only `!important` beats those.

```css
/* Named layers declared first — unnamed layer implicitly ranks above all of them */
@layer config, resets, components;

/* Unnamed layer + !important: covers both the stylesheet and runtime inline styles */
@layer {
  .c-player .video-js .vjs-big-play-button {
    background-color: var(--videojs-play-btn-bg) !important;
  }
}
```

---

## Wrapper element pattern

Wrap `` in a container element rather than styling it from the page root. This gives you:

- An easy way to control player width and aspect ratio
- A container query root scoped to the player width (not viewport width)
- A clean specificity bump via class nesting

```html

  

```

**Always add `skin="false"`** on the `` element when doing a custom skin. It disables Brightcove's default skin stylesheet, giving you a clean baseline with far fewer specificity fights.

```css
.c-player {
  width: 100%;
  max-width: 56rem;
  aspect-ratio: 16 / 9;

  .video-js {
    width: 100% !important;
    height: 100% !important;
    /* Declare a container for container queries scoped to player width */
    container: video / inline-size !important;
  }
}
```

---

## Design tokens

Define all tokens on `:root` (or `.c-player` if scoping tightly). Use `--videojs-*` prefix to keep player tokens distinct from page tokens.

```css
:root {
  --videojs-fg: oklch(10% 0.01 250);
  --videojs-fg-subtle: oklch(45% 0 0 / 0.85);
  --videojs-bg-accent: oklch(49% 0.14 250);
  --videojs-bg-accent-hover: color-mix(
    in oklch,
    var(--videojs-bg-accent),
    black 10%
  );
  --videojs-bg-control: oklch(100% 0 0 / 0.97);
  --videojs-bg-progress-holder: oklch(0% 0 0 / 0.14);
  --videojs-bg-progress-play: oklch(100% 0 0 / 0.12);
  --videojs-border: oklch(80% 0 0 / 0.6);
  --videojs-border-subtle: oklch(80% 0 0 / 0.4);
}
```

Use OKLCH — perceptually uniform, so adjusting lightness for variants is predictable. Use `color-mix(in oklch, var(--base-color), black 10%)` rather than hard-coding separate values.

---

## Light / dark theming

Use the `color-scheme` property and the `light-dark()` CSS function to switch token values based on system preference:

```css
:root {
  color-scheme: light dark; /* enables system preference detection */

  --videojs-bg-accent: light-dark(oklch(49% 0.14 250), oklch(56% 0.16 250));
  --videojs-bg-control: light-dark(
    oklch(100% 0 0 / 0.97),
    oklch(22% 0.064 259 / 0.97)
  );
}

/* Override for explicit theme choice */
:root[data-theme="light"] {
  color-scheme: light;
}
:root[data-theme="dark"] {
  color-scheme: dark;
}
```

**Always initialize `data-theme` from `prefers-color-scheme` on page load** so the explicit toggle starts in sync with the system preference — otherwise users on dark OS get a light flash before JS runs:

```javascript
function setTheme(value) {
  document.documentElement.dataset.theme = value;
  /* update any toggle buttons with aria-pressed here */
}

/* Read system preference and set immediately */
setTheme(
  window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light",
);
```

---

## Play button

| Selector                                                      | Targets                                                      |
| ------------------------------------------------------------- | ------------------------------------------------------------ |
| `.video-js .vjs-big-play-button`                              | Button container (size, shape, background, border, position) |
| `.video-js .vjs-big-play-button .vjs-icon-placeholder`        | Play icon inside the button                                  |
| `.video-js .vjs-big-play-button .vjs-icon-placeholder:before` | Icon glyph (font-size, color)                                |
| `.video-js:hover .vjs-big-play-button`                        | Button on player hover                                       |
| `.video-js .vjs-big-play-button:hover`                        | Button on direct hover                                       |
| `.video-js .vjs-big-play-button:focus`                        | Button on focus                                              |
| `.video-js.vjs-mouse .vjs-big-play-button`                    | Button during mouse interaction                              |
| `#myPlayerID .vjs-big-play-button`                            | Player-specific override (highest specificity)               |

Centering reliably: use `top: 50%; left: 50%; transform: translate(-50%, -50%)` and `margin: 0` — the default margin-based offset from Video.js doesn't account for custom button sizes.

**Fluid sizing with `clamp()` and `vi` units** — use viewport-inline units so the button scales with the player width rather than staying fixed. Always do this instead of a static rem value:

```css
:root {
  --videojs-big-btn: clamp(2rem, 8vi, 4.5rem);
  --videojs-big-btn-icon-size: clamp(1rem, 6vi, 2rem);
}

.video-js .vjs-big-play-button {
  width: var(--videojs-big-btn) !important;
  height: var(--videojs-big-btn) !important;
  font-size: var(--videojs-big-btn-icon-size) !important;
  line-height: var(--videojs-big-btn) !important;
}
```

Change accessible label text:

```javascript
videojs.getPlayer("myPlayer").ready(function () {
  this.getChild("bigPlayButton").controlText("Watch video");
});
```

---

## Control bar

| Selector                                                             | Targets                                                       |
| -------------------------------------------------------------------- | ------------------------------------------------------------- |
| `.video-js .vjs-control-bar`                                         | Bar container (background, height, padding)                   |
| `.video-js .vjs-control-bar *`                                       | All descendants — useful for resetting `text-shadow` globally |
| `.video-js .vjs-control-bar .vjs-control`                            | Individual control items (color, spacing)                     |
| `.video-js .vjs-control-bar .vjs-button`                             | Button elements                                               |
| `.video-js .vjs-control-bar .vjs-button:hover`                       | Button hover state                                            |
| `.video-js:not(.vjs-has-started) .vjs-control-bar`                   | Bar before playback has started (opacity, pointer-events)     |
| `.video-js.vjs-quality-menu .vjs-quality-menu-button-HD-flag::after` | HD quality badge                                              |

Video.js ships with a `text-shadow` on control icons — reset it explicitly via `.vjs-control-bar, .vjs-control-bar *, .vjs-menu *` if your design doesn't use it.

`backdrop-filter` requires a vendor prefix for Safari — always pair them:

```css
.video-js .vjs-control-bar {
  -webkit-backdrop-filter: blur(12px) !important;
  backdrop-filter: blur(12px) !important;
}
```

Via JS:

```javascript
videojs.getPlayer("myPlayer").ready(function () {
  this.controlBar.hide(); /* or .show() */
});
```

---

## Progress / seek bar

| Selector                                                     | Targets                                                                                                         |
| ------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------- |
| `.video-js .vjs-progress-control`                            | Outer wrapper — set `align-items: flex-end` to anchor the bar to the bottom edge as its hit area grows on hover |
| `.video-js .vjs-progress-holder`                             | Track container (height, transition)                                                                            |
| `.video-js .vjs-progress-control:hover .vjs-progress-holder` | Expanded track height on hover                                                                                  |
| `.video-js .vjs-play-progress`                               | Played portion (color)                                                                                          |
| `.video-js .vjs-play-progress::before`                       | Play head dot (font-size to shrink, top to re-center)                                                           |
| `.video-js .vjs-load-progress`                               | Buffered portion                                                                                                |
| `.video-js .vjs-load-progress div`                           | Buffered sub-segments                                                                                           |
| `.video-js .vjs-slider-bar`                                  | Unplayed track background                                                                                       |
| `.video-js .vjs-progress-holder.vjs-slider`                  | Slider track background                                                                                         |

---

## Volume

| Selector                                                                    | Targets                                                              |
| --------------------------------------------------------------------------- | -------------------------------------------------------------------- |
| `.video-js .vjs-volume-panel`                                               | Panel wrapper — controls expand/collapse width and transition timing |
| `.video-js .vjs-volume-panel:hover`                                         | Expanded state on hover                                              |
| `.video-js .vjs-volume-panel.vjs-hover`                                     | Expanded via keyboard/focus                                          |
| `.video-js .vjs-volume-panel.vjs-slider-active`                             | Active while scrubbing                                               |
| `.video-js .vjs-volume-control`                                             | Inner slider control (width, visibility transition)                  |
| `.video-js .vjs-volume-control.vjs-volume-control-horizontal`               | Horizontal layout alignment                                          |
| `.video-js .vjs-volume-level`                                               | Filled volume bar (color)                                            |
| `.video-js .vjs-volume-level::before`                                       | Volume thumb dot (color)                                             |
| `.video-js .vjs-volume-bar.vjs-slider-bar.vjs-slider.vjs-slider-horizontal` | Unfilled track (background)                                          |

For the horizontal inline panel, use asymmetric transition delays — open fast (no delay), close with a delay so the cursor can escape without the panel collapsing mid-move.

Switch to vertical volume via JS options:

```javascript
bc("myPlayer", {
  controlBar: {
    volumePanel: { inline: false, vertical: true },
  },
});
```

---

## Time display & tooltip

| Selector                        | Targets                                                                       |
| ------------------------------- | ----------------------------------------------------------------------------- |
| `.video-js .vjs-time-control`   | Base wrapper (hidden by default; use container query to show at wider widths) |
| `.video-js .vjs-current-time`   | Current time value                                                            |
| `.video-js .vjs-duration`       | Total duration value                                                          |
| `.video-js .vjs-time-divider`   | Separator between current time and duration                                   |
| `.video-js .vjs-remaining-time` | Time remaining (hide when current + duration are both shown)                  |
| `.video-js .vjs-time-tooltip`   | Seek position tooltip that appears on progress bar hover                      |

Container queries require `container: video / inline-size` on `.video-js` (set in the wrapper section above).

---

## Duration badge (pre-play overlay)

A custom element injected into the player to show total duration before playback, then hidden on play. Scope it with `@container video (inline-size >= 24rem)` so it only appears when the player is wide enough. Use a `has-played` class on `.v

…

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [mikemai2awesome](https://github.com/mikemai2awesome)
- **Source:** [mikemai2awesome/agent-skills](https://github.com/mikemai2awesome/agent-skills)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-mikemai2awesome-agent-skills-brightcove-player
- Seller: https://agentstack.voostack.com/s/mikemai2awesome
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
