news 2026/9/14 18:55:36

微信小程序音乐播放器源码解析:从页面架构到后台播放

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
微信小程序音乐播放器源码解析:从页面架构到后台播放

简介:基于微信小程序的音乐播放器源码是一份完整的小程序实战项目,面向零基础及有经验的开发者,既可用于学习,也可作为快速搭建音乐播放功能的框架。项目覆盖小程序架构的核心环节,包括WXML/WXSS页面结构设计、JavaScript业务逻辑、网络请求与本地存储、后台音频播放及状态监听等API的综合应用。压缩包共123个文件,以34个JavaScript、26个JSON、18个WXML、19个WXSS文件为主,分别承担逻辑处理、配置管理、页面结构与样式展示,另含PNG图片、WXS脚本等资源,整体体积仅157KB,模块划分清晰,便于按需阅读。当前已有90人浏览学习。通过研读源码,可掌握页面路由跳转、数据绑定、列表渲染、自定义组件及音乐加载、暂停、切换等交互实现;项目内置播放器核心逻辑、歌曲列表、个人中心等功能模块,也可借鉴其在资源组织、性能优化方面的处理思路,为二次开发和独立搭建小程序提供扎实参考。

1. 从源码包到可运行的小程序音乐台

拿到这份基于微信小程序的音乐播放器源码.zip,第一件事不是解压,而是先看它到底替你完成了哪些事。解压后最显眼的是miniprogramMusic-main目录,里面没有后端代码,没有云函数配置,是一个典型的纯前端小程序工程,核心业务全部由playerStore.jsmain-music.jsmusic-player.jsdetail-song.js这些模块支撑。也就是说,这个项目解决的是音乐播放类小程序里最关键的三个问题:歌曲列表怎么渲染、播放状态怎么跨页面同步、播放器怎么处理后台播放与生命周期。

这套代码的价值在于它给出了一个可以直接跑起来的数据流闭环。列表页发起请求拿到歌曲数据后写入playerStore.js这个全局状态池,详情页和播放器组件从同一份数据源读取,播放操作统一收敛到wx.getBackgroundAudioManager上面。对刚入门小程序开发的人来说,这是理解页面通信和状态管理的最佳范本;对写过几年业务代码的开发者,值得看的是项目如何用throttle.js处理高频交互,以及Component构造器下组件间事件派发的写法。本文接下来会按页面结构、播放器核心、列表渲染到调试排错的顺序,把每个模块的代码路径讲清楚。

2. 页面架构与 WXML 组件拆分:从 main-music 到 music-player

2.1 小程序目录结构与页面注册逻辑

微信小程序的页面体系由app.json统一注册。进入miniprogramMusic-main目录后,打开app.json你会看到类似下面的页面列表配置:

{ "pages": [ "pages/main-music/main-music", "pages/main-video/main-video", "pages/main-profile/main-profile", "pages/detail-song/detail-song", "pages/detail-video/detail-video" ], "window": { "navigationBarTitleText": "音乐台", "navigationBarBackgroundColor": "#ffffff" }, "tabBar": { "list": [ { "pagePath": "pages/main-music/main-music", "text": "音乐" }, { "pagePath": "pages/main-video/main-video", "text": "视频" }, { "pagePath": "pages/main-profile/main-profile", "text": "我的" } ] } }

pages 数组的第一个元素就是小程序启动后渲染的首页。这个项目把音乐、视频、个人中心三个 Tab 分开,主页下方的三栏 tabBar 由app.json里的tabBar字段定义,点击后通过wx.switchTab切换页面,而不是navigateTo。原因在于 tabBar 页面会被常驻在页面栈中,切换时不销毁,音乐播放状态因此能得到保留。detail-songdetail-video没有配到 tabBar 里,它们通过wx.navigateTo压栈跳转,适合展示歌曲详情这类一次性页面。

这个设计对播放器类应用至关重要。如果你把歌曲详情页也放到 tabBar,用户切换 Tab 后页面实例不会销毁,但页面栈会变得非常臃肿;而navigateTo跳转的页面栈最多十层,超过后navigateTo直接失败,这是很多新人在跳转详情时报错navigateTo:fail webview count limit exceed的常见原因。

