想在博客首页增加音乐播放功能,找了网上的各种方案,有的需要授权,有的不能和主题完美搭配,正好安装了腾讯的qclaw小龙虾,就让它打造一个侧边栏音乐播放器,思路是用最简单的代码来实现,因为本人收藏了大量无损音乐,所以音乐源也不采用其它网站的音乐,自己上传到云存储来实现。经过几次修改,现在还算初步满意了。

效果见本博客首页

  具体操作是在主题的sidebar.php侧边栏文件中,增加一个调用选项

<?php require_once 'widgets/widget-musicplayer.php'; ?>  <!-- 音乐播放 -->

  新建一个播放器文件widget-musicplayer.php和歌单文件music-list.json。以后直接编辑歌单文件增加歌曲就行了。

  播放器主文件widget-musicplayer.php

<?php

/**
 * 迷你音乐播放器
 *
 * @author  子夜歌
 * @link    https://ziyege.com
 * @update  2026-4-26 添加随机播放功能
 */
if ( ! defined('__TYPECHO_ROOT_DIR__')) {
    exit;
}
// 读取歌单文件
$musicList = json_decode(file_get_contents(__DIR__ . '/music-list.json'), true);
?>
<!-- 迷你音乐播放器 -->
<div class="hh-widget mt-3">
  <div class="widget-title p-2 w-100">
    <div class="widget-title-top-bg" style="background:linear-gradient(45deg, #66bb6a, rgba(255,255,255,0.2));"></div>
    <svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor" style="display:inline-block;vertical-align:middle;margin-right:4px;"><path d="M12 3v10.55A4 4 0 1 0 14 17V7h4V3h-6z"/></svg>清韵悠扬播放器
  </div>
  <div class="widget-content p-2">
    <div id="mini-player" class="mini-player">
      <div class="mini-player-now" id="miniNow">选择歌曲播放</div>
      <div class="mini-player-progress" id="miniProgress">
        <div class="mini-player-progress-bar" id="miniProgressBar"></div>
      </div>
      <div class="mini-player-time">
        <span id="miniCurrentTime">00:00</span><span id="miniTotalTime">00:00</span>
      </div>
      <div class="mini-player-controls">
        <button class="mini-player-btn" id="miniPrev" title="上一首">
          <svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor"><path d="M6 6h2v12H6zm3.5 6l8.5 6V6z"/></svg>
        </button>
        <button class="mini-player-btn mini-player-btn-play" id="miniPlay" title="播放">
          <svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor"><path d="M8 5v14l11-7z"/></svg>
        </button>
        <button class="mini-player-btn" id="miniNext" title="下一首">
          <svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor"><path d="M6 18l8.5-6L6 6v12zM16 6v12h2V6h-2z"/></svg>
        </button>
        <button class="mini-player-btn mini-player-shuffle" id="miniShuffle" title="随机播放">
          <span class="mini-player-shuffle-label">顺序</span>
        </button>
      </div>
      <div class="mini-player-list" id="miniList"></div>
    </div>
    <audio id="miniAudio" preload="metadata"></audio>
  </div>
