Skip to content

Latest commit

 

History

3,506 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Patagonia Pulse

macOS Swift Version License

Patagonia Pulse 是一个 local-first 的 macOS 本地音乐播放器。它直接读取用户选择的本地音乐文件夹,提取元数据并使用 AVFoundation 播放;不需要账号、服务器、网络服务或 Apple Developer 付费订阅。

Patagonia Pulse is a local-first macOS music player. It scans user-selected local files, reads metadata with AVFoundation, and plays audio locally without accounts, servers, network services, or a paid Apple Developer subscription.

项目定位

这个项目的核心目标不是做一个在线音乐平台,而是把本地音乐库的几个基本动作做好:

  • 选择一个音乐目录并递归扫描;
  • 稳定读取标题、艺术家、专辑、时长和封面;
  • 在歌曲、艺术家、专辑、收藏和最近播放之间快速浏览;
  • 播放、暂停、切歌、循环、随机和拖动进度;
  • 在本机保存收藏、播放列表、最近播放和文件访问权限。

应用不会上传、复制或移动用户的音频文件。当前没有 CloudKit、iCloud、APNs、账号同步、在线流媒体或第三方数据服务。

当前能力(Version 1.16)

领域 当前支持
播放 播放/暂停、停止、上一首、下一首、进度拖动、音量、Up Next 队列
播放模式 关闭循环、列表循环、单曲循环、随机播放
音乐库 选择文件夹、递归扫描、容错读取、重新扫描、SQLite 增量元数据索引、分页查询
音乐来源 同时管理多个本地音乐文件夹,可在侧栏移除来源
文件状态 保留丢失文件记录,显示旧路径,并通过添加定位文件夹自动尝试恢复稳定 ID
拖拽导入 拖入文件夹会替换当前库;拖入音频文件会追加到当前库
元数据 标题、艺术家、专辑、时长、封面;缺失字段使用 fallback 文本
浏览 歌曲、艺术家、专辑、最近播放、收藏
推荐 本地“为你推荐”:播放行为、收藏/播放列表、艺术家/专辑相似度、连续播放关系、多样性重排和“少推荐”反馈
搜索 按歌曲名、艺术家或专辑实时过滤
收藏 星标收藏,使用 UserDefaults 本地保存
播放列表 创建、重命名、删除、添加/移除歌曲、导入 M3U/M3U8、导出 M3U8/JSON、导入 JSON
智能播放列表 最近常听、播放最多、从未播放、重新发现、收藏;支持创建、重命名和删除规则
播放队列 查看接下来播放、下一首播放、加入队尾、立即播放、移除、清空、拖拽排序
访问权限 文件夹和拖入文件使用 security-scoped bookmark 记住本地访问权限
大库优化 封面按需加载、512px 缩略图、有上限的 NSCache、每 64 首批量更新扫描结果、不变文件复用元数据
快捷键 ⌘O 打开文件夹、⇧⌘R 重新扫描、空格播放/暂停、左右方向键切歌

支持的扩展名

扫描器目前识别以下扩展名:

aacaifaiffalaccafflacm4am4bmp3mp4wav

扩展名被识别不代表所有编码都一定能播放,最终能力取决于当前 macOS 的 AVFoundation/AVAudioPlayer 支持。如果文件损坏或编码不兼容,应用会保留 fallback 曲目信息,并在侧栏统计元数据读取失败数量。

快速开始

从源码构建(推荐)

前置条件:

  • macOS 14.0 或更高版本;
  • Xcode,建议使用支持 macOS 14 SDK 的版本;
  • 不需要 Apple Developer 付费订阅。

打开项目:

git clone https://github.com/TheRealMilesLee/MusicPlayer-macOS.git
cd MusicPlayer-macOS
open MusicPlayer/PatagoniaPulse.xcodeproj

在 Xcode 中选择 MusicPlayer scheme 和 My Mac,然后运行。scheme 名称是 MusicPlayer,生成的 app 名称是 PatagoniaPulse.app

命令行 Debug 构建:

xcodebuild \
  -project MusicPlayer/PatagoniaPulse.xcodeproj \
  -scheme MusicPlayer \
  -configuration Debug \
  -destination 'platform=macOS' \
  build

