feat(hls): add native HLS (m3u8) download protocol

New hls protocol in internal/protocol/hls (registered ahead of http):
- master playlist variant picking (highest bandwidth), RFC3986 segment
  URL resolution, fMP4 init segments, EXT-X-BYTERANGE, EXT-X-GAP skip
- AES-128 decryption with mid-playlist key rotation and sequence-derived
  IVs; explicit errors for live streams, SAMPLE-AES and multi-period fMP4
- concurrent segment downloads with retry, journal-based resume across
  pause/app restarts, automatic in-order merge into a single .ts/.mp4
- best-effort Content-Length probing for determinate progress
- anti-hotlink Referer/Cookie/UA via task extra headers (Go transport
  does not filter them)

Core: fix SchemeFilter extension match for URLs with query strings
(also benefits .torrent links); Flutter: Protocol.hls enum, hls protocol
config model (prevents config round-trip wipe) and HLS settings block
with localized labels in all 22 locales.

Tests: parser/crypto unit tests, httptest e2e (Referer-enforced CDN,
encrypted streams, byterange, pause/resume, store/restore) and an
opt-in live-site acceptance gate (HLS_LIVE_URL).
This commit is contained in:
sun committed 2026-09-14 09:55:31 +08:00
1 parent e5ac13cfdc
commit 4f74101b3a
44 files changed
+2824 -6

No files matched your search

+7 -1
View File
@@ -106,7 +106,13 @@ func (s *SchemeFilter) Match(uri string) bool {
case FilterTypeUrl:
return strings.HasPrefix(uriUpper, patternUpper+":")
case FilterTypeFile:
return strings.HasSuffix(uriUpper, "."+patternUpper)
// Strip query and fragment before matching so URLs like
// https://example.com/video.m3u8?sign=abc still route by extension.
clean := uri
if idx := strings.IndexAny(clean, "?#"); idx >= 0 {
clean = clean[:idx]
}
return strings.HasSuffix(strings.ToUpper(clean), "."+patternUpper)
case FilterTypeBase64:
return strings.HasPrefix(uriUpper, "DATA:"+patternUpper+";BASE64,")
}