/* * Copyright (C) 2014 The Android Open Source Project * * 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 com.google.android.exoplayer.hls; import com.google.android.exoplayer.chunk.Format; import com.google.android.exoplayer.chunk.MediaChunk; import com.google.android.exoplayer.extractor.DefaultExtractorInput; import com.google.android.exoplayer.extractor.Extractor; import com.google.android.exoplayer.extractor.ExtractorInput; import com.google.android.exoplayer.upstream.DataSource; import com.google.android.exoplayer.upstream.DataSpec; import com.google.android.exoplayer.util.Util; import java.io.IOException; /** * An MPEG2TS chunk. */ public final class TsChunk extends MediaChunk { /** * The wrapped extractor into which this chunk is being consumed. */ public final HlsExtractorWrapper extractorWrapper; private final boolean isEncrypted; private int bytesLoaded; private volatile boolean loadCanceled; /** * @param dataSource A {@link DataSource} for loading the data. * @param dataSpec Defines the data to be loaded. * @param trigger The reason for this chunk being selected. * @param format The format of the stream to which this chunk belongs. * @param startTimeUs The start time of the media contained by the chunk, in microseconds. * @param endTimeUs The end time of the media contained by the chunk, in microseconds. * @param chunkIndex The index of the chunk. * @param extractorWrapper A wrapped extractor to parse samples from the data. * @param encryptionKey For AES encryption chunks, the encryption key. * @param encryptionIv For AES encryption chunks, the encryption initialization vector. */ public TsChunk(DataSource dataSource, DataSpec dataSpec, int trigger, Format format, long startTimeUs, long endTimeUs, int chunkIndex, HlsExtractorWrapper extractorWrapper, byte[] encryptionKey, byte[] encryptionIv) { super(buildDataSource(dataSource, encryptionKey, encryptionIv), dataSpec, trigger, format, startTimeUs, endTimeUs, chunkIndex); this.extractorWrapper = extractorWrapper; // Note: this.dataSource and dataSource may be different. this.isEncrypted = this.dataSource instanceof Aes128DataSource; } @Override public long bytesLoaded() { return bytesLoaded; } // Loadable implementation @Override public void cancelLoad() { loadCanceled = true; } @Override public boolean isLoadCanceled() { return loadCanceled; } @Override public void load() throws IOException, InterruptedException { // If we previously fed part of this chunk to the extractor, we need to skip it this time. For // encrypted content we need to skip the data by reading it through the source, so as to ensure // correct decryption of the remainder of the chunk. For clear content, we can request the // remainder of the chunk directly. DataSpec loadDataSpec; boolean skipLoadedBytes; if (isEncrypted) { loadDataSpec = dataSpec; skipLoadedBytes = bytesLoaded != 0; } else { loadDataSpec = Util.getRemainderDataSpec(dataSpec, bytesLoaded); skipLoadedBytes = false; } try { ExtractorInput input = new DefaultExtractorInput(dataSource, loadDataSpec.absoluteStreamPosition, dataSource.open(loadDataSpec)); if (skipLoadedBytes) { input.skipFully(bytesLoaded); } try { int result = Extractor.RESULT_CONTINUE; while (result == Extractor.RESULT_CONTINUE && !loadCanceled) { result = extractorWrapper.read(input); } } finally { bytesLoaded = (int) (input.getPosition() - dataSpec.absoluteStreamPosition); } } finally { dataSource.close(); } } // Private methods /** * If the content is encrypted, returns an {@link Aes128DataSource} that wraps the original in * order to decrypt the loaded data. Else returns the original. */ private static DataSource buildDataSource(DataSource dataSource, byte[] encryptionKey, byte[] encryptionIv) { if (encryptionKey == null || encryptionIv == null) { return dataSource; } return new Aes128DataSource(dataSource, encryptionKey, encryptionIv); } }