Skip to content

feat(spf): multi-cdn support#1668

Open
cjpillsbury wants to merge 9 commits into
mainfrom
feat/spf-multi-cdn
Open

feat(spf): multi-cdn support#1668
cjpillsbury wants to merge 9 commits into
mainfrom
feat/spf-multi-cdn

Conversation

@cjpillsbury

@cjpillsbury cjpillsbury commented Jun 8, 2026

Copy link
Copy Markdown
Collaborator

TL;DR

Implements sticky multi-CDN selection (sub-feature 1) for redundant-stream HLS — e.g. Mux's ?redundant_streams=true, which serves the same renditions on multiple hosts. Those already parse as separate candidate tracks, so this is purely a selection-layer change: a new resolveCdnPriority behavior publishes the manifest-ordered CDN list (cdnPriority), and a new preferActiveCdn soft-filter rule narrows every track type to the highest-priority CDN that still has tracks — keeping the whole presentation on one host. No-op for single-CDN sources; no parser changes. Failover (failed-CDN constraint + recovery) is deliberately deferred — it needs the track-switching constraints phase and network-resilience's circuit-breaker.

For reviewers — how to read this PR

Runtime (focus here)

Read fully:

  • resolve-cdn-priority.ts (new) — new slot-owner behavior; single-writer on cdnPriority; lifecycle mirrors setupTrackSwitching (owns the slot only while presentation-resolved, clears on exit). Note the order-sensitive no-churn guard (samePriority) — see the Bugbot callout below.
  • cdn.ts (new) — CDN-identity contract: getCdnId (URL origin, raw-string fallback so the key is always stable) + getOrderedCdnIds (first-occurrence-wins dedup across all track types = manifest priority order).

Targeted careful read:

  • track-switching.ts (+67/−13) — new preferActiveCdn soft filter inserted mid-chain (filterByUserSelectionpreferActiveCdnrankByBandwidth); soft-filter fall-through (no cdnPriority signal/value, or no CDN matches → applyRules skips it, never empties the set); SwitchableTrack gains url. Verify the narrow is a genuine no-op for single-CDN sources and that the scope sits before the ranker so ABR runs only within the chosen CDN.

Skim structure only:

  • engine.ts (+27), engine-audio-only.ts (+19) — resolveCdnPriority composed before the switch* behaviors; cdnPriority added to each engine's state interface.
  • apps/sandbox/templates/spf-segment-loading/main.ts — test-harness rendition picker, no shipped runtime impact. De-dupes video buttons by bitrate+resolution and pins via a { bandwidth, width, height } filter (CDN-agnostic) instead of { id }, mirroring the audio picker's group/build/update split.

Skim file: tests (4 files) — assertion shape. The two distinguishing cases: engine.test.ts cross-type test (audio lists a different CDN first than video → shared video-derived cdnPriority must pull audio onto the primary, not a coincidental track-order match) and track-switching.test.ts late-arrival test (cdnPriority starts undefined, written after the first pick → scope must re-fire and correct, proving the settled pick is composition-order independent). cdn.test.ts and resolve-cdn-priority.test.ts cover identity/ordering + lifecycle.

Design doc (skim)

Skim file: multi-cdn-failover.md (+241/−174) — reframed from active-URI-rotation to the track-switching constraint+scope model. Sub-feature 1 (this PR) marked implemented; failover deferred. Confirm the doc's "implemented vs deferred" split matches the code.

Smoke test

Sandbox: /spf-segment-loading/?src=https://stream.mux.com/s41JYeqIpBMBzE4OzxDyGR2yrp2hD1CQ6gJN9SlVGDQ.m3u8?redundant_streams=true

  • Load locally (pnpm dev:sandbox → paste the path into the running Vite server) or via the PR's deploy preview. This page drives createSimpleHlsEngine directly (the engine that composes resolveCdnPriority) and reads the source from the src query param.
  • Click Play (the page defaults to preload=none, so nothing fetches until playback starts).
  • Observe (in the page):
    • The video rendition picker shows one button per bitrate+resolution — not duplicated across the redundant CDNs.
    • Clicking a rendition pins by bitrate+resolution: the status row shows the { bandwidth, width, height } filter, and the badge moves to that button.
  • Observe (devtools Network panel, segment/media-playlist requests):
    • All requests resolve from a single origin/host across the session (not split across the redundant hosts), including after a manual rendition pin.
    • A single-CDN source still plays normally — the CDN scope is a no-op.

What changed — by surface

CDN identity + priority list. getCdnId derives a stable grouping key from each track URL's origin; getOrderedCdnIds collapses the per-CDN candidate tracks to the distinct CDNs in manifest order. resolveCdnPriority publishes that as the cdnPriority signal while a presentation is resolved and clears it on src unload — the shared list is the per-presentation coherence guarantee (every track type reads the same one).

