Fix VLC Range Cache for MKV Streams
Removed the blanket Matroska/WebM cache bypass so direct-file MKV streams can use Dreamio's local range cache when the origin server confirms byte-range support.
Summary
Dreamio was refusing to range-cache MKV streams before checking the server. That made Torrentio and Real-Debrid MKV playback open in direct mode, so seek prefetch could not run. The cache probe now lets normal HTTP range capability decide whether the local cache should be used.
Changes Made
- Removed the hard-coded cache bypass for
.mkv,.mk3d,.mka,.mks, and.webmURLs. - Kept the existing non-HTTP and HLS playlist fallbacks intact.
- Updated the range probe regression test so MKV URLs are cacheable when the server returns
Accept-Ranges: bytesand a validContent-Length.
Context
The diagnostic logs showed [DreamioVLC] cache fallback reason=tail-index-container, followed by opening mode=direct and direct-mode seek logs. That fallback came from an extension check, not a failed HTTP range probe. Because many debrid MKV streams do support byte ranges, the app was leaving useful buffering behavior on the table.
Important Implementation Details
- The probe still requires either a HEAD response with byte-range support and content length, or a successful
GET Range: bytes=0-0response. - If an MKV origin ignores range requests, Dreamio still falls back to direct playback through the existing
range-probe-status-...path. - The expected debug signal for a compatible MKV is now
[DreamioVLC] opening mode=local-cache, and seeks should includebyteOffset=....
Relevant Diff Snippets
Dreamio/ProgressiveHTTPRangeCache.swift
271 unmodified lines27227327427527627727827928028152 unmodified lines334335336337338339340341342343271 unmodified linesguard !url.path.lowercased().hasSuffix(".m3u8") else {return HTTPRangeProbeResult(isCacheable: false, contentLength: nil, fallbackReason: "hls-playlist")}guard !Self.shouldBypassCache(for: url) else {return HTTPRangeProbeResult(isCacheable: false, contentLength: nil, fallbackReason: "tail-index-container")}if let head = try? await response(for: request(method: "HEAD")),(200..<400).contains(head.statusCode) {let acceptsRanges = header("Accept-Ranges", in: head)?.lowercased().contains("bytes") == true52 unmodified linesresponse.value(forHTTPHeaderField: name)}private static func shouldBypassCache(for url: URL) -> Bool {let extensionName = url.pathExtension.lowercased()return ["mkv", "mk3d", "mka", "mks", "webm"].contains(extensionName)}}enum HTTPRangeCacheError: Error {271 unmodified lines27227327427527627752 unmodified lines330331332333334335271 unmodified linesguard !url.path.lowercased().hasSuffix(".m3u8") else {return HTTPRangeProbeResult(isCacheable: false, contentLength: nil, fallbackReason: "hls-playlist")}if let head = try? await response(for: request(method: "HEAD")),(200..<400).contains(head.statusCode) {let acceptsRanges = header("Accept-Ranges", in: head)?.lowercased().contains("bytes") == true52 unmodified linesresponse.value(forHTTPHeaderField: name)}}enum HTTPRangeCacheError: Error {
Tests/StreamResolverTests.swift
42 unmodified lines43444546474849491 unmodified lines5415425435445455465475485495505515525535545555565573 unmodified lines56156256356456556656756856942 unmodified linestestRangeCacheForegroundMissFetchesAlignedChunks()await testRangeCacheForegroundMissReprioritizesPrefetch()await testRangeCacheHitFollowsActualPostSeekReadArea()await testRangeProbeBypassesTailIndexContainers()await testRangeProbeFallsBackWhenServerIgnoresRange()await testRangeFetcherPreservesHeaders()print("StreamResolverTests passed")491 unmodified linestry? await Task.sleep(nanoseconds: 50_000_000)}private static func testRangeProbeBypassesTailIndexContainers() async {var requestCount = 0MockURLProtocol.handler = { request inrequestCount += 1let response = HTTPURLResponse(url: request.url!,statusCode: 206,httpVersion: nil,headerFields: ["Content-Range": "bytes 0-0/20"])!return (Data([1]), response)}let fetcher = HTTPRangeRemoteFetcher(3 unmodified lines)let probe = await fetcher.probe()assertEqual(probe.isCacheable, false)assertEqual(probe.fallbackReason, "tail-index-container")assertEqual(requestCount, 0)MockURLProtocol.handler = nil}42 unmodified lines43444546474849491 unmodified lines5415425435445455465475485495505515525535545555565575585595605613 unmodified lines56556656756856957057157257357442 unmodified linestestRangeCacheForegroundMissFetchesAlignedChunks()await testRangeCacheForegroundMissReprioritizesPrefetch()await testRangeCacheHitFollowsActualPostSeekReadArea()await testRangeProbeAllowsRangeCacheForMKVWhenServerSupportsRanges()await testRangeProbeFallsBackWhenServerIgnoresRange()await testRangeFetcherPreservesHeaders()print("StreamResolverTests passed")491 unmodified linestry? await Task.sleep(nanoseconds: 50_000_000)}private static func testRangeProbeAllowsRangeCacheForMKVWhenServerSupportsRanges() async {var requestCount = 0MockURLProtocol.handler = { request inrequestCount += 1assertEqual(request.httpMethod, "HEAD")let response = HTTPURLResponse(url: request.url!,statusCode: 200,httpVersion: nil,headerFields: ["Accept-Ranges": "bytes","Content-Length": "20"])!return (Data(), response)}let fetcher = HTTPRangeRemoteFetcher(3 unmodified lines)let probe = await fetcher.probe()assertEqual(probe.isCacheable, true)assertEqual(probe.contentLength, 20)assertEqual(probe.fallbackReason, nil)assertEqual(requestCount, 1)MockURLProtocol.handler = nil}
Expected Impact for End-Users
Compatible MKV direct-file streams should start through Dreamio's local range cache instead of direct VLC mode. Backward and forward skips can now prime nearby bytes, which should reduce stalls after seeking on supported servers.
Validation
- Passed
swiftc Dreamio/StreamCandidate.swift Dreamio/StreamResolver.swift Dreamio/ProgressiveHTTPRangeCache.swift Dreamio/ExternalSubtitleTrackParser.swift Tests/StreamResolverTests.swift -o /tmp/StreamResolverTests && /tmp/StreamResolverTests - Passed
DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer xcodebuild -workspace Dreamio.xcworkspace -scheme Dreamio -configuration Debug -sdk iphonesimulator build
Issues, Limitations, and Mitigations
Follow-up Work
- Test the original Torrentio/Real-Debrid South Park stream on device and confirm logs show
opening mode=local-cache. - If startup is slower on some MKV sources, consider measuring HEAD latency and falling back to the tiny range probe sooner.
- Improve external subtitle auto-selection so English does not lose to the first parsed subtitle track.
New Changes as of May 26, 2026 at 8:16 AM
Summary of changes
Added a short timeout to the range-cache probe path so a slow HEAD or tiny range request cannot prevent native playback from starting.
Why this change was made
Device logs showed the MKV stream reached [DreamioVLC] cache-probe but never logged either opening mode=local-cache or opening mode=direct before the native-player startup timeout. The cache probe was waiting too long before any VLC media was opened.
Code diffs
Dreamio/ProgressiveHTTPRangeCache.swift
264 unmodified lines2652662672682692702712722732742752762772782 unmodified lines28128228328428528628729 unmodified lines317318319320321322323324325264 unmodified linesself.session = session}func probe() async -> HTTPRangeProbeResult {guard ["http", "https"].contains(url.scheme?.lowercased() ?? "") else {return HTTPRangeProbeResult(isCacheable: false, contentLength: nil, fallbackReason: "non-http-url")}guard !url.path.lowercased().hasSuffix(".m3u8") else {return HTTPRangeProbeResult(isCacheable: false, contentLength: nil, fallbackReason: "hls-playlist")}if let head = try? await response(for: request(method: "HEAD")),(200..<400).contains(head.statusCode) {let acceptsRanges = header("Accept-Ranges", in: head)?.lowercased().contains("bytes") == truelet length = header("Content-Length", in: head).flatMap(Int64.init)2 unmodified lines}}var tinyRequest = request(method: "GET")tinyRequest.setValue("bytes=0-0", forHTTPHeaderField: "Range")do {let (data, response) = try await session.data(for: tinyRequest)29 unmodified linesreturn response as? HTTPURLResponse}private func request(method: String) -> URLRequest {var request = URLRequest(url: url)request.httpMethod = methodheaders.forEach { key, value inrequest.setValue(value, forHTTPHeaderField: key)}264 unmodified lines2652662672682692702712722732742752762772782 unmodified lines28128228328428528628729 unmodified lines317318319320321322323324325326327328264 unmodified linesself.session = session}func probe(timeoutInterval: TimeInterval = 3) async -> HTTPRangeProbeResult {guard ["http", "https"].contains(url.scheme?.lowercased() ?? "") else {return HTTPRangeProbeResult(isCacheable: false, contentLength: nil, fallbackReason: "non-http-url")}guard !url.path.lowercased().hasSuffix(".m3u8") else {return HTTPRangeProbeResult(isCacheable: false, contentLength: nil, fallbackReason: "hls-playlist")}if let head = try? await response(for: request(method: "HEAD", timeoutInterval: timeoutInterval)),(200..<400).contains(head.statusCode) {let acceptsRanges = header("Accept-Ranges", in: head)?.lowercased().contains("bytes") == truelet length = header("Content-Length", in: head).flatMap(Int64.init)2 unmodified lines}}var tinyRequest = request(method: "GET", timeoutInterval: timeoutInterval)tinyRequest.setValue("bytes=0-0", forHTTPHeaderField: "Range")do {let (data, response) = try await session.data(for: tinyRequest)29 unmodified linesreturn response as? HTTPURLResponse}private func request(method: String, timeoutInterval: TimeInterval? = nil) -> URLRequest {var request = URLRequest(url: url)request.httpMethod = methodif let timeoutInterval {request.timeoutInterval = timeoutInterval}headers.forEach { key, value inrequest.setValue(value, forHTTPHeaderField: key)}
Dreamio/VLCNativePlaybackBackend.swift
77 unmodified lines7879808182838477 unmodified linesreturn}let fetcher = HTTPRangeRemoteFetcher(url: request.playbackURL, headers: request.headers)let probe = await fetcher.probe()guard !Task.isCancelled else {return}77 unmodified lines7879808182838477 unmodified linesreturn}let fetcher = HTTPRangeRemoteFetcher(url: request.playbackURL, headers: request.headers)let probe = await fetcher.probe(timeoutInterval: 1.5)guard !Task.isCancelled else {return}
Tests/StreamResolverTests.swift
43 unmodified lines444546474849522 unmodified lines57257357457557657743 unmodified linesawait testRangeCacheForegroundMissReprioritizesPrefetch()await testRangeCacheHitFollowsActualPostSeekReadArea()await testRangeProbeAllowsRangeCacheForMKVWhenServerSupportsRanges()await testRangeProbeFallsBackWhenServerIgnoresRange()await testRangeFetcherPreservesHeaders()print("StreamResolverTests passed")522 unmodified linesMockURLProtocol.handler = nil}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)43 unmodified lines44454647484950522 unmodified lines57357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360443 unmodified linesawait testRangeCacheForegroundMissReprioritizesPrefetch()await testRangeCacheHitFollowsActualPostSeekReadArea()await testRangeProbeAllowsRangeCacheForMKVWhenServerSupportsRanges()await testRangeProbeAppliesRequestTimeout()await testRangeProbeFallsBackWhenServerIgnoresRange()await testRangeFetcherPreservesHeaders()print("StreamResolverTests passed")522 unmodified linesMockURLProtocol.handler = nil}private static func testRangeProbeAppliesRequestTimeout() async {MockURLProtocol.handler = { request inassertEqual(request.timeoutInterval, 1.5)let response = HTTPURLResponse(url: request.url!,statusCode: 200,httpVersion: nil,headerFields: ["Accept-Ranges": "bytes","Content-Length": "20"])!return (Data(), response)}let fetcher = HTTPRangeRemoteFetcher(url: URL(string: "https://cdn.example.test/show.mkv")!,headers: [:],session: mockSession())let probe = await fetcher.probe(timeoutInterval: 1.5)assertEqual(probe.isCacheable, true)MockURLProtocol.handler = nil}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)
Related issues or PRs
Beads issue: dreamio-btc. This follows the earlier dreamio-3sw MKV cache enablement work.
Validation
- Passed
swiftc Dreamio/StreamCandidate.swift Dreamio/StreamResolver.swift Dreamio/ProgressiveHTTPRangeCache.swift Dreamio/ExternalSubtitleTrackParser.swift Tests/StreamResolverTests.swift -o /tmp/StreamResolverTests && /tmp/StreamResolverTests - Passed
DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer xcodebuild -workspace Dreamio.xcworkspace -scheme Dreamio -configuration Debug -sdk iphonesimulator build
New Changes as of May 26, 2026 at 9:01 AM
Summary of changes
Changed VLC startup to open direct playback immediately instead of waiting for the range-cache probe.
Why this change was made
Device logs showed the probe request timing out and the native player still failing to start before the startup watchdog. The reliable behavior is to start VLC first, then revisit cache probing as a non-blocking optimization later.
Code diffs
Dreamio/VLCNativePlaybackBackend.swift
70 unmodified lines717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812970 unmodified lineslastLoggedState = nillastBufferingLogTime = nil#if DEBUGprint("[DreamioVLC] cache-probe url=\(URLRedactor.redactedURLString(request.playbackURL.absoluteString))")#endifplaybackStartupTask = Task { [weak self] inguard let self else {return}let fetcher = HTTPRangeRemoteFetcher(url: request.playbackURL, headers: request.headers)let probe = await fetcher.probe(timeoutInterval: 1.5)guard !Task.isCancelled else {return}if probe.isCacheable, let contentLength = probe.contentLength, contentLength > 0 {do {let session = ProgressiveHTTPRangeCacheSession(fetcher: fetcher,contentLength: contentLength,durationProvider: { [weak self] in self?.duration ?? 0 })let localURL = try await ProgressiveHTTPRangeCacheServer.shared.localURL(for: session)await MainActor.run {self.rangeCacheSession = sessionsession.prefetch(aroundByteOffset: 0)self.startVLCMedia(url: localURL,request: request,playbackMode: "local-cache",cachingMilliseconds: 500,includeRemoteHTTPOptions: false)}return} catch {#if DEBUGprint("[DreamioVLC] cache fallback reason=local-server-error-\(error)")#endif}} else {#if DEBUGprint("[DreamioVLC] cache fallback reason=\(probe.fallbackReason ?? "unknown")")#endif}await MainActor.run {self.startVLCMedia(url: request.playbackURL,request: request,playbackMode: "direct",cachingMilliseconds: 2500,includeRemoteHTTPOptions: true)}}#elseonFailure?(NativePlaybackError.backendUnavailable)#endif70 unmodified lines71727374757677787980818283848570 unmodified lineslastLoggedState = nillastBufferingLogTime = nil#if DEBUGprint("[DreamioVLC] cache fallback reason=startup-direct-preferred url=\(URLRedactor.redactedURLString(request.playbackURL.absoluteString))")#endifstartVLCMedia(url: request.playbackURL,request: request,playbackMode: "direct",cachingMilliseconds: 2500,includeRemoteHTTPOptions: true)#elseonFailure?(NativePlaybackError.backendUnavailable)#endif
Related issues or PRs
Beads issue: dreamio-dd7. This supersedes the blocking startup probe behavior from the earlier MKV range-cache experiment.
Validation
- Passed
swiftc Dreamio/StreamCandidate.swift Dreamio/StreamResolver.swift Dreamio/ProgressiveHTTPRangeCache.swift Dreamio/ExternalSubtitleTrackParser.swift Tests/StreamResolverTests.swift -o /tmp/StreamResolverTests && /tmp/StreamResolverTests - Passed
DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer xcodebuild -workspace Dreamio.xcworkspace -scheme Dreamio -configuration Debug -sdk iphonesimulator build
New Changes as of May 26, 2026 at 8:42 PM EDT
Summary of changes
Changed VLC startup from a blanket direct-mode bypass to a bounded, non-blocking cache-preparation path. Dreamio now gives the local range cache a very short opportunity to prove it is safe and ready, then starts direct playback immediately if probing or server setup is slow, failed, or inconclusive.
Why this change was made
Recent MKV logs showed the previous cache probe could block native playback startup long enough for Native playback did not start before the timeout. The fix keeps native startup reliable while still allowing mode=local-cache when HTTP range support and the local server are ready inside the startup budget.
Code diffs
Dreamio/ProgressiveHTTPRangeCache.swift
57 unmodified lines58596061626357 unmodified lineslet fallbackReason: String?}final class SparseHTTPByteRangeStore {private struct Segment {var range: HTTPByteRange57 unmodified lines58596061626364656667686970717273747576777879808182838485868788899057 unmodified lineslet fallbackReason: String?}enum HTTPRangeCacheStartupDecision: Equatable {case useLocalCachecase skip(reason: String)}enum HTTPRangeCacheStartupPolicy {static let preparationTimeout: TimeInterval = 0.25static let probeTimeout: TimeInterval = 0.2static func immediateSkipReason(for url: URL) -> String? {guard ["http", "https"].contains(url.scheme?.lowercased() ?? "") else {return "non-http-url"}guard !url.path.lowercased().hasSuffix(".m3u8") else {return "hls-playlist"}return nil}static func decision(for probe: HTTPRangeProbeResult) -> HTTPRangeCacheStartupDecision {guard probe.isCacheable, probe.contentLength != nil else {return .skip(reason: probe.fallbackReason ?? "range-probe-inconclusive")}return .useLocalCache}}final class SparseHTTPByteRangeStore {private struct Segment {var range: HTTPByteRange
Dreamio/VLCNativePlaybackBackend.swift
24 unmodified lines25262728293025 unmodified lines5657585960618 unmodified lines70717273747576772 unmodified lines80818283848586878889103 unmodified lines19319419519619719824 unmodified lines#endifprivate var rangeCacheSession: ProgressiveHTTPRangeCacheSession?private var playbackStartupTask: Task<Void, Never>?private var lastLoggedState: String?private var lastBufferingLogTime: Date?private var attachedSubtitleURLs = Set<URL>()25 unmodified linesfunc play(request: NativePlaybackRequest) {#if canImport(MobileVLCKit)playbackStartupTask?.cancel()attachedSubtitleURLs.removeAll()pendingSubtitleCandidates.removeAll()pendingSubtitleURLs.removeAll()8 unmodified linesrangeCacheSession = nillastLoggedState = nillastBufferingLogTime = nil#if DEBUGprint("[DreamioVLC] cache fallback reason=startup-direct-preferred url=\(URLRedactor.redactedURLString(request.playbackURL.absoluteString))")#endifstartVLCMedia(url: request.playbackURL,2 unmodified linescachingMilliseconds: 2500,includeRemoteHTTPOptions: true)#elseonFailure?(NativePlaybackError.backendUnavailable)#endif}func play() {#if canImport(MobileVLCKit)103 unmodified linesfunc stop() {#if canImport(MobileVLCKit)playbackStartupTask?.cancel()rangeCacheSession = nilmediaPlayer.stop()mediaPlayer.drawable = nil24 unmodified lines252627282930313225 unmodified lines585960616263648 unmodified lines7374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621632 unmodified lines166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211103 unmodified lines31531631731831932032132224 unmodified lines#endifprivate var rangeCacheSession: ProgressiveHTTPRangeCacheSession?private var playbackStartupTask: Task<Void, Never>?private var rangeCachePreparationTask: Task<Void, Never>?private var playbackStartupID: UUID?private var lastLoggedState: String?private var lastBufferingLogTime: Date?private var attachedSubtitleURLs = Set<URL>()25 unmodified linesfunc play(request: NativePlaybackRequest) {#if canImport(MobileVLCKit)playbackStartupTask?.cancel()rangeCachePreparationTask?.cancel()attachedSubtitleURLs.removeAll()pendingSubtitleCandidates.removeAll()pendingSubtitleURLs.removeAll()8 unmodified linesrangeCacheSession = nillastLoggedState = nillastBufferingLogTime = nilstartWithNonBlockingRangeCache(request: request)#elseonFailure?(NativePlaybackError.backendUnavailable)#endif}#if canImport(MobileVLCKit)private func startWithNonBlockingRangeCache(request: NativePlaybackRequest) {if let skipReason = HTTPRangeCacheStartupPolicy.immediateSkipReason(for: request.playbackURL) {#if DEBUGprint("[DreamioVLC] cache skipped reason=\(skipReason) url=\(URLRedactor.redactedURLString(request.playbackURL.absoluteString))")#endifstartDirectPlayback(request: request, fallbackReason: skipReason)return}let startupID = UUID()playbackStartupID = startupIDplaybackStartupTask = Task { [weak self] inguard let self else {return}do {let timeoutNanoseconds = UInt64(HTTPRangeCacheStartupPolicy.preparationTimeout * 1_000_000_000)try await Task.sleep(nanoseconds: timeoutNanoseconds)} catch {return}await MainActor.run {guard self.canStartPlayback(for: startupID) else {return}#if DEBUGprint("[DreamioVLC] cache probe timed out timeoutMs=\(Int(HTTPRangeCacheStartupPolicy.preparationTimeout * 1000)) url=\(URLRedactor.redactedURLString(request.playbackURL.absoluteString))")#endifself.rangeCachePreparationTask?.cancel()self.startDirectPlayback(request: request, fallbackReason: "startup-direct-preferred")}}rangeCachePreparationTask = Task { [weak self] inguard let self else {return}let result = await self.prepareRangeCache(for: request)guard !Task.isCancelled else {return}await MainActor.run {guard self.canStartPlayback(for: startupID) else {return}self.playbackStartupTask?.cancel()switch result {case .success(let prepared):#if DEBUGprint("[DreamioVLC] cache used mode=local-cache url=\(URLRedactor.redactedURLString(prepared.localURL.absoluteString))")#endifself.rangeCacheSession = prepared.sessionself.startVLCMedia(url: prepared.localURL,request: request,playbackMode: "local-cache",cachingMilliseconds: 1000,includeRemoteHTTPOptions: false)case .failure(let failure):self.startDirectPlayback(request: request, fallbackReason: failure.reason)}}}}private func canStartPlayback(for startupID: UUID) -> Bool {playbackStartupID == startupID && !hasStartedMedia}private struct RangeCacheStartupFailure: Error {let reason: String}private func startDirectPlayback(request: NativePlaybackRequest, fallbackReason: String) {#if DEBUGprint("[DreamioVLC] direct fallback started reason=\(fallbackReason) url=\(URLRedactor.redactedURLString(request.playbackURL.absoluteString))")#endifstartVLCMedia(url: request.playbackURL,2 unmodified linescachingMilliseconds: 2500,includeRemoteHTTPOptions: true)}private struct PreparedRangeCache {let session: ProgressiveHTTPRangeCacheSessionlet localURL: URL}private func prepareRangeCache(for request: NativePlaybackRequest) async -> Result<PreparedRangeCache, RangeCacheStartupFailure> {let fetcher = HTTPRangeRemoteFetcher(url: request.playbackURL, headers: request.headers)let probe = await fetcher.probe(timeoutInterval: HTTPRangeCacheStartupPolicy.probeTimeout)switch HTTPRangeCacheStartupPolicy.decision(for: probe) {case .skip(let reason):#if DEBUGprint("[DreamioVLC] cache skipped reason=\(reason) url=\(URLRedactor.redactedURLString(request.playbackURL.absoluteString))")#endifreturn .failure(RangeCacheStartupFailure(reason: reason))case .useLocalCache:break}guard let contentLength = probe.contentLength else {return .failure(RangeCacheStartupFailure(reason: "range-probe-inconclusive"))}let session = ProgressiveHTTPRangeCacheSession(fetcher: fetcher,contentLength: contentLength,durationProvider: { [weak self] in self?.duration ?? 0 })do {let localURL = try await ProgressiveHTTPRangeCacheServer.shared.localURL(for: session)return .success(PreparedRangeCache(session: session, localURL: localURL))} catch {#if DEBUGprint("[DreamioVLC] local cache server failed error=\(error)")#endifreturn .failure(RangeCacheStartupFailure(reason: "local-cache-server-failed"))}}#else#endiffunc play() {#if canImport(MobileVLCKit)103 unmodified linesfunc stop() {#if canImport(MobileVLCKit)playbackStartupTask?.cancel()rangeCachePreparationTask?.cancel()playbackStartupID = nilrangeCacheSession = nilmediaPlayer.stop()mediaPlayer.drawable = nil
Tests/StreamResolverTests.swift
42 unmodified lines434445464748493 unmodified lines54254354454554654742 unmodified linestestRangeCacheForegroundMissFetchesAlignedChunks()await testRangeCacheForegroundMissReprioritizesPrefetch()await testRangeCacheHitFollowsActualPostSeekReadArea()await testRangeProbeAllowsRangeCacheForMKVWhenServerSupportsRanges()await testRangeProbeAppliesRequestTimeout()await testRangeProbeFallsBackWhenServerIgnoresRange()493 unmodified linestry? await Task.sleep(nanoseconds: 50_000_000)}private static func testRangeProbeAllowsRangeCacheForMKVWhenServerSupportsRanges() async {var requestCount = 0MockURLProtocol.handler = { request in42 unmodified lines434445464748495051493 unmodified lines54554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458542 unmodified linestestRangeCacheForegroundMissFetchesAlignedChunks()await testRangeCacheForegroundMissReprioritizesPrefetch()await testRangeCacheHitFollowsActualPostSeekReadArea()testRangeCacheStartupPolicySkipsHLSAndNonHTTPImmediately()testRangeCacheStartupPolicyUsesCacheOnlyForConclusiveProbe()testRangeCacheStartupPolicySkipsInconclusiveProbe()await testRangeProbeAllowsRangeCacheForMKVWhenServerSupportsRanges()await testRangeProbeAppliesRequestTimeout()await testRangeProbeFallsBackWhenServerIgnoresRange()493 unmodified linestry? await Task.sleep(nanoseconds: 50_000_000)}private static func testRangeCacheStartupPolicySkipsHLSAndNonHTTPImmediately() {assertEqual(HTTPRangeCacheStartupPolicy.immediateSkipReason(for: URL(string: "https://cdn.example.test/live.m3u8")!),"hls-playlist")assertEqual(HTTPRangeCacheStartupPolicy.immediateSkipReason(for: URL(string: "file:///tmp/movie.mkv")!),"non-http-url")assertEqual(HTTPRangeCacheStartupPolicy.immediateSkipReason(for: URL(string: "https://cdn.example.test/movie.mkv")!),nil)}private static func testRangeCacheStartupPolicyUsesCacheOnlyForConclusiveProbe() {let decision = HTTPRangeCacheStartupPolicy.decision(for: HTTPRangeProbeResult(isCacheable: true, contentLength: 20, fallbackReason: nil))assertEqual(decision, .useLocalCache)}private static func testRangeCacheStartupPolicySkipsInconclusiveProbe() {let rejectedDecision = HTTPRangeCacheStartupPolicy.decision(for: HTTPRangeProbeResult(isCacheable: false, contentLength: nil, fallbackReason: "range-probe-status-200"))let missingLengthDecision = HTTPRangeCacheStartupPolicy.decision(for: HTTPRangeProbeResult(isCacheable: true, contentLength: nil, fallbackReason: nil))assertEqual(rejectedDecision, .skip(reason: "range-probe-status-200"))assertEqual(missingLengthDecision, .skip(reason: "range-probe-inconclusive"))}private static func testRangeProbeAllowsRangeCacheForMKVWhenServerSupportsRanges() async {var requestCount = 0MockURLProtocol.handler = { request in
Related issues or PRs
- Beads issue:
dreamio-5cz. - No pull request was opened in this turn.