<commit_before>/*
 * Copyright 2014 <NAME> <<EMAIL>>
 *
 * 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.dash;

import android.content.Context;
import android.media.MediaFormat;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.os.SystemClock;
import android.util.Log;

import com.coremedia.iso.IsoFile;
import com.coremedia.iso.boxes.Container;
import com.coremedia.iso.boxes.TrackBox;
import com.googlecode.mp4parser.MemoryDataSourceImpl;
import com.googlecode.mp4parser.authoring.Movie;
import com.googlecode.mp4parser.authoring.Mp4TrackImpl;
import com.googlecode.mp4parser.authoring.builder.DefaultMp4Builder;
import com.googlecode.mp4parser.boxes.threegpp26244.SegmentIndexBox;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import net.protyposis.android.mediaplayer.MediaExtractor;

import okhttp3.Call;
import okhttp3.Response;
import okio.BufferedSink;
import okio.ByteString;
import okio.Okio;

/**
 * Encapsulates DASH data source processing. The Android API's MediaExtractor doesn't support
 * switching between / chaining of multiple data sources, e.g. an initialization segment and a
 * succeeding media data segment. This class takes care of DASH file downloads, merging init data
 * with media file segments, chaining multiple files and switching between them.
 *
 * From outside, it looks like it is processing a single data source, similar to the Android API MediaExtractor.
 *
 * Created by maguggen on 27.08.2014.
 */
public class DashMediaExtractor extends MediaExtractor {

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

    private static volatile int sInstanceCount = 0;

    private Context mContext;
    private MPD mMPD;
    private SegmentDownloader mSegmentDownloader;
    private AdaptationLogic mAdaptationLogic;
    private AdaptationSet mAdaptationSet;
    private Representation mRepresentation;
    private long mMinBufferTimeUs;
    private boolean mRepresentationSwitched;
    private int mCurrentSegment;
    private List<Integer> mSelectedTracks;
    private Map<Representation, ByteString> mInitSegments;
    private Map<Integer, CachedSegment> mFutureCache; // the cache for upcoming segments
    private SegmentLruCache mUsedCache; // cache for used or in use segments
    private int mUsedCacheSize = 100 * 1024 * 1024; // 100MB by default
    private boolean mMp4Mode;
    private long mSegmentPTSOffsetUs;

    private HandlerThread mSegmentProcessingThread;
    private HandlerThread mSegmentSwitchingThread;
    private Handler mSegmentProcessingHandler;
    private Handler mSegmentSwitchingHandler;
    private SyncBarrier<IOException> mSegmentSwitchingBarrier;

    public DashMediaExtractor() {
        // nothing to do here
    }

    public final void setDataSource(Context context, MPD mpd, SegmentDownloader segmentDownloader, AdaptationSet adaptationSet,
                                    AdaptationLogic adaptationLogic)
            throws IOException {
        try {
            mContext = context;
            mMPD = mpd;
            mSegmentDownloader = segmentDownloader;
            mAdaptationSet = adaptationSet;
            mAdaptationLogic = adaptationLogic;
            mRepresentation = adaptationLogic.initialize(mAdaptationSet);
            mMinBufferTimeUs = Math.max(mMPD.minBufferTimeUs, 10 * 1000000L); // 10 secs min buffer time; NOTE: make sure this is above MediaPlayer's low buffering water mark
            mCurrentSegment = -1;
            mSelectedTracks = new ArrayList<>();
            mInitSegments = new ConcurrentHashMap<>(mAdaptationSet.representations.size());
            mFutureCache = new ConcurrentHashMap<>();
            mUsedCache = new SegmentLruCache(mUsedCacheSize == 0 ? 1 : mUsedCacheSize);
            mMp4Mode = mRepresentation.mimeType.equals("video/mp4") || mRepresentation.initSegment.media.endsWith(".mp4");
            mSegmentPTSOffsetUs = 0;

            /* If the extractor previously crashed and could not gracefully finish, some old temp files
             * that will never be used again might be around, so just delete all of them and avoid the
             * memory fill up with trash.
             * Only clean at startup of the first instance, else newer ones delete cache files of
             * running ones.
             */
            if (sInstanceCount++ == 0) {
                clearTempDir(mContext);
            }

            if(mSegmentProcessingThread != null) {
                mSegmentProcessingThread.quit();
            }
            if(mSegmentSwitchingThread != null) {
                mSegmentSwitchingThread.quit();
            }

            mSegmentProcessingThread = new HandlerThread("DashMediaExtractor-SegmentProcessor");
            mSegmentProcessingThread.start();
            mSegmentProcessingHandler = new Handler(mSegmentProcessingThread.getLooper(), mHandlerCallback);

            mSegmentSwitchingThread = new HandlerThread("DashMediaExtractor-SegmentSwitcher");
            mSegmentSwitchingThread.start();
            mSegmentSwitchingHandler = new Handler(mSegmentSwitchingThread.getLooper(), mHandlerCallback);
            mSegmentSwitchingBarrier = new SyncBarrier<>();

            initOnWorkerThread(getNextSegment());
        } catch (Exception e) {
            Log.e(TAG, "failed to set data source");
            throw new IOException("failed to set data source", e);
        }
    }

