議原理)
MusicFree 完整源碼解析一首歌的插件化播放旅程與背后的協(xié)議原理【免費(fèi)下載鏈接】MusicFree插件化、定制化、無廣告的免費(fèi)音樂播放器項目地址: https://gitcode.com/maotoumao/MusicFreeMusicFree 是一款插件化、無廣告的免費(fèi)音樂播放器支持 Android 與 HarmonyOS。它解決的核心問題只有一個播放器本體不內(nèi)置任何音源搜索、解析、播放地址獲取全部由第三方插件按統(tǒng)一協(xié)議注入。下面以一首歌的完整播放旅程為主線把官方 README 的用法介紹和 src/core/pluginManager 的源碼實(shí)現(xiàn)串起來講。它到底是什么定位與能力速覽一句話概括它的設(shè)計哲學(xué)本軟件僅僅是一個播放器本身不集成任何平臺的任何音源音源相關(guān)的一切邏輯都解耦給插件播放器本體只專注把播這件事做好。這意味著只要某個音源在互聯(lián)網(wǎng)上能被搜索到并且有人為它寫了對應(yīng)插件你就可以用它完成搜索、播放、歌詞、歌單導(dǎo)入。主要特性一覽特性說明插件化搜索音樂、專輯、作者、播放、專輯/作者詳情、導(dǎo)入單曲/歌單、歌詞獲取等全部基于插件定制化、無廣告淺色/深色模式支持自定義背景基于 AGPL 3.0 開源并保持免費(fèi)隱私所有數(shù)據(jù)存儲在本地不收集任何個人信息歌詞關(guān)聯(lián)把歌曲 A 的歌詞關(guān)聯(lián)到 B甚至 A→B→C 形成鏈條A、B、C 均顯示 C 的歌詞平臺支持Android 與 HarmonyOS桌面版在獨(dú)立倉庫維護(hù)從 package.json 可以看到當(dāng)前版本為0.6.2技術(shù)棧是 React Native 0.76.5 React 18.3.1 TypeScript狀態(tài)管理用 jotai本地存儲用 MMKV原生播放走 react-native-track-player。憑什么做到核心設(shè)計與關(guān)鍵協(xié)議插件的本質(zhì)一個被沙箱執(zhí)行的 CommonJS 模塊按 README 的定義插件本質(zhì)上是一個滿足插件協(xié)議的 CommonJS 模塊插件開發(fā)者只關(guān)心輸入輸出邏輯分頁、緩存全部交給 MusicFree 控制。從源碼看加載動作發(fā)生在 plugin.ts 的mountPlugin插件源碼被包裹進(jìn)一個Function(...)內(nèi)執(zhí)行并注入以下運(yùn)行時環(huán)境注入項內(nèi)容require/__musicfree_require內(nèi)置cheerio、crypto-js、axios、dayjs、big-integer、qs、he、webdav等常用庫見 plugin.ts插件可直接require而無需自帶envgetUserVariables()/userVariables用戶在設(shè)置頁填寫的自定義變量、appVersion、os、langprocessplatform、version、envconsole被重定向到應(yīng)用日志系統(tǒng)devLog便于排查插件問題URL來自 react-native-url-polyfill執(zhí)行結(jié)果取自module.exports.default ?? module.exports即插件只需導(dǎo)出一個對象。插件元信息字段IPluginDefine插件導(dǎo)出對象需滿足IPluginDefine接口定義見 plugin.d.ts字段說明platform來源名插件名必填同時作為該插件產(chǎn)出媒體項的歸屬標(biāo)識appVersion匹配的應(yīng)用版本號不滿足時插件進(jìn)入VersionNotMatch錯誤態(tài)version插件版本號用于安裝/更新時的版本比較srcUrl遠(yuǎn)程更新地址供更新插件功能使用primaryKey主鍵字段列表會存入 media metadefaultSearchType/supportedSearchType默認(rèn)搜索類型 / 支持的搜索類型cacheControlcache \| no-cache \| no-store插件級緩存策略author/description作者 / 描述支持 MarkdownuserVariables用戶自定義輸入項形如{ key, name?, hint? }能力方法一覽所有能力方法都是可選的插件按需實(shí)現(xiàn)播放器通過supportedMethods集合判斷某個能力是否存在方法作用關(guān)鍵入?yún)?/ 返回search搜索音樂、專輯、作者query、page、type→{ data, isEnd }getMediaSource根據(jù)歌曲信息解析真實(shí)播放地址musicItem、quality→{ url, headers?, userAgent?, quality? }getMusicInfo按主鍵查詢歌曲信息補(bǔ)全musicBasegetLyric獲取歌詞musicItem→{ rawLrc, translation? }getAlbumInfo/getMusicSheetInfo專輯 / 歌單信息分頁對應(yīng) item、pagegetArtistWorks作者作品分頁artistItem、page、typeimportMusicSheet/importMusicItem導(dǎo)入歌單 / 單曲形如 URL 的輸入串getTopLists/getTopListDetail榜單 / 榜單詳情分頁后者需topListItem、pagegetRecommendSheetTags/getRecommendSheetsByTag推薦歌單 tag / 按 tag 取歌單后者需tag、pagegetMusicComments評論分頁musicItem、page音質(zhì)由IQualityKey定義low | standard | high | super見 music.d.tsgetMediaSource未指定時默認(rèn)按standard解析。插件生命周期一個四態(tài)狀態(tài)機(jī)每個插件實(shí)例都有狀態(tài)機(jī)plugin.tsInitializing初始化中→Loading加載中→Mounted已掛載出錯則進(jìn)入Error錯誤原因分兩類VersionNotMatchappVersion與當(dāng)前應(yīng)用版本不滿足由satisfies校驗觸發(fā)與CannotParse源碼無法解析。插件的唯一 ID 是hash sha256(funcCode)plugin.ts用于去重、卸載、緩存管理——同名但 hash 不同的插件被視為舊版本。跟著一首播放走完全程接下來跟著某首歌走一遍完整鏈路搜索 → 取播放地址 → 歌詞 → 緩存/兜底。第一站搜索——所有插件并行應(yīng)召你在搜索框輸入關(guān)鍵詞時useSearch.ts 會調(diào)用PluginManager.getSearchablePlugins()拿到所有已啟用且實(shí)現(xiàn)了search的插件然后對每個插件發(fā)起plugin.methods.search(query, page, searchType)。幾個值得注意的細(xì)節(jié)分頁完全由播放器控制插件只返回{ data, isEnd }isEnd false且本頁有數(shù)據(jù)時狀態(tài)置為PARTLY_DONE上拉加載自動請求page 1每首歌都會被resetMediaItem(_, this.plugin.name)打上歸屬platform標(biāo)記——這就是之后按平臺找插件的依據(jù)每個插件的搜索結(jié)果按searchType → plugin.hash獨(dú)立維護(hù)一份狀態(tài)RequestStateCode狀態(tài)機(jī)某個插件失敗不影響其他插件的結(jié)果展示。第二站取播放地址——getMediaSource 的五級解析點(diǎn)擊播放后TrackPlayer會按音質(zhì)順序qualityOrder由basic.defaultPlayQuality與basic.playQualityOrder配置決定逐檔嘗試核心調(diào)用是PluginMethodsWrapper.getMediaSource(musicItem, quality)plugin.ts。它按以下順序解析真實(shí)音源本地文件優(yōu)先歌曲帶本地路徑且文件存在時直接返回file://地址緩存播放讀取MediaCache僅在cacheControl cache或no-cache且當(dāng)前離線Network.isOffline時直接命中緩存的{ url, headers, userAgent }替代插件若你為插件 A 配置了替代插件 BalternativePlugin見 meta.ts且 B 實(shí)現(xiàn)了getMediaSource則用 B 解析 A 的歌插件解析調(diào)用getMediaSource(musicItem, quality)失敗時以 150ms 間隔重試一次拋NOT RETRY標(biāo)記的錯誤則不重試結(jié)果緩存除非cacheControl no-store否則把該音質(zhì)的{ headers, userAgent, url }寫回MediaCache的source[quality]。兜底路徑同樣清晰若插件未實(shí)現(xiàn)getMediaSource直接取musicItem.qualities[quality].url ?? musicItem.url若 URL 里內(nèi)嵌了賬號密碼formatAuthUrl會自動剝掉并轉(zhuǎn)成Authorization: Basic ...請求頭。此外還有兩層更外層的兜底trackPlayer按音質(zhì)從高到低降檔重試全部失敗且開啟basic.tryChangeSourceWhenPlayFail時自動搜索同名同歌手的相似歌曲、換插件再試。URL 是.m3u8后綴時會被標(biāo)記為hls類型交給播放器。第三站歌詞——五級優(yōu)先級與歌詞關(guān)聯(lián)歌詞獲取在getLyricplugin.ts中按優(yōu)先級鏈執(zhí)行關(guān)聯(lián)歌詞讀取歌曲擴(kuò)展屬性associatedLrc若存在則改去拉被關(guān)聯(lián)歌曲的歌詞——這就是歌詞關(guān)聯(lián)特性的實(shí)現(xiàn)基礎(chǔ)A→B→C 的鏈條也完全靠這個屬性遞歸傳遞與音源插件徹底解耦本地手動設(shè)置按MD5(platform)/MD5(id).lrc及.tran.lrc翻譯文件檢查本地歌詞目錄命中直接返回緩存歌詞優(yōu)先用MediaCache中的遠(yuǎn)程歌詞或本地歌詞文件路徑插件拉取調(diào)用插件getLyric成功后把歌詞寫入lrcCachePath/{nanoid}.lrc并回寫緩存下次免網(wǎng)絡(luò)本地文件兜底若是本地音樂用內(nèi)置的localFilePlugin讀取內(nèi)嵌歌詞或同名.lrc文件。拿到歌詞文本后交給 lrcParser.ts用timeReg /\[[\d:.]\]/g提取時間戳、metaReg提取[offset:...]等元信息并支持疊加用戶在播放頁設(shè)置的偏移量。原文與翻譯同時存在時解析器用雙指針?biāo)惴ò磿r間軸把翻譯逐句對齊合并到每句歌詞上lrcParser.ts渲染層則通過getPosition(position)隨播放進(jìn)度定位當(dāng)前句。第四站緩存與淘汰——MediaCache 的容量控制MediaCachemediaCache.ts基于 MMKV以platform id生成的唯一鍵存儲每首歌的緩存音源、歌詞路徑等上限800 條寫滿時隨機(jī)刪除一半并聯(lián)動清理被刪條目對應(yīng)的本地歌詞文件。配合插件側(cè)的cacheControl形成三級緩存語義cacheControl行為cache正常讀寫緩存命中即用緩存 URLno-cache默認(rèn)每次重新解析但離線時允許用緩存兜底no-store解析結(jié)果永不寫入緩存走完這四級一首歌的旅程就閉環(huán)了搜索定位歌是誰家的 →getMediaSource解析從哪播 →getLyric按優(yōu)先級鏈拿詞從哪來 → 緩存層決定下次還要不要走網(wǎng)絡(luò)。上手實(shí)戰(zhàn)安裝、配置與日常管理安裝入口與三種方式 入口在側(cè)邊欄 → 設(shè)置 → 插件設(shè)置頁面實(shí)現(xiàn)見 pluginList.tsx支持三種安裝方式從本地安裝通過系統(tǒng)文件選擇器選擇.js插件文件支持多選內(nèi)部調(diào)用PluginManager.installPluginFromLocalFile從網(wǎng)絡(luò)安裝輸入一個 URL。若以.json結(jié)尾先 GET 該文件并解析其中的plugins[].url列表逐個安裝否則把輸入本身當(dāng)作插件.js地址{ plugins: [ { version: 1.0.0, url: https://example.com/plugin.js } ] }更新訂閱 / 更新全部插件訂閱地址來自plugin.subscribeUrl配置單個插件的更新源則是其srcUrl字段updatePlugin內(nèi)部直接復(fù)用installPluginFromUrl。安裝過程中的版本處理 安裝邏輯集中在 index.ts行為可以照表預(yù)期場景結(jié)果新插件 hash 已存在靜默返回插件已安裝同名插件已存在且已裝版本更高compare-versions比較version返回已安裝更新版本的插件除非開啟basic.notCheckPluginVersion安裝成功文件以nanoid隨機(jī)名復(fù)制進(jìn)插件目錄舊版本同名片被移除并替換日常管理排序、啟停、卸載與懶加載排序setPluginOrder把順序存入插件 meta 的order字段列表與搜索結(jié)果均按該順序展示啟用/禁用setPluginEnabled維護(hù)disabledPlugins集合卸載uninstallPlugin(hash)刪除插件文件且當(dāng)無其他同名插件時清理該插件產(chǎn)生的 media extra 數(shù)據(jù)卸載全部會清空插件目錄懶加載開啟basic.lazyLoadPlugin后插件元信息名稱、hash、支持方法列表緩存在 MMKV啟動時不解析源碼首次調(diào)用能力方法時才由ensureMounted加載執(zhí)行啟動 10 秒后還會異步把剩余插件掛載完成用戶變量插件聲明userVariables后可在插件設(shè)置頁填寫運(yùn)行時經(jīng)env.getUserVariables()讀回典型用途是填 API key。如果你是二次開發(fā)者可以這樣開始git clone https://gitcode.com/maotoumao/MusicFree深挖源碼值得借鑒的實(shí)現(xiàn)細(xì)節(jié)沙箱掛載用 Function 構(gòu)造器造一個夠用的運(yùn)行時mountPlugin不引入完整 Node 運(yùn)行時而是用Function構(gòu)造器包一層只注入 8 個參數(shù)require、module、exports、console、env、URL、process等和 8 個白名單內(nèi)置庫plugin.ts。這是移動端插件沙箱的一個務(wù)實(shí)做法能力面小、攻擊面小、依賴零體積。console被綁定到devLog后插件作者的調(diào)試日志能直接落到應(yīng)用日志里。hash 去重 同名替換無狀態(tài)存儲下的版本管理插件文件用隨機(jī)名存儲{nanoid}.js身份完全靠sha256(funcCode)判定同 hash 判重、同platform判舊版本替換index.ts。卸載也是按 hash 定位。這意味著即使插件被改名分發(fā)只要源碼一字不差就會被識別為已安裝。緩存容量控制簡單粗暴但有效MediaCache的淘汰策略不是 LRU而是寫滿 800 條就隨機(jī)刪一半并順帶刪除關(guān)聯(lián)的本地歌詞文件mediaCache.ts。對一個移動端應(yīng)用來說這種 O(1) 判斷 批量清理比精確 LRU 便宜得多代碼里也留了 TODO 說明作者對它的定位。雙指針翻譯合并把兩個 LRC 對齊成一份LrcParser 處理原文與翻譯時間軸不一致的經(jīng)典問題兩個指針分別掃原文與翻譯數(shù)組翻譯指針只在時間更早時前進(jìn)命中同時間點(diǎn)就掛上翻譯否則置空。沒有用二分、沒有建索引兩句 while 循環(huán)解決邊界處理翻譯比原文短、時間點(diǎn)錯位都自然覆蓋。邊界、合規(guī)與避坑下載渠道安裝包請以發(fā)布頁為準(zhǔn)若打不開可將 github 域名替換為 gitcode或關(guān)注官方渠道獲取其他平臺的收費(fèi)版/無廣告版/破解版均為假的本來就是開源項目遇到收費(fèi)版請直接舉報。開源協(xié)議項目基于AGPL 3.0開源另有三條附加約定——打包/二次分發(fā)需保留代碼出處、不得用于商業(yè)用途、協(xié)議變更僅在倉庫更新不另行通知。安全提醒使用第三方插件請自行鑒別安全性重點(diǎn)看是否有異常網(wǎng)絡(luò)請求自己寫的插件最安全不要安裝來路不明的插件第三方惡意插件造成的損失與本軟件無關(guān)。版權(quán)合規(guī)插件使用過程中可能產(chǎn)生與本軟件無關(guān)的版權(quán)數(shù)據(jù)請及時刪除本軟件不提倡也不會提供破解行為示例插件倉庫基于互聯(lián)網(wǎng)公開接口封裝并過濾掉所有 VIP、試聽、付費(fèi)歌曲后續(xù)也不會提供具備破解功能的插件。假冒警示小米/華為/vivo 等應(yīng)用市場上名為 MusicFree 的 App 與本軟件無關(guān)是套用名稱和 Logo 的廣告軟件速悅音樂基于本軟件二次開發(fā)但未遵守開源協(xié)議請謹(jǐn)慎對待。成熟度預(yù)期README 明示這是業(yè)余維護(hù)項目、長時間處于不穩(wěn)定測試版本、更新頻率不定請謹(jǐn)慎用于唯一聽歌工具常見問題以官方 QA 文檔為準(zhǔn)新需求走 issue。一句話帶走M(jìn)usicFree 給出的可遷移結(jié)論是把內(nèi)容從哪來和內(nèi)容怎么消費(fèi)拆成協(xié)議兩端——播放器只實(shí)現(xiàn)消費(fèi)側(cè)播放、分頁、緩存、歌詞渲染、狀態(tài)機(jī)內(nèi)容側(cè)用一個帶沙箱的 CommonJS 協(xié)議完全開放。任何需要多音源/多后端/多供應(yīng)商的客戶端產(chǎn)品都可以照這條旅程去設(shè)計自己的插件協(xié)議定義最小必選字段platform 全可選能力方法 明確的生命周期與緩存語義?!久赓M(fèi)下載鏈接】MusicFree插件化、定制化、無廣告的免費(fèi)音樂播放器項目地址: https://gitcode.com/maotoumao/MusicFree創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考