diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/EventLogger.java b/demo/src/main/java/com/google/android/exoplayer/demo/EventLogger.java
index db324fb761..5ab6f038f6 100644
--- a/demo/src/main/java/com/google/android/exoplayer/demo/EventLogger.java
+++ b/demo/src/main/java/com/google/android/exoplayer/demo/EventLogger.java
@@ -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.
}
diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/UriSampleSourceProvider.java b/demo/src/main/java/com/google/android/exoplayer/demo/UriSampleSourceProvider.java
index 85bd345eda..9b99404383 100644
--- a/demo/src/main/java/com/google/android/exoplayer/demo/UriSampleSourceProvider.java
+++ b/demo/src/main/java/com/google/android/exoplayer/demo/UriSampleSourceProvider.java
@@ -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);
}
diff --git a/extensions/flac/src/androidTest/java/com/google/android/exoplayer/ext/flac/FlacPlaybackTest.java b/extensions/flac/src/androidTest/java/com/google/android/exoplayer/ext/flac/FlacPlaybackTest.java
index ff0b6f9ddd..0310fa2c78 100644
--- a/extensions/flac/src/androidTest/java/com/google/android/exoplayer/ext/flac/FlacPlaybackTest.java
+++ b/extensions/flac/src/androidTest/java/com/google/android/exoplayer/ext/flac/FlacPlaybackTest.java
@@ -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();
diff --git a/extensions/opus/src/androidTest/java/com/google/android/exoplayer/ext/opus/OpusPlaybackTest.java b/extensions/opus/src/androidTest/java/com/google/android/exoplayer/ext/opus/OpusPlaybackTest.java
index 00368af498..39e45af7bb 100644
--- a/extensions/opus/src/androidTest/java/com/google/android/exoplayer/ext/opus/OpusPlaybackTest.java
+++ b/extensions/opus/src/androidTest/java/com/google/android/exoplayer/ext/opus/OpusPlaybackTest.java
@@ -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();
diff --git a/extensions/vp9/src/androidTest/java/com/google/android/exoplayer/ext/vp9/VpxPlaybackTest.java b/extensions/vp9/src/androidTest/java/com/google/android/exoplayer/ext/vp9/VpxPlaybackTest.java
index 35af59c3f8..c55f7f7e9f 100644
--- a/extensions/vp9/src/androidTest/java/com/google/android/exoplayer/ext/vp9/VpxPlaybackTest.java
+++ b/extensions/vp9/src/androidTest/java/com/google/android/exoplayer/ext/vp9/VpxPlaybackTest.java
@@ -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)));
diff --git a/library/src/main/java/com/google/android/exoplayer/AdaptiveSourceEventListener.java b/library/src/main/java/com/google/android/exoplayer/AdaptiveSourceEventListener.java
new file mode 100644
index 0000000000..6ce4e13093
--- /dev/null
+++ b/library/src/main/java/com/google/android/exoplayer/AdaptiveSourceEventListener.java
@@ -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.
+ *
+ * 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
+ * not 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);
+ }
+
+ }
+
+}
diff --git a/library/src/main/java/com/google/android/exoplayer/C.java b/library/src/main/java/com/google/android/exoplayer/C.java
index 40e7fdb13f..89fa60c9e8 100644
--- a/library/src/main/java/com/google/android/exoplayer/C.java
+++ b/library/src/main/java/com/google/android/exoplayer/C.java
@@ -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.
diff --git a/library/src/main/java/com/google/android/exoplayer/SingleSampleSource.java b/library/src/main/java/com/google/android/exoplayer/SingleSampleSource.java
index 19c21436be..1543fb7ea5 100644
--- a/library/src/main/java/com/google/android/exoplayer/SingleSampleSource.java
+++ b/library/src/main/java/com/google/android/exoplayer/SingleSampleSource.java
@@ -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;
}
diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/BaseMediaChunk.java b/library/src/main/java/com/google/android/exoplayer/chunk/BaseMediaChunk.java
index 162360c156..17e43b3087 100644
--- a/library/src/main/java/com/google/android/exoplayer/chunk/BaseMediaChunk.java
+++ b/library/src/main/java/com/google/android/exoplayer/chunk/BaseMediaChunk.java
@@ -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);
}
/**
diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/Chunk.java b/library/src/main/java/com/google/android/exoplayer/chunk/Chunk.java
index 682479dff0..1e5d3aed8a 100644
--- a/library/src/main/java/com/google/android/exoplayer/chunk/Chunk.java
+++ b/library/src/main/java/com/google/android/exoplayer/chunk/Chunk.java
@@ -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;
}
/**
diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/ChunkTrackStream.java b/library/src/main/java/com/google/android/exoplayer/chunk/ChunkTrackStream.java
index 3a52907504..8b194e2cda 100644
--- a/library/src/main/java/com/google/android/exoplayer/chunk/ChunkTrackStream.java
+++ b/library/src/main/java/com/google/android/exoplayer/chunk/ChunkTrackStream.java
@@ -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 implements TrackStream,
Loader.Callback {
- 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 mediaChunks;
private final List 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 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 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 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 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 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 implements TrackStream,
loadingFinished = false;
}
sampleQueue.discardUpstreamSamples(removed.getFirstSampleIndex());
- eventDispatcher.upstreamDiscarded(startTimeUs, endTimeUs);
+ eventDispatcher.upstreamDiscarded(trackType, startTimeUs, endTimeUs);
return true;
}
diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/ChunkTrackStreamEventListener.java b/library/src/main/java/com/google/android/exoplayer/chunk/ChunkTrackStreamEventListener.java
deleted file mode 100644
index c8b0084110..0000000000
--- a/library/src/main/java/com/google/android/exoplayer/chunk/ChunkTrackStreamEventListener.java
+++ /dev/null
@@ -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));
- }
- });
- }
- }
-
- }
-
-}
diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/ContainerMediaChunk.java b/library/src/main/java/com/google/android/exoplayer/chunk/ContainerMediaChunk.java
index 6a36b47e73..ac880a135f 100644
--- a/library/src/main/java/com/google/android/exoplayer/chunk/ContainerMediaChunk.java
+++ b/library/src/main/java/com/google/android/exoplayer/chunk/ContainerMediaChunk.java
@@ -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;
diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/DataChunk.java b/library/src/main/java/com/google/android/exoplayer/chunk/DataChunk.java
index 264893aa51..7a0ed06314 100644
--- a/library/src/main/java/com/google/android/exoplayer/chunk/DataChunk.java
+++ b/library/src/main/java/com/google/android/exoplayer/chunk/DataChunk.java
@@ -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;
}
diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/FormatEvaluator.java b/library/src/main/java/com/google/android/exoplayer/chunk/FormatEvaluator.java
index 9650022a8e..a5f0e45ae0 100644
--- a/library/src/main/java/com/google/android/exoplayer/chunk/FormatEvaluator.java
+++ b/library/src/main/java/com/google/android/exoplayer/chunk/FormatEvaluator.java
@@ -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.
*
* 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;
}
diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/InitializationChunk.java b/library/src/main/java/com/google/android/exoplayer/chunk/InitializationChunk.java
index 3fe214a98b..a953105ccc 100644
--- a/library/src/main/java/com/google/android/exoplayer/chunk/InitializationChunk.java
+++ b/library/src/main/java/com/google/android/exoplayer/chunk/InitializationChunk.java
@@ -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;
}
diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/MediaChunk.java b/library/src/main/java/com/google/android/exoplayer/chunk/MediaChunk.java
index 776ea2c9bf..e7d2f5479e 100644
--- a/library/src/main/java/com/google/android/exoplayer/chunk/MediaChunk.java
+++ b/library/src/main/java/com/google/android/exoplayer/chunk/MediaChunk.java
@@ -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;
}
diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/SingleSampleMediaChunk.java b/library/src/main/java/com/google/android/exoplayer/chunk/SingleSampleMediaChunk.java
index f792a63bd3..8d1b85d552 100644
--- a/library/src/main/java/com/google/android/exoplayer/chunk/SingleSampleMediaChunk.java
+++ b/library/src/main/java/com/google/android/exoplayer/chunk/SingleSampleMediaChunk.java
@@ -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;
}
diff --git a/library/src/main/java/com/google/android/exoplayer/dash/DashChunkSource.java b/library/src/main/java/com/google/android/exoplayer/dash/DashChunkSource.java
index 457e8dcabb..a4207bb07c 100644
--- a/library/src/main/java/com/google/android/exoplayer/dash/DashChunkSource.java
+++ b/library/src/main/java/com/google/android/exoplayer/dash/DashChunkSource.java
@@ -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);
}
}
diff --git a/library/src/main/java/com/google/android/exoplayer/dash/DashSampleSource.java b/library/src/main/java/com/google/android/exoplayer/dash/DashSampleSource.java
index 2fab46d182..c30e56bb64 100644
--- a/library/src/main/java/com/google/android/exoplayer/dash/DashSampleSource.java
+++ b/library/src/main/java/com/google/android/exoplayer/dash/DashSampleSource.java
@@ -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 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 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 loadable,
+ long elapsedRealtimeMs, long loadDurationMs) {
+ eventDispatcher.loadCompleted(loadable.dataSpec, loadable.type, elapsedRealtimeMs,
+ loadDurationMs, loadable.bytesLoaded());
+ onUtcTimestampResolved(loadable.getResult() - elapsedRealtimeMs);
+ }
+
+ /* package */ int onUtcTimestampLoadError(ParsingLoadable 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 parser) {
- loader.startLoading(new UriLoadable<>(Uri.parse(timingElement.value), dataSource, parser),
- new UtcTimestampCallback(), 1);
+ ParsingLoadable.Parser 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 void startLoading(ParsingLoadable loadable, Callback> 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> {
+ Loader.Callback> {
@Override
- public void onLoadCompleted(UriLoadable loadable,
- long elapsedMs) {
- onManifestLoadCompleted(loadable.getResult(), elapsedMs);
+ public void onLoadCompleted(ParsingLoadable loadable,
+ long elapsedRealtimeMs, long loadDurationMs) {
+ onManifestLoadCompleted(loadable, elapsedRealtimeMs, loadDurationMs);
}
@Override
- public void onLoadCanceled(UriLoadable loadable, long elapsedMs,
- boolean released) {
- // Do nothing.
+ public void onLoadCanceled(ParsingLoadable loadable,
+ long elapsedRealtimeMs, long loadDurationMs, boolean released) {
+ DashSampleSource.this.onLoadCanceled(loadable, elapsedRealtimeMs, loadDurationMs);
}
@Override
- public int onLoadError(UriLoadable loadable, long elapsedMs,
- IOException exception) {
- return (exception instanceof ParserException) ? Loader.DONT_RETRY_FATAL : Loader.RETRY;
+ public int onLoadError(ParsingLoadable loadable,
+ long elapsedRealtimeMs, long loadDurationMs, IOException error) {
+ return onManifestLoadError(loadable, elapsedRealtimeMs, loadDurationMs, error);
}
}
- private final class UtcTimestampCallback implements Loader.Callback> {
+ private final class UtcTimestampCallback implements Loader.Callback> {
@Override
- public void onLoadCompleted(UriLoadable loadable, long elapsedMs) {
- onUtcTimestampLoadCompleted(loadable.getResult() - SystemClock.elapsedRealtime());
+ public void onLoadCompleted(ParsingLoadable loadable, long elapsedRealtimeMs,
+ long loadDurationMs) {
+ onUtcTimestampLoadCompleted(loadable, elapsedRealtimeMs, loadDurationMs);
}
@Override
- public void onLoadCanceled(UriLoadable loadable, long elapsedMs, boolean released) {
- // Do nothing.
+ public void onLoadCanceled(ParsingLoadable loadable, long elapsedRealtimeMs,
+ long loadDurationMs, boolean released) {
+ DashSampleSource.this.onLoadCanceled(loadable, elapsedRealtimeMs, loadDurationMs);
}
@Override
- public int onLoadError(UriLoadable loadable, long elapsedMs, IOException exception) {
- onUtcTimestampLoadError(exception);
- return Loader.DONT_RETRY;
+ public int onLoadError(ParsingLoadable loadable, long elapsedRealtimeMs,
+ long loadDurationMs, IOException error) {
+ return onUtcTimestampLoadError(loadable, elapsedRealtimeMs, loadDurationMs, error);
}
}
- private static final class XsDateTimeParser implements UriLoadable.Parser {
+ private static final class XsDateTimeParser implements ParsingLoadable.Parser {
@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 {
+ private static final class Iso8601Parser implements ParsingLoadable.Parser {
@Override
public Long parse(Uri uri, InputStream inputStream) throws IOException {
diff --git a/library/src/main/java/com/google/android/exoplayer/dash/mpd/MediaPresentationDescriptionParser.java b/library/src/main/java/com/google/android/exoplayer/dash/mpd/MediaPresentationDescriptionParser.java
index c585ecee48..7996077814 100644
--- a/library/src/main/java/com/google/android/exoplayer/dash/mpd/MediaPresentationDescriptionParser.java
+++ b/library/src/main/java/com/google/android/exoplayer/dash/mpd/MediaPresentationDescriptionParser.java
@@ -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 {
+ implements ParsingLoadable.Parser {
private static final String TAG = "MpdParser";
diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/ExtractorSampleSource.java b/library/src/main/java/com/google/android/exoplayer/extractor/ExtractorSampleSource.java
index 34f1d9a4f4..e89609dba9 100644
--- a/library/src/main/java/com/google/android/exoplayer/extractor/ExtractorSampleSource.java
+++ b/library/src/main/java/com/google/android/exoplayer/extractor/ExtractorSampleSource.java
@@ -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);
}
});
}
diff --git a/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java b/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java
index 86de102e58..af1cbd0b06 100644
--- a/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java
+++ b/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java
@@ -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;
}
diff --git a/library/src/main/java/com/google/android/exoplayer/hls/HlsMediaChunk.java b/library/src/main/java/com/google/android/exoplayer/hls/HlsMediaChunk.java
index d1738a04a4..1cca9fab36 100644
--- a/library/src/main/java/com/google/android/exoplayer/hls/HlsMediaChunk.java
+++ b/library/src/main/java/com/google/android/exoplayer/hls/HlsMediaChunk.java
@@ -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;
diff --git a/library/src/main/java/com/google/android/exoplayer/hls/HlsSampleSource.java b/library/src/main/java/com/google/android/exoplayer/hls/HlsSampleSource.java
index e56352472a..54b8ca3e9f 100644
--- a/library/src/main/java/com/google/android/exoplayer/hls/HlsSampleSource.java
+++ b/library/src/main/java/com/google/android/exoplayer/hls/HlsSampleSource.java
@@ -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> {
+ Loader.Callback> {
/**
* 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 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 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 loadable, long elapsedMs) {
+ public void onLoadCompleted(ParsingLoadable loadable, long elapsedRealtimeMs,
+ long loadDurationMs) {
+ eventDispatcher.loadCompleted(loadable.dataSpec, loadable.type, elapsedRealtimeMs,
+ loadDurationMs, loadable.bytesLoaded());
HlsPlaylist playlist = loadable.getResult();
List trackStreamWrapperList = buildTrackStreamWrappers(playlist);
trackStreamWrappers = new HlsTrackStreamWrapper[trackStreamWrapperList.size()];
@@ -249,13 +253,19 @@ public final class HlsSampleSource implements SampleSource,
}
@Override
- public void onLoadCanceled(UriLoadable loadable, long elapsedMs, boolean released) {
- // Do nothing.
+ public void onLoadCanceled(ParsingLoadable loadable, long elapsedRealtimeMs,
+ long loadDurationMs, boolean released) {
+ eventDispatcher.loadCompleted(loadable.dataSpec, loadable.type, elapsedRealtimeMs,
+ loadDurationMs, loadable.bytesLoaded());
}
@Override
- public int onLoadError(UriLoadable loadable, long elapsedMs, IOException exception) {
- return exception instanceof ParserException ? Loader.DONT_RETRY_FATAL : Loader.RETRY;
+ public int onLoadError(ParsingLoadable 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,
diff --git a/library/src/main/java/com/google/android/exoplayer/hls/HlsTrackStreamWrapper.java b/library/src/main/java/com/google/android/exoplayer/hls/HlsTrackStreamWrapper.java
index 3f701b75b4..d6c529eff3 100644
--- a/library/src/main/java/com/google/android/exoplayer/hls/HlsTrackStreamWrapper.java
+++ b/library/src/main/java/com/google/android/exoplayer/hls/HlsTrackStreamWrapper.java
@@ -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);
diff --git a/library/src/main/java/com/google/android/exoplayer/hls/playlist/HlsPlaylistParser.java b/library/src/main/java/com/google/android/exoplayer/hls/playlist/HlsPlaylistParser.java
index b1ab66a072..24556b59d3 100644
--- a/library/src/main/java/com/google/android/exoplayer/hls/playlist/HlsPlaylistParser.java
+++ b/library/src/main/java/com/google/android/exoplayer/hls/playlist/HlsPlaylistParser.java
@@ -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 {
+public final class HlsPlaylistParser implements ParsingLoadable.Parser {
private static final String VERSION_TAG = "#EXT-X-VERSION";
private static final String STREAM_INF_TAG = "#EXT-X-STREAM-INF";
diff --git a/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingChunkSource.java b/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingChunkSource.java
index cef6f96fe4..4d7dbc5453 100644
--- a/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingChunkSource.java
+++ b/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingChunkSource.java
@@ -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);
}
}
diff --git a/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingManifestParser.java b/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingManifestParser.java
index e0ec62e677..3d002b4cbc 100644
--- a/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingManifestParser.java
+++ b/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingManifestParser.java
@@ -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
* IIS Smooth Streaming Client Manifest Format
*/
-public class SmoothStreamingManifestParser implements UriLoadable.Parser {
+public class SmoothStreamingManifestParser implements
+ ParsingLoadable.Parser {
private final XmlPullParserFactory xmlParserFactory;
diff --git a/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingSampleSource.java b/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingSampleSource.java
index 45063c2519..2f1b33748c 100644
--- a/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingSampleSource.java
+++ b/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingSampleSource.java
@@ -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> {
+ Loader.Callback> {
/**
* 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 loadable, long elapsedMs) {
+ public void onLoadCompleted(ParsingLoadable 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 loadable, long elapsedMs,
- boolean released) {
- // Do nothing.
+ public void onLoadCanceled(ParsingLoadable loadable,
+ long elapsedRealtimeMs, long loadDurationMs, boolean released) {
+ eventDispatcher.loadCompleted(loadable.dataSpec, loadable.type, elapsedRealtimeMs,
+ loadDurationMs, loadable.bytesLoaded());
}
@Override
- public int onLoadError(UriLoadable loadable, long elapsedMs,
- IOException exception) {
- return (exception instanceof ParserException) ? Loader.DONT_RETRY_FATAL : Loader.RETRY;
+ public int onLoadError(ParsingLoadable 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 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")
diff --git a/library/src/main/java/com/google/android/exoplayer/upstream/DataSourceInputStream.java b/library/src/main/java/com/google/android/exoplayer/upstream/DataSourceInputStream.java
index 52e507a5df..5a2cd66dc5 100644
--- a/library/src/main/java/com/google/android/exoplayer/upstream/DataSourceInputStream.java
+++ b/library/src/main/java/com/google/android/exoplayer/upstream/DataSourceInputStream.java
@@ -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}.
*
@@ -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
diff --git a/library/src/main/java/com/google/android/exoplayer/upstream/Loader.java b/library/src/main/java/com/google/android/exoplayer/upstream/Loader.java
index 4e5907fc47..2c3cb88ee1 100644
--- a/library/src/main/java/com/google/android/exoplayer/upstream/Loader.java
+++ b/library/src/main/java/com/google/android/exoplayer/upstream/Loader.java
@@ -77,6 +77,18 @@ public final class Loader {
*/
public interface Callback {
+ /**
+ * Invoked when a load has completed.
+ *
+ * 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.
*
@@ -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.
- *
- * 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 void startLoading(T loadable, Callback callback,
+ public long startLoading(T loadable, Callback 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 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 callback, int minRetryCount) {
+ public LoadTask(Looper looper, T loadable, Loader.Callback 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) {
diff --git a/library/src/main/java/com/google/android/exoplayer/upstream/UriLoadable.java b/library/src/main/java/com/google/android/exoplayer/upstream/ParsingLoadable.java
similarity index 71%
rename from library/src/main/java/com/google/android/exoplayer/upstream/UriLoadable.java
rename to library/src/main/java/com/google/android/exoplayer/upstream/ParsingLoadable.java
index ac7317fea0..2ecda7f7d4 100644
--- a/library/src/main/java/com/google/android/exoplayer/upstream/UriLoadable.java
+++ b/library/src/main/java/com/google/android/exoplayer/upstream/ParsingLoadable.java
@@ -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 The type of the object being loaded.
*/
-public final class UriLoadable implements Loadable {
+public final class ParsingLoadable implements Loadable {
/**
* Parses an object from loaded data.
@@ -48,22 +49,34 @@ public final class UriLoadable 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 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 parser) {
+ public ParsingLoadable(DataSource dataSource, Uri uri, int type, Parser 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 implements Loadable {
return result;
}
+ /**
+ * Returns the number of bytes loaded.
+ *
+ * In the case that the network response was compressed, the value returned is the size of the
+ * data after 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 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();
}
}
diff --git a/library/src/main/java/com/google/android/exoplayer/util/Util.java b/library/src/main/java/com/google/android/exoplayer/util/Util.java
index 4d10ca1e2a..8ac22bca52 100644
--- a/library/src/main/java/com/google/android/exoplayer/util/Util.java
+++ b/library/src/main/java/com/google/android/exoplayer/util/Util.java
@@ -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.
*