一种视频预加载的方案

标签: geek | 发表时间:2017-01-06 00:00 | 作者:
出处:http://itindex.net/admin/pagedetail

前言:视频的预加载是提高用户体验的重要因素。预加载成为网络视频播放不可或缺的一个技术环节。


预加载的形式:

1.边存边播:下载多少播放多少。

优点:快速加载播放,实现简单;缺点:不能拖动未存区域;适合音频媒体

2.代理服务器:预先下载媒体的头部(头部Size为 s1 byte)->监听播放器的请求,当Request的是预加载的URL->代理把媒体头部作为Response返回给播放器,并改Ranage 为 s1 byte 发送Request->代理服务器纯粹作为透传。

优点:快速加载播放,支持拖动;缺点:实现非常复杂;适合视频媒体


影响预加载的因素:

  • 网络状态

  • 缓冲文件大小

  • 视频码率


码率低、网速快的情况没必要使用预加载,码率中等、网速一般的情况合适使用。另外,缓冲文件也不能设置太大:过大的缓冲区会刷爆MediaPlayer内置的缓冲区,影响正常播放;再者,读取缓冲文件也耗时。


预加载场景:

电视剧播放第1集时,在快要结束前5分钟,开始加载第2集。播放时就会连贯起来。缩短加载时间,如当系统检测您的网络环境较差时,会提示您开启预加载模式,开启预加载模式后,系统将为您缓冲整部影片。为了您流畅观看视频,建议您在开启预加载模式5分钟后,再次点击播放按钮,开始播放。这时就不会卡在缓冲中的状态。就是在视频播放的时候,在播放器通过本地URL,请求视频数据时,本地代理截取这次请求,经过本地代理逻辑,向服务器或本地缓存请求数据。本地代理在获得视频数据后,将数据转给播放器,实现播放。相比直接请求视频数据,本地代理的优势在于,buffer由本地控制,提大提升视频播放速度。提高用户体验。


一张图看清本地代理:



数据预加载:

不同rom上的MediaPlayer是不同的,会有一些差异,取决rom多媒体团队对MediaPlayer的定制程度,例如:有些MediaPlayer首次播放从头buffer,有些MdiaPlayer首次播放会多次Request,Range到网络媒体文件的头部、中间和文件尾,再从指定位置buffer。系统播放器需要下载5s的数据才开始把buffer进行播放出来。5s的数据,如果网络差的话,就处于是buffering中,一旦有5s的数据,就先播起来,而后,再在背后预加载,如一般播放panel的seekbar,有两种颜色,一种颜色(下图蓝色)是当前正在播的位置,还是一种颜色(灰色)走在前面,就是加载好的数据。



效果图1:


效果图2:



实现思路及部分代码:

MainActivity.用VideoView进行视频播放,url的视频为【鬼吹灯之精绝古城第1集】,会有一个开关,决定是否预加载。默认打开。