2.2 main-music 页面结构与数据流设计

main-music.js是整个音乐模块的入口逻辑。它负责调用接口拉取轮播图、推荐歌单和歌曲列表,然后把这些数据绑定到 WXML 模板上。核心代码结构如下:

// pages/main-music/main-music.js const playerStore = require('../../store/playerStore.js') Page({ data: { bannerList: [], songList: [], currentSong: null, isPlaying: false }, onLoad() { this.fetchBanner() this.fetchSongList() // 订阅播放状态变化 playerStore.registerListener(this.updatePlayStatus) }, onUnload() { playerStore.unregisterListener(this.updatePlayStatus) }, fetchSongList() { wx.request({ url: 'https://api.example.com/song/list', method: 'GET', success: (res) => { this.setData({ songList: res.data.list }) } }) }, updatePlayStatus(payload) { this.setData({ currentSong: payload.song, isPlaying: payload.isPlaying }) }, handleSongTap(event) { const { id, index } = event.currentTarget.dataset wx.navigateTo({ url: `/pages/detail-song/detail-song?id=${id}&index=${index}` }) } })

代码逻辑说明:onLoad阶段先拉数据,再通过playerStore.registerListener注册一个状态监听函数。updatePlayStatus接收从全局状态池广播过来的播放事件,更新页面顶部迷你播放器的 UI。handleSongTapevent.currentTarget.dataset里取参数,通过wx.navigateTo跳到详情页。注意这里没有直接把整个歌曲对象放在 URL 上传递,而是只传idindex,详情页拿这两个参数去本地 store 查数据,这样能避免 URL 过长的问题,也符合小程序页面间通信的最佳实践。

对应的 WXML 结构里,歌曲列表使用wx:for渲染,每一项点击时绑定><view class="song-list"> <view wx:for="{{songList}}" wx:key="id" class="song-item" >// components/music-player/music-player.js Component({ properties: { song: { type: Object, value: null }, isPlaying: { type: Boolean, value: false } }, methods: { onTogglePlay() { // 向父组件派发切换播放状态事件 this.triggerEvent('toggleplay', { id: this.data.song.id }) }, onPrev() { this.triggerEvent('prev', {}) }, onNext() { this.triggerEvent('next', {}) } } })

组件内部是不直接操作全局 store 的,它只做一个展示和手势收集的容器。真正的业务逻辑在父页面里处理,这种单向数据流模式在小程序开发中非常实用。triggerEvent是自定义组件向父页面派发事件的核心 API,第一个参数是自定义事件名,第二个参数是携带的数据对象。父组件在 WXML 里这样接收:

<music-player song="{{currentSong}}" isPlaying="{{isPlaying}}" bind:toggleplay="togglePlay" bind:prev="playPrev" bind:next="playNext" />

这里的bind:toggleplay中间用冒号分隔,是自定义事件的绑定写法。如果没有这层封装,你会看到播放器状态在页面与组件之间来回同步代码越写越乱。把组件设计成纯展示模块后,任何页面复用这个播放条都只需要接收songisPlaying两个属性。

3. 音频播放核心:wx.getBackgroundAudioManager 与 playerStore 状态同步

3.1 BackgroundAudioManager 与旧 API 的选型对比

早期小程序播放音乐用wx.playBackgroundAudio,这是一个全局唯一的音频播放接口,但它的缺陷非常明显:不能控制播放进度时精确 seek、不支持同时获取多个播放状态、Android 机上锁屏控制支持很差。后来官方推荐使用wx.getBackgroundAudioManager获取一个全局唯一的BackgroundAudioManager实例,提供了更完整的播放控制能力。

下面这段代码是该项目的播放器核心初始化逻辑:

// store/playerStore.js const backgroundAudioManager = wx.getBackgroundAudioManager() backgroundAudioManager.title = '音乐台' backgroundAudioManager.epname = '正在播放' backgroundAudioManager.coverImgUrl = '' backgroundAudioManager.src = '' // 空 src 不会触发播放,但可以提前设置元数据 module.exports = { backgroundAudioManager }

