- open .py/.js sources via ACTION_VIEW and save them to the vod list without switching
- name picked sources after the file instead of the clan path
- cancel in-flight config and jar fetches so switching never waits on the abandoned source
- keep load epoch guards on jar retries and on config write-back
- show http status in the terminal playback tip and allow slow opens a grace window
auto retry falls back soft->hard as well as hard->soft with one switch per ladder and skips when the hardware target is already in use; params sheet refreshes after kernel release so the decode row matches the new tier; continuous corner shapes for player sheets, bottom sheets and menus; new hard decode fallback tip in three locales
- iso: nested BDMV lookup, 4-thread mpls fetch with serial fallback, probe via Content-Range with stub guard, deferred volume snapshot, read/stream diagnostics with bounded log volume
- ui: douban hot search referer plus parse-guarded cache with self-heal, liquid glass defaults blur 5 / distortion 30 / translucency 50%, theme preview capsule mockup with continuous corners and dot-only nav row, fixed-height vertical nav rail with stride derived from slot count
- move quality chips from detail page into player episode sheet
- gate capsule press morph behind pressMorph flag on records, home poster and player params
- clear focus on blank tap, drag and ime dismiss in config manage page
- animate search rail site filter and align search field font weight
- boot guard, kv key spec and logging tweaks
- tmdb: map episodes by site episode numbers (no season hint, single regular season, unique numbers within season count) with a cache-key fingerprint, fallback to ordered mapping; episode thumbnails badge the site number
- tmdb: tvOnly guard keeps titles carrying a season hint on tv results across pickBest, bestHit, alias cache and lookup paths
- tmdb: alt-name fallback via apple cms vod_en, threaded through beans, nameCandidates, resolve and cache entries, hero, detail sections, episode row and list cards
- player: surface decode-capability-exceeded errors as a dedicated terminal message and cancel the play timeout on retry and error paths
- osd: rebuild the audio codec probe with live/predicted source tracking and extend the decoder guess chain
- detail: hero description collapses to three lines by default
- tests: episode mapping, tv guard, alt-name resolution and serialization regressions; sync the mobile ui spec
- player: keep fullscreen action icons in the bottom bar on all portrait screens
- player: scale bottom-bar action icons with the mm system to match the transport row
- detail: align follow/tmdb sheet backgrounds with the app theme
- detail: drop the line chips row from the portrait episode sheet
- detail: bump hero circle buttons to 50dp and add cast/genre section icons
- danmu: PlayerDanmakuView subclass enables non-block mode and disables idle sleep; vsync extrapolated danmu clock with rebase, retune and drift tracking in MyVideoView
- danmu: move the danmaku layer into the player container, tune the load controller and default alpha
- controls: PlayerSlideVisibility slides by measured content height, replacing the fixed-offset AnimatedVisibility wiring in top bar and overlay
- picture: remove the obsolete restart-required hint and the RestartRequired reason, sync three locales, tests, spec and history
- tmdb: fetch and cache logo artwork with language and vote rank, fallback to text title after timeout
- hero: split fixed image area from content column, add rating line and collapsible overview
- hero: scroll parallax scale and fade, pull-down stretch, status bar icon switch when hero scrolls out
- detail: add cast and genres sections after sources, drop related section, add detail_genres strings
- tests: scroll progress, rating note and tmdb logo parsing; sync mobile ui spec
refresh vodRecord.updateTime on pause/stop/completion via HistoryWriter.touch, gated by the WatchProgressStore.save result and the new canRecord; a missing row stays a no-op so deleted entries never revive
touch writes updateTime only and never the session preview's dataJson, keeping reverseSort/note/year of stored rows
flush progress and history time before engine release/releasePlayer/enterLive; sampler.flushNow carries a main-thread guard
jianpian: guard-early-return cleanup
- data: suspend-only dao and repository contracts, allowmainthreadqueries removed (r1/r2/r4)
- boot: dns-over-https client, thunder cache cleanup and quickjs native load moved off the main thread (oncreate main thread ~55ms -> ~44ms)
- probe: debug-gated step/trace/measure instrumentation for boot and frame kv cost
- player: portrait top bar actions relocated above the seek bar and evenly spread, title no longer clipped
- strictmode: detectnetwork and vmpolicy added, penaltydeath rolled back to penaltylog
- epg: parse on the okhttp callback thread, background asset load with empty-map fallback
- tests: data layer suspend contract, watch progress queue order, epg parser
- 974 `!!` removed across app/src/main/java, app/src/python/java and the quickjs wrapper (126 files, +1489/-1186)
- replacements: null guards, hoisted locals, requireNotNull; the few ?. cases are display/log-only and registered in the batch record
- no signature, visibility, field-type or nullability changes; NPE -> IllegalArgumentException at the same call sites, all inside pre-existing catch blocks
- rule + gate registered: SKILL.md null-safety line and the gate step, avbox-code-review-spec.md gains the null-safety section
- also carries the pending PyLog release log-strip keep rules (proguard-rules.pro + companion marker)
Verified: :app:assembleDebug green; :app:testDebugUnitTest 953 passed / 0 failed / 1 skipped; null_assert_gate.py PASS (only the 2 vendored L5 exemptions remain)
- migrate the 4 Java files under quickjs/src/main/.../wrapper to Kotlin
(ContextSetter, Function, JSUtils, UriUtil); the module now has 0 Java
- clarify that quickjs/ is not vendored code: the real third-party part is the
wang.harlon.quickjs maven dependency (wrapper-java 3.2.3, 21 classes), which
cannot be migrated because the native side matches it by name; only the 4
locally written files were converted
- keep the bytecode contract identical: UriUtil keeps its 4 public statics and
JSUtils keeps isEmpty/isNotEmpty/toJsonObject/toJsonArray plus the instance
methods, all with unchanged descriptors; the only rename is
toArray(byte[]) -> toByteArray (no call site uses it)
- keep @Retention(RUNTIME) on both annotations explicit, and restore the AOSP
and caoccao Apache-2.0 headers the port had dropped
- add UriUtilTest (13 cases) written against the Java version first, so the
Kotlin port is proven equivalent by tests rather than by inspection;
resolveToUri stays untested because Uri.parse returns null under Robolectric
- quickjs: add the kotlin.android plugin and set jvmTarget to 21
- full :app:testDebugUnitTest is 904 cases / 0 failures / 1 skipped
- record the rules and pitfalls in the Kotlin migration spec (section 7.24)
- migrate 11 test files / 1241 lines from Java to Kotlin, pure language change
with no logic edits: ConfigParser, Depot, VodInfoReverse, EpisodeMatcher,
FileUtilsNativeLibRepair, SearchHelper, PlaybackAttemptState,
SubtitleFilePicker, BootGuard, KVKeySpec, KVDecoder
- keep the test count of every class identical (139 cases in total); full
:app:testDebugUnitTest stays at 891 cases / 0 failures / 1 skipped
- app/src now has 0 Java; the only remaining Java lives in the vendored
quickjs wrapper module
- fix the stale "app/src/test is out of migration scope" row and record the
test-side conversion rules in the Kotlin migration spec
- player: remove the per-key 500ms click throttle entirely, delete the dead
FastClickCheckUtil; play/pause now drives the kernel without any guard
- player: add zero-ripple press elasticity for all overlay icons (transport,
top bar, side rotate/lock) and a spring-driven seek thumb
- player: add enter/exit animations for bars, hints, side buttons and info OSD,
unified at 120ms; exit really leaves the composition tree
- player: fix the top bar stuck below the top edge by keeping the scrim out of
the content flow (Box instead of Column)
- player(osd): show audio decode kind and the real decoder name, read from
MediaCodecRenderer.getCodecInfo() with a MediaCodecList fallback
- player: unify the hard/soft decode rule into PlayerHelper.decodeKindOf
- records: reduce the gap between the segmented control and the first card
to 12dp (history + collect)
- chore: bump version to 1.2.3-alpha03 (versionCode 26) and add changelog entry
- docs: update the UI spec and the history archive
- TMDB: API key / proxy / image-proxy config, poster style (fixed/random/roll),
API & image connectivity test, cache-clear integration, unit tests
- Detail: cast section and TMDB metadata sections, hero and episodes adjustments
- Config page: new live / vod / poster-style / TMDB entry icons
- Settings: hide status bar toggle; fix top bar (title + actions) shifting up on
main and secondary pages when enabled (statusBars -> statusBarsIgnoringVisibility)
- i18n synced across 4 locales; UI spec and history docs updated
- replace the VmVar reflection bridge with direct forwarding, drop the epg/channel/settings/scroll counters
- hoist live page state into one StateFlow with per-cluster slices, screens take state plus grouped actions
- compute channel rows and the settings snapshot in the view model, composing reads no kv or config
- narrow the three live controllers to vm plus a small host, collect the state once in setContent
- sync the mobile ui spec (6.2, 4.5) and the feature history, add 38 unit tests, bump to 1.2.3-alpha02
ignore unusable video size in detail orientation (wait up to 10s, keep the portrait placeholder on timeout); use visibility-ignoring insets in detail hero and episode grid; let the home hero span the full width in rail mode on large screens; keep startedPlaybackKey when the session is unchanged so cast replay takes over the same playback
- derive the play state from the kernel so switching audio or video tracks stops leaving the paused overlay behind
- carry Format.id and label in the subtitle fingerprint so same language same codec tracks stay remembered across episodes
- rename the search layout menu entries to horizontal and vertical, and show horizontal result cards with the title under the poster
- remove the MX, Kodi, VLC and Reex external players and their package visibility queries, keeping the nearby tvbox push
- normalise the stored player type on start and in the playback config so stale external types fall back to the built in kernel
- sync the ui and playback specs, the history log and the review reports
- leave the music page without handing the decoder a released surface
- keep the centre play overlay out of the exit fullscreen pause
- detach the output surface before re-parenting the container so the codec never renders to an obsolete one
- rebind the player surface on the texture view reuse branch that showed a black screen with audio
- seed the expected content url only when taking over the same playback, so stale progress stays out of the bar
- keep the audio only confirmation bound to the current session so video content is not sent back to the music page
- align the render view to the configured type when a page takes the player over
- cover the pause overlay visibility with unit tests and sync the playback spec, history and review report
- drop the 16:9 preview: unwatch state is the poster hero, watch state is fullscreen
- exit fullscreen stops playback but keeps the kernel for a fast restart
- lines, cast and source rollback no longer start playback; cast only resolves the url
- play capsule, episode and quality chip always enter fullscreen, decided by DetailPlaybackPolicy with unit tests
- drop dead strings, drawable, imports and explanatory comments; sync ui spec and history
- Following tab: weekday filter bar with per-day badges and a today outline, follow cards with poster/update day/time/progress, edit multi-delete, two empty states, source routed card click
- Data: vodFollow entity with dao and repository, database version 1->2 with migration_1_2, exported schema 2.json, appgraph wiring
- Follow reminder sheet: multi-select weekdays and an hour list, summary card, save into room from the detail page, follow icon highlight and save toast
- Watched marks: per show and weekday marks in kv, valid for the whole week and reset at the week start; the menu check, poster check and badge counts share one rule
- Records and detail: source name capsule in history rows, shared reopenViaSubscription for collect and following cards, partition list and detail content adjustments
- Player: a single tap can summon the controls when not in playback (loading/error mask); slides, double tap and long press stay gated
- Home: the top left source capsule text weight goes to medium
- Sync skill docs (avbox-mobile-ui-spec 4.12, history/features.md)
- Add 1dp outlineVariant dividers between the three groups (20dp horizontal / 6dp vertical padding); drop the summary card's now-redundant 20dp top padding
- Remove the two section hints and the header subtitle, with their strings dropped from all three locales; the panel now carries section titles only
- Collapse headerContent from a Column to a single Row with symmetric 20dp padding
- Sync skill docs (avbox-mobile-ui-spec 4.4 and history/features.md)
- Records tab: history and favorites merged into one tab with a capsule segmented control (one outline icon per segment); the top bar, edit mode and delete dialogs hoisted into RecordsPage
- Navigation: four new tab icons (Home/Records/Following/Me); the settings tab is renamed to Me; a Following placeholder page is added
- Detail title card: action icons grow from three to four (follow entry); year/area/type move below the source pill and swap with it; the 72dp poster is removed
- FollowReminderSheet: multi-select weekdays (at least one kept), 24-hour hour list, manual time input and a summary card; nothing is persisted yet
- Theme: the default seed colour changes from blue to red; DefaultSeedArgb now references the red PresetSeeds constant
- Top bar: ConfigManagePage drops collapseEnabled = false and collapses on scroll again
- Empty states: full-screen empty/loading states now subtract the floating nav reserve (8 sites across HistoryTab/CollectTab/FollowingPage/HomePage)
- Sync skill docs (avbox-mobile-ui-spec 4.2-4.4/5/6.6 and history/features.md)
- SpiderReaper: retire spiders with a delay so a third-party jar still running on another thread is not destroyed mid-use (use-after-free)
- Wire SpiderReaper.track into every loader (List/Search/Detail/Play/Sort/Push), SourceViewModel, JarLoader and JsLoader
- SearchHits: extract search-hit filtering and arrival-order sorting; add SearchHitsTest
- HomePage/OptionMenu: subscription-source dropdown in the home top bar
- Sync skill docs
Replay keeps the live kernel position: same-address restart saves current progress first, reuse branches resume from the live position for same content (stored value across content), and the player view captures the live position before release instead of trusting the polled cache. Idle no longer zeroes the progress state and the progress tick is guarded against a detached kernel, so the bar no longer snaps back to zero during a kernel rebuild. Picture effects toggles force a kernel rebuild.
Also lands pending work: jar/js/py loaders clear off the main thread, parse moved off main, ApiConfig list reference swaps, BootGeneration guard, config switch toast removal, home empty-state retry and switch-race fixes, docs synced (playback-service spec 3.4, mobile-ui spec 6.17, history).