/* * 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"