1. 教程概览

本文将带你完成以下步骤:

  1. 环境准备
  2. 挂载音乐目录到服务器
  3. 配置目录权限
  4. 安装并配置 OpenResty/nginx
  5. 编写 nginx 配置(alias、autoindex、CORS、UTF-8)
  6. 重载并测试访问
  7. 在博客中嵌入播放器
  8. 常见问题与排查

2. 环境准备

  • 操作系统:Linux(Debian/Ubuntu/CentOS 均可)
  • Web 服务器:OpenResty / nginx
  • 音乐存放目录/mnt/big/share/jellyfin/media/music/
  • 域名music.yys.zone 已解析到 NAS 公网/内网 IP
  • SSL 证书:已准备好 fullchain.cerxxx.key

提示:如果还未安装 OpenResty/nginx,可参考官方文档快速安装。


3. 挂载音乐目录到 NAS

假设你的音乐都存在于外接硬盘或另一分区上,已通过 /etc/fstab 或手动挂载到,不是的话可以忽略:

sudo mkdir -p /mnt/big/share/jellyfin/media/music
sudo mount /dev/sdX1 /mnt/big/share/jellyfin/media/music

请根据实际设备路径调整 /dev/sdX1


4. 配置目录权限

nginx(openresty)默认以 nobodywww-data 用户运行,需要对音乐目录和文件授予“其他用户”可读、可进权限:

# 递归授予所有文件可读
sudo chmod -R o+r /mnt/big/share/jellyfin/media/music

# 授予所有目录可进入权限
sudo find /mnt/big/share/jellyfin/media/music -type d -exec chmod o+x {} \;

执行完成后,nginx 即可访问所有子目录与文件。


5. OpenResty/nginx 配置

将以下内容追加到 /usr/local/openresty/nginx/conf/nginx.conf(或你自己的 include 文件):

######################################
# music.yys.zone 静态音乐目录配置
######################################

# HTTP 服务
server {
    listen 80;
    listen [::]:80;
    server_name music.yys.zone;

    # 访问根路径时,直接映射到本地音乐目录
    location / {
        alias /mnt/big/share/jellyfin/media/music/;
        autoindex on;                    # 开启目录浏览
        charset utf-8;                   # 确保中文正常显示
        add_header Access-Control-Allow-Origin *;  # 允许跨域播放
    }
}

# HTTPS 服务
server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name music.yys.zone;

    ssl_certificate     /home/music.yys.zone_ecc/fullchain.cer;
    ssl_certificate_key /home/music.yys.zone_ecc/music.yys.zone.key;
    ssl_protocols       TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers on;
    ssl_ciphers         HIGH:!aNULL:!MD5;

    location / {
        alias /mnt/big/share/jellyfin/media/music/;
        autoindex on;
        charset utf-8;
        add_header Access-Control-Allow-Origin *;
    }
}

配置要点

  • alias
    直接将 URL 路径 /xxx.mp3 映射到磁盘路径 /mnt/.../music/xxx.mp3
  • autoindex on
    生成简易文件列表,访客可直接点击播放或下载。
  • charset utf-8
    保证自动索引页面使用 UTF-8 编码,中文目录/文件名不乱码。
  • Access-Control-Allow-Origin *
    允许跨域资源共享(CORS),以便博客页面上 <audio> 正常播放。

6. 重载并测试

  1. 语法检查
    sudo openresty -t
    # 或 nginx -t
    
  2. 重载配置
    sudo systemctl reload openresty
    # 或 sudo systemctl reload nginx
    
  3. 浏览器访问
    打开:http://music.yys.zone/
    • 能看到完整的目录列表
    • 点任意 .mp3 文件即可在线播放或下载
    • 中文文件夹/文件名亦正常显示

7. 在博客中嵌入播放器

以最简单的 HTML5 <audio> 为例,复制到你的博客文章中:

<h3>试听:My Favorite Track</h3>
<audio controls preload="none">
  <source src="https://music.yys.zone/album1/song.mp3" type="audio/mpeg">
  您的浏览器不支持 audio 元素。
</audio>

如果想要更好看的播放器,可用 APlayer:

<link rel="stylesheet" href="https://unpkg.com/aplayer/dist/APlayer.min.css">
<script src="https://unpkg.com/aplayer/dist/APlayer.min.js"></script>

<div id="aplayer"></div>
<script>
  const ap = new APlayer({
    container: document.getElementById('aplayer'),
    audio: [
      {
        name: 'Song Name',
        artist: 'Artist Name',
        url: 'https://music.yys.zone/album1/song.mp3',
        cover: 'https://link-to-cover.jpg'
      },
      // 可添加多首曲目
    ]
  });
</script>

8. 常见问题与排查

问题现象 可能原因 排查/解决
部分目录打不开 目录或上级目录缺少“其他用户可执行”权限 chmod o+x 目录
文件列表中文乱码 未设置 charset utf-8 location 块加上 charset utf-8;
浏览器无法播放音频 跨域被拦(CORS) 添加 add_header Access-Control-Allow-Origin *;
nginx 启动报 alias 错误 写成 root 或路径格式不对 确保使用 alias /绝对路径/;

9. 小结

