文档章节

[翻译完成] 树莓派搭建Google TV

八宝粥
 八宝粥
发布于 2013/07/03 09:47
字数 2550
阅读 4698
收藏 29

Image By http://korben.info/

Google TV是啥玩意 ?

Google-tv-logo3-l

Google TV是支持自选图像、宽带网络、传统电视信号的综合平台,更附带电视节目搜索功能. 谷歌公布了其新版电视的两个版本, 第一个叫做Buddy Box, 由索尼代工的电视盒并且价格昂贵, 第二个是即将发布的集成电视, 将其电视盒内置到电视机内部.

Google TV界面预览:

google_tv_preview

开发者: 可以为Google TV开发新的网页应用或者把已有的android应用改为适配大尺寸屏幕, 在谷歌的开发者网站可以看到详细介绍

搭建我们自己的Google TV

极客们就是喜欢重复发明轮子, 并且自得其乐. 所以我们使用下列开源技术来搭建我们自己的Google TV:

硬件:

软件:

  • Raspbian系统 – 为树莓派特殊定制的Debian发行版

  • NodeJs

    • Socket.io – 通过websocket远程连接TV

    • 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.

© 著作权归作者所有

八宝粥
粉丝 50
博文 24
码字总数 35970
作品 0
程序员
私信 提问
加载中

评论(7)

素人派
素人派
厉害
花儿笑弯了腰
花儿笑弯了腰
高端啊
八宝粥
八宝粥 博主

引用来自“leo-H”的评论

引用来自“八宝粥”的评论

引用来自“leo-H”的评论

都是翻译的吗,不知树莓派看视频效果怎样,听说它性能比较差啊

两百多块钱的开发板 你期望能有多好的性能? 一般看视频差不多了.....

我是说和pcduino等其他板子相比

树莓派的性能比不过 优势是庞大的社区.....玩的人多 哈哈
leo-H
leo-H

引用来自“八宝粥”的评论

引用来自“leo-H”的评论

都是翻译的吗,不知树莓派看视频效果怎样,听说它性能比较差啊

两百多块钱的开发板 你期望能有多好的性能? 一般看视频差不多了.....

我是说和pcduino等其他板子相比
八宝粥
八宝粥 博主

引用来自“leo-H”的评论

都是翻译的吗,不知树莓派看视频效果怎样,听说它性能比较差啊

两百多块钱的开发板 你期望能有多好的性能? 一般看视频差不多了.....
leo-H
leo-H
都是翻译的吗,不知树莓派看视频效果怎样,听说它性能比较差啊
OpenIoT
OpenIoT
好玩,收藏了!
有时间,我也玩一下!
炸裂!小小树莓派要搭上 Google 的人工智能了

2017 年 1 月 23 日,树莓派(Raspberry Pi)在博客上发出了一则公告,表示 Google 在 2017 年将“非常有范儿地来到”树莓派社区,为“创造者(Maker)”们带来一系列智能工具,其中将包含人...

两味真火
2017/01/25
7.9K
9
用Python让Raspberry Pi“动”起来

【编者按】本文是来自奥松机器人社区的投稿,作者为小强之工,真名贝振权,无线电、电子、嵌入式爱好者。 前段时间,接触了一款在开源硬件界被称为是“人气之王”的树莓派(Raspberry Pi)袖珍...

RagingTyphoon
2015/08/16
380
0
用树莓派构建你自己的微型服务器,可以外网访

第一,你得有一个路由器,否则下面的内容都无法实现了。 第二,你得申请一个免费的动态域名解析,由于我用的是TP-LINK的路由器,只支持花生壳,所以我申请了花生壳的动态域名解析。 申请花生...

maweitao
2014/07/25
23.4K
4
手把手教你做树莓派魔镜-MagicMirror(三)

安装和设置系统 现在将SD卡插进树莓派的卡槽里,插上鼠标键盘,连接电源适配器,上电。 第一次进入系统可能时间较长,因为需要进行一个配置设置,如果顺利的话可以直接进入UI界面。 下面做出...

ersaijun
2018/12/28
0
0
[翻译完成] 用红外遥控器遥控树莓派

概述 本篇教程将会讲解如何用一部红外遥控器来遥控操作树莓派上安装的影音中心. 在开始之前, 你需要按此教程把树莓派搭建为影音中心. 需要的零件 在搭建好了影音中心之后, 你需要去买些零件....

八宝粥
2013/07/25
16.1K
17

没有更多内容

加载失败,请刷新页面

加载更多

前端技术之:Prisma Demo服务部署过程记录

安装前提条件: 1、已经安装了docker运行环境 2、以下命令执行记录发生在MackBook环境 3、已经安装了PostgreSQL(我使用的是11版本) 4、Node开发运行环境可以正常工作 首先需要通过Node包管...

popgis
今天
5
0
数组和链表

数组 链表 技巧一:掌握链表,想轻松写出正确的链表代码,需要理解指针获引用的含义: 对指针的理解,记住下面的这句话就可以了: 将某个变量赋值给指针,实际上就是将这个变量的地址赋值给指...

code-ortaerc
今天
4
0
栈-链式(c/c++实现)

上次说“栈是在线性表演变而来的,线性表很自由,想往哪里插数据就往哪里插数据,想删哪数据就删哪数据...。但给线性表一些限制呢,就没那么自由了,把线性表的三边封起来就变成了栈,栈只能...

白客C
今天
43
0
Mybatis Plus service

/** * @author beth * @data 2019-10-20 23:34 */@RunWith(SpringRunner.class)@SpringBootTestpublic class ServiceTest { @Autowired private IUserInfoService iUserInfoS......

一个yuanbeth
今天
5
0
php7-internal 7 zval的操作

## 7.7 zval的操作 扩展中经常会用到各种类型的zval,PHP提供了很多宏用于不同类型zval的操作,尽管我们也可以自己操作zval,但这并不是一个好习惯,因为zval有很多其它用途的标识,如果自己...

冻结not
昨天
6
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部