项目默认使用本地 ad-hoc 签名。如果只想验证编译、不需要运行签名后的 app,可以追加:

CODE_SIGNING_ALLOWED=NO

例如:

xcodebuild \
  -project MusicPlayer/PatagoniaPulse.xcodeproj \
  -scheme MusicPlayer \
  -configuration Debug \
  -destination 'platform=macOS' \
  CODE_SIGNING_ALLOWED=NO \
  build

Release 构建示例:

xcodebuild \
  -project MusicPlayer/PatagoniaPulse.xcodeproj \
  -scheme MusicPlayer \
  -configuration Release \
  -destination 'platform=macOS' \
  -derivedDataPath /tmp/patagonia-pulse-release-derived \
  build

生成的 app 位于:

/tmp/patagonia-pulse-release-derived/Build/Products/Release/PatagoniaPulse.app

Apple Developer 订阅边界

本项目当前只依赖本地构建和 ad-hoc 签名,因此个人开发和本机运行不需要付费订阅。没有付费订阅时,以下事情不在项目能力范围内:

  • App Store 发布;
  • Developer ID 分发和正式 notarization 流程;
  • CloudKit、iCloud 容器和 APNs 等开发者服务;
  • 通过 Apple 的分发渠道向其他用户提供经过正式签名的安装包。

这不影响本地编译、播放、文件访问 bookmark、UserDefaults 持久化或本地播放列表。

使用说明

导入音乐

有两种导入方式:

  1. 点击工具栏或菜单中的“打开音乐文件夹”,选择一个目录。应用会递归扫描该目录中的支持格式。
  2. 从 Finder 拖拽文件或文件夹到应用窗口。

拖拽行为是刻意区分的:

  • 拖入文件夹:替换当前音乐库,并记住新的文件夹 bookmark;
  • 拖入一个或多个音频文件:追加到当前音乐库,并记住文件级 bookmark;
  • 点击“添加音乐文件夹”:追加一个音乐根目录,不清空现有来源和单文件导入;
  • 在侧栏“音乐来源”中移除文件夹:只从应用索引移除该来源,不删除原始音乐文件;
  • 重新扫描当前文件夹:文件夹内的歌曲重新读取,之前追加的单文件会尝试保留;
  • 重新选择或拖入新的文件夹:切换到新文件夹,并清除之前的单文件导入记录。

应用不会复制音频文件。播放列表保存的是稳定曲目 ID,不是音频副本;M3U/M3U8 交换格式仍然保存当前文件路径。

曲目 ID 现在由本地 SQLite 索引维护:旧版本记录会把原来的标准化路径迁移为稳定 ID,新扫描的歌曲使用本地稳定标识。文件移动或重命名后,重新扫描会优先使用 macOS 文件资源标识;无法取得资源标识时,只有文件大小、标题、艺术家、专辑和时长都匹配且候选唯一,才会自动恢复原有收藏、最近播放和播放列表关联。

丢失文件和重新定位

如果当前来源中的文件被删除、移动或暂时不可访问,重新扫描不会立即删除 SQLite 记录,而是把它们放到侧栏的“丢失文件(N)”页面。页面保留标题、旧路径和基础元数据,方便确认发生了什么。

要重新定位文件:

  1. 打开“丢失文件”页面,确认旧路径和歌曲信息;
  2. 点击“添加定位文件夹”,选择包含已移动歌曲的新文件夹;
  3. 应用扫描新文件夹,并按文件资源标识或唯一元数据候选恢复稳定 ID;
  4. 恢复成功后,收藏、最近播放、普通播放列表和推荐行为会继续关联到新路径。

多个候选无法区分时,应用会保留为丢失状态,不会静默把收藏关联到错误文件。移除来源、用“打开音乐文件夹”替换来源集合,或文件夹 bookmark 解析成全新的根路径时,旧来源记录可能被清理,当前不保证整个文件夹搬家后的自动恢复。

播放和浏览

  • 双击歌曲播放;
  • 播放栏提供上一首、播放/暂停、下一首、循环、随机、停止、音量和进度控制;
  • “艺术家”和“专辑”视图用于分类浏览;
  • “最近播放”最多保留本机最近播放记录的前 100 首;
  • “收藏”通过歌曲行末的星标或右键菜单操作;
  • 在歌曲上右键可以添加到已有播放列表;
  • 播放列表支持创建、重命名、删除和移除歌曲。