Selection-layer scope. preferActiveCdn is a soft filter in the shared video/audio rule chain: it narrows candidates to the highest-priority cdnPriority entry that still has tracks, so the ranker only ever picks within one CDN. "Active" is derived (first-with-survivors), not stored — which is what makes the future failed-CDN constraint a pure consequence (pruning a cooled-down CDN's tracks moves the pick to the next entry and snaps back on recovery) and lets future content steering compose as a reorder of cdnPriority.

Notable design decisions

  • Ordered cdnPriority: string[], not a stored activeCdn string. The active CDN is derived as the first entry with surviving tracks. Alternative considered: store the active CDN directly plus sticky-pick state. Rejected because deriving it keeps failover a pure consequence of constraint-pruning (no reactive rewrite of an "active" field) and lets steering compose as a list reorder — the name mirrors HLS content steering's PATHWAY-PRIORITY.
  • CDNs modeled as separate candidate tracks — no parser / alternateUris change. Redundant-stream renditions already parse as distinct tracks per host. Alternative considered: model redundant CDNs at the parser / URI-rotation layer. Rejected because the selection layer already enumerates them, so a scope rule reuses the existing rule chain with zero parser surface.
  • resolveCdnPriority composed before switch* is a mild optimization, not a correctness requirement. Selection is reactive, so a late cdnPriority converges on the same pick (covered by the late-arrival test). Ordering only affects a transient wasted media-playlist fetch, and only for an asymmetric manifest (a track type listing a non-primary CDN first); symmetric redundant streams never hit it.

Note

Medium Risk
Changes core track-selection for all HLS engine loads (new rule in the chain); behavior is intended as a no-op for single-CDN sources but incorrect CDN ordering or scope logic could mis-route fetches on redundant streams.

Overview
Adds sticky multi-CDN selection for redundant-stream HLS (e.g. Mux ?redundant_streams=true) without parser or fetch-time URL rotation: per-CDN renditions stay separate candidate tracks, and selection keeps the whole presentation on one host.

Runtime: New getCdnId / getOrderedCdnIds (video-first CDN ordering) and resolveCdnPriority, which owns cdnPriority while the presentation is resolved. preferActiveCdn is inserted into the shared video/audio rule chain before rankByBandwidth, narrowing candidates to the first cdnPriority entry with surviving tracks; ABR then runs only within that CDN. Both HLS engines compose resolveCdnPriority before switch* and expose cdnPriority on engine state.

Sandbox: The segment-loading rendition picker groups redundant CDN duplicates by bitrate+resolution and pins manual selection via a partial-track filter instead of per-track id.

Docs: multi-cdn-failover.md is reframed around track-switching scope + constraint (sticky pick implemented; failover deferred).

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

cjpillsbury and others added 6 commits June 8, 2026 14:52
Redundant-streams sources (e.g. Mux ?redundant_streams=true) publish the
same renditions on multiple hosts, which already parse as separate
candidate tracks. Add session-level CDN selection that keeps the whole
presentation on one CDN, modeled in the track-switching rule model:

- getCdnId / getOrderedCdnIds — origin-based CDN identity
- selectActiveCdn behavior: picks the manifest-head CDN, sticky, clears
  on src unload; owns the activeCdn signal
- preferActiveCdn scope rule (shared by the video + audio chains):
  narrows candidates to the active CDN, falls through when nothing matches
- compose selectActiveCdn into the default + audio-only engines

Failover (failed-CDN constraint + rotation) is deferred; it needs the
track-switching constraints phase and network-resilience's circuit-breaker.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Supersede the active-URI-rotation framing with the track-switching
constraint+scope model: sub-feature 1 (sticky CDN pick) implemented,
sub-feature 2 (failover) deferred. Add implementation surface +
verification sections; record the per-presentation / origin-identity /
no-parser-change decisions. Advance definition coarse -> sketched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the single `activeCdn` string with an ordered `cdnPriority: string[]`
signal (most-preferred first; name mirrors HLS content steering's
PATHWAY-PRIORITY). The scope rule `preferActiveCdn` now narrows to the
highest-priority CDN that still has tracks, so "active" is derived rather
than stored.

This makes failover a pure consequence of the (future) failed-CDN
constraint — pruning a cooled-down CDN's tracks moves the pick to the next
entry and returns to the primary on recovery, with no reactive rewrite —
and lets content steering compose by reordering the list (pathway priority
as a sort key). Behavior is unchanged for sub-feature 1 (no constraint yet):
first-with-survivors is the manifest head.

- rename selectActiveCdn/activeCdn -> resolveCdnPriority/cdnPriority
  (behavior file select-active-cdn.ts -> resolve-cdn-priority.ts)
- resolveCdnPriority publishes getOrderedCdnIds, skipping the write when the
  CDN set is unchanged; no sticky-pick state needed
- update both engine state interfaces + composition wiring

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reflect the ordered-list signal: active CDN derived as first-with-survivors,
failover as a pure constraint, content steering as a reorder. Update the
implementation surface, verification, cross-cutting notes, and resolved
decisions accordingly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…effects

Two regressions that distinguish "the cdnPriority scope works" from "it
coincidentally matches track order / behavior order":

- engine cross-type test: audio renditions list a different CDN first than
  video, so the shared (video-derived) cdnPriority must pull audio onto the
  primary CDN — a no-op scope would leave it on its manifest-first track.
  Exercises the real resolveCdnPriority → preferActiveCdn pipeline with
  cdn-order != per-type track-order, without a manual reorder.
- track-switching late-arrival test: cdnPriority starts undefined (the worst
  case = resolveCdnPriority composed last) and is written after the first
  pick; the scope must re-fire and correct the selection, proving the
  settled state is composition-order independent.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Document why resolveCdnPriority is composed before the switch* behaviors:
it's a mild optimization, not a correctness requirement. Selection is
reactive, so a late cdnPriority converges on the same pick; order only
affects a transient wasted media-playlist fetch, and only for an asymmetric
manifest (a track type listing a non-primary CDN first). Symmetric redundant
streams never hit it. In the audio-only engine, with a single track type,
the order is not load-bearing at all — it's there for forward-consistency
and future failover / steering.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@netlify

netlify Bot commented Jun 8, 2026

Copy link
Copy Markdown

Deploy Preview for vjs10-site ready!

Name Link
🔨 Latest commit 6af8f5f
🔍 Latest deploy log https://app.netlify.com/projects/vjs10-site/deploys/6a29b2213d74ba0008bd3113
😎 Deploy Preview https://deploy-preview-1668--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 8, 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 10, 2026 6:51pm

Request Review

Comment thread packages/spf/src/playback/behaviors/resolve-cdn-priority.ts
@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

📦 Bundle Size Report

🎨 @videojs/html — no changes
Presets (7)
Entry Size
/video (default) 41.75 kB
/video (default + hls) 175.09 kB
/video (minimal) 41.40 kB
/video (minimal + hls) 174.97 kB
/audio (default) 35.57 kB
/audio (minimal) 32.84 kB
/background 4.22 kB
Media (9)
Entry Size
/media/background-video 1.06 kB
/media/container 1.72 kB
/media/dash-video 236.69 kB
/media/hls-video 134.98 kB
/media/mux-audio 160.96 kB
/media/mux-video 160.95 kB
/media/native-hls-video 4.63 kB
/media/simple-hls-audio-only 15.46 kB
/media/simple-hls-video 17.15 kB
Players (5)
Entry Size
/video/player 7.50 kB
/audio/player 5.30 kB
/background/player 3.92 kB
/live-video/player 7.52 kB
/live-audio/player 5.32 kB
Skins (30)
Entry Type Size
/video/minimal-skin.css css 5.23 kB
/video/skin.css css 5.19 kB
/video/minimal-skin js 41.40 kB
/video/minimal-skin.tailwind js 41.83 kB
/video/skin js 41.70 kB
/video/skin.tailwind js 42.13 kB
/audio/minimal-skin.css css 3.45 kB
/audio/skin.css css 3.36 kB
/audio/minimal-skin js 32.81 kB
/audio/minimal-skin.tailwind js 33.18 kB
/audio/skin js 35.56 kB
/audio/skin.tailwind js 35.90 kB
/background/skin.css css 133 B
/background/skin js 1.16 kB
/live-video/minimal-skin.css css 5.23 kB
/live-video/skin.css css 5.19 kB
/live-video/minimal-skin js 40.84 kB
/live-video/minimal-skin.tailwind js 41.18 kB
/live-video/skin js 40.74 kB
/live-video/skin.tailwind js 41.14 kB
/live-audio/minimal-skin.css css 3.45 kB
/live-audio/skin.css css 3.36 kB
/live-audio/minimal-skin js 27.03 kB
/live-audio/minimal-skin.tailwind js 26.57 kB
/live-audio/skin js 29.56 kB
/live-audio/skin.tailwind js 29.16 kB
/global.css css 176 B
/shared.css css 88 B
/tailwind.css css 228 B
/skin-element js 1.37 kB
UI Components (37)
Entry Size
/ui/airplay-button 2.13 kB
/ui/alert-dialog 865 B
/ui/alert-dialog-close 434 B
/ui/alert-dialog-description 249 B
/ui/alert-dialog-title 332 B
/ui/buffering-indicator 2.11 kB
/ui/captions-button 2.26 kB
/ui/captions-radio-group 2.11 kB
/ui/cast-button 2.21 kB
/ui/compounds 5.21 kB
/ui/controls 2.01 kB
/ui/error-dialog 2.44 kB
/ui/fullscreen-button 2.15 kB
/ui/hotkey 2.69 kB
/ui/menu 3.87 kB
/ui/mute-button 2.14 kB
/ui/pip-button 2.19 kB
/ui/play-button 2.24 kB
/ui/playback-rate-button 2.23 kB
/ui/playback-rate-radio-group 2.04 kB
/ui/popover 1.49 kB
/ui/poster 1.93 kB
/ui/seek-button 2.18 kB
/ui/seek-indicator 2.60 kB
/ui/seek-indicator-value 271 B
/ui/slider 1.07 kB
/ui/status-announcer 2.43 kB
/ui/status-indicator 2.46 kB
/ui/status-indicator-value 126 B
/ui/thumbnail 2.37 kB
/ui/time 1.88 kB
/ui/time-slider 2.78 kB
/ui/tooltip 1.68 kB
/ui/volume-indicator 2.63 kB
/ui/volume-indicator-fill 252 B
/ui/volume-indicator-value 251 B
/ui/volume-slider 3.56 kB

Sizes are marginal over the root entry point.

⚛️ @videojs/react — no changes
Presets (7)
Entry Size
/video (default) 34.64 kB
/video (default + hls) 167.01 kB
/video (minimal) 34.69 kB
/video (minimal + hls) 166.93 kB
/audio (default) 28.38 kB
/audio (minimal) 28.46 kB
/background 754 B
Media (8)
Entry Size
/media/background-video 575 B
/media/dash-video 235.21 kB
/media/hls-video 133.61 kB
/media/mux-audio 159.74 kB
/media/mux-video 159.75 kB
/media/native-hls-video 3.13 kB
/media/simple-hls-audio-only 14.04 kB
/media/simple-hls-video 15.79 kB
Skins (27)
Entry Type Size
/tailwind.css css 228 B
/video/minimal-skin.css css 5.14 kB
/video/skin.css css 5.10 kB
/video/minimal-skin js 34.58 kB
/video/minimal-skin.tailwind js 40.09 kB
/video/skin js 34.56 kB
/video/skin.tailwind js 39.92 kB
/audio/minimal-skin.css css 3.32 kB
/audio/skin.css css 3.23 kB
/audio/minimal-skin js 28.37 kB
/audio/minimal-skin.tailwind js 28.78 kB
/audio/skin js 28.29 kB
/audio/skin.tailwind js 31.88 kB
/background/skin.css css 90 B
/background/skin js 272 B
/live-video/minimal-skin.css css 5.14 kB
/live-video/skin.css css 5.10 kB
/live-video/minimal-skin js 30.82 kB
/live-video/minimal-skin.tailwind js 36.12 kB
/live-video/skin js 30.81 kB
/live-video/skin.tailwind js 36.06 kB
/live-audio/minimal-skin.css css 3.32 kB
/live-audio/skin.css css 3.23 kB
/live-audio/minimal-skin js 20.92 kB
/live-audio/minimal-skin.tailwind js 23.65 kB
/live-audio/skin js 20.92 kB
/live-audio/skin.tailwind js 23.75 kB
UI Components (31)
Entry Size
/ui/airplay-button 2.10 kB
/ui/alert-dialog 1.12 kB
/ui/buffering-indicator 1.85 kB
/ui/captions-button 2.17 kB
/ui/captions-radio-group 2.01 kB
/ui/cast-button 2.09 kB
/ui/controls 1.94 kB
/ui/error-dialog 2.29 kB
/ui/fullscreen-button 2.10 kB
/ui/gesture 1.27 kB
/ui/hotkey 1.90 kB
/ui/live-button 2.03 kB
/ui/menu 5.61 kB
/ui/mute-button 2.17 kB
/ui/pip-button 2.11 kB
/ui/play-button 2.10 kB
/ui/playback-rate 2.40 kB
/ui/playback-rate-button 2.16 kB
/ui/popover 2.31 kB
/ui/poster 1.79 kB
/ui/seek-button 2.13 kB
/ui/seek-indicator 1.94 kB
/ui/slider 3.41 kB
/ui/status-announcer 1.82 kB
/ui/status-indicator 2.01 kB
/ui/thumbnail 2.17 kB
/ui/time 2.00 kB
/ui/time-slider 3.03 kB
/ui/tooltip 2.49 kB
/ui/volume-indicator 1.98 kB
/ui/volume-slider 2.49 kB

Sizes are marginal over the root entry point.

🧩 @videojs/core — no changes
Entries (10)
Entry Size
. 7.94 kB
/dom 16.29 kB
/dom/media/custom-media-element 1.90 kB
/dom/media/dash 234.36 kB
/dom/media/google-cast 4.07 kB
/dom/media/hls 132.99 kB
/dom/media/mux 158.95 kB
/dom/media/native-hls 2.52 kB
/dom/media/simple-hls 15.16 kB
/dom/media/simple-hls-audio-only 13.39 kB
🏷️ @videojs/element — no changes
Entries (2)
Entry Size
. 996 B
/context 943 B
📦 @videojs/store — no changes
Entries (3)
Entry Size
. 1.39 kB
/html 696 B
/react 360 B
🔧 @videojs/utils — no changes
Entries (10)
Entry Size
/array 104 B
/dom 2.06 kB
/events 319 B
/function 327 B
/object 275 B
/predicate 265 B
/string 192 B
/style 190 B
/time 478 B
/number 158 B
📦 @videojs/spf — no changes
Entries (4)
Entry Size
. 4.45 kB
/dom 6.31 kB
/hls 14.61 kB
/background-looping-video 12.29 kB

ℹ️ How to interpret

All sizes are standalone totals (minified + brotli).

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

Run pnpm size locally to check current sizes.

The spf-segment-loading rendition picker listed every video track, so a
redundant-stream source (same renditions per CDN) showed duplicate buttons,
and clicking pinned by track id — tying the choice to one CDN's track.

Mirror the audio picker: collapse to one button per bitrate + resolution
identity, and pin via a { bandwidth, width, height } partial-track filter
so the preference is CDN-agnostic (preferActiveCdn resolves the active
CDN's copy). The status row now reflects the pinned bitrate + width +
height. Uses the audio picker's build/update split so frequent ABR
switches don't tear down buttons mid-click.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@@ -0,0 +1,91 @@
/**

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

note: I'll be renaming this in a followup PR to avoid confusion. Since "resolve" has been used for what I've been calling "resolvables" (things with a URL that are asynchronously "resolved" to a value, like Presentations or Tracks), this should be renamed. Only holding off on renaming in this PR to avoid rebase issues on the stacked branch for implementing CDN failover.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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): do/should we track these naming conventions in any document? I think rule nomenclature is also a nice to have. Short glossary of contraints, scopes, ranks, etc.
As long as it's not repetitive, of course.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Yes, I think there are lots of things we should start documenting. A "living document" glossary sounds like a very good idea imo. While there are other documents that use this jargon, none of them are structured around a glossary of terms.

* Sub-feature 1 (sticky CDN pick) uses origin-based identity; a more advanced
* or consumer-configurable derivation can replace this default later.
*/
export function getCdnId(url: string): string {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

note: Some of these will end up being config-driven in a followup PR to allow for customization of what counts as "the same CDN", since different content providers may handle these things slightly differently.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Comment thread packages/spf/src/playback/behaviors/track-switching.ts Outdated
Comment thread packages/spf/src/playback/engines/hls/tests/engine.test.ts Outdated
@spuppo-mux

Copy link
Copy Markdown
Collaborator

Testing this out on the sandbox I see that the example stream only show one host, and the different cdns are represented by ?cdn=fastly|cloudfare all with the same fastly url. I assume that wouldn't be a real use case, just a test case, but pointing it out just in case.
I also tested with this stream (
https://stream.mux.com/ihZa7qP1zY8oyLSQW9TS602VgwQvNdyIvlk9LInEGU2s.m3u8?redundant_streams=true) which has the same format for the rendition urls and got a case where cdnPriority is

[
    "https://manifest-gcp-us-east4-vop1.fastly.mux.com",
    ""
]

@spuppo-mux spuppo-mux 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.

Left mainly non-blocking comments and a couple small blocking comments. Up to you if that falls within scope for this PR

@cjpillsbury

Copy link
Copy Markdown
Collaborator Author

Testing this out on the sandbox I see that the example stream only show one host, and the different cdns are represented by ?cdn=fastly|cloudfare all with the same fastly url. I assume that wouldn't be a real use case, just a test case, but pointing it out just in case. I also tested with this stream ( https://stream.mux.com/ihZa7qP1zY8oyLSQW9TS602VgwQvNdyIvlk9LInEGU2s.m3u8?redundant_streams=true) which has the same format for the rendition urls and got a case where cdnPriority is

[
    "https://manifest-gcp-us-east4-vop1.fastly.mux.com",
    ""
]

A few things here:

  1. We'll want the default implementation for identifying a CDN to be based on the origin, since that's standard.
  2. You're right to note that Mux Video also relies on a query parameter to indicate which CDN will be used to serve the playlist's corresponding segments. This shouldn't be baked into the default, but should be something configurable. I should have added a PR comment to point out that this is another improvement (allowing this function to be provided/overridden via config) here - https://github.com/videojs/v10/pull/1671/changes

Below is the response I see for https://stream.mux.com/ihZa7qP1zY8oyLSQW9TS602VgwQvNdyIvlk9LInEGU2s.m3u8?redundant_streams=true.

Note that it uses both hostname/origin (https://manifest-gcp-us-east4-vop1.edgemv.mux.com, https://manifest-gcp-us-east4-vop1.fastly.mux.com) and cdn query param (cdn=fastly, cdn=edgemv) together to identify a different CDN category (e.g. edgemv for hosting the media playlist but fastly for segments on that media playlist's segments). When I load https://v10-sandbox-git-feat-spf-multi-cdn-mux.vercel.app/spf-segment-loading/?src=https://stream.mux.com/s41JYeqIpBMBzE4OzxDyGR2yrp2hD1CQ6gJN9SlVGDQ.m3u8?redundant_streams=true
and then load the src and open Dev Tools and check the value in the console via engine.state.cdnPriority.get(), I see
['https://manifest-oci-us-ashburn-1-vop1.edgemv.mux.com', 'https://manifest-oci-us-ashburn-1-vop1.fastly.mux.com']. This is what we should expect (i.e. identifying the different hostnames/origins but not identifying based on the cdn query param by default). Maybe I'm misunderstanding your point/test scenario here, or maybe you're seeing something different from me? Definitely some of this should have been called out via github comments from me (like I did in other sections).

#EXTM3U
#EXT-X-VERSION:5
#EXT-X-INDEPENDENT-SEGMENTS

#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub1",CHARACTERISTICS="public.accessibility.transcribes-spoken-dialog,public.accessibility.describes-music-and-sound",NAME="English",AUTOSELECT=YES,DEFAULT=NO,FORCED=NO,LANGUAGE="en",URI="https://manifest-gcp-us-east4-vop1.edgemv.mux.com/nuiK0102hHVlorYh8pGhDZA8zPhJxOfakGGrmaxfRrVloUoDj1H00jepXLA6w7a7YIrC7NjXZ15IUI/subtitles.m3u8?cdn=edgemv&expires=1781708400&skid=edgemv-default-1&signature=NmEzMmJiNGVfM2ZmYzFmNDBhNzgwMDQ1MTg2ZDc2MTFiNDdjZjY3MTA4ZjIwNTRmOWU2Y2M0MDljOGM1MDQ4YjBlZTc2MGZkOA=="
#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub1",CHARACTERISTICS="public.accessibility.transcribes-spoken-dialog,public.accessibility.describes-music-and-sound",NAME="Swedish",AUTOSELECT=YES,DEFAULT=NO,FORCED=NO,LANGUAGE="sv",URI="https://manifest-gcp-us-east4-vop1.edgemv.mux.com/uj1zN4yF01nYcblfu3ak02wftTiH027hLk4N44TBnto5yijd5dpyLXAvY7pCVqGMnh8R7BmVNo007xg/subtitles.m3u8?cdn=edgemv&expires=1781708400&skid=edgemv-default-1&signature=NmEzMmJiNGVfZjQwNWUzZDg5MDU3NmNjOTFiODRmZDg3NWJmYTRhMTYzZDRmOGQwMGQ1ZWZlMWIwNjY4ZTY5MmY5MTZmYTM2OQ=="
#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub1",CHARACTERISTICS="public.accessibility.transcribes-spoken-dialog,public.accessibility.describes-music-and-sound",NAME="Arabic",AUTOSELECT=YES,DEFAULT=NO,FORCED=NO,LANGUAGE="ar",URI="https://manifest-gcp-us-east4-vop1.edgemv.mux.com/okdNYL00ZUd00pdgqlFOKzqP025oBEcF5VMNj00014UIeLRpNSad9XqYr56AOVtWvyyBFbRRYY01smSfY/subtitles.m3u8?cdn=edgemv&expires=1781708400&skid=edgemv-default-1&signature=NmEzMmJiNGVfOWQ3NTBlZDFmNDY2YmQzZjM1NGU4NGZkZTc3ZWMyZjZhZTBkYWE4ZjY4MjY1N2ZhMWM1MTA4MzRmNDZjYjMyMQ=="
#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub2",CHARACTERISTICS="public.accessibility.transcribes-spoken-dialog,public.accessibility.describes-music-and-sound",NAME="English",AUTOSELECT=YES,DEFAULT=NO,FORCED=NO,LANGUAGE="en",URI="https://manifest-gcp-us-east4-vop1.edgemv.mux.com/nuiK0102hHVlorYh8pGhDZA8zPhJxOfakGGrmaxfRrVloUoDj1H00jepXLA6w7a7YIrC7NjXZ15IUI/subtitles.m3u8?cdn=fastly&expires=1781708400&skid=edgemv-default-1&signature=NmEzMmJiNGVfOTM4ZDVlM2VlMGY4OTI5ODAzNDU5MGU1NmFkM2Q4YTQxMzNiNDRmMTA5MmFiMzdjNTI5NjZhN2NhODhiODA0Yg=="
#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub2",CHARACTERISTICS="public.accessibility.transcribes-spoken-dialog,public.accessibility.describes-music-and-sound",NAME="Swedish",AUTOSELECT=YES,DEFAULT=NO,FORCED=NO,LANGUAGE="sv",URI="https://manifest-gcp-us-east4-vop1.edgemv.mux.com/uj1zN4yF01nYcblfu3ak02wftTiH027hLk4N44TBnto5yijd5dpyLXAvY7pCVqGMnh8R7BmVNo007xg/subtitles.m3u8?cdn=fastly&expires=1781708400&skid=edgemv-default-1&signature=NmEzMmJiNGVfYWRiMmZhNjI1MTFjZDE1OWYwYTRiOWQxZmY5ZThlYzVmMTk2ZmExMzM5NmRlOGZiYzgwMGZkYzQ5YWE0NTlhNg=="
#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub2",CHARACTERISTICS="public.accessibility.transcribes-spoken-dialog,public.accessibility.describes-music-and-sound",NAME="Arabic",AUTOSELECT=YES,DEFAULT=NO,FORCED=NO,LANGUAGE="ar",URI="https://manifest-gcp-us-east4-vop1.edgemv.mux.com/okdNYL00ZUd00pdgqlFOKzqP025oBEcF5VMNj00014UIeLRpNSad9XqYr56AOVtWvyyBFbRRYY01smSfY/subtitles.m3u8?cdn=fastly&expires=1781708400&skid=edgemv-default-1&signature=NmEzMmJiNGVfNmZhNjVkZWJjMjkzYmI1YzhmNDdmNzUxMzkxNjE1MjFjYTQ5MzkxMjE2NjYyODU0Y2JiNWY0NzUyNWJjOGU3Yg=="
#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub3",CHARACTERISTICS="public.accessibility.transcribes-spoken-dialog,public.accessibility.describes-music-and-sound",NAME="English",AUTOSELECT=YES,DEFAULT=NO,FORCED=NO,LANGUAGE="en",URI="https://manifest-gcp-us-east4-vop1.edgemv.mux.com/nuiK0102hHVlorYh8pGhDZA8zPhJxOfakGGrmaxfRrVloUoDj1H00jepXLA6w7a7YIrC7NjXZ15IUI/subtitles.m3u8?cdn=cloudflare&expires=1781708400&skid=edgemv-default-1&signature=NmEzMmJiNGVfZWY4N2Y2ODI2N2U3YmY0Njk4NzM3MDE5NzQwMDZkMTU3ZjdiMGY4ZjRkYTNkMzdkYjM1Y2VkNjhhZTIwY2FlYg=="
#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub3",CHARACTERISTICS="public.accessibility.transcribes-spoken-dialog,public.accessibility.describes-music-and-sound",NAME="Swedish",AUTOSELECT=YES,DEFAULT=NO,FORCED=NO,LANGUAGE="sv",URI="https://manifest-gcp-us-east4-vop1.edgemv.mux.com/uj1zN4yF01nYcblfu3ak02wftTiH027hLk4N44TBnto5yijd5dpyLXAvY7pCVqGMnh8R7BmVNo007xg/subtitles.m3u8?cdn=cloudflare&expires=1781708400&skid=edgemv-default-1&signature=NmEzMmJiNGVfMmNkOTg1YzFkZmNmMzgxOGUzY2IxYTNiN2E5MTkxZTk5NjA4ZjhlMmQ4YmRkZDVhNjYwZTJlOGRmODk1YjE4YQ=="
#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub3",CHARACTERISTICS="public.accessibility.transcribes-spoken-dialog,public.accessibility.describes-music-and-sound",NAME="Arabic",AUTOSELECT=YES,DEFAULT=NO,FORCED=NO,LANGUAGE="ar",URI="https://manifest-gcp-us-east4-vop1.edgemv.mux.com/okdNYL00ZUd00pdgqlFOKzqP025oBEcF5VMNj00014UIeLRpNSad9XqYr56AOVtWvyyBFbRRYY01smSfY/subtitles.m3u8?cdn=cloudflare&expires=1781708400&skid=edgemv-default-1&signature=NmEzMmJiNGVfMzYxNWIxNGVmMTQ3ZTFkMDk5NzQzYmY0Mzk2N2U4NDhmMTNiNjQ5MTU1OGY1NDljZjc3MmVlNjdjY2Y3Zjg3ZA=="
#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub4",CHARACTERISTICS="public.accessibility.transcribes-spoken-dialog,public.accessibility.describes-music-and-sound",NAME="English",AUTOSELECT=YES,DEFAULT=NO,FORCED=NO,LANGUAGE="en",URI="https://manifest-gcp-us-east4-vop1.fastly.mux.com/nuiK0102hHVlorYh8pGhDZA8zPhJxOfakGGrmaxfRrVloUoDj1H00jepXLA6w7a7YIrC7NjXZ15IUI/subtitles.m3u8?cdn=edgemv&expires=1781708400&skid=default&signature=NmEzMmJiNGVfYWI1ODQ5Y2IzODMxNzgzMDhjOTQyYzQ2MjgzZTBiYTYzNDZmOTdmZTE3NzQyODE3YjVhYTNjMDZhYmFjN2FhYQ=="
#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub4",CHARACTERISTICS="public.accessibility.transcribes-spoken-dialog,public.accessibility.describes-music-and-sound",NAME="Swedish",AUTOSELECT=YES,DEFAULT=NO,FORCED=NO,LANGUAGE="sv",URI="https://manifest-gcp-us-east4-vop1.fastly.mux.com/uj1zN4yF01nYcblfu3ak02wftTiH027hLk4N44TBnto5yijd5dpyLXAvY7pCVqGMnh8R7BmVNo007xg/subtitles.m3u8?cdn=edgemv&expires=1781708400&skid=default&signature=NmEzMmJiNGVfZjhkODJiNjI4ZTZiMzU4M2MxMDgzZjE4MzRkNzFlMzc1NDBmNjgyYTc2ZmNhMmFhYmM1OTA5N2FjNjBhMjBjYg=="
#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub4",CHARACTERISTICS="public.accessibility.transcribes-spoken-dialog,public.accessibility.describes-music-and-sound",NAME="Arabic",AUTOSELECT=YES,DEFAULT=NO,FORCED=NO,LANGUAGE="ar",URI="https://manifest-gcp-us-east4-vop1.fastly.mux.com/okdNYL00ZUd00pdgqlFOKzqP025oBEcF5VMNj00014UIeLRpNSad9XqYr56AOVtWvyyBFbRRYY01smSfY/subtitles.m3u8?cdn=edgemv&expires=1781708400&skid=default&signature=NmEzMmJiNGVfNzdhNjE5OWZkNWYyMTUwODg0NDZmYmIxNzQ1ZDlhMGViZjBiMjk1MDBjYTU2YWIyZThiZTIxMmEzM2JjODExMg=="
#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub5",CHARACTERISTICS="public.accessibility.transcribes-spoken-dialog,public.accessibility.describes-music-and-sound",NAME="English",AUTOSELECT=YES,DEFAULT=NO,FORCED=NO,LANGUAGE="en",URI="https://manifest-gcp-us-east4-vop1.fastly.mux.com/nuiK0102hHVlorYh8pGhDZA8zPhJxOfakGGrmaxfRrVloUoDj1H00jepXLA6w7a7YIrC7NjXZ15IUI/subtitles.m3u8?cdn=fastly&expires=1781708400&skid=default&signature=NmEzMmJiNGVfMjk2MTA1OWIwNjI1YTc4YjAyYjA1OTk3MDY3MzExYTgyYjZlMTlhZjBiOGI5OGU0ZDUyYzMwMmUyM2M0ZWZmOQ=="
#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub5",CHARACTERISTICS="public.accessibility.transcribes-spoken-dialog,public.accessibility.describes-music-and-sound",NAME="Swedish",AUTOSELECT=YES,DEFAULT=NO,FORCED=NO,LANGUAGE="sv",URI="https://manifest-gcp-us-east4-vop1.fastly.mux.com/uj1zN4yF01nYcblfu3ak02wftTiH027hLk4N44TBnto5yijd5dpyLXAvY7pCVqGMnh8R7BmVNo007xg/subtitles.m3u8?cdn=fastly&expires=1781708400&skid=default&signature=NmEzMmJiNGVfM2VkNGFlMjdiN2M3NDc1MDM4NmY2ZjlkZDNkMjQ0MjBmZGEzNjk3MjY1OWU0NGY0NWYxMmIzMmZiZDJkZWY4MA=="
#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub5",CHARACTERISTICS="public.accessibility.transcribes-spoken-dialog,public.accessibility.describes-music-and-sound",NAME="Arabic",AUTOSELECT=YES,DEFAULT=NO,FORCED=NO,LANGUAGE="ar",URI="https://manifest-gcp-us-east4-vop1.fastly.mux.com/okdNYL00ZUd00pdgqlFOKzqP025oBEcF5VMNj00014UIeLRpNSad9XqYr56AOVtWvyyBFbRRYY01smSfY/subtitles.m3u8?cdn=fastly&expires=1781708400&skid=default&signature=NmEzMmJiNGVfMTNkMDdiMjBkZDAyNTA5N2VlYjJlY2YwZWE3NzVjM2QwZjRjMGMxM2E3Y2E3MThkZmNiNTk3YTE3YjQ4NjliZA=="
#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub6",CHARACTERISTICS="public.accessibility.transcribes-spoken-dialog,public.accessibility.describes-music-and-sound",NAME="English",AUTOSELECT=YES,DEFAULT=NO,FORCED=NO,LANGUAGE="en",URI="https://manifest-gcp-us-east4-vop1.fastly.mux.com/nuiK0102hHVlorYh8pGhDZA8zPhJxOfakGGrmaxfRrVloUoDj1H00jepXLA6w7a7YIrC7NjXZ15IUI/subtitles.m3u8?cdn=cloudflare&expires=1781708400&skid=default&signature=NmEzMmJiNGVfODEzZWY5NDYxMGMyNjlhNGQwOTRjODlkZjVjMTdmMTg1YjhjMDFhNDQ3ZWUzNTM1ZTBiZmY1YjE4YWNmMTUzYQ=="
#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub6",CHARACTERISTICS="public.accessibility.transcribes-spoken-dialog,public.accessibility.describes-music-and-sound",NAME="Swedish",AUTOSELECT=YES,DEFAULT=NO,FORCED=NO,LANGUAGE="sv",URI="https://manifest-gcp-us-east4-vop1.fastly.mux.com/uj1zN4yF01nYcblfu3ak02wftTiH027hLk4N44TBnto5yijd5dpyLXAvY7pCVqGMnh8R7BmVNo007xg/subtitles.m3u8?cdn=cloudflare&expires=1781708400&skid=default&signature=NmEzMmJiNGVfNzEwNzU1YTU4ODg1ODZjZjViNGNiOTc3N2NhNTRhOTViYWM2MjRiYTQzZDcwZGYyYzRkODQ3MzMyNGU1MWRiNw=="
#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub6",CHARACTERISTICS="public.accessibility.transcribes-spoken-dialog,public.accessibility.describes-music-and-sound",NAME="Arabic",AUTOSELECT=YES,DEFAULT=NO,FORCED=NO,LANGUAGE="ar",URI="https://manifest-gcp-us-east4-vop1.fastly.mux.com/okdNYL00ZUd00pdgqlFOKzqP025oBEcF5VMNj00014UIeLRpNSad9XqYr56AOVtWvyyBFbRRYY01smSfY/subtitles.m3u8?cdn=cloudflare&expires=1781708400&skid=default&signature=NmEzMmJiNGVfYTFkNjQ4ZTQ1MmMzNWE3MjdkYWRiMGFhMzJiZmU0MmRiMzUwN2RhMzkyM2NlYTFjMTZhYjg3MDE4MzAyMTg5Mg=="
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",NAME="Default",AUTOSELECT=YES,DEFAULT=YES
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",NAME="Spanish",AUTOSELECT=YES,LANGUAGE="es",URI="https://manifest-gcp-us-east4-vop1.edgemv.mux.com/dpPxNR00XO3XAQzPZ85ZSUX7JSd02Y7CcXaUuoEEcALf5bRHrsoo92Wj8TRauenK4tKFjuxOJo94rfK0264HRdKFzajgI7Bx466/rendition.m3u8?cdn=edgemv&expires=1781708400&skid=edgemv-default-1&signature=NmEzMmJiNGVfOTQ2ZmVmNDk3MDMyYzJhZmQyM2RiOTQzNWU1MzFjODc2OWU3ZGY3NWM4MjVmZTE1Yjg2Zjk4NDJmMzU4YTg3Nw=="
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",NAME="Commentary",AUTOSELECT=YES,LANGUAGE="en",URI="https://manifest-gcp-us-east4-vop1.edgemv.mux.com/Yg62TT57f7lvN02hoWEf02G4yJbnE1MeSfScNBNDa02sbm01C7Us901eaat02fIG5ymype1n01WUPlYVamdWL02Bq9hYAsxgFKhL1Y8i/rendition.m3u8?cdn=edgemv&expires=1781708400&skid=edgemv-default-1&signature=NmEzMmJiNGVfMWFlZTQ3MTg3M2QzZTBkMzQ4NDJiZmFhNjU2ZTAzZWNhYmRjZmQ2NzdlNDg4OGE1NTBiYjViYWM0ODM0NTRjMg=="
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud2",NAME="Default",AUTOSELECT=YES,DEFAULT=YES
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud2",NAME="Spanish",AUTOSELECT=YES,LANGUAGE="es",URI="https://manifest-gcp-us-east4-vop1.edgemv.mux.com/dpPxNR00XO3XAQzPZ85ZSUX7JSd02Y7CcXaUuoEEcALf5bRHrsoo92Wj8TRauenK4tKFjuxOJo94rfK0264HRdKFzajgI7Bx466/rendition.m3u8?cdn=fastly&expires=1781708400&skid=edgemv-default-1&signature=NmEzMmJiNGVfYzdkYTdjNzg2YjhhODhmZWI3ODcxNDgzOWU0ZWJjNTJkNmQ1MjUwMGEwMzVjMWY4NTliZWY3NGE5NWFmOWU2Nw=="
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud2",NAME="Commentary",AUTOSELECT=YES,LANGUAGE="en",URI="https://manifest-gcp-us-east4-vop1.edgemv.mux.com/Yg62TT57f7lvN02hoWEf02G4yJbnE1MeSfScNBNDa02sbm01C7Us901eaat02fIG5ymype1n01WUPlYVamdWL02Bq9hYAsxgFKhL1Y8i/rendition.m3u8?cdn=fastly&expires=1781708400&skid=edgemv-default-1&signature=NmEzMmJiNGVfNTBhYWM2MGM3NmIzYjcwZWI4YmRiNDRiMzllZjhiYjIwYTViMTk1NmJkYmJmNjJhODcxMTg0M2ZjOTczMTllYw=="
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud3",NAME="Default",AUTOSELECT=YES,DEFAULT=YES
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud3",NAME="Spanish",AUTOSELECT=YES,LANGUAGE="es",URI="https://manifest-gcp-us-east4-vop1.edgemv.mux.com/dpPxNR00XO3XAQzPZ85ZSUX7JSd02Y7CcXaUuoEEcALf5bRHrsoo92Wj8TRauenK4tKFjuxOJo94rfK0264HRdKFzajgI7Bx466/rendition.m3u8?cdn=cloudflare&expires=1781708400&skid=edgemv-default-1&signature=NmEzMmJiNGVfOWZiZmZiZDQwYTk3Mzk1ODgzMmUwZDk4YjYzY2ZiYjVlNDg0N2QyM2Q4YTVhOGQxYjA5YjUxOWZlNTQ4YWMyZQ=="
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud3",NAME="Commentary",AUTOSELECT=YES,LANGUAGE="en",URI="https://manifest-gcp-us-east4-vop1.edgemv.mux.com/Yg62TT57f7lvN02hoWEf02G4yJbnE1MeSfScNBNDa02sbm01C7Us901eaat02fIG5ymype1n01WUPlYVamdWL02Bq9hYAsxgFKhL1Y8i/rendition.m3u8?cdn=cloudflare&expires=1781708400&skid=edgemv-default-1&signature=NmEzMmJiNGVfNjkyZGE5NGE5MmVhMTA4M2U1ZTdjNjgwOGY2ZTNkOTRlMjMwZGNjMGI2YTZjNTI2MDQzNTllZDA5ZjQyZWE0Ng=="
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud4",NAME="Default",AUTOSELECT=YES,DEFAULT=YES
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud4",NAME="Spanish",AUTOSELECT=YES,LANGUAGE="es",URI="https://manifest-gcp-us-east4-vop1.fastly.mux.com/dpPxNR00XO3XAQzPZ85ZSUX7JSd02Y7CcXaUuoEEcALf5bRHrsoo92Wj8TRauenK4tKFjuxOJo94rfK0264HRdKFzajgI7Bx466/rendition.m3u8?cdn=edgemv&expires=1781708400&skid=default&signature=NmEzMmJiNGVfNDU2ZTViZDM1Yjk4MGQyYjYxNTg5ZjU2MTRmZDMxZjk3YWJhZjk0NWFhNjM4NThlMjJmODA0YmQ4OTk4OTRmMg=="
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud4",NAME="Commentary",AUTOSELECT=YES,LANGUAGE="en",URI="https://manifest-gcp-us-east4-vop1.fastly.mux.com/Yg62TT57f7lvN02hoWEf02G4yJbnE1MeSfScNBNDa02sbm01C7Us901eaat02fIG5ymype1n01WUPlYVamdWL02Bq9hYAsxgFKhL1Y8i/rendition.m3u8?cdn=edgemv&expires=1781708400&skid=default&signature=NmEzMmJiNGVfZjg2MDdkZjZhNGI4NThjYjIzMzA0NmZmOTRlYWFkMGU1ODg2NWE0MWFlYmRmMTBhOTQ2NmRmZGNjMjU1MzNiMw=="
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud5",NAME="Default",AUTOSELECT=YES,DEFAULT=YES
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud5",NAME="Spanish",AUTOSELECT=YES,LANGUAGE="es",URI="https://manifest-gcp-us-east4-vop1.fastly.mux.com/dpPxNR00XO3XAQzPZ85ZSUX7JSd02Y7CcXaUuoEEcALf5bRHrsoo92Wj8TRauenK4tKFjuxOJo94rfK0264HRdKFzajgI7Bx466/rendition.m3u8?cdn=fastly&expires=1781708400&skid=default&signature=NmEzMmJiNGVfYWY5ZTM0ZjU2MDA5YTQ1ZDRmZWFjZjM1YzBiMDdjZjA3N2Y4M2M2NmYwODBkYmE3ODlhMzAwOGIzNjQyYzdmZg=="
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud5",NAME="Commentary",AUTOSELECT=YES,LANGUAGE="en",URI="https://manifest-gcp-us-east4-vop1.fastly.mux.com/Yg62TT57f7lvN02hoWEf02G4yJbnE1MeSfScNBNDa02sbm01C7Us901eaat02fIG5ymype1n01WUPlYVamdWL02Bq9hYAsxgFKhL1Y8i/rendition.m3u8?cdn=fastly&expires=1781708400&skid=default&signature=NmEzMmJiNGVfNGY2ZjQ5MmE1NDI5ZTE3MDQ3MmQxZmIzMmVlYTViMzFkYjA3YmQyYWMwN2E4ZDZjMjZlY2EyYTg1ZmE0Nzc0NA=="
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud6",NAME="Default",AUTOSELECT=YES,DEFAULT=YES
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud6",NAME="Spanish",AUTOSELECT=YES,LANGUAGE="es",URI="https://manifest-gcp-us-east4-vop1.fastly.mux.com/dpPxNR00XO3XAQzPZ85ZSUX7JSd02Y7CcXaUuoEEcALf5bRHrsoo92Wj8TRauenK4tKFjuxOJo94rfK0264HRdKFzajgI7Bx466/rendition.m3u8?cdn=cloudflare&expires=1781708400&skid=default&signature=NmEzMmJiNGVfOWQwYjNiNDlkY2Y3N2JjNGFlMDUxOTBhZWMyOGQ3ZGY2Zjk5Y2FhMzllMmJlNmZkZjY1MWM1ZmM5OThmMzk4Mg=="
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud6",NAME="Commentary",AUTOSELECT=YES,LANGUAGE="en",URI="https://manifest-gcp-us-east4-vop1.fastly.mux.com/Yg62TT57f7lvN02hoWEf02G4yJbnE1MeSfScNBNDa02sbm01C7Us901eaat02fIG5ymype1n01WUPlYVamdWL02Bq9hYAsxgFKhL1Y8i/rendition.m3u8?cdn=cloudflare&expires=1781708400&skid=default&signature=NmEzMmJiNGVfYzBiYmRkNzYyNWJmNjJkZDJmMGMyYmJiNGVjYWIzNzQzYjE3ZjM2ZGZmZDIyMTBiMTRkNjdjYzM1MzlkYjE5Mw=="
#EXT-X-STREAM-INF:BANDWIDTH=917400,AVERAGE-BANDWIDTH=917400,CODECS="mp4a.40.2,avc1.64001f",AUDIO="aud1",RESOLUTION=640x360,CLOSED-CAPTIONS=NONE,SUBTITLES="sub1"
https://manifest-gcp-us-east4-vop1.edgemv.mux.com/sBwl00pNy02PrG9W4GRrOqxohsMYWvmoZ02RYRbA57aZxs00f00Syk8YMD01ukt2oFLDqRokJrFSONKiI8OLML5zjkP1V6oyXnhL9P/rendition.m3u8?cdn=edgemv&expires=1781708400&skid=edgemv-default-1&signature=NmEzMmJiNGVfZWU1ZmJmZmYwYTJlODc1MWQ0ZjEyMmRjODg4YTRlNzY5OGY4ODcxZTkyMWY4OGJlMWQ5YTk3NTIyZGQ4YzUzOA==
#EXT-X-STREAM-INF:BANDWIDTH=917400,AVERAGE-BANDWIDTH=917400,CODECS="mp4a.40.2,avc1.64001f",AUDIO="aud2",RESOLUTION=640x360,CLOSED-CAPTIONS=NONE,SUBTITLES="sub2"
https://manifest-gcp-us-east4-vop1.edgemv.mux.com/sBwl00pNy02PrG9W4GRrOqxohsMYWvmoZ02RYRbA57aZxs00f00Syk8YMD01ukt2oFLDqRokJrFSONKiI8OLML5zjkP1V6oyXnhL9P/rendition.m3u8?cdn=fastly&expires=1781708400&skid=edgemv-default-1&signature=NmEzMmJiNGVfMGYzMGEyYzgyNTI1NzMxZjIyMGEzYzExNTIwZTkxZTE2YzFhNzBkOWVkOTNlODhhOTNiYzI1N2EzZGRmNGYwNg==
#EXT-X-STREAM-INF:BANDWIDTH=917400,AVERAGE-BANDWIDTH=917400,CODECS="mp4a.40.2,avc1.64001f",AUDIO="aud3",RESOLUTION=640x360,CLOSED-CAPTIONS=NONE,SUBTITLES="sub3"
https://manifest-gcp-us-east4-vop1.edgemv.mux.com/sBwl00pNy02PrG9W4GRrOqxohsMYWvmoZ02RYRbA57aZxs00f00Syk8YMD01ukt2oFLDqRokJrFSONKiI8OLML5zjkP1V6oyXnhL9P/rendition.m3u8?cdn=cloudflare&expires=1781708400&skid=edgemv-default-1&signature=NmEzMmJiNGVfMDJiYTQzMDg5MjFlOTQzMTk4MjFiZGYzODI3N2RlNjEyYTMwODA3Nzc0ODM3NmRhOWI1ZDk2MGRlYTIwNzhiNw==
#EXT-X-STREAM-INF:BANDWIDTH=917400,AVERAGE-BANDWIDTH=917400,CODECS="mp4a.40.2,avc1.64001f",AUDIO="aud4",RESOLUTION=640x360,CLOSED-CAPTIONS=NONE,SUBTITLES="sub4"
https://manifest-gcp-us-east4-vop1.fastly.mux.com/sBwl00pNy02PrG9W4GRrOqxohsMYWvmoZ02RYRbA57aZxs00f00Syk8YMD01ukt2oFLDqRokJrFSONKiI8OLML5zjkP1V6oyXnhL9P/rendition.m3u8?cdn=edgemv&expires=1781708400&skid=default&signature=NmEzMmJiNGVfZDI3MmMzZDNhNGU5MDEzZGM5NjY5ZWYzZmMyMjE1YTcxMWZkYWIxYTgxMTQyZGMyYzQ5YmMzOWM0N2RiMDZjZg==
#EXT-X-STREAM-INF:BANDWIDTH=917400,AVERAGE-BANDWIDTH=917400,CODECS="mp4a.40.2,avc1.64001f",AUDIO="aud5",RESOLUTION=640x360,CLOSED-CAPTIONS=NONE,SUBTITLES="sub5"
https://manifest-gcp-us-east4-vop1.fastly.mux.com/sBwl00pNy02PrG9W4GRrOqxohsMYWvmoZ02RYRbA57aZxs00f00Syk8YMD01ukt2oFLDqRokJrFSONKiI8OLML5zjkP1V6oyXnhL9P/rendition.m3u8?cdn=fastly&expires=1781708400&skid=default&signature=NmEzMmJiNGVfMzc2NWMzNGE2ODg0ZDY3NzhhMmM2ZTI0ZDM2ZDFiMGJlZjVkOTdmNWY3OWNmYzc1YzhmNzU0MGI3ZGRkNjZjMA==
#EXT-X-STREAM-INF:BANDWIDTH=917400,AVERAGE-BANDWIDTH=917400,CODECS="mp4a.40.2,avc1.64001f",AUDIO="aud6",RESOLUTION=640x360,CLOSED-CAPTIONS=NONE,SUBTITLES="sub6"
https://manifest-gcp-us-east4-vop1.fastly.mux.com/sBwl00pNy02PrG9W4GRrOqxohsMYWvmoZ02RYRbA57aZxs00f00Syk8YMD01ukt2oFLDqRokJrFSONKiI8OLML5zjkP1V6oyXnhL9P/rendition.m3u8?cdn=cloudflare&expires=1781708400&skid=default&signature=NmEzMmJiNGVfY2U0MzYwZTM3MDg2YmJiNzk5YTYzMDQ4ZDY5OWE3NjBiZWVhNjgyNWVhMjM3NTk2MGU2YWZiNTM3MGE0YWE2Yg==
#EXT-X-STREAM-INF:BANDWIDTH=595100,AVERAGE-BANDWIDTH=595100,CODECS="mp4a.40.2,avc1.64001e",AUDIO="aud1",RESOLUTION=480x270,CLOSED-CAPTIONS=NONE,SUBTITLES="sub1"
https://manifest-gcp-us-east4-vop1.edgemv.mux.com/8pH023cAaqGarBbNTH2npj16YYTWzuLLaUvdUPaOLpKUNz1GN01OuO01pcEKgxQH7On5WNyPWHL1ujCRD6mK9viGp95BV2QKpUv/rendition.m3u8?cdn=edgemv&expires=1781708400&skid=edgemv-default-1&signature=NmEzMmJiNGVfM2FmODUyMjI1OWQ4Y2Q4MTU0OGI5NTQ1NGI3ZWQyMTgyN2EwMjY5YjNmYzg1MDAzNDJhOGRiNmY1ODNmNTYyMQ==
#EXT-X-STREAM-INF:BANDWIDTH=595100,AVERAGE-BANDWIDTH=595100,CODECS="mp4a.40.2,avc1.64001e",AUDIO="aud2",RESOLUTION=480x270,CLOSED-CAPTIONS=NONE,SUBTITLES="sub2"
https://manifest-gcp-us-east4-vop1.edgemv.mux.com/8pH023cAaqGarBbNTH2npj16YYTWzuLLaUvdUPaOLpKUNz1GN01OuO01pcEKgxQH7On5WNyPWHL1ujCRD6mK9viGp95BV2QKpUv/rendition.m3u8?cdn=fastly&expires=1781708400&skid=edgemv-default-1&signature=NmEzMmJiNGVfMDA4MmE1MzgzM2VkODA2ZjhhNzljYzBjODFiNDQwNmQ4MWMxZTQ4ODYxN2Q0OWZjZmNjYzc2ZWFiOTYyZmViMA==
#EXT-X-STREAM-INF:BANDWIDTH=595100,AVERAGE-BANDWIDTH=595100,CODECS="mp4a.40.2,avc1.64001e",AUDIO="aud3",RESOLUTION=480x270,CLOSED-CAPTIONS=NONE,SUBTITLES="sub3"
https://manifest-gcp-us-east4-vop1.edgemv.mux.com/8pH023cAaqGarBbNTH2npj16YYTWzuLLaUvdUPaOLpKUNz1GN01OuO01pcEKgxQH7On5WNyPWHL1ujCRD6mK9viGp95BV2QKpUv/rendition.m3u8?cdn=cloudflare&expires=1781708400&skid=edgemv-default-1&signature=NmEzMmJiNGVfNTA4YjQyNWRmMTlhY2UwOWU5NTBmMmUyOGQ2NTQxMzhkNTA0NjYyZDFlMDI3MGNmYWM5MWU1M2QyOWQ5ODAzOQ==
#EXT-X-STREAM-INF:BANDWIDTH=595100,AVERAGE-BANDWIDTH=595100,CODECS="mp4a.40.2,avc1.64001e",AUDIO="aud4",RESOLUTION=480x270,CLOSED-CAPTIONS=NONE,SUBTITLES="sub4"
https://manifest-gcp-us-east4-vop1.fastly.mux.com/8pH023cAaqGarBbNTH2npj16YYTWzuLLaUvdUPaOLpKUNz1GN01OuO01pcEKgxQH7On5WNyPWHL1ujCRD6mK9viGp95BV2QKpUv/rendition.m3u8?cdn=edgemv&expires=1781708400&skid=default&signature=NmEzMmJiNGVfZThkNzFkNTMwNmIyZjU4MGNkZDEzYWVkYzMxNDgwYTllNmZhY2MxZDg1YzY1N2JlNWZhM2IyNzZkMzNmNjE1Yw==
#EXT-X-STREAM-INF:BANDWIDTH=595100,AVERAGE-BANDWIDTH=595100,CODECS="mp4a.40.2,avc1.64001e",AUDIO="aud5",RESOLUTION=480x270,CLOSED-CAPTIONS=NONE,SUBTITLES="sub5"
https://manifest-gcp-us-east4-vop1.fastly.mux.com/8pH023cAaqGarBbNTH2npj16YYTWzuLLaUvdUPaOLpKUNz1GN01OuO01pcEKgxQH7On5WNyPWHL1ujCRD6mK9viGp95BV2QKpUv/rendition.m3u8?cdn=fastly&expires=1781708400&skid=default&signature=NmEzMmJiNGVfOTU0YzJiNmFkY2I2NjE0NDc5ODZiMWM3Y2I4YzA3ZThmM2Y3MTg5M2Y1MGMwZjlkODdhZjc2ZDRmZDc3MzRiZA==
#EXT-X-STREAM-INF:BANDWIDTH=595100,AVERAGE-BANDWIDTH=595100,CODECS="mp4a.40.2,avc1.64001e",AUDIO="aud6",RESOLUTION=480x270,CLOSED-CAPTIONS=NONE,SUBTITLES="sub6"
https://manifest-gcp-us-east4-vop1.fastly.mux.com/8pH023cAaqGarBbNTH2npj16YYTWzuLLaUvdUPaOLpKUNz1GN01OuO01pcEKgxQH7On5WNyPWHL1ujCRD6mK9viGp95BV2QKpUv/rendition.m3u8?cdn=cloudflare&expires=1781708400&skid=default&signature=NmEzMmJiNGVfMWRmOTI1OTMxMTBkODc3ZmRjYzhlNWNlNmJlMmQwMGQ1OWI5ZjQ5NGU5NTcxZTA4MmMwZDY3NzAzZDE1YTA1ZQ==

@spuppo-mux

Copy link
Copy Markdown
Collaborator

Ok, I see, the response I get for https://stream.mux.com/ihZa7qP1zY8oyLSQW9TS602VgwQvNdyIvlk9LInEGU2s.m3u8?redundant_streams=true does not contain the edgemv urls.

As for the empty cdn name I'm seeing, I suspect it is because both default audio tracks have an implicit URL (audio comes with the video ts files) which may not be being inferred by the parser.

#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",NAME="Default",AUTOSELECT=YES,DEFAULT=YES

However, I also see those tracks in your manifest, but it sounds like you are not seeing it in cdnPriority

#EXTM3U
#EXT-X-VERSION:5
#EXT-X-INDEPENDENT-SEGMENTS

#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub1",CHARACTERISTICS="public.accessibility.transcribes-spoken-dialog,public.accessibility.describes-music-and-sound",NAME="English",AUTOSELECT=YES,DEFAULT=NO,FORCED=NO,LANGUAGE="en",URI="https://manifest-gcp-us-east4-vop1.fastly.mux.com/nuiK0102hHVlorYh8pGhDZA8zPhJxOfakGGrmaxfRrVloUoDj1H00jepXLA6w7a7YIrC7NjXZ15IUI/subtitles.m3u8?cdn=fastly&expires=1781719200&skid=default&signature=NmEzMmU2NWZfNjVhOGQ1NWVhOGRiMTUyOTk3OTFkNWQ4MTgzYjUwMTUxNGNlYjIzNmZkYWVlYjQ4OWIzM2EwYTEzOTk5YjdhNw=="
#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub1",CHARACTERISTICS="public.accessibility.transcribes-spoken-dialog,public.accessibility.describes-music-and-sound",NAME="Swedish",AUTOSELECT=YES,DEFAULT=NO,FORCED=NO,LANGUAGE="sv",URI="https://manifest-gcp-us-east4-vop1.fastly.mux.com/uj1zN4yF01nYcblfu3ak02wftTiH027hLk4N44TBnto5yijd5dpyLXAvY7pCVqGMnh8R7BmVNo007xg/subtitles.m3u8?cdn=fastly&expires=1781719200&skid=default&signature=NmEzMmU2NWZfZmM3ZGNjZjdmODUwZmU1ZWFjZDZiYWFmOWRiZDUxMTFmMWUyNzQwOGJmNWU3ZmE1MmUxYzk3MzJmZGE5YTBjZg=="
#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub1",CHARACTERISTICS="public.accessibility.transcribes-spoken-dialog,public.accessibility.describes-music-and-sound",NAME="Arabic",AUTOSELECT=YES,DEFAULT=NO,FORCED=NO,LANGUAGE="ar",URI="https://manifest-gcp-us-east4-vop1.fastly.mux.com/okdNYL00ZUd00pdgqlFOKzqP025oBEcF5VMNj00014UIeLRpNSad9XqYr56AOVtWvyyBFbRRYY01smSfY/subtitles.m3u8?cdn=fastly&expires=1781719200&skid=default&signature=NmEzMmU2NWZfMDE0Zjg5MGFjNjkwMWI1MjkxZjQyOTM1MTcwMWJhZWQzYmE1ZWY2NjMzM2Q0YTkyZmI1NTM3ZGY3MDY2NGVhOA=="
#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub2",CHARACTERISTICS="public.accessibility.transcribes-spoken-dialog,public.accessibility.describes-music-and-sound",NAME="English",AUTOSELECT=YES,DEFAULT=NO,FORCED=NO,LANGUAGE="en",URI="https://manifest-gcp-us-east4-vop1.fastly.mux.com/nuiK0102hHVlorYh8pGhDZA8zPhJxOfakGGrmaxfRrVloUoDj1H00jepXLA6w7a7YIrC7NjXZ15IUI/subtitles.m3u8?cdn=cloudflare&expires=1781719200&skid=default&signature=NmEzMmU2NWZfNTkxZDk5MDI0MzY2MmU4NDdmZDEyOWNjNTAxMzY3MDA2ZjE3YTc5Zjg3NGRhNjdkMTcxZTE2OTc5MTYyODgwZA=="
#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub2",CHARACTERISTICS="public.accessibility.transcribes-spoken-dialog,public.accessibility.describes-music-and-sound",NAME="Swedish",AUTOSELECT=YES,DEFAULT=NO,FORCED=NO,LANGUAGE="sv",URI="https://manifest-gcp-us-east4-vop1.fastly.mux.com/uj1zN4yF01nYcblfu3ak02wftTiH027hLk4N44TBnto5yijd5dpyLXAvY7pCVqGMnh8R7BmVNo007xg/subtitles.m3u8?cdn=cloudflare&expires=1781719200&skid=default&signature=NmEzMmU2NWZfZjIyMTgwMWU1ZjgxZDY4ZDc4Y2NhZjhmYWM4YmVlODg1ZGRhM2NhOTQ0MjI5MjBiNjliMjc4M2EwNTU3MTRmOA=="
#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub2",CHARACTERISTICS="public.accessibility.transcribes-spoken-dialog,public.accessibility.describes-music-and-sound",NAME="Arabic",AUTOSELECT=YES,DEFAULT=NO,FORCED=NO,LANGUAGE="ar",URI="https://manifest-gcp-us-east4-vop1.fastly.mux.com/okdNYL00ZUd00pdgqlFOKzqP025oBEcF5VMNj00014UIeLRpNSad9XqYr56AOVtWvyyBFbRRYY01smSfY/subtitles.m3u8?cdn=cloudflare&expires=1781719200&skid=default&signature=NmEzMmU2NWZfMDExMTYyMmQxNDg1ZGVhZTM1NDMyNDNkMTY5ZGQ4MWVlOTA0OWJiZTg0YjM2MjgxM2NhZDM2YmU5ZjM0MDcwNw=="
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",NAME="Default",AUTOSELECT=YES,DEFAULT=YES
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",NAME="Spanish",AUTOSELECT=YES,LANGUAGE="es",URI="https://manifest-gcp-us-east4-vop1.fastly.mux.com/dpPxNR00XO3XAQzPZ85ZSUX7JSd02Y7CcXaUuoEEcALf5bRHrsoo92Wj8TRauenK4tKFjuxOJo94rfK0264HRdKFzajgI7Bx466/rendition.m3u8?cdn=fastly&expires=1781719200&skid=default&signature=NmEzMmU2NWZfMDkzMjlmNmJmZjNlOWUzZTk1OGVhNmYxYzc1ODczNjUwZWExMzU2ODAyMmZiNWIyMTM2OTBjOWRiYjM0MjQ0OA=="
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",NAME="Commentary",AUTOSELECT=YES,LANGUAGE="en",URI="https://manifest-gcp-us-east4-vop1.fastly.mux.com/Yg62TT57f7lvN02hoWEf02G4yJbnE1MeSfScNBNDa02sbm01C7Us901eaat02fIG5ymype1n01WUPlYVamdWL02Bq9hYAsxgFKhL1Y8i/rendition.m3u8?cdn=fastly&expires=1781719200&skid=default&signature=NmEzMmU2NWZfZmYwY2Y0ZjgzOTlkNmZjNmZlZWVmMTAwYjkxZDQ4ZDAwZGZmOGQxMDJlNTM1NDlmZDUzYWViMDZjZWQ2ZTk4Mg=="
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud2",NAME="Default",AUTOSELECT=YES,DEFAULT=YES
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud2",NAME="Spanish",AUTOSELECT=YES,LANGUAGE="es",URI="https://manifest-gcp-us-east4-vop1.fastly.mux.com/dpPxNR00XO3XAQzPZ85ZSUX7JSd02Y7CcXaUuoEEcALf5bRHrsoo92Wj8TRauenK4tKFjuxOJo94rfK0264HRdKFzajgI7Bx466/rendition.m3u8?cdn=cloudflare&expires=1781719200&skid=default&signature=NmEzMmU2NWZfZWZlYzI0NDAzNThjODQ1MmI1NDcxZGZlMzE3YjQ1ODU5OTdjMmU5NmY1ODlmOGEyNGM1NjhkM2ZlNzliNmMwZg=="
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud2",NAME="Commentary",AUTOSELECT=YES,LANGUAGE="en",URI="https://manifest-gcp-us-east4-vop1.fastly.mux.com/Yg62TT57f7lvN02hoWEf02G4yJbnE1MeSfScNBNDa02sbm01C7Us901eaat02fIG5ymype1n01WUPlYVamdWL02Bq9hYAsxgFKhL1Y8i/rendition.m3u8?cdn=cloudflare&expires=1781719200&skid=default&signature=NmEzMmU2NWZfMzMzNDJmNmRhZGE3YWQ0Nzc5Mzc2ODhjNzM5YTM0NjI1M2E2YTYzOTA5MDcxOTRjNWJhOGVmMjY0NGEzNmM5MA=="
#EXT-X-STREAM-INF:BANDWIDTH=917400,AVERAGE-BANDWIDTH=917400,CODECS="mp4a.40.2,avc1.64001f",AUDIO="aud1",RESOLUTION=640x360,CLOSED-CAPTIONS=NONE,SUBTITLES="sub1"
https://manifest-gcp-us-east4-vop1.fastly.mux.com/sBwl00pNy02PrG9W4GRrOqxohsMYWvmoZ02RYRbA57aZxs00f00Syk8YMD01ukt2oFLDqRokJrFSONKiI8OLML5zjkP1V6oyXnhL9P/rendition.m3u8?cdn=fastly&expires=1781719200&skid=default&signature=NmEzMmU2NWZfZTVhODA3ZWU1ODExMTg2MzE1MmY5NzM0ZTY2MDM2OWY3MThmN2FiZmJkNDY1ZWU2ZWI2NmY2NjYxZDY3NzM0OQ==
#EXT-X-STREAM-INF:BANDWIDTH=917400,AVERAGE-BANDWIDTH=917400,CODECS="mp4a.40.2,avc1.64001f",AUDIO="aud2",RESOLUTION=640x360,CLOSED-CAPTIONS=NONE,SUBTITLES="sub2"
https://manifest-gcp-us-east4-vop1.fastly.mux.com/sBwl00pNy02PrG9W4GRrOqxohsMYWvmoZ02RYRbA57aZxs00f00Syk8YMD01ukt2oFLDqRokJrFSONKiI8OLML5zjkP1V6oyXnhL9P/rendition.m3u8?cdn=cloudflare&expires=1781719200&skid=default&signature=NmEzMmU2NWZfYzI5YmMzMDFmMzI3YTM2NTcwZDljMDJhN2RlYmM2MTcwYjQxMjcxNTBmZjFiYjlmMTQ0NGRmNzZkMjIzNTUyNA==
#EXT-X-STREAM-INF:BANDWIDTH=595100,AVERAGE-BANDWIDTH=595100,CODECS="mp4a.40.2,avc1.64001e",AUDIO="aud1",RESOLUTION=480x270,CLOSED-CAPTIONS=NONE,SUBTITLES="sub1"
https://manifest-gcp-us-east4-vop1.fastly.mux.com/8pH023cAaqGarBbNTH2npj16YYTWzuLLaUvdUPaOLpKUNz1GN01OuO01pcEKgxQH7On5WNyPWHL1ujCRD6mK9viGp95BV2QKpUv/rendition.m3u8?cdn=fastly&expires=1781719200&skid=default&signature=NmEzMmU2NWZfNzQ0MDc2Njk5NDRlMGJkOGRiYjQ5YjE5MzhhMDUxMDgzYzUzYjMzZTE2YzdiYjU4Mzk4YzJmNjEyMTkxMTc1NQ==
#EXT-X-STREAM-INF:BANDWIDTH=595100,AVERAGE-BANDWIDTH=595100,CODECS="mp4a.40.2,avc1.64001e",AUDIO="aud2",RESOLUTION=480x270,CLOSED-CAPTIONS=NONE,SUBTITLES="sub2"
https://manifest-gcp-us-east4-vop1.fastly.mux.com/8pH023cAaqGarBbNTH2npj16YYTWzuLLaUvdUPaOLpKUNz1GN01OuO01pcEKgxQH7On5WNyPWHL1ujCRD6mK9viGp95BV2QKpUv/rendition.m3u8?cdn=cloudflare&expires=1781719200&skid=default&signature=NmEzMmU2NWZfYWRlZWE2M2Y1MmVhNGNkMWFmMDQxYTliODRiOTBiMmYxMTQyYzdlOTZmNjE1YTgzZDA1MjRiMDc5MWRmNTU5Yg==

cjpillsbury and others added 2 commits June 10, 2026 11:49
onCdn read like a callback/event-listener; it's the candidate tracks
served from a given CDN. Rename for clarity.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
getOrderedCdnIds iterated selectionSets in parse order, so "video CDN is
primary" held only as a side effect of how the HLS multivariant parser
happens to order tracks — not a guarantee. preferActiveCdn anchors every
track type to the head of cdnPriority, so an audio-first manifest could
silently make an audio CDN primary.

Sort selection sets by track-type priority (video, then audio, then text)
before collecting CDN ids; a stable sort preserves manifest order within a
type. The head of cdnPriority is now video-derived by construction.

Strengthen the engine test to a doubly-adversarial source (audio set
listed first, cdn-b first within it) so it depends on the ordering rather
than array order, and add a getOrderedCdnIds unit test pinning it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@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 1 potential issue.

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 6af8f5f. Configure here.

if (tracksUsingCdn.length) return tracksUsingCdn;
}
return tracks;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Per-type CDN scope splits hosts

High Severity

preferActiveCdn walks cdnPriority and returns the first entry that has matching candidates in the current type’s track list only. Video and audio each run that logic independently, so if the primary CDN has video renditions but no audio candidates (e.g. a single default #EXT-X-MEDIA row or implicit audio not duplicated per CDN), audio can settle on the next CDN while video stays on the primary—segment requests split across hosts despite the sticky single-CDN goal.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 6af8f5f. 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.

2 participants