播放器

上些日子给网站做了有关于新年红的配色,今天就总觉得差点什么,索性写了一个播放器。过年了放上会更加有新年氛围,提前祝大家新年快乐!

功能说明
响应式布局:手机端底部居中(支持收缩 / 展开),电脑端左下角固定显示
核心功能:播放 / 暂停、下一曲切换、单曲结束自动切歌,随机切歌不重复
体验优化:刷新 / 页面跳转精准续播、无闪烁无冗余样式、手机端点击空白处收缩展开
样式特色:新年红金配色、悬浮阴影效果、按钮交互反馈,适配中文字体无乱码
兼容处理:突破浏览器自动播放限制,兼容微信 X5 内核,全局样式无冲突
使用说明

1. 代码部署
将以下完整代码直接粘贴到 Typecho 主题自定义 HTML/PHP 文件中(如页脚文件footer.php),建议放在标签前。

    <style>
    #musicPlayer {
        position: fixed;
        bottom: 10px;
        z-index: 999;
        display: flex;
        align-items: center;
        gap: 12px;
        padding: 8px 20px;
        background: rgba(216, 0, 15, 0.9);
        border-radius: 50px;
        box-shadow: 0 0 20px rgba(250, 108, 0, 0.8);
        -webkit-tap-highlight-color: transparent;
        box-sizing: border-box;
        border: 2px solid #dc8f03;
        left: 50%;
        transform: translateX(-50%);
        min-width: fit-content;
        transition: all 0.3s ease-in-out;
    }
    @media screen and (min-width: 768px) {
        #musicPlayer {
            left: 15px;
            transform: translateX(0);
        }
        #musicInfo {
            display: block !important;
        }
    }
    @media screen and (max-width: 767px) {
        #musicPlayer.shrink {
            padding: 8px;
        }
        #musicInfo {
            display: block;
            transition: all 0.3s ease-in-out;
        }
        #musicPlayer.shrink #musicInfo {
            display: none;
        }
    }
    .music-btn {
        width: 42px;
        height: 42px;
        border-radius: 50%;
        border: none;
        outline: none;
        cursor: pointer;
        display: flex;
        align-items: center;
        justify-content: center;
        font-size: 12px;
        font-weight: bold;
        color: #d8000f;
        background: #dc8f03;
        transition: all 0.3s ease;
        box-shadow: 0 0 10px rgba(250, 108, 0, 0.5);
    }
    .music-btn:hover {
        background: #fff;
        color: #d8000f;
        box-shadow: 0 0 15px rgba(250, 108, 0, 1);
    }
    .music-btn:active {
        transform: scale(0.95);
    }
    #musicInfo {
        color: #fff;
        font-size: 13px;
        font-family: "Microsoft YaHei", sans-serif;
        white-space: nowrap;
        line-height: 1.2;
    }
    #musicInfo span {
        color: #ffd700;
        font-weight: 500;
    }
    * {
        box-sizing: border-box;
        margin: 0;
        padding: 0;
    }
    </style>
    <div id="musicPlayer">
        <audio id="bgMusic" preload="auto" style="display: none;"></audio>
        <button id="playBtn" class="music-btn">播放</button>
        <button id="nextBtn" class="music-btn">下一曲</button>
        <div id="musicInfo">
            <span id="playStatus">暂停播放</span>:<span id="songName"></span> - <span id="singerName"></span>
        </div>
    </div>
    <script>
    window.addEventListener('load', function() {
        const audio = document.getElementById('bgMusic');
        const playBtn = document.getElementById('playBtn');
        const nextBtn = document.getElementById('nextBtn');
        const playStatusEl = document.getElementById('playStatus');
        const songNameEl = document.getElementById('songName');
        const singerNameEl = document.getElementById('singerName');
        const musicPlayer = document.getElementById('musicPlayer');
    
        const musicList = [
            { src: '/UGC/1.mp3', name: '《张灯结彩》', singer: '王二妮、阿宝' },
            { src: '/UGC/2.mp3', name: '《恭喜发财》', singer: '刘德华' },
            { src: '/UGC/3.mp3', name: '《新年大吉》', singer: '祁隆' }
        ];
    
        let lastIndex = -1;
        let isPlaying = false;
        const CACHE_KEY = 'music_play_state';
        let isInitiated = false;
        let isShrinked = false;
    
        function updateStatus() {
            const statusText = isPlaying ? '正在播放' : '暂停播放';
            playStatusEl.innerText = statusText;
            playBtn.innerText = isPlaying ? '暂停' : '播放';
        }
    
        function updateMusicInfo(index) {
            const song = musicList[index];
            songNameEl.innerText = song.name;
            singerNameEl.innerText = song.singer;
        }
    
        function getRandomIndex() {
            let randomIndex;
            do {
                randomIndex = Math.floor(Math.random() * musicList.length);
            } while (randomIndex === lastIndex && musicList.length > 1);
            lastIndex = randomIndex;
            return randomIndex;
        }
    
        function saveState() {
            const currentIndex = musicList.findIndex(item => item.src === audio.src);
            const state = {
                isPlaying,
                index: currentIndex > -1 ? currentIndex : 0,
                time: audio.currentTime,
                lastIndex
            };
            sessionStorage.setItem(CACHE_KEY, JSON.stringify(state));
        }
    
        function restoreState() {
            const cache = sessionStorage.getItem(CACHE_KEY);
            if (!cache) return false;
            try {
                const state = JSON.parse(cache);
                audio.src = musicList[state.index].src;
                audio.currentTime = state.time;
                lastIndex = state.lastIndex;
                isPlaying = state.isPlaying;
                updateMusicInfo(state.index);
                return true;
            } catch (e) {
                sessionStorage.removeItem(CACHE_KEY);
                return false;
            }
        }
    
        function toggleShrink() {
            if (window.innerWidth > 767) return;
            isShrinked = !isShrinked;
            musicPlayer.classList.toggle('shrink', isShrinked);
        }
    
        function playCore() {
            audio.muted = true;
            audio.play().then(() => {
                isPlaying = true;
                updateStatus();
                setTimeout(() => audio.muted = false, 100);
                isInitiated = true;
                saveState();
                if (window.innerWidth <= 767 && !isShrinked) {
                    isShrinked = true;
                    musicPlayer.classList.add('shrink');
                }
            }).catch(err => {
                isPlaying = false;
                updateStatus();
            });
        }
    
        function playMusic() {
            if (!audio.src) {
                const index = getRandomIndex();
                audio.src = musicList[index].src;
                updateMusicInfo(index);
            }
            playCore();
        }
    
        function pauseMusic() {
            audio.pause();
            isPlaying = false;
            updateStatus();
            saveState();
        }
    
        function togglePlay() {
            if (window.innerWidth <= 767 && isShrinked) {
                toggleShrink();
            }
            isPlaying ? pauseMusic() : playMusic();
        }
    
        function playNext() {
            if (window.innerWidth <= 767 && isShrinked) {
                toggleShrink();
            }
            const index = getRandomIndex();
            audio.src = musicList[index].src;
            updateMusicInfo(index);
            audio.currentTime = 0;
            playCore();
            setTimeout(() => {
                if (window.innerWidth <= 767 && !isShrinked) {
                    isShrinked = true;
                    musicPlayer.classList.add('shrink');
                }
            }, 2000);
        }
    
        playBtn.addEventListener('click', togglePlay);
        playBtn.addEventListener('touchstart', (e) => { e.preventDefault(); togglePlay(); });
        nextBtn.addEventListener('click', playNext);
        nextBtn.addEventListener('touchstart', (e) => { e.preventDefault(); playNext(); });
        musicPlayer.addEventListener('click', (e) => {
            if (e.target === musicPlayer && window.innerWidth <= 767) {
                toggleShrink();
            }
        });
        musicPlayer.addEventListener('touchstart', (e) => {
            if (e.target === musicPlayer && window.innerWidth <= 767) {
                e.preventDefault();
                toggleShrink();
            }
        }, { passive: false });
        audio.addEventListener('ended', playNext);
        audio.addEventListener('timeupdate', saveState);
        window.addEventListener('resize', () => {
            if (window.innerWidth > 767) {
                musicPlayer.classList.remove('shrink');
            }
        });
    
        const hasCache = restoreState();
        if (hasCache) {
            isPlaying && playCore();
        } else {
            playMusic();
        }
    
        function initOnUserAction() {
            if (isInitiated) return;
            playMusic();
            document.removeEventListener('click', initOnUserAction);
            document.removeEventListener('touchstart', initOnUserAction);
        }
        document.addEventListener('click', initOnUserAction);
        document.addEventListener('touchstart', initOnUserAction, { passive: false });
    });
    </script>

2. 音乐文件配置
将 MP3 音乐文件上传至网站/UGC/目录(若无该目录请自行创建)
如需修改音乐文件路径 / 添加 / 替换歌曲,直接修改代码中musicList数组即可,格式如下:

const musicList = [
    { src: '音乐文件路径', name: '歌曲名', singer: '歌手名' },
    // 按需添加更多歌曲
];

3. 基础操作
电脑端:直接点击「播放 / 暂停」「下一曲」按钮操作,全程显示歌曲信息
手机端:
点击按钮自动展开播放器,播放后 2 秒自动收缩
点击播放器空白处,可手动切换收缩 / 展开状态
切歌时自动展开显示新歌曲信息,2 秒后恢复收缩