在播放列表页面右上角的“播放列表文件”菜单中,可以导出当前列表为 M3U8 或 Patagonia Pulse JSON,也可以导入 M3U、M3U8 或 JSON 文件。M3U/M3U8 主要用于和其他播放器交换;JSON 会额外保存播放列表 UUID、曲目顺序和基础元数据。导入时找不到的文件会在结果提示中列出数量,不会静默删除原始文件。

搜索框会根据当前视图过滤歌曲名、艺术家和专辑。歌曲、收藏、艺术家和专辑主视图使用 SQLite 分页查询,每页最多加载 200 首;播放队列仍然来自触发播放时所在的当前曲目集合。

“为你推荐”中的歌曲可以通过右键菜单选择“少推荐这首歌”或“少推荐这个艺术家”。这只是软惩罚,不会永久隐藏歌曲;再次选择“恢复推荐”即可撤销。智能播放列表保存的是规则,不是歌曲副本,会随着本机播放记录和收藏变化自动更新。

数据、权限与隐私

Patagonia Pulse 是本地应用,不主动发起网络请求。当前本地保存的数据包括:

数据 存储方式 用途
音乐文件夹 bookmarks UserDefaults 中的 PatagoniaPulse.libraryFolderBookmarks 重启后恢复多个文件夹来源访问
拖入文件 bookmark UserDefaults 重启后恢复单文件访问
最近播放曲目 ID UserDefaults 恢复最近播放列表
收藏曲目 ID UserDefaults 恢复收藏
播放列表及曲目 ID JSON 编码后写入 UserDefaults 恢复本地播放列表
本地播放行为 UserDefaults 中的 PatagoniaPulse.listeningHistory.v1 保存播放次数、完成/跳过/中断、最近播放时间和短期连续播放关系
推荐反馈 UserDefaults 中的 PatagoniaPulse.recommendationFeedback.v1 保存歌曲和艺术家的本地“少推荐”软惩罚
智能播放列表 JSON 编码后写入 UserDefaults 保存规则、名称和结果数量限制,打开页面时动态计算
曲目元数据索引 应用 Application Support 目录中的 Library.sqlite 保存 stable_id、当前路径、文件资源标识、is_missing 和元数据,用于复用、查询和移动后重新定位
封面缩略图 进程内 NSCache 只为当前界面显示,受大小上限约束,不持久化

项目启用了 App Sandbox,并配置了只读的用户选择文件、音乐、下载、影片和图片目录访问。音频文件不会被修改。选择新的文件夹时,应用也不会删除旧文件,只会切换当前索引和本地访问记录。

性能模型

大音乐库最容易出现的问题不是音频文件大小本身,而是元数据、封面和 UI 状态被无界地留在内存中。当前实现采取了这些措施:

  • 扫描阶段不再把原始封面二进制放进 MusicTrack
  • 封面只在视图需要时读取,并先转换为最大 512px 的缩略图;
  • 图片缓存最多 64 张、成本上限 64MB,并允许系统在内存压力下回收;
  • 扫描结果每 64 首批量发布一次,减少 SwiftUI 重算;
  • 扫描前比较标准化路径、文件大小和修改时间,未变化文件直接复用本地 SQLite 索引;
  • 文件夹扫描使用事务更新索引,成功后把当前根目录未命中的记录标记为 is_missing = 1,中断时回滚索引更新;
  • 丢失记录不会参与普通歌曲、收藏、艺术家和专辑查询,但会保留在“丢失文件”页面等待重新定位;
  • 新路径命中唯一候选后会复用 stable_id 并清理旧路径记录,候选不唯一时不会自动猜测;
  • 单个损坏文件不会中断整次扫描。

主歌曲库、收藏、艺术家和专辑页面已经改为直接查询 SQLite,并使用固定大小的分页结果,避免把完整结果集交给 SwiftUI 表格。扫描和推荐目前仍保留一份轻量的 MusicTrack 元数据快照,播放队列、最近播放、普通播放列表和智能列表继续使用这份快照;后续可以再把这些次级场景改为按需查询。

项目结构

