从 MeteorJS 获取 mp3 文件并尝试将其转换为 Blob 以便我可以播放它

fetching mp3 file from MeteorJS and trying to convert it into a Blob so that I can play it

正在尝试在 Meteor 中下载和提供 mp3 文件。

我正在尝试在我的 MeteorJS 服务器端下载一个 MP3 文件 (https://www.sample-videos.com/audio/mp3/crowd-cheering.mp3)(以规避 CORS 问题),然后将其传回客户端以在 AUDIO 标签中播放。

在 Meteor 中,您使用 Meteor.call 函数调用服务器方法。配置不多,就是方法调用和回调。

当我 运行 我收到这个方法时:

content: "ID3���@K `�)�<H� e0�)������1������J}��e����2L����������fȹ\�CO��ȹ'�����}$A�Lݓ����3D/����fijw��+�LF�$?��`R�l�YA:A��@�0��pq����4�.W"�P���2.Iƭ5��_I�d7d����L��p0��0A��cA�xc��ٲR�BL8䝠4���T��..etc..", data:null, headers: { accept-ranges:"bytes", connection:"close", content-length:"443926", content-type:"audio/mpeg", date:"Mon, 20 Aug 2018 13:36:11 GMT", last-modified:"Fri, 17 Jun 2016 18:16:53 GMT", server:"Apache", statusCode:200

这是工作的Mp3文件(content-length与我在MeteorJS Server端写入磁盘的文件完全相同,并且可以播放)。

但是,我的以下代码不允许我将响应转换为 BLOB: ```

MeteorObservable.call( 'episode.download', episode.url.url ).subscribe( ( result: any )=> {
  console.log( 'response', result);
  let URL = window.URL;

  let blob = new Blob([ result.content ], {type: 'audio/mpeg'} );
  console.log('blob', blob);
  let audioUrl = URL.createObjectURL(blob);

  let audioElement:any  = document.getElementsByTagName('audio')[0];
  audioElement.setAttribute("src", audioUrl);
  audioElement.play();
})

当我 运行 代码时,Blob 大小错误并且无法播放

Blob(769806) {size: 769806, type: "audio/mpeg"}
size:769806
type:"audio/mpeg"
__proto__:Blob

Uncaught (in promise) DOMException: Failed to load because no supported source was found.

在后端,我只是在使用 import { HTTP } from 'meteor/http'.

的方法中 运行 一个 return HTTP.get( url );

我一直在尝试使用 btoaatob 但这不起作用,据我所知它已经是一个 base64 编码的文件,对吧? 我不确定为什么 Blob 构造函数会创建一个比从后端返回的源文件更大的文件。而且我不确定为什么它不播放。

谁能给我指出正确的方向?

终于找到了使用 request 而不是 Meteor 的 HTTP 的解决方案:

首先您需要安装 requestrequest-promise-native 以便于 return 向客户提供您的结果。

$ meteor npm install --save request request-promise-native

现在你只是 return Meteor 方法中请求的承诺:

server/request.js

import { Meteor } from 'meteor/meteor'
import request from 'request-promise-native'

Meteor.methods({
  getAudio (url) {
    return request.get({url, encoding: null})
  }
})

注意 encoding: null 标志,它导致结果为二进制。我找到了这个 in a comment of an answer related to downloading binary data via node. This causes not to use string but binary representation of the data (I don't know how but maybe it is a fallback that uses Node Buffer).

现在变得有趣了。在您的客户端上,您不会再收到复杂的结果,但是错误或 Uint8Array which makes sense because Meteor uses EJSON 使用 DDP 通过电线发送数据,二进制数据的表示是 Uint8Array,如文档中所述。

因为您可以将 Uint8Array 传入 Blob,所以您现在可以像这样轻松创建 blob:

const blob = new Blob([utf8Array], {type: 'audio/mpeg'})

将所有这些汇总到一个小模板中,如果看起来像这样:

client/fetch.html

<template name="fetch">
    <button id="fetchbutton">Fetch Mp3</button>
    {{#if source}}
        <audio id="player" src={{source}} preload="none" content="audio/mpeg" controls></audio>
    {{/if}}
</template>

client/fetch.js

import { Template } from 'meteor/templating'
import { ReactiveVar } from 'meteor/reactive-var'

import './fetch.html'

Template.fetch.onCreated(function helloOnCreated () {
  // counter starts at 0
  this.source = new ReactiveVar(null)
})

Template.fetch.helpers({
  source () {
    return Template.instance().source.get()
  },
})

Template.fetch.events({
  'click #fetchbutton' (event, instance) {
    Meteor.call('getAudio', 'https://www.sample-videos.com/audio/mp3/crowd-cheering.mp3', (err, uint8Array) => {
      const blob = new Blob([uint8Array], {type: 'audio/mpeg'})
      instance.source.set(window.URL.createObjectURL(blob))
    })
  },
})

替代解决方案是将 REST 端点*使用 Express) 添加到您的 Meteor 后端。 在大文件的情况下,我们使用 requestrequest-progress 来发送数据而不是 HTTP。

在前端,我使用 https://angular.io/guide/http#listening-to-progress-events 捕获块以显示加载器并处理响应。

我可以通过

收听下载
this.http.get( 'the URL to a mp3', { responseType: 'arraybuffer'} ).subscribe( ( res:any ) => {
var blob = new Blob( [res], { type: 'audio/mpeg' });
var url= window.URL.createObjectURL(blob);
window.open(url);
} );

顺便说一句,上面的例子没有显示进度,您需要按照angular文章中的说明实现进度事件。很高兴在完成后将示例更新为我的最终代码。

Meteor 服务器上的 Express 设置:

/* 
  Source:http://www.mhurwi.com/meteor-with-express/
  ## api.class.ts
*/

import { WebApp } from 'meteor/webapp';
const express = require('express');
const trackRoute = express.Router();
const request = require('request');
const progress = require('request-progress');

export function api() {
  const app = express();

  app.use(function(req, res, next) {
    res.header("Access-Control-Allow-Origin", "*");
    res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
    next();
  });
  app.use('/episodes', trackRoute);

  trackRoute.get('/:url', (req, res) => {

    res.set('content-type', 'audio/mp3');
    res.set('accept-ranges', 'bytes');

    // The options argument is optional so you can omit it
    progress(request(req.params.url  ), {
      // throttle: 2000,                    // Throttle the progress event to 2000ms, defaults to 1000ms
      // delay: 1000,                       // Only start to emit after 1000ms delay, defaults to 0ms
      // lengthHeader: 'x-transfer-length'  // Length header to use, defaults to content-length
    })
    .on('progress', function (state) {
      // The state is an object that looks like this:
      // {
      //     percent: 0.5,               // Overall percent (between 0 to 1)
      //     speed: 554732,              // The download speed in bytes/sec
      //     size: {
      //         total: 90044871,        // The total payload size in bytes
      //         transferred: 27610959   // The transferred payload size in bytes
      //     },
      //     time: {
      //         elapsed: 36.235,        // The total elapsed seconds since the start (3 decimals)
      //         remaining: 81.403       // The remaining seconds to finish (3 decimals)
      //     }
      // }
      console.log('progress', state);
    })
    .on('error', function (err) {
      // Do something with err
    })
    .on('end', function () {
      console.log('DONE');
      // Do something after request finishes
    })
    .pipe(res);

  });
  WebApp.connectHandlers.use(app);
}

然后将其添加到您的流星启动中:

import { Meteor } from 'meteor/meteor';
import { api } from './imports/lib/api.class';
Meteor.startup( () => {
   api();
});