> 文档中心 > 鸿蒙OS短视频开发--边下边播实现

鸿蒙OS短视频开发--边下边播实现

下载工具Mp4DownloadUtils

参考文章:Android 因moov播放网络mp4失败的解决办法_maowentao0416的博客-CSDN博客

主要是让moov移到前面,实现边下边播。短视频刷视频快速出现就是用了边下边播的原理。

管理播放器地址:VideoPlayerView https://gitee.com/ts_ohos/VideoPlayerManager.git 

import com.mytoutou.video.manage.player.ui.VideoPlayerView;import com.mytoutou.video.provider.PlayInfo;import com.thin.downloadmanager.util.Log;import ohos.app.Context;import ohos.eventhandler.EventHandler;import ohos.eventhandler.InnerEvent;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;import java.util.concurrent.ConcurrentHashMap;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;/** * 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;    /**     * 线程池去管理线程     */    private final static ExecutorService service = Executors.newFixedThreadPool(8);    public static ConcurrentHashMap consoleThreadMap = new ConcurrentHashMap();  //创建hashmap,用于存储线程    /**     * 下载MP4文件     *     * @param url     * @param fileName     * @param handler     * @return     */    public static File downloadMp4File(final String url, final String fileName,    final EventHandler handler, Context context, VideoPlayerView view) { final String path = context.getExternalCacheDir().getPath() + "/" + fileName; final File mp4File = new File(path); downloadVideoToFile(url, mp4File, view, 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, VideoPlayerView view, final EventHandler 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");      httpConn.connect();      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;    Log.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(dstFile, view, 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(dstFile, view, 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;    Log.i("moov ok");}if (isftypRead && isMoovRead && isMdatRead) {    break;}boxSize = readBoxSize(bis, boxSizeBuf);boxType = readBoxType(bis, boxTypeBuf);Log.i("boxSize:" + boxSize + " boxType:" + boxType);raf.write(boxSizeBuf);raf.write(boxTypeBuf);   }      } else {   PlayInfo playInfo = new PlayInfo();   playInfo.setUrl(dstFile.getPath());   playInfo.setView(view);   sendMessage(handler, DOWNLOAD_MP4_FAIL, playInfo);   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;// service.submit(thread);// consoleThreadMap.put(url,thread);    }    /**     * 此函数只有在确定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 {     Log.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;      }      Log.i("moov ok");      break;  }  boxSize = readBoxSize(bis, boxSizeBuf);  boxType = readBoxType(bis, boxTypeBuf);  Log.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(File dstFile, VideoPlayerView view, BufferedInputStream bis, int mdatSize, RandomAccessFile raf, long wirteSeek, EventHandler handler) throws IOException { Log.i("downLoadMadta");// final int buf_size = 56 * 1024;// 56kb final int buf_size = 128 * 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 = buf_size) {  Log.i("PLAYER_MP4_MSG");  viable = true;  // 发送开始播放视频消息,通知前台可以播放视频了  PlayInfo playInfo = new PlayInfo();  playInfo.setUrl(dstFile.getPath());  playInfo.setView(view);  sendMessage(handler, PLAYER_MP4_MSG, playInfo);     }     if (viable) {  sendMessage(handler, DOWNLOAD_MP4_LOADING_PROCESS, 1000L * downloadCount / totalMdatSize);     } } Log.i("下载完成"); // 发送下载消息 通知前台已经下载完成 if (handler != null) {     sendMessage(handler, DOWNLOAD_MP4_COMPLETION, null);     handler.removeEvent(PLAYER_MP4_MSG);     handler.removeEvent(DOWNLOAD_MP4_COMPLETION); }    }    /**     * 发送下载消息     *     * @param handler     * @param what     * @param obj     */    private static void sendMessage(EventHandler handler, int what, Object obj) { if (handler != null) {     InnerEvent event = InnerEvent.get(what, obj);     handler.sendEvent(event); }    }    /**     * 跳转     *     * @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;    }}

 

/** * 线程通知播放所需要的信息 */public class PlayInfo {    private String url;    private VideoPlayerView view;    public String getUrl() { return url;    }    public void setUrl(String url) { this.url = url;    }    public VideoPlayerView getView() { return view;    }    public void setView(VideoPlayerView view) { this.view = view;    }}

 

鸿蒙OS短视频开发--边下边播实现 超强干货来袭 鸿蒙OS短视频开发--边下边播实现 云风专访:近40年码龄,通宵达旦的技术人生