straw/buildSrc/src/main/kotlin/ProjectConfig.kt
Sulkta d6399b367d vc=86: audit-fix sprint (HIGH H2 + 5 MED/LOW from the 2026-06-21 audit)
- Audio-only toggle no longer drops the max-resolution cap: both the
  fullscreen button (PlayerScreen) and the detail Audio pill (VideoDetailBody)
  rebuilt TrackSelectionParameters from a fresh Builder, wiping the data-saver
  ceiling. Now buildUpon() the existing params so the cap survives. (H2)
- Subscriptions Refresh button no longer sticks at "..." forever on a warm
  restart within the cache TTL: refreshIfStale clears the initial loading
  seed when it decides nothing needs refreshing. (M2)
- Search + Channel result lists get a stable item key (video url) so paging /
  shorts-filtering stops re-binding rows to new data (re-triggered thumbnail
  loads, scroll shift). (M3, M4)
- IosSafeHttpDataSource: the unknown-length (LENGTH_UNSET) chunk path rolls
  forward to the next Range chunk at inner-EOF instead of re-reading the
  exhausted source forever (was truncating playback to the first chunk). (M5)
- strawcore channel_feed_rss propagates the real failure (network/HTTP/parse)
  instead of collapsing every error to an empty list, so a broken fetch is
  distinguishable from "no new videos" (subscription_feed keeps its per-channel
  tolerance for fan-out). (M6)
- Feed recency: a clock-skewed future upload emits "0 seconds ago" (parses to
  top) instead of "just now" (which Kotlin's recency parser couldn't read, so
  the item sank to the bottom). (L4)

Deferred to a follow-up: M1 (bg-refresh cache-key mismatch — needs a worker
redesign) + M7 (build config-cache wiring). Verified: cargo check/clippy +
full Android compileDebugKotlin green.
2026-06-21 13:37:51 -07:00

