Install
$ agentstack add skill-mikemai2awesome-agent-skills-brightcove-player ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →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:
The ` element's id attribute (myPlayer, heroPlayer, etc.) is separate — it's just a DOM handle for videojs.getPlayer()`:
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.
/* 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
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.
.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.
: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:
: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:
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:
: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:
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:
.video-js .vjs-control-bar {
-webkit-backdrop-filter: blur(12px) !important;
backdrop-filter: blur(12px) !important;
}
Via JS:
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:
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
- Source: mikemai2awesome/agent-skills
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.