作品介绍
更新于 2026-10-31它是什么
余音(Yuyin)是一款把多个网盘变成音乐库的 Windows 桌面播放器:右侧内嵌 115、百度网盘、天翼云盘、移动云盘的官方网页完成登录,左侧用清爽的播放器界面浏览、搜索、播放自己网盘里的音频文件;同时提供本地歌单、离线缓存、飞鱼会员云备份与在线更新。余音不绝,绕梁三日——自己网盘里的歌,随时接着听。内容边界很清晰:只播放当前账号自己网盘中的文件,不破解限速、不接入第三方音源。
核心功能
- 支持 115、百度网盘、天翼云盘、移动云盘,切换网盘后目录、我听、最近播放按账号各自独立
- 登录复用官方网页(扫码或账号登录),本地不保存账号密码
- 双击即可播放,支持进度拖动、顺序 / 单曲 / 列表循环与随机播放
- mp3、flac、wav、m4a 由系统解码;ape、ogg、opus、wma、dsf 等由内置 mpv 兜底,解码失败自动切换
- 常听的歌可以缓存到本地,断网也能播放;默认不自动缓存,按需手动缓存
- 自定义歌单、播放历史、快捷键设置,支持托盘常驻与全局媒体按键
- 侧栏常驻会员状态:登录后显示账号与等级(VIP 金色标出到期日),折叠后保留头像
- 飞鱼会员账号登录后,可把歌单库与网盘登录状态打包备份到云端,换机或重装一键恢复
数据与升级
- 歌单、登录信息、播放记录与缓存都保存在安装目录的 Data\ 与 Cache\ 中,整个目录拷走即可迁移
- 从旧版 FyMusic 升级时,原 Data\ 中的歌单与登录状态会在首次启动时自动迁移到余音
- 升级安装与自动更新只替换程序文件,Data\ 与 Cache\ 中的数据保留;卸载时默认保留数据
- 启动后自动检查新版本,也可以在「帮助 → 检查更新」里手动触发;有新版本时弹窗展示本次更新说明
- 更新下载窗口显示更新内容与进度,支持暂停 / 继续 / 停止,下载完成后自动启动安装程序
系统要求
Windows 10 / 11 64 位;首次使用需要在右侧网页登录自己的网盘账号。
界面预览
技术参数
余音(Yuyin)设计与实施规划
余音不绝,绕梁三日。
目标:在 Windows 上用 Python + PyQt6 做一个"多网盘音乐播放器"。
以 Chromium 内核(QtWebEngine)承载 115网盘 / 百度网盘 / 天翼云盘 / 移动云盘的网页,
作为登录与文件操作入口;另加一套独立的播放器操作界面,登录后直接浏览、搜索、播放网盘里的音频。
在此之上提供 115「我听」听单、本地歌单、离线缓存、飞鱼会员云备份,以及基于作品页的在线更新。关键决策已确认(见第 11 节)。本文档既作为设计说明,也作为实施与验收依据。
1. 项目定位
| 项目 | 内容 |
|---|---|
| 名称 | 余音(Yuyin) |
| 宣传语 | 余音不绝,绕梁三日 |
| 运行平台 | Windows 10/11 x64(先只做 Windows,跨平台留后) |
| 运行方式 | 本地 Python 进程 + 内嵌 Chromium 视图,非 Electron、非浏览器插件 |
| 网盘来源 | 115网盘、百度网盘、天翼云盘、移动云盘;115 额外支持「我听」听单 |
| 核心体验 | 左边"我的播放器",右边所选网盘的网页;网页里登录一次,播放器里即可浏览目录、搜索、播放该账号网盘中的音频,并可建本地歌单 |
| 附加能力 | 离线缓存与缓存管理、飞鱼会员云备份(歌单库 + 网盘登录态)、作品页在线更新(含更新说明、暂停/继续/停止) |
| 内容边界 | 只播放当前账号自己网盘里的音频文件;不破解 VIP、不绕过限速、不接入第三方音乐源 |
1.1 要解决的问题
- 各家网盘的网页版都没有面向"听歌"的顺手界面,切歌、调进度、看列表都很别扭。
- 网盘直链有鉴权、有时效,普通播放器无法直接使用。
- 希望登录流程完全复用网盘官方网页,不在本地保存账号密码。
- 115「我听」里已有的听单和音频元数据是现成的音乐组织方式,希望直接出现在播放器里,而不是只能在网页里点播。
- 网络不稳或想离线时,常听的歌能缓存到本地;换机或重装后,歌单与登录状态希望能一键恢复。
1.2 非目标(本期不做)
- 不做多账号切换、云盘文件上传/删除/重命名等管理功能。
- 不做在线音乐搜索、MV、直播;不做"另存/导出到用户指定目录"的下载功能(只做应用内离线缓存)。
- 不做移动端、Web 端。
- 不修改 115 网页本身的 DOM 结构(只做最小化的只读注入,见 5.9,且为可选项)。
2. 关键技术判断
2.1 "115 浏览器为内核"怎么落地
115 浏览器本身基于 Chromium(官网描述为"全新 Chromium 高速内核引擎")。因此有两条路线:
| 方案 | 做法 | 优点 | 风险 | 结论 |
|---|---|---|---|---|
| A. QtWebEngine 内嵌(推荐) | 用 PyQt6-WebEngine 的 QWebEngineView 加载 115.com,使用持久化 Profile 保存登录 Cookie,再从 Cookie 仓取会话给 Python 侧调用 WebAPI |
一体化窗口、可控、无需安装 115 浏览器、Cookie 可取 | Chromium 版本与 115 官网兼容性需实测;无官方"115 内核" | 主方案(已确认) |
| B. 接管本机 115 浏览器(CDP) | 用 --remote-debugging-port 启动 115 浏览器,通过 CDP 接管标签页并导出 Cookie |
与官方浏览器行为完全一致,风控风险最低 | 依赖用户已安装 115 浏览器、进程管理复杂、Cookie 导出受限 | 仅作兜底 |
设计上把"浏览器内核"抽象成一个接口(BrowserKernel),A/B 两种实现可替换,避免后续返工。
2.2 播放链路判断
QMediaPlayer 只能接受一个 URL,而 115 的播放地址需要 Cookie/UA 且几分钟就过期。
所以采用 本地代理 + 直链 的方式:
QMediaPlayer → http://127.0.0.1:17890/stream/<token> → 本地代理(补 Cookie/UA、透传 Range) → 115 CDN 直链
好处:播放器拿到的是稳定 URL,可拖动进度、可预加载下一首;Cookie 不出现在播放器里;
直链过期时由代理自动换新地址。
3. 技术栈
| 层 | 选型 | 说明 |
|---|---|---|
| 语言 | Python 3.11.9 | 已建虚拟环境 .venv |
| GUI | PyQt6 6.11.0(Qt 6.11.0) | 主窗口、控件、主题 |
| 浏览器内核 | PyQt6-WebEngine 6.11.0,Chromium 140.0.7339.225(已实测) | 登录 115、浏览网盘 |
| 网络 | requests | 调 115 WebAPI、代理转发 |
| 本地代理 | 标准库 http.server + ThreadingHTTPServer(或 socketserver) |
零额外依赖,够用 |
| 音频播放(主) | QtMultimedia 的 QMediaPlayer + QAudioOutput |
系统解码器(WMF),覆盖 mp3/flac/wav/m4a |
| 音频播放(兜底) | 外部 mpv.exe + 命名管道 JSON IPC |
提前落地;覆盖 ape/ogg/opus/wma/dsf 等;放 third_party/mpv/ |
| 离线缓存 | 文件系统 + SQLite 索引 | Range 断点续传、LRU 上限、优先本地播放 |
| 标签/封面 | mutagen | 读取 ID3/FLAC 标签、内嵌封面、内嵌歌词 |
| 本地存储 | SQLite(标准库 sqlite3) |
目录/我听缓存、歌单、播放历史、缓存索引 |
| 打包(后期) | PyInstaller + mpv.exe | M6 阶段再处理 |
已执行的环境准备(已安装完成并验证):
python -m venv .venv
.\.venv\Scripts\python.exe -m pip install -i https://mirrors.ustc.edu.cn/pypi/web/simple PyQt6 PyQt6-WebEngine requests mutagen
实测版本:PyQt6 6.11.0 / Qt 6.11.0 / PyQt6-WebEngine 6.11.0(Chromium 140.0.7339.225)/
requests 2.34.2 / mutagen 1.48.1。
网络备注:清华与阿里云镜像在本机下载 Qt 运行时时速度偏低且易中断,改用中科大镜像后正常,后续装包默认用 USTC。
依赖清单后续落到 requirements.txt(M0 阶段)。
4. 总体架构
┌────────────────────────────────────────────────────────────────────────┐
│ 余音 主窗口(QQ 音乐风格) │
│ ┌──────────┐ ┌─────────────────────────────────┐ ┌──────────────────┐ │
│ │ 侧边导航 │ │ 内容区 │ │ 播放队列抽屉 │ │
│ │ 发现音乐 │ │ · 歌单卡片 / 歌曲列表 │ │ (可收起) │ │
│ │ 我的音乐 │ │ · 我听听单 / 缓存管理 │ │ │ │
│ │ 我听 │ │ │ │ │ │
│ │ 本地缓存 │ │ │ │ │ │
│ └──────────┘ └─────────────────────────────────┘ └──────────────────┘ │
│ ┌────────────────────────────────────────────────────────────────────┐ │
│ │ 底部播放条:封面 / 标题 / 进度 / 控制 / 音量 / 模式 / 队列 │ │
│ └────────────────────────────────────────────────────────────────────┘ │
│ 右侧可展开:QWebEngineView(115 网页:登录 / 我听 / 文件管理) │
└───────────┬──────────────────┬───────────────────┬────────────────────┘
│ │ │
▼ ▼ ▼
┌────────────────┐ ┌────────────────┐ ┌──────────────────┐
│ PlayerService │ │ SessionService │ │ LibraryService │
│ 队列 / 状态机 │ │ Cookie 桥 │ │ 目录 / 我听索引 │
└───┬────────┬───┘ └───────┬────────┘ └────────┬─────────┘
│ │ │ │
▼ ▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌───────────────┐ ┌──────────────┐
│QtBackend │ │MpvBackend│ │Cloud115Client │ │ CacheService │
│QMediaPlayer││mpv + IPC │ │files / 我听 │ │ 离线文件+LRU │
└────┬─────┘ └────┬─────┘ └───────┬───────┘ └──────┬───────┘
│ │ │ │
└────────────┴──────► LocalStreamProxy ◄──────┘
127.0.0.1:17890
│
▼
115 CDN 直链
分层原则:
ui/只处理界面与交互,不写 115 协议。core/负责 115 会话、API、代理、我听、缓存、本地库,全部同步逻辑,独立可测。player/用PlaybackBackend统一 Qt 与 mpv 两种后端,UI 只订阅状态信号。- 网络/播放/下载类阻塞操作放到
QThreadPool/工作线程,主线程不阻塞。
5. 关键机制设计
5.1 登录态(SessionService)
- 使用独立持久化 Profile(
QWebEngineProfile("yuyin")),
setPersistentCookiesPolicy(ForcePersistentCookies),登录 Cookie 由 Chromium 自己保存与携带,
关掉程序再开仍是登录状态。 - 不导出 Cookie:实测 115 的
webapi.115.com有 WAF,requests 直连返回 405 拦截页,
因此所有 WebAPI 调用改为页面内fetch(见 5.2),HttpOnly Cookie 由浏览器自动附带。 - 登录判定:页面加载完成后调用
/files?aid=1&cid=0&limit=1,
返回state:true即已登录;/user/info可拿到脱敏账号。 - 触发时机:页面
loadFinished+ 关键 Cookie(UID/CID/SEID/KID)变化(700ms 防抖);
失败自动重试 3 次(2s/4s/6s),避免一次网络抖动就误判。 - 不保存账号密码;Cookie 由 Qt 的加密存储管理。
- UI:侧栏与顶栏显示「未登录/已登录」状态,115 面板右上角显示详细提示。
5.2 请求通道(PageApiBridge)
- 所有 115 WebAPI 通过
QWebEnginePage.runJavaScript在页面内fetch,
结果写入window.__fyResults后由 Python 轮询取回(Qt 的runJavaScript不等待 Promise)。 - 优势:自动携带 HttpOnly Cookie、复用 Chromium 的 DNS/代理/指纹,绕过 WAF。
- 统一使用
credentials: "include"、cache: "no-store",15 秒超时。 - UA 仅用于内嵌网页(
profile.setHttpUserAgent);M3 走 CDN 下载时沿用同一 UA。 - 代价:接口调用依赖页面存活;页面异常时提示「请刷新 115 页面」。
5.3 音乐库(Cloud115Client + LibraryService)
- 目录列表:
GET https://webapi.115.com/files,常用参数
aid=1、cid=<目录ID>、offset、limit(分页)、show_dir=1、o=user_ptime、asc=0、format=json。 - 文件搜索:
GET https://webapi.115.com/files/search(参数以抓包校准为准),作为"全盘搜索"能力。 - 字段映射(以实际返回为准,M1 阶段抓包核对):
| 115 字段 | 含义 | 本地用途 |
|---|---|---|
cid |
目录 ID | 目录树节点 |
fid |
文件 ID | 主键之一 |
pc |
pickcode | 取直链的关键 |
n / name |
名称 | 展示、匹配歌词 |
s / size |
大小 | 展示 |
sha |
SHA1 | 可选去重 |
ptime |
修改时间 | 排序 |
- 音频识别:不依赖 115 的类型筛选参数(
type取值需实测),先取全量再用扩展名白名单过滤:
mp3, flac, wav, m4a, aac, ogg, opus, wma, ape, dsf, dff, aiff。 - 缓存策略:SQLite 保存
folders/tracks,进入目录时先渲染缓存再后台刷新;
目录懒加载(展开时才拉子目录)。 - 音量归一化、封面、时长:时长从
QMediaPlayer.duration或 mutagen 读,写入缓存。
5.4 「我听」听单(ListenService)
115「我听」入口为 https://115.com/?ct=music(网盘左侧「我听」直接进入)。
接口已在 M2 通过内嵌页面的网络请求实测确认(见附录 A):
- 听单列表:
GET /files/musicnew?topic_id=1&type=1&user_id=<uid>&start=0&limit=20
→data.list[],字段topic_id、topic_name(可能为空串)、music_count、pic、topic_sort;
响应里的curr_user.user_id会回写本地,供下次请求使用。
返回空列表时用/files/music_topic_listnew?fond=1&start=0&limit=20兜底。 - 听单曲目:
GET /files/music_list?topic_id=<id>&start=0&limit=1150
→data[],字段id、file_name、pick_code、file_size、play_long(秒)、url(内置播放地址)。 - 本地映射:转换成统一曲目模型(
pickcode/name/size/duration),
写入listen_sheets、listen_items表,先渲染缓存再后台刷新。 - 播放:我听条目本质仍是网盘音频文件,统一走 5.5 取直链 + 5.6 代理;
条目自带的url(/files/music?pickcode=…&topic_id=…&music_id=…)作为 M3 的备选播放通道。 - 进度续播:接口若提供跨端进度则读写;否则用本地
resume表按条目 ID 记录。 - 降级:接口不可用时,在页面内执行只读 JS 读取渲染后的数据(或
window上的初始状态对象),
通过QWebChannel回传;不改页面逻辑,站点改版时可直接关闭。
5.5 取播放地址(PlayUrlResolver)
- 接口:页面内
fetch调GET https://webapi.115.com/files/download?pickcode=<pc>
→ 返回 JSON 中的file_url(CDN 直链,带签名、短时效)。 - 缓存:
pickcode → (url, expire_at),TTL 取 5 分钟;播放中收到 403/404 立即失效并重取一次。 - 备选:115 开放平台的视频/音频播放接口(
/files/play、/open/play一类,需 VIP 能力时再评估)。 - 已缓存到本地的曲目不再取直链,直接给播放器本地文件路径。
- CDN 直链已实测:
/files/download返回file_url/file_size/sha1;requests 带 Cookie + Range
可直接拿到206(M3 已验证)。 - 关键点:CDN 鉴权依赖
/files/download响应下发的 path 限定 token Cookie
(形如<32位hex>=<md5>,Path=/<存储路径hash>/),必须按路径匹配后随请求带上。 - 端点与参数在 M1 阶段已确认
/files、/user/info可用。
5.6 本地流媒体代理(LocalStreamProxy)
- 仅监听
127.0.0.1:17890,路由:GET /stream/<token>,token 为pickcode的不可预测映射(secrets.token_urlsafe)。 - 行为:
- 透传客户端
Range,向 115 CDN 发起带 Cookie/UA 的请求; - 返回
206 Partial Content+Content-Range/Content-Length/Content-Type/Accept-Ranges: bytes; - 64KB 分块转发,不整文件进内存;
- 支持连接中断清理、单曲最多 2 个并发连接(防拖拽风暴)。
- 与缓存共用同一套"取直链 + 带鉴权请求"代码,避免两处维护。
- Cookie 来源:Chromium 运行时会独占锁定
profile/Cookies,磁盘读不到;改为监听
QWebEngineCookieStore.cookieAdded在内存维护 Cookie 罐(含 HttpOnly 与 path 限定项),
代理按请求 URL 的 host + path 匹配出 Cookie 头(M3 已验证)。 - 上游返回 403 时等待 0.6s 重取 Cookie 再试一次(覆盖 token 下发与播放请求的竞态)。
- 客户端主动断开(含 mpv 探测/拖动产生的短连接)按正常现象处理,只记 debug 日志,不抛异常。
- 安全:只接受本机回环地址;进程退出即关闭端口。
5.7 离线缓存(CacheService)
- 目标:已缓存曲目秒开、断网可听;只做应用内缓存,不做"导出/另存"。
- 目录:
<程序目录>\Cache\<pickcode>.<ext>(默认位置,可在「本地缓存」页修改);下载中写.part,完成后原子重命名。 - 下载:独立工作线程 + 队列(并发 2),复用 Range 逻辑,支持断点续传、失败重试 3 次。
- 触发:
- 手动:播放条上的"缓存"按钮;
- 自动(默认关闭,可在设置里打开):播放时后台缓存当前曲目,下次播放直接命中本地文件。
- 索引:
cache_files表记录 pickcode、路径、大小、状态、最近使用时间。 - 淘汰:总上限默认 10GB(可配),LRU 清理;正在播放/下载的条目不清理。
- 校验:完成后比对文件大小;有
sha1时做完整性校验(大文件可延后到空闲时)。 - UI:列表显示"已缓存/缓存中"标记;侧栏"本地缓存"页展示占用、上限、一键清理。
- 实现(M5):
app/core/cache.py用threading工作线程并发 2 下载,进度/完成事件经
task_changed(pickcode)/cache_changed回主线程,SQLite 只在主线程读写;
_done集合供曲目表/播放条做 O(1) 状态查询;播放命中缓存时 Qt 后端把本地路径转成
file://URL 再交给 QMediaPlayer。自动缓存开关与上限(1–500GB)写meta表。 - 实测(M5):
849.好心分手 - 卢巧音.mp3(7.2MB)缓存到cache/audio/<pickcode>.mp3,
再次播放直接命中本地文件(backend=qt、无网络请求);.part断点续传与 LRU 淘汰随之生效。
5.8 播放器与双后端(PlayerService)
- 统一接口
PlaybackBackend:load / play / pause / stop / seek / set_volume,
以及position_changed / duration_changed / state_changed / finished / error信号;UI 只依赖接口。 QtBackend:QMediaPlayer+QAudioOutput(Qt 6.11 自带 FFmpeg 后端),默认后端,启动快、系统解码。MpvBackend(M4 已落地):每个实例启动独立外部进程
mpv.exe --idle=yes --no-video --audio-display=no --no-terminal --really-quiet --input-ipc-server=\\.\pipe\yuyin-mpv-<随机后缀> --volume=<n>;
用QLocalSocket连接(不带\\.\pipe\前缀,80ms × 最多 40 次重试),
连上后observe_property订阅time-pos / duration / pause,通过loadfile ... replace
起播、set_property pause暂停、seek ... absolute拖动;file-loaded/end-file
(eof→finished、error→error)映射成统一信号。进程以CREATE_NO_WINDOW静默启动,
退出时先发quit再terminate/kill,不留后台进程。- 选择策略:
ape/ogg/opus/wma/dsf/dff/aiff直接走 mpv(MPV_EXTS);
Qt 解码失败且未切换过后端时,自动切 mpv 并把当前进度作为_pending_seek续上,
同时发出state=fallback供播放条提示;mpv 也失败才换链重试一次,仍失败则提示。 - 状态机:
Idle → Resolving → Loading → Playing ⇄ Paused → Ended/Error,
另有fallback(Qt→mpv)与reconnecting(换链重试)两个提示态。 - 队列:顺序 / 单曲循环 / 列表循环 / 随机。
- 失败重试:解析失败重试 1 次;播放中途直链失效换链并恢复进度 1 次,再失败则提示。
- 实测(M4):mpv v0.41.0-1227-ge471496c5(shinchiro 20261021 构建,仅保留
mpv.exe);
云端袁咏琳;周杰伦 - 画沙.ape(29MB / 04:33)经本地代理直接播放,
backend=mpv、duration=273666、pos正常前进;Qt 遇损坏文件自动切 mpv 并发出fallback。 - 分发注意:mpv 为 GPLv2+,随包分发需附许可证与源码获取方式;MVP 阶段支持用户自行放置
mpv.exe。
5.9 浏览器区增强(可选,M6)
- 在 115 网页里注入一个悬浮小按钮("加入播放列表"),通过
QWebChannel与 Python 通信;
仅监听点击,不改动 115 原有 DOM 逻辑,站点改版时可直接关闭该功能。 - 页面
title/urlChanged变化时同步地址栏与登录状态。
5.10 数据模型(SQLite,<程序目录>\Data\yuyin.db)
-- M2 已落地(app/core/store.py)
folders(cid TEXT PRIMARY KEY, parent_cid TEXT, name TEXT, updated_at INTEGER);
tracks(pickcode TEXT PRIMARY KEY, cid TEXT, fid TEXT, name TEXT, size INTEGER,
mtime INTEGER, path TEXT, updated_at INTEGER);
listen_sheets(topic_id TEXT PRIMARY KEY, name TEXT, pic TEXT, music_count INTEGER,
sort INTEGER, updated_at INTEGER);
listen_items(item_id TEXT PRIMARY KEY, topic_id TEXT, name TEXT, pickcode TEXT,
size INTEGER, duration INTEGER, updated_at INTEGER);
meta(key TEXT PRIMARY KEY, value TEXT); -- user_id、设置、上次选中的目录、音量等
-- M5 已落地(app/core/store.py)
cache_files(pickcode TEXT PRIMARY KEY, path TEXT, size INTEGER, state TEXT,
created_at INTEGER, last_used_at INTEGER);
-- M6 预留
playlists(id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, created_at INTEGER);
playlist_items(playlist_id INTEGER, pickcode TEXT, sort INTEGER, PRIMARY KEY(playlist_id, pickcode));
history(pickcode TEXT PRIMARY KEY, played_at INTEGER, play_count INTEGER);
resume(pickcode TEXT PRIMARY KEY, position_ms INTEGER, updated_at INTEGER);
5.11 顶部菜单与快捷键
- 菜单栏:文件(搜索歌曲、显示/隐藏 115 面板、清空本地缓存、退出)、播放(播放/暂停、
上一首、下一首、音量 ±)、设置(快捷键设置…、播放时自动缓存、飞鱼会员 / 云备份…)、
帮助(会员空间、检查更新、官网、关于)。 - 「官网」指向作品页
https://feiyu.ltd/zuopin/yuyin/,「会员空间」打开https://feiyu.ltd/member/。 - 默认快捷键对齐 QQ 音乐 PC 版:播放/暂停
Ctrl+Alt+F5、上一首Ctrl+Alt+Left、
下一首Ctrl+Alt+Right、音量 ±Ctrl+Alt+Up/Down;另保留F9显示/隐藏 115、
Ctrl+F搜索、Ctrl+Q退出。 - 设置 → 快捷键设置… 逐项录制组合键,保存前检查重复;绑定写
meta.shortcuts(JSON),
清空某项即禁用;"恢复默认"回填 QQ 音乐默认值。
5.12 单曲详情(点击曲目)
- 单击曲目表任意一首歌弹出非模态详情面板:歌名、所在位置、时长、大小、格式、缓存状态,
并提供"播放 / 缓存到本地(已缓存时置灰)/ 关闭"。 - 单击信号延迟到双击判定之后再发出,双击仍只播放、不弹详情;缓存状态随下载进度实时刷新。
5.13 飞鱼会员与云备份(MemberCloudService)
- 账号即 feiyu.ltd 站点账号:
POST /api/member/login/换取 Token,存meta.feiyu_token;
Token 失效时自动回落到登录页。 - 对话框一窗两态:未登录显示账号密码 + 注册入口(
/member/register/);登录后显示作品下拉、
备份说明与云端备份列表,可上传当前配置、下载到本地、恢复、删除,并附「会员空间」「退出登录」。 - 备份包内容:
yuyin.db(走 SQLite 在线备份接口写快照,保证一致性)+ 浏览器 Profile
(跳过缓存,约 2 MB),打包 zip 上传;超过 20 MB 直接提示,不发起上传。
打包时遇到浏览器独占的 Profile 文件(如 Cookies)自动改走 SQLite 快照或共享读取,
仍失败的跳过并在对话框中提示,不中断整个备份。 - 恢复采用「先退出、下次启动覆盖」:点「恢复到本机」→ 下载 → 暂存 → 应用退出 → 下次启动时在
数据库与 Profile 打开之前应用,避免运行中写坏 SQLite / Profile;解压时拒绝绝对路径与..
等非法条目。 - 入口:设置 → 飞鱼会员 / 云备份…;帮助 → 会员空间。
- 侧栏底部常驻会员状态块:登录后显示账号首字母头像、账号名与等级(VIP 为金色,并在副行显示到期日);
未登录显示灰色「?」与「点击登录飞鱼会员」,点击直接打开会员对话框。侧栏折叠后只保留头像,
仍能一眼看出登录状态,悬停可看完整等级与到期信息。
5.14 在线更新(作品页单一来源)
- 版本信息只认作品页
https://feiyu.ltd/zuopin/yuyin/:从版本徽标取版本号、从「下载安装包」
按钮取安装包地址、从「更新历史」取该版本的更新说明。 - 检查更新:拉取作品页并与本地
APP_VERSION比较,有新版本时弹窗展示更新说明;
「跳过此版本」写meta.update_skip,设置里手动检查时忽略跳过。 - 更新下载窗口:640×540,展示本次更新内容、下载进度与状态,右下提供「暂停 / 继续」「停止」
「关闭」;停止会中断传输并删除未完成的安装包,下载中直接关窗等同于点「停止」。 - 下载完成后应用立即退出并启动安装包静默安装;安装时会自动关闭仍在运行的程序实例,
避免文件占用导致安装失败。 - 版本号固定
0.1.2(开发期不随每次改动递增),发布方式是用同版本号重新打包并覆盖
Yuyin-Setup-0.1.2.exe。
6. 代码结构(M0 落地)
Yuyin/
├── .venv/ # 虚拟环境(已创建,依赖已装)
├── README.md # 本文档
├── requirements.txt
├── Yuyin.py # 启动入口(自动切到 venv 运行 app.main)
├── main.ico # 应用图标(多分辨率,窗口/任务栏/托盘统一使用)
├── third_party/mpv/ # M4:mpv.exe + 许可证
└── app/
├── main.py # 入口:QApplication、主题、MainWindow
├── config.py # 路径、UA、扩展名白名单、常量
├── core/
│ ├── browser_kernel.py # 持久化 Profile + QWebEngineView
│ ├── page_api.py # PageApiBridge:页面内 fetch + 轮询回传
│ ├── session.py # SessionService:登录校验与状态信号
│ ├── cloud115.py # Cloud115Client:目录/搜索/我听接口
│ ├── library.py # LibraryService:目录分页、音频过滤、搜索
│ ├── listen.py # ListenService:我听听单与曲目
│ ├── cookies.py # CookieStore:内存 Cookie 罐(含 path 匹配)
│ ├── resolver.py # PlayUrlResolver:pickcode → CDN 直链(TTL 缓存)
│ ├── proxy.py # LocalStreamProxy:127.0.0.1 流代理
│ ├── cache.py # CacheService:离线下载与 LRU(M5)
│ ├── member_cloud.py # MemberCloudService:会员登录、作品列表、云备份上传/恢复
│ ├── updater.py # UpdateService:作品页版本解析、断点式下载(暂停/继续/停止)
│ └── store.py # SQLite 访问层(已落地)
├── player/
│ ├── backend.py # PlaybackBackend 接口 + QtBackend
│ ├── mpv_backend.py # MpvBackend(JSON IPC)
│ ├── engine.py # PlayerService:队列/状态机/后端切换
│ └── tagreader.py # mutagen 标签/封面/歌词
└── ui/
├── main_window.py # 整体布局:侧栏 + 内容 + 播放条
├── sidebar.py # 左侧导航
├── content_area.py # 顶栏 + 页面栈 + 搜索入口
├── library_panel.py # 目录树 + 歌曲表(已落地)
├── listen_panel.py # 我听听单 + 曲目表(已落地)
├── track_table.py # 通用曲目表格
├── cache_page.py # 缓存管理页(已落地)
├── shortcuts.py # 快捷键管理 + 设置对话框
├── track_detail.py # 单曲详情面板
├── member_dialog.py # 飞鱼会员 / 云备份对话框(登录、上传、恢复)
├── update_dialog.py # 更新下载窗口(更新内容 + 暂停/继续/停止)
├── browser_panel.py # 地址栏 + QWebEngineView
├── player_bar.py # 底部播放条
├── queue_drawer.py # 播放队列抽屉(M6)
└── style.qss # QQ 音乐风格浅色主题
7. 界面方案
- 布局参考 QQ 音乐桌面端:左侧导航 + 中间内容 + 底部播放条 + 右侧可收起的播放队列;
115 网页作为右侧抽屉/分栏滑出(F9 折叠),不打断播放。 - 主窗口 1280×820,最小 1024×680;侧栏 208px,播放条 84px。
- 配色:主色
#31C27C(QQ 音乐绿),背景#F6F7F9,卡片白底 + 8px 圆角,
主文字#222222、次文字#8A8F99,分割线#EDEFF2;默认浅色,深色主题放 M6。 - 侧栏:Logo + 发现音乐 / 我的音乐 / 我听 / 本地缓存 / 设置;选中项浅绿底 + 绿字 + 左侧指示条。
- 内容区:顶栏(页面标题、搜索框、登录状态、打开 115 按钮)+ 歌单卡片网格 + 歌曲列表。
- 播放条:左(封面 56×56、标题、歌手、缓存标记)、中(上一首 / 播放 / 下一首、进度、时间)、
右(播放模式、音量、队列)。 - 列表交互:悬停高亮、双击播放、右键菜单(播放 / 下一首播放 / 缓存 / 查看所在目录)。
- 空状态文案明确下一步动作(未登录 → "登录 115";无缓存 → "去音乐库挑几首")。
- 图标用内联 SVG/矢量绘制,不复制 QQ 音乐的图标、字体、图片素材,只借鉴布局与交互习惯。
- 快捷键:
Space、←/→、Ctrl+←/→、Ctrl+F、F9(网页抽屉)、Ctrl+Q。
8. 风险与对策
| 风险 | 影响 | 对策 |
|---|---|---|
| 115 WebAPI 改版或风控 | 列表/直链失败 | 端点与参数集中在一个文件;失败时给出可读提示;保留 CDP 方案 B |
| 115 WAF 拦截 Python 直连(实测 405) | requests 调 API 全挂 | 统一走页面内 fetch(5.2),Python 只负责编排与播放 |
| Cookie 为 HttpOnly、取不到 | 无法调 API | 走 CookieStore 原生接口,不依赖 JS |
| 直链短时效 / 绑定 UA、IP | 播到一半中断 | 本地代理统一补头;403 时自动换链并恢复进度 |
| QtWebEngine Chromium 版本与 115 页面不兼容 | 无法登录/白屏 | 用最新 PyQt6;必要时切方案 B;提供"外部浏览器登录后导入 Cookie"的降级入口(可选) |
| 「我听」无公开文档 | 听单读不到 | 先抓包确认;接口不可用时页面 JS 只读兜底;最后退化为仅网盘目录 |
| 版权与合规 | 账号风险 | 只播自有文件、正常接口低频访问,不做导出下载与破解 |
| 音频格式不全(ape/ogg/opus) | 部分歌不能播 | M4 双后端,mpv 兜底;列表标注解码后端 |
| mpv 分发许可(GPLv2+) | 合规风险 | 附许可证与源码获取方式;MVP 允许用户自备 mpv.exe |
| 离线缓存占满磁盘 | 磁盘告警 | 默认 10GB 上限 + LRU + 缓存管理页;下载前检查剩余空间 |
| 界面模仿的版权边界 | 视觉争议 | 只借鉴布局与交互,不复制 QQ 音乐图标、字体、图片素材 |
| 大目录卡顿 | 体验差 | 分页 100~500/页、懒加载、SQLite 索引、后台线程 |
| 打包体积大(WebEngine + mpv) | 分发不便 | 先以源码 + venv 运行;后期 PyInstaller onedir |
9. 里程碑
状态:M0、M1、M2、M3、M4、M5 已完成并实测(登录态、目录树、搜索、我听、直链播放、
mpv 双后端与自动兜底、离线缓存与缓存优先播放均正常);M6 待开始。
| 阶段 | 交付物 | 验收标准 |
|---|---|---|
| M0 环境与骨架 | venv、requirements.txt、Yuyin.py、QQ 音乐风格主窗口骨架(侧栏 / 内容区 / 播放条,占位) | python Yuyin.py 打开窗口无报错,布局与配色符合第 7 节 |
| M1 登录与连通 | 内嵌 115 页面、持久化登录、Cookie 桥、根目录拉取 | 网页登录后重启仍登录;能打印根目录条目;登录状态灯正确 |
| M2 音乐库 + 我听 | 目录树、音频过滤、搜索、SQLite 索引;我听抓包与听单列表 | 任意含音频目录可列出歌曲;我听页能列出听单并进入详情 |
| M3 播放闭环 | 直链解析、本地代理、Qt 后端、播放条联动、循环/切歌 | 双击可播、拖动进度、切歌无卡死;断链自动恢复一次 |
| M4 mpv 后端(提前) | third_party/mpv/、MPV IPC 封装、后端自动选择、格式兜底 |
ape/ogg/opus 可直接播放;Qt 失败能自动切 mpv 并续上进度 |
| M5 离线缓存 | 下载队列、断点续传、LRU 上限、缓存优先播放、缓存管理页 | 已缓存曲目断网可播;下载中断可续;容量超限自动清理 |
| M6 体验与打包 | 队列抽屉、歌词、快捷键、深浅主题、托盘/媒体键、PyInstaller | 关闭重开恢复队列与音量;打包后能启动并播放 |
每个里程碑结束做一次自查:主线程无阻塞、无 Cookie 明文落盘、异常可恢复。
10. 运行约定
# 启动(自动使用 .venv,无需先激活)
python Yuyin.py
- 本地代理端口:
17890(配置文件可改,被占用时自动 +1 重试)。 - 任务栏:启动时声明
AppUserModelID=Yuyin.Desktop.App,图标取根目录main.ico,
直接复用 ico 内的多分辨率,避免任务栏沿用 pythonw 图标。 - 数据目录:程序目录下的
Data\(yuyin.db、profile/、logs/、cache/),绿色便携,
整个程序目录拷走即可迁移;音频缓存默认在程序目录的Cache\。
旧版 FyMusic 放在%LOCALAPPDATA%\FyMusic\或旧安装目录Data\中的数据(歌单库、登录 Cookie)
会在首次启动时自动迁移到余音的数据目录。 - 日志:按天滚动,默认 INFO,可在设置里开 DEBUG;日志中 Cookie 一律脱敏。
- 配置:
config.json与 SQLitemeta表,二者冲突时以文件为准。
11. 已确认决策(2026-10-28)
- 内核:采用 QtWebEngine 内嵌(方案 A);CDP 仅作兜底。
- 范围:除网盘目录外,同时接管 115「我听」听单。
- 离线:支持应用内缓存到本地,不做导出/另存。
- 界面:模仿 QQ 音乐布局与配色(主色
#31C27C),默认浅色。 - 播放:提前落地 mpv 后端(M4),与 Qt 后端并存。
- 更新通道(2026-10-31):版本检查与下载统一走 feiyu.ltd 作品页,不再使用 Gitee Release;
更新下载窗口展示本次更新说明,并支持暂停 / 继续 / 停止。 - 版本与发布(2026-10-31):版本号固定
0.1.2,日常改动不递增版本,发布即用同版本号
重新打包覆盖Yuyin-Setup-0.1.2.exe,并同步作品页体积、下载链接与中英技术参数。 - 会员与云备份(2026-10-31):客户端接入 feiyu.ltd 会员账号,云备份采用「先退出、
下次启动覆盖」的方式恢复歌单库与网盘登录态。 - 品牌更名(2026-10-31):软件更名为「余音」,宣传语「余音不绝,绕梁三日」;
程序文件与安装目录使用英文标识Yuyin,数据文件改为yuyin.db,旧版 FyMusic 数据自动迁移;
作品页迁移到/zuopin/yuyin/,旧地址跳转保留。
后续如需调整,直接改本节并同步第 9 节里程碑。
附录 A:115 接口清单(待抓包校准)
| 用途 | 方法与地址 | 关键参数/返回 | 状态 |
|---|---|---|---|
| 登录页 | https://115.com/(扫码登录卡片在首页内,?ct=login 已返回 404) |
内嵌网页完成 | 已确认 |
| 文件列表 | GET https://webapi.115.com/files(页面内 fetch) |
aid/cid/offset/limit/show_dir/o/asc/count_folders;state/count/data[];文件带 fid,目录带 cid/pid |
已确认可用 |
| 文件搜索 | GET https://webapi.115.com/files/search |
aid/cid=0/search_value/limit/offset;state/count/data[],文件带 dp 所在目录 |
已确认可用 |
| 下载直链 | GET https://webapi.115.com/files/download |
pickcode;file_url |
已验证(M3) |
| 播放接口(备选) | GET https://webapi.115.com/files/play |
pickcode;m3u8/mp4 |
待评估(M3) |
| 我听列表 | GET https://webapi.115.com/files/musicnew?topic_id=1&type=1&user_id=<uid> |
data.list[]:topic_id/topic_name/music_count/pic/topic_sort;含 curr_user.user_id |
已确认可用 |
| 我听列表兜底 | GET https://webapi.115.com/files/music_topic_listnew?fond=1&start=0&limit=20 |
data.data.list[] |
已确认可用 |
| 我听详情 | GET https://webapi.115.com/files/music_list?topic_id=<id>&start=0&limit=1150 |
data[]:id/file_name/pick_code/file_size/play_long/url |
已确认可用 |
| 我听播放(备选) | GET https://webapi.115.com/files/music?pickcode=…&topic_id=…&music_id=… |
条目自带的 url 字段 |
待验证(M3) |
| 含音频目录 | GET https://webapi.115.com/files/include_music_list |
data[]:cid/pid/n |
已确认可用(暂未使用) |
| 用户信息 | GET https://webapi.115.com/user/info |
state:true + 脱敏账号 |
已确认可用 |
说明:以上均为 115 网页端自身调用的接口,仅用于播放当前账号自己的文件;
字段与错误码在 M2 阶段已用内嵌页面实测核对,后续站点改版时同步更新此表。
附录 B:术语
- pickcode(pc):115 文件的提取码,取直链的核心标识。
- cid:115 目录 ID,根目录为 0。
- Cookie 桥:把浏览器内核里的登录 Cookie 同步给 Python 网络层的机制。
- 本地流代理:把需要鉴权的远端地址包装成本机稳定 HTTP 地址的中间层。
Yuyin (余音) Design & Implementation Plan
The lingering note never fades — it circles the beams for three days.
Goal: build a "multi-cloud-drive music player" on Windows with Python + PyQt6.
A Chromium kernel (QtWebEngine) hosts the 115 / Baidu / Tianyi / China Mobile cloud drive web pages as the entry
point for login and file operations, while a separate player interface lets you browse, search and play audio
from your cloud drive once signed in.
On top of that it offers 115 "I Listen" sheets, local playlists, offline caching, Feiyu member cloud backup and
work-page based online updates.Key decisions are confirmed (see section 11). This document serves both as the design description and as the basis for implementation and acceptance.
1. Project Positioning
| Item | Content |
|---|---|
| Name | Yuyin (余音) |
| Tagline | 余音不绝,绕梁三日 — the lingering note never fades, it circles the beams for three days |
| Platform | Windows 10/11 x64 (Windows first, cross-platform deferred) |
| Runtime | Local Python process + embedded Chromium view; not Electron, not a browser extension |
| Cloud sources | 115, Baidu, Tianyi and China Mobile cloud drives; 115 additionally supports "I Listen" sheets |
| Core experience | "My player" on the left, the selected cloud drive's web page on the right; log in once in the page, then browse folders, search and play that account's audio in the player, with local playlists |
| Extras | Offline cache and cache management, Feiyu member cloud backup (library + cloud-drive login state), work-page online updates (release notes, pause/resume/stop) |
| Content boundary | Only plays audio files in the current account's own cloud drive; no VIP cracking, no rate-limit bypassing, no third-party music sources |
1.1 Problems to Solve
- Cloud drive web versions have no comfortable interface for listening to music; switching tracks, seeking and browsing lists are all awkward.
- Cloud drive direct links are authenticated and short-lived, so an ordinary player cannot use them directly.
- We want to reuse each provider's official web login entirely and never store account credentials locally.
- Playlists and audio metadata already present in 115 "I Listen" are a ready-made way to organize music; they should appear in the player instead of being playable only from the web page.
- When the network is unstable or when listening offline, frequently played tracks should be cacheable locally; when switching machines or reinstalling, playlists and login state should be restorable in one click.
1.2 Non-goals (out of scope for this phase)
- No multi-account switching, no cloud drive upload/delete/rename management features.
- No online music search, MV or live streaming; no "save/export to a user-specified directory" download feature (in-app offline cache only).
- No mobile or web client.
- No modification of the 115 web page's own DOM structure (only minimal read-only injection, see 5.9, and it is optional).
2. Key Technical Judgements
2.1 How to Deliver a "115-Browser-as-Kernel"
The 115 browser itself is based on Chromium (its site describes a "brand-new Chromium high-speed kernel"). Two routes are therefore possible:
| Option | Approach | Pros | Risks | Conclusion |
|---|---|---|---|---|
| A. Embedded QtWebEngine (recommended) | Load 115.com in a PyQt6-WebEngine QWebEngineView, keep login cookies in a persistent Profile, then take the session from the cookie jar for Python-side WebAPI calls |
Single window, controllable, no need to install the 115 browser, cookies are accessible | Chromium/WebEngine compatibility with the 115 site must be verified; there is no official "115 kernel" | Primary approach (confirmed) |
| B. Drive the installed 115 browser (CDP) | Start the 115 browser with --remote-debugging-port, take over tabs via CDP and export cookies |
Behaviour matches the official browser exactly; lowest risk-control exposure | Requires the 115 browser to be installed, complex process management, limited cookie export | Fallback only |
The "browser kernel" is abstracted behind an interface (BrowserKernel) so that A/B implementations are interchangeable and no rework is needed later.
2.2 Playback Chain
QMediaPlayer accepts only a single URL, while 115 play addresses require cookies/UA and expire within minutes.
So we use a local proxy + direct link approach:
QMediaPlayer → http://127.0.0.1:17890/stream/<token> → local proxy (injects Cookie/UA, passes Range through) → 115 CDN direct link
Benefits: the player gets a stable URL, progress seeking and next-track preloading work; cookies never appear in the player;
when a direct link expires the proxy obtains a fresh one automatically.
3. Technology Stack
| Layer | Choice | Notes |
|---|---|---|
| Language | Python 3.11.9 | Virtual environment .venv already created |
| GUI | PyQt6 6.11.0 (Qt 6.11.0) | Main window, widgets, theming |
| Browser kernel | PyQt6-WebEngine 6.11.0, Chromium 140.0.7339.225 (verified) | 115 login, cloud drive browsing |
| Network | requests | 115 WebAPI calls, proxy forwarding |
| Local proxy | Standard library http.server + ThreadingHTTPServer (or socketserver) |
No extra dependency, sufficient |
| Audio playback (primary) | QtMultimedia QMediaPlayer + QAudioOutput |
System decoders (WMF); covers mp3/flac/wav/m4a |
| Audio playback (fallback) | External mpv.exe + named-pipe JSON IPC |
Implemented early; covers ape/ogg/opus/wma/dsf etc.; lives in third_party/mpv/ |
| Offline cache | Filesystem + SQLite index | Range resume, LRU cap, local playback preferred |
| Tags/cover | mutagen | Reads ID3/FLAC tags, embedded covers, embedded lyrics |
| Local storage | SQLite (sqlite3 from the standard library) |
Folder/listen caches, playlists, play history, cache index |
| Packaging (later) | PyInstaller + mpv.exe | Handled in milestone M6 |
Environment preparation already performed (installed and verified):
python -m venv .venv
.\.venv\Scripts\python.exe -m pip install -i https://mirrors.ustc.edu.cn/pypi/web/simple PyQt6 PyQt6-WebEngine requests mutagen
Measured versions: PyQt6 6.11.0 / Qt 6.11.0 / PyQt6-WebEngine 6.11.0 (Chromium 140.0.7339.225) /
requests 2.34.2 / mutagen 1.48.1.
Network note: the Tsinghua and Aliyun mirrors were slow and prone to interruption when downloading Qt runtimes on this machine; the USTC mirror worked normally, so USTC is the default for package installs.
The dependency list will be captured in requirements.txt (milestone M0).
4. Overall Architecture
┌────────────────────────────────────────────────────────────────────────┐
│ Yuyin main window (QQ Music style) │
│ ┌──────────┐ ┌─────────────────────────────────┐ ┌──────────────────┐ │
│ │ Sidebar │ │ Content area │ │ Queue drawer │ │
│ │ Discover │ │ · Playlist cards / track list │ │ (collapsible) │ │
│ │ My music │ │ · Listen sheets / cache mgmt │ │ │ │
│ │ I Listen │ │ │ │ │ │
│ │ Cache │ │ │ │ │ │
│ └──────────┘ └─────────────────────────────────┘ └──────────────────┘ │
│ ┌────────────────────────────────────────────────────────────────────┐ │
│ │ Bottom bar: cover / title / progress / controls / volume / mode / queue │
│ └────────────────────────────────────────────────────────────────────┘ │
│ Right side, expandable: QWebEngineView (115 web: login / I Listen / files) │
└───────────┬──────────────────┬───────────────────┬────────────────────┘
│ │ │
▼ ▼ ▼
┌────────────────┐ ┌────────────────┐ ┌──────────────────┐
│ PlayerService │ │ SessionService │ │ LibraryService │
│ queue / states │ │ cookie bridge │ │ folders / index │
└───┬────────┬───┘ └───────┬────────┘ └────────┬─────────┘
│ │ │ │
▼ ▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌───────────────┐ ┌──────────────┐
│QtBackend │ │MpvBackend│ │Cloud115Client │ │ CacheService │
│QMediaPlayer││mpv + IPC │ │files / I Listen│ │ offline + LRU│
└────┬─────┘ └────┬─────┘ └───────┬───────┘ └──────┬───────┘
│ │ │ │
└────────────┴──────► LocalStreamProxy ◄──────┘
127.0.0.1:17890
│
▼
115 CDN direct link
Layering principles:
ui/handles only presentation and interaction; no 115 protocol logic.core/owns the 115 session, APIs, proxy, I Listen, cache and local database; all synchronous logic, independently testable.player/unifies the Qt and mpv backends behindPlaybackBackend; the UI only subscribes to state signals.- Blocking work (network, playback, downloads) runs on
QThreadPool/worker threads; the main thread is never blocked.
5. Key Mechanism Design
5.1 Login State (SessionService)
- Use a dedicated persistent profile (
QWebEngineProfile("yuyin")) with
setPersistentCookiesPolicy(ForcePersistentCookies); Chromium stores and sends login cookies itself,
so the session survives a restart. - Cookies are not exported:
webapi.115.comsits behind a WAF (direct requests return a 405 interception page),
so all WebAPI calls are made with in-pagefetch(see 5.2) and HttpOnly cookies are attached by the browser automatically. - Login check: after page load, call
/files?aid=1&cid=0&limit=1;
state:truemeans logged in;/user/inforeturns the masked account name. - Trigger points: page
loadFinished+ changes to key cookies (UID/CID/SEID/KID), debounced by 700 ms;
failures retry 3 times (2 s/4 s/6 s) so a single network hiccup does not cause a false negative. - No account/password storage; cookies are managed by Qt's encrypted store.
- UI: sidebar and top bar show "not logged in / logged in"; the 115 panel shows a detailed hint in its top-right corner.
5.2 Request Channel (PageApiBridge)
- All 115 WebAPI calls go through in-page
fetchexecuted viaQWebEnginePage.runJavaScript;
results are stored inwindow.__fyResultsand polled from Python (Qt'srunJavaScriptdoes not await Promises). - Advantages: HttpOnly cookies are attached automatically, Chromium's DNS/proxy/fingerprint are reused, and the WAF is bypassed.
- Always use
credentials: "include",cache: "no-store", 15 s timeout. - The UA is used only for the embedded page (
profile.setHttpUserAgent); CDN downloads in M3 reuse the same UA. - Trade-off: API calls depend on a live page; if the page breaks, prompt "please refresh the 115 page".
5.3 Music Library (Cloud115Client + LibraryService)
- Folder listing:
GET https://webapi.115.com/fileswith the common parameters
aid=1,cid=<folder id>,offset,limit(paging),show_dir=1,o=user_ptime,asc=0,format=json. - File search:
GET https://webapi.115.com/files/search(parameters to be calibrated by packet capture) as the "whole-drive search" capability. - Field mapping (subject to the actual response; verified during M1):
| 115 field | Meaning | Local use |
|---|---|---|
cid |
Folder ID | Folder tree node |
fid |
File ID | Part of the primary key |
pc |
pickcode | Key for resolving direct links |
n / name |
Name | Display, lyric matching |
s / size |
Size | Display |
sha |
SHA1 | Optional deduplication |
ptime |
Modified time | Sorting |
- Audio detection: do not rely on 115's type filter (its
typevalues need verification); fetch everything and filter by extension whitelist:
mp3, flac, wav, m4a, aac, ogg, opus, wma, ape, dsf, dff, aiff. - Caching: SQLite stores
folders/tracks; entering a folder renders the cache first and refreshes in the background;
folders are lazily expanded (children fetched on expand). - Volume normalization, cover art and duration: duration comes from
QMediaPlayer.durationor mutagen and is written to the cache.
5.4 "I Listen" Sheets (ListenService)
The 115 "I Listen" entry is https://115.com/?ct=music (the "I Listen" item in the cloud drive sidebar).
The APIs were verified in M2 with real requests from the embedded page (see Appendix A):
- Sheet list:
GET /files/musicnew?topic_id=1&type=1&user_id=<uid>&start=0&limit=20
→data.list[]with fieldstopic_id,topic_name(may be empty),music_count,pic,topic_sort;
thecurr_user.user_idin the response is written back locally for the next request.
When the list is empty, fall back to/files/music_topic_listnew?fond=1&start=0&limit=20. - Sheet tracks:
GET /files/music_list?topic_id=<id>&start=0&limit=1150
→data[]with fieldsid,file_name,pick_code,file_size,play_long(seconds),url(built-in play address). - Local mapping: converted into the unified track model (
pickcode/name/size/duration)
and stored in thelisten_sheetsandlisten_itemstables; the cache is rendered first and refreshed in the background. - Playback: I Listen entries are still cloud drive audio files and share the 5.5 direct-link and 5.6 proxy path;
an entry's ownurl(/files/music?pickcode=…&topic_id=…&music_id=…) is the alternative channel evaluated in M3. - Resume: read/write cross-device progress if the API provides it; otherwise track positions per entry in the local
resumetable. - Degradation: if the API is unavailable, run read-only JS in the page to read the rendered data (or the initial state object on
window)
and send it back viaQWebChannel; page logic is never modified, and the feature can be switched off if the site changes.
5.5 Resolving Play URLs (PlayUrlResolver)
- API: in-page
fetchofGET https://webapi.115.com/files/download?pickcode=<pc>
→ thefile_urlin the response (CDN direct link with signature and short lifetime). - Cache:
pickcode → (url, expire_at)with a 5-minute TTL; on 403/404 during playback the entry is invalidated and fetched once more. - Alternative: 115's open-platform video/audio playback APIs (
/files/play,/open/playand similar) when VIP capabilities are needed. - Tracks already cached locally never resolve a direct link; the local file path is passed instead.
- CDN direct links verified:
/files/downloadreturnsfile_url/file_size/sha1; requests with cookies plus Range
returns206directly (verified in M3). - Key detail: CDN authentication depends on the path-scoped token cookie issued with the
/files/downloadresponse
(of the form<32-hex>=<md5>,Path=/<storage-path-hash>/), which must be matched by path and sent with the request. - Endpoints and parameters confirmed in M1:
/files,/user/infowork.
5.6 Local Streaming Proxy (LocalStreamProxy)
- Listens on
127.0.0.1:17890only; route:GET /stream/<token>, with token an unpredictable mapping ofpickcode(secrets.token_urlsafe). - Behaviour:
- pass the client
Rangethrough and issue a request to the 115 CDN with cookies/UA; - return
206 Partial ContentwithContent-Range/Content-Length/Content-Type/Accept-Ranges: bytes; - forward in 64 KB chunks; never buffer whole files in memory;
- handle connection teardown; at most 2 concurrent connections per track (prevents seeking storms).
- Shares the same "resolve + authenticated request" code as the cache to avoid duplicate maintenance.
- Cookie source: Chromium exclusively locks
profile/Cookieswhile running, so disk reads fail; instead we listen to
QWebEngineCookieStore.cookieAddedand keep an in-memory cookie jar (including HttpOnly and path-scoped items),
from which the proxy derives the Cookie header by host + path match (verified in M3). - On upstream 403, wait 0.6 s, re-fetch cookies and retry once (covers the race between token issuance and the playback request).
- Client disconnects (including the short connections mpv uses for probing/seeking) are treated as normal; only debug logs, no exceptions.
- Security: loopback addresses only; the port is closed when the process exits.
5.7 Offline Cache (CacheService)
- Goal: instant playback of cached tracks and offline listening; in-app cache only, no "export/save as".
- Location:
<app dir>\Cache\<pickcode>.<ext>(the default, configurable on the "Local cache" page); downloads go to.partand are atomically renamed on completion. - Download: dedicated worker thread + queue (concurrency 2) reusing the Range logic, with resume and 3 retries on failure.
- Triggers:
- manual: the "cache" button on the player bar;
- automatic (off by default, can be enabled in settings): cache the current track in the background while playing, so the next play hits the local file.
- Index: the
cache_filestable stores pickcode, path, size, state and last-used time. - Eviction: default total cap of 10 GB (configurable) with LRU cleanup; tracks being played or downloaded are never evicted.
- Verification: compare file size after completion; run an integrity check with
sha1when available (large files may be deferred to idle time). - UI: the list shows "cached/caching" markers; the sidebar "Local cache" page shows usage, the cap and a one-click cleanup.
- Implementation (M5):
app/core/cache.pyruns 2threadingworkers; progress/completion events are delivered to the main thread via
task_changed(pickcode)/cache_changed, and SQLite is only accessed from the main thread;
the_doneset gives O(1) state queries for the track table and player bar. When playback hits the cache, the Qt backend converts the local path
to afile://URL before handing it to QMediaPlayer. The auto-cache switch and cap (1–500 GB) live in themetatable. - Measured (M5):
849.好心分手 - 卢巧音.mp3(7.2 MB) cached tocache/audio/<pickcode>.mp3;
playing it again hits the local file directly (backend=qt, no network request);.partresume and LRU eviction then take effect.
5.8 Player and Dual Backends (PlayerService)
- Uniform interface
PlaybackBackend:load / play / pause / stop / seek / set_volume,
plusposition_changed / duration_changed / state_changed / finished / errorsignals; the UI depends only on the interface. QtBackend:QMediaPlayer+QAudioOutput(Qt 6.11 ships an FFmpeg backend), the default backend: fast startup, system decoding.MpvBackend(landed in M4): each instance starts its own external process
mpv.exe --idle=yes --no-video --audio-display=no --no-terminal --really-quiet --input-ipc-server=\\.\pipe\yuyin-mpv-<random suffix> --volume=<n>;
connected withQLocalSocket(without the\\.\pipe\prefix, 80 ms × up to 40 retries),
thenobserve_propertysubscribes totime-pos / duration / pause; playback starts withloadfile ... replace,
pause usesset_property pause, seeking usesseek ... absolute;file-loaded/end-file
(eof→finished,error→error) are mapped onto the unified signals. The process starts silently withCREATE_NO_WINDOW
and is shut down withquitfollowed byterminate/kill, leaving no background processes.- Selection strategy:
ape/ogg/opus/wma/dsf/dff/aiffgo straight to mpv (MPV_EXTS);
if Qt decoding fails and the backend has not been switched yet, switch to mpv automatically, resume the current position as_pending_seek,
and emitstate=fallbackfor the player bar to show; only if mpv also fails is the link refreshed and retried once, after which an error is shown. - State machine:
Idle → Resolving → Loading → Playing ⇄ Paused → Ended/Error,
plus two hint states:fallback(Qt→mpv) andreconnecting(retry with a fresh link). - Queue modes: sequential / repeat one / repeat all / shuffle.
- Retries: resolution failures retry once; a link expiring mid-playback is replaced and playback resumes once, then an error is shown.
- Measured (M4): mpv v0.41.0-1227-ge471496c5 (shinchiro 20261021 build,
mpv.exeonly);
cloud track袁咏琳;周杰伦 - 画沙.ape(29 MB / 04:33) plays directly through the local proxy with
backend=mpv,duration=273666and a normally advancingpos; Qt automatically switches to mpv and emitsfallbackon a corrupt file. - Distribution note: mpv is GPLv2+, so bundling it requires the licence and a way to obtain the source; for the MVP users may supply
mpv.exethemselves.
5.9 Browser Area Enhancements (optional, M6)
- Inject a small floating button ("add to playlist") into the 115 page that talks to Python over
QWebChannel;
it only listens for clicks and never alters 115's own DOM logic, so the feature can be disabled if the site changes. - Sync the address bar and login state whenever the page
title/urlChangedchanges.
5.10 Data Model (SQLite, <app dir>\Data\yuyin.db)
-- Landed in M2 (app/core/store.py)
folders(cid TEXT PRIMARY KEY, parent_cid TEXT, name TEXT, updated_at INTEGER);
tracks(pickcode TEXT PRIMARY KEY, cid TEXT, fid TEXT, name TEXT, size INTEGER,
mtime INTEGER, path TEXT, updated_at INTEGER);
listen_sheets(topic_id TEXT PRIMARY KEY, name TEXT, pic TEXT, music_count INTEGER,
sort INTEGER, updated_at INTEGER);
listen_items(item_id TEXT PRIMARY KEY, topic_id TEXT, name TEXT, pickcode TEXT,
size INTEGER, duration INTEGER, updated_at INTEGER);
meta(key TEXT PRIMARY KEY, value TEXT); -- user_id, settings, last selected folder, volume, etc.
-- Landed in M5 (app/core/store.py)
cache_files(pickcode TEXT PRIMARY KEY, path TEXT, size INTEGER, state TEXT,
created_at INTEGER, last_used_at INTEGER);
-- Reserved for M6
playlists(id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, created_at INTEGER);
playlist_items(playlist_id INTEGER, pickcode TEXT, sort INTEGER, PRIMARY KEY(playlist_id, pickcode));
history(pickcode TEXT PRIMARY KEY, played_at INTEGER, play_count INTEGER);
resume(pickcode TEXT PRIMARY KEY, position_ms INTEGER, updated_at INTEGER);
5.11 Top Menus and Shortcuts
- Menu bar: File (search tracks, show/hide the 115 panel, clear local cache, quit), Playback (play/pause,
previous, next, volume ±), Settings (shortcuts…, auto-cache while playing, Feiyu member / cloud backup…),
Help (member space, check for updates, website, about). - "Website" opens the work page
https://feiyu.ltd/zuopin/yuyin/; "Member Space" openshttps://feiyu.ltd/member/. - Default shortcuts match QQ Music for PC: play/pause
Ctrl+Alt+F5, previousCtrl+Alt+Left,
nextCtrl+Alt+Right, volume ±Ctrl+Alt+Up/Down; alsoF9to show/hide 115,
Ctrl+Fto search,Ctrl+Qto quit. - Settings → Shortcuts… records each combination and checks for duplicates before saving; bindings are stored in
meta.shortcuts(JSON), clearing an entry disables it, and "restore defaults" refills the QQ Music defaults.
5.12 Track Detail (clicking a track)
- A single click on any row opens a non-modal detail panel: title, location, duration, size, format and cache state,
with "play / cache locally (greyed out when cached) / close" actions. - The single-click signal is emitted only after double-click detection so that a double click still just plays without opening the panel;
cache state refreshes live with download progress.
5.13 Feiyu Membership and Cloud Backup (MemberCloudService)
- The account is the feiyu.ltd site account:
POST /api/member/login/returns a token stored inmeta.feiyu_token;
an invalid token automatically falls back to the login form. - One dialog, two states: when signed out it shows account/password plus a register link (
/member/register/);
when signed in it shows a work selector, backup notes and the cloud backup list, with upload current config,
download to local, restore, delete, plus "Member Space" and "Sign out". - Backup package:
yuyin.db(written through the SQLite online backup API for consistency) plus the browser
Profile (caches skipped, roughly 2 MB), zipped and uploaded; anything above 20 MB is rejected before upload.
Profile files held exclusively by the running browser (such as Cookies) fall back to a SQLite snapshot or a
shared-access read; files that still fail are skipped with a notice instead of aborting the whole backup. - Restore uses a "quit now, overwrite on next launch" strategy: download → stage → quit the app → apply on the next
start before the database and Profile are opened, so SQLite/Profile are never written while running. Absolute paths
and..entries are rejected while unpacking. - Entry points: Settings → Feiyu member / cloud backup…; Help → Member Space.
- A permanent member badge sits at the bottom of the sidebar: after login it shows the account initial, name and
level (gold for VIP, with the expiry date on the second line); signed out it shows a grey "?" and
"click to sign in", and clicking it opens the member dialog. When the sidebar is collapsed only the avatar
remains, so the state is still visible at a glance and the full level and expiry appear on hover.
5.14 Online Updates (single source: the work page)
- Version information comes only from
https://feiyu.ltd/zuopin/yuyin/: the version badge, the download button
and the release notes in "Update history". - Update check: fetch the page, compare with the local
APP_VERSION, and show the release notes when a newer version
exists. "Skip this version" writesmeta.update_skip; a manual check from Settings ignores the skip. - Update download window: 640×540, showing the release notes, progress and status, with Pause / Resume, Stop and Close.
Stop aborts the transfer and deletes the partial installer; closing the window mid-download behaves like Stop. - After the download the app quits at once and the installer runs silently; any Yuyin instances still
running are closed automatically during setup so locked files never break the install. - The version stays at
0.1.2during development (no bump per change); a release repackages and overwrites
Yuyin-Setup-0.1.2.exewith the same version number.
6. Code Structure (as of M0)
Yuyin/
├── .venv/ # Virtual environment (created, dependencies installed)
├── README.md # This document
├── requirements.txt
├── Yuyin.py # Launcher (switches into the venv and runs app.main)
├── main.ico # Application icon (multi-resolution; window/taskbar/tray all use it)
├── third_party/mpv/ # M4: mpv.exe + licence
└── app/
├── main.py # Entry point: QApplication, theme, MainWindow
├── config.py # Paths, UA, extension whitelists, constants
├── core/
│ ├── browser_kernel.py # Persistent Profile + QWebEngineView
│ ├── page_api.py # PageApiBridge: in-page fetch + polling
│ ├── session.py # SessionService: login checks and state signals
│ ├── cloud115.py # Cloud115Client: folder/search/I Listen APIs
│ ├── library.py # LibraryService: folder paging, audio filtering, search
│ ├── listen.py # ListenService: I Listen sheets and tracks
│ ├── cookies.py # CookieStore: in-memory cookie jar (with path matching)
│ ├── resolver.py # PlayUrlResolver: pickcode → CDN direct link (TTL cache)
│ ├── proxy.py # LocalStreamProxy: 127.0.0.1 stream proxy
│ ├── cache.py # CacheService: offline downloads and LRU (M5)
│ ├── member_cloud.py # MemberCloudService: member login, works, cloud backup upload/restore
│ ├── updater.py # UpdateService: work-page version parsing, pausable download
│ └── store.py # SQLite access layer (landed)
├── player/
│ ├── backend.py # PlaybackBackend interface + QtBackend
│ ├── mpv_backend.py # MpvBackend (JSON IPC)
│ ├── engine.py # PlayerService: queue/state machine/backend switching
│ └── tagreader.py # mutagen tags/covers/lyrics
└── ui/
├── main_window.py # Overall layout: sidebar + content + player bar
├── sidebar.py # Left navigation
├── content_area.py # Top bar + page stack + search entry
├── library_panel.py # Folder tree + track table (landed)
├── listen_panel.py # I Listen sheets + track table (landed)
├── track_table.py # Shared track table
├── cache_page.py # Cache management page (landed)
├── shortcuts.py # Shortcut manager + settings dialog
├── track_detail.py # Track detail panel
├── member_dialog.py # Feiyu member / cloud backup dialog (login, upload, restore)
├── update_dialog.py # Update download window (release notes + pause/resume/stop)
├── browser_panel.py # Address bar + QWebEngineView
├── player_bar.py # Bottom player bar
├── queue_drawer.py # Queue drawer (M6)
└── style.qss # QQ Music style light theme
7. Interface Plan
- Layout follows QQ Music for desktop: left navigation + centre content + bottom player bar + collapsible queue on the right;
the 115 web page slides out as a right-hand drawer/column (F9to collapse) without interrupting playback. - Main window 1280×820, minimum 1024×680; sidebar 208 px, player bar 84 px.
- Palette: primary
#31C27C(QQ Music green), background#F6F7F9, white cards with 8 px radius,
primary text#222222, secondary text#8A8F99, dividers#EDEFF2; light theme by default, dark theme in M6. - Sidebar: logo + Discover / My Music / I Listen / Local Cache / Settings; the selected item gets a light-green background, green text and a left indicator bar.
- Content area: top bar (page title, search box, login state, open-115 button) + playlist card grid + track list.
- Player bar: left (56×56 cover, title, artist, cache marker), centre (previous / play / next, progress, time),
right (play mode, volume, queue). - List interactions: hover highlight, double-click to play, context menu (play / play next / cache / show containing folder).
- Empty states state the next action clearly (not logged in → "log in to 115"; no cache → "pick a few tracks from the library").
- Icons are drawn as inline SVG/vector; QQ Music's icons, fonts and image assets are never copied — only layout and interaction habits are borrowed.
- Shortcuts:
Space,←/→,Ctrl+←/→,Ctrl+F,F9(web drawer),Ctrl+Q.
8. Risks and Mitigations
| Risk | Impact | Mitigation |
|---|---|---|
| 115 WebAPI changes or risk control tightens | Listing/direct links fail | Endpoints and parameters live in a single file; readable errors are shown; option B (CDP) is kept |
| 115 WAF blocks direct Python requests (measured 405) | All requests-based API calls fail | Everything goes through in-page fetch (5.2); Python only orchestrates and plays |
| Cookies are HttpOnly and cannot be read | API calls impossible | Use the CookieStore native interface instead of JS |
| Direct links expire quickly / bind UA and IP | Playback breaks mid-track | The local proxy adds headers; on 403 the link is refreshed and playback resumes |
| Embedded Chromium version incompatible with 115 | Cannot log in / blank page | Use the latest PyQt6; switch to option B if needed; offer a "log in with an external browser then import cookies" degradation path (optional) |
| "I Listen" has no public documentation | Sheets unreadable | Confirm by packet capture first; fall back to read-only page JS; last resort is cloud drive folders only |
| Copyright and compliance | Account risk | Play only your own files, keep API usage low-frequency, no export/download or cracking |
| Incomplete audio format support (ape/ogg/opus) | Some tracks cannot play | Dual backends in M4 with mpv fallback; the list shows the decoding backend |
| mpv distribution licence (GPLv2+) | Compliance risk | Include the licence and source-availability statement; the MVP lets users supply mpv.exe |
| Offline cache fills the disk | Disk warnings | 10 GB default cap + LRU + cache management page; free space checked before download |
| Copyright boundary of the UI imitation | Visual disputes | Borrow layout and interaction only; never copy QQ Music icons, fonts or images |
| Large folders stutter | Poor experience | 100–500 items per page, lazy loading, SQLite indexes, background threads |
| Large package size (WebEngine + mpv) | Distribution burden | Run from source + venv first; PyInstaller onedir later |
9. Milestones
Status: M0, M1, M2, M3, M4 and M5 are complete and measured (login state, folder tree, search, I Listen, direct-link playback,
mpv dual backends with automatic fallback, offline cache and cache-first playback all work); M6 has not started.
| Phase | Deliverables | Acceptance criteria |
|---|---|---|
| M0 Environment and skeleton | venv, requirements.txt, Yuyin.py, QQ Music style main window skeleton (sidebar / content / player bar, placeholders) | python Yuyin.py opens the window without errors; layout and palette match section 7 |
| M1 Login and connectivity | Embedded 115 page, persistent login, cookie bridge, root folder fetch | Still logged in after a restart; root entries printed; login indicator correct |
| M2 Library + I Listen | Folder tree, audio filtering, search, SQLite index; I Listen capture and sheet list | Any folder containing audio lists its tracks; the I Listen page lists sheets and opens details |
| M3 Playback loop | Direct-link resolution, local proxy, Qt backend, player bar wiring, repeat/next | Double-click plays, seeking works, track switching does not hang; a broken link recovers once |
| M4 mpv backend (early) | third_party/mpv/, MPV IPC wrapper, automatic backend selection, format fallback |
ape/ogg/opus play directly; Qt failures switch to mpv with the position preserved |
| M5 Offline cache | Download queue, resume, LRU cap, cache-first playback, cache management page | Cached tracks play offline; interrupted downloads resume; over-capacity is cleaned automatically |
| M6 Experience and packaging | Queue drawer, lyrics, shortcuts, light/dark themes, tray/media keys, PyInstaller | Queue and volume restored after restart; the packaged build starts and plays |
At the end of each milestone, self-check: no main-thread blocking, no plaintext cookies on disk, exceptions are recoverable.
10. Runtime Conventions
# Launch (uses .venv automatically, no activation needed)
python Yuyin.py
- Local proxy port:
17890(configurable; retries with +1 when occupied). - Taskbar: declares
AppUserModelID=Yuyin.Desktop.Appat startup and usesmain.icofrom the project root,
reusing the multi-resolution ico so the taskbar does not fall back to the pythonw icon. - Data directory:
Data\under the program directory (yuyin.db,profile/,logs/,cache/); fully portable,
so copying the whole program folder migrates everything; audio cache defaults toCache\in the program directory.
Data kept by older FyMusic versions under%LOCALAPPDATA%\FyMusic\or the old install directory'sData\
(playlist database, login cookies) is migrated automatically into the Yuyin data directory on first launch. - Logging: daily rotation, INFO by default, DEBUG can be enabled in settings; cookies are always masked in logs.
- Configuration:
config.jsonand the SQLitemetatable; on conflict the file wins.
11. Confirmed Decisions (2026-10-28)
- Kernel: embedded QtWebEngine (option A); CDP is a fallback only.
- Scope: beyond cloud drive folders, also take over 115 "I Listen" sheets.
- Offline: in-app caching to local disk; no export/save-as.
- Interface: imitate QQ Music's layout and palette (primary
#31C27C), light theme by default. - Playback: land the mpv backend early (M4) and keep it alongside the Qt backend.
- Update channel (2026-10-31): version checks and downloads go through the feiyu.ltd work page only, no Gitee
Releases; the update window shows release notes and supports pause / resume / stop. - Versioning and release (2026-10-31): the version stays
0.1.2; routine changes do not bump it, a release
repackages and overwritesYuyin-Setup-0.1.2.exeand refreshes the work page size, download link and the
Chinese/English tech specs. - Membership and cloud backup (2026-10-31): the client signs in with feiyu.ltd accounts; backups are restored
by quitting the app and overwriting playlists and cloud-drive login state on the next launch. - Rebranding (2026-10-31): the app is renamed to 余音 (Yuyin) with the tagline 余音不绝,绕梁三日;
executables and the install directory use the English identifierYuyin, the database becomesyuyin.db,
and data from older FyMusic versions is migrated automatically; the work page moves to/zuopin/yuyin/
with a redirect kept for the old address.
To change any of these later, edit this section and keep section 9's milestones in sync.
Appendix A: 115 API Inventory (to be calibrated by packet capture)
| Purpose | Method and URL | Key parameters/response | Status |
|---|---|---|---|
| Login page | https://115.com/ (the QR login card is on the home page; ?ct=login returns 404) |
Completed in the embedded page | Confirmed |
| File listing | GET https://webapi.115.com/files (in-page fetch) |
aid/cid/offset/limit/show_dir/o/asc/count_folders; state/count/data[]; files carry fid, folders carry cid/pid |
Confirmed working |
| File search | GET https://webapi.115.com/files/search |
aid/cid=0/search_value/limit/offset; state/count/data[], files carry dp (containing folder) |
Confirmed working |
| Download link | GET https://webapi.115.com/files/download |
pickcode; file_url |
Verified (M3) |
| Play API (alternative) | GET https://webapi.115.com/files/play |
pickcode; m3u8/mp4 |
To be evaluated (M3) |
| I Listen list | GET https://webapi.115.com/files/musicnew?topic_id=1&type=1&user_id=<uid> |
data.list[]: topic_id/topic_name/music_count/pic/topic_sort; includes curr_user.user_id |
Confirmed working |
| I Listen list fallback | GET https://webapi.115.com/files/music_topic_listnew?fond=1&start=0&limit=20 |
data.data.list[] |
Confirmed working |
| I Listen details | GET https://webapi.115.com/files/music_list?topic_id=<id>&start=0&limit=1150 |
data[]: id/file_name/pick_code/file_size/play_long/url |
Confirmed working |
| I Listen playback (alternative) | GET https://webapi.115.com/files/music?pickcode=…&topic_id=…&music_id=… |
The entry's own url field |
To verify (M3) |
| Folders containing audio | GET https://webapi.115.com/files/include_music_list |
data[]: cid/pid/n |
Confirmed working (not yet used) |
| User info | GET https://webapi.115.com/user/info |
state:true + masked account |
Confirmed working |
Note: all of the above are endpoints that the 115 web client itself calls and are used only to play files belonging to the current account;
fields and error codes were verified with real requests from the embedded page during M2 and this table is updated whenever the site changes.
Appendix B: Glossary
- pickcode (pc): 115's extraction code for a file, the key identifier for resolving direct links.
- cid: a 115 folder ID; the root folder is 0.
- Cookie bridge: the mechanism that syncs login cookies from the browser kernel to Python's network layer.
- Local stream proxy: the middleware layer that wraps an authenticated remote address as a stable local HTTP address.
更新历史
共 1 个版本- 软件更名为「余音」,宣传语「余音不绝,绕梁三日」,窗口、侧栏、关于与安装程序全部更新
- 程序文件、安装目录与数据文件统一使用 Yuyin 命名(Yuyin.exe、Yuyin 目录、yuyin.db)
- 从旧版 FyMusic 升级时,原 Data\ 中的歌单与网盘登录状态首次启动自动迁移,旧云端备份也可正常恢复
- 侧栏顶部展示宣传语,折叠后自动隐藏;关于对话框显示名称、宣传语与版本
- 客户端内的官网、会员空间、注册入口与在线更新下载地址全部切换为 HTTPS
- 作品页迁移到 /zuopin/yuyin/,旧地址自动跳转
- 保留并整合此前版本的能力:飞鱼会员登录与云备份、侧栏常驻会员状态、可暂停的更新下载窗口、更新安装时自动关闭运行实例
使用交流
进入讨论区 →还没有讨论,去讨论区发起第一个话题吧。