Skip to content

MP4 range/preload fixes + download priority controls#52

Open
hkm5558 wants to merge 8 commits into
windows7lake:mainfrom
hkm5558:feature/precache-download-priority
Open

MP4 range/preload fixes + download priority controls#52
hkm5558 wants to merge 8 commits into
windows7lake:mainfrom
hkm5558:feature/precache-download-priority

Conversation

@hkm5558

@hkm5558 hkm5558 commented Jun 25, 2026

Copy link
Copy Markdown

Summary

Improves MP4 HTTP range handling and gives callers control over precache download scheduling. Four focused changes plus two correctness fixes found while reconciling with the latest main.

1. Fix MP4 range / preload behavior (url_parser_mp4.dart)

  • Answer every Range request with 206. The old partial = requestRangeStart > 0 || requestRangeEnd > 0 returned 200 for Range: bytes=0-. iOS AVPlayer issues bytes=0- on normal startup; a 200 makes the proxy look non-range-capable and breaks seeking. Now partial = rangeMatch != null, so any request with a Range header gets 206 + Content-Range.
  • Centralize range math in a small Mp4RangeResponse helper that derives content-length and content-range from the real total length (open-ended bytes=N- resolves end to total-1), removing the duplicated header construction in the iOS path.
  • Warm the next segment even on a cache hit. concurrent(...) now runs before serving the current segment, so a cached current segment still warms the next one (previously "preload" didn't help the next swipe).

2. Promote duplicate download task priority (download_pool.dart)

When addTask/executeTask see a task whose cache key is already queued, they promote the existing task's priority (keeping the same task object so in-flight listeners stay attached) instead of dropping the request or removing-and-re-adding.

3. Fall back when the native dio adapter is unavailable (download_pool.dart)

Some simulator/runtime combinations can't load native_dio_adapter's native library. _createHttpClientAdapter() now catches the failure and falls back to the default Dio adapter (which still supports the Range requests used here) instead of crashing pool construction.

4. Expose precache download priority (VideoCaching.precache, UrlParser)

Adds an optional priority (default 1, matching DownloadTask's default — fully backward compatible) and threads it through to the created DownloadTasks in all parsers (default / m3u8 / mp4). Higher-priority tasks are scheduled before lower-priority queued preloads, so the visible video can outrank background warming.

Correctness fixes during review

  • iOS open-ended range clamp. Reconciling the new Mp4RangeResponse refactor with the recent last-segment clamp left parseIOS clamping endRange against the raw requestRangeEnd, which is no longer rewritten to contentLength-1. For bytes=0- (requestRangeEnd == -1) the first segment's endRange became -1. Now clamps against rangeResponse.end, consistent with the sublist bounds in the same loop. (parseAndroid is unaffected — it still sets requestRangeEnd = contentLength-1.)
  • Segment-warming termination guard. concurrent() only terminated via the contentLength > 0 boundary check. A failed head() (contentLength <= 0) plus an already-cached tail left no upper bound and could spin the loop. Now bails when contentLength <= 0.

Tests

  • test/parser/url_parser_mp4_test.dart: Mp4RangeResponse metadata (open-ended / two-byte probe / non-range) and precache content-length + priority threading.
  • test/download/download_pool_test.dart: duplicate-task priority promotion.
  • dart analyze: no issues. All added tests pass.

Note: one pre-existing isolate test (DownloadIsolatePool notifyIsolate can pause and resume) is environment-dependent (it relies on a live download surviving ~1s while the test HTTP stack returns 400) and also fails on main — not introduced here.

🤖 Generated with Claude Code

kunming and others added 8 commits May 13, 2026 12:29
# Conflicts:
#	lib/parser/url_parser_mp4.dart
The merge of upstream's last-segment clamp (4d2a93f) with the
Mp4RangeResponse refactor left parseIOS clamping endRange against the
raw requestRangeEnd, which the refactor no longer rewrites to
contentLength-1. For open-ended iOS requests (bytes=0-, requestRangeEnd
== -1) the first segment's endRange was clamped to -1, breaking the
download. Clamp against rangeResponse.end, consistent with the sublist
bounds in the same loop.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
concurrent() only terminated via the contentLength>0 boundary guard. If
head() failed (contentLength<=0) and the remaining segments were already
cached/in-flight (isExit stays true), activeSize never grew and
nextStartRange had no upper bound, spinning the loop forever. Bail when
contentLength<=0.

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

1 participant