private void prepareInternal() throws IOException, IllegalStateException {
        mCueTimeline.reset();

        MediaCodecDecoder.OnDecoderEventListener decoderEventListener = new MediaCodecDecoder.OnDecoderEventListener() {
            @Override
            public void onBuffering(MediaCodecDecoder decoder) {
                // Enter buffering mode (playback pause) if cached amount is below water mark
                // Do not enter buffering mode is player is already paused (buffering mode will be
                // entered when playback is started and buffer is too empty).
                if(mPlaybackThread != null && !mPlaybackThread.isPaused()
                        && !mBuffering
                        && mDecoders.getCachedDuration() < BUFFER_LOW_WATER_MARK_US
                        && !mDecoders.hasCacheReachedEndOfStream()) {
                    mBuffering = true;
                    mEventHandler.sendMessage(mEventHandler.obtainMessage(MEDIA_INFO,
                            MEDIA_INFO_BUFFERING_START, 0));
                }
            }
        };

        if(mCurrentState == State.RELEASING) {
            // release() has already been called, drop out of prepareAsync() (can only happen with async prepare)
            return;
        }

        mDecoders = new Decoders();

        if(mVideoTrackIndex != MediaCodecDecoder.INDEX_NONE) {
            try {
                MediaCodecDecoder vd = new MediaCodecVideoDecoder(mVideoExtractor, false, mVideoTrackIndex,
                        decoderEventListener, mSurface, mVideoRenderTimingMode.isRenderModeApi21());
                mDecoders.addDecoder(vd);
            } catch (Exception e) {
                Log.e(TAG, "cannot create video decoder: " + e.getMessage());
            }
        }

        if(mAudioTrackIndex != MediaCodecDecoder.INDEX_NONE) {
            mAudioPlayback = new AudioPlayback();
            // Initialize settings in case they have already been set before the preparation
            mAudioPlayback.setAudioSessionId(mAudioSessionId);
            setVolume(mVolumeLeft, mVolumeRight); // sets the volume on mAudioPlayback

            try {
                boolean passive = (mAudioExtractor == mVideoExtractor || mAudioExtractor == null);
                MediaCodecDecoder ad = new MediaCodecAudioDecoder(mAudioExtractor != null ? mAudioExtractor : mVideoExtractor,
                        passive, mAudioTrackIndex, decoderEventListener, mAudioPlayback);
                mDecoders.addDecoder(ad);
            } catch (Exception e) {
                Log.e(TAG, "cannot create audio decoder: " + e.getMessage());
                mAudioPlayback = null;
            }
        }

        // If no decoder could be initialized, there is nothing to play back, so we throw an exception
        if(mDecoders.getDecoders().isEmpty()) {
            throw new IOException("cannot decode any stream");
        }

        if (mAudioPlayback != null) {
            mAudioSessionId = mAudioPlayback.getAudioSessionId();
            mAudioStreamType = mAudioPlayback.getAudioStreamType();
        }

        // After the decoder is initialized, we know the video size
        if(mDecoders.getVideoDecoder() != null) {
            int width = mDecoders.getVideoDecoder().getVideoWidth();
            int height = mDecoders.getVideoDecoder().getVideoHeight();
            int rotation = mDecoders.getVideoDecoder().getVideoRotation();

            // Swap width/height to report correct dimensions of rotated portrait video (rotated by 90 or 270 degrees)
            if(rotation > 0 && rotation != 180) {
                int temp = width;
                width = height;
                height = temp;
            }

            mEventHandler.sendMessage(mEventHandler.obtainMessage(MEDIA_SET_VIDEO_SIZE, width, height));
        }

        if(mCurrentState == State.RELEASING) {
            // release() has already been called, drop out of prepareAsync()
            return;
        }

        // Decode the first frame to initialize the decoder, and seek back to the start
        // This is necessary on some platforms, else a seek directly after initialization will fail,
        // or the decoder goes into a state where it does not accept any input and does not deliver
        // any output, locking up playback (observed on N4 API22).
        // N4 API22 Test: disable this code open video, seek to end, press play to start from beginning
        //                -> results in infinite decoding loop without output
        if(true) {
            if(mDecoders.getVideoDecoder() != null) {
                MediaCodecDecoder.FrameInfo vfi = mDecoders.decodeFrame(true);
                mDecoders.getVideoDecoder().releaseFrame(vfi);
            } else {
                mDecoders.decodeFrame(false);
            }
            if (mAudioPlayback != null) mAudioPlayback.pause(true);
            mDecoders.seekTo(SeekMode.FAST_TO_PREVIOUS_SYNC, 0);
        }
    }
@see android.media.MediaPlayer#setDataSource(android.content.Context, android.net.Uri)
@deprecated only for compatibility with Android API
MediaPlayer/src/main/java/net/protyposis/android/mediaplayer/MediaPlayer.java