解决思路:
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;@Overridepublic 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);elseraf.write(buffer, 0, dealSize);dealSize -= BUFFER_SIZE;}if(isMoovRead){//moov在mdat的前面,已经下载//下载mdat到destdownLoadMadta(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到destFiledownLoadMadta(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;// 56kbint 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的后面都可以播放。
作者:陈宇、茆文涛

Android 因moov播放网络mp4失败的解决办法相关推荐

  1. Android Studio gradle下载依赖包失败的解决办法

    Android Studio gradle下载依赖包失败 的解决办法 使用Android Studio在第一次导入项目或者配置完Kotlin后,会一直Build,因为需要下载一些项目中配置的依赖和gr ...

  2. android json解析异常,json数据解析异常而导致网络请求失败的解决办法(其一)

    问题概述 笔者在开发过程中临时遇到一个本来仅有web端的项目临时增加Android端,导致后端在出接口时并未考虑Android端的json数据的解析,导致接口是这样的.... 正确请求 { " ...

  3. (转)json数据解析异常而导致网络请求失败的解决办法(其一)

    原文出处 https://www.jianshu.com/p/d6bd6bd5fce3 参考文章 问题概述 笔者在开发过程中临时遇到一个本来仅有web端的项目临时增加Android端,导致后端在出接口 ...

  4. 网络连接失败(解决办法)

    电脑突然断网了怎么办? 这是我这两天面试运维岗位,面试官问的比较多的一个问题,今天就为大家总结一下常见的断网原因和解决办法: 1.物理连接问题:简单点说就是网线松动了,或者根本就没有插网线,这是最常见 ...

  5. Android中MediaPlayer播放音乐时自动中断的解决办法

    今天尝试在一个有SurfaceView持续绘图的程序里加入背景音乐,遇到一个故障: 音乐播10几秒就中止了,此后再无声响. 尝试更换为其他mp3文件,无果. 尝试将播放操作由子线程转到主线程,依然不行 ...

  6. Android之MediaPlayer播放网络视频的实现方法

    前段时间忙于工作,现在有时间来分享一下: 这篇文章主要介绍了Android的MediaPlayer播放网络视频的实现方法,是一个非常实用的功能,需要的朋友可以参考下 前面讲解了MediaPlayer播 ...

  7. Android 8.0 VTS 测试 FAIL 失败项解决记录

    Android 8.0 VTS 测试 FAIL 失败项解决记录 Qidi 2017.08.09 (Markdown & Haroopad) 注意:本文基于 Android 8.0 进行分析. ...

  8. w ndows7浏览器网页,win7系统IE浏览器播放网页视频失败的解决方法

    win7系统IE浏览器播放网页视频失败的解决方法: 一.检查视频链接是否能正常打开.如果是视频链接的问题的话,应该大家都打不开: 二.查看浏览器的版本 1. 打开ie浏览器,然后点击"工具& ...

  9. gradle下载更新依赖库失败的解决办法 - 依赖库下载加速1000%

    gradle下载更新依赖库失败的解决办法 - 依赖库下载加速1000% 现在使用gradle来构建项目的越来越多,通过gradle能更轻松完成项目的依赖以及编译等工作.特别是Google的Androi ...

最新文章

  1. TypeError: unhashable type: 'dict'
  2. hdoj 4272 LianLianKan 数据太水
  3. 递归修改子目录及文件的权限
  4. 文字投影_店铺门口投影灯,引领店铺新潮流
  5. 程序员面试金典 - 面试题 02.06. 回文链表(快慢指针+链表反转)
  6. MySql分页存储过程
  7. Tensorflow深度学习应用(进阶篇)-回归(函数拟合训练)-可视化
  8. 【Flink】Flink The TaskSlotTable has to be started
  9. GDI+ 学习记录(23): 输出文本
  10. jquery上传 php,jQuery AJAX文件上传PHP
  11. CentOS7.6Arm Qt开发环境(GCC+Qt5+QtCreator+JDK8+MySQL8+CMake+ProtoBuf+Python+SVN)
  12. ObjectiveC基础教程(第2版)
  13. Android opencv 检测屏幕是否有坏点/检测一个颜色是否有杂点
  14. 五子棋人机交互c语言代码,Windows人机交互程序设计教学课件-第10课 五子棋程序.ppt...
  15. 金融工程学(五):互换概述
  16. Vue2 Browserslist: caniuse-lite is outdated. Please run: npx browserslist@latest --update-db
  17. ae制h5文字动画_对于8个华丽的HTML5文字动画特效图文赏析
  18. 力天创见客流统计设备应用分析
  19. 手机6120C 玩仙剑dos版
  20. 基于Matlab的静电场仿真实验--求均匀带电球壳的电场

热门文章

  1. ubuntu terminal快捷键
  2. 【C语言】利用rand函数不重复地生成不重复的数组发牌且配对!扑克牌管理操作系统的升级与优化(CodeBlocks)
  3. air playit在安卓4.0下崩溃
  4. 无线显示技术:WiDi,WLAN Display,Air Play,Miracast的摘抄
  5. php 解析word文件,php解析word文档
  6. 局域网伪造源地址DDoS攻击解决方法(转)
  7. 怎么解除pdf的加密,建议收藏这几种方法
  8. 单片机变成了业余爱好
  9. 大一上半学期基础C语言程序(四则运算,日期求天数,阶乘....)
  10. ASP.NET MVC5 多语言国际化