diff --git a/.github/workflows/opencode.yml b/.github/workflows/opencode.yml index 83234c0ef..cddcee7cc 100644 --- a/.github/workflows/opencode.yml +++ b/.github/workflows/opencode.yml @@ -37,7 +37,7 @@ jobs: model: ${{ vars.OPENCODE_MODEL }} prompt: | 你的名字是「${{ vars.OPENCODE_NAME }}」,在回复开头用这个名字自我介绍。 - + 请根据当前的 issue 或 PR 内容,分析问题并给出具体的解决方案或建议。 如果是 bug 报告,请分析可能的原因并提供修复思路。 如果是功能请求,请评估可行性并给出实现建议。 @@ -53,7 +53,7 @@ jobs: model: ${{ vars.OPENCODE_MODEL }} prompt: | 你的名字是「${{ vars.OPENCODE_NAME }}」,在回复开头用这个名字自我介绍。 - + 请根据当前的 issue 或 PR 内容,分析问题并给出具体的解决方案或建议。 如果是 bug 报告,请分析可能的原因并提供修复思路。 如果是功能请求,请评估可行性并给出实现建议。 diff --git a/CHANGELOG.md b/CHANGELOG.md index 42f625971..c8a745956 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ - 🔧 修复 **跳转后 seek 位置丢失回到 0:00** - 🔧 修复 **自动切下一首进度条跳到非 0:00 错误位置** - 🎚️ 新增 **全局歌词偏移设置**(PR #11) + --- ## v3.0.0-rc.2 diff --git a/README.md b/README.md index c47604eda..3b0e20431 100644 --- a/README.md +++ b/README.md @@ -38,23 +38,23 @@ ## ✨ 特性一览 -| 分类 | 描述 | -| ----------------- | ------------------------------------------------------------------------------------- | -| 🎨 **双端 UI** | 手机 / 平板自适应布局,沉浸式全屏,状态栏可切换 | -| 📐 **手机端布局** | 全宽贴底底栏 + 圆角浮岛播放栏,底栏选中态滑动指示器;列表 / 卡片 / 设置项轻度紧凑 | -| 🔍 **页面缩放** | 50%–150% 全局缩放,缩小后等效视口变宽,达到阈值可切换 Pad 布局(尚未完全适配) | -| 🎵 **播放引擎** | 原生 ExoPlayer + WebView 双引擎,长时间后台稳定,seek / gapless 切歌进度条同步 | -| 📝 **逐字歌词** | 毫秒级插值高亮,翻译 / 罗马音,拖动吸附最近行,支持全局歌词偏移 | -| 📂 **本地音乐** | 自动扫描本地歌曲,支持 TTML / LRC 歌词匹配(同目录 / 独立歌词目录),与桌面端行为对齐 | -| ☁️ **WebDAV 音乐** | 通过 WebDAV 连接远程音乐,在线播放与浏览,扩展私人曲库 | -| 🪟 **桌面歌词** | `WindowManager` 悬浮窗,逐字动画、锁定穿透、拖拽、播控 | -| 🔔 **通知栏** | 原生 `MediaSession`,完整播控,支持桌面歌词一键开关 | -| 🎚️ **精细控制** | 渐入渐出、进度吸附歌词、允许与其他应用同时播放 | -| 🌐 **在线音乐** | 网易云 + Jellyfin / Navidrome / Emby / Subsonic / OpenSubsonic / Last.fm | -| 🔗 **网络代理** | 支持配置 HTTP/HTTPS 代理,内置逆向 API 请求可走代理访问网易云接口 | -| ⬇️ **音乐下载** | 开发者模式下可下载歌曲至 SAF 授权目录,支持自定义子目录分类、歌词/ASS 附件下载 | -| 🧩 **内置 API** | `nodejs-mobile-cordova` 嵌入网易云 API,离线可用 | -| 📦 **分架构打包** | `arm64-v8a` / `armeabi-v7a` / `x86_64` / `x86` 独立 APK | +| 分类 | 描述 | +| ------------------ | ------------------------------------------------------------------------------------- | +| 🎨 **双端 UI** | 手机 / 平板自适应布局,沉浸式全屏,状态栏可切换 | +| 📐 **手机端布局** | 全宽贴底底栏 + 圆角浮岛播放栏,底栏选中态滑动指示器;列表 / 卡片 / 设置项轻度紧凑 | +| 🔍 **页面缩放** | 50%–150% 全局缩放,缩小后等效视口变宽,达到阈值可切换 Pad 布局(尚未完全适配) | +| 🎵 **播放引擎** | 原生 ExoPlayer + WebView 双引擎,长时间后台稳定,seek / gapless 切歌进度条同步 | +| 📝 **逐字歌词** | 毫秒级插值高亮,翻译 / 罗马音,拖动吸附最近行,支持全局歌词偏移 | +| 📂 **本地音乐** | 自动扫描本地歌曲,支持 TTML / LRC 歌词匹配(同目录 / 独立歌词目录),与桌面端行为对齐 | +| ☁️ **WebDAV 音乐** | 通过 WebDAV 连接远程音乐,在线播放与浏览,扩展私人曲库 | +| 🪟 **桌面歌词** | `WindowManager` 悬浮窗,逐字动画、锁定穿透、拖拽、播控 | +| 🔔 **通知栏** | 原生 `MediaSession`,完整播控,支持桌面歌词一键开关 | +| 🎚️ **精细控制** | 渐入渐出、进度吸附歌词、允许与其他应用同时播放 | +| 🌐 **在线音乐** | 网易云 + Jellyfin / Navidrome / Emby / Subsonic / OpenSubsonic / Last.fm | +| 🔗 **网络代理** | 支持配置 HTTP/HTTPS 代理,内置逆向 API 请求可走代理访问网易云接口 | +| ⬇️ **音乐下载** | 开发者模式下可下载歌曲至 SAF 授权目录,支持自定义子目录分类、歌词/ASS 附件下载 | +| 🧩 **内置 API** | `nodejs-mobile-cordova` 嵌入网易云 API,离线可用 | +| 📦 **分架构打包** | `arm64-v8a` / `armeabi-v7a` / `x86_64` / `x86` 独立 APK | --- diff --git a/src/api/streaming/webdav.ts b/src/api/streaming/webdav.ts index 8d600a09d..91dbdd604 100644 --- a/src/api/streaming/webdav.ts +++ b/src/api/streaming/webdav.ts @@ -60,8 +60,22 @@ const tagCacheKey = (config: StreamingServerConfig, path: string) => `${config.i // ============ 公共辅助 ============ const AUDIO_EXTS = new Set([ - "mp3", "flac", "wav", "ogg", "oga", "m4a", "aac", "ape", "wma", "opus", "alac", - "dsf", "dff", "dsd", "aiff", "aif", + "mp3", + "flac", + "wav", + "ogg", + "oga", + "m4a", + "aac", + "ape", + "wma", + "opus", + "alac", + "dsf", + "dff", + "dsd", + "aiff", + "aif", ]); const isAudioFile = (name: string): boolean => { @@ -233,11 +247,7 @@ const parsePropfindXml = (xml: string, requestedPath: string): WebDavEntry[] => prop.getElementsByTagNameNS(NS, "displayname")[0]?.textContent?.trim() || ""; // 计算文件名:优先 displayname,否则 href 末段 - const fallbackName = decodedHref - .replace(/\/+$/, "") - .split("/") - .filter(Boolean) - .pop() || ""; + const fallbackName = decodedHref.replace(/\/+$/, "").split("/").filter(Boolean).pop() || ""; entries.push({ path: decodedHref, @@ -265,11 +275,7 @@ const fetchTagsForSong = async ( ): Promise => { const cacheKey = tagCacheKey(config, entry.path); const cached = tagCache.get(cacheKey); - if ( - cached && - cached.size === entry.size && - cached.lastModified === entry.lastModified - ) { + if (cached && cached.size === entry.size && cached.lastModified === entry.lastModified) { return cached; } @@ -306,10 +312,7 @@ const fetchTagsForSong = async ( let binary = ""; const chunk = 0x8000; for (let i = 0; i < bytes.length; i += chunk) { - binary += String.fromCharCode.apply( - null, - Array.from(bytes.subarray(i, i + chunk)), - ); + binary += String.fromCharCode.apply(null, Array.from(bytes.subarray(i, i + chunk))); } coverDataUrl = `data:${pic.format};base64,${btoa(binary)}`; } @@ -531,9 +534,7 @@ export const getRandomSongs = async ( }; /** 按标签聚合艺术家 */ -export const getArtists = async ( - config: StreamingServerConfig, -): Promise => { +export const getArtists = async (config: StreamingServerConfig): Promise => { const all = await getOrBuildIndex(config); const map = new Map }>(); for (const song of all) { @@ -566,9 +567,7 @@ export const getArtists = async ( }; /** 按标签聚合专辑 */ -export const getAlbums = async ( - config: StreamingServerConfig, -): Promise => { +export const getAlbums = async (config: StreamingServerConfig): Promise => { const all = await getOrBuildIndex(config); const map = new Map< string, @@ -717,12 +716,13 @@ export const search = async ( typeof s.artists === "string" ? s.artists.toLowerCase() : Array.isArray(s.artists) - ? s.artists.map((a) => a.name).join(" ").toLowerCase() + ? s.artists + .map((a) => a.name) + .join(" ") + .toLowerCase() : ""; const album = - typeof s.album === "string" - ? s.album.toLowerCase() - : (s.album?.name || "").toLowerCase(); + typeof s.album === "string" ? s.album.toLowerCase() : (s.album?.name || "").toLowerCase(); return name.includes(q) || artist.includes(q) || album.includes(q); }); @@ -747,10 +747,8 @@ export const search = async ( }; /** WebDAV 没有歌词接口,留空实现保持接口一致 */ -export const getLyrics = async ( - _config: StreamingServerConfig, - _songId: string, -): Promise => ""; +export const getLyrics = async (_config: StreamingServerConfig, _songId: string): Promise => + ""; export default { ping, diff --git a/src/components/Modal/Setting/StreamingServerConfig.vue b/src/components/Modal/Setting/StreamingServerConfig.vue index 69d12dce0..01adb28c1 100644 --- a/src/components/Modal/Setting/StreamingServerConfig.vue +++ b/src/components/Modal/Setting/StreamingServerConfig.vue @@ -51,7 +51,11 @@ /> - + Digest Auth 当前不支持。建议改用 Basic Auth;如果服务器强制 Digest,请先在服务器侧切换。 @@ -68,11 +72,7 @@