Android视频播放之边缓存边播放
转载地址:http://blog.zhourunsheng.com/2012/05/android%e8%a7%86%e9%a2%91%e6%92%ad%e6%94%be%e4%b9%8b%e8%be%b9%e7%bc%93%e5%ad%98%e8%be%b9%e6%92%ad%e6%94%be/
最近在做Android视频播放的有关项目,其中有一项需求就是要求视频可以边加载缓存边播放,类似于优酷土豆的视频点播。网上找了一些相关的资料,比较了每种视频格式的优缺点之后,结合Android手机自身的优势,默认支持mp4编码和解码,最终采用mp4格式作为视频的存储格式。
其实最真实的流媒体协议传输格式并不是普通的http方式,而是rtsp,那样的话得搭建专门的流媒体服务器,成本比较高,采用普通的http方式,实现的是一种伪流媒体传输,但是对于常用的视频缓存播放也足够了。
要想实现视频的边缓存边播放,原则上就要求视频的存储格式是分段的,而mp4正好满足这个要求,只要将mp4的整体视频信息放在mp4文件的开头,这样只要加载了mp4文件的头部之后,就能解析出该mp4文件的时长,比特率等等,为后续的视频缓存做初始化设置,然后每加载一段mp4文件的数据流,通过解析头部来或得当前视频流的帧信息,并在播放器中播放,这样就能先加载一段进行播放,同时缓存后续的一段,依此原理就能实现。
本文的目的就是给大家介绍一种以此原理而开发一个Android视频边缓存边播放的示例,通过该示例的学习,相信大家能对该原理有更深入的理解。
在介绍具体的demo之前,先放上几张截图,分别为视频播放前的缓存,视频边缓存边播放,缓存完毕的视频播放。
代码解析
VideoViewDemo.java 主要是用来设置启动参数,设定网络视频的url地址和本地缓存的地址,本地缓存的地址可以不设置,程序会自己维护,如果您自己设置了,视频就会缓存到该位置。
public class VideoViewDemo extends Activity { @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); //String url = "http://carey-blog-image.googlecode.com/files/vid_20120510_090204.mp4"; String url = "http://bbfile.b0.upaiyun.com/data/videos/2/vid_20120510_090204.mp4"; Intent intent = new Intent(); intent.setClass(VideoViewDemo.this, BBVideoPlayer.class); intent.putExtra("url", url); intent.putExtra("cache", Environment.getExternalStorageDirectory().getAbsolutePath() + "/VideoCache/" + System.currentTimeMillis() + ".mp4"); startActivity(intent);}}
BBVideoPlayer.java 就是视频缓存的核心了,READYBUFF定义了初始缓存区的大小,当视频加载到初始缓存区满的时候,播放器开始播放,CACHEBUFF则是核心交换缓存区,主要是用来动态调节缓存区,当网络环境较好的时候,该缓存区为初始大小,当网络环境差的时候,该缓存区会动态增加,主要就是为了避免视频播放的时候出现一卡一卡的现象。
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271 | public class BBVideoPlayer extends Activity { private VideoView mVideoView;private TextView tvcache;private String remoteUrl;private String localUrl;private ProgressDialog progressDialog = null; private static final int READY_BUFF = 2000 * 1024;private static final int CACHE_BUFF = 500 * 1024; private boolean isready = false;private boolean iserror = false;private int errorCnt = 0;private int curPosition = 0;private long mediaLength = 0;private long readSize = 0; @Overrideprotected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.bbvideoplayer); findViews(); init(); playvideo();} private void findViews() { this.mVideoView = (VideoView) findViewById(R.id.bbvideoview); this.tvcache = (TextView) findViewById(R.id.tvcache);} private void init() { Intent intent = getIntent(); this.remoteUrl = intent.getStringExtra("url"); System.out.println("remoteUrl: " + remoteUrl); if (this.remoteUrl == null) { finish(); return; } this.localUrl = intent.getStringExtra("cache"); mVideoView.setMediaController(new MediaController(this)); mVideoView.setOnPreparedListener(new OnPreparedListener() { public void onPrepared(MediaPlayer mediaplayer) { dismissProgressDialog(); mVideoView.seekTo(curPosition); mediaplayer.start(); } }); mVideoView.setOnCompletionListener(new OnCompletionListener() { public void onCompletion(MediaPlayer mediaplayer) { curPosition = 0; mVideoView.pause(); } }); mVideoView.setOnErrorListener(new OnErrorListener() { public boolean onError(MediaPlayer mediaplayer, int i, int j) { iserror = true; errorCnt++; mVideoView.pause(); showProgressDialog(); return true; } });} private void showProgressDialog() { mHandler.post(new Runnable() { @Override public void run() { if (progressDialog == null) { progressDialog = ProgressDialog.show(BBVideoPlayer.this, "视频缓存", "正在努力加载中 ...", true, false); } } });} private void dismissProgressDialog() { mHandler.post(new Runnable() { @Override public void run() { if (progressDialog != null) { progressDialog.dismiss(); progressDialog = null; } } });} private void playvideo() { if (!URLUtil.isNetworkUrl(this.remoteUrl)) { mVideoView.setVideoPath(this.remoteUrl); mVideoView.start(); return; } showProgressDialog(); new Thread(new Runnable() { @Override public void run() { FileOutputStream out = null; InputStream is = null; try { URL url = new URL(remoteUrl); HttpURLConnection httpConnection = (HttpURLConnection) url .openConnection(); if (localUrl == null) { localUrl = Environment.getExternalStorageDirectory() .getAbsolutePath() + "/VideoCache/" + System.currentTimeMillis() + ".mp4"; } System.out.println("localUrl: " + localUrl); File cacheFile = new File(localUrl); if (!cacheFile.exists()) { cacheFile.getParentFile().mkdirs(); cacheFile.createNewFile(); } readSize = cacheFile.length(); out = new FileOutputStream(cacheFile, true); httpConnection.setRequestProperty("User-Agent", "NetFox"); httpConnection.setRequestProperty("RANGE", "bytes=" + readSize + "-"); is = httpConnection.getInputStream(); mediaLength = httpConnection.getContentLength(); if (mediaLength == -1) { return; } mediaLength += readSize; byte buf[] = new byte[4 * 1024]; int size = 0; long lastReadSize = 0; mHandler.sendEmptyMessage(VIDEO_STATE_UPDATE); while ((size = is.read(buf)) != -1) { try { out.write(buf, 0, size); readSize += size; } catch (Exception e) { e.printStackTrace(); } if (!isready) { if ((readSize - lastReadSize) > READY_BUFF) { lastReadSize = readSize; mHandler.sendEmptyMessage(CACHE_VIDEO_READY); } } else { if ((readSize - lastReadSize) > CACHE_BUFF * (errorCnt + 1)) { lastReadSize = readSize; mHandler.sendEmptyMessage(CACHE_VIDEO_UPDATE); } } } mHandler.sendEmptyMessage(CACHE_VIDEO_END); } catch (Exception e) { e.printStackTrace(); } finally { if (out != null) { try { out.close(); } catch (IOException e) { // } } if (is != null) { try { is.close(); } catch (IOException e) { // } } } } }).start(); } private final static int VIDEO_STATE_UPDATE = 0;private final static int CACHE_VIDEO_READY = 1;private final static int CACHE_VIDEO_UPDATE = 2;private final static int CACHE_VIDEO_END = 3; private final Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { case VIDEO_STATE_UPDATE: double cachepercent = readSize * 100.00 / mediaLength * 1.0; String s = String.format("已缓存: [%.2f%%]", cachepercent); if (mVideoView.isPlaying()) { curPosition = mVideoView.getCurrentPosition(); int duration = mVideoView.getDuration(); duration = duration == 0 ? 1 : duration; double playpercent = curPosition * 100.00 / duration * 1.0; int i = curPosition / 1000; int hour = i / (60 * 60); int minute = i / 60 % 60; int second = i % 60; s += String.format(" 播放: %02d:%02d:%02d [%.2f%%]", hour, minute, second, playpercent); } tvcache.setText(s); mHandler.sendEmptyMessageDelayed(VIDEO_STATE_UPDATE, 1000); break; case CACHE_VIDEO_READY: isready = true; mVideoView.setVideoPath(localUrl); mVideoView.start(); break; case CACHE_VIDEO_UPDATE: if (iserror) { mVideoView.setVideoPath(localUrl); mVideoView.start(); iserror = false; } break; case CACHE_VIDEO_END: if (iserror) { mVideoView.setVideoPath(localUrl); mVideoView.start(); iserror = false; } break; } super.handleMessage(msg); }};} 总体来说,原理比较简单,就是把视频加载了一段后,就送到播放器播放,如果出现了错误,则优先缓存一部分文件,然后再继续播放,类似的处理过程循环往复。 程序源代码下载: code 大家可以在实际环境中做测试,和依据实际情况动态维护缓存区,在具体的使用过程中有神马问题留言即可! |
更多相关文章
- android之视频播放
- android开发视频教程 android培训入门教程 Android菜鸟教程
- Android 异步加载图片分析
- FFmpeg打造Android万能音频播放器-杨万里-专题视频课程
- Android——Bitmap的加载和Cache
- android mediaplayer 播放 视频 【转】
- Android中webview加载的网页上的按钮点击失效
- Android视频桌面,动态桌面开发
- Android搜索视媒体库视频 列表显示选择