Skip to content

feat: cue points support#1734

Open
luwes wants to merge 17 commits into
mainfrom
feat/cue-points
Open

feat: cue points support#1734
luwes wants to merge 17 commits into
mainfrom
feat/cue-points

Conversation

@luwes

@luwes luwes commented Jun 24, 2026

Copy link
Copy Markdown
Collaborator

Refs #1442
Demo at https://vjs10-api-demo.netlify.app/

API

Open Questions

  • Should we add a more specialized media API? ala Mux player. media.addCuePoints(), media.activeCuePoint, media.cuePoints
  • We could also go another way and make it more generic, cue points are just a text track with kind metadata.
    Should API's reflect this?
media.config = {
	cuePoints: {
		// label: 'cuepoints', // optional to have multiple tracks
		cuePoints: [
			{"time":1,"value":"Simple Value"},
			{"time":3,"value":{"complex":"Complex Object","duration":2}},
			{"time":10,"value":true},
			{"time":15,"value":{"anything":"That can be serialized to JSON and makes sense for your use case"}}
		]
	}
}

media.addEventListener('cuepointchange', (event) => {
	const activeCuePoint = event.detail;
	console.log(activeCuePoint);
});

Summary

Add a cue points API for media elements: timed markers carrying JSON payloads, surfaced on a hidden metadata text track with a cuepointchange event as the playhead enters each cue.

Changes

  • CuePoints component in core (@videojs/core/dom/media/cue-points) — register/append cue points, read active cue point, subscribe via cuepointchange
  • Registered on HLS-backed media elements (HlsVideo, MuxVideo, MuxAudio) in both html and react
  • Scoped the HLS text tracks cleanup to caption/subtitle tracks so the cue points metadata track is no longer wiped when subtitles load
  • New api-demo Astro app exercising the cue points API and media state, with a dev:api-demo task
Implementation notes

Cue points are stored as VTTCues on a hidden metadata track. Since the engine clears cues on (re)load, the component re-populates on each loadstart. Adapted from muxinc/elements playback-core (MIT).

Testing

pnpm -F @videojs/core test — covered by new tests for the cue points component and the text tracks cleanup fix.


Note

Medium Risk
Introduces playback-adjacent media config and custom events across HLS players; risk is moderated by unit tests and a narrow change to subtitle track teardown.

Overview
Adds a cue points capability to the media stack: timed markers with JSON payloads are written to a hidden metadata text track, exposed via media.config.cuePoints, and announced with a cuepointchange event (active cue in event.detail). A new CuePoints core component handles reload/loadstart repopulation, duration-based end times, and addCuePoints.

HLS.js text-track cleanup now removes only caption/subtitle tracks marked data-removeondestroy, so the cue-points metadata track is not cleared when subtitles are discovered.

CuePoints is registered on HLS-backed players in @videojs/html and @videojs/react (HlsJsVideo, MuxVideo, MuxAudio).

A new @videojs/api-demo Astro app (plus root dev:api-demo) provides an interactive playground: HlsJsVideo, transport/secondary controls, cue-point add/remove, getter logging, URL-persisted state, and videojs.org-aligned styling.

Reviewed by Cursor Bugbot for commit 14d44dd. Bugbot is set up for automated code reviews on this repo. Configure here.

@netlify

netlify Bot commented Jun 24, 2026

Copy link
Copy Markdown

Deploy Preview for vjs10-site ready!

Name Link
🔨 Latest commit 14d44dd
🔍 Latest deploy log https://app.netlify.com/projects/vjs10-site/deploys/6a3eeef77c4bf0000744bd77
😎 Deploy Preview https://deploy-preview-1734--vjs10-site.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@vercel

vercel Bot commented Jun 24, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
v10-sandbox Ready Ready Preview, Comment Jun 26, 2026 9:28pm

Request Review

Comment thread packages/core/src/dom/media/cue-points/index.ts
Comment thread packages/core/src/dom/media/cue-points/index.ts
@github-actions

github-actions Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

📦 Bundle Size Report

🎨 @videojs/html