backgroundAudioManager是全局单例,在应用任意页面里require这个文件拿到的都是同一个实例。这意味着在任何 A 页面设置src开始播放,切换到 B 页面都能通过同一个实例控制播放状态,不会出现两个播放器实例互相干扰的情况。titleepnamecoverImgUrl这三个字段用于控制锁屏界面和通知栏显示的信息,需要在调用play()之前设置好,否则部分 Android 机型上通知栏会显示空白歌曲名。

3.2 播放、暂停、停止与切歌的完整实现

继续看这个项目里如何封装播放控制方法。正常的音乐播放器至少需要playpauseseekstopswitchSong这五个核心操作:

// store/playerStore.js const backgroundAudioManager = wx.getBackgroundAudioManager() const playerStore = { currentSong: null, playList: [], playIndex: -1, isPlaying: false, playSong(song, list = [], index = 0) { // 更新本地状态 this.currentSong = song this.playList = list this.playIndex = index this.isPlaying = true // 同步到背景音频管理器 backgroundAudioManager.title = song.name backgroundAudioManager.singer = song.author backgroundAudioManager.coverImgUrl = song.cover backgroundAudioManager.src = song.url backgroundAudioManager.play() // 广播状态给所有订阅页面 this._notifyListeners() }, togglePlay() { if (this.isPlaying) { backgroundAudioManager.pause() this.isPlaying = false } else { if (this.currentSong) { backgroundAudioManager.play() this.isPlaying = true } } this._notifyListeners() }, next() { const nextIndex = (this.playIndex + 1) % this.playList.length this.playSong(this.playList[nextIndex], this.playList, nextIndex) }, prev() { const prevIndex = (this.playIndex - 1 + this.playList.length) % this.playList.length this.playSong(this.playList[prevIndex], this.playList, prevIndex) } } module.exports = playerStore

参数说明:playSong(song, list, index)接收三个参数,其中list是当前播放列表的全部歌曲,index是当前歌曲在列表中的位置。把整个列表传给playSong是为了在next函数中能直接取列表长度计算循环播放的下标。取模运算(this.playIndex + 1) % this.playList.length实现了列表循环,当播到最后一首时自动回到第一首。

一个值得注意的细节是:backgroundAudioManager.src一旦被赋了新值,会自动开始加载并播放,不需要再显式调用play()。不过项目里仍然保留了play()调用,这是为了确保在某些 iOS 版本上 src 赋值后不自动播放的兼容性问题。如果你在自己的项目里发现设置了src但没有声音,调用一次play()是最直接的修复方式。

_notifyListeners是这个 store 的核心广播机制。它维护一个监听者数组,播放状态变化时遍历调用所有注册过的回调:

_notifyListeners() { const payload = { song: this.currentSong, isPlaying: this.isPlaying, playlist: this.playList, index: this.playIndex } this.listeners.forEach((cb) => { if (typeof cb === 'function') { cb(payload) } }) }, registerListener(cb) { this.listeners.push(cb) }, unregisterListener(cb) { const index = this.listeners.indexOf(cb) if (index > -1) { this.listeners.splice(index, 1) } }

这段代码实现了一个极简的发布订阅模式。页面在onLoadregisterListener,在onUnloadunregisterListener,防止页面销毁后回调仍然被调用导致内存泄漏。forEach遍历时用typeof cb === 'function'做类型守卫,防止传入非法值导致运行时崩溃。这个模式的通信效率远高于wx.setStorageSync轮询,也符合小程序页面通信的推荐思路。

3.3 事件监听与锁屏控制

backgroundAudioManager支持多种事件回调,以监听歌曲播放状态:

事件名称触发时机实际应用场景
onPlay音频开始播放时更新 UI 为播放态,同步播放按钮图标
onPause音频暂停时更新 UI 为暂停态,记录当前播放进度
onStop音频停止时清除播放状态,停止进度动画
onEnded音频自然播放完毕自动切到下一首
onError播放出错时弹出错误提示,恢复播放器状态
onTimeUpdate播放进度变化时更新进度条,触发throttle节流的进度事件
onNext用户点击锁屏下一首调用next()切歌
onPrev用户点击锁屏上一首调用prev()切歌

