Google TV是支持自選圖像、寬帶網絡、傳統電視信號的綜合平臺,更附帶電視節目搜索功能. 谷歌公佈了其新版電視的兩個版本, 第一個叫作Buddy Box, 由索尼代工的電視盒而且價格昂貴, 第二個是即將發佈的集成電視, 將其電視盒內置到電視機內部.css
Google TV界面預覽:html
開發者: 能夠爲Google TV開發新的網頁應用或者把已有的android應用改成適配大尺寸屏幕, 在谷歌的開發者網站能夠看到詳細介紹前端
極客們就是喜歡重複發明輪子, 而且自得其樂. 因此咱們使用下列開源技術來搭建咱們本身的Google TV:node
硬件: android
樹莓派ios
軟件: git
Raspbian系統 – 爲樹莓派特殊定製的Debian發行版github
NodeJsweb
Socket.io – 經過websocket遠程鏈接TVshell
Express – 用來處理一些基本的http請求
Omxcontrol – 用來控制樹莓派上最棒的視頻播放器OMXPlayer
Chromium瀏覽器
Youtube-dl – 一個下載youtube視頻的腳本
QuoJS – 在手機網頁上處理滑動手勢
HTML5, CSS3, Javascript, 和Moustache模板引擎
Youtube API
樹莓派TV及其特殊的遠程遙控器
主要分爲4步:
安裝軟件
shell命令及腳本
搭建後臺: NodeJS + Express + Socket.io
搭建前端
按照這篇教程在樹莓派上安裝Raspbian和Node Js
安裝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.
若是你在用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來自動化實現上面的整個過程
下面是源碼的目錄結構:
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
我一直都認爲WebSocket是現代web的基礎, 對於Socket.io我認爲其意義重大
當AJAX剛興起的時候, 雖然很神往, 可是開發者總被不一樣瀏覽器處理異步JavaScript和XML請求時不一樣的方式所困擾. jQuery提供了統一的一組函數從而解決了這個噩夢. Socket.io對於WebSocket有一樣做用, 甚至更多!
爲了在全部瀏覽器上提供實時鏈接, Socket.IO會根據運行時選擇傳輸能力最強的方式, 且不須要修改API. 下面是其支持的傳輸協議:
WebSocket
Adobe® Flash® Socket
AJAX long polling
AJAX multipart streaming
Forever Iframe
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..."); } }); )};
在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容許咱們新建子進程來運行系統命令, 並監聽其輸入輸出. 還能給命令傳遞參數, 甚至能把一個命令的執行結果重定向給另外一個命令.
在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); }
我是偶然間在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前端屏幕顯示樣式:
關於如何編寫這個前端的介紹超出了本教程的範圍, 不過我想我會在不久以後發一些在開發中實用的小技巧.
在爲大尺寸屏幕設計時, 你應當遵循一些設計上的考量, Google在其開發者網站上詳述了一套他們的標準
樹莓派TV遠程控制端樣式:
大部分遠程控制端設計粗糙, 充滿了樣式醜陋的按鈕, 因此我決定使用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.