/*
 * Copyright 2015 Mario Guggenberger <mg@protyposis.net>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package net.protyposis.android.mediaplayer;

import android.util.Log;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * Created by Mario on 13.09.2015.
 */
class Decoders {

    private static final String TAG = Decoders.class.getSimpleName();

    private List<MediaCodecDecoder> mDecoders;
    private MediaCodecVideoDecoder mVideoDecoder;
    private MediaCodecAudioDecoder mAudioDecoder;

    public Decoders() {
        mDecoders = new ArrayList<>();
    }

    public void addDecoder(MediaCodecDecoder decoder) {
        mDecoders.add(decoder);

        if (decoder instanceof MediaCodecVideoDecoder) {
            mVideoDecoder = (MediaCodecVideoDecoder) decoder;
        } else if (decoder instanceof MediaCodecAudioDecoder) {
            mAudioDecoder = (MediaCodecAudioDecoder) decoder;
        }
    }

    public List<MediaCodecDecoder> getDecoders() {
        return mDecoders;
    }

    public MediaCodecVideoDecoder getVideoDecoder() {
        return mVideoDecoder;
    }

    public MediaCodecAudioDecoder getAudioDecoder() {
        return mAudioDecoder;
    }

    /**
     * Runs the audio/video decoder loop, optionally until a new frame is available.
     * The returned frameInfo object keeps metadata of the decoded frame. To render the frame
     * to the screen and/or dismiss its data, call {@link MediaCodecVideoDecoder#releaseFrame(MediaCodecDecoder.FrameInfo, boolean)}
     * or {@link MediaCodecVideoDecoder#releaseFrame(MediaCodecDecoder.FrameInfo, long)}.
     *
     * @param force force decoding in a loop until a frame becomes available or the EOS is reached
     * @return a VideoFrameInfo object holding metadata of a decoded video frame or NULL if no frame has been decoded
     */
    public MediaCodecDecoder.FrameInfo decodeFrame(boolean force) {
        //Log.d(TAG, "decodeFrame");
        boolean outputEos = false;

        while(!outputEos) {
            int outputEosCount = 0;
            MediaCodecDecoder.FrameInfo fi;
            MediaCodecDecoder.FrameInfo vfi = null;

            for (MediaCodecDecoder decoder : mDecoders) {
                while((fi = decoder.dequeueDecodedFrame()) != null) {

                    if(decoder == mVideoDecoder) {
                        vfi = fi;
                        break;
                    } else {
                        decoder.renderFrame(fi, 0);
                    }
                }

                while (decoder.queueSampleToCodec(false)) {}

                if(decoder.isOutputEos()) {
                    outputEosCount++;
                }
            }

            if(vfi != null) {
                // If a video frame has been decoded, return it
                return vfi;
            }

            if(!force) {
                // If we have not decoded a video frame and we're not forcing decoding until a frame
                // becomes available, return null.
                return null;
            }

            outputEos = (outputEosCount == mDecoders.size());
        }

        Log.d(TAG, "EOS NULL");
        return null; // EOS already reached, no video frame left to return
    }

    /**
     * Releases all decoders. This must be called to free decoder resources when this object is no longer in use.
     */
    public void release() {
        for (MediaCodecDecoder decoder : mDecoders) {
            decoder.release();
        }
        mDecoders.clear();
    }

    public void seekTo(MediaPlayer.SeekMode seekMode, long seekTargetTimeUs) throws IOException {
        for (MediaCodecDecoder decoder : mDecoders) {
            decoder.seekTo(seekMode, seekTargetTimeUs);
        }
    }

    public void renderFrames() {
        for (MediaCodecDecoder decoder : mDecoders) {
            decoder.renderFrame();
        }
    }

    public void dismissFrames() {
        for (MediaCodecDecoder decoder : mDecoders) {
            decoder.dismissFrame();
        }
    }

    public long getCurrentDecodingPTS() {
        long minPTS = Long.MAX_VALUE;
        for (MediaCodecDecoder decoder : mDecoders) {
            long pts = decoder.getCurrentDecodingPTS();
            if(pts != MediaCodecDecoder.PTS_NONE && minPTS > pts) {
                minPTS = pts;
            }
        }
        return minPTS;
    }

    public boolean isEOS() {
        //return getCurrentDecodingPTS() == MediaCodecDecoder.PTS_EOS;
        int eosCount = 0;
        for (MediaCodecDecoder decoder : mDecoders) {
            if(decoder.isOutputEos()) {
                eosCount++;
            }
        }
        return eosCount == mDecoders.size();
    }

    public long getCachedDuration() {
        // Init with the largest possible value...
        long minCachedDuration = Long.MAX_VALUE;

        // ...then decrease to the lowest duration.
        // We always return the lowest value, because if only one decoder has to refill its buffer,
        // all others have to wait. If one decoder returns -1, this function returns -1 too (which
        // makes sense because we cannot calculate a meaningful cache duration in this case).
        for (MediaCodecDecoder decoder : mDecoders) {
            long cachedDuration = decoder.getCachedDuration();
            minCachedDuration = Math.min(cachedDuration, minCachedDuration);
        }

        if(minCachedDuration == Long.MAX_VALUE) {
            // There were no decoders that updated this value, which means we don't have information
            // on a cached duration, so we return -1 to signal that the information is not available.
            return -1;
        }

        return minCachedDuration;
    }

    /**
     * Returns true only if all decoders have reached the end of stream.
     */
    public boolean hasCacheReachedEndOfStream() {
        for (MediaCodecDecoder decoder : mDecoders) {
            if(!decoder.hasCacheReachedEndOfStream()) {
                return false;
            }
        }
        return true;
    }
}

@@ -3995,26 +3995,303 @@
     
-decoder.release();
+// Catch decoder.release() exceptions to avoid breaking the release loop on the first%0A            // exception and leaking unreleased decoders.%0A            try %7B%0A                decoder.release();%0A            %7D catch (Exception e) %7B%0A                Log.e(TAG, %22release failed%22, e);%0A            %7D
 %0A   

Make sure all decoders are released