Skip to content

feat(spf): expose media tracks on the SPF media adapter - #1826

Merged
spuppo-mux merged 9 commits into
videojs:mainfrom
spuppo-mux:feat/spf-adapter-media-tracks
Jul 30, 2026
Merged

feat(spf): expose media tracks on the SPF media adapter#1826
spuppo-mux merged 9 commits into
videojs:mainfrom
spuppo-mux:feat/spf-adapter-media-tracks

Conversation

@spuppo-mux

@spuppo-mux spuppo-mux commented Jul 13, 2026

Copy link
Copy Markdown
Collaborator

Part 1 of #1795 (video+audio → streamType → live → parity/autoplay).

What: SimpleHlsMedia now projects the SPF engine's video renditions and audio tracks onto the standard videoTracks / videoRenditions / audioTracks DOM surface (read, write, events) so a quality menu binds to it with no adapter-specific code, matching HlsJsMedia.

How:

  • SPF - New @videojs/spf/media-tracks module Provides mapping from SPF Media Tracks (AudioTrack and VideoTrack lists) into MediaTrack API model.

    • dedupedAudioTracks and dedupedVideoTracks- the distinct tracks of a presentation, deduped by properties (video: width+height+bandwidth; audio: language+name).
    • toUserAudioTrackSelection and toUserVideoTrackSelection - build the user*TrackSelection partials the engine's track-switching matches on.
    • isSameVideoTrack and isSameAudioTrack - compare two tracks by their dedupe key.
    • frameRateToNumber, plus the VideoDedupeKey / AudioDedupeKey shapes.
      Mainly deduped lists of tracks grouped by certain keys. These same keys are used to create userAudioTrackSelection and userVideoTrackSelection using the existing SPF logic. Also provides a utility function to translate from the FrameRate model to a number (as used in API). This module uses SPF model and is purposely DOM-free.
  • Core - A projection mixin (SimpleHlsMediaMediaTracksMixin):

    • subscribes to composed signals (which map the presentation to it's deduped track lists),
    • builds one selected main video track + a VideoRendition per item obtained from dedupedVideoTracks and one audioTrack per item in dedupedAudioTracks, reflecting engine selection.
    • and writes user selection back to engine by setting userVideoTrackSelection and audioTrackSelection using toUser*TrackSelection functions.

Design decisions:

  • Stuck to the "no SPF -> core dependency" rule.
    • Ideally this wouldn't belong to @videojs/core, but to do that we would need an independant @videojs/media-track package. Therefore, the mixin is added to core's dom/media/simple-hls which depends on spf/media-tracks and spf/hls. Tried to keep dom related vocabulary to the mixin and SPF model vocabulary in the new module.
  • Order = manifest declaration order (what SPF already handled), deterministic but unsorted. Not sorted here; a bandwidth-ascending sort in the selector is a one-liner if we later want hls.js-style ordering.
  • Video vs audio asymmetry is intentional: VideoRenditionList has an exclusive selectedIndex; AudioTrackList uses non-exclusive enabled (driven exclusively by the audio-track store feature), and audio's enabled doubles as both user input and engine reflection, hence the extra guard in the audio handler.

Test plan:

  • SPF media-tracks unit tests (transforms, property dedup incl. same-language commentary, selection round-tripped through the engine's real matchesPartialTrack).
  • core projection tests (jsdom, signal-backed fake engine): rendition/audio projection, active/enabled reflection, selection -> user*TrackSelection, live-reload set-stability, source-unset teardown, destroy cleanup.

Note

Medium Risk
Touches playback track switching and bidirectional sync between DOM and engine signals; mistakes could mis-report quality/audio or cause selection loops, though behavior is heavily tested.

Overview
SimpleHlsMedia now mirrors HlsJsMedia: the SPF engine’s presentation is projected onto videoTracks / videoRenditions / audioTracks, and UI-driven changes write back to userVideoTrackSelection and userAudioTrackSelection.

A new @videojs/spf/media-tracks export provides DOM-free helpers—dedupedVideoTracks / dedupedAudioTracks (multi-CDN collapse by resolution+bandwidth or language+name), toUser*TrackSelection, property-based isSame* matching, and find*TrackById for engine-resolved ids that may not appear in the deduped list.

SimpleHlsMediaMediaTracksMixin (stacked with MediaTracksMixin on SimpleHlsMedia) uses SPF effects to rebuild lists when track ids change, reflect active / enabled from engine selection, pin manual quality (selectedIndex, Auto clears the pin), handle non-exclusive audio enabled with a guard against feedback loops, clear pins when the manifest URL changes (but keep pins on same-URL live reload), and tear down on destroy.

Unit tests cover the SPF transforms and the DOM projection (dedup, failover reflection, source change, destroy).

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

@spuppo-mux
spuppo-mux requested a review from cjpillsbury July 13, 2026 20:00
@vercel

vercel Bot commented Jul 13, 2026

Copy link
Copy Markdown

@spuppo-mux is attempting to deploy a commit to the Mux Team on Vercel.

A member of the Team first needs to authorize it.

@netlify

netlify Bot commented Jul 13, 2026

Copy link
Copy Markdown

Deploy Preview for vjs10-site ready!

Name Link
🔨 Latest commit 6813123
🔍 Latest deploy log https://app.netlify.com/projects/vjs10-site/deploys/6a6ba15ec675cb0008ca099e
😎 Deploy Preview https://deploy-preview-1826--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.

Comment thread packages/media/src/dom/simple-hls/media-tracks.ts
Comment thread packages/spf/src/media/media-tracks/media-tracks.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 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

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 22c5af0. Configure here.

Comment thread packages/media/src/dom/simple-hls/media-tracks.ts
@spuppo-mux spuppo-mux linked an issue Jul 17, 2026 that may be closed by this pull request
9 tasks

@cjpillsbury cjpillsbury left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

A few callouts, mostly minor or non-blocking with one testing ask. Otherwise and overall, looks 😎.

Comment thread packages/media/src/dom/simple-hls/tests/media-tracks.test.ts
});

