Android 因moov播放网络mp4失败的解决办法
来源:互联网 发布:linux gdb调试core文件 编辑:程序博客网 时间:2024/06/05 20:56
解决思路:
1、通过网址读取mp4流的关键字来判断ftyp、free、mdat、moov。新建文件destFile,然后:
a、下载ftyp的全部到newFile
b、下载moov全部到newFile
c、写mdat大小的空白数据到newFIle
d、等b和c都完成之后(因b和c这两步的先后不确定),再重新定位mp4流到mdat部分,下载56k(大小可以自行设定,这里我设的是56k)的数据到newFile,然后通知播放端开始播放视频(就用MediaPlayer.setDataSource(newfile)方法),后台下载端继续将最后部分mdat数据完全下载到newFile,即完成mp4数据的下载
代码如下
import java.io.BufferedInputStream;import java.io.File;import java.io.IOException;import java.io.InputStream;import java.io.RandomAccessFile;import java.io.UnsupportedEncodingException;import java.net.HttpURLConnection;import java.net.URL;/** * Created by Administrator on 2017/11/16. */public class Mp4DownloadUtils { /** * 播放MP4消息 */ public static final int PLAYER_MP4_MSG = 0x1001; /** * 下载MP4完成 */ public static final int DOWNLOAD_MP4_COMPLETION = 0x1002; /** * 下载MP4失败 */ public static final int DOWNLOAD_MP4_FAIL = 0x1003; /** * 下载MP4进度 */ public static final int DOWNLOAD_MP4_LOADING_PROCESS = 0x1004; /** * 下载MP4文件 * * @param url * @param fileName * @param handler * @return */ public static File downloadMp4File(final String url, final String fileName, final Handler handler) { final File mp4File = new File(fileName); downloadVideoToFile(url, mp4File, handler); return mp4File; } /** * 下载视频数据到文件 * * @param url * @param dstFile */ private static final int BUFFER_SIZE = 4 * 1024; static boolean isRunning = true; static Thread thread; public static void kill(){ if (thread!=null){ isRunning = false; thread.interrupt(); } } /** * @param url:mp4文件url * @param dstFile:下载后缓存文件 * @param handler: 通知UI主线程的handler */ private static void downloadVideoToFile(final String url, final File dstFile, final Handler handler) { isRunning = true; thread = new Thread() { InputStream is = null; RandomAccessFile raf = null; BufferedInputStream bis = null; @Override public void run() { super.run(); try { URL request = new URL(url); HttpURLConnection httpConn = (HttpURLConnection) request.openConnection(); httpConn.setConnectTimeout(3000); httpConn.setDefaultUseCaches(false); httpConn.setRequestMethod("GET");// httpConn.setRequestProperty("Charset", "UTF-8");// httpConn.setRequestProperty("Accept-Encoding", "identity"); int responseCode = httpConn.getResponseCode(); Log.d("chy", responseCode + ""); if ((responseCode == HttpURLConnection.HTTP_OK)) {//链接成功 // 获取文件总长度// int totalLength = httpConn.getContentLength(); is = httpConn.getInputStream(); if (dstFile.exists()) { dstFile.delete(); } //新建缓存文件 dstFile.createNewFile(); raf = new RandomAccessFile(dstFile, "rw"); bis = new BufferedInputStream(is); int readSize; //读取Mp4流 int mdatSize = 0;// mp4的mdat长度 int headSize = 0;// mp4从流里已经读取的长度 int mdatMark = 0;//mdat的标记位置 byte[] boxSizeBuf = new byte[4]; byte[] boxTypeBuf = new byte[4]; // 由MP4的文件格式读取 int boxSize = readBoxSize(bis, boxSizeBuf); String boxType = readBoxType(bis, boxTypeBuf); raf.write(boxSizeBuf); raf.write(boxTypeBuf); boolean isMoovRead = false; boolean isMdatRead = false; boolean isftypRead = false; byte[] buffer = new byte[BUFFER_SIZE]; //判断ftyp、free、mdat、moov 并下载 while (isRunning &&!Thread.currentThread().isInterrupted()) { int count = boxSize - 8; if (boxType.equalsIgnoreCase("ftyp")) { headSize += boxSize; byte[] ftyps = new byte[count]; bis.read(ftyps, 0, count); raf.write(ftyps, 0, count); isftypRead = true; LogUtils.getInstance().i("ftyp ok"); } else if (boxType.equalsIgnoreCase("mdat")) { headSize += boxSize; //正常模式 mdatSize = boxSize - 8; int dealSize = mdatSize; //填充mdata大小的空白数据到dstFile,先填充destFile的大小 while (dealSize > 0) { if (dealSize > BUFFER_SIZE) raf.write(buffer); else raf.write(buffer, 0, dealSize); dealSize -= BUFFER_SIZE; } if(isMoovRead){//moov在mdat的前面,已经下载 //下载mdat到dest downLoadMadta(bis,mdatSize,raf,headSize - boxSize+ 8,handler); bis.close(); is.close(); raf.close(); httpConn.disconnect(); return; }else {//moov在mdat的后面 //下载moov到dest 与mp4服务器端重新连接一个通道,专门下载moov部分 downLoadMoov(url,headSize,raf); //从destFile的起点开始,下载mdat到destFile downLoadMadta(bis,mdatSize,raf,headSize - boxSize+ 8,handler); bis.close(); is.close(); raf.close(); httpConn.disconnect(); return; } } else if (boxType.equalsIgnoreCase("free")) { headSize += boxSize; } else if (boxType.equalsIgnoreCase("moov")) { Log.d("chy","moov size:"+boxSize); headSize += boxSize; int moovSize = count; while (moovSize > 0) { if (moovSize > BUFFER_SIZE) { readSize = bis.read(buffer); } else { readSize = bis.read(buffer, 0, moovSize); } if (readSize == -1) break; raf.write(buffer, 0, readSize); moovSize -= readSize; } isMoovRead = true; LogUtils.getInstance().i("moov ok"); } if (isftypRead && isMoovRead && isMdatRead) { break; } boxSize = readBoxSize(bis, boxSizeBuf); boxType = readBoxType(bis, boxTypeBuf); LogUtils.getInstance().i("boxSize:"+boxSize+" boxType:"+boxType); raf.write(boxSizeBuf); raf.write(boxTypeBuf); } }else { sendMessage(handler, DOWNLOAD_MP4_FAIL, null); is.close(); bis.close(); raf.close(); } } catch (Exception e) { e.printStackTrace(); this.interrupt(); //中断这个分线程 sendMessage(handler, DOWNLOAD_MP4_FAIL, null); try { if (is!=null && bis != null && raf != null){ is.close(); bis.close(); raf.close(); } } catch (IOException e1) { e1.printStackTrace(); } } } }; thread.start();// thread = null; } /** 此函数只有在确定moov在mdat的后面才可以调用 * @param url:mp4服务器地址 * @param begin:mdat的末点,也就是moov的起点。 * */ private static void downLoadMoov(final String url, int begin, RandomAccessFile raf){ HttpURLConnection httpConn = null; InputStream is = null; BufferedInputStream bis = null; try { LogUtils.getInstance().i("downLoadMoov"); URL request = new URL(url); httpConn = (HttpURLConnection) request.openConnection(); httpConn.setConnectTimeout(3000); httpConn.setDefaultUseCaches(false); httpConn.setRequestMethod("GET"); httpConn.setRequestProperty("range", "bytes=" + begin + "-"); is = httpConn.getInputStream(); bis = new BufferedInputStream(is); int readSize =0; int headSize = 0;// mp4从流里已经读取的长度 byte[] boxSizeBuf = new byte[4]; byte[] boxTypeBuf= new byte[4]; // 由MP4的文件格式读取 int boxSize = readBoxSize(bis, boxSizeBuf); String boxType = readBoxType(bis, boxTypeBuf); raf.write(boxSizeBuf); raf.write(boxTypeBuf); int count =0; byte[] buffer = new byte[4*1024]; while(true) { count = boxSize - 8; if (boxType.equalsIgnoreCase("free")) { headSize += boxSize; } else if (boxType.equalsIgnoreCase("moov")) { Log.d("chy", "moov size:" + boxSize); headSize += boxSize; int moovSize = count; while (moovSize > 0) { if (moovSize > BUFFER_SIZE) { readSize = bis.read(buffer); } else { readSize = bis.read(buffer, 0, moovSize); } if (readSize == -1) break; raf.write(buffer, 0, readSize); moovSize -= readSize; } LogUtils.getInstance().i("moov ok"); break; } boxSize = readBoxSize(bis, boxSizeBuf); boxType = readBoxType(bis, boxTypeBuf); LogUtils.getInstance().i("boxSizeMoov:" + boxSize + " boxTypeMoov:" + boxType); raf.write(boxSizeBuf); raf.write(boxTypeBuf); } bis.close(); is.close(); httpConn.disconnect(); }catch (Exception e){ try { bis.close(); is.close(); } catch (IOException e1) { e1.printStackTrace(); } if (httpConn!=null) httpConn.disconnect(); } } /** * 此方法直接定位到mdat的起点开始下载mdata。1、下载部分通知前端 2、下载完成也通知前端 * @param bis:服务器流 * @param mdatSize:mdat的大小 * @param raf:目标文件的句柄 * @param wirteSeek:让目标文件指针跳过ftyp等部分 * */ private static void downLoadMadta(BufferedInputStream bis,int mdatSize,RandomAccessFile raf,long wirteSeek,Handler handler) throws IOException { LogUtils.getInstance().i("downLoadMadta"); final int buf_size = 56 * 1024;// 56kb int downloadCount = 0; boolean viable = false; byte[] buffer = new byte[BUFFER_SIZE]; int readSize = 0; if(wirteSeek>0){ raf.seek(wirteSeek); } int totalMdatSize = mdatSize; while (mdatSize > 0) { readSize = bis.read(buffer); if(readSize < 0) break; raf.write(buffer, 0, readSize); mdatSize -= readSize; downloadCount += readSize; if (handler != null && !viable && downloadCount >= buf_size) { LogUtils.getInstance().i("PLAYER_MP4_MSG"); viable = true; // 发送开始播放视频消息,通知前台可以播放视频了 sendMessage(handler, PLAYER_MP4_MSG, null); } if (viable){ sendMessage(handler,DOWNLOAD_MP4_LOADING_PROCESS,1000L*downloadCount/totalMdatSize); } } LogUtils.getInstance().i("下载完成"); // 发送下载消息 通知前台已经下载完成 if (handler != null) { sendMessage(handler, DOWNLOAD_MP4_COMPLETION, null); handler.removeMessages(PLAYER_MP4_MSG); handler.removeMessages(DOWNLOAD_MP4_COMPLETION); } } /** * 发送下载消息 * * @param handler * @param what * @param obj */ private static void sendMessage(Handler handler, int what, Object obj) { if (handler != null) { Message msg = new Message(); msg.what = what; msg.obj = obj; handler.sendMessage(msg); } } /** * 跳转 * * @param is * @param count 跳转长度 * @throws IOException */ private static void skip(BufferedInputStream is, long count) throws IOException { while (count > 0) { long amt = is.skip(count); if (amt == -1) { throw new RuntimeException("inputStream skip exception"); } count -= amt; } } /** * 读取mp4文件box大小 * * @param is * @param buffer * @return */ private static int readBoxSize(InputStream is, byte[] buffer) { int sz = fill(is, buffer); if (sz == -1) { return 0; } return bytesToInt(buffer, 0, 4); } /** * 读取MP4文件box类型 * * @param is * @param buffer * @return */ private static String readBoxType(InputStream is, byte[] buffer) { fill(is, buffer); return byteToString(buffer); } /** * byte转换int * * @param buffer * @param pos * @param bytes * @return */ private static int bytesToInt(byte[] buffer, int pos, int bytes) { /* * int intvalue = (buffer[pos + 0] & 0xFF) << 24 | (buffer[pos + 1] & * 0xFF) << 16 | (buffer[pos + 2] & 0xFF) << 8 | buffer[pos + 3] & 0xFF; */ int retval = 0; for (int i = 0; i < bytes; ++i) { retval |= (buffer[pos + i] & 0xFF) << (8 * (bytes - i - 1)); } return retval; } /** * byte数据转换String * * @param buffer * @return */ private static String byteToString(byte[] buffer) { assert buffer.length == 4; String retval = new String(); try { retval = new String(buffer, 0, buffer.length, "ascii"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } return retval; } private static int fill(InputStream stream, byte[] buffer) { return fill(stream, 0, buffer.length, buffer); } /** * 读取流数据 * * @param stream * @param pos * @param len * @param buffer * @return */ private static int fill(InputStream stream, int pos, int len, byte[] buffer) { int readSize = 0; try { readSize = stream.read(buffer, pos, len); if (readSize == -1) { return -1; } assert readSize == len : String.format("len %d readSize %d", len, readSize); } catch (IOException e) { e.printStackTrace(); } return readSize; }}
上述代码让moov无论是在mdat前面还是在moov的后面都可以播放。
作者:陈宇、茆文涛
阅读全文
1 0
- Android 因moov播放网络mp4失败的解决办法
- 为何moov头在尾部的mp4可以快速播放、拖动
- android、电脑可以播放.mp4的视频,ios无法播放解决办法
- HTML5网页上播放mp4失败的原因
- android---- 小型的播放器mp4
- Android 播放MP4文件
- 关于HTML5中Video标签无法播放mp4的解决办法
- html5 <video>播放mp4格式视频没有图像的解决办法
- mp4网页播放代码,有声音无图像的解决办法~
- 64位ubuntu播放mp4安装插件出错的解决办法
- MP4学习(六)ts-mp4源码阅读(4)moov box的解析
- 微信浏览器 MP4播放失败,安卓下微信浏览器不能播放MP4问题的解决,gzip捣的鬼
- android studio更新提示网络连接失败的解决办法
- android 播放工程中的MP4
- Android中播放MP4文件
- Android VideoView播放MP4视频文件
- android 自定义MP4播放器
- Win7/Win8因网络配置导致无法上网的解决办法
- CSS复习笔记
- 两军问题与拜占庭将军问题
- Python 周报(一)
- sql语句
- 大话设计模式------依赖倒转原则
- Android 因moov播放网络mp4失败的解决办法
- 11-单线程下载文件
- TIME
- 高德地图API之定位API
- svn的使用
- Android之Adapter用法总结
- numpy基础教程—算术运算
- MVp封装RxJava+Retrofit
- a标签嵌套img标签,结果a标签不能被img标签撑开