fix(player): centralize playback speed writes in a single controller - #3238
Draft
NihilDigit wants to merge 9 commits into
Draft
fix(player): centralize playback speed writes in a single controller#3238NihilDigit wants to merge 9 commits into
NihilDigit wants to merge 9 commits into
Conversation
Add a "remember playback speed" toggle, on by default, preserving the existing global-sticky behaviour. With it off, speed is scoped to a single playback: it survives episode switches inside the player page, resets on leaving, and in-player adjustments are no longer written back to the config. Speed previously had one source, the global config, and PlaybackSpeedExtension re-applied that value on every onStart, so "for this playback only" could not be expressed. No new holder is needed: PlayerExtensionManager is constructed once in EpisodeFetchSelectPlayState and only onStart is per-session, so an extension instance already lives exactly as long as the player page. The caller now supplies the speed flow, and EpisodeViewModel holds a session-scoped override that falls back to the config until the user adjusts it. The toggle needs no upgrade migration. Users who had set a global speed keep their stored playbackSpeed and start at the same speed as before; for users who never adjusted it the value was already 1. Digit keys 1/2/3 and their numpad equivalents jump to 1x/2x/3x, clamped to the configured range. The long-press indicator now shows the speed actually in use, and the Chinese description of that setting is corrected from 播放倍率 to 速度, since it is an absolute speed rather than a multiplier. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The rewritten subject details page lost the caches entry point: desktop had none at all, and on mobile it was buried inside the episode list dialog. Add a "manage cache" button to the episodes SectionHeader, matching the Figma spec (SectionHeader + Actions): - Wide two/three-column: download icon + "Manage cache" text button - Narrow two-column and compact: icon-only (the labelled button squeezes the section title into vertical wrapping with English strings), controlled by SubjectDetailsLayoutParams.showCacheButtonLabel Verified via SubjectDetailsScreenshotTest at all four breakpoints. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…el area (open-ani#3212) fix(player): 滑动快进改为上滑取消, 不再使用角落取消区域 (open-ani#3211) 取消判定改为以起手位置为基准的垂直位移: 快进中向上滑动超过 144dp 即取消, 滑回阈值以内恢复. 起手落点不再直接决定是否取消, 修复右手拇指起手即误取消的问题, 同时简化手势模型. 进度条触摸拖动的取消判定同步改为同一套上滑阈值, 与屏幕横滑共用 isVerticalDragCancelled. 一并修复滑动快进松手后的 500ms 空指示器: 该指示器只由 SwipeSeekerState 驱动, GestureIndicatorState.state 全程为 null, 松手后淡出的 500ms 里 when 分支落到 null, 渲染出一个空 Surface. 改为在淡出期间保留最后一帧快照. 另: 快进过程中滑回原点现在显示 00:00, 不再沿用上一次的秒数.
…open-ani#3222) - Add windows-11-arm to the release matrix, publishing ani-*-windows-aarch64.zip, and add the matching links to the release template - Ship the WoA64 SQLite native on the runtime classpath: AndroidX's NativeLibraryLoader resolves natives/windows_arm64/sqliteJni.dll through the classloader, so the official jar no longer needs patching. This works uniformly for local gradlew run, tests, CI packaging and releases. - Build that DLL from source in :ci-helper:sqlite-woa64 on Windows ARM64 hosts. The module downloads the SQLite 3.50.1 amalgamation and the sqlite_bindings.cpp of androidx.sqlite 2.6.1, both pinned by sha256, and compiles them with MSVC using the same defines as the AndroidX build. No binary is checked in, and no manual step is needed: :app:desktop:run pulls the DLL in through app-data's runtimeOnly dependency. The compile task is only wired up on Windows ARM64 hosts, so other platforms are unaffected. - Building it needs the Visual Studio "C++ ARM64 build tools" component, which the windows-11-arm runner already provides - Drop the CI step that compiled with MSVC and patched the jar in place Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
取页统一改走 App 同一条 WebSessionManager 链路, 复用完全相同的 solver 链 (MacCMS 纯 HTTP 协议 → JCEF 浏览器 + ONNX 识别), 解出的 cookie 与 UA 同步到 HTTP 侧与播放页 WebView, 后续步骤沿用同一个会话. MCP 是无人值守的评测工具, 没有 App 那样的手动兜底对话框, 因此只有两种结局: 能自动解则解完重试, 并在 details.autoSolvedCaptcha 记录解掉的类型; 解不掉则 立即终止该数据源的整个流程 (不再换搜索词硬试, 也不再对站点发请求), 避免把 "被挡" 误报成 "selector 写错了". 顺带对齐三处 App 行为: - test_subject_episode_source 的 selector 源改为共用同一个会话管理器 (原先固定用 UnsupportedCaptchaBrowserFactory, 等于永远解不了); - 视频解析的 matcher 注入验证码 cookie, 播放页沿用刚解掉的会话; - 验证码浏览器与视频解析共用同一个 CefApp 与 cache 目录. 改动全部限于 tools 模块与其文档, 未改 App 源码. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
fix(player): centralize playback speed writes in a single controller WIP. 新增 PlaybackSpeedController 作为倍速的唯一写入者, 把倍速拆成基础倍速与 临时倍速两层, 生效值为 `临时 ?: 基础`。 修复长按快进松手后倍速失步 (原先依赖 originalSpeed 快照), 以及 PlaybackSpeedExtension 在后台线程调用 PlaybackSpeed.set 抛 wrong-thread 异常导致倍速同步任务终止。 删除 PlaybackSpeedExtension 及其测试: ExoPlayer / mpv / AVKit 换 media 都不会 重置倍速, 该扩展的重新应用逻辑没有对应的实际行为。 尚未编译与测试。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> @
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
WIP
倍速原先有三个写入者:
PlaybackSpeedExtension跟随配置流写入,PlaybackSpeedControllerState响应倍速 UI 写入,PlayerFastSkipState在长按快进期间写入。三者都直接调用PlaybackSpeed.set,互相看不到对方的状态,长按结束时只能依靠快照还原此前的倍速。本 PR 新增
PlaybackSpeedController,由它统一写入倍速。修复
长按快进松手后倍速失步。
PlayerFastSkipState按下时快照playbackSpeed.value,松手时写回。这个快照是否正确,取决于按下那一刻播放器上恰好是基础倍速。若onStart与onStop不配对,写回的就是快进倍速本身;originalSpeed初值为0f,一旦在任何onStart之前触发onStop,写回的是 0,mpv 会抛出异常(require(speed > 0f)),ExoPlayer 则表现为画面冻结。改动后,松手只是丢弃临时倍速,播放器随即回到基础倍速,不再需要快照。
后台线程写入播放器,倍速同步中断。
PlaybackSpeedExtension在Dispatchers.Default上调用PlaybackSpeed.set。ExoPlayer 要求在主线程访问,而PlaybackSpeed.set没有@UiThread标注,签名上看不出这条约束。异常抛在collect内部,倍速同步任务随之终止,此后所有倍速变更都不再生效。复现步骤:杀进程,重新进入播放页。改动后只有
PlaybackSpeedController调用set,线程切换也只需在这一处处理。结构
倍速拆成两个状态,生效值由二者推导:
EpisodeViewModel.playbackSpeedFlow为准,即配置值叠加本次播放的 override,来自 feat(player): add a playback speed scope toggle and digit shortcuts #3227播放器上生效的倍速恒为
临时 ?: 基础。生效值不单独保存,因此始终与这两个状态一致。倍速控件读基础倍速,长按快进期间不跳动;剩余时间这类按真实播放速率计算的显示读生效倍速。
PlaybackSpeedController由EpisodeViewModel持有,与播放器同生命周期。临时倍速的存活范围必须大于单次 composition,否则重组或退出 composition 会把长按倍速留在播放器上。PlaybackSpeedControllerState相应改为适配层,只负责转发 UI 事件、把基础倍速映射为 Compose state,不再写入播放器。video-player不依赖app-data,它拿到的只有StateFlow和 lambda,类型上无法直接触及播放器。EpisodeViewModel.setPlaybackSpeed增加persist参数。拖动 Slider 期间不写 DataStore,松手和键盘调整时才写。删除 PlaybackSpeedExtension
在 #3227 里引入这个扩展,是为了在切集后重新应用倍速,注释写的是“播放器在换片源后可能把速度重置回 1x”。这是一项防御性假设,三个主力后端均不重置:
PlaybackParameters,属播放器级设置,stop()和clearMediaItems()都不影响speed是全局属性,跨loadfile保持AVPlayer.rate兼任播放与暂停,换 item 时清零,但resumeImpl会用playbackSpeedFeature.value重新起播对应测试
reapplies the speed after switching episode一并删除。该测试手动调用set(1f)模拟重置,其断言的行为并不存在。AVKit 的恢复依赖
playbackSpeedFeature.value准确,这是统一写入口的另一项收益:绕开PlaybackSpeedController写播放器,会让 AVKit 用错误的值恢复播放。基于 #3227,该 PR 合并前本 PR 的 diff 包含它的改动。
Closes #3237