HttpProxy,就是启动一个本地代理,用127.0.0.1来替换视频源的服务器地址。

   
  1. packagecom.hejunlin.videopreloaded;

  2. importandroid.util.Log;


  3. publicclassHttpProxy{

  4.    publicstaticfinalintSIZE = (int) (5*1024*1024);

  5.    publicstaticfinalStringTAG =HttpProxy.class.getSimpleName();

  6.    privateintremotePort = -1;

  7.    privateStringremoteHost;

  8.    privateintlocalPort;

  9.    privateStringlocalHost;

  10.    privateServerSocketlocalServer =null;

  11.    privateSocketsckPlayer =null;

  12.    privateSocketsckServer =null;

  13.    privateSocketAddressserverAddress;


  14.    privateHandleDownLoaddownload =null;

  15.    /**

  16.     * 初始化代理服务器

  17.     *

  18.     * @param localport

  19.     *            代理服务器监听的端口

  20.     */

  21.    publicHttpProxy(intlocalport) {

  22.        try{

  23.            localPort = localport;

  24.            localHost =Contants.LOCAL_IP_ADDRESS;

  25.            localServer =newServerSocket(localport,1,

  26.                    InetAddress.getByName(localHost));

  27.        }catch(Exceptione) {

  28.            System.exit(0);

  29.        }

  30.           URI tmpURI =newURI(urlString);

  31.        StringfileName =Utils.urlToFileName(tmpURI.getPath());

  32.        StringfilePath =Contants.getBufferDir() +"/"+ fileName;

  33.        download =newHandleDownLoad(urlString, filePath, size);

  34.        download.startThread();

  35.        returnfilePath;

  36.    }

  37.    /**

  38.     * 把网络URL转为本地URL,127.0.0.1替换网络域名

  39.     *

  40.     * @param url网络URL

  41.     * @return [0]:重定向后MP4真正URL,[1]:本地URL

  42.     */

  43.    publicString[] getLocalURL(StringurlString) {

  44.        

  45.        StringtargetUrl =Utils.getRedirectUrl(urlString);

  46.        // ----获取对应本地代理服务器的链接----//

  47.        StringlocalUrl =null;

  48.        URI originalURI = URI.create(targetUrl);

  49.        remoteHost = originalURI.getHost();

  50.        if(originalURI.getPort() != -1) {// URL带Port

  51.            serverAddress =newInetSocketAddress(remoteHost,

  52.                    originalURI.getPort());// 使用默认端口

  53.            remotePort = originalURI.getPort();// 保存端口,中转时替换

  54.            localUrl = targetUrl.replace(

  55.                    remoteHost +":"+ originalURI.getPort(), localHost +":"

  56.                            + localPort);

  57.        }else{// URL不带Port

  58.            serverAddress =newInetSocketAddress(remoteHost,Contants.HTTP_PORT);// 使用80端口

  59.            remotePort = -1;

  60.            localUrl = targetUrl.replace(remoteHost, localHost +":"

  61.                    + localPort);

  62.        }

  63.        String[] result =newString[] { targetUrl, localUrl };

  64.        returnresult;

  65.    }

  66.    /**

  67.     * 异步启动代理服务器

  68.     *

  69.     * @throws IOException

  70.     */

  71.    publicvoidasynStartProxy() {

  72.        newThread() {

  73.            publicvoidrun() {

  74.                startProxy();

  75.            }

  76.        }.start();

  77.    }

  78.    privatevoidstartProxy() {

  79.        HttpParserhttpParser =null;

  80.        HttpProxyUtilsutils =null;

  81.        intbytes_read;

  82.        byte[] local_request =newbyte[1024];

  83.        byte[] remote_reply =newbyte[1024];

  84.        while(true) {

  85.            booleansentResponseHeader =false;

  86.            try{// 开始新的request之前关闭过去的Socket

  87.                if(sckPlayer !=null)

  88.                    sckPlayer.close();

  89.                if(sckServer !=null)

  90.                    sckServer.close();

  91.            }catch(IOExceptione1) {

  92.            }

  93.            try{

  94.                // --------------------------------------

  95.                // 监听MediaPlayer的请求,MediaPlayer->代理服务器

  96.                // --------------------------------------

  97.                sckPlayer = localServer.accept();

  98.                Log.e(TAG,

  99.                        "------------------------------------------------------------------");

  100.                if(download !=null&& download.isDownloading())

  101.                    download.stopThread(false);

  102.                httpParser =newHttpParser(remoteHost, remotePort, localHost,

  103.                        localPort);

  104.                utils =newHttpProxyUtils(sckPlayer, sckServer,

  105.                        serverAddress);

  106.                HttpParser.ProxyRequestrequest =null;

  107.                while((bytes_read = sckPlayer.getInputStream().read(

  108.                        local_request)) != -1) {

  109.                    byte[] buffer = httpParser.getRequestBody(local_request,

  110.                            bytes_read);

  111.                    if(buffer !=null) {

  112.                        request = httpParser.getProxyRequest(buffer);

  113.                        break;

  114.                    }

  115.                }

  116.                booleanisExists =newFile(request._prebufferFilePath)

  117.                        .exists();

  118.                if(isExists)

  119.                    Log.e(TAG,">> prebuffer size:"+ download.getDownloadedSize());

  120.                sckServer = utils.sentToServer(request._body);

  121.                // ------------------------------------------------------

  122.                // 把网络服务器的反馈发到MediaPlayer,网络服务器->代理服务器->MediaPlayer

  123.                // ------------------------------------------------------

  124.                while((bytes_read = sckServer.getInputStream().read(

  125.                        remote_reply)) != -1) {

  126.                    if(sentResponseHeader) {

  127.                        try{// 拖动进度条时,容易在此异常,断开重连

  128.                            utils.sendToMP(remote_reply, bytes_read);

  129.                        }catch(Exceptione) {

  130.                            break;// 发送异常直接退出while

  131.                        }

  132.                        continue;// 退出本次while

  133.                    }

  134.                    List<byte[]> httpResponse = httpParser.getResponseBody(

  135.                            remote_reply, bytes_read);

  136.                    if(httpResponse.size() ==0)

  137.                        continue;// 没Header则退出本次循环

  138.                    sentResponseHeader =true;

  139.                    StringresponseStr =newString(httpResponse.get(0));

  140.                    Log.e(TAG,">> responseStr "+ responseStr);

  141.                    // send http header to mediaplayer

  142.                    utils.sendToMP(httpResponse.get(0));

  143.                    if(isExists) {// 需要发送预加载到MediaPlayer

  144.                        isExists =false;

  145.                        intsentBufferSize =0;

  146.                        try{

  147.                            sentBufferSize = utils.sendPrebufferToMP(

  148.                                    request._prebufferFilePath,

  149.                                    request._rangePosition);

  150.                        }catch(Exceptionex) {

  151.                            break;

  152.                        }

  153.                        if(sentBufferSize >0) {// 成功发送预加载,重新发送请求到服务器

  154.                            intnewRange = (int) (sentBufferSize + request._rangePosition);

  155.                            StringnewRequestStr = httpParser

  156.                                    .modifyRequestRange(request._body, newRange);

  157.                            Log.e(TAG +"-pre->", newRequestStr);

  158.                            // 修改Range后的Request发送给服务器

  159.                            sckServer = utils.sentToServer(newRequestStr);

  160.                            // 把服务器的Response的Header去掉

  161.                            utils.removeResponseHeader(httpParser);

  162.                            continue;

  163.                        }

  164.                    }

  165.                    // 发送剩余数据

  166.                    if(httpResponse.size() ==2) {

  167.                        utils.sendToMP(httpResponse.get(1));

  168.                    }

  169.                }

  170.                Log.e(TAG,">> preloaded over");

  171.                // 关闭 2个SOCKET

  172.                sckPlayer.close();

  173.                sckServer.close();

  174.            }catch(Exceptione) {

  175.                Log.e(TAG, e.toString());

  176.                Log.e(TAG,Utils.getExceptionMessage(e));

  177.            }

  178.        }

  179.    }

  180. }

