[翻譯完成] 樹莓派搭建Google TV

Image By http://korben.info/

Google TV是啥玩意 ?

Google-tv-logo3-l

Google TV是支持自選圖像、寬帶網絡、傳統電視信號的綜合平臺,更附帶電視節目搜索功能. 谷歌公佈了其新版電視的兩個版本, 第一個叫作Buddy Box, 由索尼代工的電視盒而且價格昂貴, 第二個是即將發佈的集成電視, 將其電視盒內置到電視機內部.css

Google TV界面預覽:html

google_tv_preview

開發者: 能夠爲Google TV開發新的網頁應用或者把已有的android應用改成適配大尺寸屏幕, 在谷歌的開發者網站能夠看到詳細介紹前端

搭建咱們本身的Google TV

極客們就是喜歡重複發明輪子, 而且自得其樂. 因此咱們使用下列開源技術來搭建咱們本身的Google TV:node

硬件: android

軟件: git

  • Raspbian系統 – 爲樹莓派特殊定製的Debian發行版github

  • NodeJsweb

    • Socket.io – 經過websocket遠程鏈接TVshell

    • Express – 用來處理一些基本的http請求

    • Omxcontrol – 用來控制樹莓派上最棒的視頻播放器OMXPlayer

  • Chromium瀏覽器

  • OMXPlayer

  • Youtube-dl – 一個下載youtube視頻的腳本

  • QuoJS – 在手機網頁上處理滑動手勢

  • HTML5, CSS3, Javascript, 和Moustache模板引擎

  • Youtube API

最終效果

raspberrypi_tv_google_tv

樹莓派TV及其特殊的遠程遙控器

 

步驟

主要分爲4步:

  1. 安裝軟件

  2. shell命令及腳本

  3. 搭建後臺: NodeJS + Express + Socket.io

  4. 搭建前端

安裝軟件:

安裝Raspbian和NodeJS

按照這篇教程在樹莓派上安裝Raspbian和Node Js

安裝Chromium和Youtube-dl

安裝Chromium瀏覽器

sudo apt-get install chromium-browser

爲了顯示效果更佳咱們能夠安裝使用MC字體

sudo apt-get install ttf-mscorefonts-installer

安裝並升級Youtube下載器

sudo apt-get install youtube-dl 

sudo youtube-dl -U

注意-1: 如今還沒法在樹莓派上用Chromium看youtube的視頻流, 由於在那種狀況下視頻並未經過GPU渲染, 會巨卡無比. Youtube-dl是不錯的替代方案, 先將視頻下載下來而後用OMXPlayer播放, 因爲用GPU渲染了視頻, 因此播放高清視頻比較順暢.

注意-2: Raspbian上默認就裝了OMXPlayer.

shell命令及腳本

若是你在用SSH鏈接樹莓派, 你須要先添加個環境變量「DISPLAY=:0.0″, 執行如下命令

export DISPLAY=:0.0

執行如下命令可列出所有環境變量

env

在全屏模式下測試Chromium:

chromium --kiosk http://www.google.com

測試Youtube-dl

youtube-dl youtube_video_url

你能夠給youtube-dl加幾個參數, 好比添加「-o youtube ID [dot] the extension」會自動更改下載文件的名稱,  「-f /22/18 」能夠強制下載視頻的720p版本. 這裏有完整的參數格式列表.

youtube-dl  -o "%(id)s.%(ext)s" -f /22/18 youtube_video_url

下載視頻完成後, 用OMXPLayer來播放

omxplayer youtube_video_file

能夠用鍵盤快捷鍵來暫停/恢復視頻, 更多快捷鍵說明看這裏

太棒了! 下面就讓咱們用Node JS來自動化實現上面的整個過程

搭建後臺: NodeJS + Express + Socket.io

下面是源碼的目錄結構:

  • public

    • js

    • css

    • images

    • fonts

    • index.html

    • remote.html

  • app.js

  • package.json

Package.json – npm用來自動安裝依賴的JSON文件, 並存儲了一些基本信息

{ "name": "GoogleTV-rPi", 
  "version": "0.0.1", 
  "private": false, 
  "scripts": { "start": "node app.js" }, 
  "dependencies": { "express": "3.1.1", 
                    "socket.io":"0.9.14", 
                    "omxcontrol":"*" }
}

在建立並修改文件以後, 在應用目錄執行下列命令來安裝依賴.

npm install

注意-3: 在安裝依賴前會自動建立一個名爲node_modules 的文件夾, 若是你使用git, 別忘了要建立一個.gitignore文件並把「 node_modules」寫入其中, 在添加git項目時忽略這個文件夾.

新建一個名爲app.js的文件來建立咱們的本地HTTP訪問服務

var express = require('express')
  , app = express()  
  , server = require('http').createServer(app)
  , path = require('path') 
