解决Pjax导致Aplayer Js加载失败问题
Pjax是一款常用的插件,主要用于实现跳转不刷新网页实现某些事件的连续,例如本站使用了Pjax实现了音乐tag全站连续播放,其原理是通过不刷新网页的方式获取js资源,从而不会阻断连续事件的发生。但是pjax会引入比较大的问题,某些页面跳转时需要刷新加载的部件无法正常加载,例如评论模块、自建Aplayer等,只能手动刷新,导致每次在Music页选择完音乐,重新进入时无法获取正在播放的列表。
最常用的解决方法是:在每次pjax调用完成后,使用回调函数加载js资源,Aplayer列表确实不会丢失了,但是音乐却只能在当页播放,体验一般,本文提供了一种可行的方案,能够完美兼容Aplayer加载问题与Pjax调用问题,主要骨干是pjax回调函数初始化Aplayer,以及用于辅助记录Aplayer歌曲播放状态的函数。
建立一个Aplayer
Aplayer可以使用列表语法调用,也可以使用js调用,为了方便函数互调,使用了js语法,如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20function BuildPlayer(){
globalPlayer = new APlayer({
container: document.getElementById('aplayer'),
narrow: false,
autoplay: false,
mode: 'random',
showlrc: 3,
mutex: true,
theme: '#e6d0b2',
preload: 'metadata',
listmaxheight: '513px',
music: [
{title: 'xxx',author: 'xxx',url: 'xxx',lrc:'xxx'}]
});
// 监听播放状态变化事件,savePlayState用于记录播放状态,后文给出
globalPlayer.on('play', savePlayState);
globalPlayer.on('pause', savePlayState);
globalPlayer.on('seeked', savePlayState);
}
状态记录与恢复
pjax全局生效时跳转播放是没问题的,但是会导致js重复加载,点击进入html页面,pjax在首次进入时会完成一次调用使用回调函数新建Aplayer,那么再次点击进入,就会出现再新建一次Aplayer,出现了Aplayer无限叠加的问题。因此必须使用函数用于记录上次歌曲播放状态,并且再次点击时如果Aplayer对象是存在的,就创建新建对象用于拷贝旧对象的状态再销毁对象。新建对象的目的在于在不刷新的前提(刷新代表所有资源被覆盖)下,能够正常加载Aplayer的列表,并且恢复到上次的状态。保存的内容包括正在播放的歌曲、歌曲时间、暂停状态:
1 | function savePlayState() { |
Aplayer的初始化策略
有了上面的基础,就可以进行Aplayer的初始化了。Aplayer的初始化分为两种情况:其一是用户第一次进入html,此时没有Aplayer对象;其二是用户听着音乐了,Aplayer存在:
1. 第一次初始化:建立列表 1
2
3
4
5
6
7if(!globalPlayer){
BuildPlayer();
// 恢复播放状态
restorePlayState();
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25else{
savePlayState();
const newPlayer = new APlayer({
container: document.getElementById('aplayer'),
narrow: false,
autoplay: false,
mode: 'random',
showlrc: 3,
mutex: true,
theme: '#FF6347',
preload: 'metadata',
listmaxheight: '1026px',
music: globalPlayer.list.audios
});
// 将旧实例的状态复制到新实例中
newPlayer.list.index = globalPlayer.list.index;
newPlayer.audio.currentTime = globalPlayer.audio.currentTime;
newPlayer.audio.paused = globalPlayer.audio.paused;
// 销毁旧实例
globalPlayer.destroy();
// 更新全局播放器实例引用
globalPlayer = newPlayer;
// 恢复播放状态
restorePlayState();
}
pjax回调
pjax回调保证每次进入时都能够根据有无Aplayer对象进行初始化,不知道为什么pjax:complete无效,pjax:success可以,效果是一致的。
1
2
3
4document.addEventListener('pjax:success', function() {
console.log('pjax:success - Restoring/initializing player');
initPlayer();
});
细节问题
有一个特别要注意的地方是:假如在当前页面刷新时,由于本页没有主动引入pjax,所以刷新是不会导致pjax:success事件发生,也就是说,刷新时不会进入初始化,列表资源会显示空白。为了解决这个问题,只需要在刷新时加入资源初始化:
1
2
3//冲突方法
if (!globalPlayer) {
initPlayer();}
但是又会导致新的故障:上述这段if代码和pjax:success回调函数在第一次进入时会发生冲突:因为第一次进入时既满足pjax:success,也满足!globalPlayer,导致程序未来得及pjax后回调,就进入初始化,导致第一次进入必然是初始化失败的。解决方法是不要使用直接判断,引入标志位和DOMContentLoaded事件,该事件在浏览器文本加载完成即可调用,不等待js和css加载:
1
2
3
4
5
6
7
8let flag=false;
document.addEventListener('DOMContentLoaded', function() {
setTimeout(() => {
if (!flag) {
initPlayer();
flag=true;}
}, 50);
});
总结
可以说整个配置过程原理并不复杂,但是步骤比较繁琐,由于Aplayer版本以及pjax版本不一样,相关封装函数也不一样,函数位置错了就得不到需要的效果。这里要感谢Claude-3-Opus的帮助,尽管GPT-4-Turbo数据库已经更新到24年的4月份,但在代码意见上Claude-3-Opus仍然是当之无愧的榜首,上述代码性能还有很大的调整空间,但就个人而言也算是合格的方案。