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

Node.js Express中ytdl-core流转Blob及播放列表音频下载问题

问题分析与解决方案

错误根源

你遇到的TypeError: source.on is not a function主要有三个原因:

  1. 浏览器API误用:new Response().blob()是浏览器专属API,Node.js环境不支持,导致返回的不是有效Blob对象。
  2. 异步循环失效:forEach(async song => {})不会等待每个异步操作完成,导致表单还未填充数据就提前发送了响应。
  3. 数据类型不兼容:Node.js版form-data不接受浏览器Blob对象,它仅支持流、Buffer或字符串类型。

推荐解决方案:直接用流填充form-data(内存友好)

批量下载时转Blob会占用大量内存,直接使用ytdl返回的流是最优方案:

1. 修改音频获取函数(返回流而非Blob)

const getAudioAsStream = (videoId) => {
  return ytdl(videoId, {
    quality: "highestaudio",
    filter: "audioonly",
  });
};

2. 修复路由逻辑(等待所有流添加完成并流式响应)

app.post("/", async (req, res) => {
  console.log(`Request: ${req.url}`);
  const form = new FormData();

  try {
    const playlist = await getListOfSongsFromPlaylist(req.body.url);
    console.log('Playlist name: ' + playlist.title);
    console.log('Total songs in playlist: ' + playlist.estimatedItemCount);

    // 用Promise.all等待所有流添加到表单
    await Promise.all(playlist.items.map(async (song) => {
      const audioStream = getAudioAsStream(song.id);
      form.append(song.title, audioStream, {
        filename: `${song.title}.mp3`,
        contentType: 'audio/mpeg',
      });
    }));

    // 设置响应头
    res.setHeader('Content-Type', `multipart/form-data; boundary=${form.getBoundary()}`);
    res.setHeader('Access-Control-Allow-Credentials', 'true');
    res.setHeader('Access-Control-Allow-Origin', '*');

    // 直接将表单流管道到响应,避免内存溢出
    form.pipe(res);
  } catch (err) {
    console.error(err);
    res.status(500).send('下载失败');
  }
});

备选方案:转Blob后处理(不推荐批量场景)

如果确实需要Blob格式,可在Node.js中将流转换为Buffer再转Blob:

1. 新增流转Buffer工具函数

const streamToBuffer = (stream) => {
  return new Promise((resolve, reject) => {
    const chunks = [];
    stream.on('data', chunk => chunks.push(chunk));
    stream.on('end', () => resolve(Buffer.concat(chunks)));
    stream.on('error', reject);
  });
};

2. 修改音频转Blob函数

const getAudioAsBlob = async (videoId) => {
  const stream = ytdl(videoId, {
    quality: "highestaudio",
    filter: "audioonly",
  });
  const buffer = await streamToBuffer(stream);
  return new Blob([buffer], { type: 'audio/mpeg' });
};

3. 修复路由中的表单填充逻辑

app.post("/", async (req, res) => {
  console.log(`Request: ${req.url}`);
  const form = new FormData();

  try {
    const playlist = await getListOfSongsFromPlaylist(req.body.url);
    console.log('Playlist name: ' + playlist.title);
    console.log('Total songs in playlist: ' + playlist.estimatedItemCount);

    await Promise.all(playlist.items.map(async (song) => {
      const audioAsBlob = await getAudioAsBlob(song.id);
      // Node.js Blob需转成Buffer才能被form-data识别
      form.append(song.title, Buffer.from(await audioAsBlob.arrayBuffer()), {
        filename: `${song.title}.mp3`,
        contentType: 'audio/mpeg',
        knownLength: audioAsBlob.size,
      });
    }));

    res.writeHead(200, {
      'Content-Type': `multipart/form-data; boundary=${form.getBoundary()}`,
      'Content-Length': form.getLengthSync(),
      'Access-Control-Allow-Credentials': 'true',
      'Access-Control-Allow-Origin': '*', 
    });

    form.pipe(res);
  } catch (err) {
    console.error(err);
    res.status(500).send('下载失败');
  }
});

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 19:03:21