- 106 动画 + 2366 表情包 + 语义标签 - 三层记忆 / 心情系统 / 9 可配置插件 / 10 技能 - DeepSeek 对话直连 + MCP/HTTP 接入 + 接入文档 - 动画随机链 + 物理引擎 + 贴边碰撞校准 - 大资源(动画webp/表情图)由 scripts/ 再生成,不入库
小鲸娘 · 悬浮宠物(安卓原生 App)
一个独立的安卓「悬浮宠物」应用:把一只透明动画宠物做成 TYPE_APPLICATION_OVERLAY 悬浮窗,浮在手机所有 App 之上,并起一个本机 HTTP 控制端口(127.0.0.1:7350),让你(或 DSH 里的 Agent)用 curl 远程指挥它说话、换动作。
从 v1.0 起已演进为完整的小鲸娘:106 个动画、2366 张表情包、三层记忆、心情系统、9 个可配置插件、10 个技能、DeepSeek 对话直连、MCP/HTTP 接入、动画随机链 + 物理引擎。
定位说明:这不是对
dsh-petnpm 插件的修改——网页插件改不出原生悬浮窗。它是 dsh-pet 的原生伴生 App,复用了 dsh-pet 的控制契约(/broadcast)与动画素材,跑在安卓系统层。
1. 目录结构
dsh-pet-floating/
├── README.md
├── settings.gradle.kts
├── build.gradle.kts
├── gradle.properties
├── gradle/wrapper/gradle-wrapper.properties # wrapper jar 未包含(二进制)
└── app/
├── build.gradle.kts
└── src/main/
├── AndroidManifest.xml
├── assets/pet.html # 悬浮窗前端(气泡 + 轮询 /state)
└── java/com/dsh/petfloat/
├── MainActivity.kt # 索要悬浮窗权限 + 启动服务
├── FloatingPetService.kt # 前台服务:悬浮窗 + WebView + 拖拽
└── PetServer.kt # 本地 HTTP 控制端口(127.0.0.1:7350)
2. 为什么动画用 WebP 而不是 dsh-pet 的 webm
这是本工程最关键的设计点:
- dsh-pet 的动画是 VP9-alpha webm;
- 安卓 WebView(Chromium)对「透明 webm」支持很差,大概率渲染成黑底;
- 而 Chromium 对动态 WebP(animated WebP)+ alpha 支持良好,
<img>就能透明播放。
所以:把 dsh-pet 的 webm 转成动态 WebP,丢进 app/src/main/assets/,即可透明悬浮。默认没放素材时,App 会用一只 🐋 emoji 兜底,保证能跑。
3. 前置条件
- 一台能跑 Android Studio 的电脑;
- JDK 17;
- Android Studio(会自带 AGP / Gradle);本工程锁定 AGP 8.2.2 + Kotlin 1.9.22 + Gradle 8.2,Gradle Wrapper 的
gradle-wrapper.jar未提交,用 Android Studio 打开即可自动生成,或在本机 Gradle 下执行gradle wrapper。
4. 构建
cd dsh-pet-floating
./gradlew :app:assembleDebug # 或 assembleRelease(需要自己配签名)
产物:app/build/outputs/apk/debug/app-debug.apk。
release 未配签名,直接装会失败;先用 debug,或自行加 signingConfig。
5. 安装 & 授权悬浮窗
-
安装 APK(任选其一):
- 电脑
adb install app-debug.apk; - 或让宿主的 Agent 用 pilot 的
app.install {"upload":"<文件名>"}(先把 APK 放进/opt/pilot/run/uploads/)装。
- 电脑
-
首次启动:App 会跳到系统「显示在其他应用上层」授权页,勾选允许。
-
也可用 Agent 侧 appops 直接开(android 8+):
appops.set { package: "com.dsh.petfloat", op: "android:system_alert_window", mode: "allow" } -
授权后回到 App(或由
app.launch拉起com.dsh.petfloat),悬浮宠物即出现,可按住拖动位置;前台服务常驻,通知栏有一条「悬浮宠物运行中」,关闭该通知/强杀即可停。
6. 控制接口(本机 127.0.0.1:7350)
契约对齐 dsh-pet 的 /broadcast,Agent 用同一套 curl 就能指挥。
让宠物头顶冒气泡
curl -X POST 'http://127.0.0.1:7350/broadcast' \
-H 'Content-Type: application/json' \
-d '{"text":"搞定啦,回来看看","duration":8000}'
text:气泡正文(原样显示);空 → 收起。duration:显示毫秒,缺省 8000。
换动画
curl -X POST 'http://127.0.0.1:7350/anim' \
-H 'Content-Type: application/json' \
-d '{"name":"dance"}'
- 前端会尝试加载
/dance.webp;若assets/dance.webp不存在,保持当前形象。
收起气泡
curl -X POST 'http://127.0.0.1:7350/bubble/clear'
读取状态 / 探活
curl 'http://127.0.0.1:7350/state'
对照表
| 本 App | dsh-pet 原接口 | 说明 |
|---|---|---|
POST /broadcast |
POST /dsh-pet-7340/broadcast |
气泡接力,语义一致 |
POST /anim |
POST /dsh-pet-7340/anim |
简化为「按名换 webp」 |
POST /bubble/clear |
POST /dsh-pet-7340/bubble/clear |
语义一致 |
7. 素材转换(VP9-alpha webm → animated WebP)
在一台有 ffmpeg(含 libwebp 编码器)的机器上:
ffmpeg -i 输入.webm -c:v libwebp -q:v 80 -lossless 0 -loop 0 -pix_fmt yuva420p -an 输出.webp
- 默认形象:转一个待机动画,命名
pet.webp,放app/src/main/assets/pet.webp; - 点播动画:每个动作命名
<name>.webp,用/anim {"name":"<name>"}切换; - dsh-pet 素材源在
dsh-pet/assets/webm/(106 个、约 53MB、含中文文件名;放进 assets 的 webp 文件名建议用[a-zA-Z0-9._-],本服务的静态路由只放行该字符集)。
验证 alpha:
ffprobe -v error -show_entries stream=pix_fmt 输出.webp应看到yuva420p。
8. Agent 如何对接(在宿主机上)
App 的控制端口绑定 127.0.0.1:7350,与 dsh-pet 一样走本机 HTTP,因此 Agent 直接:
curl -X POST 'http://127.0.0.1:7350/broadcast' -H 'Content-Type: application/json' \
-d '{"text":"…","duration":8000}'
即可让手机上的悬浮宠物说话;/anim、/bubble/clear 同理。
9. 已知限制 / 风险
- 本机没有安卓构建链(无 JDK/Gradle/SDK),本工程源码在此环境下未实际编译验证,需在 Android Studio 里首次构建时排错;
- 透明动画依赖「webm→动画 WebP」转码,未在此环境验证最终观感;
- 悬浮窗在系统设置、部分锁屏/支付界面可能被系统临时隐藏(安卓安全策略);
- 控制端口只绑回环地址、无鉴权,仅限本机可信环境使用,别开放公网。
10. 素材再生成(新机器还原大体积资源)
动画与表情包体积大(动画两套 537MB、表情包 114MB),不纳入 git。 新机器上按下面顺序重建即可(均不依赖本仓库之外的私有数据):
# 1) 动画 webp(assets_hq / assets_small,106 个,来自 dsh-pet 的 webm 转码)
DSH_PET=/path/to/dsh-pet ./scripts/regenerate_assets.sh
# 2) 表情包图(assets_net,约 2366 张,来自梗鲸档案馆 aigengtu.com)
python3 scripts/download_memes.py
scripts/anim_index.json:106 个动画的「中文名 → a000N → 时长」映射(已随仓库提交)scripts/meme_meta.json:档案馆 2436 条「编号 → 台词(含情绪描述)」元数据(已提交)scripts/gen_config.py:从 dsh-pet 的config.jsonc重建 config.json 的动画部分(保留 memes)app/src/main/assets_common/(config.json、字体、pet.html、m01–m51 精选表情)随仓库提交,是可直接构建的最小集
无 dsh-pet 源或 ffmpeg 时,App 仍可用 🐋 emoji 兜底启动,只是没有正式动画。
11. 对外接入(速览)
详细文档见 INTEGRATION.md 与各端口 /docs / /docs.md / /llms.txt。
| 能力 | 入口 |
|---|---|
| HTTP 控制 | POST /api/task /api/notify /api/anim /api/meme /api/skill 等(127.0.0.1:7350,写请求需 X-Pet-Token) |
| SSE 互动事件 | GET /api/events?token=… |
| MCP Server | POST /mcp(JSON-RPC,7 个工具) |
| 能力目录 | GET /api/catalog(106 动作 + 2366 表情 + 语义标签) |
| 插件 | GET/POST /api/plugin、GET /api/plugins.js(JS,WebView 内跑) |
10. 素材再生成(新机器还原)
三个大体积资源目录不纳入 git(合计约 650MB,多为可再生/可下载的二进制), 在新机器上按下面顺序重建:
# ① 动画 webp(assets_hq 640×360 / assets_small 320×180,共 106 个)
# 从 dsh-pet 的 webm 源转码,映射表在 scripts/anim_index.json
DSH_PET=/path/to/dsh-pet ./scripts/regenerate_assets.sh
# ② 表情包图(assets_net,~2300 张)
# 从「梗鲸档案馆」(aigengtu.com) 下载并转码,元数据在 scripts/meme_meta.json
python3 scripts/download_memes.py
# ③(可选)动画配置 config.json 若需从 dsh-pet 重建,运行后保留 memes 列表
DSH_PET=/path/to/dsh-pet python3 scripts/gen_config.py
依赖:ffmpeg(libvpx-vp9 解码 + libwebp_anim 编码)、requests、Pillow。
config.json、candy.ttf、pet.html、内置表情包(m01–m51)在 assets_common/,已随仓库提交。
仓库只存「源码 + 脚本 + 源数据 + 小体积源资产」,构建产物(APK)与可再生资源(hq/small/net) 始终留在本机,不被 git 追踪——这是为了让仓库能稳定推送、克隆,而不是塞进 650MB 二进制。