package com.sheep.gamegroup.util;

import android.media.MediaCodec;
import android.media.MediaExtractor;
import android.media.MediaFormat;
import android.media.MediaMuxer;
import android.util.Log;

import com.sheep.gamegroup.absBase.AbsObserver;
import com.sheep.gamegroup.model.entity.Video;

import java.io.File;
import java.nio.ByteBuffer;
import java.util.Locale;

import io.reactivex.Observable;
import io.reactivex.ObservableEmitter;
import io.reactivex.ObservableOnSubscribe;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;

/**
 * Created by realicing on 2018/11/30.
 * realicing@sina.com
 */
public class MediaHandleUtil {
    public static final String TAG = "MediaHandleUtil";
    private String url;
    private long clipPoint;
    private long clipDuration;

    public MediaHandleUtil(String url, long clipPoint, long clipDuration) {
        this.url = url;
        this.clipPoint = clipPoint;
        this.clipDuration = clipDuration;
    }

    public void tryCutVideo(AbsObserver<Video> absObserver) {
        Observable.create(new ObservableOnSubscribe<Video>() {
            @Override
            public void subscribe(ObservableEmitter<Video> emitter) throws Exception {
                long time = System.currentTimeMillis();
                Video video = cutVideo(url, clipPoint, clipDuration);
                if (video == null) {
                    emitter.onError(new Throwable("剪切失败"));
                } else {
                    emitter.onNext(video);
                }
                long curTime = System.currentTimeMillis();
                LogUtil.println(TAG, time, curTime, curTime - time, (curTime - time) / 1000);
            }
        })
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(absObserver);
    }

    /**
     * 获取可以剪切出的视频的地址
     *
     * @param url          源文本地址
     * @param clipPoint    开始位置
     * @param clipDuration 要剪切的长度
     * @return 返回可以剪切出来的文件的地址
     */
    public static String getCutVideoFilePath(String url, long clipPoint, long clipDuration) {
        if (clipPoint < 0) {
            Log.e(TAG, "clipPoint is error! but reset clipPoint " + clipPoint + "0 ");
            clipPoint = 0;
        }
        return String.format(Locale.CHINA, "%s_%d_%d.%s", url.substring(0, url.lastIndexOf(".")), clipPoint, clipDuration, FileUtil.getExtensionName(url));
    }

