NTV、ITV、IPTV电视端开发小总结

2,087 阅读10分钟

这是我参与更文挑战的第19天,活动详情查看: 更文挑战

将最近的电视端项目简单介绍一下,主要适配IPTV、NTV、ITV机顶盒,web端的页游项目部署到机顶盒上面的一些坑点简单罗列一下

前言

机顶盒

机顶盒写页面最大的问题,大概就是遥控器的适配、以及返回键了。不同于键盘鼠标,我们非常有必要根据机顶盒的不同,而觉得你的页面URL是否变化,页面之间的跳转是URL变化还是页内跳转,这点很重要,因为返回键只有一个,而你需要在很多情况下使用它。

此外由于电信机顶盒厂商标准不一,EPG焦点(a标签)样式不能被重置成为一个头疼的问题,至少我没有得到一个官方的接口来处理。鉴于产品的需求,我们不得不设置自己的焦点风格,解决办法就是弃用a标签。弃用之后就必须自己来写移动规则,许多公司的做法是写一个文件来设置焦点的移动以及相关的数据。假如交互复杂,焦点特别多且存在变数,这就是一个复杂的工程。epg插件使用javascript编写,旨在解决焦点移动的问题,另外插件附加了一些图片集的特殊效果。值得注意的是:如果epg本身不存在太多的交互,最好不要使用这个插件。

IPTV

IPTV是中国联通的机顶盒一种,基于chrome浏览器,支持canvas,这点很重要。类似网络电视但又不完全一样主要注意的就是遥控器的返回键的配置,另外由于基于chrome(我做的那一款是这样子的),所以在web端调试与在机顶盒调试差异不是很大,而且localStorage这些东西都是可以正常存取使用的。

// 比如遥控器确定键
isPressEnter = (keyEvent, level) => {
  if (keyEvent && keyEvent.which === 13) {
    if (level === 1) {
      this.props.history.push('/study');
    } else if (level === 2) {
      this.props.history.push('/purchase');
    } else if (level === 3) {
      let disnoss = window.localStorage.getItem('ispayed');
      if (disnoss === 'true') {
      } else {
        this.initKGpay();
      }
    }
  } else {
    return;
  }
};

// 上下选择
isPressBtnSelectLevel = (keyEvent, currentSelectedIndex) => {
  if (keyEvent && (keyEvent.which === 37 || keyEvent.which === 38)) {
    currentSelectedIndex = currentSelectedIndex - 1;
  }
  if (keyEvent && (keyEvent.which === 40 || keyEvent.which === 39)) {
    currentSelectedIndex += 1;
  }
  return currentSelectedIndex;
};

// 存取
componentDidMount() {
  let disno = window.localStorage.getItem('ispayed');
  this.preloadImgs(this.imgs)
    .then(() => {
      this.setState({ imgsBgSrc: this.imgs });
    })
    .catch(err => {
      console.log('请检查网络', err);
    });
}

NTV

这是一款巨坑的机顶盒,同时ntv.js是一个运行在机顶盒浏览器上的web应用快速开发框架。支持上海东方有线(OCN)网内所有NGB高清机顶盒。源代码:git.oschina.net/ntv/ntv.js 源码不多,在那里未必能找到你想要的东西。

NTV.JS的注意事项

UI设计:

机顶盒的输出设备为电视机,电视机的图像重现率比计算机的显示器小,靠近边缘部分的文字与图像可能会在电视机中无法看到,所以在进行应用界面设计时,文字与图像尽量设计在屏幕中间的部位。为了保证页面上的内容可以完全显示在电视机上,需要设置一个安全显示区域。各种型号的电视机,其重现率都是不同的,因此对于安全显示区没有固定的大小,

推荐的安全显示区域分辨率为1120×620,即左右各保留80,上下各保留50。

机顶盒浏览器的显示分辨率是1280×720像素。

设计图上下各保留25px,左右各保留40px的距离,不再安全显示距离的范围内摆放元素。

CSS样式:

机顶盒支持基础的CSS样式属性和派生选择器,例如:margin, padding, float, position, line-height等,各厂商对于基础样式属性的渲染差异性也不大。不支持的CSS样式列表(这里只列举在PC平台浏览器上常用的CSS属性):

伪类,例如: ":active", ":focus", ":visited"等。机顶盒上使用的是遥控器,所以鼠标悬停之类的行为无效。

min-width/height, max-width/height 设置元素最大/最小宽带/高度的属性,无效。

display: inline, list-item, table等,不建议使用。各厂商中间件渲染差异非常大。

z-index,无效(这一点很重要,也很致命)。

CSS3和动画效果:

在支持上各厂商偏差较大,由于机顶盒属弱终端类型(硬件资源弱),内置支持的动画功能有限,而且也不是运营商目前强制要求支持的功能。同样就目前网内的应用设计要求上来讲,使用到CSS3及动画的应用非常少,大多停留在实验室演示阶段。

HTML标签:

避免嵌套层级过深,嵌套层级越深CSS渲染偏差越大,元素位置越难控制。

使用 绝对定位来布局(div + position: absolute;) ,因为机顶盒有固定的分辨率。在PC平台浏览器上不建议使用绝对定位的原因是因为多分辨率,但这种问题不存在于机顶盒上,而且使用绝对定位来布局复杂结构时更容易控制。

横竖列表形式的布局使用 ul+li+float 而不推荐使用 table,因为 table 内元素样式不容易控制。

在无 input 标签的页面不使用 a 标签,因为在机顶盒上浏览器会将 a 标签定义为可通过遥控器选择的元素。

// 放出一段可运行在NTV上的简陋代码
// HTML
<!DOCTYPE html>
<html>
 <head>
  <title> 商品首页 </title>
  <meta http-equiv="content-type" content="text/html; charset=UTF-8">
  <meta name="page-view-size" content="1280*720">
  <link rel="stylesheet" href="../ntv/css/ntv.css">
  <link rel="stylesheet" href="../css/app.css">
 </head>
 <body>
 <div class="container product-index">

    <section class="layout-top-bar">
      <div class="pos-abs logo"><img src="../images/common/logo.png" alt=""></div>
      <div class="pos-abs banner"><img src="../images/common/banner.png" alt=""></div>
      <div class="pos-abs nav-bar">
        <ul>
          <li><img src="../images/common/nav_item1.png" alt="11" name="sale.html"></li>
          <li><img src="../images/common/nav_item6.png" alt="12" name="product-index.html"></li>
          <li><img src="../images/common/nav_item2.png" alt="13" name="cart.html"></li>
          <li><img src="../images/common/nav_item3.png" alt="14" name=""></li>
          <li><img src="../images/common/nav_item4.png" alt="15" name=""></li>
          <li><img src="../images/common/nav_item5.png" alt="16" name="help.html"></li>
        </ul>
        <div class="float-clear"></div>
      </div>
    </section>

    <section class="layout-content">
      <div class="pos-abs content">
        <div class="category">
          <div class="pre-btn"><img src="../images/product/pre_btn.png" alt=""></div>
          <ul>
            <li><img src="../images/test/category1.png"></li>
            <li><img src="../images/test/category2.png"></li>
            <li><img src="../images/test/category3.png"></li>
            <li><img src="../images/test/category4.png"></li>
            <li><img src="../images/test/category5.png"></li>
          </ul>
          <div class="pos-rel next-btn"><img src="../images/product/next_btn.png" alt=""></div>
          <div class="float-clear"></div>
        </div>

        <div class="line"></div>

        <div class="ad">
          <ul>
            <li><img src="../images/test/product_index_ad1.png"></li>
            <li><img src="../images/test/product_index_ad2.png"></li>
            <li><img src="../images/test/product_index_ad3.png"></li>
          </ul>
          <div class="float-clear"></div>
        </div>
      </div>
    </section>


    <!-- 页面焦点元素 start -->
    <section class="layout-focus">
      <div class="pos-abs category-f">
        <ul>
          <li><img src="../images/product/category_item.png" alt="21" name="product-list.html"></li>
          <li><img src="../images/product/category_item.png" alt="22" name="product-list.html"></li>
          <li><img src="../images/product/category_item.png" alt="23" name="product-list.html"></li>
          <li><img src="../images/product/category_item.png" alt="24" name="product-list.html"></li>
          <li><img src="../images/product/category_item.png" alt="25,26" name="product-list.html"></li>
        </ul>
        <div class="float-clear"></div>
      </div>

      <div class="pos-abs ad-f">
        <ul>
          <li><img src="../images/product/ad_item.png" alt="31,32"></li>
          <li><img src="../images/product/ad_item.png" alt="33,34"></li>
          <li><img src="../images/product/ad_item.png" alt="35,36"></li>
        </ul>
        <div class="float-clear"></div>
      </div>
    </section>
    <!-- 页面焦点元素 end -->