    /**
     * Gets the size of the segment cache.
     *
     * @return the size of the segment cache in bytes
     */
    public int getCacheSize() {
        return mUsedCacheSize;
    }

    /**
     * Sets the size of the segment cache.
     * On Android < 21, the size must be set before setting the data source (which is when the
     * cache is created), else an exception will be thrown. From Android 21 Lollipop onward,
     * the cache can be dynamically resized at any time.
     *
     * Segments that are larger than the cache size will not be cached. Setting a very small cache
     * size or zero cache size effectively disables caching.
     *
     * @param sizeInBytes the size of the segment cache in bytes
     * @throws IllegalStateException on Android < 21 if the data source has already been set
     */
    public void setCacheSize(int sizeInBytes) {
        mUsedCacheSize = sizeInBytes;
        if(mUsedCache != null) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                // If desired size is zero, set to 1 because 0 is not allowed
                // (a size of 1 byte equals zero because every segment will be larger than 1 byte)
                mUsedCache.resize(sizeInBytes == 0 ? 1 : sizeInBytes);
            } else {
                throw new IllegalStateException("On Android < 21, cache size must be set before setting data source");<commit_msg>Fix javadoc error: malformed HTML<commit_after>/*
 * Copyright 2014 <NAME> <<EMAIL>>
 *
 * 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.dash;

import android.content.Context;
import android.media.MediaFormat;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.os.SystemClock;
import android.util.Log;

import com.coremedia.iso.IsoFile;
import com.coremedia.iso.boxes.Container;
import com.coremedia.iso.boxes.TrackBox;
import com.googlecode.mp4parser.MemoryDataSourceImpl;
import com.googlecode.mp4parser.authoring.Movie;
import com.googlecode.mp4parser.authoring.Mp4TrackImpl;
import com.googlecode.mp4parser.authoring.builder.DefaultMp4Builder;
import com.googlecode.mp4parser.boxes.threegpp26244.SegmentIndexBox;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import net.protyposis.android.mediaplayer.MediaExtractor;

import okhttp3.Call;
import okhttp3.Response;
import okio.BufferedSink;
import okio.ByteString;
import okio.Okio;

/**
 * Encapsulates DASH data source processing. The Android API's MediaExtractor doesn't support
 * switching between / chaining of multiple data sources, e.g. an initialization segment and a
 * succeeding media data segment. This class takes care of DASH file downloads, merging init data
 * with media file segments, chaining multiple files and switching between them.
 *
 * From outside, it looks like it is processing a single data source, similar to the Android API MediaExtractor.
 *
 * Created by maguggen on 27.08.2014.
 */
public class DashMediaExtractor extends MediaExtractor {

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

    private static volatile int sInstanceCount = 0;

    private Context mContext;
    private MPD mMPD;
    private SegmentDownloader mSegmentDownloader;
    private AdaptationLogic mAdaptationLogic;
    private AdaptationSet mAdaptationSet;
    private Representation mRepresentation;
    private long mMinBufferTimeUs;
    private boolean mRepresentationSwitched;
    private int mCurrentSegment;
    private List<Integer> mSelectedTracks;
    private Map<Representation, ByteString> mInitSegments;
    private Map<Integer, CachedSegment> mFutureCache; // the cache for upcoming segments
    private SegmentLruCache mUsedCache; // cache for used or in use segments
    private int mUsedCacheSize = 100 * 1024 * 1024; // 100MB by default
    private boolean mMp4Mode;
    private long mSegmentPTSOffsetUs;

    private HandlerThread mSegmentProcessingThread;
    private HandlerThread mSegmentSwitchingThread;
    private Handler mSegmentProcessingHandler;
    private Handler mSegmentSwitchingHandler;
    private SyncBarrier<IOException> mSegmentSwitchingBarrier;

    public DashMediaExtractor() {
        // nothing to do here
    }

    public final void setDataSource(Context context, MPD mpd, SegmentDownloader segmentDownloader, AdaptationSet adaptationSet,
                                    AdaptationLogic adaptationLogic)
            throws IOException {
        try {
            mContext = context;
            mMPD = mpd;
            mSegmentDownloader = segmentDownloader;
            mAdaptationSet = adaptationSet;
            mAdaptationLogic = adaptationLogic;
            mRepresentation = adaptationLogic.initialize(mAdaptationSet);
            mMinBufferTimeUs = Math.max(mMPD.minBufferTimeUs, 10 * 1000000L); // 10 secs min buffer time; NOTE: make sure this is above MediaPlayer's low buffering water mark
            mCurrentSegment = -1;
            mSelectedTracks = new ArrayList<>();
            mInitSegments = new ConcurrentHashMap<>(mAdaptationSet.representations.size());
            mFutureCache = new ConcurrentHashMap<>();
            mUsedCache = new SegmentLruCache(mUsedCacheSize == 0 ? 1 : mUsedCacheSize);
            mMp4Mode = mRepresentation.mimeType.equals("video/mp4") || mRepresentation.initSegment.media.endsWith(".mp4");
            mSegmentPTSOffsetUs = 0;

            /* If the extractor previously crashed and could not gracefully finish, some old temp files
             * that will never be used again might be around, so just delete all of them and avoid the
             * memory fill up with trash.
             * Only clean at startup of the first instance, else newer ones delete cache files of
             * running ones.
             */
            if (sInstanceCount++ == 0) {
                clearTempDir(mContext);
            }

            if(mSegmentProcessingThread != null) {
                mSegmentProcessingThread.quit();
            }
            if(mSegmentSwitchingThread != null) {
                mSegmentSwitchingThread.quit();
            }

            mSegmentProcessingThread = new HandlerThread("DashMediaExtractor-SegmentProcessor");
            mSegmentProcessingThread.start();
            mSegmentProcessingHandler = new Handler(mSegmentProcessingThread.getLooper(), mHandlerCallback);

            mSegmentSwitchingThread = new HandlerThread("DashMediaExtractor-SegmentSwitcher");
            mSegmentSwitchingThread.start();
            mSegmentSwitchingHandler = new Handler(mSegmentSwitchingThread.getLooper(), mHandlerCallback);
            mSegmentSwitchingBarrier = new SyncBarrier<>();

            initOnWorkerThread(getNextSegment());
        } catch (Exception e) {
            Log.e(TAG, "failed to set data source");
            throw new IOException("failed to set data source", e);
        }
    }

    /**
     * Gets the size of the segment cache.
     *
     * @return the size of the segment cache in bytes
     */
    public int getCacheSize() {
        return mUsedCacheSize;
    }

    /**
     * Sets the size of the segment cache.
     * On Android before API 21, the size must be set before setting the data source (which is when the
     * cache is created), else an exception will be thrown. From Android 21 Lollipop onward,
     * the cache can be dynamically resized at any time.
     *
     * Segments that are larger than the cache size will not be cached. Setting a very small cache
     * size or zero cache size effectively disables caching.
     *
     * @param sizeInBytes the size of the segment cache in bytes
     * @throws IllegalStateException on Android before API 21 if the data source has already been set
     */
    public void setCacheSize(int sizeInBytes) {
        mUsedCacheSize = sizeInBytes;
        if(mUsedCache != null) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                // If desired size is zero, set to 1 because 0 is not allowed
                // (a size of 1 byte equals zero because every segment will be larger than 1 byte)
                mUsedCache.resize(sizeInBytes == 0 ? 1 : sizeInBytes);
            } else {
                throw new IllegalStateException("On Android < 21, cache size must be set before setting data source");
{'fraction_non_alphanumeric': 0.06974459724950884, 'fraction_numerical': 0.010347085789129011, 'mean_word_length': 3.064679265371307, 'pattern_counts': {'":': 0, '<': 29, '<?xml version=': 0, '>': 27, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 16, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1570905', 'n_tokens_mistral': 4110, 'n_tokens_neox': 3897, 'n_words': 1347}