6

我有一个带有文本输入和按钮的页面。当我将 youtube 视频的链接插入文本字段并按下按钮时 - 视频下载到本地文件夹中。

问题:如何将下载视频的本地副本的链接发送回客户端?

更一般的问题:如何将变量从服务器发送到客户端(此变量是临时的,不会存储在任何地方)?

我现在拥有的代码:

客户端代码

if (Meteor.isClient) {
    Path = new Meteor.Collection("path");
    Meteor.subscribe("path");

    Template.hello.events(
        {
            'submit .form' : function() {
                var link = document.getElementById("youtube-url").value;
                Meteor.call('download', link);
                event.preventDefault();
            }
        }
    );
}

服务器代码(“收集”部分不起作用)

if (Meteor.isServer) {
    Meteor.startup(function () {

        Meteor.methods({
            download: function (link) {
                var youtubedl = Npm.require('youtube-dl');
                var Fiber = Npm.require("fibers");
                var dl = youtubedl.download(link, './videos');

                // called when youtube-dl finishes
                dl.on('end', function(data) {
                  console.log('\nDownload finished!');
                  Fiber(function() { 
                      Path = new Meteor.Collection("path");
                      Path.insert({path: './videos/' + data.filename});
                  })
                });              
            }
        });
    });
}

谢谢!

4

4 回答 4

3

问题的答案分为两部分:(a)在 Meteor 的方法中处理异步函数和(b)使用youtube-dlpackage.json 。

Meteor 方法中的异步函数

在 Meteor 的方法中,基本上有 2+ 种方法可以使用异步函数: usingfuture和 using wrapAsync. 如果您查看 Meteor 的来源,您会看到它wrapAsync本身使用futurehttps ://github.com/meteor/meteor/blob/master/packages/meteor/helpers.js#L90 。也可以fibers直接使用,但不推荐

以下是如何使用它们的通用示例:

'use strict';

if (Meteor.isClient) {

   Template.methods.events({

    'click #btnAsync' : function() {
      console.log('Meteor.call(asyncMethod)');
      Meteor.call('asyncMethod', 1000, function(error, result) {
        if (error) {
          console.log('Meteor.call(asyncMethod): error:', error);
        } else {
          console.log('Meteor.call(asyncMethod): result:', result);
        }
      });
    },

    'click #btnFuture' : function() {
      console.log('Meteor.call(futureMethod)');
      Meteor.call('futureMethod', 1000, function(error, result) {
        if (error) {
          console.log('Meteor.call(futureMethod): error:', error);
        } else {
          console.log('Meteor.call(futureMethod): result:', result);
        }
      });
    },

    'click #btnFiber' : function() {
      console.log('Meteor.call(fiberMethod)');
      Meteor.call('fiberMethod', 1000, function(error, result) {
        if (error) {
          console.log('Meteor.call(fiberMethod): error:', error);
        } else {
          console.log('Meteor.call(fiberMethod): result:', result);
        }
      });
    }

  });

}

if (Meteor.isServer) {

  var demoFunction = function(duration, callback) {
    console.log('asyncDemoFunction: enter.');
    setTimeout(function() {
      console.log('asyncDemoFunction: finish.');
      callback(null, { result: 'this is result' });
    }, duration);
    console.log('asyncDemoFunction: exit.');
  };

  var asyncDemoFunction = Meteor.wrapAsync(demoFunction);

  var futureDemoFunction = function(duration) {
    var Future = Npm.require('fibers/future');
    var future = new Future();

    demoFunction(duration, function(error, result) {
      if (error) {
        future.throw(error);
      } else {
        future.return(result);
      }
    });
    return future.wait();
  };

  var fiberDemoFunction = function(duration) {
    var Fiber = Npm.require('fibers');
    var fiber = Fiber.current;

    demoFunction(duration, function(error, result) {
      if (error) {
        fiber.throwInto(new Meteor.Error(error));
      } else {
        fiber.run(result);
      }
    });

    return Fiber.yield();
  };

  Meteor.methods({

    asyncMethod: function (duration) {
      return asyncDemoFunction(duration);
    },
    futureMethod: function (duration) {
      return futureDemoFunction(duration);
    },
    fiberMethod: function (duration) {
      return fiberDemoFunction(duration);
    }

  });
}

