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

在Express服务器获取音频时长并传递至客户端的实现方案咨询

在Express服务器获取音频时长并传递至客户端的实现方案咨询

当然可以实现啦!我给你两种实用的方案,你可以根据自己的业务场景来选择:

方案一:实时解析音频流获取时长(适合低频请求场景)

这种方案不需要提前存储额外数据,每次请求时直接解析音频文件的元数据拿到时长,推荐用纯JS库music-metadata来做,不用依赖外部工具,非常方便。

步骤1:安装依赖

在项目根目录执行命令:

npm install music-metadata

步骤2:修改你的Express路由代码

我们需要先解析音频流拿到时长,再把时长放到响应头里,最后重新打开一个流把音频内容返回给客户端(因为解析元数据会消费掉原流):

const mm = require('music-metadata');
const { ObjectId } = require('mongoose').Types;

server.get('/play', async (req, res) => {
  const gridFSBucket = new mongoose.mongo.GridFSBucket(conn.db);
  const dataType = "audio/mp3";
  res.set('Content-Type', dataType);

  if (req.query.afn) {
    const filename = req.query.afn;
    try {
      // 先打开流解析元数据
      const tempStream = gridFSBucket.openDownloadStreamByName(filename);
      const metadata = await mm.parseStream(tempStream, { mimeType: dataType, size: Infinity });
      const duration = metadata.format.duration; // 时长单位:秒

      // 把时长放入自定义响应头
      res.set('X-Audio-Duration', duration.toString());

      // 重新打开流返回音频内容
      const downloadStream = gridFSBucket.openDownloadStreamByName(filename);
      res.set('Content-Disposition', `inline; filename="${filename}"`);
      downloadStream.pipe(res);
    } catch (err) {
      console.error('解析音频元数据出错:', err);
      res.status(500).send('获取音频信息失败');
    }
  }

  if (req.query.id) {
    const recordID = new ObjectId(req.query.id);
    try {
      const tempStream = gridFSBucket.openDownloadStream(recordID);
      const metadata = await mm.parseStream(tempStream, { mimeType: dataType, size: Infinity });
      const duration = metadata.format.duration;

      res.set('X-Audio-Duration', duration.toString());

      const downloadStream = gridFSBucket.openDownloadStream(recordID);
      res.set('Content-Disposition', `inline; filename=APP-Audio`);
      downloadStream.pipe(res);
    } catch (err) {
      console.error('解析音频元数据出错:', err);
      res.status(500).send('获取音频信息失败');
    }
  }
});

方案二:存储音频时提前保存时长(适合高频请求场景)

如果你的音频会被频繁请求,每次都解析流会浪费资源,最好在上传音频到GridFS的时候就把时长存到对应的文件元数据里,后续请求直接读取即可。

步骤1:上传时保存时长

假设你有上传音频的路由,修改它来解析时长并存储:

const mm = require('music-metadata');

server.post('/upload', async (req, res) => {
  const gridFSBucket = new mongoose.mongo.GridFSBucket(conn.db);
  const uploadStream = gridFSBucket.openUploadStream(req.file.originalname);

  try {
    // 解析上传的音频流获取时长
    const metadata = await mm.parseStream(req.file.stream, { mimeType: req.file.mimetype });
    const duration = metadata.format.duration;

    // 上传完成后,把时长更新到GridFS的文件文档中
    uploadStream.on('finish', async () => {
      await conn.db.collection('fs.files').updateOne(
        { _id: uploadStream.id },
        { $set: { duration: duration } }
      );
    });

    req.file.stream.pipe(uploadStream);
    uploadStream.on('finish', () => {
      res.send('音频上传成功');
    });
  } catch (err) {
    console.error('解析音频时长出错:', err);
    res.status(500).send('上传失败');
  }
});

步骤2:播放路由直接读取时长

现在播放时不需要再解析音频,直接从GridFS的文件集合里拿时长:

const { ObjectId } = require('mongoose').Types;

server.get('/play', async (req, res) => {
  const gridFSBucket = new mongoose.mongo.GridFSBucket(conn.db);
  const dataType = "audio/mp3";
  res.set('Content-Type', dataType);

  if (req.query.afn) {
    const filename = req.query.afn;
    // 查询GridFS文件文档获取时长
    const fileDoc = await conn.db.collection('fs.files').findOne({ filename });
    if (!fileDoc) {
      return res.status(404).send('音频不存在');
    }
    res.set('X-Audio-Duration', fileDoc.duration.toString());

    // 返回音频流
    const downloadStream = gridFSBucket.openDownloadStreamByName(filename);
    res.set('Content-Disposition', `inline; filename="${filename}"`);
    downloadStream.pipe(res);
  }

  if (req.query.id) {
    const recordID = new ObjectId(req.query.id);
    const fileDoc = await conn.db.collection('fs.files').findOne({ _id: recordID });
    if (!fileDoc) {
      return res.status(404).send('音频不存在');
    }
    res.set('X-Audio-Duration', fileDoc.duration.toString());

    const downloadStream = gridFSBucket.openDownloadStream(recordID);
    res.set('Content-Disposition', `inline; filename=APP-Audio`);
    downloadStream.pipe(res);
  }
});

客户端如何获取时长?

不管用哪种方案,客户端都可以通过读取响应头来拿到时长,比如用Fetch API的示例:

fetch('/play?afn=your-audio-file.mp3')
  .then(response => {
    // 从响应头获取时长并转成数字
    const duration = parseFloat(response.headers.get('X-Audio-Duration'));
    console.log('音频时长(秒):', duration);
    // 处理音频播放
    return response.blob();
  })
  .then(blob => {
    const audio = new Audio(URL.createObjectURL(blob));
    audio.play();
  })
  .catch(err => console.error('请求出错:', err));

备注:内容来源于stack exchange,提问作者Michel

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.20 09:35:29