您好,欢迎访问代理记账网站
  • 价格透明
  • 信息保密
  • 进度掌控
  • 售后无忧

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

下载工具Mp4DownloadUtils

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

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

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<String, Thread> consoleThreadMap = new ConcurrentHashMap<String, Thread>();  //创建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 < 0) break;
            raf.write(buffer, 0, readSize);
            mdatSize -= readSize;
            downloadCount += readSize;
            if (handler != null && !viable && downloadCount >= 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;
    }
}

 


分享:

低价透明

统一报价,无隐形消费

金牌服务

一对一专属顾问7*24小时金牌服务

信息保密

个人信息安全有保障

售后无忧

服务出问题客服经理全程跟进