MusicPlayer/
├── PatagoniaPulse.xcodeproj/
│   └── xcshareddata/xcschemes/MusicPlayer.xcscheme
└── PatagoniaPulse/
    ├── PatagoniaPulseApp.swift              # App 入口、菜单和快捷键
    ├── PatagoniaPulse.entitlements          # App Sandbox 入口
    ├── ModelAndFunctions/
    │   ├── MusicLibrary.swift                # 扫描、权限、来源、持久化、筛选
    │   ├── LibraryIndexStore.swift           # SQLite 索引、搜索、分页和增量扫描缓存
    │   ├── MusicTrack.swift                  # 曲目模型、封面缓存和时长格式化
    │   ├── MusicViewModel.swift              # 播放模式、侧栏目的地、播放列表模型
    │   ├── AudioPlayManager.swift            # AVAudioPlayer 播放队列和状态
    │   ├── FileLoadingManager.swift          # NSOpenPanel/NSSavePanel 文件选择
    │   ├── PlaylistFileCodec.swift           # M3U8/JSON 播放列表编解码
    │   ├── ListeningHistory.swift             # 本地播放事件、行为统计和推荐反馈
    │   ├── RecommendationEngine.swift        # 混合打分和多样性重排
    │   └── DoubleClick.swift                 # SwiftUI 双击手势辅助
    └── View/
        ├── ContentView.swift                 # 主布局、侧栏、播放栏、封面
        ├── LocalPlaylistsView.swift          # 歌曲表格和本地播放列表
        ├── QueueView.swift                   # Up Next 播放队列
        ├── RecommendationView.swift          # 本地推荐列表
        ├── ArtistView.swift                  # 艺术家浏览
        ├── AlbumView.swift                   # 专辑浏览
        ├── RecentView.swift                  # 最近播放
        └── MissingTracksView.swift           # 丢失文件和重新定位入口

更完整的架构说明见 ARCHITECTURE.md

验证与测试状态

当前仓库没有 XCTest target,也没有 CI workflow。每次涉及核心代码的修改至少应执行:

git diff --check

xcodebuild \
  -project MusicPlayer/PatagoniaPulse.xcodeproj \
  -scheme MusicPlayer \
  -configuration Debug \
  -destination 'platform=macOS' \
  build

核心模型和播放器也可以用 swiftc -typecheck 做独立检查。手动验证清单见 DEVELOPMENT.md

稳定身份和 SQLite 迁移可以在没有完整 Xcode 的环境中运行回归 harness:

zsh scripts/run-library-index-harness.sh

该 harness 使用临时 SQLite 数据库,不读取或修改真实音乐库。它覆盖旧库迁移、丢失记录、跨路径稳定 ID、资源标识优先、非唯一候选拒绝匹配和普通查询过滤。

如果机器只有 /Library/Developer/CommandLineTools 而没有完整的 Xcode.app,xcodebuild 无法完成 macOS app 构建;这不影响上述核心模型检查和 SQLite harness。要验证 SwiftUI 界面及真实 app 运行,仍需安装完整 Xcode,但不需要加入付费 Apple Developer 计划。

当前已验证的构建配置:macOS deployment target 14.0、Swift 5 language mode、MusicPlayer scheme、Apple Silicon Debug 构建、ad-hoc 签名。

已知限制

  • 多音乐文件夹来源已经支持,丢失文件页面和基于稳定 ID 的重新定位已经支持,但暂时没有独立的“离线来源”状态或重新授权流程;
  • 主歌曲库、收藏、艺术家和专辑页面已经使用 SQLite 分页查询;最近播放、普通播放列表和智能列表仍是进程内动态结果;
  • 播放列表可以导入/导出 M3U、M3U8 和 JSON,但还不能在 UI 中拖拽排序;
  • 推荐仍只使用本地播放行为、收藏/播放列表、艺术家和专辑等信号,尚未使用音频指纹、歌词或在线画像;“少推荐”反馈目前是有上限的软惩罚,不是永久屏蔽;
  • 旧版本路径 ID 会在 SQLite 迁移时保留为稳定 ID;新路径只有在资源标识匹配或元数据候选唯一时才会自动恢复,内容改变、候选重复或来源集合被替换时可能仍需人工处理;
  • 当前没有独立的 source bookmark 稳定身份;整个音乐文件夹搬家后,如果 bookmark 解析为新的根路径,旧来源记录可能已被清理;
  • 不会自动监视 Finder 中的文件变化,需要手动重新扫描;
  • 没有内置歌词、均衡器、淡入淡出、无缝播放或系统 Now Playing 集成;
  • 没有在线搜索、流媒体、账号同步、Last.fm scrobbling 或云端备份;
  • 仓库根目录的 Patagonia Pulse.dmg 是历史遗留文件,当前不能作为有效发布包验证。请优先从源码构建;
  • 当前没有正式签名、notarization 或 App Store 发布流程。

