
MusicFree 完整源码解析一首歌的插件化播放旅程与背后的协议原理【免费下载链接】MusicFree插件化、定制化、无广告的免费音乐播放器项目地址: https://gitcode.com/maotoumao/MusicFreeMusicFree 是一款插件化、无广告的免费音乐播放器支持 Android 与 HarmonyOS。它解决的核心问题只有一个播放器本体不内置任何音源搜索、解析、播放地址获取全部由第三方插件按统一协议注入。下面以一首歌的完整播放旅程为主线把官方 README 的用法介绍和 src/core/pluginManager 的源码实现串起来讲。它到底是什么定位与能力速览一句话概括它的设计哲学本软件仅仅是一个播放器本身不集成任何平台的任何音源音源相关的一切逻辑都解耦给插件播放器本体只专注把播这件事做好。这意味着只要某个音源在互联网上能被搜索到并且有人为它写了对应插件你就可以用它完成搜索、播放、歌词、歌单导入。主要特性一览特性说明插件化搜索音乐、专辑、作者、播放、专辑/作者详情、导入单曲/歌单、歌词获取等全部基于插件定制化、无广告浅色/深色模式支持自定义背景基于 AGPL 3.0 开源并保持免费隐私所有数据存储在本地不收集任何个人信息歌词关联把歌曲 A 的歌词关联到 B甚至 A→B→C 形成链条A、B、C 均显示 C 的歌词平台支持Android 与 HarmonyOS桌面版在独立仓库维护从 package.json 可以看到当前版本为0.6.2技术栈是 React Native 0.76.5 React 18.3.1 TypeScript状态管理用 jotai本地存储用 MMKV原生播放走 react-native-track-player。凭什么做到核心设计与关键协议插件的本质一个被沙箱执行的 CommonJS 模块按 README 的定义插件本质上是一个满足插件协议的 CommonJS 模块插件开发者只关心输入输出逻辑分页、缓存全部交给 MusicFree 控制。从源码看加载动作发生在 plugin.ts 的mountPlugin插件源码被包裹进一个Function(...)内执行并注入以下运行时环境注入项内容require/__musicfree_require内置cheerio、crypto-js、axios、dayjs、big-integer、qs、he、webdav等常用库见 plugin.ts插件可直接require而无需自带envgetUserVariables()/userVariables用户在设置页填写的自定义变量、appVersion、os、langprocessplatform、version、envconsole被重定向到应用日志系统devLog便于排查插件问题URL来自 react-native-url-polyfill执行结果取自module.exports.default ?? module.exports即插件只需导出一个对象。插件元信息字段IPluginDefine插件导出对象需满足IPluginDefine接口定义见 plugin.d.ts字段说明platform来源名插件名必填同时作为该插件产出媒体项的归属标识appVersion匹配的应用版本号不满足时插件进入VersionNotMatch错误态version插件版本号用于安装/更新时的版本比较srcUrl远程更新地址供更新插件功能使用primaryKey主键字段列表会存入 media metadefaultSearchType/supportedSearchType默认搜索类型 / 支持的搜索类型cacheControlcache \| no-cache \| no-store插件级缓存策略author/description作者 / 描述支持 MarkdownuserVariables用户自定义输入项形如{ key, name?, hint? }能力方法一览所有能力方法都是可选的插件按需实现播放器通过supportedMethods集合判断某个能力是否存在方法作用关键入参 / 返回search搜索音乐、专辑、作者query、page、type→{ data, isEnd }getMediaSource根据歌曲信息解析真实播放地址musicItem、quality→{ url, headers?, userAgent?, quality? }getMusicInfo按主键查询歌曲信息补全musicBasegetLyric获取歌词musicItem→{ rawLrc, translation? }getAlbumInfo/getMusicSheetInfo专辑 / 歌单信息分页对应 item、pagegetArtistWorks作者作品分页artistItem、page、typeimportMusicSheet/importMusicItem导入歌单 / 单曲形如 URL 的输入串getTopLists/getTopListDetail榜单 / 榜单详情分页后者需topListItem、pagegetRecommendSheetTags/getRecommendSheetsByTag推荐歌单 tag / 按 tag 取歌单后者需tag、pagegetMusicComments评论分页musicItem、page音质由IQualityKey定义low | standard | high | super见 music.d.tsgetMediaSource未指定时默认按standard解析。插件生命周期一个四态状态机每个插件实例都有状态机plugin.tsInitializing初始化中→Loading加载中→Mounted已挂载出错则进入Error错误原因分两类VersionNotMatchappVersion与当前应用版本不满足由satisfies校验触发与CannotParse源码无法解析。插件的唯一 ID 是hash sha256(funcCode)plugin.ts用于去重、卸载、缓存管理——同名但 hash 不同的插件被视为旧版本。跟着一首播放走完全程接下来跟着某首歌走一遍完整链路搜索 → 取播放地址 → 歌词 → 缓存/兜底。第一站搜索——所有插件并行应召你在搜索框输入关键词时useSearch.ts 会调用PluginManager.getSearchablePlugins()拿到所有已启用且实现了search的插件然后对每个插件发起plugin.methods.search(query, page, searchType)。几个值得注意的细节分页完全由播放器控制插件只返回{ data, isEnd }isEnd false且本页有数据时状态置为PARTLY_DONE上拉加载自动请求page 1每首歌都会被resetMediaItem(_, this.plugin.name)打上归属platform标记——这就是之后按平台找插件的依据每个插件的搜索结果按searchType → plugin.hash独立维护一份状态RequestStateCode状态机某个插件失败不影响其他插件的结果展示。第二站取播放地址——getMediaSource 的五级解析点击播放后TrackPlayer会按音质顺序qualityOrder由basic.defaultPlayQuality与basic.playQualityOrder配置决定逐档尝试核心调用是PluginMethodsWrapper.getMediaSource(musicItem, quality)plugin.ts。它按以下顺序解析真实音源本地文件优先歌曲带本地路径且文件存在时直接返回file://地址缓存播放读取MediaCache仅在cacheControl cache或no-cache且当前离线Network.isOffline时直接命中缓存的{ url, headers, userAgent }替代插件若你为插件 A 配置了替代插件 BalternativePlugin见 meta.ts且 B 实现了getMediaSource则用 B 解析 A 的歌插件解析调用getMediaSource(musicItem, quality)失败时以 150ms 间隔重试一次抛NOT RETRY标记的错误则不重试结果缓存除非cacheControl no-store否则把该音质的{ headers, userAgent, url }写回MediaCache的source[quality]。兜底路径同样清晰若插件未实现getMediaSource直接取musicItem.qualities[quality].url ?? musicItem.url若 URL 里内嵌了账号密码formatAuthUrl会自动剥掉并转成Authorization: Basic ...请求头。此外还有两层更外层的兜底trackPlayer按音质从高到低降档重试全部失败且开启basic.tryChangeSourceWhenPlayFail时自动搜索同名同歌手的相似歌曲、换插件再试。URL 是.m3u8后缀时会被标记为hls类型交给播放器。第三站歌词——五级优先级与歌词关联歌词获取在getLyricplugin.ts中按优先级链执行关联歌词读取歌曲扩展属性associatedLrc若存在则改去拉被关联歌曲的歌词——这就是歌词关联特性的实现基础A→B→C 的链条也完全靠这个属性递归传递与音源插件彻底解耦本地手动设置按MD5(platform)/MD5(id).lrc及.tran.lrc翻译文件检查本地歌词目录命中直接返回缓存歌词优先用MediaCache中的远程歌词或本地歌词文件路径插件拉取调用插件getLyric成功后把歌词写入lrcCachePath/{nanoid}.lrc并回写缓存下次免网络本地文件兜底若是本地音乐用内置的localFilePlugin读取内嵌歌词或同名.lrc文件。拿到歌词文本后交给 lrcParser.ts用timeReg /\[[\d:.]\]/g提取时间戳、metaReg提取[offset:...]等元信息并支持叠加用户在播放页设置的偏移量。原文与翻译同时存在时解析器用双指针算法按时间轴把翻译逐句对齐合并到每句歌词上lrcParser.ts渲染层则通过getPosition(position)随播放进度定位当前句。第四站缓存与淘汰——MediaCache 的容量控制MediaCachemediaCache.ts基于 MMKV以platform id生成的唯一键存储每首歌的缓存音源、歌词路径等上限800 条写满时随机删除一半并联动清理被删条目对应的本地歌词文件。配合插件侧的cacheControl形成三级缓存语义cacheControl行为cache正常读写缓存命中即用缓存 URLno-cache默认每次重新解析但离线时允许用缓存兜底no-store解析结果永不写入缓存走完这四级一首歌的旅程就闭环了搜索定位歌是谁家的 →getMediaSource解析从哪播 →getLyric按优先级链拿词从哪来 → 缓存层决定下次还要不要走网络。上手实战安装、配置与日常管理安装入口与三种方式 入口在侧边栏 → 设置 → 插件设置页面实现见 pluginList.tsx支持三种安装方式从本地安装通过系统文件选择器选择.js插件文件支持多选内部调用PluginManager.installPluginFromLocalFile从网络安装输入一个 URL。若以.json结尾先 GET 该文件并解析其中的plugins[].url列表逐个安装否则把输入本身当作插件.js地址{ plugins: [ { version: 1.0.0, url: https://example.com/plugin.js } ] }更新订阅 / 更新全部插件订阅地址来自plugin.subscribeUrl配置单个插件的更新源则是其srcUrl字段updatePlugin内部直接复用installPluginFromUrl。安装过程中的版本处理 安装逻辑集中在 index.ts行为可以照表预期场景结果新插件 hash 已存在静默返回插件已安装同名插件已存在且已装版本更高compare-versions比较version返回已安装更新版本的插件除非开启basic.notCheckPluginVersion安装成功文件以nanoid随机名复制进插件目录旧版本同名片被移除并替换日常管理排序、启停、卸载与懒加载排序setPluginOrder把顺序存入插件 meta 的order字段列表与搜索结果均按该顺序展示启用/禁用setPluginEnabled维护disabledPlugins集合卸载uninstallPlugin(hash)删除插件文件且当无其他同名插件时清理该插件产生的 media extra 数据卸载全部会清空插件目录懒加载开启basic.lazyLoadPlugin后插件元信息名称、hash、支持方法列表缓存在 MMKV启动时不解析源码首次调用能力方法时才由ensureMounted加载执行启动 10 秒后还会异步把剩余插件挂载完成用户变量插件声明userVariables后可在插件设置页填写运行时经env.getUserVariables()读回典型用途是填 API key。如果你是二次开发者可以这样开始git clone https://gitcode.com/maotoumao/MusicFree深挖源码值得借鉴的实现细节沙箱挂载用 Function 构造器造一个够用的运行时mountPlugin不引入完整 Node 运行时而是用Function构造器包一层只注入 8 个参数require、module、exports、console、env、URL、process等和 8 个白名单内置库plugin.ts。这是移动端插件沙箱的一个务实做法能力面小、攻击面小、依赖零体积。console被绑定到devLog后插件作者的调试日志能直接落到应用日志里。hash 去重 同名替换无状态存储下的版本管理插件文件用随机名存储{nanoid}.js身份完全靠sha256(funcCode)判定同 hash 判重、同platform判旧版本替换index.ts。卸载也是按 hash 定位。这意味着即使插件被改名分发只要源码一字不差就会被识别为已安装。缓存容量控制简单粗暴但有效MediaCache的淘汰策略不是 LRU而是写满 800 条就随机删一半并顺带删除关联的本地歌词文件mediaCache.ts。对一个移动端应用来说这种 O(1) 判断 批量清理比精确 LRU 便宜得多代码里也留了 TODO 说明作者对它的定位。双指针翻译合并把两个 LRC 对齐成一份LrcParser 处理原文与翻译时间轴不一致的经典问题两个指针分别扫原文与翻译数组翻译指针只在时间更早时前进命中同时间点就挂上翻译否则置空。没有用二分、没有建索引两句 while 循环解决边界处理翻译比原文短、时间点错位都自然覆盖。边界、合规与避坑下载渠道安装包请以发布页为准若打不开可将 github 域名替换为 gitcode或关注官方渠道获取其他平台的收费版/无广告版/破解版均为假的本来就是开源项目遇到收费版请直接举报。开源协议项目基于AGPL 3.0开源另有三条附加约定——打包/二次分发需保留代码出处、不得用于商业用途、协议变更仅在仓库更新不另行通知。安全提醒使用第三方插件请自行鉴别安全性重点看是否有异常网络请求自己写的插件最安全不要安装来路不明的插件第三方恶意插件造成的损失与本软件无关。版权合规插件使用过程中可能产生与本软件无关的版权数据请及时删除本软件不提倡也不会提供破解行为示例插件仓库基于互联网公开接口封装并过滤掉所有 VIP、试听、付费歌曲后续也不会提供具备破解功能的插件。假冒警示小米/华为/vivo 等应用市场上名为 MusicFree 的 App 与本软件无关是套用名称和 Logo 的广告软件速悦音乐基于本软件二次开发但未遵守开源协议请谨慎对待。成熟度预期README 明示这是业余维护项目、长时间处于不稳定测试版本、更新频率不定请谨慎用于唯一听歌工具常见问题以官方 QA 文档为准新需求走 issue。一句话带走MusicFree 给出的可迁移结论是把内容从哪来和内容怎么消费拆成协议两端——播放器只实现消费侧播放、分页、缓存、歌词渲染、状态机内容侧用一个带沙箱的 CommonJS 协议完全开放。任何需要多音源/多后端/多供应商的客户端产品都可以照这条旅程去设计自己的插件协议定义最小必选字段platform 全可选能力方法 明确的生命周期与缓存语义。【免费下载链接】MusicFree插件化、定制化、无广告的免费音乐播放器项目地址: https://gitcode.com/maotoumao/MusicFree创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考