Skip to content

Refactor/layering - #46

Open
poluruprvn wants to merge 8 commits into
varunsalian:mainfrom
poluruprvn:refactor/layering
Open

poluruprvn wants to merge 8 commits into
varunsalian:mainfrom
poluruprvn:refactor/layering

Conversation

@poluruprvn

Copy link
Copy Markdown
Collaborator

No description provided.

varunsalian and others added 8 commits August 27, 2026 09:55
The player's "Checking stream 1 of N…" gate counts up to QuickPlayRules
.maxAttempts, but the slider that set it was dropped when the Quick Play
page was simplified. The pref survived upgrades and is per-device, so
users saw different counts on different installs (a desktop carrying an
old 10 next to a fresh Apple TV on the 5 default) with no way to change
either.

Adds a "Streams to try" dropdown (1-10) inside the Movies/Series tabs,
since the budget is per-content. It reads and writes the effective value
the players compute -- `tryNext ? maxAttempts : 1` -- so a legacy profile
with failover off shows 1 rather than a stale maxAttempts nothing honors,
and picking 1 disarms tryNextOnFailure instead of leaving a count the
ladder would ignore.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…okmark

A pinned-source launch commits its startup candidate ~40ms into decode and
seeks to the stored position 2ms later; on a fresh debrid stream mpv can
answer that seek by restarting at 0, and the exit save then filed ~2s over
the real bookmark (observed on-device: 2169ms over 661786ms) — locally and,
via the scrobble stops, on every connected tracker.

ResumeWriteGuard (new, unit-tested) arms at every resume seek and blocks
shallow writes until the seek lands, the viewer seeks, or a 30s settle
window elapses. Blocked writes persist the promised target instead of being
dropped, so speed/aspect changes survive and trackers scrobble the position
the resume promised. Startup seeks are verified and re-issued once if they
did not land; source switches, the renderer fallback, and the manual source
sheet carry the held target instead of the restart artifact. Near-finished
targets (>=80% of duration) never arm, so a held position can never
fabricate a watched mark. Every content open invalidates the outgoing
media's protection at the _openMedia choke point.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The activity's anti-yank guard can drop the resume seek (late-resolving
duration) and a stream can clamp one away — either way progress reports then
carry the shallow live position and overwrite the very bookmark the resume
promised, locally and on every tracker, because one positionMs feeds both.

The hold lives in the activity itself, armed with the exact target the READY
handler chose (including the anti-yank drop branch, capped to near-start),
so there is no Dart-side re-derivation to drift out of sync. While armed,
sendProgress substitutes the target for the live position; completion stays
on the raw position so a held target can never fabricate a watched mark, and
near-finished targets (>=80%) never arm. Releases on landing, any viewer
seek (controls, skip, a >3s backward discontinuity for remote seeks — the
floor keeps the subtitle-offset self-seek from releasing), item change, the
Stremio TV payload swap, or a 30s settle window. Source switches and the
manual-candidate rollback snapshot carry the held target instead of the
restart artifact.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
"Playback stops instead of falling back" overstated it: probeAttemptCount's
pack-top safety still probes the best single episode when a season pack was
ranked first, even at 1 stream.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replace the PikPak singleton and ad-hoc HTTP calls with a composition
root: one injected HTTP gateway (timeouts, retries, typed decoding) and
an injected PikPak client behind every call. PikPak returns typed files
and tasks instead of Map<String, dynamic>, and the two token-refresh
paths collapse into one.

Debrid providers resolve through a registry rather than broadcasting
questions to every provider, and the browser logic each cloud provider
had rewritten now lives in one place. Source files are grouped by
feature and named for their role; PlaylistEntry moves into the domain
layer; the deprecated screens and home sections are deleted.
…sion

The Map -> typed-model conversion left call sites reading kind/phase/size
as the wire values they used to be. All of them compile, the analyzer
reports eight as info, and one is invisible to it (String == String). Each
lands in an existing swallowing catch, so the symptom was a reassuring
wrong answer rather than an error:

- tv_service indexed a PikPakFile with file['mime_type'], throwing into
  _findVideoInFolder's catch — every pack resolved as "no source"
- phase == 'PHASE_TYPE_COMPLETE' was always false, so "Play folder" and
  "Add folder to playlist" always reported an empty folder
- kind as String? and size as String? threw TypeError, killing search
- kind == 'drive#folder' never recursed, flattening playlist folder trees
- select-all took virtual season rows, then deleted their synthetic ids

PikPakFile already had isFolder/isFile/isVirtual/isReady/isVideo and used
them correctly elsewhere; these sites now do too.

Tighten the parsers so this class cannot come back by hand: fromJson takes
Map rather than dynamic, which makes the shape the compiler's obligation
and deletes the `json as Map` runtime stand-in; a malformed list entry is
filtered with whereType instead of throwing away the whole listing; and a
missing captcha token raises PikPakUnexpectedResponse rather than a
TypeError that escaped the sealed PikPakApiFailure hierarchy.

A failed token refresh no longer clears the session. refreshAccessToken
returns false for recoverable reasons too — the 60s re-auth cooldown, a
network blip, a profile-scope StateError — and expire() cleared the stored
email, password, tokens, device id and restricted-folder pin, and set
pikpak_enabled false. refreshAccessToken already logs out in the one
unrecoverable branch, so expire() leaves the interface entirely.

Also restores guards the refactor dropped (empty-list batchTrash/Delete,
the fileId null check in magnet_link_handler), stops destinationId falling
back to a task id across two id spaces, lets a failed download phase escape
its poll loop instead of waiting out the 5-minute timeout, fires onProgress
at 0%, keeps unknown-size videos out of the 100 MB floor, and makes the two
readings of Real-Debrid's `selected` agree.

unrelated_type_equality_checks is now an error and packages/** is excluded
from analysis (it contributed 917 phantom errors). A guard test covers the
toString() and as-String forms the analyzer cannot see.

Doc fixes: the four blocks claiming the new layers are adopted now say what
is still live, the fabricated "90% local finished cutoff" is gone (it is
user-configurable, default 80, clamped 50-95), CODEMAP points at files that
exist, and test_baseline.py stops writing a wrong path into its own header.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
flutter analyze only flags unused *private* members, so orphaned public
widgets and whole files accumulate invisibly. dart_code_linter walks the
import graph from lib/ and reports both; dev/tool/dead_code.sh runs it.

Adding the dep re-resolves matcher/meta/test_api in pubspec.lock.
Found by dev/tool/dead_code.sh: no file in lib/, test/ or dev/ references
any of these, and none is a `part`. ~11.9k lines.

Left in place pending a look: features/pikpak/view_model.dart (only its
test keeps it alive) and app/cloud_sources.dart.
@poluruprvn
poluruprvn requested a review from varunsalian August 27, 2026 08:54

This branch has not been deployed

No deployments
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.

2 participants