Skip to content

Preserve existing query string when adding HLS delivery directives - #7969

Merged
robwalch merged 2 commits into
video-dev:masterfrom
CarlLindqvist:bugfix/preserve-query-params-in-delivery-directives
Aug 7, 2026
Merged

Preserve existing query string when adding HLS delivery directives#7969
robwalch merged 2 commits into
video-dev:masterfrom
CarlLindqvist:bugfix/preserve-query-params-in-delivery-directives

Conversation

@CarlLindqvist

Copy link
Copy Markdown
Contributor

This PR will...

Stop HlsUrlParameters.addDirectives from re-encoding query parameters it did not add, so signed playlist URLs survive Playlist Delta Update and blocking reload requests.

Why is this Pull Request needed?

addDirectives sets _HLS_msn / _HLS_part / _HLS_skip through url.searchParams. Reading url.href afterwards re-serializes the whole query using application/x-www-form-urlencoded rules, so characters that are perfectly legal in a query (~, =, /) come back percent-encoded in parameters hls.js never touched.

Given a playlist URL carrying a signed token:

https://example.com/media.m3u8?token=st=1600000000~exp=1600003600~acl=/live/*~hmac=abc123

a delta update request currently becomes:

https://example.com/media.m3u8?token=st%3D1600000000%7Eexp%3D1600003600%7Eacl%3D%2Flive%2F*%7Ehmac%3Dabc123&_HLS_skip=YES

CDNs that sign URLs compare the token byte-for-byte, so the request is rejected with 403 and live playback stalls on the first reload. Akamai tokens are the common case; CloudFront is affected too, since its URL-safe base64 maps / to ~, putting ~ in every Policy and Signature value. Native HLS implementations do not rewrite the URL, so affected streams play fine outside hls.js.

With this change the request keeps the token intact:

https://example.com/media.m3u8?token=st=1600000000~exp=1600003600~acl=/live/*~hmac=abc123&_HLS_skip=YES

#3786 reported this in 2021. #3787 fixed the parameter reordering half by removing searchParams.sort(), but url.searchParams.set() still forces a full re-serialization when href is read, so the re-encoding half remained.

Are there any points in the code the reviewer needs to double check?

  • Directives are appended as text, with any directive already present filtered out first, so repeated reloads replace rather than duplicate, matching searchParams.set(). The one behavioural difference is position: a replaced directive moves to the end of the query instead of keeping its original slot. Since searchParams.sort() was removed in Direct set SearchParams on URL and remove sort. #3787, query order is not normalized either way.
  • The fix relies on url.search = ... not percent-encoding ~, =, / or *, as those are outside the URL query percent-encode set. Worth a second pair of eyes.
  • HlsSkip.No is the empty string and stays falsy, and msn / part of 0 are still emitted. Both are covered by tests.

Resolves issues:

Completes the fix for #3786, partially addressed by #3787.

Checklist

  • changes have been done against master branch, and PR does not conflict
  • new unit / functional tests have been added (whenever applicable)
  • API or design changes are documented in API.md — not applicable, no API or design change

Mutating url.searchParams and then reading url.href re-serializes the
entire query using application/x-www-form-urlencoded rules. That
percent-encodes `~`, `=` and `/` in query parameters hls.js was not
asked to touch. Signed CDN URLs carry those characters verbatim and
validate the request byte-for-byte, so every playlist reload that adds
_HLS_msn, _HLS_part or _HLS_skip is rejected with 403 and live playback
stalls.

Build the query as text instead, filtering out any directive already
present so repeated reloads replace rather than duplicate.

video-dev#3787 addressed the parameter reordering half of video-dev#3786 by dropping
searchParams.sort(), but the re-encoding remained.
Comment thread src/types/level.ts Outdated
…rams.set

Extracts the query-string-preserving logic from HlsUrlParameters.addDirectives
into a setQueryParam export in utils/url-tools and uses it everywhere _HLS_
parameters are appended (content-steering-controller, interstitial-event,
interstitial-asset-list). Adds a lint rule disallowing URLSearchParams.set,
which re-encodes the whole query string and breaks signed CDN tokens.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@robwalch
robwalch merged commit dac0275 into video-dev:master Aug 7, 2026
12 checks passed
@github-project-automation github-project-automation Bot moved this from Top priorities to Done in HLS.js Release Planning and Backlog Aug 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants