Skip to content

feat(core): add media tracks and renditions support#1664

Open
luwes wants to merge 2 commits into
refactor/media-host-contractfrom
feat/media-track-capabilities
Open

feat(core): add media tracks and renditions support#1664
luwes wants to merge 2 commits into
refactor/media-host-contractfrom
feat/media-track-capabilities

Conversation

@luwes

@luwes luwes commented Jun 5, 2026

Copy link
Copy Markdown
Collaborator

Adds support for audioTracks and videoRenditions to the HLS and Mux media.


Add audio/video track and rendition capabilities to the media layer so engines can expose multivariant audio/video tracks and their underlying renditions through a consistent, framework-agnostic contract.

Changes

  • Track/rendition capability interfaces (AudioTrackLike, VideoTrackLike, AudioRenditionLike, VideoRenditionLike, and their lists/capabilities) in the core media contract
  • MediaTracksMixin providing the track/rendition lists, composed into HlsJsMedia
  • HLS wiring: HlsJsMediaMediaTracksMixin populates tracks/renditions, and the HlsMedia delegate forwards videoTracks/audioTracks/videoRenditions/audioRenditions
  • Fix media-attach disconnect ordering so the store detaches and features (e.g. remote-playback) unwind against the live element before the host is destroyed

Testing

pnpm -F @videojs/core test — covered by new tests for the tracks mixin, HLS media tracks, and remote-playback teardown.


Note

Medium Risk
Touches core playback selection (HLS level/audio switching) and new public media APIs, but changes are isolated to track lists with dedicated tests and no security-sensitive paths.

Overview
Adds a framework-agnostic audio/video track and rendition layer so players can expose multivariant choices and quality levels through a single contract, not only through engine-specific APIs.

The PR introduces track/rendition types and list implementations (with addtrack / change style events), MediaTracksMixin to attach audioTracks, videoTracks, audioRenditions, and videoRenditions to media hosts (replacing or bridging native track getters where needed), and extends types.ts with the corresponding capability interfaces.

HLS (hls.js) is wired via HlsJsMediaMediaTracksMixin: manifest levels become video renditions on a selected video track, alternate audio becomes audioTracks, and user changes on those lists drive engine.nextLevel and engine.audioTrack. HlsMedia forwards the four properties only when the MSE delegate is HlsJsMedia. DashMedia composes MediaTracksMixin for the same host shape (engine-specific population is not in this diff).

Smaller fixes: MediaAttachMixin detaches the store before super.disconnectedCallback() so remote-playback cleanup still sees a live element; remote-playback uses optional cancelWatchAvailability on abort.

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

Add audio/video track and rendition capability interfaces with a
MediaTracksMixin, wire them through HlsJsMedia and the HlsMedia
delegate, and fix media-attach disconnect ordering so features unwind
before the host is destroyed.
@vercel

vercel Bot commented Jun 5, 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 5, 2026 8:38pm

Request Review

if (rendition.id && !levelIds.includes(rendition.id)) {
this.#currentVideoTrack.removeRendition(rendition);
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

LEVELS_UPDATED breaks rendition map

Medium Severity

#onLevelsUpdated decides which renditions to drop by looking up each incoming level in #levelIdMap, but that map is only populated during MANIFEST_PARSED. If LEVELS_UPDATED carries new level object references (typical when hls.js rebuilds its levels array), every lookup is undefined, so every rendition with an id is treated as stale and removed even though the levels still exist.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 0448d07. Configure here.

}

addVideoTrack(media, event.track as VideoTrack);
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Native tracks linger after custom add

Medium Severity

When native videoTracks/audioTracks are mirrored and the app adds a real VideoTrack/AudioTrack via addVideoTrack/addAudioTrack, mirrored native entries stay in the custom list until a native addtrack event fires. The cleanup that removes native entries runs only inside the native addtrack listener, not when the custom track is added.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 0448d07. Configure here.

Compose MediaTracksMixin into DashMedia so DASH playback exposes track capabilities.

Also guard remote.cancelWatchAvailability with optional chaining for engines lacking it.

@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 and found 2 potential issues.

There are 4 total unresolved issues (including 2 from previous reviews).

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 83dce08. Configure here.


queueMicrotask(() => {
trackList.dispatchEvent(new TrackEvent('removetrack', { track }));
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Spurious removetrack after failed delete

Medium Severity

removeVideoTrack and removeAudioTrack always queue a removetrack event even when the track was not in the list. Native removetrack forwarding and duplicate removes can emit bogus events, so listeners may react to tracks that were never removed.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 83dce08. Configure here.


nativeTracks.addEventListener('change', onChange);
nativeTracks.addEventListener('addtrack', onAddTrack);
nativeTracks.addEventListener('removetrack', onRemoveTrack);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Native track listeners never detached

Medium Severity

getVideoTracks / getAudioTracks register change, addtrack, and removetrack listeners on the native track list once, with no matching teardown on detach or destroy. After the host detaches, those handlers can keep firing and dispatch events on the synthetic lists.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 83dce08. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

1 participant