# Technical Documentation: misaka10032w/Han1meViewer

> ℹ️ **Provenance:** Multi-Tier Ingestion · [CodeWiki Reference](https://codewiki.google/github.com/misaka10032w/Han1meViewer)

### Overview (`README.md`)

# 🚫 请不要在任何公开平台宣传本软件

本软件不接受任何形式的公开宣传。若出现公开宣传、搬运或引流，仓库维护者可能随时归档或隐藏仓库，并删除已编译的发行版。

# 🌸 Han1meViewer

🔞 R18 警告：未满 18 岁禁止下载和使用。

Han1meViewer 是一个使用 Kotlin 开发的 Android 客户端，用于浏览、搜索、播放和管理 hanime 相关公开视频页面内容。当前项目以 Jetpack Compose、Navigation Compose、ViewModel、StateFlow、Retrofit、Jsoup、Room、WorkManager、Media3/JZVD/MPV 为主要技术栈，围绕视频浏览、详情播放、搜索、用户列表、下载管理、评论、订阅、设置和隐私保护等功能组织。

本应用没有任何官方网站。GitHub Release 与 CI 构建产物是唯一下载及更新渠道。

## 📜 项目来源与许可

此项目最初 Fork 自 [YenalyLiew/Han1meViewer](https://github.com/YenalyLiew/Han1meViewer)，感谢原作者的贡献。原项目采用 [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0)。

根据 Apache 2.0 许可证要求：

- 保留原版权声明。
- 包含许可证文件副本。
- 说明修改内容。

原始仓库：[YenalyLiew/Han1meViewer](https://github.com/YenalyLiew/Han1meViewer)

许可证文件：[LICENSE](LICENSE)

## ⚠️ 免责声明

本应用程序与 `https://hanime1.me/` 及其关联方无任何隶属、合作或授权关系。

🔍 数据来源：

- 本应用仅通过常规网络请求和 HTML DOM 解析获取目标网站公开显示的信息。
- 本应用不访问网站后端数据库。
- 本应用不进行注入攻击、绕过授权或获取非公开用户隐私数据。

⚖️ 使用限制：

- 本应用仅用于技术研究、学习交流和移动端体验优化。
- 本应用不提供商业化服务。
- 使用者应自行遵守所在地区法律法规和目标网站规则。

©️ 版权归属：

- 原始视频、图文和相关内容版权归 `https://hanime1.me/` 或原始制作、发行方所有。
- 本应用不存储版权内容，不修改原始内容，不声称拥有内容所有权。

🛡️ 责任豁免：

- 本应用不保证数据完整性、准确性和实时性。
- 使用本应用产生的一切后果由用户自行承担。
- 不得将本应用用于任何非法用途。

若 `https://hanime1.me/` 认为本应用行为不当，可通过 GitHub 仓库内置功能联系维护者。

🔄 最后更新日期：`2026-05-23`

ℹ️ 建议通过官方渠道支持原站内容，并点击广告以支持网站运营者。

## 💬 参与讨论

Telegram 群组：[https://t.me/Han1meViewer](https://t.me/Han1meViewer)

## ✨ 功能概览

- 🏠 首页浏览：支持首页推荐、最新上传、分类入口、预览页和搜索入口。
- 🎬 视频详情：支持简介、作者、标签、系列视频、相关推荐、评论和子评论。
- ▶️ 视频播放：支持在线播放、本地下载文件播放、画中画、播放内核切换、倍速和画质选择。
- 🔍 搜索系统：支持关键词搜索、高级搜索、历史记录、筛选条件展示和分页加载。
- 👤 用户内容：支持收藏、稍后观看、播放列表、在线历史、订阅作者和创作中心。
- 💬 评论系统：支持评论排序、回复查看、举报和复制分享文本。
- 📥 下载管理：支持后台下载、下载分组（下载时可按系列名自动创建分组）、导入本地文件、自定义下载目录和下载后播放。
- 🔐 账号能力：支持登录、手动录入 cookies、账号资料、头像裁剪、登录状态检测和退出登录。
- 🎨 主题与设置：支持 Material 3 主题、动态颜色、首页布局、播放设置、MPV 设置、下载设置和网络设置。
- 🕵️ 隐私保护：支持应用锁、启动器图标伪装和手动 Cookie 管理。
- 🌐 网络辅助：支持 Cloudflare 处理、备用域名、代理相关设置、CDN/网络检测和更新检查。
- 📅 健康打卡：内置冲了么打卡页面和桌面小组件。
- 🎯 共享关键 H 帧：支持内置关键帧数据读取、设置和共享数据展示。
- 🗞️ 公告系统：支持首页公告展示、公告详情弹窗，并配套 `HanimeAnnouncementManagerWebUI` 维护 Firebase Realtime Database 中的 `announcements` 节点。

## 🗞️ 公告系统使用须知

公告系统分为两部分：

- 用户侧：应用启动后由 `MainViewModel.loadAnnouncements()` 从 Firebase Realtime Database 读取 `announcements` 节点，首页会展示有效公告，并可点开公告详情弹窗。
- 维护侧：`HanimeAnnouncementManagerWebUI` 用于维护公告数据，负责向同一个 `announcements` 节点写入、更新或下线公告。

`HanimeAnnouncementManagerWebUI` 目录包含两个文件：

- `HanimeAnnouncementManager.html`：公告管理网页，用于增删改查公告、切换启用状态。
- `PermitAdmin.py`：给 Firebase Auth 用户写入 `isAdmin` 自定义声明，用于控制谁能访问管理端。

前置条件：

- 应用必须能够正常访问网络。
- 项目中配置的 Firebase Realtime Database 地址必须可用。
- `announcements` 节点下的公告数据结构需要和 `Announcement` 模型一致，至少包含 `title`、`content`、`isActive` 等字段。
- 公告内容会按 `priority` 排序，只有 `isActive = true` 的公告才会显示。
- 管理端需要启用 Firebase Authentication 和 Realtime Database。
- 管理端需要准备 Firebase Web 配置参数和可登录的管理员邮箱、密码。
- 若使用 `PermitAdmin.py`，还需要 Firebase Admin SDK 的 `serviceAccountKey.json`。

使用规则：

- 首页公告默认会展示为有效公告列表。
- 用户关闭公告后，会记录 `last_dismiss_time`，24 小时内不会再次自动弹出。
- 公告详情支持标题、内容、发布时间、图片和按钮文案。
- 若公告配置了链接，内容中可直接展示可点击的 URL。

管理端使用步骤：

1. 在 `HanimeAnnouncementManager.html` 中填写 `firebaseConfig`。
2. 在 `ADMIN_EMAIL` 和 `ADMIN_PASSWORD` 中填入可登录 Firebase Auth 的管理员账号。
3. 确保 Realtime Database 规则允许该管理员账号读写 `announcements` 节点。
4. 如需批量授权管理员，先安装 `firebase-admin`，准备 `serviceAccountKey.json`，再运行 `PermitAdmin.py` 并填写目标用户 UID。
5. 打开管理页后即可新增、编辑、启用、停用或删除公告。

公告字段说明：

- `title`：公告标题。
- `content`：公告正文。
- `imageUrl`：公告图片地址，可选。
- `positiveText`：确认按钮文案，可选。
- `negativeText`：取消按钮文案，可选。
- `priority`：排序优先级。
- `timestamp`：公告时间戳，单位为秒。
- `isActive`：是否启用。

维护建议：

- 新公告建议先在 WebUI 中预览，再切换为 `isActive = true`。
- 过期或不再需要显示的公告应直接下线，而不是删除，以便回溯。
- 重要公告优先级数值应更高或更低需与 WebUI 约定保持一致，避免排序混乱。

## 📷 截图预览

 

 

 



## 🧱 当前架构

📦 项目采用多模块 Gradle 结构：

- `:app`：主应用模块，包含界面、导航、业务 ViewModel、网络解析、数据库、下载 Worker、播放器和资源文件。
- `:yenaly_libs`：项目内公共基础库，封装通用 Activity、Fragment、ViewModel、偏好设置、工具类和基础组件。
- `buildSrc`：构建配置辅助代码，用于版本号、构建类型和提交信息等 Gradle 逻辑。

🗂️ 主应用采用分层组织：

- `ui.activity`：入口 Activity、登录、Cloudflare 处理和手动 Cookie 输入页面。
- `ui.navigation`：Navigation Compose 路由定义、主导航、设置导航和安全跳转封装。
- `ui.screen`：Compose 页面实现，按首页、搜索、视频、设置、账号、下载等业务域划分。
- `ui.component`：可复用 Compose 组件，如视频卡片、弹窗、空/错/加载状态、AppBar、评论卡片等。
- `ui.viewmodel`：页面状态和业务动作入口，主要通过 `StateFlow` / `SharedFlow` 对 UI 暴露状态。
- `logic.network`：Retrofit Service、OkHttp 拦截器、DNS、CookieJar、更新服务和网络入口。
- `logic.model`：页面和网络解析后的领域模型，如 `HanimeVideo`、`HanimeInfo`、`HomePage`、`Playlists` 等。
- `logic.dao` / `logic.entity`：Room 数据库、DAO 和实体，用于历史、下载、搜索历史、关键帧、打卡等本地数据。
- `announcement` 链路：公告数据从 Firebase Realtime Database 的 `announcements` 节点读取，首页通过 `MainViewModel.loadAnnouncements()` 拉取并展示，管理端使用 `HanimeAnnouncementManagerWebUI` 维护公告内容。
- `worker`：WorkManager 下载任务和更新任务。
- `util`：主题、网络、文件、权限、Cookie、Toast、视频和通用工具方法。

🔁 整体数据流：

```text
Compose Screen -> ViewModel -> NetworkRepo / DatabaseRepo -> Retrofit + Jsoup / Room -> StateFlow -> Compose Screen
```

🎞️ 视频页数据流：

```text
VideoRoute -> VideoViewModel -> NetworkRepo.getHanimeVideo -> Parser -> HanimeVideo -> VideoScreen / Player / CommentScreen
```

📥 下载数据流：

```text
DownloadScreen -> DownloadViewModel -> HanimeDownloadManagerV2 -> WorkManager Worker -> Room -> Flow -> UI
```

## 🛠️ 技术栈

- Kotlin 2.3.x
- Java 21 toolchain
- Android Gradle Plugin 9.2.x
- Jetpack Compose + Material 3
- Navigation Compose + Kotlin Serialization typed routes
- ViewModel + StateFlow + SharedFlow
- Retrofit 3 + kotlinx.serialization converter
- Jsoup HTML 解析
- Room + KSP
- WorkManager 后台任务
- Coil 3 Compose 图片加载
- Media3 ExoPlayer、JZVD、MPV Android
- Firebase Analytics、Crashlytics、Performance、Remote Config、Realtime Database
- AndroidX Window、Biometric、SplashScreen、Preference
- AboutLibraries 开源许可展示

## 🧰 构建环境

✅ 推荐环境：

- Android Studio Panda 或更新版本。
- JDK 21。
- Gradle Wrapper 使用仓库内置版本。
- 编译 SDK：`37`。
- 最低支持：Android 8.1，API `27`。
- 目标 SDK：`37`。

📌 关键版本以仓库文件为准：

- `gradle.properties`
- `gradle/libs.versions.toml`
- `app/build.gradle.kts`
- `buildSrc/src/main/java/Config.kt`

## 🚀 本地运行

1. 克隆项目：

   ```bash
   git clone https://github.com/misaka10032w/Han1meViewer.git
   ```

2. 使用 Android Studio 打开项目根目录。

3. 等待 Gradle Sync 完成。

4. 选择 `debug` 变体运行到设备或模拟器。

💻 常用命令：

```bash
./gradlew :app:compileDebugKotlin
./gradlew :app:assembleDebug
```

🪟 Windows PowerShell：

```powershell
.\gradlew.bat :app:compileDebugKotlin
.\gradlew.bat :app:assembleDebug
```

🔏 Release 构建需要本地或 CI 提供签名相关环境变量：

- `KEYSTORE_PASSWORD`
- `KEY_ALIAS`

🔑 GitHub API Token 可通过环境变量或本地文件提供：

- 环境变量：`HA_GITHUB_TOKEN`
- 本地文件：`app/ha1_github_token.txt`

## ✅ 代码约定

- UI 优先使用 Jetpack Compose，少量历史页面和组件仍保留 XML / ViewBinding / DataBinding。
- 页面状态通过 `StateFlow` 暴露，事件通过 `SharedFlow` 或回调传递。
- 网络页面列表追加时应按稳定业务键去重，避免 Compose `Lazy*` 列表 key 冲突。
- `LazyColumn`、`LazyVerticalGrid`、`LazyRow` 使用稳定 key 时应确保数据源唯一。

## 🗺️ 目录速查

```text
Han1meViewer/
├── app/                         主 Android 应用
│   ├── src/main/java/.../logic   网络、解析、模型、数据库、状态
│   ├── src/main/java/.../ui      Activity、Compose 页面、导航、组件、ViewModel
│   ├── src/main/java/.../util    通用工具
│   ├── src/main/java/.../worker  WorkManager 任务
│   └── src/main/res              资源文件、主题、布局、图标、小组件
├── yenaly_libs/                  项目内公共库
├── buildSrc/                     Gradle 构建辅助代码
├── gradle/libs.versions.toml     依赖版本目录
├── README.md                     项目说明
└── README_TECH.md                历史技术说明与部分实现笔记
```

## 🤝 贡献说明

- 提交代码前请先确认可以通过 `:app:compileDebugKotlin`。
- 修改网络列表、分页或 Compose `Lazy*` 列表时，请检查重复 key 风险。
- 修改播放、下载、账号、Cookie、Cloudflare、更新逻辑时，请尽量说明验证方式。
- 提交共享关键 H 帧可参考 `.github/PULL_REQUEST_TEMPLATE/submit_h_keyframe.md`。

## 🧩 TODO

- 随时有想法随时写。

## 📄 许可证

本项目继承原始项目的 [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0)。主要条款包括：

- 允许商用、修改和分发。
- 要求保留版权声明和许可证文件。
- 要求提供修改说明。
- 不提供质量担保。
- 不承担用户使用风险。

完整条款请参阅项目根目录下的 [LICENSE](LICENSE) 文件。

---

### Overview (`app/src/main/assets/h_keyframes/README.md`)

# 关键H帧

## 何为关键H帧？

就是影片到达「**顶点**」的那一刻。

还在因为不能和影片内「顶点」同步，而不停地记着「顶点」的时刻然后看进度条卡时间同步吗？

现在你可能不需要了。如果你开启了该功能（默认开启），并且在影片内**手动标记**每一个关键H帧，它会在 5-30s
前在屏幕**左上角**倒计时提醒（可调整）。在 0-1s 时，会精确到一位小数，方便你更好的控制时间。

此外，还提供了**共享关键H帧集**。这些关键H帧是内置于软件里的，来源于贡献者在 GitHub
上的补充。目前是随着版本更新（或 CI 实时更新）来更新共享关键H帧集，暂时不考虑服务器在线共享之类的（太危险）。

[共享关键库位置](./)

## 须知

1. 中字候补（生肉，字幕未上传）时**不要**上传，因为出字幕后可能会与未出字幕前**有一定的时间差**，导致时间不准。
2. 每一个关键H帧的`prompt`尽可能短，因为太长会影响观看。
3. 两个关键H帧必须**间隔 5s 及以上**！

## 怎么共享关键H帧？

> 注意：共享关键H帧是无法在程序内部修改的，只可读。

### Fork & Clone

先把我项目 Fork 了，然后从你项目里 Clone 到本地。

fork 仓库步骤：

1. 登录GitHub账号，进入要fork的仓库页面。
2. 点击右上角的“Fork”按钮。
3. 等待一段时间后，就可以在个人的GitHub仓库中看到已经fork过来的仓库。

clone 仓库步骤：

1. 在要clone的仓库页面上找到“clone with HTTPS”或“clone with SSH”按钮，并复制HTTPS或SSH地址。
2. 在本地打开终端（或命令行界面），进入要存放仓库的文件夹。
3. 输入命令 `git clone <复制的仓库地址>`，其中`<复制的仓库地址>`替换为刚才复制的地址。
4. 等待一段时间后，就可以在本地文件夹中看到已经克隆过来的仓库。

以上步骤完成后，就可以在本地使用这个仓库了。

### 创建文件

在该 md 文件所处的文件夹下创建一个 JSON 文件，命名为 `<影片代号>.json`。
该影片代号从哪里来呢？举个例子，你所看的番，网址是`https://hanime1.me/watch?v=37453`
，它的代号就是`v=`后面的`37453`。如果你想共享该影片的关键H帧，你需要创建名为`37453.json`的文件。

### 了解并编写 JSON 结构

以代号为`84711`的影片为例（内容我乱填的），就按这个格式写：

```json
{
  "videoCode": "84711",
  "group": "aaa",
  "title": "abc",
  "episode": 1,
  "author": "yenaly",
  "keyframes": [
    {
      "position": 1000000,
      "prompt": "abc"
    },
    {
      "position": 2000000,
      "prompt": null
    }
  ]
}
```

介绍一下每一个键值是干什么的：

> 注意-1：`author`为`null`时代表个人本地储存的，不为空才代表是共享的。所以不允许 JSON 文件里出现`null`的`author`。
>
> 注意-2：编写前务必查询当前已上传的关键H帧是否存在。假设有一人已上传 AAA 的第一集，group 名为`aaa`，title 为`aaa-01`，你也要按照他的方式进行修改，不要自行更改 group 名为`AAA`。如果你觉得你的命名更准确、更优质，也可以选择将其所有当前系列的group名和title名一起修改。

| 键        | 值类型            | 作用                                                         |
| --------- | ----------------- | ------------------------------------------------------------ |
| videoCode | string (not null) | 影片代号，用于识别影片                                       |
| group     | string (nullable) | 影片系列名称，用于影片分组，为 null 代表无系列               |
| title     | string (not null) | 影片标题，看得清晰些                                         |
| episode   | number            | 影片集数，如果无系列请填 0                                   |
| author    | string (not null) | 关键H帧作者，也就是你，**而不是**影片作者！                  |
| keyframes | array             | 关键H帧列表                                                  |
| position  | number            | 「顶点」位置，怎么获取下面说，两个 position 必须间隔 5s 及以上 |
| prompt    | string (nullable) | 「顶点」提示，最好少于五个字，或 null                        |

#### 怎么获取「顶点」位置？

首先要打开关键H帧功能，然后进入该影片全屏界面，在「顶点」处暂停，长按选单栏的`🥵`按钮，会弹出一个窗口，例如：

> ......
>
> 当前时刻：1830ms

此时，`1830`就是你所需要填写的`position`数值。同时，你也可以在此时保存到你的个人本地关键H帧中。

### Commit & Push

将代码提交到本地仓库和推送到远程仓库的步骤如下：

commit 到本地仓库：

1. 在本地文件夹中找到要修改的代码文件。
2. 对代码进行修改、添加或删除。
3. 使用命令 `git add <文件名>` 将修改后的文件添加到暂存区，也可以使用 `git add .` 将所有修改后的文件添加到暂存区。
4. 使用命令 `git commit -m "<提交信息>"` 提交暂存区的文件到本地仓库，其中 `<提交信息>` 是本次提交的说明。

push 到远程仓库：

1. 使用命令 `git push origin <分支名>` 将当前分支推送到远程仓库，其中 `<分支名>`
   是要推送的分支名称。例如，如果要将当前分支推送到远程主机的 master
   分支，可以输入 `git push origin master`。

以上步骤完成后，就可以将代码从本地仓库推送到远程仓库了。

> 注意，提交信息的格式为：`[HKeyframe] 添加/修改/删除 <影片名称>`
>
> 简繁都行，英文也行。
>
> 例如：`[HKeyframe] 添加 初恋时间系列`

### Pull Request

从我本体的 Pull Request 里提交合并请求，如果没什么大问题就合并了。

以下是Pull Request的步骤：

1. 确认分支：首先，确保你已经在本地仓库中创建了一个新的分支，并且在该分支上进行了修改。
2. 创建远程分支：将本地分支推送到远程仓库中，可以使用 `git push -u origin <分支名>`
   命令，其中 `<分支名>` 是当前分支的名称。
3. 打开 GitHub 页面：在浏览器中打开目标仓库的页面，并切换到要合并的分支。
4. 创建 Pull Request：在页面上找到 “New Pull Request” 按钮，点击后会出现一个新的页面，其中包含了你推送的分支和目标仓库的信息。
5. 填写 Pull Request 信息：在新页面中，可以填写一些关于 Pull Request
   的信息，例如标题、描述、标签等。这些信息可以帮助其他人更好地理解你提交的代码修改。
6. 提交 Pull Request：最后，点击 “Create Pull Request” 按钮提交 Pull Request。
7. 等待审核：一旦提交了 Pull Request，等我审核你的修改。如果审核通过，你的修改将合并到目标仓库中。

---

--- METRICS ---
- Files Extracted: 2
- Estimated Token Budget: ~2876 tokens
- Canonical Reference: https://codewiki.google/github.com/misaka10032w/Han1meViewer
