万博网页版登陆页派论坛

用万博网页版登陆页派搭建Google TV

八宝粥 发表于 2013-7-4 00:03:36 | 显示全部楼层 |阅读模式

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

开发者: 可以为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
  • 最终效果
    万博网页版登陆页派TV及其特殊的远程遥控器:

    八宝粥  楼主| 发表于 2013-7-4 00:04:32 | 显示全部楼层
    本帖最后由 八宝粥 于 2013-7-4 00:14 编辑

    步骤
    主要分为4步:
  • 安装软件
  • shell命令及脚本
  • 搭建后台: NodeJS + Express + Socket.io
  • 搭建前端
  • 安装软件:
    安装Raspbian和NodeJS
    按照这篇教程在万博网页版登陆页派上安装Raspbian和Node Js
    安装Chromium和Youtube-dl
    安装Chromium浏览器
    1. sudo apt-get install chromium-browser
    复制代码
    为了显示效果更佳我们可以安装使用MC字体
    1. sudo apt-get install ttf-mscorefonts-installer
    复制代码
    安装并升级Youtube下载器
    1. sudo apt-get install youtube-dl

    2. sudo youtube-dl -U
    复制代码
    注意-1: 现在还无法在万博网页版登陆页派上用Chromium看youtube的视频流, 因为在那种情况下视频并未通过GPU渲染, 会巨卡无比. Youtube-dl是不错的替代方案, 先将视频下载下来然后用OMXPlayer播放, 由于用GPU渲染了视频, 所以播放高清视频比较顺畅.
    注意-2: Raspbian上默认就装了OMXPlayer.
    shell命令及脚本
    如果你在用SSH连接万博网页版登陆页派, 你需要先添加个环境变量“DISPLAY=:0.0″, 执行以下命令
    1. export DISPLAY=:0.0
    复制代码
    执行以下命令可列出全部环境变量
    1. env
    复制代码
    在全屏模式下测试Chromium:
    1. chromium --kiosk http://www.google.com
    复制代码
    测试Youtube-dl
    1. youtube-dl youtube_video_url
    复制代码
    你可以给youtube-dl加几个参数, 比如添加“-o youtube ID [dot] the extension”会自动更改下载文件的名称,  “-f /22/18 ”可以强制下载视频的720p版本. 这里有完整的参数格式列表.
    1. youtube-dl  -o "%(id)s.%(ext)s" -f /22/18 youtube_video_url
    复制代码
    下载视频完成后, 用OMXPLayer来播放
    1. omxplayer youtube_video_file
    复制代码
    可以用键盘快捷键来暂停/恢复视频, 更多快捷键说明看这里
    太棒了! 下面就让我们用Node JS来自动化实现上面的整个过程
    八宝粥  楼主| 发表于 2013-7-4 00:05:11 | 显示全部楼层
    本帖最后由 八宝粥 于 2013-7-4 00:19 编辑

    搭建后台: NodeJS + Express + Socket.io
    下面是源码的目录结构:
  • public
  • js
  • css
  • images
  • fonts
  • index.html
  • remote.html
  • app.js
  • package.json
  • Package.json – npm用来自动安装依赖的JSON文件, 并存储了一些基本信息
    1. { "name": "GoogleTV-rPi",
    2.   "version": "0.0.1",
    3.   "private": false,
    4.   "scripts": { "start": "node app.js" },
    5.   "dependencies": { "express": "3.1.1",
    6.                     "socket.io":"0.9.14",
    7.                     "omxcontrol":"*" }
    8. }
    复制代码
    在创建并修改文件之后, 在应用目录执行下列命令来安装依赖.
    1. npm install
    复制代码
    注意-3: 在安装依赖前会自动创建一个名为node_modules 的文件夹, 如果你使用git, 别忘了要创建一个.gitignore文件并把“ node_modules”写入其中, 在添加git项目时忽略这个文件夹.
    新建一个名为app.js的文件来创建我们的本地HTTP访问服务
    1. var express = require('express')
    2.   , app = express()  
    3.   , server = require('http').createServer(app)
    4.   , path = require('path')
    5. // all environments
    6. app.set('port', process.env.TEST_PORT || 8080);
    7. app.use(express.favicon());
    8. app.use(express.logger('dev')); app.use(express.bodyParser());
    9. app.use(express.methodOverride());
    10. app.use(express.static(path.join(__dirname, 'public')));
    11. //Routes
    12. app.get('/', function (req, res) {
    13.   res.sendfile(__dirname + '/public/index.html');
    14. });

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

    18. server.listen(app.get('port'), function(){
    19.   console.log('Express server listening on port ' + app.get('port'));
    20. });
    复制代码
    上面已经配置了本地访问的路径. 现在我们来测试一下搭建是否成功, 在public/目录中创建index.html和remote.html文件, 写入“Hello, World”, 然后执行命令行
    1. node app.js
    复制代码
    1. npm start
    复制代码
    注意-4: 要在 package.json文件中添加:
    1. ...
    2. "scripts": {
    3.         "start": "node app.js"
    4.     },
    5. ...
    复制代码
    当服务正常启动时会输出"Express server listening on port 8080"
    执行下列命令来测试我们的“Hello, World”页面
    1. node app.js &
    复制代码
    这是在后台启动Node应用的最原始方法, 如果你熟悉node, 你可以用Forever.js这样的模块来自动执行这项简单的任务
    我们的Node应用现在已经在后台启动了, 执行下列命令用chromium在全屏模式下打开我们的Hello, World页面.
    1. chromium --kiosk http://localhost:8080
    复制代码
    八宝粥  楼主| 发表于 2013-7-4 00:05:36 | 显示全部楼层
    本帖最后由 八宝粥 于 2013-7-4 00:21 编辑

    添加Socket.io
    我一直都认为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:
    1. var express = require('express')
    2.   , app = express()  
    3.   , server = require('http').createServer(app)
    4.   , path = require('path')
    5.   , io = require('socket.io').listen(server)
    6.   , spawn = require('child_process').spawn
    复制代码
    并添加以下内容降低日志级别:
    1. //Socket.io Config
    2. io.set('log level', 1);
    复制代码
    现在我们的Socket.io就配好了, 但其还没有任何功能, 现在我们要实现如何处理从客户端发到服务端的消息和事件.
    下面是实现服务端功能的方法, 对应的我们还要实现在客户端实现如何处理消息, 这会在下一章介绍.
    1. io.sockets.on('connection', function (socket) {
    2.     socket.emit('message', { message: 'welcome to the chat' });
    3.     socket.on('send', function (data) {
    4.         //Emit to all
    5.         io.sockets.emit('message', data);
    6.     });
    7. });
    复制代码
    服务端现在会在有新客户端连接后发送消息“message”, 然后等待接收名为“send”的事件来处理数据再回复所有连接的客户端
    在这里我们只有两种类型的客户端: 万博网页版登陆页派的显示器 (屏幕) 和移动Web应用 (远程控制)
    1. var ss; //Socket.io Server
    2. io.sockets.on('connection', function (socket) {
    3.     socket.on("screen", function(data){
    4.         socket.type = "screen";
    5.         //Save the screen socket
    6.         ss = socket;
    7.         console.log("Screen ready...");
    8.     });

    9.     socket.on("remote", function(data){
    10.         socket.type = "remote";
    11.         console.log("Remote ready...");
    12.         if(ss != undefined){
    13.             console.log("Synced...");
    14.         }
    15.     });
    16. )};
    复制代码
    客户端处理Socket通信
    在remote.html和index.html中添加下列内容:
    1. <script src="/socket.io/socket.io.js"> </script>
    2. <script>
    3. //use http://raspberryPi.local if your using Avahi Service  
    4. //or use your RasperryPi IP instead
    5. var socket = io.connect('http://raspberrypi.local:8080');
    6. socket.on('connect', function(data){
    7.     socket.emit('screen');
    8. });
    9. </script>
    复制代码
    八宝粥  楼主| 发表于 2013-7-4 00:06:36 | 显示全部楼层
    本帖最后由 八宝粥 于 2013-7-4 00:23 编辑

    在Node服务器上执行Shell命令
    Node允许我们新建子进程来运行系统命令, 并监听其输入输出. 还能给命令传递参数, 甚至能把一个命令的执行结果重定向给另一个命令.
    在NodeJS中执行shell命令的基本方法:
    1. spawn('echo',['foobar']);
    复制代码
    如果需要重定向输出, 我们需要把下列函数加到app.js文件中:
    1. //Run and pipe shell script output  
    2. function run_shell(cmd, args, cb, end) {
    3.     var spawn = require('child_process').spawn,
    4.         child = spawn(cmd, args),
    5.         me = this;
    6.     child.stdout.on('data', function (buffer) { cb(me, buffer) });
    7.     child.stdout.on('end', end);
    8. }
    复制代码
    添加OMXControl – 可以控制OMXPlayer的Node模块
    我是偶然间在npmjs.org上发现可以控制OMXPlayer的模块!
    把下列内容添加app.js文件中来使用这个模块.
    1. var omx = require('omxcontrol');
    2. //use it with express
    3. app.use(omx());
    复制代码
    这个模块会为我们创建以下访问路径来控制视频的播放:
    1. http://localhost:8080/omx/start/:filename

    2. http://localhost:8080/omx/pause

    3. http://localhost:8080/omx/quit
    复制代码
    太TM帅气鸟!
    八宝粥  楼主| 发表于 2013-7-4 00:08:14 | 显示全部楼层
    本帖最后由 八宝粥 于 2013-7-4 00:25 编辑

    汇总
    最终的app.js文件
    1. /**
    2. * Module dependencies.
    3. */
    4. var express = require('express')
    5.   , app = express()  
    6.   , server = require('http').createServer(app)
    7.   , path = require('path')
    8.   , io = require('socket.io').listen(server)
    9.   , spawn = require('child_process').spawn
    10.   , omx = require('omxcontrol');
    11. // all environments
    12. app.set('port', process.env.TEST_PORT || 8080);
    13. app.use(express.favicon());
    14. app.use(express.logger('dev'));
    15. app.use(express.bodyParser());
    16. app.use(express.methodOverride());
    17. app.use(express.static(path.join(__dirname, 'public')));
    18. app.use(omx());
    19. //Routes
    20. app.get('/', function (req, res) {
    21.     res.sendfile(__dirname + '/public/index.html');
    22. });
    23. app.get('/remote', function (req, res) {
    24.     res.sendfile(__dirname + '/public/remote.html');
    25. });
    26. //Socket.io Congfig
    27. io.set('log level', 1);

    28. server.listen(app.get('port'), function(){
    29.     console.log('Express server listening on port ' + app.get('port'));
    30. });
    31. //Run and pipe shell script output  
    32. function run_shell(cmd, args, cb, end) {
    33.     var spawn = require('child_process').spawn,
    34.         child = spawn(cmd, args),
    35.         me = this;
    36.     child.stdout.on('data', function (buffer) { cb(me, buffer) });
    37.     child.stdout.on('end', end);
    38. }
    39. //Save the Screen Socket in this variable
    40. var ss;
    41. //Socket.io Server
    42. io.sockets.on('connection', function (socket) {
    43.     socket.on("screen", function(data){
    44.         socket.type = "screen";
    45.         ss = socket;
    46.         console.log("Screen ready...");
    47.     });
    48.     socket.on("remote", function(data){ socket.type = "remote";
    49.         console.log("Remote ready...");
    50.     });

    51.     socket.on("controll", function(data){
    52.         console.log(data);
    53.         if(socket.type === "remote"){
    54.             if(data.action === "tap"){
    55.                 if(ss != undefined){
    56.                     ss.emit("controlling", {action:"enter"});
    57.                 }
    58.             } else if(data.action === "swipeLeft"){
    59.                 if(ss != undefined){
    60.                     ss.emit("controlling", {action:"goLeft"});
    61.                 }
    62.             } else if(data.action === "swipeRight"){
    63.                 if(ss != undefined){
    64.                     ss.emit("controlling", {action:"goRight"});
    65.                 }
    66.             }
    67.          }
    68.      });

    69.     socket.on("video", function(data){
    70.         if( data.action === "play"){
    71.             var id = data.video_id,
    72.                 url = "http://www.youtube.com/watch?v="+id;
    73.             var runShell = new run_shell('youtube-dl',
    74.                 ['-o','%(id)s.%(ext)s','-f','/18/22',url],
    75.                 function (me, buffer) {
    76.                     me.stdout += buffer.toString();
    77.                     socket.emit("loading",{output: me.stdout});
    78.                     console.log(me.stdout)
    79.                 },
    80.                 function () {
    81.                     //child = spawn('omxplayer',[id+'.mp4']);
    82.                     omx.start(id+'.mp4');
    83.                 }
    84.             );
    85.         }   

    86.     });
    87. });
    复制代码
    搭建前端
    万博网页版登陆页派TV前端屏幕显示样式:

    关于如何编写这个前端的介绍超出了本教程的范围, 不过我想我会在不久之后发一些在开发中实用的小技巧.
    在为大尺寸屏幕设计时, 你应当遵循一些设计上的考量, Google在其开发者网站上详述了一套他们的标准
    万博网页版登陆页派TV远程控制端样式:

    大部分远程控制端设计粗糙, 充满了样式丑陋的按钮, 所以我决定使用QuoJS, 现在变得又帅气又易用!
    1. $(".r-container").swipeLeft(function(){
    2.     socket.emit('control',{action:"swipeLeft"});
    3. });
    复制代码
    这是如何用”swipeLeft”方法把“Control”消息传回服务器的示例.
    服务器会把这条消息传到屏幕上, 然后根据选择框的指向(Watch, Listen, Play)进行处理
    这里还有几个小技巧能让你的web应用在iphone上看起来像原生应用一样带有好看的Icon和启动画面.
    只需要把下列内容加到HTML的 <head></head>块中
    1. <link rel="apple-touch-icon" href="&rvpu;jnbhft/dvtupn_jdpo.qoh&rvpu;/&hu;"
    2. <link rel="apple-touch-startup-image" href="&rvpu;jnbhft/tubsuvq.qoh&rvpu;&hu;"
    3. <meta name="viewport" content="width=device-width initial-scale=1, maximum-scale=1, user-scalable=no" />
    4. <meta name="apple-mobile-web-app-title" content="Remote">
    5. <meta name="apple-mobile-web-app-capable" content="yes">
    复制代码
    总结
    这个项目仍在不断开发中, 不久之后便会有更新. 如果你喜欢本教程不妨上Github给项目加个星标. 视频也录好了! 请看这里(youtube).

    阿くbia 发表于 2013-7-4 21:14:29 | 显示全部楼层
    吊炸天的感觉,我和我的小伙伴们惊呆了
    tuma 发表于 2013-7-6 10:00:27 | 显示全部楼层
    不能中文吗?
    guiyu 发表于 2013-11-8 10:14:03 | 显示全部楼层
    得先翻墙吧
    返回列表 发新帖
    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    手机版 | Archiver | 万博网页版登陆页派论坛 ( 粤ICP备15075382号-1 )