一.源码

我只实现了跳转页面之后能拿到原本的歌曲信息和播放状态,但是恢复不了播放进度,代码如下(设置,页脚代码里面注入他就好了)

<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这很奇怪,我也找不出原因