HttpPaser就是解析真实地址的类,这里就不再给出。

第一时间获得博客更新提醒,以及更多 android干货,源码分析,欢迎关注我的微信公众号,扫一扫下方二维码或者长按识别二维码,即可关注。



相关 [视频 加载] 推荐:

一种视频预加载的方案

- - IT瘾-geek
前言:视频的预加载是提高用户体验的重要因素. 预加载成为网络视频播放不可或缺的一个技术环节. 1.边存边播:下载多少播放多少. 优点:快速加载播放,实现简单;缺点:不能拖动未存区域;适合音频媒体. 2.代理服务器:预先下载媒体的头部(头部Size为 s1 byte)->监听播放器的请求,当Request的是预加载的URL->代理把媒体头部作为Response返回给播放器,并改Ranage 为 s1 byte 发送Request->代理服务器纯粹作为透传.

Android 视频缩略图的缓存机制和异步加载

- - 移动开发 - ITeye博客
关注微信号:javalearns   随时随地学Java. 在这次的工作开发项目中,涉及到一个视频缩略图的视频列表;这个在大家看来,制作视频缩略图就是两行代码就搞定的事. 确实是这样的,百度一下,每个 帖子都知道制作视频缩略图的方法,在这里确实也是一样的,但是我要实现的是让缩略图 在ListView上显示,而且不会出现卡顿,其实也不是特别难,确实很实用;.