// all environments 
app.set('port', process.env.TEST_PORT || 8080);
app.use(express.favicon());
app.use(express.logger('dev')); app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(express.static(path.join(__dirname, 'public'))); 
//Routes
app.get('/', function (req, res) {
  res.sendfile(__dirname + '/public/index.html');
});

app.get('/remote', function (req, res) {
  res.sendfile(__dirname + '/public/remote.html');
});

server.listen(app.get('port'), function(){
  console.log('Express server listening on port ' + app.get('port'));
});

上面已經配置了本地訪問的路徑. 如今咱們來測試一下搭建是否成功, 在public/目錄中建立index.html和remote.html文件, 寫入「Hello, World」, 而後執行命令行

node app.js

npm start

注意-4: 要在 package.json文件中添加:

...
"scripts": {
        "start": "node app.js"
    },
...

當服務正常啓動時會輸出"Express server listening on port 8080"
執行下列命令來測試咱們的「Hello, World」頁面

node app.js &

這是在後臺啓動Node應用的最原始方法, 若是你熟悉node, 你能夠用Forever.js這樣的模塊來自動執行這項簡單的任務

咱們的Node應用如今已經在後臺啓動了, 執行下列命令用chromium在全屏模式下打開咱們的Hello, World頁面.

chromium --kiosk http://localhost:8080

添加Socket.io

我一直都認爲WebSocket是現代web的基礎, 對於Socket.io我認爲其意義重大

當AJAX剛興起的時候, 雖然很神往, 可是開發者總被不一樣瀏覽器處理異步JavaScript和XML請求時不一樣的方式所困擾. jQuery提供了統一的一組函數從而解決了這個噩夢. Socket.io對於WebSocket有一樣做用, 甚至更多!

爲了在全部瀏覽器上提供實時鏈接, Socket.IO會根據運行時選擇傳輸能力最強的方式, 且不須要修改API. 下面是其支持的傳輸協議:

  1. WebSocket

  2. Adobe® Flash® Socket

  3. AJAX long polling

  4. AJAX multipart streaming

  5. Forever Iframe

  6. JSONP Polling

把下列內容添加到app.js文件來整合Socket.io:

var express = require('express')
  , app = express()  
  , server = require('http').createServer(app)
  , path = require('path')
  , io = require('socket.io').listen(server)
  , spawn = require('child_process').spawn

並添加如下內容下降日誌級別:

//Socket.io Config 
io.set('log level', 1);

如今咱們的Socket.io就配好了, 但其尚未任何功能, 如今咱們要實現如何處理從客戶端發到服務端的消息和事件.

下面是實現服務端功能的方法, 對應的咱們還要實如今客戶端實現如何處理消息, 這會在下一章介紹.

io.sockets.on('connection', function (socket) { 
    socket.emit('message', { message: 'welcome to the chat' });
    socket.on('send', function (data) { 
        //Emit to all
        io.sockets.emit('message', data);
    });
});

服務端如今會在有新客戶端鏈接後發送消息「message」, 而後等待接收名爲「send」的事件來處理數據再回復全部鏈接的客戶端

在這裏咱們只有兩種類型的客戶端: 樹莓派的顯示器 (屏幕) 和移動Web應用 (遠程控制)

var ss; //Socket.io Server 
io.sockets.on('connection', function (socket) { 
    socket.on("screen", function(data){ 
        socket.type = "screen"; 
        //Save the screen socket 
        ss = socket;
        console.log("Screen ready...");
    });

    socket.on("remote", function(data){ 
        socket.type = "remote";
        console.log("Remote ready..."); 
        if(ss != undefined){
            console.log("Synced...");
        }
    });
)};

客戶端處理Socket通訊

在remote.html和index.html中添加下列內容:

<script src="/socket.io/socket.io.js"> </script> 
<script> 
//use http://raspberryPi.local if your using Avahi Service  
//or use your RasperryPi IP instead 
var socket = io.connect('http://raspberrypi.local:8080');
socket.on('connect', function(data){ 
    socket.emit('screen');
}); 
</script>

在Node服務器上執行Shell命令

Node容許咱們新建子進程來運行系統命令, 並監聽其輸入輸出. 還能給命令傳遞參數, 甚至能把一個命令的執行結果重定向給另外一個命令. 

在NodeJS中執行shell命令的基本方法:

spawn('echo',['foobar']);

若是須要重定向輸出, 咱們須要把下列函數加到app.js文件中:

//Run and pipe shell script output  
function run_shell(cmd, args, cb, end) { 
    var spawn = require('child_process').spawn,
        child = spawn(cmd, args),
        me = this;
    child.stdout.on('data', function (buffer) { cb(me, buffer) });
    child.stdout.on('end', end);
}

添加OMXControl – 能夠控制OMXPlayer的Node模塊

我是偶然間在npmjs.org上發現能夠控制OMXPlayer的模塊!
把下列內容添加app.js文件中來使用這個模塊.