Path Base initial PR initial Diff % Lazy
/media/hlsjs-video 141.13 kB 141.93 kB +819 B +0.6% 🔺
/media/mux-audio 163.91 kB 164.50 kB +605 B +0.4% 🔺
/media/mux-video 163.68 kB 164.36 kB +698 B +0.4% 🔺
/video (default + hls) 183.97 kB 184.61 kB +655 B +0.3% 🔺
/video (minimal + hls) 183.58 kB 184.29 kB +733 B +0.4% 🔺
Presets (7)
Entry Initial
/video (default) 44.32 kB
/video (default + hls) 184.61 kB
/video (minimal) 43.98 kB
/video (minimal + hls) 184.29 kB
/audio (default) 37.83 kB
/audio (minimal) 36.61 kB
/background 4.20 kB
Media (10)
Entry Initial
/media/background-video 1.14 kB
/media/container 1.72 kB
/media/dash-video 214.74 kB
/media/hlsjs-video 141.93 kB
/media/mux-audio 164.50 kB
/media/mux-video 164.36 kB
/media/native-hls-video 9.05 kB
/media/simple-hls-audio-only 17.15 kB
/media/simple-hls-video 18.69 kB
/media/vimeo-video 12.31 kB
Players (5)
Entry Initial
/video/player 8.07 kB
/audio/player 5.38 kB
/background/player 3.92 kB
/live-video/player 7.64 kB
/live-audio/player 5.39 kB
Skins (30)
Entry Type Initial
/video/minimal-skin.css css 5.45 kB
/video/skin.css css 5.43 kB
/video/minimal-skin js 44.01 kB
/video/minimal-skin.tailwind js 44.58 kB
/video/skin js 44.28 kB
/video/skin.tailwind js 44.98 kB
/audio/minimal-skin.css css 3.60 kB
/audio/skin.css css 3.53 kB
/audio/minimal-skin js 36.62 kB
/audio/minimal-skin.tailwind js 37.04 kB
/audio/skin js 37.80 kB
/audio/skin.tailwind js 38.25 kB
/background/skin.css css 133 B
/background/skin js 1.14 kB
/live-video/minimal-skin.css css 5.45 kB
/live-video/skin.css css 5.43 kB
/live-video/minimal-skin js 43.12 kB
/live-video/minimal-skin.tailwind js 43.58 kB
/live-video/skin js 43.08 kB
/live-video/skin.tailwind js 43.58 kB
/live-audio/minimal-skin.css css 3.60 kB
/live-audio/skin.css css 3.53 kB
/live-audio/minimal-skin js 29.73 kB
/live-audio/minimal-skin.tailwind js 29.20 kB
/live-audio/skin js 31.04 kB
/live-audio/skin.tailwind js 30.66 kB
/global.css css 176 B
/shared.css css 88 B
/tailwind.css css 228 B
/skin-element js 1.44 kB
UI Components (38)
Entry Initial
/ui/airplay-button 2.29 kB
/ui/alert-dialog 2.45 kB
/ui/alert-dialog-close 2.15 kB
/ui/alert-dialog-description 2.18 kB
/ui/alert-dialog-title 2.17 kB
/ui/buffering-indicator 2.24 kB
/ui/captions-button 2.37 kB
/ui/captions-radio-group 2.73 kB
/ui/cast-button 2.25 kB
/ui/compounds 2.85 kB
/ui/controls 2.58 kB
/ui/error-dialog 2.61 kB
/ui/fullscreen-button 2.29 kB
/ui/hotkey 2.31 kB
/ui/menu 2.63 kB
/ui/mute-button 2.27 kB
/ui/pip-button 2.28 kB
/ui/play-button 2.28 kB
/ui/playback-rate-button 2.36 kB
/ui/playback-rate-radio-group 2.78 kB
/ui/popover 2.64 kB
/ui/poster 2.14 kB
/ui/quality-radio-group 2.75 kB
/ui/seek-button 2.27 kB
/ui/seek-indicator 2.29 kB
/ui/seek-indicator-value 465 B
/ui/slider 2.62 kB
/ui/status-announcer 2.28 kB
/ui/status-indicator 2.36 kB
/ui/status-indicator-value 467 B
/ui/thumbnail 2.09 kB
/ui/time 2.60 kB
/ui/time-slider 2.64 kB
/ui/tooltip 2.59 kB
/ui/volume-indicator 2.36 kB
/ui/volume-indicator-fill 410 B
/ui/volume-indicator-value 426 B
/ui/volume-slider 2.64 kB

