播放器优化
一.源码
我只实现了跳转页面之后能拿到原本的歌曲信息和播放状态,但是恢复不了播放进度,代码如下(设置,页脚代码里面注入他就好了)
<script>
document.addEventListener('DOMContentLoaded', function() {
// 全局播放器状态
const PlayerState = {
index: null,
time: 0,
isPlaying: false,
save: function(aplayer) {
if (!aplayer) return;
this.index = aplayer.list.index;
this.time = aplayer.audio.currentTime;
this.isPlaying = !aplayer.audio.paused;
// 同时保存播放器的 DOM 状态
this.buttonClass = aplayer.button.className;
this.containerClass = aplayer.container.className;
sessionStorage.setItem('playerState', JSON.stringify(this));
},
restore: function(aplayer) {
const state = JSON.parse(sessionStorage.getItem('playerState'));
if (!state || !aplayer) return;
this.index = state.index;
this.time = state.time;
this.isPlaying = state.isPlaying;
// 恢复播放器状态
setTimeout(() => {
aplayer.list.switch(this.index);
aplayer.seek(this.time);
// 先恢复 DOM 状态
if (state.buttonClass) aplayer.button.className = state.buttonClass;
if (state.containerClass) aplayer.container.className = state.containerClass;
if (this.isPlaying) {
aplayer.play();
// 强制更新 UI 状态
requestAnimationFrame(() => {
aplayer.button.classList.remove('aplayer-pause');
aplayer.button.classList.add('aplayer-play');
aplayer.container.classList.add('aplayer-playing');
});
}
}, 500);
}
};
// 初始化播放器
function initPlayer() {
const meting = document.querySelector('meting-js');
if (!meting) return;
const initCheck = setInterval(() => {
if (meting.aplayer) {
clearInterval(initCheck);
const ap = meting.aplayer;
// 绑定事件
ap.on('play', () => {
PlayerState.save(ap);
// 强制更新 UI
requestAnimationFrame(() => {
ap.button.classList.remove('aplayer-pause');
ap.button.classList.add('aplayer-play');
ap.container.classList.add('aplayer-playing');
});
});
ap.on('pause', () => {
PlayerState.save(ap);
// 强制更新 UI
requestAnimationFrame(() => {
ap.button.classList.add('aplayer-pause');
ap.button.classList.remove('aplayer-play');
ap.container.classList.remove('aplayer-playing');
});
});
ap.on('timeupdate', () => {
PlayerState.time = ap.audio.currentTime;
});
// 恢复状态
PlayerState.restore(ap);
}
}, 100);
}
// PJAX 事件监听
document.addEventListener('pjax:send', () => {
const ap = document.querySelector('meting-js')?.aplayer;
if (ap) PlayerState.save(ap);
});
document.addEventListener('pjax:complete', () => {
setTimeout(initPlayer, 500);
});
// 页面加载完成后初始化
initPlayer();
});
</script>
二.问题
1.我尝试过去恢复,但是我拿到了数据也恢复了,但是恢复状态只维持了一秒左右,等页面完全跳转之后播放进度又从0开始了,而且还有一个播放状态的小bug,代码和调试如下
<script>
document.addEventListener('DOMContentLoaded', function() {
// 全局播放器状态
const PlayerState = {
index: null,
time: 0,
isPlaying: false,
save: function(aplayer) {
if (!aplayer) return;
this.index = aplayer.list.index;
this.time = aplayer.audio.currentTime;
this.isPlaying = !aplayer.audio.paused;
// 保存状态到 sessionStorage
sessionStorage.setItem('playerState', JSON.stringify(this));
// 调试信息
console.log('保存的状态:', {
index: this.index,
time: this.time,
isPlaying: this.isPlaying
});
},
restore: function(aplayer) {
const state = JSON.parse(sessionStorage.getItem('playerState'));
if (!state || !aplayer) return;
this.index = state.index;
this.time = state.time;
this.isPlaying = state.isPlaying;
// 恢复播放器状态
aplayer.list.switch(this.index);
aplayer.seek(this.time); // 设置播放时间
// 仅在用户交互后播放
document.addEventListener('click', function restoreAfterClick() {
if (PlayerState.isPlaying) {
aplayer.play(); // 播放
}
document.removeEventListener('click', restoreAfterClick); // 移除事件监听器
}, { once: true }); // 只监听一次
// 调试信息
console.log('恢复的状态:', state);
}
};
// 初始化播放器
function initPlayer() {
const meting = document.querySelector('meting-js');
if (!meting) return;
const initCheck = setInterval(() => {
if (meting.aplayer) {
clearInterval(initCheck);
const ap = meting.aplayer;
// 调试信息:输出当前播放器实例
console.log('当前播放器实例:', ap);
// 绑定事件
ap.on('play', () => PlayerState.save(ap));
ap.on('pause', () => PlayerState.save(ap));
ap.on('timeupdate', () => {
PlayerState.time = ap.audio.currentTime; // 更新当前时间
console.log('当前播放时间:', PlayerState.time);
});
// 恢复状态
PlayerState.restore(ap);
}
}, 100);
}
// PJAX 事件监听
document.addEventListener('pjax:send', () => {
const ap = document.querySelector('meting-js')?.aplayer;
if (ap) PlayerState.save(ap); // 保存状态
});
document.addEventListener('pjax:complete', () => {
setTimeout(initPlayer, 500); // 页面加载完成后初始化播放器
});
// 页面加载完成后初始化
initPlayer();
});
</script>
2.调试结果
三.主题源码文件参考部分
templates/modules/layouts/layout.html - 主布局文件
templates/assets/libs/aplayer/ - 播放器相关文件
templates/assets/libs/pjax/ - PJAX 相关文件
我的想法:播放器状态丢失的原因可能与 PJAX 的使用有关。PJAX 在页面切换时会重新加载部分内容,这可能导致播放器的状态被重置。
四.希望大佬看到能解决一下
用的是hao主题,懒得改源码重新部署,所以只想在页脚部分注入代码把效果做出来,作者已红温,后续这个问题我估计不会过多的去调试了,就先这样凑合着了
五.后续,已解决
源码:
<script>
document.addEventListener('DOMContentLoaded', function() {
// 全局播放器状态
const PlayerState = {
index: null,
time: 0,
isPlaying: false,
save: function(aplayer) {
if (!aplayer || !aplayer.button || !aplayer.container) return;
try {
this.index = aplayer.list.index;
this.time = aplayer.audio.currentTime;
this.isPlaying = !aplayer.audio.paused;
sessionStorage.setItem('playerState', JSON.stringify(this));
} catch (error) {
console.log('Error saving player state:', error);
}
},
restore: function(aplayer) {
if (!aplayer || !aplayer.button || !aplayer.container) return;
try {
const state = JSON.parse(sessionStorage.getItem('playerState'));
if (!state) return;
this.index = state.index;
this.time = state.time;
this.isPlaying = state.isPlaying;
// 恢复播放器状态
setTimeout(() => {
try {
aplayer.list.switch(this.index);
aplayer.seek(this.time);
if (this.isPlaying) {
aplayer.play();
// 安全地更新 UI
if (aplayer.button && aplayer.container) {
requestAnimationFrame(() => {
try {
aplayer.button.classList.remove('aplayer-pause');
aplayer.button.classList.add('aplayer-play');
aplayer.container.classList.add('aplayer-playing');
} catch (error) {
console.log('Error updating UI:', error);
}
});
}
}
} catch (error) {
console.log('Error restoring state:', error);
}
}, 500);
} catch (error) {
console.log('Error parsing player state:', error);
}
}
};
// 初始化播放器
function initPlayer() {
const meting = document.querySelector('meting-js');
if (!meting) return;
const initCheck = setInterval(() => {
if (meting.aplayer) {
clearInterval(initCheck);
const ap = meting.aplayer;
// 绑定事件
ap.on('play', () => {
if (ap && ap.button && ap.container) {
PlayerState.save(ap);
requestAnimationFrame(() => {
try {
ap.button.classList.remove('aplayer-pause');
ap.button.classList.add('aplayer-play');
ap.container.classList.add('aplayer-playing');
} catch (error) {
console.log('Error updating UI on play:', error);
}
});
}
});
ap.on('pause', () => {
if (ap && ap.button && ap.container) {
PlayerState.save(ap);
requestAnimationFrame(() => {
try {
ap.button.classList.add('aplayer-pause');
ap.button.classList.remove('aplayer-play');
ap.container.classList.remove('aplayer-playing');
} catch (error) {
console.log('Error updating UI on pause:', error);
}
});
}
});
ap.on('timeupdate', () => {
if (ap && ap.audio) {
PlayerState.time = ap.audio.currentTime;
}
});
// 恢复状态
PlayerState.restore(ap);
}
}, 100);
}
// PJAX 事件监听
document.addEventListener('pjax:send', () => {
try {
const ap = document.querySelector('meting-js')?.aplayer;
if (ap && ap.button && ap.container) {
PlayerState.save(ap);
}
} catch (error) {
console.log('Error on pjax:send:', error);
}
});
document.addEventListener('pjax:complete', () => {
setTimeout(initPlayer, 500);
});
// 页面加载完成后初始化
initPlayer();
});
</script>
5.1实现音乐播放器跨页面播放的原理:
1.状态管理
const PlayerState = {
index: null, // 当前播放歌曲的索引
time: 0, // 当前播放时间
isPlaying: false, // 是否正在播放
// 保存状态到 sessionStorage
save: function(aplayer) {
this.index = aplayer.list.index;
this.time = aplayer.audio.currentTime;
this.isPlaying = !aplayer.audio.paused;
sessionStorage.setItem('playerState', JSON.stringify(this));
},
// 从 sessionStorage 恢复状态
restore: function(aplayer) {
const state = JSON.parse(sessionStorage.getItem('playerState'));
aplayer.list.switch(state.index); // 切换到保存的歌曲
aplayer.seek(state.time); // 恢复播放时间
if (state.isPlaying) aplayer.play(); // 恢复播放状态
}
}
2.关键时机
// 页面跳转前保存状态
document.addEventListener('pjax:send', () => {
const ap = document.querySelector('meting-js')?.aplayer;
if (ap) PlayerState.save(ap);
});
// 新页面加载后恢复状态
document.addEventListener('pjax:complete', () => {
setTimeout(initPlayer, 500);
});
3..工作流程:
用户点击链接跳转页面
PJAX 触发 pjax:send 事件
保存当前播放状态到 sessionStorage
新页面加载完成,触发 pjax:complete 事件
初始化新页面的播放器
从 sessionStorage 恢复之前保存的状态
4.核心原理:
使用 sessionStorage 在页面跳转间保存状态
利用 PJAX 的事件机制捕获页面切换时机
在新页面重新初始化播放器时恢复之前的状态
5. 完整流程
页面条状
→ 保存当前状态到 sessionStorage
→ 加载新页面
→ 初始化新播放器
→ 恢复播放状态和UI状态
5.2之前失败的原因总结
1. 状态保存的差异:
// 失败版本
save: function(aplayer) {
// 只保存基本状态
this.index = aplayer.list.index;
this.time = aplayer.audio.currentTime;
this.isPlaying = !aplayer.audio.paused;
}
// 成功版本
save: function(aplayer) {
// 保存基本状态 + DOM状态
this.index = aplayer.list.index;
this.time = aplayer.audio.currentTime;
this.isPlaying = !aplayer.audio.paused;
this.buttonClass = aplayer.button.className; // 额外保存按钮的CSS类
this.containerClass = aplayer.container.className; // 额外保存容器的CSS类
}
2.UI状态处理
// 失败版本
// 没有专门处理 UI 状态的同步
// 成功版本
// 使用 requestAnimationFrame 确保 UI 更新
requestAnimationFrame(() => {
ap.button.classList.remove('aplayer-pause');
ap.button.classList.add('aplayer-play');
ap.container.classList.add('aplayer-playing');
});
3. 小结:
失败原因:
没有保存 DOM 状态(CSS类)
没有强制更新 UI
播放状态和 UI 状态不同步
没有使用 requestAnimationFrame 来确保 UI 更新的时机正确
成功版本的改进:
保存完整的 DOM 状态
使用 requestAnimationFrame 确保 UI 更新时机
在播放/暂停时强制更新 UI
先恢复 DOM 状态,再恢复播放状态
简单来说,前者失败是因为只关注了音频播放状态的保存和恢复,忽略了 UI 状态的同步问题。而成功的版本通过保存和恢复完整的 DOM 状态,并使用 requestAnimationFrame 来确保 UI 更新的正确性,解决了这个问题
六.目前的效果
除了跳转进文章页面的时候播放器状态不会保留,其他时候都能继承完完全全的播放状态,emm这很奇怪,我也找不出原因