通过以上步骤,你已成功:

  1. 在 NAS 上以静态方式挂载本地音乐目录
  2. 配置 nginx 自动列表与 UTF-8 编码
  3. 添加 CORS 头,确保博客页面可播放
  4. 无需后台服务,浏览器直连静态文件,性能开销极小

10.代码

<link rel="stylesheet" href="https://unpkg.com/aplayer/dist/APlayer.min.css">
<script src="https://unpkg.com/aplayer/dist/APlayer.min.js"></script>
/* 一体化浮动播放器容器 */
  #floating-player {
  position: fixed;
  top: 90px; right: 10px;
  width: 50px; height: 50px;
  border-radius: 50%;
  overflow: hidden;
  z-index: 9999;
  background: #fff;
  box-shadow: 0 2px 6px rgba(0,0,0,0.2);
  cursor: pointer;
  transition: width 0.3s, height 0.3s, border-radius 0.3s;
  display: flex; align-items: center; justify-content: center;
}
/* 展开状态 */
#floating-player.expanded {
  width: 320px; height: auto;
  border-radius: 8px;
  padding: 10px;
}
/* 使 APlayer 主体在容器内铺满 */
#floating-player.expanded #aplayer-container .aplayer {
  width: 100% !important;
  max-width: none;
}
/* 封面图 */
#fp-icon img {
  width: 50px; height: 50px;
  border-radius: 50%;
  object-fit: cover;
}
/* 隐藏播放器内容 */
#fp-content {
  display: none;
  width: 100%;
}
/* 展开后显示 */
#floating-player.expanded #fp-content {
  display: block;
}
/* 加载动画 */
#fp-spinner {
  margin: 10px auto;
  width: 24px; height: 24px;
  border: 3px solid rgba(0,0,0,0.1);
  border-top-color: #3498db;
  border-radius: 50%;
  animation: spin 1s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
@keyframes spin-cover {
  from { transform: rotate(0deg); }
  to   { transform: rotate(360deg); }
}
#fp-icon.rotating img {
  animation: spin-cover 5s linear infinite;
}
 <!-- 一体化浮动播放器 -->
 <div id="floating-player">
  <div id="fp-icon">
    <img src="https://music.yys.zone:4000/徐良/犯贱.jpg" alt="Cover" />
  </div>
  <div id="fp-content">
    <div id="fp-spinner"></div>
    <div id="aplayer-container"></div>
  </div>
</div>
    document.addEventListener('DOMContentLoaded', function(){
      const fp      = document.getElementById('floating-player');
      const iconImg = document.querySelector('#fp-icon img');
      const spinner = document.getElementById('fp-spinner');
      const content = document.getElementById('fp-content');

      // const tracks = [
      //   ['https://xxxx/徐良/犯贱.mp3', '犯贱', '徐良', 'https://xxxx/徐良/犯贱.jpg'],
      //   // …可继续添加更多曲目
      // ];

      let metadataTimeout;
      
      // 检测可用音源
      Promise.all(tracks.map(item =>
        fetch(item[0], { method: 'HEAD' })
          .then(res => res.ok ? item : null)
          .catch(() => null)
      )).then(results => {
        const available = results.filter(Boolean);
        if (!available.length) {
          fp.style.display = 'none';
          return;
        }

        // 初始封面
        iconImg.src = available[0][3];

        const audioList = available.map(item => ({
          url:    item[0],
          name:   item[1],
          artist: item[2],
          cover:  item[3],
          lrc:    item[0].replace(/\.\w+$/, '.lrc')
        }));

        // 初始化 APlayer,关键加 lrcType:3
        const ap = new APlayer({
          container: document.getElementById('aplayer-container'),
          fixed:     false,
          autoplay:  false,
          loop:      'all',
          volume:    0.7,
          audio:     audioList,
          lrcType:   3    // 加载外部歌词
        });

        // 隐藏加载动画
        ap.on('canplay', () => {
          clearTimeout(metadataTimeout);
          spinner.style.display = 'none';
        });
        metadataTimeout = setTimeout(() => spinner.style.display = 'none', 5000);

        // 播放时:更新封面、标题、开始旋转
        ap.on('play', () => {
          const idx  = ap.list.index;
          const song = ap.list.audios[idx];
          iconImg.src = song.cover;
          // 标题
          let titleEl = document.getElementById('fp-title');
          if (!titleEl) {
            titleEl = document.createElement('div');
            titleEl.id = 'fp-title';
            titleEl.style.marginTop = '8px';
            titleEl.style.color = '#333';
            titleEl.style.textAlign = 'center';
            content.insertBefore(titleEl, document.getElementById('aplayer-container'));
          }
          titleEl.innerText = `${song.name} — ${song.artist}`;
          // 旋转
          fp.querySelector('#fp-icon').classList.add('rotating');
        });

        // 暂停/结束:停止旋转
        const stopRotate = () => fp.querySelector('#fp-icon').classList.remove('rotating');
        ap.on('pause', stopRotate);
        ap.on('ended', stopRotate);

        // 强制歌词滚动与音频同步
        ap.on('timeupdate', () => {
          ap.lrc && ap.lrc.reset();
        });

        // 点击切换展开/收起
        fp.addEventListener('click', e => {
          if (fp.classList.contains('expanded') && content.contains(e.target)) return;
          fp.classList.toggle('expanded');
        });
      });
    });

显示效果