在项目的music-player.js组件里,需要显式录制这些事件来维护 UI 状态。特别是onEnded,如果不在播放器管理器层面处理,歌曲播完会直接停止在最后一帧,用户需要手动点下一首。项目里通过如下代码实现自动连播:

backgroundAudioManager.onEnded(() => { playerStore.next() })

注意这里的onEnded回调只监听一次,不需要在页面onLoad里反复注册。因为backgroundAudioManager是全局单例,重复注册会导致一个播放结束事件触发多次next()调用,歌曲会跳一首。这个坑在线上的安卓机型上尤其常见,onEnded被注册了多次,播放完一首后瞬间跨了好几首歌。正确的做法是把事件注册放到app.jsonLaunch里,并且只执行一次。

3.4 iOS 与 Android 的音频行为差异

小程序音频开发绕不开平台差异。这里直接用这个项目的实践总结一张对照表:

差异点iOSAndroid项目中的应对措施
锁屏控制支持,需要title等元数据部分机型支持,依赖 ROM 实现统一设置titleepnamecoverImgUrl
后台播放默认支持,无需额外配置需要requiredBackgroundModes配置app.json配置requiredBackgroundModes: ["audio"]
音频焦点处理较好来电等场景可能中断播放监听wx.onAudioInterruptionBegin暂停,End恢复
切歌延迟较低较高,受解码器影响预加载下一首的src到隐藏 audio,但小程序限制较大

项目在app.json中的关键配置是requiredBackgroundModes字段。没有这个配置,Android 用户切到后台后音频很大概率会被系统挂起。把这个字段加上后,音频可以在后台持续播放,保证锁屏场景可用。

处理音频中断的代码可以放到app.js

// app.js App({ onLaunch() { const manager = wx.getBackgroundAudioManager() wx.onAudioInterruptionBegin(() => { // 来电或其他音频抢占,暂停播放 manager.pause() }) wx.onAudioInterruptionEnd(() => { // 音频焦点恢复,继续播放 manager.play() }) } })

这段代码的意义在于,当用户播放音乐时来了电话,音乐不会继续响,话语权让给电话通话;挂断电话后音乐自动续播,提升体验。

4. 列表渲染与交互优化:从 detail-song 到 throttle 防抖

4.1 detail-song 页面参数获取与歌曲渲染

详情页拿到idindex两个参数后,从全局数据中定位歌曲。核心代码在detail-song.js

// pages/detail-song/detail-song.js const playerStore = require('../../store/playerStore.js') Page({ data: { song: null, isPlaying: false, comments: [], currentTime: '00:00', duration: '00:00' }, onLoad(options) { const { id, index } = options // 从 store 中读取歌曲详情 const songList = playerStore.playList if (songList && songList.length > 0) { const song = songList.find(item => item.id === Number(id)) if (song) { this.setData({ song: song, isPlaying: playerStore.isPlaying }) } } } })

onLoad接收的options对象里是 URL 上拼接的参数,参数类型是字符串。因此这里用Number(id)把字符串转成数字再匹配数组里的歌曲id。如果不做类型转换,item.id === id的结果永远是false,页面会显示空白。这种 bug 在真实开发里出现的概率极高,很多新人会因为漏掉这一行排查一晚上。

详情页里的歌曲封面图和歌词展示区都用 WXML 标签渲染,播放进度条的 UI 更新需要监听onTimeUpdate事件。由于onTimeUpdate触发频率很高,通常每 250 毫秒触发一次,如果每次触发都执行this.setData更新进度条文本,页面渲染压力会非常大。这就是项目引入throttle.js的原因。

4.2 throttle.js 节流函数的前端防抖实践

throttle.js是项目里的一个工具模块,核心功能是限制函数在指定时间内的执行次数。看一下它的实现:

