【问题标题】:fetching mp3 file from MeteorJS and trying to convert it into a Blob so that I can play it从 MeteorJS 获取 mp3 文件并尝试将其转换为 Blob 以便我可以播放它
【发布时间】:2018-08-24 08:35:12
【问题描述】:

我正在玩在 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文件(内容长度与我在MeteorJS服务器端写入磁盘的文件完全相同,并且可以播放)。

但是,我的以下代码不允许我将响应转换为 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 构造函数会创建一个比后端返回的源文件更大的文件。而且我不确定它为什么不播放。

谁能指出我正确的方向?

【问题讨论】:

  • 完全奇怪,我可以确认这一点,并且我尝试了几乎所有可能的方法,即使使用 npm 请求模块但没有机会。然而,内容不是 base64 编码的,而是需要首先放入 UTF8Array 的二进制字符串,但这也不会创建代表可播放文件的 blob。也许你打开了一个问题,因为我认为这与 EJSON 有关,它用于格式化通过 ddp 发送到客户端的内容。
  • 我已经标记了 Meteor 平台的问题。我觉得奇怪的是,我从服务器返回的源代码看起来与我预览文件本身时相同。以ID3开头??似乎也以同样的方式结束。但是,是的,格式一定有问题。您认为有更好的方法将内容流式传输到客户端吗?我的最终目标是将二进制文件存储在 IndexedDB 中以供离线播放。由于 CORS 问题,我必须通过 Meteor/nodejs 服务器进行路由。
  • 我更喜欢 ostrio:files 和 gridfs 但这需要你将文件保存在服务器上
  • 是的,我有一个使用 Mongo 的 gridFS (medium.com/@richard534/…) 的示例,但是是的,我实际上只是想代理它们。我创建了一个小型 nodejs 服务器来执行此操作,但我必须查看是否可以将其捕获并将其存储到 IndexedDB 中。

标签: meteor blob audio-streaming


【解决方案1】:

终于找到了使用request代替Meteor的HTTP的解决方案:

首先您需要安装requestrequest-promise-native,以便轻松将结果返回给客户。

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

现在您只需在 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 与通过节点下载二进制数据有关。这导致不使用字符串,而是使用数据的二进制表示(我不知道如何,但也许它是使用Node Buffer 的后备)。

现在变得有趣了。在您的客户端上,您将不会再收到一个复杂的结果,但要么是错误要么是 Uint8Array,这是有道理的,因为 Meteor 使用 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))
    })
  },
})

【讨论】:

  • 感谢您深入研究这个问题,伙计!我确实找到了绕过自己的方法。我在我的 Meteor 项目 (mhurwi.com/meteor-with-express) 中添加了一个 Express API,并设置了一个使用 RequestRequest-progress 的 REST 端点以分块发送文件。效果很好,并且确实提供了与您的解决方案相同的数组缓冲区。然后使用 Angular,您可以跟踪进度:angular.io/guide/http#intercepting-requests-and-responses
【解决方案2】:

另一种解决方案是向 Meteor 后端添加一个 REST 端点(*使用 Express)。 我们使用 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);
} );

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

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();
});

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-12-08
    • 2018-06-04
    • 2018-03-23
    • 1970-01-01
    • 2017-05-18
    • 2011-02-15
    • 2010-09-09
    相关资源
    最近更新 更多