您可能还想查看Meteor.bindEnvironment()future.resolver()处理更复杂的案例。

Christian Fritz提供了正确的wrapAsync使用模式,但是,从提出最初的问题开始的 2 年内,youtube-dl包的 API 发生了变化。

使用youtube-dl

由于 API 的更改,如果您运行他的代码,服务器会在其控制台中抛出可见的异常:

Exception while invoking method 'download' TypeError: Object function (videoUrl, args, options) {
...
} has no method 'download'

Meteor 返回客户端undefined值:

here is the path: undefined

下面的代码正在运行(只需将 downloadDir 替换为您的路径)并将文件名返回给客户端:

here is the path: test.mp4


文件index.html

<head>
  <title>meteor-methods</title>
</head>
<body>
  {{> hello}}
</body>

<template name="hello">
  <form>
    <input type="text" id="youtube-url" value="https://www.youtube.com/watch?v=alIq_wG9FNk">
    <input type="button" id="downloadBtn" value="Download by click">
    <input type="submit" value="Download by submit">
  </form>
</template>

文件index.js

'use strict';

if (Meteor.isClient) {
  //Path = new Meteor.Collection("path");
  //Meteor.subscribe("path");

  Template.hello.events(
    {
      'submit .form, click #downloadBtn' : function() {
        var link = document.getElementById("youtube-url").value;

        //Meteor.call('download', link);
        Meteor.call('download', link, function(err, path) {
          if (err) { 
            console.log('Error:', err); 
          } else {
            console.log("here is the path:", path);
          }
        });

        event.preventDefault();
      }
    }
  );
}

if (Meteor.isServer) {

  var fs = Npm.require('fs');
  var youtubedl = Npm.require('youtube-dl');

  var downloadSync = Meteor.wrapAsync(function(link, callback) {
    var fname = 'test.mp4';
    // by default it will be downloaded to 
    // <project-root>/.meteor/local/build/programs/server/ 
    var downloadDir = './'; 
    console.log('\nStarting download...');

    // var dl = youtubedl.download(link, './videos');
    var dl = youtubedl(link, [], []);
    dl.on('info', function(info) {
      console.log('\nDownload started: ' + info._filename);
    });
    // dl.on('end', function(data) {
    dl.on('end', function() {
      console.log('\nDownload finished!');
      //callback(null, './videos/' + data.filename);
      callback(null, fname);
    });
    dl.on('error', function(error) {
      console.log('\nDownload error:', error);
      callback(new Meteor.Error(error.message) );
    });
    dl.pipe(fs.createWriteStream(downloadDir + fname));
  });

  Meteor.methods({
    download: function (link) {
      return downloadSync(link);
    }
  });

}

当前 API 不允许在保存文件时获取 youtube 的文件名。如果要使用 youtube 的文件名(如初始问题中提供的)保存文件,则需要使用getInfo()package.json 方法youtube-dl

于 2015-12-26T21:18:57.720 回答
2

您可以使用这个小包:https ://atmosphere.meteor.com/package/client-call 。它允许以与其他方式相同的方式从服务器调用客户端方法Meteor.methods

于 2013-08-02T10:52:50.950 回答
0

您必须在方法定义中使用 async Future,如本答案中所述。然后,只有在异步下载操作完成后,您才能等待回调客户端

于 2015-12-21T20:45:23.727 回答
0

我认为如果你只是从方法调用中返回你想要的路径会容易得多。要做到这一点,您需要做的就是使 youtube 下载同步——这是一种流星式的做事方式。

这应该有效:

if (Meteor.isServer) {
    var youtubedl = Npm.require('youtube-dl');
    var sync = Meteor.wrapAsync(function(url, callback) {
        var dl = youtubedl.download(link, './videos');
        dl.on('end', function(data) {
            console.log('\nDownload finished!');
            callback(null, './videos/' + data.filename);
        });
    });

    Meteor.methods({
        download: function (link) {
            return sync(link);
        }
    });
}

然后,在客户端上,使用:

Meteor.call('download', link, function(err, path) {
    console.log("here is the path:", path);
});
于 2015-12-21T17:28:32.623 回答