如何在流星中从服务器向客户端发送变量?

时间:2013-08-02 07:49:34

标签: node.js meteor

我有一个带文字输入和按钮的页面。当我将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 个答案:

答案 0 :(得分:3)

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

Meteor方法中的异步函数

在Meteor的方法中,基本上有两种方法可以使用异步函数:使用future和使用wrapAsync。如果您查看Meteor的来源,您会看到wrapAsync本身使用futurehttps://github.com/meteor/meteor/blob/master/packages/meteor/helpers.js#L90。您也可以直接使用fibers,但it is not recommended

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

'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 FritzwrapAsync使用提供了正确的模式,但是,从提出初始问题开始的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() youtube-dl包的方法。

答案 1 :(得分:2)

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

答案 2 :(得分: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);
});

答案 3 :(得分:0)

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