// utils/throttle.js function throttle(fn, interval = 500) { let lastTime = 0 return function (...args) { const now = Date.now() if (now - lastTime >= interval) { lastTime = now fn.apply(this, args) } } } module.exports = throttle

参数说明:fn是需要被节流的原函数,interval是时间间隔,默认 500 毫秒。函数内部维护一个lastTime变量记录上次执行的毫秒时间戳。每次调用都会计算当前时间与上次执行时间的差值,大于等于间隔时才真正执行原函数,否则直接丢弃本次调用。

把这个 throttle 引用到播放进度条更新场景中:

// pages/detail-song/detail-song.js const throttle = require('../../utils/throttle.js') Page({ onLoad() { const manager = wx.getBackgroundAudioManager() manager.onTimeUpdate( throttle(() => { const currentTime = this.formatTime(manager.currentTime) const duration = this.formatTime(manager.duration) this.setData({ currentTime, duration, progress: (manager.currentTime / manager.duration) * 100 }) }, 1000) ) }, formatTime(seconds) { const min = Math.floor(seconds / 60) const sec = Math.floor(seconds % 60) return `${min < 10 ? '0' + min : min}:${sec < 10 ? '0' + sec : sec}` } })

代码逻辑说明:onTimeUpdate每 250ms 触发一次,如果直接setData会频繁触发页面 diff 计算和视图渲染,在低端 Android 设备上会导致进度条拖动卡顿。包上一层throttle后,setData的执行频率最大是 1 秒一次,进度条虽然看起来不那么细腻,但页面流畅度明显提升。formatTime函数把秒数转成mm:ss格式,确保 UI 上显示正常。

值得一提的坑:这段代码里的manager.onTimeUpdate如果不在页面onUnload时手动移除监听,页面销毁后回调仍然会被触发,this.setData就会报错。但由于BackgroundAudioManager是全局单例,页面销毁后再次进入详情页时不应该重复注册。常见的做法是在每页的onUnload中调用manager.onTimeUpdate(null)来取消监听。

4.3 song-item-v2 组件与列表更新优化

项目中的song-item-v2.js是一个列表项组件,用来渲染单首歌曲的信息。把它抽成组件后,歌曲列表页、搜索页、歌单详情页都可以复用同一个列表项。它的结构如下:

// components/song-item-v2/song-item-v2.js Component({ properties: { song: { type: Object, value: {} }, index: { type: Number, value: 0 }, showIndex: { type: Boolean, value: false } }, methods: { onTap() { this.triggerEvent('songtap', { id: this.data.song.id, index: this.data.index }) } } })

使用方只需要在 WXML 中导入组件并绑定数据:

{ "usingComponents": { "song-item-v2": "/components/song-item-v2/song-item-v2" } }

列表渲染的关键优化点在于wx:for的 key 值设置和setData的更新范围。在小程序中,局部更新大列表时尽量不要整体设置list字段,而是通过this.setData({ [songList[${index}].name]: newName })这种路径更新方式,只触发对应行重新渲染。这个技巧在小程序官方文档里没有强调,但实际业务中非常有用,短视频和信息流类页面尤其依赖这一招。

4.4 分页加载与加载状态处理

歌曲列表如果一次全部拉取会非常慢。项目提供了一个可参考的分页加载模型,核心逻辑在main-music.jsonReachBottom里:

onReachBottom() { if (this.data.isLoading || this.data.hasMore === false) { return } this.setData({ isLoading: true }) const page = this.data.page + 1 wx.request({ url: 'https://api.example.com/song/list', data: { page: page, pageSize: 20 }, success: (res) => { this.setData({ songList: this.data.songList.concat(res.data.list), page: page, hasMore: res.data.hasMore, isLoading: false }) }, fail: () => { this.setData({ isLoading: false }) } }) }

onReachBottom是小程序页面滚动到底部时触发的事件。isLoading字段是并发锁,防止用户快速滚动时同时发出多个重复请求。hasMore表示后端是否还有更多数据,为false时直接 return,不再发起无效请求。concat把新数据追加到旧列表后面,而不是整体替换。这个模型同样适用于detail-video页面,视频列表的懒加载思路完全一致。