文档索引

更新日志

1.16

  • 为曲目增加稳定 ID、文件资源标识和 is_missing 状态;旧版 SQLite 记录会自动迁移;
  • 重新扫描时保留未命中的记录,新增“丢失文件”页面展示旧路径和元数据;
  • 添加定位文件夹后,优先按文件资源标识、其次按唯一元数据候选恢复收藏、最近播放和播放列表关联;
  • 非唯一候选不会自动猜测,避免把本地行为关联到错误文件;
  • 增加可在无完整 Xcode 环境下运行的 SQLite 迁移与重定位回归 harness。

1.15

  • 增加 SQLite 查询驱动的歌曲、收藏、艺术家和专辑浏览;
  • 支持固定大小分页、关键词搜索、排序和数据库聚合,降低大曲库 UI 内存压力;
  • 增加歌曲/艺术家级别的“少推荐”和“恢复推荐”反馈,反馈只保存在本机并使用软惩罚;
  • 增加可创建、重命名、删除的智能播放列表,支持最近常听、播放最多、从未播放、重新发现和收藏规则;

1.14

  • 增加“为你推荐”入口;
  • 记录开始、完成、跳过和中断等本地播放事件;
  • 使用行为、内容、连续播放关系、显式偏好和探索位混合打分,并通过艺术家/专辑多样性重排;
  • 推荐数据只写入本机,不需要账号、网络或 Apple Developer 付费服务。

1.13

  • 支持从 M3U/M3U8 导入播放列表;
  • 支持导出 M3U8 和 Patagonia Pulse JSON 播放列表;
  • 导入时报告缺失文件和不支持格式,不静默吞掉整张播放列表。

1.12

  • 支持多个本地音乐文件夹来源;
  • 使用 bookmark 数组恢复多个来源,并兼容旧版本的单文件夹 bookmark;
  • 支持在侧栏移除来源,且不会删除用户原始音乐文件。

1.11

  • 增加可见的 Up Next 播放队列;
  • 支持下一首播放、加入队尾、立即播放、移除、清空和拖拽排序;
  • 随机模式只把队列作为候选池,不伪装成固定的下一首顺序。

1.10

  • 增加本地 SQLite 曲目元数据索引;
  • 通过文件大小和修改时间跳过未变化文件的重复元数据读取;
  • 文件夹扫描使用事务更新索引,支持成功清理失效记录和中断回滚。

1.9

  • 修复大音乐库扫描时封面原始数据无界驻留的问题;
  • 增加按需封面加载、512px 缩略图和有上限的 NSCache
  • 扫描结果改为批量发布,降低 SwiftUI 重算和内存峰值;
  • 使用现代 NSItemProvider API 读取 Finder 拖拽 URL,避免新 SDK 弃用警告;
  • 完整同步当前构建、权限、持久化和 Apple Developer 订阅边界文档。

1.8

  • 修正拖入单文件会替换整个音乐库的问题,改为追加导入;
  • 增加单文件 security-scoped bookmark;
  • 清理 macOS 14 的 onChange API 警告。

1.7

  • 增加收藏和本地播放列表;
  • 增加 Finder 拖拽导入;
  • 播放列表、收藏和最近播放使用本地持久化。

1.6

  • 重整音乐库和播放器核心;
  • 递归扫描音频文件并记住上次选择的文件夹;
  • 增加艺术家、专辑和最近播放浏览;
  • 修复时间显示、无封面崩溃、路径错位和失焦退出。

许可证

本项目使用 MIT License,详见 LICENSE

About

This is a music player application that running on macOS

Resources

Stars

4 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages