diff --git a/.beads/interactions.jsonl b/.beads/interactions.jsonl index 4085860..ce73f0e 100644 --- a/.beads/interactions.jsonl +++ b/.beads/interactions.jsonl @@ -43,3 +43,4 @@ {"id":"int-79713eba","kind":"field_change","created_at":"2026-05-25T21:55:32.323229Z","actor":"dirtydishes","issue_id":"dreamio-6bv","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"handled VLC buffering follow-up by supporting HEAD probes, moving fetch work off listener queue, reducing foreground range size, and locking cache access"}} {"id":"int-b2667330","kind":"field_change","created_at":"2026-05-25T23:44:07.439593Z","actor":"dirtydishes","issue_id":"dreamio-9gw","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Capped local range-cache responses to 1 MB chunks, trimmed cached overlap windows, added focused tests, and confirmed the iOS simulator build succeeds."}} {"id":"int-6ca684f7","kind":"field_change","created_at":"2026-05-26T04:00:46.072019Z","actor":"dirtydishes","issue_id":"dreamio-42s","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Fixed seek-time range-cache prefetching to prioritize the post-seek byte offset and avoid cancelling active prefetch work inside the same window; added focused coverage and validated with StreamResolverTests plus xcodebuild."}} +{"id":"int-176e3ad2","kind":"field_change","created_at":"2026-05-26T04:14:19.812849Z","actor":"dirtydishes","issue_id":"dreamio-meh","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Fixed range-cache prefetch reprioritization so foreground VLC misses cancel stale speculative work and restart around VLC's actual requested byte range; added regression coverage for the observed jump mismatch."}} diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index 370b1cc..9ddaebb 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -1,5 +1,6 @@ {"_type":"issue","id":"dreamio-mun","title":"fix vlc cache loopback port startup","description":"Device logs showed local-cache playback opening http://127.0.0.1:0, because the NWListener ephemeral port was read before the listener reached ready. Wait for the real assigned port before returning the local cache URL.","status":"closed","priority":0,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-25T22:32:41Z","created_by":"dirtydishes","updated_at":"2026-05-25T22:33:15Z","started_at":"2026-05-25T22:33:14Z","closed_at":"2026-05-25T22:33:15Z","close_reason":"Wait for NWListener ready state before returning the local cache URL; verified tests and simulator build.","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"dreamio-8cz","title":"fix stremio external subtitle loading regression","description":"After adding late subtitle forwarding for native playback, Stremio external subtitle loading is failing. Investigate the injected bridge and native subtitle forwarding path, then adjust behavior so Stremio can still load external subtitles while native playback receives late candidates.","status":"closed","priority":0,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-25T11:05:42Z","created_by":"dirtydishes","updated_at":"2026-05-25T11:07:35Z","started_at":"2026-05-25T11:05:55Z","closed_at":"2026-05-25T11:07:35Z","close_reason":"Hardened subtitle bridge network observers so non-text Stremio subtitle loads are not touched, and made parser traversal deterministic for metadata preservation.","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"dreamio-meh","title":"Use VLC range requests to reprioritize seek prefetch","description":"Jump logs show duration-based byteOffset estimates can be far behind VLC's actual post-seek range requests, so prefetch keeps warming stale bytes while VLC buffers on higher cache misses.","status":"closed","priority":1,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-26T04:11:38Z","created_by":"dirtydishes","updated_at":"2026-05-26T04:14:20Z","started_at":"2026-05-26T04:11:40Z","closed_at":"2026-05-26T04:14:20Z","close_reason":"Fixed range-cache prefetch reprioritization so foreground VLC misses cancel stale speculative work and restart around VLC's actual requested byte range; added regression coverage for the observed jump mismatch.","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"dreamio-42s","title":"Reduce VLC range-cache buffering after seeks","description":"Logs show repeated local-cache misses and cancelled prefetch tasks after VLC jumps backward, causing buffering while the cache restarts speculative requests instead of preserving useful adjacent downloads.","status":"closed","priority":1,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-26T03:58:03Z","created_by":"dirtydishes","updated_at":"2026-05-26T04:00:46Z","started_at":"2026-05-26T03:58:10Z","closed_at":"2026-05-26T04:00:46Z","close_reason":"Fixed seek-time range-cache prefetching to prioritize the post-seek byte offset and avoid cancelling active prefetch work inside the same window; added focused coverage and validated with StreamResolverTests plus xcodebuild.","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"dreamio-9gw","title":"Cap VLC local range cache memory","description":"Playback can be killed for memory when VLC asks the loopback cache for a very large byte range. The local range cache should answer with bounded partial ranges and trim cached segments to the active window.","status":"closed","priority":1,"issue_type":"bug","owner":"dishes@dpdrm.com","created_at":"2026-05-25T23:38:08Z","created_by":"dirtydishes","updated_at":"2026-05-25T23:44:07Z","closed_at":"2026-05-25T23:44:07Z","close_reason":"Capped local range-cache responses to 1 MB chunks, trimmed cached overlap windows, added focused tests, and confirmed the iOS simulator build succeeds.","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"dreamio-4t0","title":"Fix native external subtitle overlay fallback","description":"Parsed external subtitles are discovered but MobileVLCKit may report no imported subtitle tracks. Make Dreamio's parsed subtitle overlay the reliable fallback and add parser/overlay coverage.","status":"closed","priority":1,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-25T23:23:15Z","created_by":"dirtydishes","updated_at":"2026-05-25T23:28:44Z","started_at":"2026-05-25T23:23:18Z","closed_at":"2026-05-25T23:28:44Z","close_reason":"Implemented parsed external subtitle overlay fallback, parser extraction, focused parser tests, and simulator build validation.","dependency_count":0,"dependent_count":0,"comment_count":0} diff --git a/Dreamio/ProgressiveHTTPRangeCache.swift b/Dreamio/ProgressiveHTTPRangeCache.swift index 73b7065..a950b71 100644 --- a/Dreamio/ProgressiveHTTPRangeCache.swift +++ b/Dreamio/ProgressiveHTTPRangeCache.swift @@ -259,6 +259,7 @@ final class ProgressiveHTTPRangeCacheSession { private let responseChunkSize: Int64 = 1_048_576 private var prefetchTask: Task? private var activePrefetchWindow: HTTPByteRange? + private var activePrefetchPreferredOffset: Int64? init(fetcher: HTTPRangeRemoteFetcher, contentLength: Int64, durationProvider: @escaping () -> TimeInterval) { self.fetcher = fetcher @@ -266,6 +267,10 @@ final class ProgressiveHTTPRangeCacheSession { self.durationProvider = durationProvider } + deinit { + cancelPrefetch() + } + func data(for requestedRange: HTTPByteRange) async throws -> Data { let bounded = clamp(requestedRange) if let data = store.data(for: bounded) { @@ -278,9 +283,10 @@ final class ProgressiveHTTPRangeCacheSession { #if DEBUG print("[DreamioRangeCache] cache=miss range=\(bounded.start)-\(bounded.end)") #endif + cancelPrefetchIfNeeded(forForegroundRange: bounded) let data = try await fetcher.fetch(range: bounded) store.insert(data: data, at: bounded.start) - prefetch(aroundByteOffset: bounded.end + 1) + prefetch(aroundByteOffset: bounded.end + 1, forceRestart: true) return store.data(for: bounded) ?? data } @@ -293,16 +299,22 @@ final class ProgressiveHTTPRangeCacheSession { } func prefetch(aroundByteOffset offset: Int64) { - if activePrefetchWindow?.contains(offset) == true, prefetchTask?.isCancelled == false { + prefetch(aroundByteOffset: offset, forceRestart: false) + } + + func prefetch(aroundByteOffset offset: Int64, forceRestart: Bool) { + if !forceRestart, activePrefetchWindow?.contains(offset) == true, prefetchTask?.isCancelled == false { return } prefetchTask?.cancel() let window = targetWindow(aroundByteOffset: offset) activePrefetchWindow = window + activePrefetchPreferredOffset = offset store.evict(keeping: window) guard !store.hasData(for: window) else { activePrefetchWindow = nil + activePrefetchPreferredOffset = nil return } @@ -333,14 +345,33 @@ final class ProgressiveHTTPRangeCacheSession { } } self.activePrefetchWindow = nil + self.activePrefetchPreferredOffset = nil } } + func cancelPrefetch() { + prefetchTask?.cancel() + activePrefetchWindow = nil + activePrefetchPreferredOffset = nil + } + func byteOffset(for position: Float) -> Int64 { let clamped = max(0, min(1, position)) return Int64(Float(contentLength) * clamped) } + private func cancelPrefetchIfNeeded(forForegroundRange range: HTTPByteRange) { + guard activePrefetchWindow?.contains(range.start) == true, + let preferredOffset = activePrefetchPreferredOffset, + abs(range.start - preferredOffset) >= responseChunkSize else { + return + } +#if DEBUG + print("[DreamioRangeCache] prefetch reprioritize from=\(preferredOffset) to=\(range.start)") +#endif + cancelPrefetch() + } + private func targetWindow(aroundByteOffset offset: Int64) -> HTTPByteRange { let bytesPerSecond = estimatedBytesPerSecond() let behind = max(prefetchChunkSize, bytesPerSecond * 30) diff --git a/Tests/StreamResolverTests.swift b/Tests/StreamResolverTests.swift index 9a390ec..3a00f2c 100644 --- a/Tests/StreamResolverTests.swift +++ b/Tests/StreamResolverTests.swift @@ -37,6 +37,7 @@ struct StreamResolverTests { testSparseRangeStoreTrimsOverlappingWindow() testRangeCacheSessionCapsResponseRange() testRangeCachePrefetchPrioritizesSeekOffset() + await testRangeCacheForegroundMissReprioritizesPrefetch() await testRangeProbeFallsBackWhenServerIgnoresRange() await testRangeFetcherPreservesHeaders() print("StreamResolverTests passed") @@ -367,6 +368,62 @@ struct StreamResolverTests { ]) } + private static func testRangeCacheForegroundMissReprioritizesPrefetch() async { + let queue = DispatchQueue(label: "dreamio.range-cache-test") + var requestedRanges: [String] = [] + MockURLProtocol.handler = { request in + let range = request.value(forHTTPHeaderField: "Range") ?? "" + queue.sync { + requestedRanges.append(range) + } + let byteRange = byteRange(fromHeader: range, contentLength: 80_000_000) + let response = HTTPURLResponse( + url: request.url!, + statusCode: 206, + httpVersion: nil, + headerFields: ["Content-Range": "bytes \(byteRange.start)-\(byteRange.end)/80000000"] + )! + return (Data(repeating: 1, count: Int(byteRange.length)), response) + } + + let session = ProgressiveHTTPRangeCacheSession( + fetcher: HTTPRangeRemoteFetcher( + url: URL(string: "https://cdn.example.test/movie.mp4")!, + headers: [:], + session: mockSession() + ), + contentLength: 80_000_000, + durationProvider: { 100 } + ) + defer { + session.cancelPrefetch() + } + + session.prefetch(aroundByteOffset: 28_242_716) + _ = try? await session.data(for: HTTPByteRange(start: 51_818_977, end: 52_867_552)) + try? await Task.sleep(nanoseconds: 50_000_000) + + let ranges = queue.sync { requestedRanges } + assert(ranges.contains("bytes=51818977-52867552"), "Expected foreground VLC range to be fetched") + assert(ranges.contains { range in + range.hasPrefix("bytes=51936225-") + }, "Expected prefetch to restart near VLC's foreground range, got \(ranges)") + session.cancelPrefetch() + MockURLProtocol.handler = nil + try? await Task.sleep(nanoseconds: 50_000_000) + } + + private static func byteRange(fromHeader header: String, contentLength: Int64) -> HTTPByteRange { + let value = header.replacingOccurrences(of: "bytes=", with: "") + let pieces = value.split(separator: "-", maxSplits: 1).map(String.init) + guard pieces.count == 2, + let start = Int64(pieces[0]) else { + return HTTPByteRange(start: 0, end: 0) + } + let end = pieces[1].isEmpty ? contentLength - 1 : (Int64(pieces[1]) ?? contentLength - 1) + return HTTPByteRange(start: start, end: min(end, contentLength - 1)) + } + private static func testRangeProbeFallsBackWhenServerIgnoresRange() async { MockURLProtocol.handler = { request in if request.httpMethod == "HEAD" { diff --git a/docs/turns/2026-05-25-vlc-local-range-cache.html b/docs/turns/2026-05-25-vlc-local-range-cache.html index ae398c9..4071930 100644 --- a/docs/turns/2026-05-25-vlc-local-range-cache.html +++ b/docs/turns/2026-05-25-vlc-local-range-cache.html @@ -575,6 +575,158 @@
let chunks = session.prefetchChunks(
in: HTTPByteRange(start: 0, end: 4_194_303),
preferredOffset: 2_200_000
)
assertEqual(chunks.prefix(2).map { $0 }, [
HTTPByteRange(start: 2_097_152, end: 3_145_727),
HTTPByteRange(start: 3_145_728, end: 4_194_303)
])
assertEqual(chunks.suffix(2).map { $0 }, [
HTTPByteRange(start: 0, end: 1_048_575),
HTTPByteRange(start: 1_048_576, end: 2_097_151)
])
}
private static func testRangeProbeFallsBackWhenServerIgnoresRange() async {
MockURLProtocol.handler = { request in
if request.httpMethod == "HEAD" {

Related issues or PRs

Related to Beads issue dreamio-42s and branch lavender/vlc-local-range-cache.

+

New Changes as of 2026-05-26 00:14 EDT

Summary of changes

Changed foreground cache misses from VLC into a stronger prefetch signal. When VLC asks for a real range that is far from the current prefetch cursor, Dreamio now cancels the stale speculative task and restarts prefetching beside VLC's actual requested bytes.

Why this change was made

The jump logs showed duration-based seek estimates could land around 28M while VLC immediately requested ranges around 52M. Because those real ranges were still inside the broad prefetch window, the cache previously kept fetching old chunks and left VLC buffering on repeated misses.

Code diffs

Rendered with @pierre/diffs/ssr. These diffs cover foreground miss reprioritization and the regression test that reproduces the observed jump pattern.

Dreamio/ProgressiveHTTPRangeCache.swift

Dreamio/ProgressiveHTTPRangeCache.swift
-2+33
258 unmodified lines
259
260
261
262
263
264
1 unmodified line
266
267
268
269
270
271
6 unmodified lines
278
279
280
281
282
283
284
285
286
6 unmodified lines
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
24 unmodified lines
333
334
335
336
337
338
339
340
341
342
343
344
345
346
258 unmodified lines
private let responseChunkSize: Int64 = 1_048_576
private var prefetchTask: Task<Void, Never>?
private var activePrefetchWindow: HTTPByteRange?
+
init(fetcher: HTTPRangeRemoteFetcher, contentLength: Int64, durationProvider: @escaping () -> TimeInterval) {
self.fetcher = fetcher
1 unmodified line
self.durationProvider = durationProvider
}
+
func data(for requestedRange: HTTPByteRange) async throws -> Data {
let bounded = clamp(requestedRange)
if let data = store.data(for: bounded) {
6 unmodified lines
#if DEBUG
print("[DreamioRangeCache] cache=miss range=\(bounded.start)-\(bounded.end)")
#endif
let data = try await fetcher.fetch(range: bounded)
store.insert(data: data, at: bounded.start)
prefetch(aroundByteOffset: bounded.end + 1)
return store.data(for: bounded) ?? data
}
+
6 unmodified lines
}
+
func prefetch(aroundByteOffset offset: Int64) {
if activePrefetchWindow?.contains(offset) == true, prefetchTask?.isCancelled == false {
return
}
+
prefetchTask?.cancel()
let window = targetWindow(aroundByteOffset: offset)
activePrefetchWindow = window
store.evict(keeping: window)
guard !store.hasData(for: window) else {
activePrefetchWindow = nil
return
}
+
24 unmodified lines
}
}
self.activePrefetchWindow = nil
}
}
+
func byteOffset(for position: Float) -> Int64 {
let clamped = max(0, min(1, position))
return Int64(Float(contentLength) * clamped)
}
+
private func targetWindow(aroundByteOffset offset: Int64) -> HTTPByteRange {
let bytesPerSecond = estimatedBytesPerSecond()
let behind = max(prefetchChunkSize, bytesPerSecond * 30)
258 unmodified lines
259
260
261
262
263
264
265
1 unmodified line
267
268
269
270
271
272
273
274
275
276
6 unmodified lines
283
284
285
286
287
288
289
290
291
292
6 unmodified lines
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
24 unmodified lines
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
258 unmodified lines
private let responseChunkSize: Int64 = 1_048_576
private var prefetchTask: Task<Void, Never>?
private var activePrefetchWindow: HTTPByteRange?
private var activePrefetchPreferredOffset: Int64?
+
init(fetcher: HTTPRangeRemoteFetcher, contentLength: Int64, durationProvider: @escaping () -> TimeInterval) {
self.fetcher = fetcher
1 unmodified line
self.durationProvider = durationProvider
}
+
deinit {
cancelPrefetch()
}
+
func data(for requestedRange: HTTPByteRange) async throws -> Data {
let bounded = clamp(requestedRange)
if let data = store.data(for: bounded) {
6 unmodified lines
#if DEBUG
print("[DreamioRangeCache] cache=miss range=\(bounded.start)-\(bounded.end)")
#endif
cancelPrefetchIfNeeded(forForegroundRange: bounded)
let data = try await fetcher.fetch(range: bounded)
store.insert(data: data, at: bounded.start)
prefetch(aroundByteOffset: bounded.end + 1, forceRestart: true)
return store.data(for: bounded) ?? data
}
+
6 unmodified lines
}
+
func prefetch(aroundByteOffset offset: Int64) {
prefetch(aroundByteOffset: offset, forceRestart: false)
}
+
func prefetch(aroundByteOffset offset: Int64, forceRestart: Bool) {
if !forceRestart, activePrefetchWindow?.contains(offset) == true, prefetchTask?.isCancelled == false {
return
}
+
prefetchTask?.cancel()
let window = targetWindow(aroundByteOffset: offset)
activePrefetchWindow = window
activePrefetchPreferredOffset = offset
store.evict(keeping: window)
guard !store.hasData(for: window) else {
activePrefetchWindow = nil
activePrefetchPreferredOffset = nil
return
}
+
24 unmodified lines
}
}
self.activePrefetchWindow = nil
self.activePrefetchPreferredOffset = nil
}
}
+
func cancelPrefetch() {
prefetchTask?.cancel()
activePrefetchWindow = nil
activePrefetchPreferredOffset = nil
}
+
func byteOffset(for position: Float) -> Int64 {
let clamped = max(0, min(1, position))
return Int64(Float(contentLength) * clamped)
}
+
private func cancelPrefetchIfNeeded(forForegroundRange range: HTTPByteRange) {
guard activePrefetchWindow?.contains(range.start) == true,
let preferredOffset = activePrefetchPreferredOffset,
abs(range.start - preferredOffset) >= responseChunkSize else {
return
}
#if DEBUG
print("[DreamioRangeCache] prefetch reprioritize from=\(preferredOffset) to=\(range.start)")
#endif
cancelPrefetch()
}
+
private func targetWindow(aroundByteOffset offset: Int64) -> HTTPByteRange {
let bytesPerSecond = estimatedBytesPerSecond()
let behind = max(prefetchChunkSize, bytesPerSecond * 30)

Tests/StreamResolverTests.swift

Tests/StreamResolverTests.swift
+57
36 unmodified lines
37
38
39
40
41
42
324 unmodified lines
367
368
369
370
371
372
36 unmodified lines
testSparseRangeStoreTrimsOverlappingWindow()
testRangeCacheSessionCapsResponseRange()
testRangeCachePrefetchPrioritizesSeekOffset()
await testRangeProbeFallsBackWhenServerIgnoresRange()
await testRangeFetcherPreservesHeaders()
print("StreamResolverTests passed")
324 unmodified lines
])
}
+
private static func testRangeProbeFallsBackWhenServerIgnoresRange() async {
MockURLProtocol.handler = { request in
if request.httpMethod == "HEAD" {
36 unmodified lines
37
38
39
40
41
42
43
324 unmodified lines
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
36 unmodified lines
testSparseRangeStoreTrimsOverlappingWindow()
testRangeCacheSessionCapsResponseRange()
testRangeCachePrefetchPrioritizesSeekOffset()
await testRangeCacheForegroundMissReprioritizesPrefetch()
await testRangeProbeFallsBackWhenServerIgnoresRange()
await testRangeFetcherPreservesHeaders()
print("StreamResolverTests passed")
324 unmodified lines
])
}
+
private static func testRangeCacheForegroundMissReprioritizesPrefetch() async {
let queue = DispatchQueue(label: "dreamio.range-cache-test")
var requestedRanges: [String] = []
MockURLProtocol.handler = { request in
let range = request.value(forHTTPHeaderField: "Range") ?? ""
queue.sync {
requestedRanges.append(range)
}
let byteRange = byteRange(fromHeader: range, contentLength: 80_000_000)
let response = HTTPURLResponse(
url: request.url!,
statusCode: 206,
httpVersion: nil,
headerFields: ["Content-Range": "bytes \(byteRange.start)-\(byteRange.end)/80000000"]
)!
return (Data(repeating: 1, count: Int(byteRange.length)), response)
}
+
let session = ProgressiveHTTPRangeCacheSession(
fetcher: HTTPRangeRemoteFetcher(
url: URL(string: "https://cdn.example.test/movie.mp4")!,
headers: [:],
session: mockSession()
),
contentLength: 80_000_000,
durationProvider: { 100 }
)
defer {
session.cancelPrefetch()
}
+
session.prefetch(aroundByteOffset: 28_242_716)
_ = try? await session.data(for: HTTPByteRange(start: 51_818_977, end: 52_867_552))
try? await Task.sleep(nanoseconds: 50_000_000)
+
let ranges = queue.sync { requestedRanges }
assert(ranges.contains("bytes=51818977-52867552"), "Expected foreground VLC range to be fetched")
assert(ranges.contains { range in
range.hasPrefix("bytes=51936225-")
}, "Expected prefetch to restart near VLC's foreground range, got \(ranges)")
session.cancelPrefetch()
MockURLProtocol.handler = nil
try? await Task.sleep(nanoseconds: 50_000_000)
}
+
private static func byteRange(fromHeader header: String, contentLength: Int64) -> HTTPByteRange {
let value = header.replacingOccurrences(of: "bytes=", with: "")
let pieces = value.split(separator: "-", maxSplits: 1).map(String.init)
guard pieces.count == 2,
let start = Int64(pieces[0]) else {
return HTTPByteRange(start: 0, end: 0)
}
let end = pieces[1].isEmpty ? contentLength - 1 : (Int64(pieces[1]) ?? contentLength - 1)
return HTTPByteRange(start: start, end: min(end, contentLength - 1))
}
+
private static func testRangeProbeFallsBackWhenServerIgnoresRange() async {
MockURLProtocol.handler = { request in
if request.httpMethod == "HEAD" {

Related issues or PRs

Related to Beads issue dreamio-meh and branch lavender/vlc-local-range-cache.