Sizes are marginal over the root entry point.

⚛️ @videojs/react

Path Base initial PR initial Diff % Lazy
/media/hlsjs-video 139.64 kB 140.55 kB +931 B +0.7% 🔺
/media/mux-audio 162.26 kB 162.96 kB +717 B +0.4% 🔺
/media/mux-video 162.26 kB 163.01 kB +765 B +0.5% 🔺
/video (default + hls) 175.21 kB 175.87 kB +677 B +0.4% 🔺
/video (minimal + hls) 175.19 kB 175.78 kB +597 B +0.3% 🔺
Presets (7)
Entry Initial
/video (default) 36.83 kB
/video (default + hls) 175.87 kB
/video (minimal) 36.91 kB
/video (minimal + hls) 175.78 kB
/audio (default) 29.97 kB
/audio (minimal) 30.04 kB
/background 754 B
Media (9)
Entry Initial
/media/background-video 575 B
/media/dash-video 213.24 kB
/media/hlsjs-video 140.55 kB
/media/mux-audio 162.96 kB
/media/mux-video 163.01 kB
/media/native-hls-video 7.39 kB
/media/simple-hls-audio-only 15.56 kB
/media/simple-hls-video 17.15 kB
/media/vimeo-video 10.58 kB
Skins (27)
Entry Type Initial
/tailwind.css css 228 B
/video/minimal-skin.css css 5.37 kB
/video/skin.css css 5.34 kB
/video/minimal-skin js 36.81 kB
/video/minimal-skin.tailwind js 42.58 kB
/video/skin js 36.73 kB
/video/skin.tailwind js 42.51 kB
/audio/minimal-skin.css css 3.47 kB
/audio/skin.css css 3.39 kB
/audio/minimal-skin js 29.98 kB
/audio/minimal-skin.tailwind js 31.76 kB
/audio/skin js 29.91 kB
/audio/skin.tailwind js 33.75 kB
/background/skin.css css 90 B
/background/skin js 272 B
/live-video/minimal-skin.css css 5.37 kB
/live-video/skin.css css 5.34 kB
/live-video/minimal-skin js 32.56 kB
/live-video/minimal-skin.tailwind js 38.22 kB
/live-video/skin js 32.58 kB
/live-video/skin.tailwind js 38.24 kB
/live-audio/minimal-skin.css css 3.47 kB
/live-audio/skin.css css 3.39 kB
/live-audio/minimal-skin js 21.74 kB
/live-audio/minimal-skin.tailwind js 24.64 kB
/live-audio/skin js 21.78 kB
/live-audio/skin.tailwind js 24.78 kB
UI Components (32)
Entry Initial
/ui/airplay-button 2.23 kB
/ui/alert-dialog 2.20 kB
/ui/buffering-indicator 2.10 kB
/ui/captions-button 2.16 kB
/ui/captions-radio-group 2.09 kB
/ui/cast-button 2.19 kB
/ui/controls 2.05 kB
/ui/error-dialog 2.24 kB
/ui/fullscreen-button 2.26 kB
/ui/gesture 2.25 kB
/ui/hotkey 2.27 kB
/ui/live-button 2.12 kB
/ui/menu 2.43 kB
/ui/mute-button 2.24 kB
/ui/pip-button 2.26 kB
/ui/play-button 2.19 kB
/ui/playback-rate 2.06 kB
/ui/playback-rate-button 2.25 kB
/ui/popover 2.64 kB
/ui/poster 2.09 kB
/ui/quality 2.10 kB
/ui/seek-button 2.20 kB
/ui/seek-indicator 2.19 kB
/ui/slider 2.26 kB
/ui/status-announcer 2.11 kB
/ui/status-indicator 2.15 kB
/ui/thumbnail 2.00 kB
/ui/time 2.05 kB
/ui/time-slider 2.29 kB
/ui/tooltip 2.59 kB
/ui/volume-indicator 2.18 kB
/ui/volume-slider 2.30 kB

