Posted at

Raspberry PiでNode.jsを使ってYoutubeストリーミング

More than 3 years have passed since last update.

Raspberry Piを手に入れてまず試しにやってみるのは音楽をストリーミング再生させてみることではないでしょうか。(私だけ?)

コマンドラインを利用すれば簡単にストリーミングさせることができます。

コマンドラインからyoutubeを再生する - Qiita

こちらにあるように


ssh

$ youtube-dl 'https://www.youtube.com/watch?v=hoge' -o - | mplayer -


簡単にストリーミング再生出来ます。


Node.jsでコマンドライン操作

Node.jsのマニュアル & ドキュメンテーションを見ると


app.js

var spawn = require('child_process').spawn,

ls = spawn('ls', ['-lh', '/usr']);

ls.stdout.on('data', function (data) {
console.log('stdout: ' + data);
});

ls.stderr.on('data', function (data) {
console.log('stderr: ' + data);
});

ls.on('close', function (code) {
console.log('child process exited with code ' + code);
});


こんな風に書いてあります。通常のコマンドはこれでいけるのですが、二つ以上のコマンドを組み合わせようとすると(パイプ)上手くいきません。


node.jsでパイプを使ったコマンドを実行するには

node.jsでパイプを使ったコマンドを実行するには - GitHubGist

こちらのGistを拝見したところ、sh -c コマンドをchild_processで実行して、実際に行いたいコマンドは引数に格納して実行すれば良い様です。

分かった様な、分からない様な。(分ってない)試しにapp.jsをこんな風に書いてみました。


app.js

var spawn = require('child_process').spawn;

var cmd = "youtube-dl 'https://www.youtube.com/watch?v=wArJc179HNk' -o - | mplayer - -novideo";

function shspawn(command) {
return spawn('sh', ['-c', command]);
}

var child = shspawn(cmd);
var buf="";

child.stdout.on('data',function(data){
buf=buf+data;
});
child.stderr.on('data',function (data){
console.log('exec error: '+data);
});

child.on('close',function(code) {
console.log("exit.");
});


1時間ほどのプレイリスト - Indie/Indie-Pop Compilation - May 2015 (55-Minute Playlist) ですが上手くストリーミング再生出来ました。


expressを使ってブラウザで操作する

expressを利用して幾つかのプレイリストを操作できる様にしてみました。

PonTube Pi 2016-03-02 23-29-46.png


app.js

var express = require('express');

var ejs = require("ejs");
var spawn = require('child_process').spawn;

var app = express();
app.engine('ejs',ejs.renderFile);

app.get('/', function(req, res){
res.render('pontube.ejs',
{title: 'PonTube Pi'});
});

app.get('/control', function (req, res) {
console.log(req.query);
var id = req.query.id;
var watch = "https://www.youtube.com/watch?v=" + id;

var clear_cmd = "killall mplayer";
//console.log("cmd = "+cmd);

function shspawn(command) {
return spawn('sh', ['-c', command]);
}

var child = shspawn(clear_cmd);
var buf="";

child.stdout.on('data',function(data){
buf=buf+data;
});
child.stderr.on('data',function (data){
//console.log('exec error: '+data);
});

child.on('close',function(code) {
console.log( 'CLEAR. PLAY.' );
});

var play_cmd = "youtube-dl " + watch + " -o - | mplayer - -novideo";

function shspawn(command) {
return spawn('sh', ['-c', command]);
}

var child = shspawn(play_cmd);
var buf="";

child.stdout.on('data',function(data){
buf=buf+data;
});
child.stderr.on('data',function (data){
//console.log('exec error: '+data);
});

child.on('close',function(code) {
console.log( 'EXIT.' );
});

});

app.get('/stop', function (req, res) {
console.log(req.query);
var id = req.query.id;

var stop_cmd = "killall mplayer";
//console.log("cmd = "+cmd);

function shspawn(command) {
return spawn('sh', ['-c', command]);
}

var child = shspawn(stop_cmd);
var buf="";

child.stdout.on('data',function(data){
buf=buf+data;
});
child.stderr.on('data',function (data){
//console.log('exec error: '+data);
});

child.on('close',function(code) {
console.log( 'STOP.' );
});

});

var server = app.listen(3000, function () {
var host = server.address().address
var port = server.address().port
console.log('This app listening at http://192.168.0.12:'+ port)
});



views/pontube.ejs

<!DOCTYPE html>

<html lang="ja">
<head>
<meta http-equiv="content-type"
content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0,user-scalable=no">
<title><%=title %></title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" type="text/css" />
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css">
<style>
body{
margin: 0 auto;
max-width: 640px;
}
article{
margin: 20px;
}
</style>
</head>
<body>
<header>
<h1 class="text-center h2"><%=title %></h1>
</header>
<article>
<div class="list-group">
<button type="button" class="list-group-item" id="wArJc179HNk">Indie/Indie-Pop Compilation - May 2015 (55-Minute Playlist)</button>
<button type="button" class="list-group-item" id="YYYJDalKI9E">Indie/Pop/Folk Compilation - January 2016 (1-Hour Playlist)</button>
<button type="button" class="list-group-item" id="wiKgHFU8L9g">1.5 Hours of Rock n' Roll - Rock/Hard Rock Playlist</button>
<button type="button" class="list-group-item" id="WVP3fUzQHcg">2 HOUR LONG Piano Music for Studying, Concentrating, and Focusing Playlist</button>
<button type="button" class="list-group-item" id="ubZASS3uPrs">Relaxing Love Songs 80's & 90's Playlist</button>
</div>
<button class="btn btn-default" id="stop">stop</button>
</article>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>

<script>
$(".list-group-item").click(function() {
var station = $(this).attr("id"); 
$.get("http://192.168.0.12:3000/control", { id: station } );
});
$(".btn").click(function() {
var stop = $(this).attr("id"); 
$.get("http://192.168.0.12:3000/stop", { id: stop } );
});

</script>

</body>
</html>


コマンドライン操作も良いですが、手元のブラウザから操作出来ると愛着が湧いてきます。

DSC_0001.jpg

音声コマンドと組み合わせても面白いかもしれません。では。