Android视频播放之边缓存边播放
2015-10-20 11:43
363 查看
最近在做Android视频播放的有关项目,其中有一项需求就是要求视频可以边加载缓存边播放,类似于优酷土豆的视频点播。网上找了一些相关的资料,比较了每种视频格式的优缺点之后,结合Android手机自身的优势,默认支持mp4编码和解码,最终采用mp4格式作为视频的存储格式。
其实最真实的流媒体协议传输格式并不是普通的http方式,而是rtsp,那样的话得搭建专门的流媒体服务器,成本比较高,采用普通的http方式,实现的是一种伪流媒体传输,但是对于常用的视频缓存播放也足够了。
要想实现视频的边缓存边播放,原则上就要求视频的存储格式是分段的,而mp4正好满足这个要求,只要将mp4的整体视频信息放在mp4文件的开头,这样只要加载了mp4文件的头部之后,就能解析出该mp4文件的时长,比特率等等,为后续的视频缓存做初始化设置,然后每加载一段mp4文件的数据流,通过解析头部来或得当前视频流的帧信息,并在播放器中播放,这样就能先加载一段进行播放,同时缓存后续的一段,依此原理就能实现。
本文的目的就是给大家介绍一种以此原理而开发一个Android视频边缓存边播放的示例,通过该示例的学习,相信大家能对该原理有更深入的理解。
代码解析
VideoViewDemo.java 主要是用来设置启动参数,设定网络视频的url地址和本地缓存的地址,本地缓存的地址可以不设置,程序会自己维护,如果您自己设置了,视频就会缓存到该位置。
BBVideoPlayer.java 就是视频缓存的核心了,READYBUFF定义了初始缓存区的大小,当视频加载到初始缓存区满的时候,播放器开始播放,CACHEBUFF则是核心交换缓存区,主要是用来动态调节缓存区,当网络环境较好的时候,该缓存区为初始大小,当网络环境差的时候,该缓存区会动态增加,主要就是为了避免视频播放的时候出现一卡一卡的现象。
总体来说,原理比较简单,就是把视频加载了一段后,就送到播放器播放,如果出现了错误,则优先缓存一部分文件,然后再继续播放,类似的处理过程循环往复。
其实最真实的流媒体协议传输格式并不是普通的http方式,而是rtsp,那样的话得搭建专门的流媒体服务器,成本比较高,采用普通的http方式,实现的是一种伪流媒体传输,但是对于常用的视频缓存播放也足够了。
要想实现视频的边缓存边播放,原则上就要求视频的存储格式是分段的,而mp4正好满足这个要求,只要将mp4的整体视频信息放在mp4文件的开头,这样只要加载了mp4文件的头部之后,就能解析出该mp4文件的时长,比特率等等,为后续的视频缓存做初始化设置,然后每加载一段mp4文件的数据流,通过解析头部来或得当前视频流的帧信息,并在播放器中播放,这样就能先加载一段进行播放,同时缓存后续的一段,依此原理就能实现。
本文的目的就是给大家介绍一种以此原理而开发一个Android视频边缓存边播放的示例,通过该示例的学习,相信大家能对该原理有更深入的理解。
代码解析
VideoViewDemo.java 主要是用来设置启动参数,设定网络视频的url地址和本地缓存的地址,本地缓存的地址可以不设置,程序会自己维护,如果您自己设置了,视频就会缓存到该位置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 1819 | 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; @Override protected 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); } }; } |
相关文章推荐
- android开发游记:图片的上传下载-使用七牛云存储管理图片
- Android jni系统变量、函数、接口定义汇总
- 解决Android Studio 的 Alt 快捷键和Mac自带的Alt转换功能冲突
- Android 自定义的CheckBox
- Android更改或增加手机默认属性
- Spinner 相关
- Android自定义标题栏
- Android中Environment类的
- AndroidStudio安装遇到的拦路虎
- Android 消息推送方案简析
- Android中音频管理--AudioFocus机制使用说明
- android之TextView使用HTML处理字体样式、显示图片等
- [Android记录]meta-data小结
- Android体系结构初探
- Android GC 那点事
- android Button背景高度被拉伸问题--解决方案
- android MediaPlayer实现简单的音乐播放
- 在android中运行java main方法
- Android中ViewFlipper的使用及设置动画效果实例详解
- android电池剩余使用时间