Sizes are marginal over the root entry point.

🧩 @videojs/core

Path Base initial PR initial Diff % Lazy
/dom/media/cue-points 1.08 kB 🆕
Entries (15)
Entry Initial
. 9.08 kB
/dom 17.02 kB
/dom/media/cue-points 1.08 kB
/dom/media/custom-media-element 2.09 kB
/dom/media/dash 209.07 kB
/dom/media/google-cast 4.04 kB
/dom/media/hls-js 135.70 kB
/dom/media/media-host 1.25 kB
/dom/media/media-played-ranges 576 B
/dom/media/mux 151.26 kB
/dom/media/native-hls 3.05 kB
/dom/media/simple-hls 16.47 kB
/dom/media/simple-hls-audio-only 14.92 kB
/dom/media/vimeo 9.86 kB
/media/predicate 573 B
🏷️ @videojs/element — no changes
Entries (2)
Entry Initial
. 996 B
/context 943 B
📦 @videojs/store — no changes
Entries (3)
Entry Initial
. 1.39 kB
/html 696 B
/react 360 B
🔧 @videojs/utils — no changes
Entries (10)
Entry Initial
/array 104 B
/dom 2.26 kB
/events 319 B
/function 327 B
/object 275 B
/predicate 265 B
/string 231 B
/style 190 B
/time 478 B
/number 158 B
📦 @videojs/spf — no changes
Entries (4)
Entry Initial
. 4.45 kB
/dom 6.33 kB
/hls 15.37 kB
/background-video 12.85 kB

ℹ️ How to interpret

JS sizes are initial static graph totals (minified + brotli). Lazy dynamic chunks are shown separately when present.

Icon Meaning
No change
🔺 Increased ≤ 10%
🔴 Increased > 10%
🔽 Decreased
🆕 New (no baseline)

Run pnpm size locally to check current initial sizes.

Comment thread packages/core/src/dom/media/cue-points/index.ts
@netlify

netlify Bot commented Jun 24, 2026

Copy link
Copy Markdown

Deploy Preview for vjs10-api-demo ready!

Name Link
🔨 Latest commit 14d44dd
🔍 Latest deploy log https://app.netlify.com/projects/vjs10-api-demo/deploys/6a3eeef7ef8a0a0008d9d213
😎 Deploy Preview https://deploy-preview-1734--vjs10-api-demo.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

Comment thread packages/core/src/dom/media/cue-points/index.ts
Comment thread packages/core/src/dom/media/cue-points/index.ts Outdated
Comment thread apps/api-demo/src/components/player-demo/track-selects.tsx
Comment thread packages/core/src/dom/media/cue-points/index.ts
Comment thread packages/core/src/dom/media/cue-points/index.ts Outdated

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 6cb2780. Configure here.

Comment thread packages/core/src/dom/media/cue-points/index.ts Outdated
Comment thread packages/core/src/dom/media/cue-points/index.ts
luwes added 7 commits June 26, 2026 14:12
Stores cue points as VTTCues on a hidden metadata text track and dispatches
a cuepointchange event as the playhead enters each. Adapted from
muxinc/elements playback-core.

Refs #1442
The HLS text tracks mixin removed every removable track when subtitles were
found, wiping the cue points metadata track owned by another component. Scope
the cleanup to caption/subtitle tracks only.

Refs #1442
Astro + React playground demonstrating the cue points API and media element
state/getters. Adds a dev:api-demo turbo task.

Refs #1442
System/light/dark theme toggle matching the site, with class-based dark mode.
Tracks buffered, logs progress with loaded %, restores loop/preload from params.
luwes added 9 commits June 26, 2026 14:12
Sticky player, TE-style key controls, tally-bar sliders, cue-point table, and a preload control.
addCuePoints rewrites the full set via setup, inheriting its abort guard.
Append-after-last now shortens the prior cue to avoid overlap.
The cuePoints getter now returns #cuePoints while the track is missing or unpopulated.
Explicit endTimes are clamped to the next cue's start so a long marker can't overlap a follower.
Sync the trailing cue's sentinel end to the real duration on durationchange.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants