Report uri and response headers of chunks.

Both values are helpful for event reporting, but are only available while
the data source is open. Similar to bytesLoaded, they need to be reported
through the Chunk.

Issue:#2054
Issue:#4361

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=201664907
This commit is contained in:
tonihei 2018-06-22 04:03:15 -07:00 committed by Oliver Woodman
parent ddda4a026d
commit fcc0bd403f
13 changed files with 197 additions and 83 deletions

View file

@ -15,6 +15,7 @@
*/
package com.google.android.exoplayer2.extractor;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.util.ParsableByteArray;
@ -50,9 +51,12 @@ public final class DummyTrackOutput implements TrackOutput {
}
@Override
public void sampleMetadata(long timeUs, @C.BufferFlags int flags, int size, int offset,
CryptoData cryptoData) {
public void sampleMetadata(
long timeUs,
@C.BufferFlags int flags,
int size,
int offset,
@Nullable CryptoData cryptoData) {
// Do nothing.
}
}

View file

@ -125,21 +125,23 @@ public interface TrackOutput {
/**
* Called when metadata associated with a sample has been extracted from the stream.
* <p>
* The corresponding sample data will have already been passed to the output via calls to
* {@link #sampleData(ExtractorInput, int, boolean)} or
* {@link #sampleData(ParsableByteArray, int)}.
*
* <p>The corresponding sample data will have already been passed to the output via calls to
* {@link #sampleData(ExtractorInput, int, boolean)} or {@link #sampleData(ParsableByteArray,
* int)}.
*
* @param timeUs The media timestamp associated with the sample, in microseconds.
* @param flags Flags associated with the sample. See {@code C.BUFFER_FLAG_*}.
* @param size The size of the sample data, in bytes.
* @param offset The number of bytes that have been passed to
* {@link #sampleData(ExtractorInput, int, boolean)} or
* {@link #sampleData(ParsableByteArray, int)} since the last byte belonging to the sample
* whose metadata is being passed.
* @param offset The number of bytes that have been passed to {@link #sampleData(ExtractorInput,
* int, boolean)} or {@link #sampleData(ParsableByteArray, int)} since the last byte belonging
* to the sample whose metadata is being passed.
* @param encryptionData The encryption data required to decrypt the sample. May be null.
*/
void sampleMetadata(long timeUs, @C.BufferFlags int flags, int size, int offset,
CryptoData encryptionData);
void sampleMetadata(
long timeUs,
@C.BufferFlags int flags,
int size,
int offset,
@Nullable CryptoData encryptionData);
}

View file

