I have a page with text input and a button. When I insert link to youtube video into text field and press the button - video downloads into the local folder.
The problem: how can I send link to local copy of the downloaded video back to the client?
More general question: How can I send a variable from server to client (this variable is temporary and is not going to be stored anywhere) ?
The code I have right now:
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});
})
});
}
});
});
}
Thanks!
The answer to the question splits into 2 parts: (a) handling async functions inside Meteor's methods and (b) using youtube-dl
package.
There are basically 2+ ways to work with async functions inside Meteor's methods: using future
and using wrapAsync
. If you look into Meteor's sources, you'll see that wrapAsync
itself using future
: https://github.com/meteor/meteor/blob/master/packages/meteor/helpers.js#L90. You can also use fibers
directly, but it is not recommended.
Below are generic examples how to use them:
'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);
}
});
}
You may also want look to Meteor.bindEnvironment()
and future.resolver()
for more complex cases.
Christian Fritz provided correct pattern for wrapAsync
usage, however, during 2 years starting from the time the initial question was asked, the API of youtube-dl
package has changed.
youtube-dl
packageBecause of changes in API, if you run his code, the server throws the exception visible in its console:
Exception while invoking method 'download' TypeError: Object function (videoUrl, args, options) {
...
} has no method 'download'
And Meteor returns to client undefined
value:
here is the path: undefined
The code below is working (just replace downloadDir with your path) and returning filename to the client:
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>
File 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);
}
});
}
Current API does not allow to get youtube's filename when saving the file. If you want to save the file with the youtube's filename (as provided in initial question), you need to use getInfo()
method of youtube-dl
package.
You can use this small package: https://atmosphere.meteor.com/package/client-call . It allows to call client-side methods from the server in the same way as Meteor.methods
do for the other way.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With