var omx = require('omxcontrol'); 
//use it with express 
app.use(omx());

這個模塊會爲咱們建立如下訪問路徑來控制視頻的播放:

http://localhost:8080/omx/start/:filename

http://localhost:8080/omx/pause

http://localhost:8080/omx/quit

太TM帥氣鳥!

彙總

最終的app.js文件

/**
 * Module dependencies.
 */ 
var express = require('express')
  , app = express()  
  , server = require('http').createServer(app)
  , path = require('path')
  , io = require('socket.io').listen(server)
  , spawn = require('child_process').spawn
  , omx = require('omxcontrol'); 
// all environments 
app.set('port', process.env.TEST_PORT || 8080);
app.use(express.favicon());
app.use(express.logger('dev'));
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(express.static(path.join(__dirname, 'public')));
app.use(omx()); 
//Routes 
app.get('/', function (req, res) { 
    res.sendfile(__dirname + '/public/index.html');
});
app.get('/remote', function (req, res) { 
    res.sendfile(__dirname + '/public/remote.html');
}); 
//Socket.io Congfig 
io.set('log level', 1);

server.listen(app.get('port'), function(){ 
    console.log('Express server listening on port ' + app.get('port'));
}); 
//Run and pipe shell script output  
function run_shell(cmd, args, cb, end) { 
    var spawn = require('child_process').spawn,
        child = spawn(cmd, args),
        me = this;
    child.stdout.on('data', function (buffer) { cb(me, buffer) });
    child.stdout.on('end', end);
} 
//Save the Screen Socket in this variable 
var ss; 
//Socket.io Server 
io.sockets.on('connection', function (socket) { 
    socket.on("screen", function(data){ 
        socket.type = "screen";
        ss = socket;
        console.log("Screen ready...");
    });
    socket.on("remote", function(data){ socket.type = "remote";
        console.log("Remote ready...");
    });

    socket.on("controll", function(data){ 
        console.log(data); 
        if(socket.type === "remote"){ 
            if(data.action === "tap"){ 
                if(ss != undefined){
                    ss.emit("controlling", {action:"enter"}); 
                }
            } else if(data.action === "swipeLeft"){ 
                if(ss != undefined){
                    ss.emit("controlling", {action:"goLeft"}); 
                }
            } else if(data.action === "swipeRight"){ 
                if(ss != undefined){
                    ss.emit("controlling", {action:"goRight"}); 
                }
            }
         }
     });

    socket.on("video", function(data){ 
        if( data.action === "play"){ 
            var id = data.video_id,
                url = "http://www.youtube.com/watch?v="+id; 
            var runShell = new run_shell('youtube-dl',
                ['-o','%(id)s.%(ext)s','-f','/18/22',url], 
                function (me, buffer) { 
                    me.stdout += buffer.toString();
                    socket.emit("loading",{output: me.stdout});
                    console.log(me.stdout)
                }, 
                function () { 
                    //child = spawn('omxplayer',[id+'.mp4']);
                    omx.start(id+'.mp4');
                }
            );
        }    

    });
});

搭建前端

樹莓派TV前端屏幕顯示樣式:

Raspberry Pi TV Screen Front-end

關於如何編寫這個前端的介紹超出了本教程的範圍, 不過我想我會在不久以後發一些在開發中實用的小技巧.

在爲大尺寸屏幕設計時, 你應當遵循一些設計上的考量, Google在其開發者網站上詳述了一套他們的標準

樹莓派TV遠程控制端樣式:

Raspberry Pi TV Remote

大部分遠程控制端設計粗糙, 充滿了樣式醜陋的按鈕, 因此我決定使用QuoJS, 如今變得又帥氣又易用!

$$(".r-container").swipeLeft(function(){ 
    socket.emit('control',{action:"swipeLeft"}); 
});

這是如何用」swipeLeft」方法把「Control」消息傳回服務器的示例.
服務器會把這條消息傳到屏幕上, 而後根據選擇框的指向(Watch, Listen, Play)進行處理

這裏還有幾個小技巧能讓你的web應用在iphone上看起來像原生應用同樣帶有好看的Icon和啓動畫面.
只須要把下列內容加到HTML的 <head></head>塊中

<link rel="apple-touch-icon" href="images/custom_icon.png"/> 
<link rel="apple-touch-startup-image" href="images/startup.png"> 
<meta name="viewport" content="width=device-width initial-scale=1, maximum-scale=1, user-scalable=no" /> 
<meta name="apple-mobile-web-app-title" content="Remote"> 
<meta name="apple-mobile-web-app-capable" content="yes">

總結

這個項目仍在不斷開發中, 不久以後便會有更新. 若是你喜歡本教程不妨上Github給項目加個星標. 視頻也錄好了! 請看here.

相關文章
相關標籤/搜索