@ -568,8 +568,12 @@ public final class SampleQueue implements TrackOutput {
}
@Override
public void sampleMetadata(long timeUs, @C.BufferFlags int flags, int size, int offset,
CryptoData cryptoData) {
public void sampleMetadata(
long timeUs,
@C.BufferFlags int flags,
int size,
int offset,
@Nullable CryptoData cryptoData) {
if (pendingFormatAdjustment) {
format(lastUnadjustedFormat);
}

View file

@ -15,13 +15,17 @@
*/
package com.google.android.exoplayer2.source.chunk;
import android.net.Uri;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.upstream.Loader.Loadable;
import com.google.android.exoplayer2.upstream.StatsDataSource;
import com.google.android.exoplayer2.util.Assertions;
import java.util.List;
import java.util.Map;
/**
* An abstract base class for {@link Loadable} implementations that load chunks of data required
@ -64,7 +68,7 @@ public abstract class Chunk implements Loadable {
*/
public final long endTimeUs;
protected final DataSource dataSource;
protected final StatsDataSource dataSource;
/**
* @param dataSource The source from which the data should be loaded.
@ -85,7 +89,7 @@ public abstract class Chunk implements Loadable {
@Nullable Object trackSelectionData,
long startTimeUs,
long endTimeUs) {
this.dataSource = Assertions.checkNotNull(dataSource);
this.dataSource = new StatsDataSource(dataSource);
this.dataSpec = Assertions.checkNotNull(dataSpec);
this.type = type;
this.trackFormat = trackFormat;
@ -103,8 +107,31 @@ public abstract class Chunk implements Loadable {
}
/**
* Returns the number of bytes that have been loaded.
* Returns the number of bytes that have been loaded. Must only be called after the load
* completed, failed, or was canceled.
*/
public abstract long bytesLoaded();
public final long bytesLoaded() {
return dataSource.getBytesRead();
}
/**
* Returns the {@link Uri} associated with the last {@link DataSource#open} call. If redirection
* occurred, this is the redirected uri. Must only be called after the load completed, failed, or
* was canceled.
*
* @see DataSource#getUri().
*/
public final Uri getUri() {
return dataSource.getLastOpenedUri();
}
/**
* Returns the response headers associated with the last {@link DataSource#open} call. Must only
* be called after the load completed, failed, or was canceled.
*
* @see DataSource#getResponseHeaders().
*/
public final Map<String, List<String>> getResponseHeaders() {
return dataSource.getLastResponseHeaders();
}
}

View file

@ -15,6 +15,7 @@
*/
package com.google.android.exoplayer2.source.chunk;
import android.support.annotation.Nullable;
import android.util.SparseArray;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
@ -103,7 +104,7 @@ public final class ChunkExtractorWrapper implements ExtractorOutput {
* @param seekTimeUs The seek position within the new chunk, or {@link C#TIME_UNSET} to output the
* whole chunk.
*/
public void init(TrackOutputProvider trackOutputProvider, long seekTimeUs) {
public void init(@Nullable TrackOutputProvider trackOutputProvider, long seekTimeUs) {
this.trackOutputProvider = trackOutputProvider;
if (!extractorInitialized) {
extractor.init(this);

View file

@ -20,6 +20,7 @@ import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.extractor.DefaultExtractorInput;
import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.extractor.ExtractorInput;
import com.google.android.exoplayer2.extractor.PositionHolder;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.util.Assertions;
@ -31,13 +32,15 @@ import java.io.IOException;
*/
public class ContainerMediaChunk extends BaseMediaChunk {
private static final PositionHolder DUMMY_POSITION_HOLDER = new PositionHolder();
private final int chunkCount;
private final long sampleOffsetUs;
private final ChunkExtractorWrapper extractorWrapper;
private volatile int bytesLoaded;
private long nextLoadPosition;
private volatile boolean loadCanceled;
private volatile boolean loadCompleted;
private boolean loadCompleted;
/**
* @param dataSource The source from which the data should be loaded.
@ -94,11 +97,6 @@ public class ContainerMediaChunk extends BaseMediaChunk {
return loadCompleted;
}
@Override
public final long bytesLoaded() {
return bytesLoaded;
}
// Loadable implementation.
@Override
@ -109,12 +107,12 @@ public class ContainerMediaChunk extends BaseMediaChunk {
@SuppressWarnings("NonAtomicVolatileUpdate")
@Override
public final void load() throws IOException, InterruptedException {
DataSpec loadDataSpec = dataSpec.subrange(bytesLoaded);
DataSpec loadDataSpec = dataSpec.subrange(nextLoadPosition);
try {
// Create and open the input.
ExtractorInput input = new DefaultExtractorInput(dataSource,
loadDataSpec.absoluteStreamPosition, dataSource.open(loadDataSpec));
if (bytesLoaded == 0) {
if (nextLoadPosition == 0) {
// Configure the output and set it as the target for the extractor wrapper.
BaseMediaChunkOutput output = getOutput();
output.setSampleOffsetUs(sampleOffsetUs);
@ -126,11 +124,11 @@ public class ContainerMediaChunk extends BaseMediaChunk {
Extractor extractor = extractorWrapper.extractor;
int result = Extractor.RESULT_CONTINUE;
while (result == Extractor.RESULT_CONTINUE && !loadCanceled) {
result = extractor.read(input, null);
result = extractor.read(input, DUMMY_POSITION_HOLDER);
}
Assertions.checkState(result != Extractor.RESULT_SEEK);
} finally {
bytesLoaded = (int) (input.getPosition() - dataSpec.absoluteStreamPosition);
nextLoadPosition = input.getPosition() - dataSpec.absoluteStreamPosition;
}
} finally {
Util.closeQuietly(dataSource);

View file

@ -32,7 +32,6 @@ public abstract class DataChunk extends Chunk {
private static final int READ_GRANULARITY = 16 * 1024;
private byte[] data;
private int limit;
private volatile boolean loadCanceled;
@ -63,11 +62,6 @@ public abstract class DataChunk extends Chunk {
return data;
}
@Override
public long bytesLoaded() {
return limit;
}
// Loadable implementation
@Override
@ -79,10 +73,10 @@ public abstract class DataChunk extends Chunk {
public final void load() throws IOException, InterruptedException {
try {
dataSource.open(dataSpec);
limit = 0;
int limit = 0;
int bytesRead = 0;
while (bytesRead != C.RESULT_END_OF_INPUT && !loadCanceled) {
maybeExpandData();
maybeExpandData(limit);
bytesRead = dataSource.read(data, limit, READ_GRANULARITY);
if (bytesRead != -1) {
limit += bytesRead;
@ -106,7 +100,7 @@ public abstract class DataChunk extends Chunk {
*/
protected abstract void consume(byte[] data, int limit) throws IOException;
private void maybeExpandData() {
private void maybeExpandData(int limit) {
if (data == null) {
data = new byte[READ_GRANULARITY];
} else if (data.length < limit + READ_GRANULARITY) {
@ -115,5 +109,4 @@ public abstract class DataChunk extends Chunk {
data = Arrays.copyOf(data, data.length + READ_GRANULARITY);
}
}
}

View file

@ -21,6 +21,7 @@ import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.extractor.DefaultExtractorInput;
import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.extractor.ExtractorInput;
import com.google.android.exoplayer2.extractor.PositionHolder;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.util.Assertions;
@ -32,9 +33,11 @@ import java.io.IOException;
*/
public final class InitializationChunk extends Chunk {
private static final PositionHolder DUMMY_POSITION_HOLDER = new PositionHolder();
private final ChunkExtractorWrapper extractorWrapper;
private volatile int bytesLoaded;
private long nextLoadPosition;
private volatile boolean loadCanceled;
/**
@ -57,11 +60,6 @@ public final class InitializationChunk extends Chunk {
this.extractorWrapper = extractorWrapper;
}
@Override
public long bytesLoaded() {
return bytesLoaded;
}
// Loadable implementation.
@Override
@ -72,12 +70,12 @@ public final class InitializationChunk extends Chunk {
@SuppressWarnings("NonAtomicVolatileUpdate")
@Override
public void load() throws IOException, InterruptedException {
DataSpec loadDataSpec = dataSpec.subrange(bytesLoaded);
DataSpec loadDataSpec = dataSpec.subrange(nextLoadPosition);
try {
// Create and open the input.
ExtractorInput input = new DefaultExtractorInput(dataSource,
loadDataSpec.absoluteStreamPosition, dataSource.open(loadDataSpec));
if (bytesLoaded == 0) {
if (nextLoadPosition == 0) {
extractorWrapper.init(/* trackOutputProvider= */ null, C.TIME_UNSET);
}
// Load and decode the initialization data.
@ -85,11 +83,11 @@ public final class InitializationChunk extends Chunk {
Extractor extractor = extractorWrapper.extractor;
int result = Extractor.RESULT_CONTINUE;
while (result == Extractor.RESULT_CONTINUE && !loadCanceled) {
result = extractor.read(input, null);
result = extractor.read(input, DUMMY_POSITION_HOLDER);
}
Assertions.checkState(result != Extractor.RESULT_SEEK);
} finally {
bytesLoaded = (int) (input.getPosition() - dataSpec.absoluteStreamPosition);
nextLoadPosition = input.getPosition() - dataSpec.absoluteStreamPosition;
}
} finally {
Util.closeQuietly(dataSource);

View file

@ -33,8 +33,8 @@ public final class SingleSampleMediaChunk extends BaseMediaChunk {
private final int trackType;
private final Format sampleFormat;
private volatile int bytesLoaded;
private volatile boolean loadCompleted;
private long nextLoadPosition;
private boolean loadCompleted;
/**
* @param dataSource The source from which the data should be loaded.
@ -80,11 +80,6 @@ public final class SingleSampleMediaChunk extends BaseMediaChunk {
return loadCompleted;
}
@Override
public long bytesLoaded() {
return bytesLoaded;
}
// Loadable implementation.
@Override
@ -95,14 +90,15 @@ public final class SingleSampleMediaChunk extends BaseMediaChunk {
@SuppressWarnings("NonAtomicVolatileUpdate")
@Override
public void load() throws IOException, InterruptedException {
DataSpec loadDataSpec = dataSpec.subrange(bytesLoaded);
DataSpec loadDataSpec = dataSpec.subrange(nextLoadPosition);
try {
// Create and open the input.
long length = dataSource.open(loadDataSpec);
if (length != C.LENGTH_UNSET) {
length += bytesLoaded;
length += nextLoadPosition;
}
ExtractorInput extractorInput = new DefaultExtractorInput(dataSource, bytesLoaded, length);
ExtractorInput extractorInput =
new DefaultExtractorInput(dataSource, nextLoadPosition, length);
BaseMediaChunkOutput output = getOutput();
output.setSampleOffsetUs(0);
TrackOutput trackOutput = output.track(0, trackType);
@ -110,10 +106,10 @@ public final class SingleSampleMediaChunk extends BaseMediaChunk {
// Load the sample data.
int result = 0;
while (result != C.RESULT_END_OF_INPUT) {
bytesLoaded += result;
nextLoadPosition += result;
result = trackOutput.sampleData(extractorInput, Integer.MAX_VALUE, true);
}
int sampleSize = bytesLoaded;
int sampleSize = (int) nextLoadPosition;
trackOutput.sampleMetadata(startTimeUs, C.BUFFER_FLAG_KEY_FRAME, sampleSize, 0, null);
} finally {
Util.closeQuietly(dataSource);

View file

@ -0,0 +1,102 @@
/*
* Copyright (C) 2018 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.exoplayer2.upstream;
import android.net.Uri;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.util.Assertions;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
* {@link DataSource} wrapper which keeps track of bytes transferred, redirected uris, and response
* headers.
*/
public final class StatsDataSource implements DataSource {
private final DataSource dataSource;
private long bytesRead;
private Uri lastOpenedUri;
private Map<String, List<String>> lastResponseHeaders;
/**
* Creates the stats data source.
*
* @param dataSource The wrapped {@link DataSource}.
*/
public StatsDataSource(DataSource dataSource) {
this.dataSource = Assertions.checkNotNull(dataSource);
lastOpenedUri = Uri.EMPTY;
lastResponseHeaders = Collections.emptyMap();
}
/** Returns the total number of bytes that have been read from the data source. */
public long getBytesRead() {
return bytesRead;
}
/**
* Returns the {@link Uri} associated with the last {@link #open(DataSpec)} call. If redirection
* occurred, this is the redirected uri.
*/
public Uri getLastOpenedUri() {
return lastOpenedUri;
}
/** Returns the response headers associated with the last {@link #open(DataSpec)} call. */
public Map<String, List<String>> getLastResponseHeaders() {
return lastResponseHeaders;
}
@Override
public long open(DataSpec dataSpec) throws IOException {
// Reassign defaults in case dataSource.open throws an exception.
lastOpenedUri = dataSpec.uri;
lastResponseHeaders = Collections.emptyMap();
long availableBytes = dataSource.open(dataSpec);
lastOpenedUri = Assertions.checkNotNull(getUri());
lastResponseHeaders = getResponseHeaders();
return availableBytes;
}
@Override
public int read(byte[] buffer, int offset, int readLength) throws IOException {
int bytesRead = dataSource.read(buffer, offset, readLength);
if (bytesRead != C.RESULT_END_OF_INPUT) {
this.bytesRead += bytesRead;
}
return bytesRead;
}
@Override
public @Nullable Uri getUri() {
return dataSource.getUri();
}
@Override
public Map<String, List<String>> getResponseHeaders() {
return dataSource.getResponseHeaders();
}
@Override
public void close() throws IOException {
dataSource.close();
}
}

View file

@ -421,10 +421,5 @@ public final class AdaptiveTrackSelectionTest {
public boolean isLoadCompleted() {
return true;
}
@Override
public long bytesLoaded() {
return 0;
}
}
}

View file

@ -336,7 +336,7 @@ public final class PlayerEmsgHandler implements Handler.Callback {
@Override
public void sampleMetadata(
long timeUs, int flags, int size, int offset, CryptoData encryptionData) {
long timeUs, int flags, int size, int offset, @Nullable CryptoData encryptionData) {
sampleQueue.sampleMetadata(timeUs, flags, size, offset, encryptionData);
parseAndDiscardSamples();
}

View file

@ -80,11 +80,11 @@ import java.util.concurrent.atomic.AtomicInteger;
private ParsableByteArray id3Data;
private HlsSampleStreamWrapper output;
private int initSegmentBytesLoaded;
private int bytesLoaded;
private int nextLoadPosition;
private boolean id3TimestampPeeked;
private boolean initLoadCompleted;
private volatile boolean loadCanceled;
private volatile boolean loadCompleted;
private boolean loadCompleted;
/**
* @param extractorFactory A {@link HlsExtractorFactory} from which the HLS media chunk extractor
@ -145,8 +145,7 @@ import java.util.concurrent.atomic.AtomicInteger;
this.hlsUrl = hlsUrl;
this.isMasterTimestampSource = isMasterTimestampSource;
this.timestampAdjuster = timestampAdjuster;
// Note: this.dataSource and dataSource may be different.
this.isEncrypted = this.dataSource instanceof Aes128DataSource;
this.isEncrypted = fullSegmentEncryptionKey != null;
this.hasGapTag = hasGapTag;
this.extractorFactory = extractorFactory;
this.muxedCaptionFormats = muxedCaptionFormats;
@ -181,11 +180,6 @@ import java.util.concurrent.atomic.AtomicInteger;
return loadCompleted;
}
@Override
public long bytesLoaded() {
return bytesLoaded;
}
// Loadable implementation
@Override
@ -237,9 +231,9 @@ import java.util.concurrent.atomic.AtomicInteger;
boolean skipLoadedBytes;
if (isEncrypted) {
loadDataSpec = dataSpec;
skipLoadedBytes = bytesLoaded != 0;
skipLoadedBytes = nextLoadPosition != 0;
} else {
loadDataSpec = dataSpec.subrange(bytesLoaded);
loadDataSpec = dataSpec.subrange(nextLoadPosition);
skipLoadedBytes = false;
}
if (!isMasterTimestampSource) {
@ -257,7 +251,7 @@ import java.util.concurrent.atomic.AtomicInteger;
? timestampAdjuster.adjustTsTimestamp(id3Timestamp) : startTimeUs);
}
if (skipLoadedBytes) {
input.skipFully(bytesLoaded);
input.skipFully(nextLoadPosition);
}
try {
int result = Extractor.RESULT_CONTINUE;
@ -265,7 +259,7 @@ import java.util.concurrent.atomic.AtomicInteger;
result = extractor.read(input, null);
}
} finally {
bytesLoaded = (int) (input.getPosition() - dataSpec.absoluteStreamPosition);
nextLoadPosition = (int) (input.getPosition() - dataSpec.absoluteStreamPosition);
}
} finally {
Util.closeQuietly(dataSource);