</div>
<style>
.mini-player{font-size:.85rem;color:var(--font-color-main)}
.mini-player-now{font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-bottom:.4rem;font-size:.9rem}
.mini-player-progress{width:100%;height:4px;background-color:var(--overlay-color-dark-1);border-radius:2px;cursor:pointer;position:relative}
.mini-player-progress-bar{height:100%;width:0;background:linear-gradient(90deg,#66bb6a,#43a047);border-radius:2px;transition:width .15s linear}
.mini-player-time{display:flex;justify-content:space-between;font-size:.7rem;color:var(--font-color-main-light);margin:2px 0 .4rem}
.mini-player-controls{display:flex;justify-content:center;align-items:center;gap:1rem;margin-bottom:.5rem}
.mini-player-btn{background:none;border:none;cursor:pointer;color:var(--font-color-main);padding:4px;border-radius:50%;display:flex;align-items:center;justify-content:center;transition:all .2s ease}
.mini-player-btn:hover{background-color:var(--overlay-color-dark-1)}
.mini-player-btn-play{width:36px;height:36px;background-color:#43a047;color:var(--white)}
.mini-player-btn-play:hover{background-color:#388e3c}
.mini-player-shuffle{padding:4px 12px;border-radius:16px;background-color:var(--overlay-color-dark-1);font-size:.7rem;color:var(--font-color-main-light);transition:all .2s ease}
.mini-player-shuffle:hover{background-color:rgba(0,0,0,.12)}
.mini-player-shuffle.active{background-color:#43a047;color:var(--white)}
.mini-player-shuffle.active:hover{background-color:#388e3c}
.mini-player-shuffle-label{font-size:.7rem}
.mini-player-list{height:10rem;overflow-y:auto;border-top:1px solid var(--border-color-main,rgba(0,0,0,.08));padding-top:.3rem}
.mini-player-list::-webkit-scrollbar{width:2px!important;-webkit-appearance:none;appearance:none}
.mini-player-list::-webkit-scrollbar-track{background-color:transparent}
.mini-player-song{padding:.3rem .4rem;border-radius:4px;cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-size:.8rem;color:var(--font-color-main-light);transition:all .2s ease}
.mini-player-song:hover{background-color:rgba(255,152,0,.1);color:#ff9800}
.mini-player-song.playing{color:#43a047;font-weight:600;background-color:rgba(67,160,71,.08)}
.mini-player-song.playing:hover{background-color:rgba(255,152,0,.1);color:#ff9800}
</style>
<script>
(function(){
  var songs=<?php echo json_encode($musicList); ?>;
  // 随机初始化索引
  var idx=Math.floor(Math.random()*songs.length);
  var shuffle=false; // false=顺序播放, true=随机播放
  var audio=document.getElementById('miniAudio'),
      now=document.getElementById('miniNow'),
      bar=document.getElementById('miniProgressBar'),
      prog=document.getElementById('miniProgress'),
      curT=document.getElementById('miniCurrentTime'),
      totT=document.getElementById('miniTotalTime'),
      list=document.getElementById('miniList'),
      playBtn=document.getElementById('miniPlay'),
      shuffleBtn=document.getElementById('miniShuffle'),
      shuffleLabel=shuffleBtn.querySelector('.mini-player-shuffle-label');
  
  songs.forEach(function(s,i){
    var d=document.createElement('div');
    d.className='mini-player-song';
    d.textContent=(i+1)+'. '+s.t;
    d.onclick=function(){play(i)};
    list.appendChild(d);
  });
  
  function fmt(t){
    if(!t||!isFinite(t))return'--:--';
    var m=Math.floor(t/60),s=Math.floor(t%60);
    return(m<10?'0':'')+m+':'+(s<10?'0':'')+s;
  }
  
  function play(i){
    if(i<0||i>=songs.length)return;
    idx=i;audio.src=songs[i].f;audio.play().catch(function(){});
    now.textContent=songs[i].t;
    playBtn.innerHTML='<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor"><path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"/></svg>';
    highlight();
  }
  
  function highlight(){
    var items=list.children;
    for(var i=0;i<items.length;i++)
      items[i].className=i===idx?'mini-player-song playing':'mini-player-song';
  }
  
  function updateShuffleUI(){
    if(shuffle){
      shuffleBtn.classList.add('active');
      shuffleLabel.textContent='随机';
      shuffleBtn.title='随机播放中,点击切换为顺序播放';
    }else{
      shuffleBtn.classList.remove('active');
      shuffleLabel.textContent='顺序';
      shuffleBtn.title='顺序播放中,点击切换为随机播放';
    }
  }
  
  function getNextIndex(){
    if(shuffle){
      // 随机模式:随机选择一首(避免连续相同)
      var next;
      do{next=Math.floor(Math.random()*songs.length)}while(next===idx&&songs.length>1);
      return next;
    }else{
      return (idx+1)%songs.length;
    }
  }
  
  audio.addEventListener('timeupdate',function(){
    if(audio.duration&&isFinite(audio.duration)){
      bar.style.width=(audio.currentTime/audio.duration*100)+'%';
      curT.textContent=fmt(audio.currentTime);totT.textContent=fmt(audio.duration);
    }
  });
  
  audio.addEventListener('ended',function(){
    play(getNextIndex());
  });
  
  prog.addEventListener('click',function(e){
    if(!audio.duration)return;var r=prog.getBoundingClientRect();
    audio.currentTime=((e.clientX-r.left)/r.width)*audio.duration;
  });
  
  playBtn.onclick=function(){
    if(!audio.src||audio.src===location.href){play(0);return;}
    if(audio.paused){audio.play();playBtn.innerHTML='<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor"><path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"/></svg>';}
    else{audio.pause();playBtn.innerHTML='<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor"><path d="M8 5v14l11-7z"/></svg>';}
  };
  
  document.getElementById('miniPrev').onclick=function(){
    var prevIdx=(idx-1+songs.length)%songs.length;
    play(prevIdx);
  };
  
  document.getElementById('miniNext').onclick=function(){
    play(getNextIndex());
  };
  
  shuffleBtn.onclick=function(){
    shuffle=!shuffle;
    updateShuffleUI();
  };
  
  // 初始化随机播放按钮状态
  updateShuffleUI();
  
  // 页面加载后尝试自动播放(需用户交互才能成功)
  function tryAutoPlay(){
    audio.src=songs[idx].f;
    audio.load();
    audio.play().then(function(){
      now.textContent=songs[idx].t;
      playBtn.innerHTML='<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor"><path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"/></svg>';
      highlight();
    }).catch(function(){
      // 浏览器阻止自动播放,等待用户交互后播放
      now.textContent='点击任意位置开始播放';
      function startOnInteraction(){
        audio.play().then(function(){
          now.textContent=songs[idx].t;
          playBtn.innerHTML='<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor"><path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"/></svg>';
          highlight();
        }).catch(function(){});
        document.removeEventListener('click',startOnInteraction);
        document.removeEventListener('touchstart',startOnInteraction);
        document.removeEventListener('keydown',startOnInteraction);
      }
      document.addEventListener('click',startOnInteraction,{once:true});
      document.addEventListener('touchstart',startOnInteraction,{once:true});
      document.addEventListener('keydown',startOnInteraction,{once:true});
    });
  }
  
  // 延迟一点执行,确保 DOM 完全就绪
  setTimeout(tryAutoPlay,100);
})();
</script>

  歌单文件music-list.json

[
  {"f": "https://***.com/muc/music/a01.mp3", "t": "JinriCP直播秀配乐一"},
  {"f": "https://***.com/muc/music/a02.mp3", "t": "JinriCP直播秀配乐二"},
  {"f": "https://***.com/muc/music/a03.mp3", "t": "Bressanone布列瑟农-马修连恩"}
]