You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Node.js实现多MP3文件连续流式播放问题求助

解决多MP3文件连续流式播放的问题

直接拼接多个MP3文件的原始二进制数据无法让浏览器连续播放,核心原因是每个MP3文件都包含独立的ID3元数据标签(位于文件头部的ID3v2或尾部的ID3v1),浏览器会将整个响应视为单个MP3文件,当第一个文件的音频帧结束后,就会判定流已终止,不会识别后续的MP3数据。

以下是两种可行的解决方案:

方案一:使用FFmpeg合并为连续音频流(推荐,稳定可靠)

FFmpeg可以自动处理MP3的标签和帧结构,将多个文件合并成浏览器可识别的连续音频流,无需手动处理标签。

步骤:

  1. 安装依赖和FFmpeg:

    • 安装Node.js包:npm install fluent-ffmpeg
    • 确保系统已安装FFmpeg(可通过包管理器安装,如apt install ffmpeg、brew install ffmpeg,或从官网下载)
  2. 修改Express路由代码:

import express from 'express';
import ffmpeg from 'fluent-ffmpeg';
import fs from 'fs';

const app = express();

function startServer() {
  const port = process.env.PORT || 3000;
  app.listen(port, () => {
    console.log(`Server is running on port ${port}`);
  });
}

app.get('/stream', (req, res) => {
  const mp3Files = fs.readdirSync('.').filter(file => file.endsWith('.mp3'));
  if (mp3Files.length === 0) {
    res.status(404).send('No MP3 files found');
    return;
  }

  res.setHeader('Content-Type', 'audio/mpeg');
  res.setHeader('Transfer-Encoding', 'chunked');

  // 构建FFmpeg命令,合并所有MP3文件
  const command = ffmpeg();
  mp3Files.forEach(file => command.input(file));

  command
    .on('error', err => {
      console.error('FFmpeg error:', err);
      res.end();
    })
    .on('end', () => res.end())
    .outputOptions('-c', 'copy') // 直接复制音频流,避免重新编码,提升性能
    .outputOptions('-f', 'mpeg') // 指定输出为MPEG格式,适配浏览器流式播放
    .outputOptions('-metadata', 'title=') // 清空元数据,避免干扰连续播放
    .pipe(res, { end: true });
});

startServer();

方案二:纯Node.js处理,移除ID3标签并拼接音频帧

如果不想依赖FFmpeg,可以使用MP3解析库提取纯音频帧,再拼接写入响应流。

步骤:

  1. 安装依赖:npm install mp3-parser

  2. 修改Express路由代码:

import express from 'express';
import fs from 'fs';
import mp3Parser from 'mp3-parser';

const app = express();

function startServer() {
  const port = process.env.PORT || 3000;
  app.listen(port, () => {
    console.log(`Server is running on port ${port}`);
  });
}

// 提取MP3文件中的纯音频帧(跳过ID3标签)
async function extractAudioFrames(filePath) {
  const fileBuffer = fs.readFileSync(filePath);
  const id3Header = mp3Parser.readId3v2Header(fileBuffer);
  let offset = id3Header ? id3Header.size : 0;

  const frames = [];
  while (offset < fileBuffer.length) {
    try {
      const frameHeader = mp3Parser.readFrameHeader(fileBuffer, offset);
      if (!frameHeader) break;
      frames.push(fileBuffer.slice(offset, offset + frameHeader.frameLength));
      offset += frameHeader.frameLength;
    } catch (e) {
      // 遇到无效数据(如尾部ID3v1标签),停止处理
      break;
    }
  }
  return Buffer.concat(frames);
}

app.get('/stream', async (req, res) => {
  const mp3Files = fs.readdirSync('.').filter(file => file.endsWith('.mp3'));
  if (mp3Files.length === 0) {
    res.status(404).send('No MP3 files found');
    return;
  }

  res.setHeader('Content-Type', 'audio/mpeg');
  res.setHeader('Transfer-Encoding', 'chunked');

  for (const file of mp3Files) {
    console.log(`processing ${file}`);
    const audioFrames = await extractAudioFrames(file);
    res.write(audioFrames);
    // 等待响应缓冲区排空,避免背压问题
    await new Promise(resolve => res.once('drain', resolve));
  }

  res.end();
});

startServer();

验证效果

启动服务器后,在Chrome中访问http://localhost:3000/stream,浏览器会自动开始播放,且播放完第一个MP3后会无缝切换到下一个,直到所有文件播放完成。

内容的提问来源于stack exchange,提问作者Scott

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 09:22:13