MusicFree 源码级拆解:插件化音乐播放器如何用一份 JS 文件承载全部音源 MusicFree 源码级拆解插件化音乐播放器如何用一份 JS 文件承载全部音源【免费下载链接】MusicFree插件化、定制化、无广告的免费音乐播放器项目地址: https://gitcode.com/maotoumao/MusicFreeMusicFree 是一款插件化的免费音乐播放器播放器本体不带任何音源搜索、播放、歌词全部由 JS 插件按统一协议注入。当前版本 0.6.2见 package.json本文从src/core/pluginManager的源码切入讲清插件如何被挂载、播放地址如何兜底、歌词如何按优先级回退。架构鸟瞰一个只做播放器的壳整个工程可以拆成播放器核心 插件运行时 页面层三块。关键点在于core层不直接访问任何第三方音源 API所有与音源相关的请求都必须经过PluginMethodsWrapper这个包装层插件只是其中可替换的一环。模块位置职责插件管理器src/core/pluginManager/index.ts插件的安装、卸载、排序、启用、懒加载缓存插件实例src/core/pluginManager/plugin.ts挂载插件代码、计算 hash、包装并兜底每个能力方法插件元信息src/core/pluginManager/meta.ts排序、禁用名单、替代插件、用户变量存于 MMKV播放队列src/core/trackPlayer/index.ts队列管理、音源获取失败时的跨源切换媒体缓存src/core/mediaCache.ts按platformid缓存音源与歌词路径上限 800 条歌词管理src/core/lyricManager.ts歌词刷新、关联歌词、本地歌词读写页面层src/pages/搜索、歌单、设置、插件管理界面只消费上面几层暴露的接口核心链路拆解插件加载一个 .js 文件如何变成可调用的能力集触发点是 App 启动时PluginManager.setup()index.ts读取插件目录路径定义在 pathConst.ts 的pluginPath下的所有.js文件。关键步骤逐个readFile拿到源码字符串new Plugin(funcCode, path)mountPlugin用Function构造器把源码包进一个 CommonJS 风格的闭包执行plugin.ts注入require、module、console、env、URL、process等参数_require只放行内置包cheerio、crypto-js、axios、dayjs、big-integer、qs、he、webdavplugin.ts插件无需自带依赖checkValid用compare-versions的satisfies校验appVersion字段不匹配则抛VersionNotMatchplugin.ts校验通过后hash sha256(funcCode)状态置为Mountedhash 即插件唯一 IDplugin.ts。边界与兜底解析失败进Error态原因分VersionNotMatch和CannotParseplugin.ts开启basic.lazyLoadPlugin后启动不解析源码元信息写进 MMKV 的plugin.cache首次调用能力方法时才由ensureMounted真正挂载index.ts、plugin.ts另有 10 秒后的异步补挂载兜底index.ts。播放链路一首歌从点击到出声的音源解析触发点是TrackPlayer.play()trackPlayer/index.ts。它先按basic.playQualityOrder生成音质序列再对每档音质调用插件的getMediaSource拿到源后交给react-native-track-player。真正的解析逻辑在PluginMethodsWrapper.getMediaSourceplugin.ts按顺序过五道关卡本地文件媒体项带localPath且文件存在时直接返回file://地址跳过网络plugin.ts缓存命中查MediaCache仅当插件声明cacheControl cache或no-cache且当前离线Network.isOffline时才用缓存源plugin.ts替代插件meta 里给某插件配置了alternativePlugin时改由替代插件解析plugin.ts配置存取见 meta.ts插件解析未实现getMediaSource的插件直接回退到musicItem.qualities[quality].url ?? musicItem.urlplugin.ts实现了的则调用它URL 里若内嵌账号密码会被formatAuthUrl转成Authorization: Basic ...请求头plugin.ts写回缓存除no-store外把该音质的{headers, userAgent, url}写入MediaCache.source[quality]plugin.ts。边界与兜底插件调用抛错时按 150ms 间隔重试一次消息为NOT RETRY则不重试plugin.ts若所有音质都拿不到源且开启了basic.tryChangeSourceWhenPlayFailgetSimilarMusic会用minDistance在其他插件里搜同名歌曲换源搜索总预算 8 秒trackPlayer/index.ts仍失败则触发handlePlayFail500ms 后自动切下一首trackPlayer/index.ts。.m3u8结尾的源会被打上type: hls标记交给底层播放器trackPlayer/index.ts。歌词链路关联、本地、缓存、插件四级回退触发点是切歌事件CurrentMusicChangedLyricManager.setup里监听它并调用refreshLyriclyricManager.ts。取词主体在PluginMethodsWrapper.getLyricplugin.ts优先级从高到低关联歌词读媒体扩展属性associatedLrc若存在则换成被关联歌曲去取词这就是 README 里 A→B→C 歌词链的实现关联动作本身在associateLyric里patchMediaExtralyricManager.ts本地手动歌词按MD5(platform)/MD5(id).lrc与.tran.lrc翻译查文件路径常量见 pathConst.ts缓存MediaCache里的远程歌词文本或已落地的本地歌词文件路径插件拉取调插件getLyric成功后把原文/翻译各写一个nanoid命名的文件到lrcCachePath并把文件路径回写进缓存plugin.ts本地文件走内置localFilePlugin先读 MP3 内嵌歌词再读同名.lrcplugin.ts。渲染侧由 lrcParser.ts 负责[xx:xx.xx]时间戳与[offset:...]元信息分别用timeReg、metaReg提取lrcParser.tsgetPosition用lastSearchIndex做双向线性查找定位当前句lrcParser.ts原文与翻译按双指针在相同时间轴上对齐合并lrcParser.ts。契约与数据IPluginDefine 字段速查插件导出对象必须满足IPluginDefinesrc/types/plugin.d.ts。元信息字段决定插件如何被识别和管控能力方法全部可选未实现时包装层会给出空结果兜底。字段类型/取值行为说明platformstring来源名必填媒体项靠它反查插件缺失则 hash 为空、进入 ErrorappVersionstring应用版本范围satisfies不满足时报VersionNotMatchversionstring安装时用compare-versions与已装同名插件比大小旧版被拒srcUrlstring远程更新地址更新全部插件据此拉新index.tsprimaryKeystring[]主键字段随 media meta 存储供getMusicInfo回查cacheControlcache/no-cache/no-store分别控制缓存读取、仅离线读、不写缓存常量见 commonConst.tsuserVariables{key, name?, hint?}[]声明用户在设置页要填的变量运行时经env.getUserVariables()读回search/getMediaSource/getLyric方法搜索、取播放地址默认standard音质、取歌词签名见 plugin.d.tsgetAlbumInfo/getMusicSheetInfo/getArtistWorks方法专辑、歌单、作者作品均为分页接口返回isEndimportMusicSheet/importMusicItem方法导入歌单 / 单曲入参是形如 URL 的字符串getTopLists/getTopListDetail/getRecommendSheetTags/getRecommendSheetsByTag/getMusicComments方法榜单、推荐歌单、评论均为可选能力音质键值为IQualityKey low | standard | high | supersrc/types/music.d.ts缓存键统一为platformidmediaUtils.ts。快速上手从 clone 到装好第一个插件克隆仓库git clone https://gitcode.com/maotoumao/MusicFree预期得到含android/、src/、package.json的目录安装依赖Node 需 18package.jsonengines执行yarn install预期无报错生成node_modules启动 Metroyarn start预期看到Waiting on 0.0.0.0:8081编译 Android另开终端执行yarn android等价于react-native run-android预期模拟器或真机上打开 App安装插件侧边栏「设置 → 插件设置」点右下角选「从本地安装插件」挑.js文件或选「从网络安装插件」填一个 URL预期出现插件安装成功提示列表里出现新插件入口代码见 pluginList.tsx验证在搜索页输入关键词预期新插件出现在参与搜索的插件列表里结果带有该插件名标记。网络安装时若 URL 以.json结尾会先解析其中的plugins[].url数组逐个安装否则把输入本身当插件地址pluginList.tsx。设计与坑源码里能读到的四个决策hash 而不是版本号做身份。插件 ID 是sha256(funcCode)plugin.ts安装去重、卸载、替换旧版本全部按 hash 匹配version只用于拒绝装旧版判断index.ts。坑同一插件只要改一个字符 hash 就变旧文件被删后缓存也随之清理index.ts依赖 hash 做跨设备识别不成立。能力方法全部可选包装层负责空态。PluginMethodsWrapper里每个方法开头都有ensurePluginIsMounted 未实现判断如 plugin.ts插件只需实现自己支持的部分。坑supportedMethods是挂载后按属性是函数统计的plugin.ts所以懒加载插件在真正挂载前其supportedMethods来自 MMKV 缓存的快照若缓存丢失能力列表会缺项。缓存三态而非布尔。cache/no-cache/no-store把能否读旧缓存和要不要写新缓存拆开了离线时no-cache插件也能播放缓存源plugin.ts。坑缓存条目上限 800满了直接按 key 顺序删一半mediaCache.ts不是 LRU高频播放的插件条目也可能被先删。播放队列里塞假 track 处理循环。setQueue永远只放两首真实歌曲 一条musicfree://fake-audio的假下一曲trackPlayer/index.ts 与 L879-L903监听PlaybackActiveTrackChanged切到假 track 时再真正加载下一首。这样单曲循环/列表循环的控制权留在 JS 层但代价是队列长度在底层永远为 2任何直接读getActiveTrack判断下一曲的逻辑都要防住假 track。延伸阅读MusicFree 的核心不在某个页面或组件而在pluginManager这套协议 包装层 兜底链插件只承诺输入输出分页、缓存、重试、换源、歌词回退全由宿主收口这也是它能把音源相关风险隔离在插件边界之外的原因。进一步阅读可以从 src/core/pluginManager/ 三个文件开始再顺着getMediaSource跳到 src/core/trackPlayer/index.ts 看队列侧如何消费结果。项目基于 AGPL 3.0 开源使用约定与安全提示含第三方插件需自行鉴别、应用市场同名 App 非本软件见 readme.md版本变更记录见 changelog.md新需求可提 issue 讨论。【免费下载链接】MusicFree插件化、定制化、无广告的免费音乐播放器项目地址: https://gitcode.com/maotoumao/MusicFree创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考