分页加载还有一层隐性要求:后端返回的数据必须包含hasMoretotal字段。如果接口不返回这个字段,前端只能预估总数,实现上要改成判断返回数组长度是否小于pageSize,小于则视为没有更多数据。

5. 调试、真机验证与进阶优化:从加载页到后台播放

5.1 自定义导航栏与加载状态帧率优化

这个项目里有一个常见需求:修改刚进入的加载页面。小程序启动时先渲染app.json里配置的入口页面,然后加载页面所需的 JS 逻辑和 WXSS 样式。如果你发现启动白屏时间偏长,可以从两个方向优化。

第一是开启自定义导航栏。在app.json对应页面配置"navigationStyle": "custom",页面顶部进入status-bar安全区由 WXML 自定义填充。这种方式可以让首屏渲染感觉更快,因为导航栏不再等待系统渲染,而是跟随页面内容一起呈现。项目里main-music页面大量使用自定义导航栏,代价是需要手动适配 iPhone 的刘海屏安全区。

第二是减少首屏请求的阻塞。小程序首页如果同时发起 banner、歌单、歌曲列表三个请求,每个请求都是独立的回调,页面渲染会在最后一个请求完成后才完整。合理的做法是先把 banner 数据和推荐歌单数据一个接口返回,再在用户下滑时加载歌曲详情。代码层面可以用Promise.all合并请求,减少建立多个连接的开销:

onLoad() { Promise.all([this.fetchBanner(), this.fetchRecommend()]).then(() => { this.setData({ loading: false }) }) }

5.2 音频播放失败与断网场景的容错处理

音乐类应用的弱网场景是大考。项目在music-player.js里监听onError事件,播放失败时提示用户并尝试自动恢复:

backgroundAudioManager.onError((res) => { console.error('播放出错:', res.errMsg) if (res.errCode === 10001) { // 资源不存在或格式不支持 wx.showToast({ title: '该歌曲暂时无法播放', icon: 'none' }) } else if (res.errCode === -1) { // 未知错误,尝试重新加载 backgroundAudioManager.src = playerStore.currentSong.url backgroundAudioManager.play() } })

errCode的含义在小程序官方文档里没有完整的枚举列表,实践中10001通常表示资源解析失败,-1表示系统级错误。断网时backgroundAudioManager.play()会触发onError,此时不建议自动重试超过两次,否则会连续弹出错误提示,体验极差。

5.3 网络请求返回的 Set-Cookie 与身份校验

小程序环境中的wx.request默认是不携带 Cookie 的。如果后端接口依赖登录态,常见做法是把 token 放在请求头里:

wx.request({ url: 'https://api.example.com/song/detail', header: { 'Authorization': `Bearer ${wx.getStorageSync('token')}` }, success: (res) => { // 处理业务数据 } })

detail-video.js中同样能看到类似的请求封装。小程序没有 Cookie 容器,这既是限制也是安全优势。全套接口入口统一收敛在这个请求封装里,后续升级加签名、加时间戳都只要改一个文件,不用全局替换。

调试这套逻辑时,推荐使用微信开发者工具自带的「模拟操作」面板的 Network 标签页,直接查看每个请求的 Header 和响应体。wx.request的最大超时时间是 60 秒,超过后请求进入fail回调,timeout字段需要显式设置。

5.4 真机调试与端侧验证清单

项目代码在开发者工具里跑得通不代表真机就行,特别是音频模块。我建议按下面的顺序做一轮完整的真机验证。

测试项操作步骤预期结果失败排查方向
首屏加载冷启动小程序音乐列表 3 秒内展示检查wx.request域名是否合法、是否启用 HTTP 校验
点击播放点击列表首歌曲播放按钮变为暂停态,声音输出检查src是否为 HTTPS 地址,音频格式是否浏览器兼容
切后台播放中按 Home 键音乐继续播放,状态栏出现播放通知检查app.json是否配置requiredBackgroundModes
锁屏切歌锁屏后点击下一首按钮播放下一首歌曲app.js中注册onNext,确认事件上抛
播放完自动切歌等待当前歌曲播完自动开始播下一首确认onEnded回调只注册一次
来电中断恢复播放中模拟来电来电时暂停,挂断后恢复确认onAudioInterruptionBeginEnd均正确注册
弱网恢复飞行模式再关闭当前歌曲不崩溃,可手动重试使用onError监听错误码,重试不超过 2 次

真机调试时有一点特别容易忽视:开发者工具默认不校验合法域名,但在真机上,wx.request的域名必须在小程序后台配置 request 合法域名,且必须是 HTTPS。如果你拿到的源码接口地址还是 http 或本地 IP,真机必然请求失败。此时有两种处理方式:在小程序后台的「开发管理-开发设置-服务器域名」里添加合法域名,或者在开发者工具右上角详情里勾选「不校验合法域名」仅用于开发预览。

另外,音频文件本身的跨域问题也会有影响。backgroundAudioManager加载的音频地址需要支持 CORS,否则部分 Android 机型会播放失败。测试时建议用云存储或 CDN 地址,避免使用未经处理的个人服务器直链。项目源码注释里对src的格式做了说明:建议统一使用 HTTPS 协议、编码格式为 MP3 或 M4A,避免使用 WMA 等小程序端不原生支持的格式。

整套走下来,这份源码最值得带走的实践是 playerStore 这个状态池模式。它没有依赖任何第三方状态管理库,只用几十行原生 JS 就实现了跨页面的播放状态同步。后续你要扩展歌词展示、收藏列表、历史播放,都是往这个 store 里加字段和响应函数的事。理解了这一层,再去读detail-video.jsmain-profile.js里的逻辑,会发现完全是同一套思路在不同业务场景的复用。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/14 18:55:26

al-folio 如何部署到 Netlify?

al-folio 如何部署到 Netlify&#xff1f; 【免费下载链接】al-folio A beautiful, simple, clean, and responsive Jekyll theme for academics 项目地址: https://gitcode.com/GitHub_Trending/al/al-folio al-folio 是面向学术主页的 Jekyll 主题&#xff0c;默认的部…

作者头像 李华
网站建设 2026/9/14 18:54:52

Haystack Agent Pack 实战指南:构建 Advanced RAG 与 Deep Research Agent

Haystack Agent Pack 实战指南&#xff1a;构建 Advanced RAG 与 Deep Research Agent 【免费下载链接】haystack Open-source AI orchestration framework for building context-engineered, production-ready LLM applications. Design modular pipelines and agent workflow…

作者头像 李华
网站建设 2026/9/14 18:54:14

HoRain云--Python asyncio 异步编程实战:从协程到高并发爬虫

1. 同步 vs 异步同步爬虫按顺序请求&#xff0c;耗时累加。异步爬虫在等待网络响应时切换任务&#xff0c;大幅提升吞吐量。2. 协程基础python复制下载import asyncioasync def hello():print("Hello")await asyncio.sleep(1)print("World")asyncio.run(he…

作者头像 李华
网站建设 2026/9/14 18:52:19

两阶段鲁棒优化在微电网调度中的Matlab实现

1. 项目概述&#xff1a;两阶段鲁棒微网优化调度的核心逻辑 微电网作为分布式能源系统的关键载体&#xff0c;其调度优化一直面临风光出力波动、负荷变化等不确定性的挑战。传统随机规划方法依赖精确概率分布&#xff0c;而鲁棒优化则通过构建不确定性集合来规避风险。两阶段鲁…

作者头像 李华
网站建设 2026/9/14 18:51:34

固态电解质智能热闸:高温断路冷却自恢复的电池安全新策略

前几天九章算AM上有一条关于大连理工大学胡方圆教授团队的解读&#xff0c;标题叫“动态防护新策略”&#xff0c;说的是给固态电解质装一个智能“热闸”&#xff1a;高温下离子通道自动断路&#xff0c;冷却后自动恢复。粗看像实验室里的花活&#xff0c;细想其实是在回应电池…

作者头像 李华