Local Music - 離線的 iPhone 音樂播放器

era: agent
created_at: 2026/10/12 03:00:00

cover image

一個完全離線的 iPhone 私人音樂播放器,不需要帳號、沒有雲端、沒有追蹤、沒有任何第三方 SDK。

在Github上觀看


為什麼會有這個 App

我的手機是 iPhone 8 Plus,而現在的 iTunes 已經不支援它了,所以沒辦法再把新的音樂同步進去。

其實從以前就覺得用 iTunes 同步這件事很麻煩,只是一直將就著用,這次算是壓倒駱駝的最後一根稻草。

把自己的音樂放進自己的手機然後單純地播放它,這件事困難得沒有道理:不是要註冊帳號、訂閱服務,就是要裝一套桌面同步程式,或是把檔案上傳到別人的伺服器。

剛好 AI 變得這麼厲害,就做了一個都不需要的。


刻意的 Vibe Coding

這個專案和其他的不太一樣,Swift 和 iOS 都不是我熟悉的領域,所以這是一份純 Vibe Coding 的專案:幾乎每一行都是 AI Agent 依照分階段的 prompt 寫出來的,我負責指揮,以及拿著實機測試。

而且我是在 Windows 上開發的:Xcode 專案檔由 XcodeGen 產生,編譯和測試都丟給 CI 上的 macOS runner,產出未簽署的 IPA 再用 AltStore 側載到手機。

在這個專案裡,我把關的方式不是一行一行讀 code,而是測試:每個階段先定好要驗證什麼,做完之後裝到手機上實際操作過才算數。


只靠 CI 不夠

CI 能確保的事情是:程式編譯得過、單元測試驗證的邏輯是對的。

但 CI 的 runner 上只有 iOS 18 以上的模擬器,而我的手機是 iOS 16,版本根本對不上,所以 CI 綠燈沒辦法告訴我「在我的手機上是好的」。

版面就是實際踩到的例子:同一份 build,CI 是綠的,裝到手機上卻有好幾個頁面的清單底部沒有留出空間給迷你播放器,因為 SwiftUI 的某個排版行為在 iOS 16 和新版上並不一樣。

至於背景播放、鎖定畫面控制、Wi-Fi 傳輸這些,模擬器本來就測不出來,一樣只能拿手機試。

所以最後的做法是:

  • 每一項裝置上的行為都在實體 iPhone 8 Plus 上手動驗證
  • 沒有真的測過的項目就標記為 NOT RUN,不會因為 CI 綠了就當成「可以動」
  • prompt、決策紀錄、測試報告全部都放在 repo 裡

功能

  • 從「檔案」、AirDrop 匯入,或是透過 Wi-Fi 傳輸:App 會在區域網路上開一個上傳頁面,從電腦把檔案拖進去就好
  • 背景播放、鎖定畫面控制、睡眠計時器
  • 播放速度與音高獨立調整、10 段等化器
  • 播放清單、本機歌詞(.lrc)
  • 單一檔案的備份與還原,還原前會先預覽

只使用 Apple 官方的框架,沒有任何第三方相依套件。

播放畫面,右下角可以看到目前是 1.5 倍速 Wi-Fi 傳輸:App 開一個區域網路的上傳頁面,掃 QR Code 就能從電腦丟檔案進來

左邊是播放畫面,右邊是 Wi-Fi 傳輸:App 自己當伺服器,網址帶一組一次性的 token,只在這個畫面停留時才收檔案,閒置幾分鐘就自動關掉。

音訊設定:播放速度與音高是兩個獨立的滑桿 10 段等化器的內建預設集

速度和音高是兩個獨立的滑桿:一般播放器把速度調快,聲音的音調也會跟著變高;這裡調速度不會動到音調,想變調再另外調音高。等化器有十組預設集,也可以自己存一組。

睡眠計時器,可選分鐘數或播到這首結束