    /**
     * 剪切视频
     *
     * @param url          源文本地址
     * @param clipPoint    开始位置
     * @param clipDuration 要剪切的长度
     * @return 返回剪切出来的文件的地址
     */
    public static Video cutVideo(String url, long clipPoint, long clipDuration) {
        Video video = new Video();
        if (clipPoint < 0) {
            Log.e(TAG, "clipPoint is error! but reset clipPoint " + clipPoint + "0 ");
            clipPoint = 0;
        }
        LogUtil.println(TAG, url, clipPoint, clipDuration);
        String outFilePath = getCutVideoFilePath(url, clipPoint, clipDuration);
        video.setFilePath(outFilePath);
        int videoTrackIndex = -1;
        int audioTrackIndex = -1;
        int videoMaxInputSize = 0;
        int audioMaxInputSize = 0;
        int sourceVTrack = 0;
        int sourceATrack = 0;
        long videoDuration, audioDuration;
        //创建分离器
        MediaExtractor mediaExtractor = new MediaExtractor();
        MediaMuxer mediaMuxer = null;
        MediaFormat mediaFormat;

        try {
            //设置文件路径
            mediaExtractor.setDataSource(url);
            //创建合成器
            mediaMuxer = new MediaMuxer(outFilePath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
            String mime;
            //获取每个轨道的信息
            for (int i = 0; i < mediaExtractor.getTrackCount(); i++) {
                mediaFormat = mediaExtractor.getTrackFormat(i);
                mime = mediaFormat.getString(MediaFormat.KEY_MIME);
                if (mime.startsWith("video/")) {
                    sourceVTrack = i;
                    int width = mediaFormat.getInteger(MediaFormat.KEY_WIDTH);
                    int height = mediaFormat.getInteger(MediaFormat.KEY_HEIGHT);
                    videoMaxInputSize = mediaFormat.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE);
                    videoDuration = mediaFormat.getLong(MediaFormat.KEY_DURATION);
                    //检测剪辑点和剪辑时长是否正确
                    if (clipPoint >= videoDuration) {
                        Log.e(TAG, "clip point is error! but reset clipPoint " + clipPoint + " 0 ");
                        clipPoint = 0;
                    }
                    if ((clipDuration + clipPoint) > videoDuration) {
                        if (clipPoint == 0) {
                            Log.e(TAG, "clip duration is error!"
                                    + ";duration is " + videoDuration
                                    + ";clipPoint is " + clipPoint
                                    + ";clipDuration is " + clipDuration
                                    + ";totalDuration is " + (clipDuration + clipPoint)
                            );
                            return null;
                        }
                        Log.e(TAG, "clip duration is error! but reset clipDuration " + clipDuration + "  " + (videoDuration - clipPoint));
                        clipDuration = videoDuration - clipPoint;
                    }
                    Log.d(TAG, "width and height is " + width + " " + height
                            + ";maxInputSize is " + videoMaxInputSize
                            + ";duration is " + videoDuration
                            + ";clipPoint is " + clipPoint
                            + ";clipDuration is " + clipDuration
                            + ";totalDuration is " + (clipDuration + clipPoint)
                    );
                    video.setWidth(width);
                    video.setHeight(height);
                    video.setDuration(clipDuration / 1000);//微秒转换为毫秒
                    //向合成器添加视频轨
                    videoTrackIndex = mediaMuxer.addTrack(mediaFormat);
                } else if (mime.startsWith("audio/")) {
                    sourceATrack = i;
                    int sampleRate = mediaFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE);
                    int channelCount = mediaFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
                    audioMaxInputSize = mediaFormat.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE);
                    audioDuration = mediaFormat.getLong(MediaFormat.KEY_DURATION);
                    Log.d(TAG, "sampleRate is " + sampleRate
                            + ";channelCount is " + channelCount
                            + ";audioMaxInputSize is " + audioMaxInputSize
                            + ";audioDuration is " + audioDuration
                    );
                    //添加音轨
                    audioTrackIndex = mediaMuxer.addTrack(mediaFormat);
                }
                Log.d(TAG, "file mime is " + mime);
            }
            //分配缓冲
            ByteBuffer inputBuffer = ByteBuffer.allocate(videoMaxInputSize);
            //根据官方文档的解释MediaMuxer的start一定要在addTrack之后
            mediaMuxer.start();
            //视频处理部分
            mediaExtractor.selectTrack(sourceVTrack);
            MediaCodec.BufferInfo videoInfo = new MediaCodec.BufferInfo();
            videoInfo.presentationTimeUs = 0;
            long videoSampleTime;
            //获取源视频相邻帧之间的时间间隔。(1)
            {
                mediaExtractor.readSampleData(inputBuffer, 0);
                //skip first I frame
                if (mediaExtractor.getSampleFlags() == MediaExtractor.SAMPLE_FLAG_SYNC)
                    mediaExtractor.advance();
                mediaExtractor.readSampleData(inputBuffer, 0);
                long firstVideoPTS = mediaExtractor.getSampleTime();
                mediaExtractor.advance();
                mediaExtractor.readSampleData(inputBuffer, 0);
                long SecondVideoPTS = mediaExtractor.getSampleTime();
                videoSampleTime = Math.abs(SecondVideoPTS - firstVideoPTS);
                Log.d(TAG, "videoSampleTime is " + videoSampleTime);
            }
            //选择起点
            mediaExtractor.seekTo(clipPoint, MediaExtractor.SEEK_TO_PREVIOUS_SYNC);
            while (true) {
                int sampleSize = mediaExtractor.readSampleData(inputBuffer, 0);
                if (sampleSize < 0) {
                    //这里一定要释放选择的轨道，不然另一个轨道就无法选中了
                    mediaExtractor.unselectTrack(sourceVTrack);
                    break;
                }
                int trackIndex = mediaExtractor.getSampleTrackIndex();
                //获取时间戳
                long presentationTimeUs = mediaExtractor.getSampleTime();
                //获取帧类型，只能识别是否为I帧
                int sampleFlag = mediaExtractor.getSampleFlags();
                Log.d(TAG, "trackIndex is " + trackIndex
                        + ";presentationTimeUs is " + presentationTimeUs
                        + ";sampleFlag is " + sampleFlag
                        + ";sampleSize is " + sampleSize);
                //剪辑时间到了就跳出
                if ((clipDuration != 0) && (presentationTimeUs > (clipPoint + clipDuration))) {
                    mediaExtractor.unselectTrack(sourceVTrack);
                    break;
                }
                mediaExtractor.advance();
                videoInfo.offset = 0;
                videoInfo.size = sampleSize;
                videoInfo.flags = sampleFlag;
                mediaMuxer.writeSampleData(videoTrackIndex, inputBuffer, videoInfo);
                videoInfo.presentationTimeUs += videoSampleTime;//presentationTimeUs;
            }
            //音频部分
            mediaExtractor.selectTrack(sourceATrack);
            MediaCodec.BufferInfo audioInfo = new MediaCodec.BufferInfo();
            audioInfo.presentationTimeUs = 0;
            long audioSampleTime;
            //获取音频帧时长
            {
                mediaExtractor.readSampleData(inputBuffer, 0);
                //skip first sample
                if (mediaExtractor.getSampleTime() == 0)
                    mediaExtractor.advance();
                mediaExtractor.readSampleData(inputBuffer, 0);
                long firstAudioPTS = mediaExtractor.getSampleTime();
                mediaExtractor.advance();
                mediaExtractor.readSampleData(inputBuffer, 0);
                long SecondAudioPTS = mediaExtractor.getSampleTime();
                audioSampleTime = Math.abs(SecondAudioPTS - firstAudioPTS);
                Log.d(TAG, "AudioSampleTime is " + audioSampleTime);
            }
            mediaExtractor.seekTo(clipPoint, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
            while (true) {
                int sampleSize = mediaExtractor.readSampleData(inputBuffer, 0);
                if (sampleSize < 0) {
                    mediaExtractor.unselectTrack(sourceATrack);
                    break;
                }
                int trackIndex = mediaExtractor.getSampleTrackIndex();
                long presentationTimeUs = mediaExtractor.getSampleTime();
                Log.d(TAG, "trackIndex is " + trackIndex
                        + ";presentationTimeUs is " + presentationTimeUs);
                if ((clipDuration != 0) && (presentationTimeUs > (clipPoint + clipDuration))) {
                    mediaExtractor.unselectTrack(sourceATrack);
                    break;
                }
                mediaExtractor.advance();
                audioInfo.offset = 0;
                audioInfo.size = sampleSize;
                mediaMuxer.writeSampleData(audioTrackIndex, inputBuffer, audioInfo);
                audioInfo.presentationTimeUs += audioSampleTime;//presentationTimeUs;
            }
        } catch (Exception e) {
            Log.e(TAG, " read error " + e.getMessage());
        } finally {
            //全部写完后释放MediaMuxer和MediaExtractor
            if (mediaMuxer != null) {
                mediaMuxer.stop();
                mediaMuxer.release();
            }
            mediaExtractor.release();
        }
        return video;
    }

    public static void mixVideAndAudio(File outVideoPath, File outAudioPath, File outMergeVideoPath) throws Exception {
        //分别初始化视频、音频的Extractor
        MediaExtractor videoExtractor = new MediaExtractor();
        videoExtractor.setDataSource(outVideoPath.getAbsolutePath());
        MediaExtractor audioExtractor = new MediaExtractor();
        audioExtractor.setDataSource(outAudioPath.getAbsolutePath());
        int videoTrack = getTrack(videoExtractor, "video/");
        int audioTrack = getTrack(audioExtractor, "audio/");
        videoExtractor.selectTrack(videoTrack);
        audioExtractor.selectTrack(audioTrack);
        MediaCodec.BufferInfo videoBufferInfo = new MediaCodec.BufferInfo();
        MediaCodec.BufferInfo audioBufferInfo = new MediaCodec.BufferInfo();
        //写入新的视频
        if (outMergeVideoPath.exists() && outMergeVideoPath.createNewFile())
            ;
        ByteBuffer byteBuffer = ByteBuffer.allocate(500 * 1024);

        MediaMuxer mediaMuxer = new MediaMuxer(outMergeVideoPath.getAbsolutePath(), MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
        int writeVideoTrackIndex = mediaMuxer.addTrack(videoExtractor.getTrackFormat(videoTrack));
        int writeAudioTrackIndex = mediaMuxer.addTrack(audioExtractor.getTrackFormat(audioTrack));
        mediaMuxer.start();
        long videoSampleTime = getSampleTime(videoExtractor, byteBuffer, videoTrack);
        while (true) {
            int data = videoExtractor.readSampleData(byteBuffer, 0);
            if (data < 0) {
                break;
            }
            videoBufferInfo.size = data;
            videoBufferInfo.offset = 0;
            videoBufferInfo.flags = videoExtractor.getSampleFlags();
            videoBufferInfo.presentationTimeUs += videoSampleTime;
            mediaMuxer.writeSampleData(writeVideoTrackIndex, byteBuffer, videoBufferInfo);
            videoExtractor.advance();
        }
        long audioSampleTime = getSampleTime(audioExtractor, byteBuffer, audioTrack);
        while (true) {
            int data = audioExtractor.readSampleData(byteBuffer, 0);
            if (data < 0) {
                break;
            }
            audioBufferInfo.size = data;
            audioBufferInfo.offset = 0;
            audioBufferInfo.flags = audioExtractor.getSampleFlags();
            audioBufferInfo.presentationTimeUs += audioSampleTime;
            mediaMuxer.writeSampleData(writeAudioTrackIndex, byteBuffer, audioBufferInfo);
            audioExtractor.advance();
        }
        Log.i("video", "合并完成");
        mediaMuxer.stop();
        mediaMuxer.release();
        videoExtractor.release();
        audioExtractor.release();
    }

    public static long getSampleTime(MediaExtractor mediaExtractor, ByteBuffer byteBuffer, int videoTrack) {
        mediaExtractor.readSampleData(byteBuffer, 0);
        //跳过I帧，要P帧（视频是由个别I帧和很多P帧组成）h264编码中有IBP帧 I为关键帧。
        if (mediaExtractor.getSampleFlags() == MediaExtractor.SAMPLE_FLAG_SYNC) {
            mediaExtractor.advance();
        }
        mediaExtractor.readSampleData(byteBuffer, 0);

        // 得到第一帧的PTS
        long firstVideoPTS = mediaExtractor.getSampleTime();
        //下一帧
        mediaExtractor.advance();
        mediaExtractor.readSampleData(byteBuffer, 0);
        long secondVideoPTS = mediaExtractor.getSampleTime();
        long sampleTime = Math.abs(secondVideoPTS - firstVideoPTS);

        // 重新切换此信道，不然上面跳过了3帧,造成前面的帧数模糊
        mediaExtractor.unselectTrack(videoTrack);
        mediaExtractor.selectTrack(videoTrack);
        return sampleTime;
    }

    public static int getTrack(MediaExtractor extractor, String willFormat) {
        //获得信道数
        int trackCount = extractor.getTrackCount();
        for (int i = 0; i < trackCount; i++) {
            MediaFormat trackFormat = extractor.getTrackFormat(i);
            String format = trackFormat.getString(MediaFormat.KEY_MIME);
            if (format.startsWith(willFormat)) {
                return i;
            }
        }
        return 0;
    }
}