</div>
<!--Script
====================================================== -->
 <script type="text/javascript" src="../ntv/js/1common.js"></script>
 <script type="text/javascript" src="../ntv/js/2core.js"></script>
 <script type="text/javascript" src="../ntv/js/3key.js"></script>
 <script type="text/javascript" src="../ntv/js/4navigation.js"></script>
 <script type="text/javascript" src="../ntv/js/5page.js"></script>
 <script type="text/javascript" src="../ntv/js/6ipanel.js"></script>
 <script type="text/javascript" src="../ntv/js/6ngb_h.js"></script>
 <script type="text/javascript" src="../ntv/js/6pc.js"></script>
 <script type="text/javascript" src="../ntv/js/6shdv.js"></script>
 <script type="text/javascript" src="../ntv/js/7msg.js"></script>
 <script type="text/javascript" src="../ntv/js/8stb.js"></script>
 <script type="text/javascript" src="../ntv/js/effect/effect-slidemenu.js"></script>
 <script type="text/javascript">
    (function(){
       ntv.log.console("/page/product-index.html onload");
    })();
 </script>
 </body>
</html>

可运行的CSS代码

// CSS
/*!
 *
 * Copyright 2014-2015 ...
 *
 */

/*-- common
====================================================== */
.logo{left:45px; top:30px;}
.banner{left:0px; top:100px;}
.nav-bar{left:800px; top:40px;}
.nav-bar li{float: left; height: 45px; height: 45px; margin: 0px 30px 0px 0px;}

/*-- page/product-index.html
====================================================== */
.product-index{background-image: url("../images/common/bg.png"); background-repeat: no-repeat;}

.product-index .content{left:0px; top: 170px; height: 550px; width: 1280px; background-image: url("../images/product/list_bg.png"); background-repeat: no-repeat;}

.product-index .content .category {margin: 10px 0px;}
.product-index .content .category .pre-btn{float: left; width:25px; height: 270px; margin: 0px 10px 0px 50px;}
.product-index .content .category .pre-btn img{margin-top:90px;}
.product-index .content .category li{float: left; height: 260px; width: 181px; margin: 0px 18px 0px 18px;}

.product-index .content .category .next-btn{float: left; width:25px; height: 270px; top: -3px;}
.product-index .content .category .next-btn img{margin-top:90px;}

.product-index .line{height:2px; width:1280px; background-image: url("../images/product/line.png"); background-repeat: no-repeat;}

.product-index .content .ad {margin: 20px 0px 0px 20px;}
.product-index .content .ad li{float: left; height: 223px; width: 358px; margin: 0px 20px 0px 20px;}


/* 页面焦点元素 */
.product-index .category-f {left:85px; top:180px;}
.product-index .category-f li{float: left; height: 260px; width: 181px; margin: 0px 18px 0px 18px;}
.product-index .ad-f {left:41px; top:471px;}
.product-index .ad-f li{float: left; height: 223px; width: 358px; margin: 0px 19px 0px 20px;}

在我们编写Javscript时,首先要分清楚2个概念,1是使用W3C标准来处理HTML DOM元素(例如修改标签HTML、文本、状态、形状等),2是使用中间件规范接口来处理音视频等业务功能(例如播放音视频等)。一般在PC浏览器上我们使用例如jQuery框架来简化W3C标准的接口,但机顶盒内置的浏览器不支持jQuery框架的所有属性方法,ntv.js框架暂时只提供了一些主要的方法实现,暂未能提供jQuery框架的所有属性方法实现。

// W3C标准写法:
document.getElementById("div_id").innerHTML = "<p>title<p>";
document.getElementById("img_id").src = "http://ip/images/bg.png";

// jQuery框架写法:
$("#div_id").html("<p>title<p>");
$("#img_id").attr("src", "http://ip/images/bg.png");

// ntv.js框架写法:
$("#div_id").innerHTML = "<p>title<p>";
$("#img_id").src = "http://ip/images/bg.png";

在W3C发布HTML5标准之前,浏览器上实现音视频功能,一般做法是采用Adobe Flash方式。但在机顶盒浏览器上并不是采用这种方式,它有自己的私有标准实现,这个标准就是前面一直提到的 中间件规范。

// ntv.js框架写法:
// 播放HTTP协议的音频
ntv.stb.mediaplayer.play("AUDIO", "http://ip/audio.mp3");
ntv.stb.mediaplayer.pause();
ntv.stb.mediaplayer.resume();
ntv.stb.mediaplayer.stop();
JavaScript调试方法:

// 你可以在引用了框架脚本之后,通过使用如下代码来在页面上开启调试模式和输出调试信息。
ntv.log.debug = true;
ntv.log.console("调试信息")

特别注意,调试信息的显示只支持一屏,不支持自动滚动。所以在调试时尽量精简输出条数。

音视频协议

1. 音频(HTTP协议)

播放:ntv.stb.ipanel.mediaplayer.play("AUDIO", "http://192.168.1.163/demo.mp3"); 暂停播放:ntv.stb.ipanel.mediaplayer.pause(); 恢复播放:ntv.stb.ipanel.mediaplayer.resume(); 停止播放:ntv.stb.ipanel.mediaplayer.stop();