// Drain microtasks (effects) and the queued DOM track/rendition events.
const flush = () => new Promise((resolve) => setTimeout(resolve, 0));

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

suggestion(non-blocking): consider tying this more directly to the effect() timing so it's more resilient, something like:

const flush = async () => {
  const sig = signal(0);
  return new Promise((resolve) => {
    effect(() => {
      resolve(sig.get());
    });
    sig.set(sig.get() + 1);
  });
}

Comment thread packages/core/src/dom/media/simple-hls/tests/media-tracks.test.ts Outdated
Comment thread packages/media/src/dom/simple-hls/media-tracks.ts
Comment thread packages/media/src/dom/simple-hls/media-tracks.ts
Comment thread packages/media/src/dom/simple-hls/media-tracks.ts
Comment thread packages/media/src/dom/simple-hls/media-tracks.ts
* Dedupe tracks by a key function, keeping the first occurrence of each key.
* Keys are compared field-by-field ({@link sameKey}).
*/
function dedupe<T, K extends object>({

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

thought(non-blocking): This could probably be refactored to more directly use Object.entries() in a reduce or similiar instead of seen + kept but this is probably also fine.

@vercel

vercel Bot commented Jul 28, 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 Jul 28, 2026 4:38pm

Request Review

@spuppo-mux
spuppo-mux requested a review from cjpillsbury July 29, 2026 23:21

@cjpillsbury cjpillsbury left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Ship it!

@spuppo-mux
spuppo-mux force-pushed the feat/spf-adapter-media-tracks branch from 1efea49 to 3bba439 Compare July 30, 2026 19:06
@spuppo-mux
spuppo-mux merged commit c83b044 into videojs:main Jul 30, 2026
24 of 26 checks passed
@luwes luwes mentioned this pull request Jul 30, 2026
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.

Feature: Expose Media API Extensions on the SPF Adapter

2 participants