230 lines
14 KiB
Kotlin
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* SPDX-FileCopyrightText: 2026 Sulkta
* SPDX-License-Identifier: GPL-3.0-or-later
*/
const val STRAW_SDK_COMPILE_MAJOR = 36
const val STRAW_SDK_COMPILE_MINOR = 1
const val STRAW_SDK_TARGET = 35
// Sulkta fork — Straw
//
// vc=86 / 0.1.0-CT — audit-fix sprint (code-audit HIGH H2 + 5 MED/LOW):
// * Audio-only toggle no longer drops your max-resolution cap. Both the
// fullscreen button and the detail "Audio" pill rebuilt the track-
// selection params from a fresh Builder, wiping the data-saver ceiling;
// they now buildUpon() the existing params so the cap survives.
// * Subscriptions "Refresh" button no longer sticks at "..." forever on a
// warm restart within the cache TTL — refreshIfStale now clears the
// initial loading seed when it decides nothing needs refreshing.
// * Search + Channel result lists carry a stable item key (the video url)
// so appending a page / filtering shorts stops re-binding rows to new
// data (which re-triggered thumbnail loads + shifted scroll).
// * iOS-safe data source: the unknown-length (LENGTH_UNSET) chunk path now
// rolls forward to the next Range chunk at inner-EOF instead of
// re-reading the exhausted source forever (was truncating playback to
// the first chunk on any inner that reports no length).
// * strawcore channel_feed_rss now PROPAGATES the real failure (network /
// HTTP / parse) instead of collapsing every error to an empty list, so a
// broken channel fetch is distinguishable from "no new videos" (the
// bulk subscription_feed keeps its per-channel tolerance).
// * Feed recency: a clock-skewed future upload emits "0 seconds ago"
// (parses to top) instead of "just now" (which the Kotlin recency parser
// couldn't read → sank the item to the bottom).
//
// vc=85 / 0.1.0-CS — image caching + SB/RYD → Rust + crash/autoplay fixes:
// * Thumbnails + channel icons stay cached. Coil's default disk cache is
// only 2% of the phone's FREE space, so on a storage-tight device the
// subs feed (the most image-heavy screen) thrashed its cache and
// re-downloaded thumbnails on every visit — the "each load takes a
// second" lag. Pinned an explicit 256 MB image disk cache + a sized
// memory cache via a SingletonImageLoader.Factory.
// * SponsorBlock + Return-YouTube-Dislike clients moved out of Kotlin
// into Rust (strawcore fetchSponsorSegments / fetchRydVotes) — network
// + JSON belong behind the FFI, not in the UI layer. SponsorBlock keeps
// its privacy-preserving SHA-256 hash-prefix lookup. Kotlin is now a
// thin shim mapping the FFI records onto the existing SbSegment/RydVotes
// domain types; behavior identical. (Migration #2 of "all backend → Rust".)
// * FIX a crash: extract_channel_id() sliced the channel URL using a
// length derived from a lowercased copy of itself — to_lowercase() can
// change byte length on non-ASCII, so a channel URL with any non-ASCII
// tail could panic across the FFI and abort the app on a feed refresh.
// Now matches the prefix case-insensitively against the original with
// length + char-boundary guards.
// * FIX autoplay hijack: advancing to the next video resolves over ~500ms;
// if you manually started a different video in that window, autoplay
// would still fire and replace your choice with the stale next-up. Added
// a staleness fence (same class as the vc=83 minibar fix).
//
// vc=84 / 0.1.0-CR — name + stream-picker → Rust:
// * The app is now just "Straw" in the launcher, not "Straw debug" —
// we're past the debug-branding phase. (The package id stays
// com.sulkta.straw.debug under the hood so updates install in place
// and nobody loses their subscriptions/history; dropping the suffix
// for real is a separate release-track cutover.)
// * The stream-selection logic (pick the playable video/audio/combined
// URLs from an extraction, honoring the resolution cap) moved out of
// Kotlin into the Rust strawcore layer (resolvePlayback). The Kotlin
// side is now a thin shim that supplies the cap and attaches
// SponsorBlock segments. Behavior is identical — same URLs picked,
// same data-saver fallback — it's just backend logic living in Rust
// where it belongs. First step of moving all backend logic to Rust.
//
// vc=83 / 0.1.0-CQ — fix: swapping videos from the minibar kept playing
// the old one:
// * With a video minimized to the bottom minibar, picking a different
// video updated the title, details and related list but the OLD video
// kept playing. The inline player's resolve→play effect read the
// shared (activity-scoped) view-model's `resolved` stream without
// checking it actually belonged to the newly-opened video. For one
// frame after the swap the view-model still holds the previous video's
// resolved URLs, so playback was claimed under the NEW url while
// streaming the OLD media — and the correct re-fire was then swallowed
// by the "already playing this url" short-circuit, so the new video
// never started. Restored the loadedUrl fence (the same guard
// VideoDetailBody and every ViewModel already use) that the vc=75
// expandable-player rearchitect had dropped from the inline wiring.
//
// vc=82 / 0.1.0-CP — subscription-feed enrichment goes lightweight:
// * Filling in a feed row's view count + duration used to run the FULL
// stream extraction per item (the same path opening a video does):
// Android /player, the JS signature/nsig deobfuscation, an extra WEB
// /player metadata round-trip, plus stream/manifest/caption parsing —
// then threw all of it away except those two numbers. On a refresh
// that touched dozens of items that was dozens of redundant heavy
// round-trips.
// * Now it calls a new strawcore stream_metadata() path that does only
// the Android /player fetch + the videoDetails read those two fields
// come from, skipping the JS eval, the extra WEB round-trip, iOS, and
// stream extraction. The values are identical (they come from the same
// videoDetails the full path used), the feed just stops paying for
// work it discarded. (strawcore 30f24d2.)
//
// vc=81 / 0.1.0-CO — perf-audit app-side batch (no behavior change):
// * Search: the reactive cache-preview filter no longer runs on the
// main thread on every keystroke. It walked the entire cached-
// results pool (thousands of items on a heavy user) inline; now each
// keystroke debounces ~150ms and the scan runs on Dispatchers.Default.
// A submit cancels the pending preview so it can't clobber live
// results.
// * Subscription feed: the merge memoizes the relative-upload-date
// parse by string, so the recency regex runs once per distinct
// "N days ago" value instead of once per item (~3000 per merge on a
// 200-sub feed) — across hydration, every refresh, and each
// background-enrichment emit.
//
// vc=80 / 0.1.0-CN — strawcore extraction perf (Rust batch):
// * The extractor borrows the streamingData subtree out of the Android
// + iOS player responses instead of deep-cloning the largest part of
// each response, and merges format objects by reference rather than
// cloning all ~20-40 of them per video open.
// * Channel pages fetch their Home + Videos tabs concurrently, so
// opening a channel costs one network round-trip of latency instead
// of two.
// * Response bodies decode in place on the (overwhelmingly common)
// valid-UTF-8 path instead of always copying.
// No behavior change — purely allocation + latency wins in strawcore
// (strawcore 91d4824).
//
// vc=79 / 0.1.0-CM — perf-audit pass-2 (app-side slam-dunks):
// * FIX a wrong-thread crash: the headphone-disconnect settings watcher
// ran on the IO scope and touched the ExoPlayer (thread-affine to the
// Main thread it was built on) → "Player accessed on the wrong thread"
// latent on every session. Collector now runs on Dispatchers.Main.
// * recordSearch (json-encode + SharedPrefs write) moved off the Main
// thread into withContext(Dispatchers.IO).
//
// vc=78 / 0.1.0-CL — perf-audit batch 1 (app-side):
// * Autoplay-next no longer drops the user's max-resolution cap. The
// enter-video track-selection reset built params from scratch, wiping
// the data-saver ceiling on every URL change; now it re-enables the
// video track surgically + re-asserts applyMaxResolutionCap().
// * VideoDetail body is now a LazyColumn, not a verticalScroll Column.
// The related + more-from-channel lists recycle and defer each row's
// image decode + its two progress-overlay flow collectors until
// scrolled into view (was ~40 decodes + ~80 collectors mounted eagerly
// on every video open). Namespaced item keys; dialogs hoisted out.
//
// vc=77 / 0.1.0-CK — morph perf: static poster during collapse/morph:
// * The minibar + the whole collapse/expand morph now render the video's
// static poster, not the live TextureView. Scaling a live-playing
// TextureView through the morph's graphicsLayer every frame was the
// remaining sluggishness; the live PlayerView only mounts once settled
// fully expanded. Audio is unaffected (it's in the foreground service).
//
// vc=76 / 0.1.0-CJ — expandable-player smoothness pass:
// * The detail body no longer renders to an offscreen buffer every
// frame during the morph (CompositingStrategy.ModulateAlpha) — that
// offscreen alpha pass on the whole scroll subtree was the main
// cause of the sluggish feel.
// * Morph animation is now a snappy no-bounce spring instead of a
// 300ms FastOutSlowIn tween (which ramped slowly at both ends).
//
// vc=75 / 0.1.0-CI — expandable player (full rearchitect):
// * The video page and the bottom minibar are now ONE container that
// morphs continuously between them, both directions. Replaces the
// old separate Screen.VideoDetail page + MinibarOverlay (which just
// appeared/vanished). One fraction (0=minibar, 1=full page) drives a
// graphicsLayer scale+translate on a single mounted TextureView, so
// the morph runs in the render phase (smooth) and the same video
// surface stays live across the whole range — a true shared-element
// transition. Swipe the player down → it shrinks into the toolbar;
// swipe/tap the toolbar up → it grows back into the page.
// * Opening a video is no longer a nav push — it sets OpenVideo +
// expands. The browse screen underneath stays put, so collapsing
// drops you right back where you were.
// * Playback plumbing unchanged: shared controller, NowPlaying,
// setPlayingFrom, SponsorBlock, autoplay-next, PiP, background audio,
// and the true-fullscreen Player (⛶) all still key off NowPlaying.
//
// vc=73 / 0.1.0-CG — VideoDetail cleanup:
// * Inline player → TextureView surface so the swipe-down-to-minimize
// drag is smooth (a SurfaceView won't follow the Compose graphicsLayer
// transform — that was the stutter).
// * Description folded into a collapsible "Details" section, collapsed
// by default, sitting just above the recommendations.
// * Action buttons restyled into one tidy horizontally-scrollable row
// of uniform icon pills (dropped the redundant "Play").
//
// vc=23 / 0.1.0-AI — minibar + downloads UI + green theme:
// * MediaController/MediaSessionService unification — single ExoPlayer
// owned by PlaybackService, every UI surface is a controller client.
// Inline player on VideoDetail, fullscreen Player, and the new
// minibar overlay all drive the same underlying player; nothing
// restarts on screen transitions.
// * Persistent minibar overlay at the bottom of every non-Player
// screen whenever something is loaded. Tap → expand to fullscreen.
// Drag-down on fullscreen → minimize to minibar. ⌄ overlay button
// also minimizes. × on the minibar stops + clears.
// * Downloads page wired into the drawer.
// * Theme: forest-green primary palette in place of M3 default
// lavender / NewPipe red — modern, clean, distinct.
//
// vc=22 / 0.1.0-AH — V-2 player polish + local playlists:
// * Inline → fullscreen now hands off seek position. Tap Play (or the
// ⛶ pill on the inline player) while the inline is mid-track and
// the fullscreen Player picks up at the same point. Same handoff
// pattern as fullscreen → background from vc=21.
// * Local playlists: drawer entry "Playlists", "Save" button on
// VideoDetail. SharedPreferences-backed, no queue/autoplay yet
// (tap an entry to open VideoDetail as normal).
//
// vc=21 / 0.1.0-AG — player hand-off polish:
// * 🎧 background-audio button now captures the current position and
// resumes the foreground service from there instead of restarting.
// * HOME / recents button while on the player now hands off seamlessly
// to background audio (same position-preserving path) instead of
// auto-entering Picture-in-Picture. Manual PiP via the ⊟ overlay
// button is unchanged.
//
// vc=20 / 0.1.0-AF — channel-videos fix on top of the rust pipeline
// cutover. vc=19 returned empty subscription feeds because
// strawcore-core's channel_info wasn't doing the second browse for the
// Videos tab AND wasn't parsing the new lockupViewModel shape.
//
// vc=19 / 0.1.0-AE — rust pipeline cutover. Extraction via
// strawcore-core (Sulkta-OSS/strawcore) via the UniFFI wrapper; no
// NewPipeExtractor in the runtime path.
const val STRAW_VERSION_CODE = 86
const val STRAW_VERSION_NAME = "0.1.0-CT"
const val STRAW_APPLICATION_ID = "com.sulkta.straw"