2. 视频(HTTP协议)

播放:ntv.stb.ipanel.mediaplayer.play("HTTP", "http://192.168.1.163/demo.mp4"); 暂停播放:ntv.stb.ipanel.mediaplayer.pause(); 恢复播放:ntv.stb.ipanel.mediaplayer.resume(); 停止播放:ntv.stb.ipanel.mediaplayer.stop();

3. 现网点播

// 使用 <vod />标签,2个主要参数 run_time和content_name。
<vod index="2743912" id="2743912" ServiceName="FOD-TV" ServiceType="SVOD" Preview="0" backstep="1"> 
    <vodpara name="passthru_ip" value="10.27.65.50" /> 
    <vodpara name="mod_app_ip" value="10.27.65.50" /> 
    <vodpara name="srm_ip" value="10.27.65.50" /> 
    <vodpara name="poster_server_ip" value="10.27.65.50" /> 
    <vodpara name="lsc_comm_proxy_ip" value="10.27.65.50" /> 
    <vodpara name="session_gateway_ip" value="10.27.65.50" /> 

    <vodpara name="freq1" value="" />
    <vodpara name="sym_rate1" value="" />
    <vodpara name="qam_mode1" value="" />
    <vodpara name="freq2" value="" />
    <vodpara name="sym_rate2" value="" />
    <vodpara name="qam_mode2" value="" />

    <vodpara name="ApplicationType" value="vod" />
    <vodpara name="run_time" value="00:19:16" />
    <vodpara name="content_name" value="SH-changydh.mpg" />

    <vodpara name="protocol_type" value="rtsp" />
    <vodpara name="rtsp" value="10.27.65.80:18082" />
    <vodpara name="rtsp_vendor" value="OCN.RTSP" />

    <vodpara name="streamTransMode" value="ip_stream" />

    <vodpara name="category" value="免费/免费" />
    <vodpara name="provider" value="OCN" />
    <vodpara name="ServiceCode" value="00003" />
</vod>

ITV

ITV是中国电信的机顶盒,是宽带+机顶盒+电视机的一种组合,有频道和点播等基本功能,默认不能浏览网页,可以通过插入宽带网线来达到上网目的,最常见的是电视家浏览器。

ITV的机顶盒是可以和安卓端联调的,但是机顶盒实际配置落后(1.5G的ROM,512M的RAM),还是别肆意安装应用了,至于缓存就别想了,保活什么就算了。由于机顶盒还是通过遥控器操作,这方面的适配需要参考说明文档,页面开发必定受限,无解,但是总体比NTV好太多。

新特性

1、支持电视盒、Android手机端使用,支持Android 4.2以上手机;支持IOS 9.0以上手机和IPAD使用。兼容遥控器、触屏等操作体验; 2、完整支持视频直播、视频点播方案:不止是一款播放器,还包括完整的自建直播频道的后台系统(采集、发布、流媒体服务器)、点播管理系统; 3、完整的视频加密安全支持:基于点量视频加密内核,支持视频的加密防盗链;可以防止其他播放器盗用自建的直播、点播视频; 4、自主研发播放器内核,支持软硬解自适应播放,优先选择最合适的解码方式; 5、支持MP4/FLV/M3U8/MKV/RMVB/AVI/MPG等大部分常见视频格式,以及自定义加密视频; 6、支持HLS(M3U8)、RTMP、HTTP、RTSP等大部分常见视频协议; 7、关键数据采用so安全加密:对核心的传输验证等,采用so方式,并防止so逆向,相比java层代码容易被人逆向,该方式可以大大提升系统的安全; 8、多国语言支持:可支持多国语言; 9、支持多源自适应加速:可以支持一个视频源有多路服务器资源同时加速,解决卡顿问题; 10、基于点量视频解析技术,可实现国内外几十家视频网站的视频解析聚合; 11、适配兼容过上百款主流TV、电视盒硬件,多年成熟改进,稳定性兼容性极好; 12、UI完全可后台CMS控制前端布局、样式;提供所见即所得的UI编辑模式; 13、支持视频聚合支持,比如针对海外华人客户群,提供youtube片源聚合; 14、完善的广告解决方案:APP首屏广告、直播换台广告、点播片头广告、暂停广告、台标广告、走马灯广告、主界面网页广告区域等; 15、首页布局模块支持静态图片、GIF等多种展现方式,布局、点击行为和广告等均可后台设置; 16、多种统计功能。

最后

最关键电视端付费细节不方便透露,同时调起的付费页面是联通or电信的安卓页,这里需要特别注意和后端设计的接口回调、请求超时处理等等细节,今天的电视端机顶盒的简单分享就到这里,有问题欢迎大家留言,谢谢~