From f4e4fd51c62bfaa96ffc67778a5932787e8c84bb Mon Sep 17 00:00:00 2001 From: olly Date: Mon, 13 Jun 2016 09:22:50 -0700 Subject: [PATCH] Clean up chunked media source event listeners. - Event listener is now at the SampleSource level, since the individual ChunkTrackStream instances are created internally. - Event listener receives events corresponding to loads made at the SampleSource level (e.g. manifest fetches) in addition to those made by the ChunkTrackStream instances. - Added ability for FormatEvaluators to propagate arbitrary information (as an Object) through to the listeners. This was a request from YouTube. - Added significantly more information to each event, for example the DataSpec that defines the load being made is now passed, as is timing information. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=124732984 --- .../android/exoplayer/demo/EventLogger.java | 41 ++- .../demo/UriSampleSourceProvider.java | 2 +- .../exoplayer/ext/flac/FlacPlaybackTest.java | 2 +- .../exoplayer/ext/opus/OpusPlaybackTest.java | 2 +- .../exoplayer/ext/vp9/VpxPlaybackTest.java | 2 +- .../AdaptiveSourceEventListener.java | 313 ++++++++++++++++++ .../java/com/google/android/exoplayer/C.java | 7 +- .../android/exoplayer/SingleSampleSource.java | 11 +- .../exoplayer/chunk/BaseMediaChunk.java | 15 +- .../google/android/exoplayer/chunk/Chunk.java | 64 ++-- .../exoplayer/chunk/ChunkTrackStream.java | 78 ++--- .../chunk/ChunkTrackStreamEventListener.java | 200 ----------- .../exoplayer/chunk/ContainerMediaChunk.java | 17 +- .../android/exoplayer/chunk/DataChunk.java | 16 +- .../exoplayer/chunk/FormatEvaluator.java | 59 +++- .../exoplayer/chunk/InitializationChunk.java | 17 +- .../android/exoplayer/chunk/MediaChunk.java | 25 +- .../chunk/SingleSampleMediaChunk.java | 16 +- .../exoplayer/dash/DashChunkSource.java | 26 +- .../exoplayer/dash/DashSampleSource.java | 138 +++++--- .../MediaPresentationDescriptionParser.java | 4 +- .../extractor/ExtractorSampleSource.java | 32 +- .../android/exoplayer/hls/HlsChunkSource.java | 62 ++-- .../android/exoplayer/hls/HlsMediaChunk.java | 20 +- .../exoplayer/hls/HlsSampleSource.java | 44 ++- .../exoplayer/hls/HlsTrackStreamWrapper.java | 72 ++-- .../hls/playlist/HlsPlaylistParser.java | 4 +- .../SmoothStreamingChunkSource.java | 13 +- .../SmoothStreamingManifestParser.java | 5 +- .../SmoothStreamingSampleSource.java | 48 +-- .../upstream/DataSourceInputStream.java | 26 +- .../android/exoplayer/upstream/Loader.java | 63 ++-- ...{UriLoadable.java => ParsingLoadable.java} | 40 ++- .../google/android/exoplayer/util/Util.java | 10 - 34 files changed, 881 insertions(+), 613 deletions(-) create mode 100644 library/src/main/java/com/google/android/exoplayer/AdaptiveSourceEventListener.java delete mode 100644 library/src/main/java/com/google/android/exoplayer/chunk/ChunkTrackStreamEventListener.java rename library/src/main/java/com/google/android/exoplayer/upstream/{UriLoadable.java => ParsingLoadable.java} (71%) 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. *