mirror of
https://github.com/dirtydishes/dreamio.git
synced 2026-06-06 13:37:24 +00:00
Compare commits
4 commits
46a52b533f
...
c4236afe7a
| Author | SHA1 | Date | |
|---|---|---|---|
| c4236afe7a | |||
| 4b173e0b88 | |||
| 4d0e675aa3 | |||
| b16857a9fa |
6 changed files with 612 additions and 72 deletions
|
|
@ -48,3 +48,6 @@
|
||||||
{"id":"int-91b3db21","kind":"field_change","created_at":"2026-05-26T04:40:10.299245Z","actor":"dirtydishes","issue_id":"dreamio-mi1","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Used actual foreground VLC reads as prefetch follow signals on hits and changed foreground misses to fetch aligned chunks; added regression tests and updated the turn document."}}
|
{"id":"int-91b3db21","kind":"field_change","created_at":"2026-05-26T04:40:10.299245Z","actor":"dirtydishes","issue_id":"dreamio-mi1","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Used actual foreground VLC reads as prefetch follow signals on hits and changed foreground misses to fetch aligned chunks; added regression tests and updated the turn document."}}
|
||||||
{"id":"int-ff0aeb09","kind":"field_change","created_at":"2026-05-26T04:47:44.48931Z","actor":"dirtydishes","issue_id":"dreamio-2hw","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Fixed stale local range-cache prefetch state after cached seek reads and documented the validation."}}
|
{"id":"int-ff0aeb09","kind":"field_change","created_at":"2026-05-26T04:47:44.48931Z","actor":"dirtydishes","issue_id":"dreamio-2hw","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Fixed stale local range-cache prefetch state after cached seek reads and documented the validation."}}
|
||||||
{"id":"int-204223f5","kind":"field_change","created_at":"2026-05-26T04:56:13.920284Z","actor":"dirtydishes","issue_id":"dreamio-816","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Bypassed the local range cache for Matroska-family tail-index containers and added a regression test confirming MKV probes fall back to direct VLC playback without issuing cache probe requests."}}
|
{"id":"int-204223f5","kind":"field_change","created_at":"2026-05-26T04:56:13.920284Z","actor":"dirtydishes","issue_id":"dreamio-816","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Bypassed the local range cache for Matroska-family tail-index containers and added a regression test confirming MKV probes fall back to direct VLC playback without issuing cache probe requests."}}
|
||||||
|
{"id":"int-b6f641ed","kind":"field_change","created_at":"2026-05-26T12:10:16.392655Z","actor":"dirtydishes","issue_id":"dreamio-3sw","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Removed the Matroska/WebM extension-level range-cache bypass and added a regression test proving MKV URLs use the cache when the origin advertises byte-range support."}}
|
||||||
|
{"id":"int-2b073805","kind":"field_change","created_at":"2026-05-26T12:16:53.567972Z","actor":"dirtydishes","issue_id":"dreamio-btc","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Added a short timeout to range-cache probe requests so slow MKV HEAD/range probes fall back to direct VLC startup instead of tripping the native-player startup timeout."}}
|
||||||
|
{"id":"int-1ed0a18a","kind":"field_change","created_at":"2026-05-26T13:01:27.690486Z","actor":"dirtydishes","issue_id":"dreamio-dd7","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Changed VLC startup to open direct playback immediately instead of waiting for slow range-cache probes, restoring reliable native-player startup for MKV streams."}}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
|
{"_type":"issue","id":"dreamio-dd7","title":"Start VLC before slow range-cache probes","description":"Current MKV cache probing can still block native VLC startup because HEAD and tiny range timeout sequentially before any media is opened. Start direct playback immediately or otherwise ensure VLC media opens before probing completes, while preserving range-cache support when it is ready quickly.","status":"closed","priority":0,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-26T13:00:37Z","created_by":"dirtydishes","updated_at":"2026-05-26T13:01:28Z","started_at":"2026-05-26T13:00:43Z","closed_at":"2026-05-26T13:01:28Z","close_reason":"Changed VLC startup to open direct playback immediately instead of waiting for slow range-cache probes, restoring reliable native-player startup for MKV streams.","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||||
|
{"_type":"issue","id":"dreamio-btc","title":"Bound VLC range cache probe startup latency","description":"After enabling MKV range cache probing, some Torrentio/Real-Debrid MKV streams log cache-probe but never reach opening mode before the native-player startup timeout. Add a bounded probe/local-cache startup path that falls back to direct playback when the range probe is slow or inconclusive.","status":"closed","priority":0,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-26T12:14:02Z","created_by":"dirtydishes","updated_at":"2026-05-26T12:16:53Z","started_at":"2026-05-26T12:14:11Z","closed_at":"2026-05-26T12:16:53Z","close_reason":"Added a short timeout to range-cache probe requests so slow MKV HEAD/range probes fall back to direct VLC startup instead of tripping the native-player startup timeout.","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||||
{"_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-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-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-3sw","title":"Fix VLC range cache fallback for tail-index MKV streams","description":"Video range caching currently refuses streams classified as tail-index containers, causing VLC playback to use direct mode and lose seek prefetch behavior. Investigate the probe logic and enable safe local range caching for these streams without breaking playback startup.","status":"closed","priority":1,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-26T12:05:20Z","created_by":"dirtydishes","updated_at":"2026-05-26T12:10:16Z","started_at":"2026-05-26T12:05:38Z","closed_at":"2026-05-26T12:10:16Z","close_reason":"Removed the Matroska/WebM extension-level range-cache bypass and added a regression test proving MKV URLs use the cache when the origin advertises byte-range support.","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||||
{"_type":"issue","id":"dreamio-816","title":"Fix local range cache playback buffering","description":"Normal VLC playback can stay in buffering after the local progressive HTTP range cache is enabled. Logs show VLC repeatedly probes header/tail MKV ranges through the loopback server while the cache foreground fetch path serializes 1 MB remote requests. Investigate and adjust the cache path so normal direct-file playback can start reliably.","status":"closed","priority":1,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-26T04:54:13Z","created_by":"dirtydishes","updated_at":"2026-05-26T04:56:14Z","started_at":"2026-05-26T04:54:17Z","closed_at":"2026-05-26T04:56:14Z","close_reason":"Bypassed the local range cache for Matroska-family tail-index containers and added a regression test confirming MKV probes fall back to direct VLC playback without issuing cache probe requests.","dependency_count":0,"dependent_count":0,"comment_count":0}
|
{"_type":"issue","id":"dreamio-816","title":"Fix local range cache playback buffering","description":"Normal VLC playback can stay in buffering after the local progressive HTTP range cache is enabled. Logs show VLC repeatedly probes header/tail MKV ranges through the loopback server while the cache foreground fetch path serializes 1 MB remote requests. Investigate and adjust the cache path so normal direct-file playback can start reliably.","status":"closed","priority":1,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-26T04:54:13Z","created_by":"dirtydishes","updated_at":"2026-05-26T04:56:14Z","started_at":"2026-05-26T04:54:17Z","closed_at":"2026-05-26T04:56:14Z","close_reason":"Bypassed the local range cache for Matroska-family tail-index containers and added a regression test confirming MKV probes fall back to direct VLC playback without issuing cache probe requests.","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||||
{"_type":"issue","id":"dreamio-2hw","title":"Fix range cache prefetch cursor after cached seek reads","description":"Skipping after the local range cache has warmed can leave prefetch following an older foreground cursor instead of the post-seek cached read position. Update the cache so cached foreground reads can reset the follow cursor and add regression coverage.","status":"closed","priority":1,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-26T04:45:44Z","created_by":"dirtydishes","updated_at":"2026-05-26T04:47:44Z","started_at":"2026-05-26T04:46:36Z","closed_at":"2026-05-26T04:47:44Z","close_reason":"Fixed stale local range-cache prefetch state after cached seek reads and documented the validation.","dependency_count":0,"dependent_count":0,"comment_count":0}
|
{"_type":"issue","id":"dreamio-2hw","title":"Fix range cache prefetch cursor after cached seek reads","description":"Skipping after the local range cache has warmed can leave prefetch following an older foreground cursor instead of the post-seek cached read position. Update the cache so cached foreground reads can reset the follow cursor and add regression coverage.","status":"closed","priority":1,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-26T04:45:44Z","created_by":"dirtydishes","updated_at":"2026-05-26T04:47:44Z","started_at":"2026-05-26T04:46:36Z","closed_at":"2026-05-26T04:47:44Z","close_reason":"Fixed stale local range-cache prefetch state after cached seek reads and documented the validation.","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||||
{"_type":"issue","id":"dreamio-mi1","title":"adapt vlc prefetch to actual post-seek reads","description":"Use real foreground VLC reads after a seek as a prefetch signal even when they are cache hits, and fetch aligned chunks for partial foreground misses so the cache warms ahead before VLC reaches the edge of retained data.","status":"closed","priority":1,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-26T04:38:14Z","created_by":"dirtydishes","updated_at":"2026-05-26T04:40:10Z","started_at":"2026-05-26T04:38:16Z","closed_at":"2026-05-26T04:40:10Z","close_reason":"Used actual foreground VLC reads as prefetch follow signals on hits and changed foreground misses to fetch aligned chunks; added regression tests and updated the turn document.","dependency_count":0,"dependent_count":0,"comment_count":0}
|
{"_type":"issue","id":"dreamio-mi1","title":"adapt vlc prefetch to actual post-seek reads","description":"Use real foreground VLC reads after a seek as a prefetch signal even when they are cache hits, and fetch aligned chunks for partial foreground misses so the cache warms ahead before VLC reaches the edge of retained data.","status":"closed","priority":1,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-26T04:38:14Z","created_by":"dirtydishes","updated_at":"2026-05-26T04:40:10Z","started_at":"2026-05-26T04:38:16Z","closed_at":"2026-05-26T04:40:10Z","close_reason":"Used actual foreground VLC reads as prefetch follow signals on hits and changed foreground misses to fetch aligned chunks; added regression tests and updated the turn document.","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||||
|
|
@ -42,6 +45,8 @@
|
||||||
{"_type":"issue","id":"dreamio-l68","title":"Add native playback for direct debrid streams","description":"Implement a WKWebView JavaScript bridge that detects direct-file debrid media URLs and routes unsupported containers to a native player backend, initially MobileVLCKit, while preserving normal Stremio Web playback for compatible streams.","status":"closed","priority":1,"issue_type":"feature","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-25T03:13:19Z","created_by":"dirtydishes","updated_at":"2026-05-25T03:20:17Z","started_at":"2026-05-25T03:13:28Z","closed_at":"2026-05-25T03:20:17Z","close_reason":"Implemented native direct-stream bridge, classification, MobileVLCKit backend wiring, CocoaPods workflow docs, and turn documentation. Full iOS build is blocked locally by missing CocoaPods and iPhoneOS SDK.","dependency_count":0,"dependent_count":0,"comment_count":0}
|
{"_type":"issue","id":"dreamio-l68","title":"Add native playback for direct debrid streams","description":"Implement a WKWebView JavaScript bridge that detects direct-file debrid media URLs and routes unsupported containers to a native player backend, initially MobileVLCKit, while preserving normal Stremio Web playback for compatible streams.","status":"closed","priority":1,"issue_type":"feature","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-25T03:13:19Z","created_by":"dirtydishes","updated_at":"2026-05-25T03:20:17Z","started_at":"2026-05-25T03:13:28Z","closed_at":"2026-05-25T03:20:17Z","close_reason":"Implemented native direct-stream bridge, classification, MobileVLCKit backend wiring, CocoaPods workflow docs, and turn documentation. Full iOS build is blocked locally by missing CocoaPods and iPhoneOS SDK.","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||||
{"_type":"issue","id":"dreamio-tnv","title":"Fix iOS bundle identifier install failure","description":"Xcode built Dreamio.app without a valid CFBundleIdentifier, causing device install to fail with CoreDeviceError 3000/3002. Investigate project bundle settings, fix the source configuration, validate the app bundle Info.plist, and document the change.","status":"closed","priority":1,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-25T01:23:00Z","created_by":"dirtydishes","updated_at":"2026-05-25T01:25:36Z","started_at":"2026-05-25T01:23:07Z","closed_at":"2026-05-25T01:25:36Z","close_reason":"Added bundle metadata to Info.plist and validated processed app bundle identifier.","dependency_count":0,"dependent_count":0,"comment_count":0}
|
{"_type":"issue","id":"dreamio-tnv","title":"Fix iOS bundle identifier install failure","description":"Xcode built Dreamio.app without a valid CFBundleIdentifier, causing device install to fail with CoreDeviceError 3000/3002. Investigate project bundle settings, fix the source configuration, validate the app bundle Info.plist, and document the change.","status":"closed","priority":1,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-25T01:23:00Z","created_by":"dirtydishes","updated_at":"2026-05-25T01:25:36Z","started_at":"2026-05-25T01:23:07Z","closed_at":"2026-05-25T01:25:36Z","close_reason":"Added bundle metadata to Info.plist and validated processed app bundle identifier.","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||||
{"_type":"issue","id":"dreamio-4yn","title":"Build WKWebView MVP shell","description":"Create the first Dreamio MVP implementation: a minimal iOS WKWebView wrapper around hosted Stremio Web, with configuration, launch behavior, diagnostics, and documentation for real-device viability testing.","acceptance_criteria":"App project exists; WKWebView loads hosted Stremio Web; external/new-window navigation is handled; basic diagnostics and manual test documentation exist; quality gates are run or documented.","status":"closed","priority":1,"issue_type":"feature","owner":"dishes@dpdrm.com","created_at":"2026-05-24T14:55:12Z","created_by":"dirtydishes","updated_at":"2026-05-24T14:59:44Z","closed_at":"2026-05-24T14:59:44Z","close_reason":"Implemented the MVP WKWebView iOS shell, added run and validation documentation, and recorded current validation limits.","dependency_count":0,"dependent_count":0,"comment_count":0}
|
{"_type":"issue","id":"dreamio-4yn","title":"Build WKWebView MVP shell","description":"Create the first Dreamio MVP implementation: a minimal iOS WKWebView wrapper around hosted Stremio Web, with configuration, launch behavior, diagnostics, and documentation for real-device viability testing.","acceptance_criteria":"App project exists; WKWebView loads hosted Stremio Web; external/new-window navigation is handled; basic diagnostics and manual test documentation exist; quality gates are run or documented.","status":"closed","priority":1,"issue_type":"feature","owner":"dishes@dpdrm.com","created_at":"2026-05-24T14:55:12Z","created_by":"dirtydishes","updated_at":"2026-05-24T14:59:44Z","closed_at":"2026-05-24T14:59:44Z","close_reason":"Implemented the MVP WKWebView iOS shell, added run and validation documentation, and recorded current validation limits.","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||||
|
{"_type":"issue","id":"dreamio-p8p","title":"Recreate OpenSubtitles language turn doc with template","description":"Rebuild the OpenSubtitles caption-track turn document using the new lavender template and contained Clean SSR diff shells.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-26T05:16:17Z","created_by":"dirtydishes","updated_at":"2026-05-26T05:19:00Z","started_at":"2026-05-26T05:16:21Z","closed_at":"2026-05-26T05:19:00Z","close_reason":"Recreated the OpenSubtitles language turn document using the new lavender template and contained Clean SSR diff shells.","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||||
|
{"_type":"issue","id":"dreamio-h28","title":"Clean up turn document template guidance","description":"Add a reusable turn document template and tighten repository instructions so future turn docs use contained clean SSR diffs instead of raw generated diff blobs.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-26T05:07:57Z","created_by":"dirtydishes","updated_at":"2026-05-26T05:11:37Z","started_at":"2026-05-26T05:08:01Z","closed_at":"2026-05-26T05:11:37Z","close_reason":"Added the reusable turn document template and updated repository instructions for clean SSR diff rendering.","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||||
{"_type":"issue","id":"dreamio-3yb","title":"Add VLC seek buffer for native playback","description":"Configure balanced VLC media caching in the native playback backend so short seek jumps are less likely to feel like stream restarts while preserving existing playback controls, audio tracks, and subtitles.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-25T19:05:27Z","created_by":"dirtydishes","updated_at":"2026-05-25T19:07:24Z","started_at":"2026-05-25T19:05:31Z","closed_at":"2026-05-25T19:07:24Z","close_reason":"Added centralized 30-second VLC media caching for native playback and validated the iOS build.","comments":[{"id":"019e60b6-9b11-7c5f-af57-c9e54ac6129f","issue_id":"dreamio-3yb","author":"dirtydishes","text":"Device testing still shows repeated VLC buffering after 15-second jumps. Added DEBUG playback snapshots on state changes and delayed post-jump probes so the next pass can distinguish a stalled stream/range reconnect from a seek-state issue.","created_at":"2026-05-25T19:57:21Z"},{"id":"019e60c4-f824-79fe-b974-9fbe9fe91788","issue_id":"dreamio-3yb","author":"dirtydishes","text":"Latest device logs show VLC remains at the pre-jump time/position after a fixed skip while buffering. Added backend-only stalled jump recovery: after a short no-progress buffering window, reopen the same media with :start-time set to the requested target and reattach accepted subtitle candidates plus selected tracks where available.","created_at":"2026-05-25T20:13:02Z"}],"dependency_count":0,"dependent_count":0,"comment_count":2}
|
{"_type":"issue","id":"dreamio-3yb","title":"Add VLC seek buffer for native playback","description":"Configure balanced VLC media caching in the native playback backend so short seek jumps are less likely to feel like stream restarts while preserving existing playback controls, audio tracks, and subtitles.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-25T19:05:27Z","created_by":"dirtydishes","updated_at":"2026-05-25T19:07:24Z","started_at":"2026-05-25T19:05:31Z","closed_at":"2026-05-25T19:07:24Z","close_reason":"Added centralized 30-second VLC media caching for native playback and validated the iOS build.","comments":[{"id":"019e60b6-9b11-7c5f-af57-c9e54ac6129f","issue_id":"dreamio-3yb","author":"dirtydishes","text":"Device testing still shows repeated VLC buffering after 15-second jumps. Added DEBUG playback snapshots on state changes and delayed post-jump probes so the next pass can distinguish a stalled stream/range reconnect from a seek-state issue.","created_at":"2026-05-25T19:57:21Z"},{"id":"019e60c4-f824-79fe-b974-9fbe9fe91788","issue_id":"dreamio-3yb","author":"dirtydishes","text":"Latest device logs show VLC remains at the pre-jump time/position after a fixed skip while buffering. Added backend-only stalled jump recovery: after a short no-progress buffering window, reopen the same media with :start-time set to the requested target and reattach accepted subtitle candidates plus selected tracks where available.","created_at":"2026-05-25T20:13:02Z"}],"dependency_count":0,"dependent_count":0,"comment_count":2}
|
||||||
{"_type":"issue","id":"dreamio-kdf","title":"Stop tracking Xcode user state","description":"Xcode user interface state files are machine-specific and currently tracked, which causes noisy local modifications and pull conflicts. Remove tracked xcuserstate files from the git index while keeping ignore rules in place.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-25T18:31:36Z","created_by":"dirtydishes","updated_at":"2026-05-25T18:31:51Z","started_at":"2026-05-25T18:31:39Z","closed_at":"2026-05-25T18:31:51Z","close_reason":"Tracked Xcode user interface state files were removed from the git index, and existing ignore rules now cover regenerated xcuserdata files.","dependency_count":0,"dependent_count":0,"comment_count":0}
|
{"_type":"issue","id":"dreamio-kdf","title":"Stop tracking Xcode user state","description":"Xcode user interface state files are machine-specific and currently tracked, which causes noisy local modifications and pull conflicts. Remove tracked xcuserstate files from the git index while keeping ignore rules in place.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-25T18:31:36Z","created_by":"dirtydishes","updated_at":"2026-05-25T18:31:51Z","started_at":"2026-05-25T18:31:39Z","closed_at":"2026-05-25T18:31:51Z","close_reason":"Tracked Xcode user interface state files were removed from the git index, and existing ignore rules now cover regenerated xcuserdata files.","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||||
{"_type":"issue","id":"dreamio-0lt","title":"add audio track selection","description":"Add native player support for viewing and switching available audio tracks during playback.","status":"closed","priority":2,"issue_type":"feature","owner":"dishes@dpdrm.com","created_at":"2026-05-25T17:00:53Z","created_by":"dirtydishes","updated_at":"2026-05-25T17:01:33Z","closed_at":"2026-05-25T17:01:33Z","close_reason":"not implementing now; user asked only to move previous work to the audio-track-selection branch","dependency_count":0,"dependent_count":0,"comment_count":0}
|
{"_type":"issue","id":"dreamio-0lt","title":"add audio track selection","description":"Add native player support for viewing and switching available audio tracks during playback.","status":"closed","priority":2,"issue_type":"feature","owner":"dishes@dpdrm.com","created_at":"2026-05-25T17:00:53Z","created_by":"dirtydishes","updated_at":"2026-05-25T17:01:33Z","closed_at":"2026-05-25T17:01:33Z","close_reason":"not implementing now; user asked only to move previous work to the audio-track-selection branch","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||||
|
|
|
||||||
|
|
@ -265,18 +265,14 @@ final class HTTPRangeRemoteFetcher {
|
||||||
self.session = session
|
self.session = session
|
||||||
}
|
}
|
||||||
|
|
||||||
func probe() async -> HTTPRangeProbeResult {
|
func probe(timeoutInterval: TimeInterval = 3) async -> HTTPRangeProbeResult {
|
||||||
guard ["http", "https"].contains(url.scheme?.lowercased() ?? "") else {
|
guard ["http", "https"].contains(url.scheme?.lowercased() ?? "") else {
|
||||||
return HTTPRangeProbeResult(isCacheable: false, contentLength: nil, fallbackReason: "non-http-url")
|
return HTTPRangeProbeResult(isCacheable: false, contentLength: nil, fallbackReason: "non-http-url")
|
||||||
}
|
}
|
||||||
guard !url.path.lowercased().hasSuffix(".m3u8") else {
|
guard !url.path.lowercased().hasSuffix(".m3u8") else {
|
||||||
return HTTPRangeProbeResult(isCacheable: false, contentLength: nil, fallbackReason: "hls-playlist")
|
return HTTPRangeProbeResult(isCacheable: false, contentLength: nil, fallbackReason: "hls-playlist")
|
||||||
}
|
}
|
||||||
guard !Self.shouldBypassCache(for: url) else {
|
if let head = try? await response(for: request(method: "HEAD", timeoutInterval: timeoutInterval)),
|
||||||
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) {
|
(200..<400).contains(head.statusCode) {
|
||||||
let acceptsRanges = header("Accept-Ranges", in: head)?.lowercased().contains("bytes") == true
|
let acceptsRanges = header("Accept-Ranges", in: head)?.lowercased().contains("bytes") == true
|
||||||
let length = header("Content-Length", in: head).flatMap(Int64.init)
|
let length = header("Content-Length", in: head).flatMap(Int64.init)
|
||||||
|
|
@ -285,7 +281,7 @@ final class HTTPRangeRemoteFetcher {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var tinyRequest = request(method: "GET")
|
var tinyRequest = request(method: "GET", timeoutInterval: timeoutInterval)
|
||||||
tinyRequest.setValue("bytes=0-0", forHTTPHeaderField: "Range")
|
tinyRequest.setValue("bytes=0-0", forHTTPHeaderField: "Range")
|
||||||
do {
|
do {
|
||||||
let (data, response) = try await session.data(for: tinyRequest)
|
let (data, response) = try await session.data(for: tinyRequest)
|
||||||
|
|
@ -321,9 +317,12 @@ final class HTTPRangeRemoteFetcher {
|
||||||
return response as? HTTPURLResponse
|
return response as? HTTPURLResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
private func request(method: String) -> URLRequest {
|
private func request(method: String, timeoutInterval: TimeInterval? = nil) -> URLRequest {
|
||||||
var request = URLRequest(url: url)
|
var request = URLRequest(url: url)
|
||||||
request.httpMethod = method
|
request.httpMethod = method
|
||||||
|
if let timeoutInterval {
|
||||||
|
request.timeoutInterval = timeoutInterval
|
||||||
|
}
|
||||||
headers.forEach { key, value in
|
headers.forEach { key, value in
|
||||||
request.setValue(value, forHTTPHeaderField: key)
|
request.setValue(value, forHTTPHeaderField: key)
|
||||||
}
|
}
|
||||||
|
|
@ -334,10 +333,6 @@ final class HTTPRangeRemoteFetcher {
|
||||||
response.value(forHTTPHeaderField: name)
|
response.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 {
|
enum HTTPRangeCacheError: Error {
|
||||||
|
|
|
||||||
|
|
@ -71,59 +71,15 @@ final class VLCNativePlaybackBackend: NSObject, NativePlaybackBackend {
|
||||||
lastLoggedState = nil
|
lastLoggedState = nil
|
||||||
lastBufferingLogTime = nil
|
lastBufferingLogTime = nil
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
print("[DreamioVLC] cache-probe url=\(URLRedactor.redactedURLString(request.playbackURL.absoluteString))")
|
print("[DreamioVLC] cache fallback reason=startup-direct-preferred url=\(URLRedactor.redactedURLString(request.playbackURL.absoluteString))")
|
||||||
#endif
|
#endif
|
||||||
playbackStartupTask = Task { [weak self] in
|
startVLCMedia(
|
||||||
guard let self else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let fetcher = HTTPRangeRemoteFetcher(url: request.playbackURL, headers: request.headers)
|
|
||||||
let probe = await fetcher.probe()
|
|
||||||
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 = session
|
|
||||||
session.prefetch(aroundByteOffset: 0)
|
|
||||||
self.startVLCMedia(
|
|
||||||
url: localURL,
|
|
||||||
request: request,
|
|
||||||
playbackMode: "local-cache",
|
|
||||||
cachingMilliseconds: 500,
|
|
||||||
includeRemoteHTTPOptions: false
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
} catch {
|
|
||||||
#if DEBUG
|
|
||||||
print("[DreamioVLC] cache fallback reason=local-server-error-\(error)")
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
#if DEBUG
|
|
||||||
print("[DreamioVLC] cache fallback reason=\(probe.fallbackReason ?? "unknown")")
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
await MainActor.run {
|
|
||||||
self.startVLCMedia(
|
|
||||||
url: request.playbackURL,
|
url: request.playbackURL,
|
||||||
request: request,
|
request: request,
|
||||||
playbackMode: "direct",
|
playbackMode: "direct",
|
||||||
cachingMilliseconds: 2500,
|
cachingMilliseconds: 2500,
|
||||||
includeRemoteHTTPOptions: true
|
includeRemoteHTTPOptions: true
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
|
||||||
#else
|
#else
|
||||||
onFailure?(NativePlaybackError.backendUnavailable)
|
onFailure?(NativePlaybackError.backendUnavailable)
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,8 @@ struct StreamResolverTests {
|
||||||
testRangeCacheForegroundMissFetchesAlignedChunks()
|
testRangeCacheForegroundMissFetchesAlignedChunks()
|
||||||
await testRangeCacheForegroundMissReprioritizesPrefetch()
|
await testRangeCacheForegroundMissReprioritizesPrefetch()
|
||||||
await testRangeCacheHitFollowsActualPostSeekReadArea()
|
await testRangeCacheHitFollowsActualPostSeekReadArea()
|
||||||
await testRangeProbeBypassesTailIndexContainers()
|
await testRangeProbeAllowsRangeCacheForMKVWhenServerSupportsRanges()
|
||||||
|
await testRangeProbeAppliesRequestTimeout()
|
||||||
await testRangeProbeFallsBackWhenServerIgnoresRange()
|
await testRangeProbeFallsBackWhenServerIgnoresRange()
|
||||||
await testRangeFetcherPreservesHeaders()
|
await testRangeFetcherPreservesHeaders()
|
||||||
print("StreamResolverTests passed")
|
print("StreamResolverTests passed")
|
||||||
|
|
@ -541,17 +542,21 @@ struct StreamResolverTests {
|
||||||
try? await Task.sleep(nanoseconds: 50_000_000)
|
try? await Task.sleep(nanoseconds: 50_000_000)
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func testRangeProbeBypassesTailIndexContainers() async {
|
private static func testRangeProbeAllowsRangeCacheForMKVWhenServerSupportsRanges() async {
|
||||||
var requestCount = 0
|
var requestCount = 0
|
||||||
MockURLProtocol.handler = { request in
|
MockURLProtocol.handler = { request in
|
||||||
requestCount += 1
|
requestCount += 1
|
||||||
|
assertEqual(request.httpMethod, "HEAD")
|
||||||
let response = HTTPURLResponse(
|
let response = HTTPURLResponse(
|
||||||
url: request.url!,
|
url: request.url!,
|
||||||
statusCode: 206,
|
statusCode: 200,
|
||||||
httpVersion: nil,
|
httpVersion: nil,
|
||||||
headerFields: ["Content-Range": "bytes 0-0/20"]
|
headerFields: [
|
||||||
|
"Accept-Ranges": "bytes",
|
||||||
|
"Content-Length": "20"
|
||||||
|
]
|
||||||
)!
|
)!
|
||||||
return (Data([1]), response)
|
return (Data(), response)
|
||||||
}
|
}
|
||||||
|
|
||||||
let fetcher = HTTPRangeRemoteFetcher(
|
let fetcher = HTTPRangeRemoteFetcher(
|
||||||
|
|
@ -561,9 +566,36 @@ struct StreamResolverTests {
|
||||||
)
|
)
|
||||||
let probe = await fetcher.probe()
|
let probe = await fetcher.probe()
|
||||||
|
|
||||||
assertEqual(probe.isCacheable, false)
|
assertEqual(probe.isCacheable, true)
|
||||||
assertEqual(probe.fallbackReason, "tail-index-container")
|
assertEqual(probe.contentLength, 20)
|
||||||
assertEqual(requestCount, 0)
|
assertEqual(probe.fallbackReason, nil)
|
||||||
|
assertEqual(requestCount, 1)
|
||||||
|
MockURLProtocol.handler = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func testRangeProbeAppliesRequestTimeout() async {
|
||||||
|
MockURLProtocol.handler = { request in
|
||||||
|
assertEqual(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
|
MockURLProtocol.handler = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
549
docs/turns/2026-05-26-fix-vlc-range-cache-mkv.html
Normal file
549
docs/turns/2026-05-26-fix-vlc-range-cache-mkv.html
Normal file
File diff suppressed because one or more lines are too long
Loading…
Add table
Add a link
Reference in a new issue