宅视频:裸

- 小鱼儿 - 宅映像
明天公司组织去贵州走红路,唱红歌,一去就是三天,所以这三天俺估计就没法发博客了,等我被红歌洗脑后回来再给大家继续胸奴吧. 今天要给大家推荐的是一段视频,名字叫《裸》是不是很带感,至于内容,大家看了就知道了. 这段关于女优和AV的故事片是不是相当泪流满面(坑爹啊. 里面竟然没有我们想要的AV镜头,这才真是让我泪流满面).

视频拍立得

- Jing - 设计|生活|发现新鲜
视频拍立得,像经典的Polaroid相机一样,按下快门拍摄一段美好时光,就可以即刻吐出一张可以播放动态视频的“照片”,与亲朋好友一起分享. 这种视频照片包含触摸显示屏、存储单元和微型电池,在摄影完成后的瞬间将数据写入电子相片的存储器. 这听起来很像是哈利波特里魔法城堡里的报纸,但是得益于今日发展迅速的显像技术和超薄电池,也许不久的将来我们就能在市场上看到它.

hold住姐视频

- xiaocaochong - 无聊哦
在2011年8月9日的《大学生了没》中,一位名叫miss lin的网友以夸张另类的造型、一口做作的英语、扭捏妖娆的姿态向大学生们介绍什么是Fashion. 其极度夸张搞笑的表演震撼了所有观众,miss lin的口头禅是“整个场面我要Hold住”. 因此得名“hold住姐”或“hold姐”而蹿红网络.

视频:Xoom2 vs iPad2

- - The Leying's Blog
iPad2是平板电脑的标杆,所以我们看到任何厂家的平板上市后首先就会浮想起一个问题:“这玩意和iPad2相比如何. ”于是乎,新上市的Xoom2还是和iPad2走到了一起,青梅煮酒,坐而论道. 评测来自国外媒体,对比内容包括两者的设计,系统,速度,摄像等方面.   不过作为平板,你也许更希望了解两者在网页浏览上的详细区别,那么下面这个视频会告诉你答案,虽然Xoom2屏幕看上去很花,但那是拍摄因素,与机器无关.

Android视频录制

- - CSDN博客移动开发推荐文章
/*下面设置Surface不维护自己的缓冲区,而是等待屏幕的渲染引擎将内容推送到用户面前*/. this.surfaceView.getHolder().setFixedSize(320, 240);//设置分辨率. mediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); //从照相机采集视频.

动态加载HQL

- senyo - BlogJava-首页技术区
Java代码如下:(ReloadableDynamicHibernate.java). 135         private Map qlMap;                //查询的映射. 这样就实现了每次修改SQL or HQL语句后不用重启服务器,立刻看到结果,加快了开发速度.

java动态加载

- - Java - 编程语言 - ITeye博客
第一部分:Java虚拟机启动时,关于类加载方面的一些动作. 当使用java ProgramName.class运行程序时,Java找到JRE,接着找到jvm.dll,把该动态库载入内存,这就是JVM. 然后加载其它动态库, 并激活JVM. JVM激活之后会进行一些初始化工作,之后生成BootstrapLoader,该Class Loader是由C++写的.

spring 加载顺序

- - 企业架构 - ITeye博客
web.xml文件加载顺序.      1 、启动一个 WEB 项目的时候, WEB 容器会去读取它的配置文件 web.xml ,读取 两个结点.      2 、紧急着,容创建一个 ServletContext ( servlet 上下文),这个 web 项目的所有部分都将共享这个上下文.