Clean up chunked media source event listeners.

- Event listener is now at the SampleSource level, since the
  individual ChunkTrackStream instances are created internally.
- Event listener receives events corresponding to loads made
  at the SampleSource level (e.g. manifest fetches) in addition
  to those made by the ChunkTrackStream instances.
- Added ability for FormatEvaluators to propagate arbitrary
  information (as an Object) through to the listeners. This was
  a request from YouTube.
- Added significantly more information to each event, for example
  the DataSpec that defines the load being made is now passed, as
  is timing information.
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=124732984
This commit is contained in:
olly 2016-06-13 09:22:50 -07:00 committed by Oliver Woodman
parent 172bc4e8d8
commit f4e4fd51c6
34 changed files with 881 additions and 613 deletions

View File

@ -15,6 +15,7 @@
*/
package com.google.android.exoplayer.demo;
import com.google.android.exoplayer.AdaptiveSourceEventListener;
import com.google.android.exoplayer.CodecCounters;
import com.google.android.exoplayer.DefaultTrackSelector;
import com.google.android.exoplayer.DefaultTrackSelector.TrackInfo;
@ -26,9 +27,9 @@ import com.google.android.exoplayer.TrackGroup;
import com.google.android.exoplayer.TrackGroupArray;
import com.google.android.exoplayer.TrackRenderer;
import com.google.android.exoplayer.TrackSelection;
import com.google.android.exoplayer.chunk.ChunkTrackStreamEventListener;
import com.google.android.exoplayer.drm.StreamingDrmSessionManager;
import com.google.android.exoplayer.extractor.ExtractorSampleSource;
import com.google.android.exoplayer.upstream.DataSpec;
import android.os.SystemClock;
import android.util.Log;
@ -41,7 +42,7 @@ import java.util.Locale;
* Logs player events using {@link Log}.
*/
public class EventLogger implements ExoPlayer.EventListener, SimpleExoPlayer.DebugListener,
ChunkTrackStreamEventListener, ExtractorSampleSource.EventListener,
AdaptiveSourceEventListener, ExtractorSampleSource.EventListener,
StreamingDrmSessionManager.EventListener, DefaultTrackSelector.EventListener {
private static final String TAG = "EventLogger";
@ -200,38 +201,52 @@ public class EventLogger implements ExoPlayer.EventListener, SimpleExoPlayer.Deb
Log.d(TAG, "drmKeysLoaded [" + getSessionTimeString() + "]");
}
// SampleSource listeners
// ExtractorSampleSource.EventListener
@Override
public void onLoadStarted(int sourceId, long length, int type, int trigger, Format format,
long mediaStartTimeMs, long mediaEndTimeMs) {
public void onLoadError(IOException error) {
printInternalError("loadError", error);
}
// AdaptiveSourceEventListener
@Override
public void onLoadStarted(DataSpec dataSpec, int dataType, int trackType, Format format,
int formatEvaluatorTrigger, Object formatEvaluatorData, long mediaStartTimeMs,
long mediaEndTimeMs, long elapsedRealtimeMs) {
// Do nothing.
}
@Override
public void onLoadError(int sourceId, IOException e) {
printInternalError("loadError", e);
public void onLoadError(DataSpec dataSpec, int dataType, int trackType, Format format,
int formatEvaluatorTrigger, Object formatEvaluatorData, long mediaStartTimeMs,
long mediaEndTimeMs, long elapsedRealtimeMs, long loadDurationMs, long bytesLoaded,
IOException error, boolean wasCanceled) {
printInternalError("loadError", error);
}
@Override
public void onLoadCanceled(int sourceId, long bytesLoaded) {
public void onLoadCanceled(DataSpec dataSpec, int dataType, int trackType, Format format,
int formatEvaluatorTrigger, Object formatEvaluatorData, long mediaStartTimeMs,
long mediaEndTimeMs, long elapsedRealtimeMs, long loadDurationMs, long bytesLoaded) {
// Do nothing.
}
@Override
public void onLoadCompleted(int sourceId, long bytesLoaded, int type, int trigger, Format format,
long mediaStartTimeMs, long mediaEndTimeMs, long elapsedRealtimeMs, long loadDurationMs) {
public void onLoadCompleted(DataSpec dataSpec, int dataType, int trackType, Format format,
int formatEvaluatorTrigger, Object formatEvaluatorData, long mediaStartTimeMs,
long mediaEndTimeMs, long elapsedRealtimeMs, long loadDurationMs, long bytesLoaded) {
// Do nothing.
}
@Override
public void onUpstreamDiscarded(int sourceId, long mediaStartTimeMs, long mediaEndTimeMs) {
public void onUpstreamDiscarded(int trackType, long mediaStartTimeMs, long mediaEndTimeMs) {
// Do nothing.
}
@Override
public void onDownstreamFormatChanged(int sourceId, Format format, int trigger,
long mediaTimeMs) {
public void onDownstreamFormatChanged(int trackType, Format format, int formatEvaluatorTrigger,
Object formatEvaluatorData, long mediaTimeMs) {
// Do nothing.
}

View File

@ -113,7 +113,7 @@ public final class UriSampleSourceProvider implements SampleSourceProvider {
eventLogger);
case Util.TYPE_OTHER:
return new ExtractorSampleSource(uri, dataSourceFactory, player.getBandwidthMeter(),
ExtractorSampleSource.newDefaultExtractors(), handler, eventLogger, 0);
ExtractorSampleSource.newDefaultExtractors(), handler, eventLogger);
default:
throw new IllegalStateException("Unsupported type: " + type);
}

View File

@ -81,7 +81,7 @@ public class FlacPlaybackTest extends InstrumentationTestCase {
uri,
new DefaultDataSourceFactory(context, "ExoPlayerExtFlacTest"), null,
new Extractor[] {new MatroskaExtractor()},
null, null, 0);
null, null);
player.setSource(sampleSource);
player.setPlayWhenReady(true);
Looper.loop();

View File

@ -81,7 +81,7 @@ public class OpusPlaybackTest extends InstrumentationTestCase {
uri,
new DefaultDataSourceFactory(context, "ExoPlayerExtOpusTest"), null,
new Extractor[] {new MatroskaExtractor()},
null, null, 0);
null, null);
player.setSource(sampleSource);
player.setPlayWhenReady(true);
Looper.loop();

View File

@ -97,7 +97,7 @@ public class VpxPlaybackTest extends InstrumentationTestCase {
uri,
new DefaultDataSourceFactory(context, "ExoPlayerExtVp9Test"), null,
new Extractor[] {new MatroskaExtractor()},
null, null, 0);
null, null);
player.sendMessages(new ExoPlayer.ExoPlayerMessage(videoRenderer,
LibvpxVideoTrackRenderer.MSG_SET_OUTPUT_BUFFER_RENDERER,
new VpxVideoSurfaceView(context)));

View File

@ -0,0 +1,313 @@
/*
* 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;
import com.google.android.exoplayer.chunk.FormatEvaluator;
import com.google.android.exoplayer.upstream.DataSpec;
import com.google.android.exoplayer.util.Assertions;
import android.os.Handler;
import android.os.SystemClock;
import java.io.IOException;
/**
* Interface for callbacks to be notified about events during adaptive playbacks.
*/
public interface AdaptiveSourceEventListener {
/**
* Invoked when a load begins.
*
* @param dataSpec Defines the data being loaded.
* @param dataType One of the {@link C} {@code DATA_TYPE_*} constants defining the type of data
* being loaded.
* @param trackType One of the {@link C} {@code TRACK_TYPE_*} constants if the data corresponds
* to media of a specific type. {@link C#TRACK_TYPE_UNKNOWN} otherwise.
* @param format The particular {@link Format} to which the data corresponds, or null if the data
* being loaded does not correspond to a specific format.
* @param formatEvaluatorTrigger One of the {@link FormatEvaluator} {@code TRIGGER_*} constants
* if a format evaluation was performed to determine that the data should be loaded.
* {@link FormatEvaluator#TRIGGER_UNKNOWN} otherwise.
* @param formatEvaluatorData Optional data set by a {@link FormatEvaluator} if a format
* evaluation was performed to determine that the data should be loaded. Null otherwise.
* @param mediaStartTimeMs The start time of the media being loaded, or -1 if the load is not for
* media data.
* @param mediaEndTimeMs The end time of the media being loaded, or -1 if the load is not for
* media data.
* @param elapsedRealtimeMs The value of {@link SystemClock#elapsedRealtime} when the load began.
*/
void onLoadStarted(DataSpec dataSpec, int dataType, int trackType, Format format,
int formatEvaluatorTrigger, Object formatEvaluatorData, long mediaStartTimeMs,
long mediaEndTimeMs, long elapsedRealtimeMs);
/**
* Invoked when a load ends.
*
* @param dataSpec Defines the data being loaded.
* @param dataType One of the {@link C} {@code DATA_TYPE_*} constants defining the type of data
* being loaded.
* @param trackType One of the {@link C} {@code TRACK_TYPE_*} constants if the data corresponds
* to media of a specific type. {@link C#TRACK_TYPE_UNKNOWN} otherwise.
* @param format The particular {@link Format} to which the data corresponds, or null if the data
* being loaded does not correspond to a specific format.
* @param formatEvaluatorTrigger One of the {@link FormatEvaluator} {@code TRIGGER_*} constants
* if a format evaluation was performed to determine that the data should be loaded.
* {@link FormatEvaluator#TRIGGER_UNKNOWN} otherwise.
* @param formatEvaluatorData Optional data set by a {@link FormatEvaluator} if a format
* evaluation was performed to determine that the data should be loaded. Null otherwise.
* @param mediaStartTimeMs The start time of the media being loaded, or -1 if the load is not for
* media data.
* @param mediaEndTimeMs The end time of the media being loaded, or -1 if the load is not for
* media data.
* @param elapsedRealtimeMs The value of {@link SystemClock#elapsedRealtime} when the load ended.
* @param loadDurationMs The duration of the load.
* @param bytesLoaded The number of bytes that were loaded.
*/
void onLoadCompleted(DataSpec dataSpec, int dataType, int trackType, Format format,
int formatEvaluatorTrigger, Object formatEvaluatorData, long mediaStartTimeMs,
long mediaEndTimeMs, long elapsedRealtimeMs, long loadDurationMs, long bytesLoaded);
/**
* Invoked when a load is canceled.
*
* @param dataSpec Defines the data being loaded.
* @param dataType One of the {@link C} {@code DATA_TYPE_*} constants defining the type of data
* being loaded.
* @param trackType One of the {@link C} {@code TRACK_TYPE_*} constants if the data corresponds
* to media of a specific type. {@link C#TRACK_TYPE_UNKNOWN} otherwise.
* @param format The particular {@link Format} to which the data corresponds, or null if the data
* being loaded does not correspond to a specific format.
* @param formatEvaluatorTrigger One of the {@link FormatEvaluator} {@code TRIGGER_*} constants
* if a format evaluation was performed to determine that the data should be loaded.
* {@link FormatEvaluator#TRIGGER_UNKNOWN} otherwise.
* @param formatEvaluatorData Optional data set by a {@link FormatEvaluator} if a format
* evaluation was performed to determine that the data should be loaded. Null otherwise.
* @param mediaStartTimeMs The start time of the media being loaded, or -1 if the load is not for
* media data.
* @param mediaEndTimeMs The end time of the media being loaded, or -1 if the load is not for
* media data.
* @param elapsedRealtimeMs The value of {@link SystemClock#elapsedRealtime} when the load was
* canceled.
* @param loadDurationMs The duration of the load up to the point at which it was canceled.
* @param bytesLoaded The number of bytes that were loaded prior to cancelation.
*/
void onLoadCanceled(DataSpec dataSpec, int dataType, int trackType, Format format,
int formatEvaluatorTrigger, Object formatEvaluatorData, long mediaStartTimeMs,
long mediaEndTimeMs, long elapsedRealtimeMs, long loadDurationMs, long bytesLoaded);
/**
* Invoked when a load error occurs.
* <p>
* The error may or may not have resulted in the load being canceled, as indicated by the
* {@code wasCanceled} parameter. If the load was canceled, {@link #onLoadCanceled} will
* <em>not</em> be invoked in addition to this method.
*
* @param dataSpec Defines the data being loaded.
* @param dataType One of the {@link C} {@code DATA_TYPE_*} constants defining the type of data
* being loaded.
* @param trackType One of the {@link C} {@code TRACK_TYPE_*} constants if the data corresponds
* to media of a specific type. {@link C#TRACK_TYPE_UNKNOWN} otherwise.
* @param format The particular {@link Format} to which the data corresponds, or null if the data
* being loaded does not correspond to a specific format.
* @param formatEvaluatorTrigger One of the {@link FormatEvaluator} {@code TRIGGER_*} constants
* if a format evaluation was performed to determine that the data should be loaded.
* {@link FormatEvaluator#TRIGGER_UNKNOWN} otherwise.
* @param formatEvaluatorData Optional data set by a {@link FormatEvaluator} if a format
* evaluation was performed to determine that the data should be loaded. Null otherwise.
* @param mediaStartTimeMs The start time of the media being loaded, or -1 if the load is not for
* media data.
* @param mediaEndTimeMs The end time of the media being loaded, or -1 if the load is not for
* media data.
* @param elapsedRealtimeMs The value of {@link SystemClock#elapsedRealtime} when the error
* occurred.
* @param loadDurationMs The duration of the load up to the point at which the error occurred.
* @param bytesLoaded The number of bytes that were loaded prior to the error.
* @param error The load error.
* @param wasCanceled True if the load was canceled as a result of the error. False otherwise.
*/
void onLoadError(DataSpec dataSpec, int dataType, int trackType, Format format,
int formatEvaluatorTrigger, Object formatEvaluatorData, long mediaStartTimeMs,
long mediaEndTimeMs, long elapsedRealtimeMs, long loadDurationMs, long bytesLoaded,
IOException error, boolean wasCanceled);
/**
* Invoked when data is removed from the back of a media buffer, typically so that it can be
* re-buffered in a different format.
*
* @param trackType The type of the media. One of the {@link C} {@code TRACK_TYPE_*} constants.
* @param mediaStartTimeMs The start time of the media being discarded.
* @param mediaEndTimeMs The end time of the media being discarded.
*/
void onUpstreamDiscarded(int trackType, long mediaStartTimeMs, long mediaEndTimeMs);
/**
* Invoked when a downstream format change occurs (i.e. when the format of the media being read
* from one or more {@link TrackStream}s provided by the source changes).
*
* @param trackType The type of the media. One of the {@link C} {@code TRACK_TYPE_*} constants.
* @param format The new format.
* @param formatEvaluatorTrigger One of the {@link FormatEvaluator} {@code TRIGGER_*} constants
* if a format evaluation was performed that resulted in this format change taking place.
* {@link FormatEvaluator#TRIGGER_UNKNOWN} otherwise.
* @param formatEvaluatorData Optional data set by a {@link FormatEvaluator} if a format
* evaluation was performed that resulted in this format change taking place. Null otherwise.
* @param mediaTimeMs The media time at which the change occurred.
*/
void onDownstreamFormatChanged(int trackType, Format format, int formatEvaluatorTrigger,
Object formatEvaluatorData, long mediaTimeMs);
/**
* Dispatches events to a {@link AdaptiveSourceEventListener}.
*/
final class EventDispatcher {
private final Handler handler;
private final AdaptiveSourceEventListener listener;
public EventDispatcher(Handler handler, AdaptiveSourceEventListener listener) {
this.handler = listener != null ? Assertions.checkNotNull(handler) : null;
this.listener = listener;
}
public void loadStarted(DataSpec dataSpec, int dataType, long elapsedRealtimeMs) {
loadStarted(dataSpec, dataType, C.TRACK_TYPE_UNKNOWN, null, FormatEvaluator.TRIGGER_UNKNOWN,
null, C.UNSET_TIME_US, C.UNSET_TIME_US, elapsedRealtimeMs);
}
public void loadStarted(final DataSpec dataSpec, final int dataType, final int trackType,
final Format format, final int formatEvaluatorTrigger, final Object formatEvaluatorData,
final long mediaStartTimeUs, final long mediaEndTimeUs, final long elapsedRealtimeMs) {
if (listener != null) {
handler.post(new Runnable() {
@Override
public void run() {
listener.onLoadStarted(dataSpec, dataType, trackType, format, formatEvaluatorTrigger,
formatEvaluatorData, usToMs(mediaStartTimeUs), usToMs(mediaEndTimeUs),
elapsedRealtimeMs);
}
});
}
}
public void loadCompleted(DataSpec dataSpec, int dataType, long elapsedRealtimeMs,
long loadDurationMs, long bytesLoaded) {
loadCompleted(dataSpec, dataType, C.TRACK_TYPE_UNKNOWN, null, FormatEvaluator.TRIGGER_UNKNOWN,
null, C.UNSET_TIME_US, C.UNSET_TIME_US, elapsedRealtimeMs, loadDurationMs, bytesLoaded);
}
public void loadCompleted(final DataSpec dataSpec, final int dataType, final int trackType,
final Format format, final int formatEvaluatorTrigger, final Object formatEvaluatorData,
final long mediaStartTimeUs, final long mediaEndTimeUs, final long elapsedRealtimeMs,
final long loadDurationMs, final long bytesLoaded) {
if (listener != null) {
handler.post(new Runnable() {
@Override
public void run() {
listener.onLoadCompleted(dataSpec, dataType, trackType, format, formatEvaluatorTrigger,
formatEvaluatorData, usToMs(mediaStartTimeUs), usToMs(mediaEndTimeUs),
elapsedRealtimeMs, loadDurationMs, bytesLoaded);
}
});
}
}
public void loadCanceled(DataSpec dataSpec, int dataType, long elapsedRealtimeMs,
long loadDurationMs, long bytesLoaded) {
loadCanceled(dataSpec, dataType, C.TRACK_TYPE_UNKNOWN, null, FormatEvaluator.TRIGGER_UNKNOWN,
null, C.UNSET_TIME_US, C.UNSET_TIME_US, elapsedRealtimeMs, loadDurationMs, bytesLoaded);
}
public void loadCanceled(final DataSpec dataSpec, final int dataType, final int trackType,
final Format format, final int formatEvaluatorTrigger, final Object formatEvaluatorData,
final long mediaStartTimeUs, final long mediaEndTimeUs, final long elapsedRealtimeMs,
final long loadDurationMs, final long bytesLoaded) {
if (listener != null) {
handler.post(new Runnable() {
@Override
public void run() {
listener.onLoadCanceled(dataSpec, dataType, trackType, format, formatEvaluatorTrigger,
formatEvaluatorData, usToMs(mediaStartTimeUs), usToMs(mediaEndTimeUs),
elapsedRealtimeMs, loadDurationMs, bytesLoaded);
}
});
}
}
public void loadError(DataSpec dataSpec, int dataType, long elapsedRealtimeMs,
long loadDurationMs, long bytesLoaded, IOException error, boolean wasCanceled) {
loadError(dataSpec, dataType, C.TRACK_TYPE_UNKNOWN, null, FormatEvaluator.TRIGGER_UNKNOWN,
null, C.UNSET_TIME_US, C.UNSET_TIME_US, elapsedRealtimeMs, loadDurationMs, bytesLoaded,
error, wasCanceled);
}
public void loadError(final DataSpec dataSpec, final int dataType, final int trackType,
final Format format, final int formatEvaluatorTrigger, final Object formatEvaluatorData,
final long mediaStartTimeUs, final long mediaEndTimeUs, final long elapsedRealtimeMs,
final long loadDurationMs, final long bytesLoaded, final IOException error,
final boolean wasCanceled) {
if (listener != null) {
handler.post(new Runnable() {
@Override
public void run() {
listener.onLoadError(dataSpec, dataType, trackType, format, formatEvaluatorTrigger,
formatEvaluatorData, usToMs(mediaStartTimeUs), usToMs(mediaEndTimeUs),
elapsedRealtimeMs, loadDurationMs, bytesLoaded, error, wasCanceled);
}
});
}
}
public void upstreamDiscarded(final int trackType, final long mediaStartTimeUs,
final long mediaEndTimeUs) {
if (listener != null) {
handler.post(new Runnable() {
@Override
public void run() {
listener.onUpstreamDiscarded(trackType, usToMs(mediaStartTimeUs),
usToMs(mediaEndTimeUs));
}
});
}
}
public void downstreamFormatChanged(final int trackType, final Format format,
final int formatEvaluatorTrigger, final Object formatEvaluatorData,
final long mediaTimeUs) {
if (listener != null) {
handler.post(new Runnable() {
@Override
public void run() {
listener.onDownstreamFormatChanged(trackType, format, formatEvaluatorTrigger,
formatEvaluatorData, usToMs(mediaTimeUs));
}
});
}
}
/**
* Converts microseconds to milliseconds (rounding down), mapping {@link C#UNSET_TIME_US} to -1
*
* @param timeUs A microsecond value.
* @return The value in milliseconds.
*/
private static long usToMs(long timeUs) {
return timeUs == C.UNSET_TIME_US ? -1 : (timeUs / 1000);
}
}
}

View File

@ -168,7 +168,7 @@ public interface C {
int DATA_TYPE_MEDIA_INITIALIZATION = 2;
/**
* A data type constant for drm or encryption related data.
* A data type constant for drm or encryption data.
*/
int DATA_TYPE_DRM = 3;
@ -177,6 +177,11 @@ public interface C {
*/
int DATA_TYPE_MANIFEST = 4;
/**
* A data type constant for time synchronization data.
*/
int DATA_TYPE_TIME_SYNCHRONIZATION = 5;
/**
* Applications or extensions may define custom {@code DATA_TYPE_*} constants greater than or
* equal to this value.

View File

@ -215,20 +215,23 @@ public final class SingleSampleSource implements SampleSource, TrackStream,
// Loader.Callback implementation.
@Override
public void onLoadCompleted(SingleSampleSource loadable, long elapsedMs) {
public void onLoadCompleted(SingleSampleSource loadable, long elapsedRealtimeMs,
long loadDurationMs) {
loadingFinished = true;
}
@Override
public void onLoadCanceled(SingleSampleSource loadable, long elapsedMs, boolean released) {
public void onLoadCanceled(SingleSampleSource loadable, long elapsedRealtimeMs,
long loadDurationMs, boolean released) {
if (!released) {
maybeStartLoading();
}
}
@Override
public int onLoadError(SingleSampleSource loadable, long elapsedMs, IOException e) {
notifyLoadError(e);
public int onLoadError(SingleSampleSource loadable, long elapsedRealtimeMs,
long loadDurationMs, IOException error) {
notifyLoadError(error);
return Loader.RETRY;
}

View File

@ -31,17 +31,20 @@ public abstract class BaseMediaChunk extends MediaChunk {
private int firstSampleIndex;
/**
* @param dataSource A {@link DataSource} for loading the data.
* @param dataSource The source from which the data should be loaded.
* @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 format See {@link #format}.
* @param formatEvaluatorTrigger See {@link #formatEvaluatorTrigger}.
* @param formatEvaluatorData See {@link #formatEvaluatorData}.
* @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.
*/
public BaseMediaChunk(DataSource dataSource, DataSpec dataSpec, int trigger, Format format,
long startTimeUs, long endTimeUs, int chunkIndex) {
super(dataSource, dataSpec, trigger, format, startTimeUs, endTimeUs, chunkIndex);
public BaseMediaChunk(DataSource dataSource, DataSpec dataSpec, Format format,
int formatEvaluatorTrigger, Object formatEvaluatorData, long startTimeUs, long endTimeUs,
int chunkIndex) {
super(dataSource, dataSpec, format, formatEvaluatorTrigger, formatEvaluatorData, startTimeUs,
endTimeUs, chunkIndex);
}
/**

View File

@ -29,67 +29,63 @@ import com.google.android.exoplayer.util.Assertions;
public abstract class Chunk implements Loadable {
/**
* Value of {@link #trigger} for a load whose reason is unspecified.
* The {@link DataSpec} that defines the data to be loaded.
*/
public static final int TRIGGER_UNSPECIFIED = 0;
/**
* Value of {@link #trigger} for a load triggered by an initial format selection.
*/
public static final int TRIGGER_INITIAL = 1;
/**
* Value of {@link #trigger} for a load triggered by a user initiated format selection.
*/
public static final int TRIGGER_MANUAL = 2;
/**
* Value of {@link #trigger} for a load triggered by an adaptive format selection.
*/
public static final int TRIGGER_ADAPTIVE = 3;
/**
* Value of {@link #trigger} for a load triggered whilst in a trick play mode.
*/
public static final int TRIGGER_TRICK_PLAY = 4;
/**
* Implementations may define custom {@link #trigger} codes greater than or equal to this value.
*/
public static final int TRIGGER_CUSTOM_BASE = 10000;
public final DataSpec dataSpec;
/**
* The type of the chunk. One of the {@code DATA_TYPE_*} constants defined in {@link C}. For
* reporting only.
*/
public final int type;
/**
* The reason why the chunk was generated. For reporting only.
* One of the {@link FormatEvaluator} {@code TRIGGER_*} constants if a format evaluation was
* performed to determine that this chunk should be loaded.
* {@link FormatEvaluator#TRIGGER_UNKNOWN} otherwise.
*/
public final int trigger;
public final int formatEvaluatorTrigger;
/**
* Optional data set by a {@link FormatEvaluator} if a format evaluation was performed to
* determine that this chunk should be loaded. Null otherwise.
*/
public final Object formatEvaluatorData;
/**
* The format associated with the data being loaded, or null if the data being loaded is not
* associated with a specific format.
*/
public final Format format;
/**
* The {@link DataSpec} that defines the data to be loaded.
* The start time of the media contained by the chunk, or {@link C#UNSET_TIME_US} if the data
* being loaded does not contain media samples.
*/
public final DataSpec dataSpec;
public final long startTimeUs;
/**
* The end time of the media contained by the chunk, or {@link C#UNSET_TIME_US} if the data being
* loaded does not contain media samples.
*/
public final long endTimeUs;
protected final DataSource dataSource;
/**
* @param dataSource The source from which the data should be loaded.
* @param dataSpec Defines the data to be loaded. {@code dataSpec.length} must not exceed
* {@link Integer#MAX_VALUE}. If {@code dataSpec.length == C.LENGTH_UNBOUNDED} then
* the length resolved by {@code dataSource.open(dataSpec)} must not exceed
* {@link Integer#MAX_VALUE}.
* @param dataSpec Defines the data to be loaded.
* @param type See {@link #type}.
* @param trigger See {@link #trigger}.
* @param format See {@link #format}.
* @param formatEvaluatorTrigger See {@link #formatEvaluatorTrigger}.
* @param formatEvaluatorData See {@link #formatEvaluatorData}.
* @param startTimeUs See {@link #startTimeUs}.
* @param endTimeUs See {@link #endTimeUs}.
*/
public Chunk(DataSource dataSource, DataSpec dataSpec, int type, int trigger, Format format) {
public Chunk(DataSource dataSource, DataSpec dataSpec, int type, Format format,
int formatEvaluatorTrigger, Object formatEvaluatorData, long startTimeUs, long endTimeUs) {
this.dataSource = Assertions.checkNotNull(dataSource);
this.dataSpec = Assertions.checkNotNull(dataSpec);
this.type = type;
this.trigger = trigger;
this.format = format;
this.formatEvaluatorTrigger = formatEvaluatorTrigger;
this.formatEvaluatorData = formatEvaluatorData;
this.startTimeUs = startTimeUs;
this.endTimeUs = endTimeUs;
}
/**

View File

@ -15,18 +15,17 @@
*/
package com.google.android.exoplayer.chunk;
import com.google.android.exoplayer.AdaptiveSourceEventListener.EventDispatcher;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.DecoderInputBuffer;
import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.FormatHolder;
import com.google.android.exoplayer.LoadControl;
import com.google.android.exoplayer.TrackStream;
import com.google.android.exoplayer.chunk.ChunkTrackStreamEventListener.EventDispatcher;
import com.google.android.exoplayer.extractor.DefaultTrackOutput;
import com.google.android.exoplayer.upstream.Loader;
import com.google.android.exoplayer.util.Assertions;
import android.os.Handler;
import android.os.SystemClock;
import java.io.IOException;
@ -40,15 +39,16 @@ import java.util.List;
public class ChunkTrackStream<T extends ChunkSource> implements TrackStream,
Loader.Callback<Chunk> {
private final Loader loader;
private final int trackType;
private final T chunkSource;
private final LoadControl loadControl;
private final EventDispatcher eventDispatcher;
private final int minLoadableRetryCount;
private final LinkedList<BaseMediaChunk> mediaChunks;
private final List<BaseMediaChunk> readOnlyMediaChunks;
private final DefaultTrackOutput sampleQueue;
private final ChunkHolder nextChunkHolder;
private final EventDispatcher eventDispatcher;
private final LoadControl loadControl;
private final Loader loader;
private boolean readingEnabled;
private long lastPreferredQueueSizeEvaluationTimeMs;
@ -61,25 +61,24 @@ public class ChunkTrackStream<T extends ChunkSource> implements TrackStream,
private boolean loadingFinished;
/**
* @param trackType The type of the track. One of the {@link C} {@code TRACK_TYPE_*} constants.
* @param chunkSource A {@link ChunkSource} from which chunks to load are obtained.
* @param loadControl Controls when the source is permitted to load data.
* @param bufferSizeContribution The contribution of this source to the media buffer, in bytes.
* @param positionUs The position from which to start loading media.
* @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
* null if delivery of events is not required.
* @param eventListener A listener of events. May be null if delivery of events is not required.
* @param eventSourceId An identifier that gets passed to {@code eventListener} methods.
* @param minLoadableRetryCount The minimum number of times that the source should retry a load
* before propagating an error.
* @param eventDispatcher A dispatcher to notify of events.
*/
public ChunkTrackStream(T chunkSource, LoadControl loadControl,
int bufferSizeContribution, long positionUs, Handler eventHandler,
ChunkTrackStreamEventListener eventListener, int eventSourceId, int minLoadableRetryCount) {
public ChunkTrackStream(int trackType, T chunkSource, LoadControl loadControl,
int bufferSizeContribution, long positionUs, int minLoadableRetryCount,
EventDispatcher eventDispatcher) {
this.trackType = trackType;
this.chunkSource = chunkSource;
this.loadControl = loadControl;
this.eventDispatcher = eventDispatcher;
this.minLoadableRetryCount = minLoadableRetryCount;
loader = new Loader("Loader:ChunkTrackStream");
eventDispatcher = new EventDispatcher(eventHandler, eventListener, eventSourceId);
nextChunkHolder = new ChunkHolder();
mediaChunks = new LinkedList<>();
readOnlyMediaChunks = Collections.unmodifiableList(mediaChunks);
@ -204,7 +203,8 @@ public class ChunkTrackStream<T extends ChunkSource> implements TrackStream,
Format format = currentChunk.format;
if (!format.equals(downstreamFormat)) {
eventDispatcher.downstreamFormatChanged(format, currentChunk.trigger,
eventDispatcher.downstreamFormatChanged(trackType, format,
currentChunk.formatEvaluatorTrigger, currentChunk.formatEvaluatorData,
currentChunk.startTimeUs);
}
downstreamFormat = format;
@ -215,35 +215,34 @@ public class ChunkTrackStream<T extends ChunkSource> implements TrackStream,
// Loader.Callback implementation.
@Override
public void onLoadCompleted(Chunk loadable, long elapsedMs) {
long now = SystemClock.elapsedRealtime();
public void onLoadCompleted(Chunk loadable, long elapsedRealtimeMs, long loadDurationMs) {
chunkSource.onChunkLoadCompleted(loadable);
if (isMediaChunk(loadable)) {
BaseMediaChunk mediaChunk = (BaseMediaChunk) loadable;
eventDispatcher.loadCompleted(loadable.bytesLoaded(), mediaChunk.type,
mediaChunk.trigger, mediaChunk.format, mediaChunk.startTimeUs, mediaChunk.endTimeUs, now,
elapsedMs);
} else {
eventDispatcher.loadCompleted(loadable.bytesLoaded(), loadable.type, loadable.trigger,
loadable.format, -1, -1, now, elapsedMs);
}
eventDispatcher.loadCompleted(loadable.dataSpec, loadable.type, trackType, loadable.format,
loadable.formatEvaluatorTrigger, loadable.formatEvaluatorData, loadable.startTimeUs,
loadable.endTimeUs, elapsedRealtimeMs, loadDurationMs, loadable.bytesLoaded());
maybeStartLoading();
}
@Override
public void onLoadCanceled(Chunk loadable, long elapsedMs, boolean released) {
eventDispatcher.loadCanceled(loadable.bytesLoaded());
public void onLoadCanceled(Chunk loadable, long elapsedRealtimeMs, long loadDurationMs,
boolean released) {
eventDispatcher.loadCanceled(loadable.dataSpec, loadable.type, trackType, loadable.format,
loadable.formatEvaluatorTrigger, loadable.formatEvaluatorData, loadable.startTimeUs,
loadable.endTimeUs, elapsedRealtimeMs, loadDurationMs, loadable.bytesLoaded());
if (!released) {
restartFrom(pendingResetPositionUs);
}
}
@Override
public int onLoadError(Chunk loadable, long elapsedMs, IOException e) {
public int onLoadError(Chunk loadable, long elapsedRealtimeMs, long loadDurationMs,
IOException error) {
long bytesLoaded = loadable.bytesLoaded();
boolean isMediaChunk = isMediaChunk(loadable);
boolean cancelable = !isMediaChunk || bytesLoaded == 0 || mediaChunks.size() > 1;
if (chunkSource.onChunkLoadError(loadable, cancelable, e)) {
boolean canceled = false;
if (chunkSource.onChunkLoadError(loadable, cancelable, error)) {
canceled = true;
if (isMediaChunk) {
BaseMediaChunk removed = mediaChunks.removeLast();
Assertions.checkState(removed == loadable);
@ -252,12 +251,15 @@ public class ChunkTrackStream<T extends ChunkSource> implements TrackStream,
pendingResetPositionUs = lastSeekPositionUs;
}
}
eventDispatcher.loadError(e);
eventDispatcher.loadCanceled(bytesLoaded);
}
eventDispatcher.loadError(loadable.dataSpec, loadable.type, trackType, loadable.format,
loadable.formatEvaluatorTrigger, loadable.formatEvaluatorData, loadable.startTimeUs,
loadable.endTimeUs, elapsedRealtimeMs, loadDurationMs, bytesLoaded, error,
canceled);
if (canceled) {
maybeStartLoading();
return Loader.DONT_RETRY;
} else {
eventDispatcher.loadError(e);
return Loader.RETRY;
}
}
@ -312,13 +314,11 @@ public class ChunkTrackStream<T extends ChunkSource> implements TrackStream,
BaseMediaChunk mediaChunk = (BaseMediaChunk) loadable;
mediaChunk.init(sampleQueue);
mediaChunks.add(mediaChunk);
eventDispatcher.loadStarted(mediaChunk.dataSpec.length, mediaChunk.type, mediaChunk.trigger,
mediaChunk.format, mediaChunk.startTimeUs, mediaChunk.endTimeUs);
} else {
eventDispatcher.loadStarted(loadable.dataSpec.length, loadable.type, loadable.trigger,
loadable.format, -1, -1);
}
loader.startLoading(loadable, this, minLoadableRetryCount);
long elapsedRealtimeMs = loader.startLoading(loadable, this, minLoadableRetryCount);
eventDispatcher.loadStarted(loadable.dataSpec, loadable.type, trackType, loadable.format,
loadable.formatEvaluatorTrigger, loadable.formatEvaluatorData, loadable.startTimeUs,
loadable.endTimeUs, elapsedRealtimeMs);
// Update the load control again to indicate that we're now loading.
loadControl.update(this, downstreamPositionUs, getNextLoadPositionUs(), true);
}
@ -363,7 +363,7 @@ public class ChunkTrackStream<T extends ChunkSource> implements TrackStream,
loadingFinished = false;
}
sampleQueue.discardUpstreamSamples(removed.getFirstSampleIndex());
eventDispatcher.upstreamDiscarded(startTimeUs, endTimeUs);
eventDispatcher.upstreamDiscarded(trackType, startTimeUs, endTimeUs);
return true;
}

View File

@ -1,200 +0,0 @@
/*
* 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.chunk;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.TrackStream;
import com.google.android.exoplayer.util.Assertions;
import com.google.android.exoplayer.util.Util;
import android.os.Handler;
import java.io.IOException;
/**
* Interface for callbacks to be notified of chunk based {@link ChunkTrackStream} events.
*/
public interface ChunkTrackStreamEventListener {
/**
* Invoked when an upstream load is started.
*
* @param sourceId The id of the reporting {@link ChunkTrackStream}.
* @param length The length of the data being loaded in bytes, or {@link C#LENGTH_UNBOUNDED} if
* the length of the data is not known in advance.
* @param type The type of the data being loaded.
* @param trigger The reason for the data being loaded.
* @param format The particular format to which this data corresponds, or null if the data being
* loaded does not correspond to a format.
* @param mediaStartTimeMs The media time of the start of the data being loaded, or -1 if this
* load is for initialization data.
* @param mediaEndTimeMs The media time of the end of the data being loaded, or -1 if this
* load is for initialization data.
*/
void onLoadStarted(int sourceId, long length, int type, int trigger, Format format,
long mediaStartTimeMs, long mediaEndTimeMs);
/**
* Invoked when the current load operation completes.
*
* @param sourceId The id of the reporting {@link ChunkTrackStream}.
* @param bytesLoaded The number of bytes that were loaded.
* @param type The type of the loaded data.
* @param trigger The reason for the data being loaded.
* @param format The particular format to which this data corresponds, or null if the loaded data
* does not correspond to a format.
* @param mediaStartTimeMs The media time of the start of the loaded data, or -1 if this load was
* for initialization data.
* @param mediaEndTimeMs The media time of the end of the loaded data, or -1 if this load was for
* initialization data.
* @param elapsedRealtimeMs {@code elapsedRealtime} timestamp of when the load finished.
* @param loadDurationMs Amount of time taken to load the data.
*/
void onLoadCompleted(int sourceId, long bytesLoaded, int type, int trigger, Format format,
long mediaStartTimeMs, long mediaEndTimeMs, long elapsedRealtimeMs, long loadDurationMs);
/**
* Invoked when the current upstream load operation is canceled.
*
* @param sourceId The id of the reporting {@link ChunkTrackStream}.
* @param bytesLoaded The number of bytes that were loaded prior to the cancellation.
*/
void onLoadCanceled(int sourceId, long bytesLoaded);
/**
* Invoked when an error occurs loading media data.
*
* @param sourceId The id of the reporting {@link ChunkTrackStream}.
* @param e The cause of the failure.
*/
void onLoadError(int sourceId, IOException e);
/**
* Invoked when data is removed from the back of the buffer, typically so that it can be
* re-buffered using a different representation.
*
* @param sourceId The id of the reporting {@link ChunkTrackStream}.
* @param mediaStartTimeMs The media time of the start of the discarded data.
* @param mediaEndTimeMs The media time of the end of the discarded data.
*/
void onUpstreamDiscarded(int sourceId, long mediaStartTimeMs, long mediaEndTimeMs);
/**
* Invoked when the downstream format changes (i.e. when the format being supplied to the
* caller of {@link TrackStream#readData} changes).
*
* @param sourceId The id of the reporting {@link ChunkTrackStream}.
* @param format The format.
* @param trigger The trigger specified in the corresponding upstream load, as specified by the
* {@link ChunkSource}.
* @param mediaTimeMs The media time at which the change occurred.
*/
void onDownstreamFormatChanged(int sourceId, Format format, int trigger, long mediaTimeMs);
/**
* Dispatches events to a {@link ChunkTrackStreamEventListener}.
*/
final class EventDispatcher {
private final Handler handler;
private final ChunkTrackStreamEventListener listener;
private final int sourceId;
public EventDispatcher(Handler handler, ChunkTrackStreamEventListener listener, int sourceId) {
this.handler = listener != null ? Assertions.checkNotNull(handler) : null;
this.listener = listener;
this.sourceId = sourceId;
}
public void loadStarted(final long length, final int type, final int trigger,
final Format format, final long mediaStartTimeUs, final long mediaEndTimeUs) {
if (listener != null) {
handler.post(new Runnable() {
@Override
public void run() {
listener.onLoadStarted(sourceId, length, type, trigger, format,
Util.usToMs(mediaStartTimeUs), Util.usToMs(mediaEndTimeUs));
}
});
}
}
public void loadCompleted(final long bytesLoaded, final int type, final int trigger,
final Format format, final long mediaStartTimeUs, final long mediaEndTimeUs,
final long elapsedRealtimeMs, final long loadDurationMs) {
if (listener != null) {
handler.post(new Runnable() {
@Override
public void run() {
listener.onLoadCompleted(sourceId, bytesLoaded, type, trigger, format,
Util.usToMs(mediaStartTimeUs), Util.usToMs(mediaEndTimeUs), elapsedRealtimeMs,
loadDurationMs);
}
});
}
}
public void loadCanceled(final long bytesLoaded) {
if (listener != null) {
handler.post(new Runnable() {
@Override
public void run() {
listener.onLoadCanceled(sourceId, bytesLoaded);
}
});
}
}
public void loadError(final IOException e) {
if (listener != null) {
handler.post(new Runnable() {
@Override
public void run() {
listener.onLoadError(sourceId, e);
}
});
}
}
public void upstreamDiscarded(final long mediaStartTimeUs, final long mediaEndTimeUs) {
if (listener != null) {
handler.post(new Runnable() {
@Override
public void run() {
listener.onUpstreamDiscarded(sourceId, Util.usToMs(mediaStartTimeUs),
Util.usToMs(mediaEndTimeUs));
}
});
}
}
public void downstreamFormatChanged(final Format format, final int trigger,
final long positionUs) {
if (listener != null) {
handler.post(new Runnable() {
@Override
public void run() {
listener.onDownstreamFormatChanged(sourceId, format, trigger,
Util.usToMs(positionUs));
}
});
}
}
}
}

View File

@ -42,10 +42,11 @@ public class ContainerMediaChunk extends BaseMediaChunk implements SingleTrackMe
private volatile boolean loadCompleted;
/**
* @param dataSource A {@link DataSource} for loading the data.
* @param dataSource The source from which the data should be loaded.
* @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 format See {@link #format}.
* @param formatEvaluatorTrigger See {@link #formatEvaluatorTrigger}.
* @param formatEvaluatorData See {@link #formatEvaluatorData}.
* @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.
@ -54,10 +55,12 @@ public class ContainerMediaChunk extends BaseMediaChunk implements SingleTrackMe
* @param sampleFormat The {@link Format} of the samples in the chunk, if known. May be null if
* the data is known to define its own sample format.
*/
public ContainerMediaChunk(DataSource dataSource, DataSpec dataSpec, int trigger, Format format,
long startTimeUs, long endTimeUs, int chunkIndex, long sampleOffsetUs,
ChunkExtractorWrapper extractorWrapper, Format sampleFormat) {
super(dataSource, dataSpec, trigger, format, startTimeUs, endTimeUs, chunkIndex);
public ContainerMediaChunk(DataSource dataSource, DataSpec dataSpec, Format format,
int formatEvaluatorTrigger, Object formatEvaluatorData, long startTimeUs, long endTimeUs,
int chunkIndex, long sampleOffsetUs, ChunkExtractorWrapper extractorWrapper,
Format sampleFormat) {
super(dataSource, dataSpec, format, formatEvaluatorTrigger, formatEvaluatorData, startTimeUs,
endTimeUs, chunkIndex);
this.extractorWrapper = extractorWrapper;
this.sampleOffsetUs = sampleOffsetUs;
this.sampleFormat = sampleFormat;

View File

@ -15,6 +15,7 @@
*/
package com.google.android.exoplayer.chunk;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.upstream.DataSource;
import com.google.android.exoplayer.upstream.DataSpec;
@ -37,18 +38,17 @@ public abstract class DataChunk extends Chunk {
/**
* @param dataSource The source from which the data should be loaded.
* @param dataSpec Defines the data to be loaded. {@code dataSpec.length} must not exceed
* {@link Integer#MAX_VALUE}. If {@code dataSpec.length == C.LENGTH_UNBOUNDED} then
* the length resolved by {@code dataSource.open(dataSpec)} must not exceed
* {@link Integer#MAX_VALUE}.
* @param dataSpec Defines the data to be loaded.
* @param type See {@link #type}.
* @param trigger See {@link #trigger}.
* @param format See {@link #format}.
* @param formatEvaluatorTrigger See {@link #formatEvaluatorTrigger}.
* @param formatEvaluatorData See {@link #formatEvaluatorData}.
* @param data An optional recycled array that can be used as a holder for the data.
*/
public DataChunk(DataSource dataSource, DataSpec dataSpec, int type, int trigger, Format format,
byte[] data) {
super(dataSource, dataSpec, type, trigger, format);
public DataChunk(DataSource dataSource, DataSpec dataSpec, int type, Format format,
int formatEvaluatorTrigger, Object formatEvaluatorData, byte[] data) {
super(dataSource, dataSpec, type, format, formatEvaluatorTrigger, formatEvaluatorData,
C.UNSET_TIME_US, C.UNSET_TIME_US);
this.data = data;
}

View File

@ -25,7 +25,33 @@ import java.util.Random;
* Selects from a number of available formats during playback.
*/
public interface FormatEvaluator {
/**
* A trigger for a load whose reason is unknown or unspecified.
*/
int TRIGGER_UNKNOWN = 0;
/**
* A trigger for a load triggered by an initial format selection.
*/
int TRIGGER_INITIAL = 1;
/**
* A trigger for a load triggered by a user initiated format selection.
*/
int TRIGGER_MANUAL = 2;
/**
* A trigger for a load triggered by an adaptive format selection.
*/
int TRIGGER_ADAPTIVE = 3;
/**
* A trigger for a load triggered whilst in a trick play mode.
*/
int TRIGGER_TRICK_PLAY = 4;
/**
* Applications or extensions may define custom {@code TRIGGER_*} constants greater than or equal
* to this value.
*/
int TRIGGER_CUSTOM_BASE = 10000;
/**
* Enables the evaluator.
*
@ -42,8 +68,8 @@ public interface FormatEvaluator {
* Update the supplied evaluation.
* <p>
* When invoked, {@code evaluation} must contain the currently selected format (null for an
* initial evaluation) and the most recent trigger {@link Chunk#TRIGGER_INITIAL} for an initial
* evaluation).
* initial evaluation), the most recent trigger {@link #TRIGGER_INITIAL} for an initial
* evaluation) and the most recent evaluation data (null for an initial evaluation).
*
* @param bufferedDurationUs The duration of media currently buffered in microseconds.
* @param blacklistFlags An array whose length is equal to the number of available formats. A
@ -82,16 +108,13 @@ public interface FormatEvaluator {
*/
public int trigger;
public Evaluation() {
trigger = Chunk.TRIGGER_INITIAL;
}
/**
* Clears {@link #format} and sets {@link #trigger} to {@link Chunk#TRIGGER_INITIAL}.
* Sticky optional data relating to the evaluation.
*/
public void clear() {
format = null;
trigger = Chunk.TRIGGER_INITIAL;
public Object data;
public Evaluation() {
trigger = TRIGGER_INITIAL;
}
}
@ -150,7 +173,7 @@ public interface FormatEvaluator {
}
Format newFormat = formats[formatIndex];
if (evaluation.format != null && evaluation.format != newFormat) {
evaluation.trigger = Chunk.TRIGGER_ADAPTIVE;
evaluation.trigger = TRIGGER_ADAPTIVE;
}
evaluation.format = newFormat;
}
@ -173,12 +196,12 @@ public interface FormatEvaluator {
*/
final class AdaptiveEvaluator implements FormatEvaluator {
public static final int DEFAULT_MAX_INITIAL_BITRATE = 800000;
private static final int DEFAULT_MAX_INITIAL_BITRATE = 800000;
public static final int DEFAULT_MIN_DURATION_FOR_QUALITY_INCREASE_MS = 10000;
public static final int DEFAULT_MAX_DURATION_FOR_QUALITY_DECREASE_MS = 25000;
public static final int DEFAULT_MIN_DURATION_TO_RETAIN_AFTER_DISCARD_MS = 25000;
public static final float DEFAULT_BANDWIDTH_FRACTION = 0.75f;
private static final int DEFAULT_MIN_DURATION_FOR_QUALITY_INCREASE_MS = 10000;
private static final int DEFAULT_MAX_DURATION_FOR_QUALITY_DECREASE_MS = 25000;
private static final int DEFAULT_MIN_DURATION_TO_RETAIN_AFTER_DISCARD_MS = 25000;
private static final float DEFAULT_BANDWIDTH_FRACTION = 0.75f;
private final BandwidthMeter bandwidthMeter;
private final int maxInitialBitrate;
@ -257,7 +280,7 @@ public interface FormatEvaluator {
ideal = current;
}
if (current != null && ideal != current) {
evaluation.trigger = Chunk.TRIGGER_ADAPTIVE;
evaluation.trigger = TRIGGER_ADAPTIVE;
}
evaluation.format = ideal;
}

View File

@ -48,17 +48,18 @@ public final class InitializationChunk extends Chunk implements SingleTrackMetad
private volatile boolean loadCanceled;
/**
* Constructor for a chunk of media samples.
*
* @param dataSource A {@link DataSource} for loading the initialization data.
* @param dataSpec Defines the initialization 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 dataSource The source from which the data should be loaded.
* @param dataSpec Defines the data to be loaded.
* @param format See {@link #format}.
* @param formatEvaluatorTrigger See {@link #formatEvaluatorTrigger}.
* @param formatEvaluatorData See {@link #formatEvaluatorData}.
* @param extractorWrapper A wrapped extractor to use for parsing the initialization data.
*/
public InitializationChunk(DataSource dataSource, DataSpec dataSpec, int trigger, Format format,
public InitializationChunk(DataSource dataSource, DataSpec dataSpec, Format format,
int formatEvaluatorTrigger, Object formatEvaluatorData,
ChunkExtractorWrapper extractorWrapper) {
super(dataSource, dataSpec, C.DATA_TYPE_MEDIA_INITIALIZATION, trigger, format);
super(dataSource, dataSpec, C.DATA_TYPE_MEDIA_INITIALIZATION, format, formatEvaluatorTrigger,
formatEvaluatorData, C.UNSET_TIME_US, C.UNSET_TIME_US);
this.extractorWrapper = extractorWrapper;
}

View File

@ -26,34 +26,27 @@ import com.google.android.exoplayer.util.Assertions;
*/
public abstract class MediaChunk extends Chunk {
/**
* The start time of the media contained by the chunk.
*/
public final long startTimeUs;
/**
* The end time of the media contained by the chunk.
*/
public final long endTimeUs;
/**
* The chunk index.
*/
public final int chunkIndex;
/**
* @param dataSource A {@link DataSource} for loading the data.
* @param dataSource The source from which the data should be loaded.
* @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 format See {@link #format}.
* @param formatEvaluatorTrigger See {@link #formatEvaluatorTrigger}.
* @param formatEvaluatorData See {@link #formatEvaluatorData}.
* @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.
*/
public MediaChunk(DataSource dataSource, DataSpec dataSpec, int trigger, Format format,
long startTimeUs, long endTimeUs, int chunkIndex) {
super(dataSource, dataSpec, C.DATA_TYPE_MEDIA, trigger, format);
public MediaChunk(DataSource dataSource, DataSpec dataSpec, Format format,
int formatEvaluatorTrigger, Object formatEvaluatorData, long startTimeUs, long endTimeUs,
int chunkIndex) {
super(dataSource, dataSpec, C.DATA_TYPE_MEDIA, format, formatEvaluatorTrigger,
formatEvaluatorData, startTimeUs, endTimeUs);
Assertions.checkNotNull(format);
this.startTimeUs = startTimeUs;
this.endTimeUs = endTimeUs;
this.chunkIndex = chunkIndex;
}

View File

@ -38,18 +38,20 @@ public final class SingleSampleMediaChunk extends BaseMediaChunk {
private volatile boolean loadCompleted;
/**
* @param dataSource A {@link DataSource} for loading the data.
* @param dataSource The source from which the data should be loaded.
* @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 format See {@link #format}.
* @param formatEvaluatorTrigger See {@link #formatEvaluatorTrigger}.
* @param formatEvaluatorData See {@link #formatEvaluatorData}.
* @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 sampleFormat The format of the sample.
*/
public SingleSampleMediaChunk(DataSource dataSource, DataSpec dataSpec, int trigger,
Format format, long startTimeUs, long endTimeUs, int chunkIndex, Format sampleFormat) {
super(dataSource, dataSpec, trigger, format, startTimeUs, endTimeUs, chunkIndex);
public SingleSampleMediaChunk(DataSource dataSource, DataSpec dataSpec, Format format,
int formatEvaluatorTrigger, Object formatEvaluatorData, long startTimeUs, long endTimeUs,
int chunkIndex, Format sampleFormat) {
super(dataSource, dataSpec, format, formatEvaluatorTrigger, formatEvaluatorData, startTimeUs,
endTimeUs, chunkIndex);
this.sampleFormat = sampleFormat;
}

View File

@ -167,7 +167,8 @@ public class DashChunkSource implements ChunkSource {
evaluation);
} else {
evaluation.format = enabledFormats[0];
evaluation.trigger = Chunk.TRIGGER_MANUAL;
evaluation.trigger = FormatEvaluator.TRIGGER_UNKNOWN;
evaluation.data = null;
}
}
@ -194,7 +195,7 @@ public class DashChunkSource implements ChunkSource {
// We have initialization and/or index requests to make.
Chunk initializationChunk = newInitializationChunk(pendingInitializationUri, pendingIndexUri,
selectedRepresentation, representationHolder.extractorWrapper, dataSource,
evaluation.trigger);
evaluation.trigger, evaluation.data);
lastChunkWasInitialization = true;
out.chunk = initializationChunk;
return;
@ -238,7 +239,7 @@ public class DashChunkSource implements ChunkSource {
}
Chunk nextMediaChunk = newMediaChunk(representationHolder, dataSource, selectedFormat,
sampleFormat, segmentNum, evaluation.trigger);
sampleFormat, segmentNum, evaluation.trigger, evaluation.data);
lastChunkWasInitialization = false;
out.chunk = nextMediaChunk;
}
@ -291,7 +292,7 @@ public class DashChunkSource implements ChunkSource {
private Chunk newInitializationChunk(RangedUri initializationUri, RangedUri indexUri,
Representation representation, ChunkExtractorWrapper extractor, DataSource dataSource,
int trigger) {
int formatEvaluatorTrigger, Object formatEvaluatorData) {
RangedUri requestUri;
if (initializationUri != null) {
// It's common for initialization and index data to be stored adjacently. Attempt to merge
@ -305,12 +306,13 @@ public class DashChunkSource implements ChunkSource {
}
DataSpec dataSpec = new DataSpec(requestUri.getUri(), requestUri.start, requestUri.length,
representation.getCacheKey());
return new InitializationChunk(dataSource, dataSpec, trigger, representation.format,
extractor);
return new InitializationChunk(dataSource, dataSpec, representation.format,
formatEvaluatorTrigger, formatEvaluatorData, extractor);
}
private Chunk newMediaChunk(RepresentationHolder representationHolder, DataSource dataSource,
Format trackFormat, Format sampleFormat, int segmentNum, int trigger) {
Format trackFormat, Format sampleFormat, int segmentNum, int formatEvaluatorTrigger,
Object formatEvaluatorData) {
Representation representation = representationHolder.representation;
long startTimeUs = representationHolder.getSegmentStartTimeUs(segmentNum);
long endTimeUs = representationHolder.getSegmentEndTimeUs(segmentNum);
@ -319,13 +321,13 @@ public class DashChunkSource implements ChunkSource {
representation.getCacheKey());
if (representationHolder.extractorWrapper == null) {
return new SingleSampleMediaChunk(dataSource, dataSpec, Chunk.TRIGGER_INITIAL, trackFormat,
startTimeUs, endTimeUs, segmentNum, trackFormat);
return new SingleSampleMediaChunk(dataSource, dataSpec, trackFormat, formatEvaluatorTrigger,
formatEvaluatorData, startTimeUs, endTimeUs, segmentNum, trackFormat);
} else {
long sampleOffsetUs = -representation.presentationTimeOffsetUs;
return new ContainerMediaChunk(dataSource, dataSpec, trigger, trackFormat, startTimeUs,
endTimeUs, segmentNum, sampleOffsetUs, representationHolder.extractorWrapper,
sampleFormat);
return new ContainerMediaChunk(dataSource, dataSpec, trackFormat, formatEvaluatorTrigger,
formatEvaluatorData, startTimeUs, endTimeUs, segmentNum, sampleOffsetUs,
representationHolder.extractorWrapper, sampleFormat);
}
}

View File

@ -15,6 +15,8 @@
*/
package com.google.android.exoplayer.dash;
import com.google.android.exoplayer.AdaptiveSourceEventListener;
import com.google.android.exoplayer.AdaptiveSourceEventListener.EventDispatcher;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.DefaultLoadControl;
import com.google.android.exoplayer.Format;
@ -26,7 +28,6 @@ import com.google.android.exoplayer.TrackGroupArray;
import com.google.android.exoplayer.TrackSelection;
import com.google.android.exoplayer.TrackStream;
import com.google.android.exoplayer.chunk.ChunkTrackStream;
import com.google.android.exoplayer.chunk.ChunkTrackStreamEventListener;
import com.google.android.exoplayer.chunk.FormatEvaluator;
import com.google.android.exoplayer.chunk.FormatEvaluator.AdaptiveEvaluator;
import com.google.android.exoplayer.dash.mpd.AdaptationSet;
@ -40,7 +41,8 @@ import com.google.android.exoplayer.upstream.DataSource;
import com.google.android.exoplayer.upstream.DataSourceFactory;
import com.google.android.exoplayer.upstream.DefaultAllocator;
import com.google.android.exoplayer.upstream.Loader;
import com.google.android.exoplayer.upstream.UriLoadable;
import com.google.android.exoplayer.upstream.Loader.Callback;
import com.google.android.exoplayer.upstream.ParsingLoadable;
import com.google.android.exoplayer.util.Util;
import android.net.Uri;
@ -73,8 +75,7 @@ public final class DashSampleSource implements SampleSource {
private final DataSourceFactory dataSourceFactory;
private final BandwidthMeter bandwidthMeter;
private final Handler eventHandler;
private final ChunkTrackStreamEventListener eventListener;
private final EventDispatcher eventDispatcher;
private final LoadControl loadControl;
private final Loader loader;
private final DataSource dataSource;
@ -97,12 +98,11 @@ public final class DashSampleSource implements SampleSource {
public DashSampleSource(Uri manifestUri, DataSourceFactory dataSourceFactory,
BandwidthMeter bandwidthMeter, Handler eventHandler,
ChunkTrackStreamEventListener eventListener) {
AdaptiveSourceEventListener eventListener) {
this.manifestUri = manifestUri;
this.dataSourceFactory = dataSourceFactory;
this.bandwidthMeter = bandwidthMeter;
this.eventHandler = eventHandler;
this.eventListener = eventListener;
eventDispatcher = new EventDispatcher(eventHandler, eventListener);
loadControl = new DefaultLoadControl(new DefaultAllocator(C.DEFAULT_BUFFER_SEGMENT_SIZE));
loader = new Loader("Loader:DashSampleSource");
dataSource = dataSourceFactory.createDataSource();
@ -227,11 +227,13 @@ public final class DashSampleSource implements SampleSource {
// Loadable callbacks.
/* package */ void onManifestLoadCompleted(MediaPresentationDescription manifest,
long elapsedMs) {
this.manifest = manifest;
manifestLoadEndTimestamp = SystemClock.elapsedRealtime();
manifestLoadStartTimestamp = manifestLoadEndTimestamp - elapsedMs;
/* package */ void onManifestLoadCompleted(ParsingLoadable<MediaPresentationDescription> loadable,
long elapsedRealtimeMs, long loadDurationMs) {
eventDispatcher.loadCompleted(loadable.dataSpec, loadable.type, elapsedRealtimeMs,
loadDurationMs, loadable.bytesLoaded());
manifest = loadable.getResult();
manifestLoadStartTimestamp = elapsedRealtimeMs - loadDurationMs;
manifestLoadEndTimestamp = elapsedRealtimeMs;
if (manifest.location != null) {
manifestUri = manifest.location;
}
@ -250,22 +252,40 @@ public final class DashSampleSource implements SampleSource {
}
}
/* package */ void onUtcTimestampLoadCompleted(long elapsedRealtimeOffset) {
this.elapsedRealtimeOffset = elapsedRealtimeOffset;
prepared = true;
/* package */ int onManifestLoadError(ParsingLoadable<MediaPresentationDescription> loadable,
long elapsedRealtimeMs, long loadDurationMs, IOException error) {
boolean isFatal = error instanceof ParserException;
eventDispatcher.loadError(loadable.dataSpec, loadable.type, elapsedRealtimeMs, loadDurationMs,
loadable.bytesLoaded(), error, isFatal);
return isFatal ? Loader.DONT_RETRY_FATAL : Loader.RETRY;
}
/* package */ void onUtcTimestampLoadError(IOException e) {
Log.e(TAG, "Failed to resolve UtcTiming element.", e);
// Be optimistic and continue in the hope that the device clock is correct.
prepared = true;
/* package */ void onUtcTimestampLoadCompleted(ParsingLoadable<Long> loadable,
long elapsedRealtimeMs, long loadDurationMs) {
eventDispatcher.loadCompleted(loadable.dataSpec, loadable.type, elapsedRealtimeMs,
loadDurationMs, loadable.bytesLoaded());
onUtcTimestampResolved(loadable.getResult() - elapsedRealtimeMs);
}
/* package */ int onUtcTimestampLoadError(ParsingLoadable<Long> loadable, long elapsedRealtimeMs,
long loadDurationMs, IOException error) {
eventDispatcher.loadError(loadable.dataSpec, loadable.type, elapsedRealtimeMs, loadDurationMs,
loadable.bytesLoaded(), error, true);
onUtcTimestampResolutionError(error);
return Loader.DONT_RETRY;
}
/* package */ void onLoadCanceled(ParsingLoadable<?> loadable, long elapsedRealtimeMs,
long loadDurationMs) {
eventDispatcher.loadCanceled(loadable.dataSpec, loadable.type, elapsedRealtimeMs,
loadDurationMs, loadable.bytesLoaded());
}
// Internal methods.
private void startLoadingManifest() {
loader.startLoading(new UriLoadable<>(manifestUri, dataSource, manifestParser),
manifestCallback, MIN_LOADABLE_RETRY_COUNT);
startLoading(new ParsingLoadable<>(dataSource, manifestUri, C.DATA_TYPE_MANIFEST,
manifestParser), manifestCallback, MIN_LOADABLE_RETRY_COUNT);
}
private void resolveUtcTimingElement(UtcTimingElement timingElement) {
@ -279,24 +299,40 @@ public final class DashSampleSource implements SampleSource {
resolveUtcTimingElementHttp(timingElement, new XsDateTimeParser());
} else {
// Unsupported scheme.
onUtcTimestampLoadError(new IOException("Unsupported UTC timing scheme"));
onUtcTimestampResolutionError(new IOException("Unsupported UTC timing scheme"));
}
}
private void resolveUtcTimingElementDirect(UtcTimingElement timingElement) {
try {
long utcTimestamp = Util.parseXsDateTime(timingElement.value);
long elapsedRealtimeOffset = utcTimestamp - manifestLoadEndTimestamp;
onUtcTimestampLoadCompleted(elapsedRealtimeOffset);
onUtcTimestampResolved(utcTimestamp - manifestLoadEndTimestamp);
} catch (ParseException e) {
onUtcTimestampLoadError(new ParserException(e));
onUtcTimestampResolutionError(new ParserException(e));
}
}
private void resolveUtcTimingElementHttp(UtcTimingElement timingElement,
UriLoadable.Parser<Long> parser) {
loader.startLoading(new UriLoadable<>(Uri.parse(timingElement.value), dataSource, parser),
new UtcTimestampCallback(), 1);
ParsingLoadable.Parser<Long> parser) {
startLoading(new ParsingLoadable<>(dataSource, Uri.parse(timingElement.value),
C.DATA_TYPE_TIME_SYNCHRONIZATION, parser), new UtcTimestampCallback(), 1);
}
private void onUtcTimestampResolved(long elapsedRealtimeOffsetMs) {
this.elapsedRealtimeOffset = elapsedRealtimeOffsetMs;
prepared = true;
}
private void onUtcTimestampResolutionError(IOException error) {
Log.e(TAG, "Failed to resolve UtcTiming element.", error);
// Be optimistic and continue in the hope that the device clock is correct.
prepared = true;
}
private <T> void startLoading(ParsingLoadable<T> loadable, Callback<ParsingLoadable<T>> callback,
int minRetryCount) {
long elapsedRealtimeMs = loader.startLoading(loadable, callback, minRetryCount);
eventDispatcher.loadStarted(loadable.dataSpec, loadable.type, elapsedRealtimeMs);
}
private void buildTrackGroups(MediaPresentationDescription manifest) {
@ -341,8 +377,8 @@ public final class DashSampleSource implements SampleSource {
DashChunkSource chunkSource = new DashChunkSource(loader, manifest, adaptationSetIndex,
trackGroups.get(selection.group), selectedTracks, dataSource, adaptiveEvaluator,
elapsedRealtimeOffset);
return new ChunkTrackStream<>(chunkSource, loadControl, bufferSize, positionUs, eventHandler,
eventListener, adaptationSetType, MIN_LOADABLE_RETRY_COUNT);
return new ChunkTrackStream<>(adaptationSetType, chunkSource, loadControl, bufferSize,
positionUs, MIN_LOADABLE_RETRY_COUNT, eventDispatcher);
}
@SuppressWarnings("unchecked")
@ -351,49 +387,51 @@ public final class DashSampleSource implements SampleSource {
}
private final class ManifestCallback implements
Loader.Callback<UriLoadable<MediaPresentationDescription>> {
Loader.Callback<ParsingLoadable<MediaPresentationDescription>> {
@Override
public void onLoadCompleted(UriLoadable<MediaPresentationDescription> loadable,
long elapsedMs) {
onManifestLoadCompleted(loadable.getResult(), elapsedMs);
public void onLoadCompleted(ParsingLoadable<MediaPresentationDescription> loadable,
long elapsedRealtimeMs, long loadDurationMs) {
onManifestLoadCompleted(loadable, elapsedRealtimeMs, loadDurationMs);
}
@Override
public void onLoadCanceled(UriLoadable<MediaPresentationDescription> loadable, long elapsedMs,
boolean released) {
// Do nothing.
public void onLoadCanceled(ParsingLoadable<MediaPresentationDescription> loadable,
long elapsedRealtimeMs, long loadDurationMs, boolean released) {
DashSampleSource.this.onLoadCanceled(loadable, elapsedRealtimeMs, loadDurationMs);
}
@Override
public int onLoadError(UriLoadable<MediaPresentationDescription> loadable, long elapsedMs,
IOException exception) {
return (exception instanceof ParserException) ? Loader.DONT_RETRY_FATAL : Loader.RETRY;
public int onLoadError(ParsingLoadable<MediaPresentationDescription> loadable,
long elapsedRealtimeMs, long loadDurationMs, IOException error) {
return onManifestLoadError(loadable, elapsedRealtimeMs, loadDurationMs, error);
}
}
private final class UtcTimestampCallback implements Loader.Callback<UriLoadable<Long>> {
private final class UtcTimestampCallback implements Loader.Callback<ParsingLoadable<Long>> {
@Override
public void onLoadCompleted(UriLoadable<Long> loadable, long elapsedMs) {
onUtcTimestampLoadCompleted(loadable.getResult() - SystemClock.elapsedRealtime());
public void onLoadCompleted(ParsingLoadable<Long> loadable, long elapsedRealtimeMs,
long loadDurationMs) {
onUtcTimestampLoadCompleted(loadable, elapsedRealtimeMs, loadDurationMs);
}
@Override
public void onLoadCanceled(UriLoadable<Long> loadable, long elapsedMs, boolean released) {
// Do nothing.
public void onLoadCanceled(ParsingLoadable<Long> loadable, long elapsedRealtimeMs,
long loadDurationMs, boolean released) {
DashSampleSource.this.onLoadCanceled(loadable, elapsedRealtimeMs, loadDurationMs);
}
@Override
public int onLoadError(UriLoadable<Long> loadable, long elapsedMs, IOException exception) {
onUtcTimestampLoadError(exception);
return Loader.DONT_RETRY;
public int onLoadError(ParsingLoadable<Long> loadable, long elapsedRealtimeMs,
long loadDurationMs, IOException error) {
return onUtcTimestampLoadError(loadable, elapsedRealtimeMs, loadDurationMs, error);
}
}
private static final class XsDateTimeParser implements UriLoadable.Parser<Long> {
private static final class XsDateTimeParser implements ParsingLoadable.Parser<Long> {
@Override
public Long parse(Uri uri, InputStream inputStream) throws IOException {
@ -407,7 +445,7 @@ public final class DashSampleSource implements SampleSource {
}
private static final class Iso8601Parser implements UriLoadable.Parser<Long> {
private static final class Iso8601Parser implements ParsingLoadable.Parser<Long> {
@Override
public Long parse(Uri uri, InputStream inputStream) throws IOException {

View File

@ -25,7 +25,7 @@ import com.google.android.exoplayer.dash.mpd.SegmentBase.SingleSegmentBase;
import com.google.android.exoplayer.drm.DrmInitData;
import com.google.android.exoplayer.drm.DrmInitData.SchemeData;
import com.google.android.exoplayer.extractor.mp4.PsshAtomUtil;
import com.google.android.exoplayer.upstream.UriLoadable;
import com.google.android.exoplayer.upstream.ParsingLoadable;
import com.google.android.exoplayer.util.Assertions;
import com.google.android.exoplayer.util.MimeTypes;
import com.google.android.exoplayer.util.ParserUtil;
@ -56,7 +56,7 @@ import java.util.regex.Pattern;
* A parser of media presentation description files.
*/
public class MediaPresentationDescriptionParser extends DefaultHandler
implements UriLoadable.Parser<MediaPresentationDescription> {
implements ParsingLoadable.Parser<MediaPresentationDescription> {
private static final String TAG = "MpdParser";

View File

@ -81,10 +81,9 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
/**
* Invoked when an error occurs loading media data.
*
* @param sourceId The id of the reporting {@link SampleSource}.
* @param e The cause of the failure.
* @param error The load error.
*/
void onLoadError(int sourceId, IOException e);
void onLoadError(IOException error);
}
@ -119,7 +118,6 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
private final int minLoadableRetryCount;
private final Handler eventHandler;
private final EventListener eventListener;
private final int eventSourceId;
private final DataSource dataSource;
private final Allocator allocator;
private final Loader loader;
@ -153,13 +151,12 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
* Where this is not possible, {@link #newDefaultExtractors()} can be used to construct an
* array of default extractors.
* @param eventListener A listener of events. May be null if delivery of events is not required.
* @param eventSourceId An identifier that gets passed to {@code eventListener} methods.
*/
public ExtractorSampleSource(Uri uri, DataSourceFactory dataSourceFactory,
BandwidthMeter bandwidthMeter, Extractor[] extractors, Handler eventHandler,
EventListener eventListener, int eventSourceId) {
EventListener eventListener) {
this(uri, dataSourceFactory, bandwidthMeter, extractors, MIN_RETRY_COUNT_DEFAULT_FOR_MEDIA,
eventHandler, eventListener, eventSourceId);
eventHandler, eventListener);
}
/**
@ -173,17 +170,15 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
* @param minLoadableRetryCount The minimum number of times that the sample source will retry
* if a loading error occurs.
* @param eventListener A listener of events. May be null if delivery of events is not required.
* @param eventSourceId An identifier that gets passed to {@code eventListener} methods.
*/
public ExtractorSampleSource(Uri uri, DataSourceFactory dataSourceFactory,
BandwidthMeter bandwidthMeter, Extractor[] extractors, int minLoadableRetryCount,
Handler eventHandler, EventListener eventListener, int eventSourceId) {
Handler eventHandler, EventListener eventListener) {
Assertions.checkState(extractors != null && extractors.length > 0);
this.uri = uri;
this.minLoadableRetryCount = minLoadableRetryCount;
this.eventListener = eventListener;
this.eventHandler = eventHandler;
this.eventSourceId = eventSourceId;
dataSource = dataSourceFactory.createDataSource(bandwidthMeter);
allocator = new DefaultAllocator(C.DEFAULT_BUFFER_SEGMENT_SIZE);
loader = new Loader("Loader:ExtractorSampleSource");
@ -460,13 +455,15 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
// Loader.Callback implementation.
@Override
public void onLoadCompleted(ExtractingLoadable loadable, long elapsedMs) {
public void onLoadCompleted(ExtractingLoadable loadable, long elapsedRealtimeMs,
long loadDurationMs) {
copyLengthFromLoader(loadable);
loadingFinished = true;
}
@Override
public void onLoadCanceled(ExtractingLoadable loadable, long elapsedMs, boolean released) {
public void onLoadCanceled(ExtractingLoadable loadable, long elapsedRealtimeMs,
long loadDurationMs, boolean released) {
copyLengthFromLoader(loadable);
if (!released && enabledTrackCount > 0) {
restartFrom(pendingResetPositionUs);
@ -474,10 +471,11 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
}
@Override
public int onLoadError(ExtractingLoadable loadable, long elapsedMs, IOException e) {
public int onLoadError(ExtractingLoadable loadable, long elapsedRealtimeMs,
long loadDurationMs, IOException error) {
copyLengthFromLoader(loadable);
notifyLoadError(e);
if (isLoadableExceptionFatal(e)) {
notifyLoadError(error);
if (isLoadableExceptionFatal(error)) {
return Loader.DONT_RETRY_FATAL;
}
int extractedSamplesCount = getExtractedSamplesCount();
@ -598,12 +596,12 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
return e instanceof UnrecognizedInputFormatException;
}
private void notifyLoadError(final IOException e) {
private void notifyLoadError(final IOException error) {
if (eventHandler != null && eventListener != null) {
eventHandler.post(new Runnable() {
@Override
public void run() {
eventListener.onLoadError(eventSourceId, e);
eventListener.onLoadError(error);
}
});
}

View File

@ -244,14 +244,21 @@ public class HlsChunkSource {
* @param out A holder to populate.
*/
public void getNextChunk(HlsMediaChunk previous, long playbackPositionUs, ChunkHolder out) {
int variantIndex = getNextVariantIndex(previous, playbackPositionUs);
updateFormatEvaluation(previous, playbackPositionUs);
int variantIndex = 0;
for (int i = 0; i < variants.length; i++) {
if (variants[i].format == evaluation.format) {
variantIndex = i;
break;
}
}
boolean switchingVariant = previous != null
&& variants[variantIndex].format != previous.format;
HlsMediaPlaylist mediaPlaylist = variantPlaylists[variantIndex];
if (mediaPlaylist == null) {
// We don't have the media playlist for the next variant. Request it now.
out.chunk = newMediaPlaylistChunk(variantIndex);
out.chunk = newMediaPlaylistChunk(variantIndex, evaluation.trigger, evaluation.data);
return;
}
@ -272,7 +279,7 @@ public class HlsChunkSource {
if (!mediaPlaylist.live) {
out.endOfStream = true;
} else if (shouldRerequestLiveMediaPlaylist(variantIndex)) {
out.chunk = newMediaPlaylistChunk(variantIndex);
out.chunk = newMediaPlaylistChunk(variantIndex, evaluation.trigger, evaluation.data);
}
return;
}
@ -285,7 +292,8 @@ public class HlsChunkSource {
Uri keyUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, segment.encryptionKeyUri);
if (!keyUri.equals(encryptionKeyUri)) {
// Encryption is specified and the key has changed.
out.chunk = newEncryptionKeyChunk(keyUri, segment.encryptionIV, variantIndex);
out.chunk = newEncryptionKeyChunk(keyUri, segment.encryptionIV, variantIndex,
evaluation.trigger, evaluation.data);
return;
}
if (!Util.areEqual(segment.encryptionIV, encryptionIvString)) {
@ -313,7 +321,6 @@ public class HlsChunkSource {
startTimeUs = segment.startTimeUs;
}
long endTimeUs = startTimeUs + (long) (segment.durationSecs * C.MICROS_PER_SECOND);
int trigger = Chunk.TRIGGER_UNSPECIFIED;
Format format = variants[variantIndex].format;
// Configure the extractor that will read the chunk.
@ -368,9 +375,9 @@ public class HlsChunkSource {
extractorNeedsInit = false;
}
out.chunk = new HlsMediaChunk(dataSource, dataSpec, trigger, format, startTimeUs, endTimeUs,
chunkMediaSequence, segment.discontinuitySequenceNumber, extractor, extractorNeedsInit,
switchingVariant, encryptionKey, encryptionIv);
out.chunk = new HlsMediaChunk(dataSource, dataSpec, format, evaluation.trigger, evaluation.data,
startTimeUs, endTimeUs, chunkMediaSequence, segment.discontinuitySequenceNumber, extractor,
extractorNeedsInit, switchingVariant, encryptionKey, encryptionIv);
}
/**
@ -434,7 +441,7 @@ public class HlsChunkSource {
// Private methods.
private int getNextVariantIndex(HlsMediaChunk previous, long playbackPositionUs) {
private void updateFormatEvaluation(HlsMediaChunk previous, long playbackPositionUs) {
clearStaleBlacklistedVariants();
if (enabledVariants.length > 1) {
long bufferedDurationUs;
@ -449,14 +456,9 @@ public class HlsChunkSource {
evaluation);
} else {
evaluation.format = enabledVariants[0].format;
evaluation.trigger = Chunk.TRIGGER_MANUAL;
evaluation.trigger = FormatEvaluator.TRIGGER_UNKNOWN;
evaluation.data = null;
}
for (int i = 0; i < variants.length; i++) {
if (variants[i].format == evaluation.format) {
return i;
}
}
throw new IllegalStateException();
}
private boolean shouldRerequestLiveMediaPlaylist(int variantIndex) {
@ -467,18 +469,21 @@ public class HlsChunkSource {
return timeSinceLastMediaPlaylistLoadMs >= (mediaPlaylist.targetDurationSecs * 1000) / 2;
}
private MediaPlaylistChunk newMediaPlaylistChunk(int variantIndex) {
private MediaPlaylistChunk newMediaPlaylistChunk(int variantIndex, int formatEvaluatorTrigger,
Object formatEvaluatorData) {
Uri mediaPlaylistUri = UriUtil.resolveToUri(baseUri, variants[variantIndex].url);
DataSpec dataSpec = new DataSpec(mediaPlaylistUri, 0, C.LENGTH_UNBOUNDED, null,
DataSpec.FLAG_ALLOW_GZIP);
return new MediaPlaylistChunk(dataSource, dataSpec, variants[variantIndex].format, scratchSpace,
playlistParser, variantIndex, mediaPlaylistUri);
return new MediaPlaylistChunk(dataSource, dataSpec, variants[variantIndex].format,
formatEvaluatorTrigger, formatEvaluatorData, scratchSpace, playlistParser, variantIndex,
mediaPlaylistUri);
}
private EncryptionKeyChunk newEncryptionKeyChunk(Uri keyUri, String iv, int variantIndex) {
private EncryptionKeyChunk newEncryptionKeyChunk(Uri keyUri, String iv, int variantIndex,
int formatEvaluatorTrigger, Object formatEvaluatorData) {
DataSpec dataSpec = new DataSpec(keyUri, 0, C.LENGTH_UNBOUNDED, null, DataSpec.FLAG_ALLOW_GZIP);
return new EncryptionKeyChunk(dataSource, dataSpec, variants[variantIndex].format, scratchSpace,
iv);
return new EncryptionKeyChunk(dataSource, dataSpec, variants[variantIndex].format,
formatEvaluatorTrigger, formatEvaluatorData, scratchSpace, iv);
}
private void setEncryptionData(Uri keyUri, String iv, byte[] secretKey) {
@ -556,10 +561,11 @@ public class HlsChunkSource {
private HlsMediaPlaylist result;
public MediaPlaylistChunk(DataSource dataSource, DataSpec dataSpec, Format format,
byte[] scratchSpace, HlsPlaylistParser playlistParser, int variantIndex,
int formatEvaluatorTrigger, Object formatEvaluatorData, byte[] scratchSpace,
HlsPlaylistParser playlistParser, int variantIndex,
Uri playlistUri) {
super(dataSource, dataSpec, C.DATA_TYPE_MANIFEST, Chunk.TRIGGER_UNSPECIFIED, format,
scratchSpace);
super(dataSource, dataSpec, C.DATA_TYPE_MANIFEST, format, formatEvaluatorTrigger,
formatEvaluatorData, scratchSpace);
this.variantIndex = variantIndex;
this.playlistParser = playlistParser;
this.playlistUri = playlistUri;
@ -584,9 +590,9 @@ public class HlsChunkSource {
private byte[] result;
public EncryptionKeyChunk(DataSource dataSource, DataSpec dataSpec, Format format,
byte[] scratchSpace, String iv) {
super(dataSource, dataSpec, C.DATA_TYPE_DRM, Chunk.TRIGGER_UNSPECIFIED, format,
scratchSpace);
int formatEvaluatorTrigger, Object formatEvaluatorData, byte[] scratchSpace, String iv) {
super(dataSource, dataSpec, C.DATA_TYPE_DRM, format, formatEvaluatorTrigger,
formatEvaluatorData, scratchSpace);
this.iv = iv;
}

View File

@ -50,10 +50,11 @@ import java.io.IOException;
private volatile boolean loadCompleted;
/**
* @param dataSource A {@link DataSource} for loading the data.
* @param dataSource The source from which the data should be loaded.
* @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 format See {@link #format}.
* @param formatEvaluatorTrigger See {@link #formatEvaluatorTrigger}.
* @param formatEvaluatorData See {@link #formatEvaluatorData}.
* @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.
@ -66,12 +67,13 @@ import java.io.IOException;
* @param encryptionKey For AES encryption chunks, the encryption key.
* @param encryptionIv For AES encryption chunks, the encryption initialization vector.
*/
public HlsMediaChunk(DataSource dataSource, DataSpec dataSpec, int trigger, Format format,
long startTimeUs, long endTimeUs, int chunkIndex, int discontinuitySequenceNumber,
Extractor extractor, boolean extractorNeedsInit, boolean shouldSpliceIn,
byte[] encryptionKey, byte[] encryptionIv) {
super(buildDataSource(dataSource, encryptionKey, encryptionIv), dataSpec, trigger, format,
startTimeUs, endTimeUs, chunkIndex);
public HlsMediaChunk(DataSource dataSource, DataSpec dataSpec, Format format,
int formatEvaluatorTrigger, Object formatEvaluatorData, long startTimeUs, long endTimeUs,
int chunkIndex, int discontinuitySequenceNumber, Extractor extractor,
boolean extractorNeedsInit, boolean shouldSpliceIn, byte[] encryptionKey,
byte[] encryptionIv) {
super(buildDataSource(dataSource, encryptionKey, encryptionIv), dataSpec, format,
formatEvaluatorTrigger, formatEvaluatorData, startTimeUs, endTimeUs, chunkIndex);
this.discontinuitySequenceNumber = discontinuitySequenceNumber;
this.extractor = extractor;
this.extractorNeedsInit = extractorNeedsInit;

View File

@ -15,6 +15,8 @@
*/
package com.google.android.exoplayer.hls;
import com.google.android.exoplayer.AdaptiveSourceEventListener;
import com.google.android.exoplayer.AdaptiveSourceEventListener.EventDispatcher;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.DefaultLoadControl;
import com.google.android.exoplayer.Format;
@ -25,7 +27,6 @@ import com.google.android.exoplayer.TrackGroup;
import com.google.android.exoplayer.TrackGroupArray;
import com.google.android.exoplayer.TrackSelection;
import com.google.android.exoplayer.TrackStream;
import com.google.android.exoplayer.chunk.ChunkTrackStreamEventListener;
import com.google.android.exoplayer.chunk.FormatEvaluator;
import com.google.android.exoplayer.hls.playlist.HlsMasterPlaylist;
import com.google.android.exoplayer.hls.playlist.HlsMediaPlaylist;
@ -37,7 +38,7 @@ import com.google.android.exoplayer.upstream.DataSource;
import com.google.android.exoplayer.upstream.DataSourceFactory;
import com.google.android.exoplayer.upstream.DefaultAllocator;
import com.google.android.exoplayer.upstream.Loader;
import com.google.android.exoplayer.upstream.UriLoadable;
import com.google.android.exoplayer.upstream.ParsingLoadable;
import com.google.android.exoplayer.util.MimeTypes;
import android.net.Uri;
@ -54,7 +55,7 @@ import java.util.List;
* A {@link SampleSource} for HLS streams.
*/
public final class HlsSampleSource implements SampleSource,
Loader.Callback<UriLoadable<HlsPlaylist>> {
Loader.Callback<ParsingLoadable<HlsPlaylist>> {
/**
* The minimum number of times to retry loading data prior to failing.
@ -64,8 +65,7 @@ public final class HlsSampleSource implements SampleSource,
private final Uri manifestUri;
private final DataSourceFactory dataSourceFactory;
private final BandwidthMeter bandwidthMeter;
private final Handler eventHandler;
private final ChunkTrackStreamEventListener eventListener;
private final EventDispatcher eventDispatcher;
private final LoadControl loadControl;
private final IdentityHashMap<TrackStream, HlsTrackStreamWrapper> trackStreamSources;
private final PtsTimestampAdjusterProvider timestampAdjusterProvider;
@ -85,13 +85,12 @@ public final class HlsSampleSource implements SampleSource,
public HlsSampleSource(Uri manifestUri, DataSourceFactory dataSourceFactory,
BandwidthMeter bandwidthMeter, Handler eventHandler,
ChunkTrackStreamEventListener eventListener) {
AdaptiveSourceEventListener eventListener) {
this.manifestUri = manifestUri;
this.dataSourceFactory = dataSourceFactory;
this.bandwidthMeter = bandwidthMeter;
this.eventHandler = eventHandler;
this.eventListener = eventListener;
eventDispatcher = new EventDispatcher(eventHandler, eventListener);
loadControl = new DefaultLoadControl(new DefaultAllocator(C.DEFAULT_BUFFER_SEGMENT_SIZE));
timestampAdjusterProvider = new PtsTimestampAdjusterProvider();
trackStreamSources = new IdentityHashMap<>();
@ -110,9 +109,11 @@ public final class HlsSampleSource implements SampleSource,
if (trackStreamWrappers == null) {
manifestFetcher.maybeThrowError();
if (!manifestFetcher.isLoading()) {
manifestFetcher.startLoading(
new UriLoadable<>(manifestUri, manifestDataSource, manifestParser), this,
ParsingLoadable<HlsPlaylist> loadable = new ParsingLoadable<>(manifestDataSource,
manifestUri, C.DATA_TYPE_MANIFEST, manifestParser);
long elapsedRealtimeMs = manifestFetcher.startLoading(loadable, this,
MIN_LOADABLE_RETRY_COUNT);
eventDispatcher.loadStarted(loadable.dataSpec, loadable.type, elapsedRealtimeMs);
}
return false;
}
@ -240,7 +241,10 @@ public final class HlsSampleSource implements SampleSource,
// Loader.Callback implementation.
@Override
public void onLoadCompleted(UriLoadable<HlsPlaylist> loadable, long elapsedMs) {
public void onLoadCompleted(ParsingLoadable<HlsPlaylist> loadable, long elapsedRealtimeMs,
long loadDurationMs) {
eventDispatcher.loadCompleted(loadable.dataSpec, loadable.type, elapsedRealtimeMs,
loadDurationMs, loadable.bytesLoaded());
HlsPlaylist playlist = loadable.getResult();
List<HlsTrackStreamWrapper> trackStreamWrapperList = buildTrackStreamWrappers(playlist);
trackStreamWrappers = new HlsTrackStreamWrapper[trackStreamWrapperList.size()];
@ -249,13 +253,19 @@ public final class HlsSampleSource implements SampleSource,
}
@Override
public void onLoadCanceled(UriLoadable<HlsPlaylist> loadable, long elapsedMs, boolean released) {
// Do nothing.
public void onLoadCanceled(ParsingLoadable<HlsPlaylist> loadable, long elapsedRealtimeMs,
long loadDurationMs, boolean released) {
eventDispatcher.loadCompleted(loadable.dataSpec, loadable.type, elapsedRealtimeMs,
loadDurationMs, loadable.bytesLoaded());
}
@Override
public int onLoadError(UriLoadable<HlsPlaylist> loadable, long elapsedMs, IOException exception) {
return exception instanceof ParserException ? Loader.DONT_RETRY_FATAL : Loader.RETRY;
public int onLoadError(ParsingLoadable<HlsPlaylist> loadable, long elapsedRealtimeMs,
long loadDurationMs, IOException error) {
boolean isFatal = error instanceof ParserException;
eventDispatcher.loadError(loadable.dataSpec, loadable.type, elapsedRealtimeMs, loadDurationMs,
loadable.bytesLoaded(), error, isFatal);
return isFatal ? Loader.DONT_RETRY_FATAL : Loader.RETRY;
}
// Internal methods.
@ -333,8 +343,8 @@ public final class HlsSampleSource implements SampleSource,
DataSource dataSource = dataSourceFactory.createDataSource(bandwidthMeter);
HlsChunkSource defaultChunkSource = new HlsChunkSource(baseUri, variants, dataSource,
timestampAdjusterProvider, formatEvaluator);
return new HlsTrackStreamWrapper(defaultChunkSource, loadControl, bufferSize, muxedAudioFormat,
muxedCaptionFormat, eventHandler, eventListener, trackType, MIN_LOADABLE_RETRY_COUNT);
return new HlsTrackStreamWrapper(trackType, defaultChunkSource, loadControl, bufferSize,
muxedAudioFormat, muxedCaptionFormat, MIN_LOADABLE_RETRY_COUNT, eventDispatcher);
}
private int selectTracks(HlsTrackStreamWrapper trackStreamWrapper,

View File

@ -15,6 +15,7 @@
*/
package com.google.android.exoplayer.hls;
import com.google.android.exoplayer.AdaptiveSourceEventListener.EventDispatcher;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.DecoderInputBuffer;
import com.google.android.exoplayer.Format;
@ -26,8 +27,6 @@ import com.google.android.exoplayer.TrackSelection;
import com.google.android.exoplayer.TrackStream;
import com.google.android.exoplayer.chunk.Chunk;
import com.google.android.exoplayer.chunk.ChunkHolder;
import com.google.android.exoplayer.chunk.ChunkTrackStreamEventListener;
import com.google.android.exoplayer.chunk.ChunkTrackStreamEventListener.EventDispatcher;
import com.google.android.exoplayer.extractor.DefaultTrackOutput;
import com.google.android.exoplayer.extractor.ExtractorOutput;
import com.google.android.exoplayer.extractor.SeekMap;
@ -35,8 +34,6 @@ import com.google.android.exoplayer.upstream.Loader;
import com.google.android.exoplayer.util.Assertions;
import com.google.android.exoplayer.util.MimeTypes;
import android.os.Handler;
import android.os.SystemClock;
import android.util.SparseArray;
import java.io.IOException;
@ -54,6 +51,7 @@ import java.util.List;
private static final int PRIMARY_TYPE_AUDIO = 2;
private static final int PRIMARY_TYPE_VIDEO = 3;
private final int trackType;
private final HlsChunkSource chunkSource;
private final LoadControl loadControl;
private final int bufferSizeContribution;
@ -87,6 +85,7 @@ import java.util.List;
private boolean loadingFinished;
/**
* @param trackType The type of the track. One of the {@link C} {@code TRACK_TYPE_*} constants.
* @param chunkSource A {@link HlsChunkSource} from which chunks to load are obtained.
* @param loadControl Controls when the source is permitted to load data.
* @param bufferSizeContribution The contribution of this source to the media buffer, in bytes.
@ -94,25 +93,22 @@ import java.util.List;
* this is the audio {@link Format} as defined by the playlist.
* @param muxedCaptionFormat If HLS master playlist indicates that the stream contains muxed
* captions, this is the audio {@link Format} as defined by the playlist.
* @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
* null if delivery of events is not required.
* @param eventListener A listener of events. May be null if delivery of events is not required.
* @param eventSourceId An identifier that gets passed to {@code eventListener} methods.
* @param minLoadableRetryCount The minimum number of times that the source should retry a load
* before propagating an error.
* @param eventDispatcher A dispatcher to notify of events.
*/
public HlsTrackStreamWrapper(HlsChunkSource chunkSource, LoadControl loadControl,
public HlsTrackStreamWrapper(int trackType, HlsChunkSource chunkSource, LoadControl loadControl,
int bufferSizeContribution, Format muxedAudioFormat, Format muxedCaptionFormat,
Handler eventHandler, ChunkTrackStreamEventListener eventListener, int eventSourceId,
int minLoadableRetryCount) {
int minLoadableRetryCount, EventDispatcher eventDispatcher) {
this.trackType = trackType;
this.chunkSource = chunkSource;
this.loadControl = loadControl;
this.bufferSizeContribution = bufferSizeContribution;
this.muxedAudioFormat = muxedAudioFormat;
this.muxedCaptionFormat = muxedCaptionFormat;
this.minLoadableRetryCount = minLoadableRetryCount;
this.eventDispatcher = eventDispatcher;
loader = new Loader("Loader:HlsTrackStreamWrapper");
eventDispatcher = new EventDispatcher(eventHandler, eventListener, eventSourceId);
nextChunkHolder = new ChunkHolder();
sampleQueues = new SparseArray<>();
mediaChunks = new LinkedList<>();
@ -303,7 +299,8 @@ import java.util.List;
HlsMediaChunk currentChunk = mediaChunks.getFirst();
Format format = currentChunk.format;
if (!format.equals(downstreamFormat)) {
eventDispatcher.downstreamFormatChanged(format, currentChunk.trigger,
eventDispatcher.downstreamFormatChanged(trackType, format,
currentChunk.formatEvaluatorTrigger, currentChunk.formatEvaluatorData,
currentChunk.startTimeUs);
}
downstreamFormat = format;
@ -315,34 +312,33 @@ import java.util.List;
// Loader.Callback implementation.
@Override
public void onLoadCompleted(Chunk loadable, long elapsedMs) {
long now = SystemClock.elapsedRealtime();
public void onLoadCompleted(Chunk loadable, long elapsedRealtimeMs, long loadDurationMs) {
chunkSource.onChunkLoadCompleted(loadable);
if (isMediaChunk(loadable)) {
HlsMediaChunk mediaChunk = (HlsMediaChunk) loadable;
eventDispatcher.loadCompleted(loadable.bytesLoaded(), mediaChunk.type, mediaChunk.trigger,
mediaChunk.format, mediaChunk.startTimeUs, mediaChunk.endTimeUs, now, elapsedMs);
} else {
eventDispatcher.loadCompleted(loadable.bytesLoaded(), loadable.type, loadable.trigger,
loadable.format, -1, -1, now, elapsedMs);
}
eventDispatcher.loadCompleted(loadable.dataSpec, loadable.type, trackType, loadable.format,
loadable.formatEvaluatorTrigger, loadable.formatEvaluatorData, loadable.startTimeUs,
loadable.endTimeUs, elapsedRealtimeMs, loadDurationMs, loadable.bytesLoaded());
maybeStartLoading();
}
@Override
public void onLoadCanceled(Chunk loadable, long elapsedMs, boolean released) {
eventDispatcher.loadCanceled(loadable.bytesLoaded());
public void onLoadCanceled(Chunk loadable, long elapsedRealtimeMs, long loadDurationMs,
boolean released) {
eventDispatcher.loadCanceled(loadable.dataSpec, loadable.type, trackType, loadable.format,
loadable.formatEvaluatorTrigger, loadable.formatEvaluatorData, loadable.startTimeUs,
loadable.endTimeUs, elapsedRealtimeMs, loadDurationMs, loadable.bytesLoaded());
if (!released && enabledTrackCount > 0) {
restartFrom(pendingResetPositionUs);
}
}
@Override
public int onLoadError(Chunk loadable, long elapsedMs, IOException e) {
public int onLoadError(Chunk loadable, long elapsedRealtimeMs, long loadDurationMs,
IOException error) {
long bytesLoaded = loadable.bytesLoaded();
boolean isMediaChunk = isMediaChunk(loadable);
boolean cancelable = !isMediaChunk || bytesLoaded == 0;
if (chunkSource.onChunkLoadError(loadable, cancelable, e)) {
boolean canceled = false;
if (chunkSource.onChunkLoadError(loadable, cancelable, error)) {
if (isMediaChunk) {
HlsMediaChunk removed = mediaChunks.removeLast();
Assertions.checkState(removed == loadable);
@ -350,12 +346,16 @@ import java.util.List;
pendingResetPositionUs = lastSeekPositionUs;
}
}
eventDispatcher.loadError(e);
eventDispatcher.loadCanceled(bytesLoaded);
canceled = true;
}
eventDispatcher.loadError(loadable.dataSpec, loadable.type, trackType, loadable.format,
loadable.formatEvaluatorTrigger, loadable.formatEvaluatorData, loadable.startTimeUs,
loadable.endTimeUs, elapsedRealtimeMs, loadDurationMs, loadable.bytesLoaded(), error,
canceled);
if (canceled) {
maybeStartLoading();
return Loader.DONT_RETRY;
} else {
eventDispatcher.loadError(e);
return Loader.RETRY;
}
}
@ -543,14 +543,12 @@ import java.util.List;
pendingResetPositionUs = C.UNSET_TIME_US;
HlsMediaChunk mediaChunk = (HlsMediaChunk) loadable;
mediaChunk.init(this);
mediaChunks.addLast(mediaChunk);
eventDispatcher.loadStarted(mediaChunk.dataSpec.length, mediaChunk.type, mediaChunk.trigger,
mediaChunk.format, mediaChunk.startTimeUs, mediaChunk.endTimeUs);
} else {
eventDispatcher.loadStarted(loadable.dataSpec.length, loadable.type, loadable.trigger,
loadable.format, -1, -1);
mediaChunks.add(mediaChunk);
}
loader.startLoading(loadable, this, minLoadableRetryCount);
long elapsedRealtimeMs = loader.startLoading(loadable, this, minLoadableRetryCount);
eventDispatcher.loadStarted(loadable.dataSpec, loadable.type, trackType, loadable.format,
loadable.formatEvaluatorTrigger, loadable.formatEvaluatorData, loadable.startTimeUs,
loadable.endTimeUs, elapsedRealtimeMs);
if (prepared) {
// Update the load control again to indicate that we're now loading.
loadControl.update(this, downstreamPositionUs, getNextLoadPositionUs(), true);

View File

@ -19,7 +19,7 @@ import com.google.android.exoplayer.C;
import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.ParserException;
import com.google.android.exoplayer.hls.playlist.HlsMediaPlaylist.Segment;
import com.google.android.exoplayer.upstream.UriLoadable;
import com.google.android.exoplayer.upstream.ParsingLoadable;
import com.google.android.exoplayer.util.MimeTypes;
import android.net.Uri;
@ -38,7 +38,7 @@ import java.util.regex.Pattern;
/**
* HLS playlists parsing logic.
*/
public final class HlsPlaylistParser implements UriLoadable.Parser<HlsPlaylist> {
public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlaylist> {
private static final String VERSION_TAG = "#EXT-X-VERSION";
private static final String STREAM_INF_TAG = "#EXT-X-STREAM-INF";

View File

@ -171,7 +171,8 @@ public class SmoothStreamingChunkSource implements ChunkSource {
evaluation);
} else {
evaluation.format = enabledFormats[0];
evaluation.trigger = Chunk.TRIGGER_MANUAL;
evaluation.trigger = FormatEvaluator.TRIGGER_UNKNOWN;
evaluation.data = null;
}
Format selectedFormat = evaluation.format;
@ -219,7 +220,7 @@ public class SmoothStreamingChunkSource implements ChunkSource {
Uri uri = streamElement.buildRequestUri(manifestTrackIndex, chunkIndex);
out.chunk = newMediaChunk(selectedFormat, dataSource, uri, null, currentAbsoluteChunkIndex,
chunkStartTimeUs, chunkEndTimeUs, evaluation.trigger, extractorWrapper);
chunkStartTimeUs, chunkEndTimeUs, evaluation.trigger, evaluation.data, extractorWrapper);
}
@Override
@ -274,14 +275,16 @@ public class SmoothStreamingChunkSource implements ChunkSource {
}
private static MediaChunk newMediaChunk(Format format, DataSource dataSource, Uri uri,
String cacheKey, int chunkIndex, long chunkStartTimeUs, long chunkEndTimeUs, int trigger,
String cacheKey, int chunkIndex, long chunkStartTimeUs, long chunkEndTimeUs,
int formatEvaluatorTrigger, Object formatEvaluatorData,
ChunkExtractorWrapper extractorWrapper) {
DataSpec dataSpec = new DataSpec(uri, 0, -1, cacheKey);
// In SmoothStreaming each chunk contains sample timestamps relative to the start of the chunk.
// To convert them the absolute timestamps, we need to set sampleOffsetUs to chunkStartTimeUs.
long sampleOffsetUs = chunkStartTimeUs;
return new ContainerMediaChunk(dataSource, dataSpec, trigger, format, chunkStartTimeUs,
chunkEndTimeUs, chunkIndex, sampleOffsetUs, extractorWrapper, format);
return new ContainerMediaChunk(dataSource, dataSpec, format, formatEvaluatorTrigger,
formatEvaluatorData, chunkStartTimeUs, chunkEndTimeUs, chunkIndex, sampleOffsetUs,
extractorWrapper, format);
}
}

View File

@ -23,7 +23,7 @@ import com.google.android.exoplayer.drm.DrmInitData.SchemeData;
import com.google.android.exoplayer.extractor.mp4.PsshAtomUtil;
import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.ProtectionElement;
import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.StreamElement;
import com.google.android.exoplayer.upstream.UriLoadable;
import com.google.android.exoplayer.upstream.ParsingLoadable;
import com.google.android.exoplayer.util.Assertions;
import com.google.android.exoplayer.util.CodecSpecificDataUtil;
import com.google.android.exoplayer.util.MimeTypes;
@ -52,7 +52,8 @@ import java.util.UUID;
* @see <a href="http://msdn.microsoft.com/en-us/library/ee673436(v=vs.90).aspx">
* IIS Smooth Streaming Client Manifest Format</a>
*/
public class SmoothStreamingManifestParser implements UriLoadable.Parser<SmoothStreamingManifest> {
public class SmoothStreamingManifestParser implements
ParsingLoadable.Parser<SmoothStreamingManifest> {
private final XmlPullParserFactory xmlParserFactory;

View File

@ -15,6 +15,8 @@
*/
package com.google.android.exoplayer.smoothstreaming;
import com.google.android.exoplayer.AdaptiveSourceEventListener;
import com.google.android.exoplayer.AdaptiveSourceEventListener.EventDispatcher;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.DefaultLoadControl;
import com.google.android.exoplayer.Format;
@ -26,7 +28,6 @@ import com.google.android.exoplayer.TrackGroupArray;
import com.google.android.exoplayer.TrackSelection;
import com.google.android.exoplayer.TrackStream;
import com.google.android.exoplayer.chunk.ChunkTrackStream;
import com.google.android.exoplayer.chunk.ChunkTrackStreamEventListener;
import com.google.android.exoplayer.chunk.FormatEvaluator;
import com.google.android.exoplayer.chunk.FormatEvaluator.AdaptiveEvaluator;
import com.google.android.exoplayer.extractor.mp4.TrackEncryptionBox;
@ -37,7 +38,7 @@ import com.google.android.exoplayer.upstream.DataSource;
import com.google.android.exoplayer.upstream.DataSourceFactory;
import com.google.android.exoplayer.upstream.DefaultAllocator;
import com.google.android.exoplayer.upstream.Loader;
import com.google.android.exoplayer.upstream.UriLoadable;
import com.google.android.exoplayer.upstream.ParsingLoadable;
import com.google.android.exoplayer.util.Util;
import android.net.Uri;
@ -53,7 +54,7 @@ import java.util.List;
* A {@link SampleSource} for SmoothStreaming media.
*/
public final class SmoothStreamingSampleSource implements SampleSource,
Loader.Callback<UriLoadable<SmoothStreamingManifest>> {
Loader.Callback<ParsingLoadable<SmoothStreamingManifest>> {
/**
* The minimum number of times to retry loading data prior to failing.
@ -66,8 +67,7 @@ public final class SmoothStreamingSampleSource implements SampleSource,
private final Uri manifestUri;
private final DataSourceFactory dataSourceFactory;
private final BandwidthMeter bandwidthMeter;
private final Handler eventHandler;
private final ChunkTrackStreamEventListener eventListener;
private final EventDispatcher eventDispatcher;
private final LoadControl loadControl;
private final Loader manifestLoader;
private final DataSource manifestDataSource;
@ -88,13 +88,12 @@ public final class SmoothStreamingSampleSource implements SampleSource,
public SmoothStreamingSampleSource(Uri manifestUri, DataSourceFactory dataSourceFactory,
BandwidthMeter bandwidthMeter, Handler eventHandler,
ChunkTrackStreamEventListener eventListener) {
AdaptiveSourceEventListener eventListener) {
this.manifestUri = Util.toLowerInvariant(manifestUri.getLastPathSegment()).equals("manifest")
? manifestUri : Uri.withAppendedPath(manifestUri, "Manifest");
this.dataSourceFactory = dataSourceFactory;
this.bandwidthMeter = bandwidthMeter;
this.eventHandler = eventHandler;
this.eventListener = eventListener;
this.eventDispatcher = new EventDispatcher(eventHandler, eventListener);
loadControl = new DefaultLoadControl(new DefaultAllocator(C.DEFAULT_BUFFER_SEGMENT_SIZE));
trackStreams = newTrackStreamArray(0);
manifestDataSource = dataSourceFactory.createDataSource();
@ -216,9 +215,12 @@ public final class SmoothStreamingSampleSource implements SampleSource,
// Loader.Callback implementation
@Override
public void onLoadCompleted(UriLoadable<SmoothStreamingManifest> loadable, long elapsedMs) {
public void onLoadCompleted(ParsingLoadable<SmoothStreamingManifest> loadable,
long elapsedRealtimeMs, long loadDurationMs) {
eventDispatcher.loadCompleted(loadable.dataSpec, loadable.type, elapsedRealtimeMs,
loadDurationMs, loadable.bytesLoaded());
manifest = loadable.getResult();
manifestLoadStartTimestamp = SystemClock.elapsedRealtime() - elapsedMs;
manifestLoadStartTimestamp = elapsedRealtimeMs - loadDurationMs;
if (!prepared) {
durationUs = manifest.durationUs;
buildTrackGroups(manifest);
@ -237,22 +239,28 @@ public final class SmoothStreamingSampleSource implements SampleSource,
}
@Override
public void onLoadCanceled(UriLoadable<SmoothStreamingManifest> loadable, long elapsedMs,
boolean released) {
// Do nothing.
public void onLoadCanceled(ParsingLoadable<SmoothStreamingManifest> loadable,
long elapsedRealtimeMs, long loadDurationMs, boolean released) {
eventDispatcher.loadCompleted(loadable.dataSpec, loadable.type, elapsedRealtimeMs,
loadDurationMs, loadable.bytesLoaded());
}
@Override
public int onLoadError(UriLoadable<SmoothStreamingManifest> loadable, long elapsedMs,
IOException exception) {
return (exception instanceof ParserException) ? Loader.DONT_RETRY_FATAL : Loader.RETRY;
public int onLoadError(ParsingLoadable<SmoothStreamingManifest> loadable, long elapsedRealtimeMs,
long loadDurationMs, IOException error) {
boolean isFatal = error instanceof ParserException;
eventDispatcher.loadError(loadable.dataSpec, loadable.type, elapsedRealtimeMs, loadDurationMs,
loadable.bytesLoaded(), error, isFatal);
return isFatal ? Loader.DONT_RETRY_FATAL : Loader.RETRY;
}
// Internal methods
private void startLoadingManifest() {
manifestLoader.startLoading(new UriLoadable<>(manifestUri, manifestDataSource, manifestParser),
this, MIN_LOADABLE_RETRY_COUNT);
ParsingLoadable<SmoothStreamingManifest> loadable = new ParsingLoadable<>(manifestDataSource,
manifestUri, C.DATA_TYPE_MANIFEST, manifestParser);
long elapsedRealtimeMs = manifestLoader.startLoading(loadable, this, MIN_LOADABLE_RETRY_COUNT);
eventDispatcher.loadStarted(loadable.dataSpec, loadable.type, elapsedRealtimeMs);
}
private void buildTrackGroups(SmoothStreamingManifest manifest) {
@ -290,8 +298,8 @@ public final class SmoothStreamingSampleSource implements SampleSource,
SmoothStreamingChunkSource chunkSource = new SmoothStreamingChunkSource(manifestLoader,
manifest, streamElementIndex, trackGroups.get(selection.group), selectedTracks, dataSource,
adaptiveEvaluator, trackEncryptionBoxes);
return new ChunkTrackStream<>(chunkSource, loadControl, bufferSize, positionUs, eventHandler,
eventListener, streamElementType, MIN_LOADABLE_RETRY_COUNT);
return new ChunkTrackStream<>(streamElementType, chunkSource, loadControl, bufferSize,
positionUs, MIN_LOADABLE_RETRY_COUNT, eventDispatcher);
}
@SuppressWarnings("unchecked")

View File

@ -33,6 +33,7 @@ public final class DataSourceInputStream extends InputStream {
private boolean opened = false;
private boolean closed = false;
private long totalBytesRead;
/**
* @param dataSource The {@link DataSource} from which the data should be read.
@ -44,6 +45,13 @@ public final class DataSourceInputStream extends InputStream {
singleByteArray = new byte[1];
}
/**
* Returns the total number of bytes that have been read or skipped.
*/
public long bytesRead() {
return totalBytesRead;
}
/**
* Optional call to open the underlying {@link DataSource}.
* <p>
@ -63,12 +71,17 @@ public final class DataSourceInputStream extends InputStream {
if (length == -1) {
return -1;
}
totalBytesRead++;
return singleByteArray[0] & 0xFF;
}
@Override
public int read(byte[] buffer) throws IOException {
return read(buffer, 0, buffer.length);
int bytesRead = read(buffer, 0, buffer.length);
if (bytesRead != -1) {
totalBytesRead += bytesRead;
}
return bytesRead;
}
@Override
@ -76,14 +89,21 @@ public final class DataSourceInputStream extends InputStream {
Assertions.checkState(!closed);
checkOpened();
int bytesRead = dataSource.read(buffer, offset, length);
return bytesRead == C.RESULT_END_OF_INPUT ? -1 : bytesRead;
if (bytesRead == C.RESULT_END_OF_INPUT) {
return -1;
} else {
totalBytesRead += bytesRead;
return bytesRead;
}
}
@Override
public long skip(long byteCount) throws IOException {
Assertions.checkState(!closed);
checkOpened();
return super.skip(byteCount);
long bytesSkipped = super.skip(byteCount);
totalBytesRead += bytesSkipped;
return bytesSkipped;
}
@Override

View File

@ -77,6 +77,18 @@ public final class Loader {
*/
public interface Callback<T extends Loadable> {
/**
* Invoked when a load has completed.
* <p>
* There is guaranteed to exist a memory barrier between {@link Loadable#load()} exiting and
* this callback being invoked.
*
* @param loadable The loadable whose load has completed.
* @param elapsedRealtimeMs {@link SystemClock#elapsedRealtime} when the load ended.
* @param loadDurationMs The duration of the load.
*/
void onLoadCompleted(T loadable, long elapsedRealtimeMs, long loadDurationMs);
/**
* Invoked when a load has been canceled.
* <p>
@ -86,22 +98,12 @@ public final class Loader {
* {@link Loadable#load()} exits.
*
* @param loadable The loadable whose load has been canceled.
* @param elapsedMs The elapsed time in milliseconds since loading started.
* @param elapsedRealtimeMs {@link SystemClock#elapsedRealtime} when the load was canceled.
* @param loadDurationMs The duration of the load up to the point at which it was canceled.
* @param released True if the load was canceled because the {@link Loader} was released. False
* otherwise.
*/
void onLoadCanceled(T loadable, long elapsedMs, boolean released);
/**
* Invoked when a load has completed.
* <p>
* There is guaranteed to exist a memory barrier between {@link Loadable#load()} exiting and
* this callback being invoked.
*
* @param loadable The loadable whose load has completed.
* @param elapsedMs The elapsed time in milliseconds since loading started.
*/
void onLoadCompleted(T loadable, long elapsedMs);
void onLoadCanceled(T loadable, long elapsedRealtimeMs, long loadDurationMs, boolean released);
/**
* Invoked when a load encounters an error.
@ -110,13 +112,14 @@ public final class Loader {
* this callback being invoked.
*
* @param loadable The loadable whose load has encountered an error.
* @param elapsedMs The elapsed time in milliseconds since loading started.
* @param exception The error.
* @param elapsedRealtimeMs {@link SystemClock#elapsedRealtime} when the error occurred.
* @param loadDurationMs The duration of the load up to the point at which the error occurred.
* @param error The load error.
* @return The desired retry action. One of {@link Loader#RETRY},
* {@link Loader#RETRY_RESET_ERROR_COUNT}, {@link Loader#DONT_RETRY} and
* {@link Loader#DONT_RETRY_FATAL}.
*/
int onLoadError(T loadable, long elapsedMs, IOException exception);
int onLoadError(T loadable, long elapsedRealtimeMs, long loadDurationMs, IOException error);
}
@ -154,12 +157,15 @@ public final class Loader {
* @param minRetryCount The minimum number of times the load must be retried before
* {@link #maybeThrowError()} will propagate an error.
* @throws IllegalStateException If the calling thread does not have an associated {@link Looper}.
* @return {@link SystemClock#elapsedRealtime} when the load started.
*/
public <T extends Loadable> void startLoading(T loadable, Callback<T> callback,
public <T extends Loadable> long startLoading(T loadable, Callback<T> callback,
int minRetryCount) {
Looper looper = Looper.myLooper();
Assertions.checkState(looper != null);
new LoadTask<>(looper, loadable, callback, minRetryCount).start(0);
long startTimeMs = SystemClock.elapsedRealtime();
new LoadTask<>(looper, loadable, callback, minRetryCount, startTimeMs).start(0);
return startTimeMs;
}
/**
@ -214,8 +220,8 @@ public final class Loader {
private final T loadable;
private final Loader.Callback<T> callback;
private final long startTimeMs;
private final int minRetryCount;
private final long startTimeMs;
private IOException currentError;
private int errorCount;
@ -223,12 +229,13 @@ public final class Loader {
private volatile Thread executorThread;
private volatile boolean released;
public LoadTask(Looper looper, T loadable, Loader.Callback<T> callback, int minRetryCount) {
public LoadTask(Looper looper, T loadable, Loader.Callback<T> callback, int minRetryCount,
long startTimeMs) {
super(looper);
this.loadable = loadable;
this.callback = callback;
this.minRetryCount = minRetryCount;
this.startTimeMs = SystemClock.elapsedRealtime();
this.startTimeMs = startTimeMs;
}
public void maybeThrowError() throws IOException {
@ -263,7 +270,8 @@ public final class Loader {
}
if (released) {
finish();
callback.onLoadCanceled(loadable, SystemClock.elapsedRealtime() - startTimeMs, true);
long nowMs = SystemClock.elapsedRealtime();
callback.onLoadCanceled(loadable, nowMs, nowMs - startTimeMs, true);
}
}
@ -323,21 +331,22 @@ public final class Loader {
throw (Error) msg.obj;
}
finish();
long elapsedMs = SystemClock.elapsedRealtime() - startTimeMs;
long nowMs = SystemClock.elapsedRealtime();
long durationMs = nowMs - startTimeMs;
if (loadable.isLoadCanceled()) {
callback.onLoadCanceled(loadable, elapsedMs, false);
callback.onLoadCanceled(loadable, nowMs, durationMs, false);
return;
}
switch (msg.what) {
case MSG_CANCEL:
callback.onLoadCanceled(loadable, elapsedMs, false);
callback.onLoadCanceled(loadable, nowMs, durationMs, false);
break;
case MSG_END_OF_SOURCE:
callback.onLoadCompleted(loadable, elapsedMs);
callback.onLoadCompleted(loadable, nowMs, durationMs);
break;
case MSG_IO_EXCEPTION:
currentError = (IOException) msg.obj;
int retryAction = callback.onLoadError(loadable, elapsedMs, currentError);
int retryAction = callback.onLoadError(loadable, nowMs, durationMs, currentError);
if (retryAction == DONT_RETRY_FATAL) {
fatalError = currentError;
} else if (retryAction != DONT_RETRY) {

View File

@ -15,6 +15,7 @@
*/
package com.google.android.exoplayer.upstream;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.ParserException;
import com.google.android.exoplayer.upstream.Loader.Loadable;
@ -24,11 +25,11 @@ import java.io.IOException;
import java.io.InputStream;
/**
* A {@link Loadable} for loading an object from a URI.
* A {@link Loadable} for objects that can be parsed from binary data using a {@link Parser}.
*
* @param <T> The type of the object being loaded.
*/
public final class UriLoadable<T> implements Loadable {
public final class ParsingLoadable<T> implements Loadable {
/**
* Parses an object from loaded data.
@ -48,22 +49,34 @@ public final class UriLoadable<T> implements Loadable {
}
private final DataSpec dataSpec;
/**
* The {@link DataSpec} that defines the data to be loaded.
*/
public final DataSpec dataSpec;
/**
* The type of the data. One of the {@code DATA_TYPE_*} constants defined in {@link C}. For
* reporting only.
*/
public final int type;
private final DataSource dataSource;
private final Parser<T> parser;
private volatile T result;
private volatile boolean isCanceled;
private volatile long bytesLoaded;
/**
* @param uri The {@link Uri} from which the object should be loaded.
* @param dataSource A {@link DataSource} to use when loading the data.
* @param uri The {@link Uri} from which the object should be loaded.
* @param type See {@link #type}.
* @param parser Parses the object from the response.
*/
public UriLoadable(Uri uri, DataSource dataSource, Parser<T> parser) {
public ParsingLoadable(DataSource dataSource, Uri uri, int type, Parser<T> parser) {
this.dataSource = dataSource;
this.dataSpec = new DataSpec(uri, DataSpec.FLAG_ALLOW_GZIP);
this.type = type;
this.parser = parser;
dataSpec = new DataSpec(uri, DataSpec.FLAG_ALLOW_GZIP);
}
/**
@ -73,6 +86,18 @@ public final class UriLoadable<T> implements Loadable {
return result;
}
/**
* Returns the number of bytes loaded.
* <p>
* In the case that the network response was compressed, the value returned is the size of the
* data <em>after</em> decompression.
*
* @return The number of bytes loaded.
*/
public long bytesLoaded() {
return bytesLoaded;
}
@Override
public final void cancelLoad() {
// We don't actually cancel anything, but we need to record the cancellation so that
@ -88,11 +113,12 @@ public final class UriLoadable<T> implements Loadable {
@Override
public final void load() throws IOException, InterruptedException {
DataSourceInputStream inputStream = new DataSourceInputStream(dataSource, dataSpec);
inputStream.open();
try {
inputStream.open();
result = parser.parse(dataSource.getUri(), inputStream);
} finally {
inputStream.close();
bytesLoaded = inputStream.bytesRead();
}
}

View File

@ -115,16 +115,6 @@ public final class Util {
private Util() {}
/**
* Converts microseconds to milliseconds (rounding down).
*
* @param timeUs A microsecond value.
* @return The value in milliseconds.
*/
public static long usToMs(long timeUs) {
return timeUs / 1000;
}
/**
* Converts the entirety of an {@link InputStream} to a byte array.
*