mirror of
https://github.com/androidx/media.git
synced 2025-05-07 23:50:44 +08:00
Clean up chunked media source event listeners.
- Event listener is now at the SampleSource level, since the individual ChunkTrackStream instances are created internally. - Event listener receives events corresponding to loads made at the SampleSource level (e.g. manifest fetches) in addition to those made by the ChunkTrackStream instances. - Added ability for FormatEvaluators to propagate arbitrary information (as an Object) through to the listeners. This was a request from YouTube. - Added significantly more information to each event, for example the DataSpec that defines the load being made is now passed, as is timing information. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=124732984
This commit is contained in:
parent
172bc4e8d8
commit
f4e4fd51c6
@ -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.
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
|
@ -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)));
|
||||
|
@ -0,0 +1,313 @@
|
||||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.google.android.exoplayer;
|
||||
|
||||
import com.google.android.exoplayer.chunk.FormatEvaluator;
|
||||
import com.google.android.exoplayer.upstream.DataSpec;
|
||||
import com.google.android.exoplayer.util.Assertions;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.SystemClock;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Interface for callbacks to be notified about events during adaptive playbacks.
|
||||
*/
|
||||
public interface AdaptiveSourceEventListener {
|
||||
|
||||
/**
|
||||
* Invoked when a load begins.
|
||||
*
|
||||
* @param dataSpec Defines the data being loaded.
|
||||
* @param dataType One of the {@link C} {@code DATA_TYPE_*} constants defining the type of data
|
||||
* being loaded.
|
||||
* @param trackType One of the {@link C} {@code TRACK_TYPE_*} constants if the data corresponds
|
||||
* to media of a specific type. {@link C#TRACK_TYPE_UNKNOWN} otherwise.
|
||||
* @param format The particular {@link Format} to which the data corresponds, or null if the data
|
||||
* being loaded does not correspond to a specific format.
|
||||
* @param formatEvaluatorTrigger One of the {@link FormatEvaluator} {@code TRIGGER_*} constants
|
||||
* if a format evaluation was performed to determine that the data should be loaded.
|
||||
* {@link FormatEvaluator#TRIGGER_UNKNOWN} otherwise.
|
||||
* @param formatEvaluatorData Optional data set by a {@link FormatEvaluator} if a format
|
||||
* evaluation was performed to determine that the data should be loaded. Null otherwise.
|
||||
* @param mediaStartTimeMs The start time of the media being loaded, or -1 if the load is not for
|
||||
* media data.
|
||||
* @param mediaEndTimeMs The end time of the media being loaded, or -1 if the load is not for
|
||||
* media data.
|
||||
* @param elapsedRealtimeMs The value of {@link SystemClock#elapsedRealtime} when the load began.
|
||||
*/
|
||||
void onLoadStarted(DataSpec dataSpec, int dataType, int trackType, Format format,
|
||||
int formatEvaluatorTrigger, Object formatEvaluatorData, long mediaStartTimeMs,
|
||||
long mediaEndTimeMs, long elapsedRealtimeMs);
|
||||
|
||||
/**
|
||||
* Invoked when a load ends.
|
||||
*
|
||||
* @param dataSpec Defines the data being loaded.
|
||||
* @param dataType One of the {@link C} {@code DATA_TYPE_*} constants defining the type of data
|
||||
* being loaded.
|
||||
* @param trackType One of the {@link C} {@code TRACK_TYPE_*} constants if the data corresponds
|
||||
* to media of a specific type. {@link C#TRACK_TYPE_UNKNOWN} otherwise.
|
||||
* @param format The particular {@link Format} to which the data corresponds, or null if the data
|
||||
* being loaded does not correspond to a specific format.
|
||||
* @param formatEvaluatorTrigger One of the {@link FormatEvaluator} {@code TRIGGER_*} constants
|
||||
* if a format evaluation was performed to determine that the data should be loaded.
|
||||
* {@link FormatEvaluator#TRIGGER_UNKNOWN} otherwise.
|
||||
* @param formatEvaluatorData Optional data set by a {@link FormatEvaluator} if a format
|
||||
* evaluation was performed to determine that the data should be loaded. Null otherwise.
|
||||
* @param mediaStartTimeMs The start time of the media being loaded, or -1 if the load is not for
|
||||
* media data.
|
||||
* @param mediaEndTimeMs The end time of the media being loaded, or -1 if the load is not for
|
||||
* media data.
|
||||
* @param elapsedRealtimeMs The value of {@link SystemClock#elapsedRealtime} when the load ended.
|
||||
* @param loadDurationMs The duration of the load.
|
||||
* @param bytesLoaded The number of bytes that were loaded.
|
||||
*/
|
||||
void onLoadCompleted(DataSpec dataSpec, int dataType, int trackType, Format format,
|
||||
int formatEvaluatorTrigger, Object formatEvaluatorData, long mediaStartTimeMs,
|
||||
long mediaEndTimeMs, long elapsedRealtimeMs, long loadDurationMs, long bytesLoaded);
|
||||
|
||||
/**
|
||||
* Invoked when a load is canceled.
|
||||
*
|
||||
* @param dataSpec Defines the data being loaded.
|
||||
* @param dataType One of the {@link C} {@code DATA_TYPE_*} constants defining the type of data
|
||||
* being loaded.
|
||||
* @param trackType One of the {@link C} {@code TRACK_TYPE_*} constants if the data corresponds
|
||||
* to media of a specific type. {@link C#TRACK_TYPE_UNKNOWN} otherwise.
|
||||
* @param format The particular {@link Format} to which the data corresponds, or null if the data
|
||||
* being loaded does not correspond to a specific format.
|
||||
* @param formatEvaluatorTrigger One of the {@link FormatEvaluator} {@code TRIGGER_*} constants
|
||||
* if a format evaluation was performed to determine that the data should be loaded.
|
||||
* {@link FormatEvaluator#TRIGGER_UNKNOWN} otherwise.
|
||||
* @param formatEvaluatorData Optional data set by a {@link FormatEvaluator} if a format
|
||||
* evaluation was performed to determine that the data should be loaded. Null otherwise.
|
||||
* @param mediaStartTimeMs The start time of the media being loaded, or -1 if the load is not for
|
||||
* media data.
|
||||
* @param mediaEndTimeMs The end time of the media being loaded, or -1 if the load is not for
|
||||
* media data.
|
||||
* @param elapsedRealtimeMs The value of {@link SystemClock#elapsedRealtime} when the load was
|
||||
* canceled.
|
||||
* @param loadDurationMs The duration of the load up to the point at which it was canceled.
|
||||
* @param bytesLoaded The number of bytes that were loaded prior to cancelation.
|
||||
*/
|
||||
void onLoadCanceled(DataSpec dataSpec, int dataType, int trackType, Format format,
|
||||
int formatEvaluatorTrigger, Object formatEvaluatorData, long mediaStartTimeMs,
|
||||
long mediaEndTimeMs, long elapsedRealtimeMs, long loadDurationMs, long bytesLoaded);
|
||||
|
||||
/**
|
||||
* Invoked when a load error occurs.
|
||||
* <p>
|
||||
* The error may or may not have resulted in the load being canceled, as indicated by the
|
||||
* {@code wasCanceled} parameter. If the load was canceled, {@link #onLoadCanceled} will
|
||||
* <em>not</em> be invoked in addition to this method.
|
||||
*
|
||||
* @param dataSpec Defines the data being loaded.
|
||||
* @param dataType One of the {@link C} {@code DATA_TYPE_*} constants defining the type of data
|
||||
* being loaded.
|
||||
* @param trackType One of the {@link C} {@code TRACK_TYPE_*} constants if the data corresponds
|
||||
* to media of a specific type. {@link C#TRACK_TYPE_UNKNOWN} otherwise.
|
||||
* @param format The particular {@link Format} to which the data corresponds, or null if the data
|
||||
* being loaded does not correspond to a specific format.
|
||||
* @param formatEvaluatorTrigger One of the {@link FormatEvaluator} {@code TRIGGER_*} constants
|
||||
* if a format evaluation was performed to determine that the data should be loaded.
|
||||
* {@link FormatEvaluator#TRIGGER_UNKNOWN} otherwise.
|
||||
* @param formatEvaluatorData Optional data set by a {@link FormatEvaluator} if a format
|
||||
* evaluation was performed to determine that the data should be loaded. Null otherwise.
|
||||
* @param mediaStartTimeMs The start time of the media being loaded, or -1 if the load is not for
|
||||
* media data.
|
||||
* @param mediaEndTimeMs The end time of the media being loaded, or -1 if the load is not for
|
||||
* media data.
|
||||
* @param elapsedRealtimeMs The value of {@link SystemClock#elapsedRealtime} when the error
|
||||
* occurred.
|
||||
* @param loadDurationMs The duration of the load up to the point at which the error occurred.
|
||||
* @param bytesLoaded The number of bytes that were loaded prior to the error.
|
||||
* @param error The load error.
|
||||
* @param wasCanceled True if the load was canceled as a result of the error. False otherwise.
|
||||
*/
|
||||
void onLoadError(DataSpec dataSpec, int dataType, int trackType, Format format,
|
||||
int formatEvaluatorTrigger, Object formatEvaluatorData, long mediaStartTimeMs,
|
||||
long mediaEndTimeMs, long elapsedRealtimeMs, long loadDurationMs, long bytesLoaded,
|
||||
IOException error, boolean wasCanceled);
|
||||
|
||||
/**
|
||||
* Invoked when data is removed from the back of a media buffer, typically so that it can be
|
||||
* re-buffered in a different format.
|
||||
*
|
||||
* @param trackType The type of the media. One of the {@link C} {@code TRACK_TYPE_*} constants.
|
||||
* @param mediaStartTimeMs The start time of the media being discarded.
|
||||
* @param mediaEndTimeMs The end time of the media being discarded.
|
||||
*/
|
||||
void onUpstreamDiscarded(int trackType, long mediaStartTimeMs, long mediaEndTimeMs);
|
||||
|
||||
/**
|
||||
* Invoked when a downstream format change occurs (i.e. when the format of the media being read
|
||||
* from one or more {@link TrackStream}s provided by the source changes).
|
||||
*
|
||||
* @param trackType The type of the media. One of the {@link C} {@code TRACK_TYPE_*} constants.
|
||||
* @param format The new format.
|
||||
* @param formatEvaluatorTrigger One of the {@link FormatEvaluator} {@code TRIGGER_*} constants
|
||||
* if a format evaluation was performed that resulted in this format change taking place.
|
||||
* {@link FormatEvaluator#TRIGGER_UNKNOWN} otherwise.
|
||||
* @param formatEvaluatorData Optional data set by a {@link FormatEvaluator} if a format
|
||||
* evaluation was performed that resulted in this format change taking place. Null otherwise.
|
||||
* @param mediaTimeMs The media time at which the change occurred.
|
||||
*/
|
||||
void onDownstreamFormatChanged(int trackType, Format format, int formatEvaluatorTrigger,
|
||||
Object formatEvaluatorData, long mediaTimeMs);
|
||||
|
||||
/**
|
||||
* Dispatches events to a {@link AdaptiveSourceEventListener}.
|
||||
*/
|
||||
final class EventDispatcher {
|
||||
|
||||
private final Handler handler;
|
||||
private final AdaptiveSourceEventListener listener;
|
||||
|
||||
public EventDispatcher(Handler handler, AdaptiveSourceEventListener listener) {
|
||||
this.handler = listener != null ? Assertions.checkNotNull(handler) : null;
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
public void loadStarted(DataSpec dataSpec, int dataType, long elapsedRealtimeMs) {
|
||||
loadStarted(dataSpec, dataType, C.TRACK_TYPE_UNKNOWN, null, FormatEvaluator.TRIGGER_UNKNOWN,
|
||||
null, C.UNSET_TIME_US, C.UNSET_TIME_US, elapsedRealtimeMs);
|
||||
}
|
||||
|
||||
public void loadStarted(final DataSpec dataSpec, final int dataType, final int trackType,
|
||||
final Format format, final int formatEvaluatorTrigger, final Object formatEvaluatorData,
|
||||
final long mediaStartTimeUs, final long mediaEndTimeUs, final long elapsedRealtimeMs) {
|
||||
if (listener != null) {
|
||||
handler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
listener.onLoadStarted(dataSpec, dataType, trackType, format, formatEvaluatorTrigger,
|
||||
formatEvaluatorData, usToMs(mediaStartTimeUs), usToMs(mediaEndTimeUs),
|
||||
elapsedRealtimeMs);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void loadCompleted(DataSpec dataSpec, int dataType, long elapsedRealtimeMs,
|
||||
long loadDurationMs, long bytesLoaded) {
|
||||
loadCompleted(dataSpec, dataType, C.TRACK_TYPE_UNKNOWN, null, FormatEvaluator.TRIGGER_UNKNOWN,
|
||||
null, C.UNSET_TIME_US, C.UNSET_TIME_US, elapsedRealtimeMs, loadDurationMs, bytesLoaded);
|
||||
}
|
||||
|
||||
public void loadCompleted(final DataSpec dataSpec, final int dataType, final int trackType,
|
||||
final Format format, final int formatEvaluatorTrigger, final Object formatEvaluatorData,
|
||||
final long mediaStartTimeUs, final long mediaEndTimeUs, final long elapsedRealtimeMs,
|
||||
final long loadDurationMs, final long bytesLoaded) {
|
||||
if (listener != null) {
|
||||
handler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
listener.onLoadCompleted(dataSpec, dataType, trackType, format, formatEvaluatorTrigger,
|
||||
formatEvaluatorData, usToMs(mediaStartTimeUs), usToMs(mediaEndTimeUs),
|
||||
elapsedRealtimeMs, loadDurationMs, bytesLoaded);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void loadCanceled(DataSpec dataSpec, int dataType, long elapsedRealtimeMs,
|
||||
long loadDurationMs, long bytesLoaded) {
|
||||
loadCanceled(dataSpec, dataType, C.TRACK_TYPE_UNKNOWN, null, FormatEvaluator.TRIGGER_UNKNOWN,
|
||||
null, C.UNSET_TIME_US, C.UNSET_TIME_US, elapsedRealtimeMs, loadDurationMs, bytesLoaded);
|
||||
}
|
||||
|
||||
public void loadCanceled(final DataSpec dataSpec, final int dataType, final int trackType,
|
||||
final Format format, final int formatEvaluatorTrigger, final Object formatEvaluatorData,
|
||||
final long mediaStartTimeUs, final long mediaEndTimeUs, final long elapsedRealtimeMs,
|
||||
final long loadDurationMs, final long bytesLoaded) {
|
||||
if (listener != null) {
|
||||
handler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
listener.onLoadCanceled(dataSpec, dataType, trackType, format, formatEvaluatorTrigger,
|
||||
formatEvaluatorData, usToMs(mediaStartTimeUs), usToMs(mediaEndTimeUs),
|
||||
elapsedRealtimeMs, loadDurationMs, bytesLoaded);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void loadError(DataSpec dataSpec, int dataType, long elapsedRealtimeMs,
|
||||
long loadDurationMs, long bytesLoaded, IOException error, boolean wasCanceled) {
|
||||
loadError(dataSpec, dataType, C.TRACK_TYPE_UNKNOWN, null, FormatEvaluator.TRIGGER_UNKNOWN,
|
||||
null, C.UNSET_TIME_US, C.UNSET_TIME_US, elapsedRealtimeMs, loadDurationMs, bytesLoaded,
|
||||
error, wasCanceled);
|
||||
}
|
||||
|
||||
public void loadError(final DataSpec dataSpec, final int dataType, final int trackType,
|
||||
final Format format, final int formatEvaluatorTrigger, final Object formatEvaluatorData,
|
||||
final long mediaStartTimeUs, final long mediaEndTimeUs, final long elapsedRealtimeMs,
|
||||
final long loadDurationMs, final long bytesLoaded, final IOException error,
|
||||
final boolean wasCanceled) {
|
||||
if (listener != null) {
|
||||
handler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
listener.onLoadError(dataSpec, dataType, trackType, format, formatEvaluatorTrigger,
|
||||
formatEvaluatorData, usToMs(mediaStartTimeUs), usToMs(mediaEndTimeUs),
|
||||
elapsedRealtimeMs, loadDurationMs, bytesLoaded, error, wasCanceled);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void upstreamDiscarded(final int trackType, final long mediaStartTimeUs,
|
||||
final long mediaEndTimeUs) {
|
||||
if (listener != null) {
|
||||
handler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
listener.onUpstreamDiscarded(trackType, usToMs(mediaStartTimeUs),
|
||||
usToMs(mediaEndTimeUs));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void downstreamFormatChanged(final int trackType, final Format format,
|
||||
final int formatEvaluatorTrigger, final Object formatEvaluatorData,
|
||||
final long mediaTimeUs) {
|
||||
if (listener != null) {
|
||||
handler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
listener.onDownstreamFormatChanged(trackType, format, formatEvaluatorTrigger,
|
||||
formatEvaluatorData, usToMs(mediaTimeUs));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts microseconds to milliseconds (rounding down), mapping {@link C#UNSET_TIME_US} to -1
|
||||
*
|
||||
* @param timeUs A microsecond value.
|
||||
* @return The value in milliseconds.
|
||||
*/
|
||||
private static long usToMs(long timeUs) {
|
||||
return timeUs == C.UNSET_TIME_US ? -1 : (timeUs / 1000);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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.
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -15,18 +15,17 @@
|
||||
*/
|
||||
package com.google.android.exoplayer.chunk;
|
||||
|
||||
import com.google.android.exoplayer.AdaptiveSourceEventListener.EventDispatcher;
|
||||
import com.google.android.exoplayer.C;
|
||||
import com.google.android.exoplayer.DecoderInputBuffer;
|
||||
import com.google.android.exoplayer.Format;
|
||||
import com.google.android.exoplayer.FormatHolder;
|
||||
import com.google.android.exoplayer.LoadControl;
|
||||
import com.google.android.exoplayer.TrackStream;
|
||||
import com.google.android.exoplayer.chunk.ChunkTrackStreamEventListener.EventDispatcher;
|
||||
import com.google.android.exoplayer.extractor.DefaultTrackOutput;
|
||||
import com.google.android.exoplayer.upstream.Loader;
|
||||
import com.google.android.exoplayer.util.Assertions;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.SystemClock;
|
||||
|
||||
import java.io.IOException;
|
||||
@ -40,15 +39,16 @@ import java.util.List;
|
||||
public class ChunkTrackStream<T extends ChunkSource> implements TrackStream,
|
||||
Loader.Callback<Chunk> {
|
||||
|
||||
private final Loader loader;
|
||||
private final int trackType;
|
||||
private final T chunkSource;
|
||||
private final LoadControl loadControl;
|
||||
private final EventDispatcher eventDispatcher;
|
||||
private final int minLoadableRetryCount;
|
||||
private final LinkedList<BaseMediaChunk> mediaChunks;
|
||||
private final List<BaseMediaChunk> readOnlyMediaChunks;
|
||||
private final DefaultTrackOutput sampleQueue;
|
||||
private final ChunkHolder nextChunkHolder;
|
||||
private final EventDispatcher eventDispatcher;
|
||||
private final LoadControl loadControl;
|
||||
private final Loader loader;
|
||||
|
||||
private boolean readingEnabled;
|
||||
private long lastPreferredQueueSizeEvaluationTimeMs;
|
||||
@ -61,25 +61,24 @@ public class ChunkTrackStream<T extends ChunkSource> implements TrackStream,
|
||||
private boolean loadingFinished;
|
||||
|
||||
/**
|
||||
* @param trackType The type of the track. One of the {@link C} {@code TRACK_TYPE_*} constants.
|
||||
* @param chunkSource A {@link ChunkSource} from which chunks to load are obtained.
|
||||
* @param loadControl Controls when the source is permitted to load data.
|
||||
* @param bufferSizeContribution The contribution of this source to the media buffer, in bytes.
|
||||
* @param positionUs The position from which to start loading media.
|
||||
* @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
|
||||
* null if delivery of events is not required.
|
||||
* @param eventListener A listener of events. May be null if delivery of events is not required.
|
||||
* @param eventSourceId An identifier that gets passed to {@code eventListener} methods.
|
||||
* @param minLoadableRetryCount The minimum number of times that the source should retry a load
|
||||
* before propagating an error.
|
||||
* @param eventDispatcher A dispatcher to notify of events.
|
||||
*/
|
||||
public ChunkTrackStream(T chunkSource, LoadControl loadControl,
|
||||
int bufferSizeContribution, long positionUs, Handler eventHandler,
|
||||
ChunkTrackStreamEventListener eventListener, int eventSourceId, int minLoadableRetryCount) {
|
||||
public ChunkTrackStream(int trackType, T chunkSource, LoadControl loadControl,
|
||||
int bufferSizeContribution, long positionUs, int minLoadableRetryCount,
|
||||
EventDispatcher eventDispatcher) {
|
||||
this.trackType = trackType;
|
||||
this.chunkSource = chunkSource;
|
||||
this.loadControl = loadControl;
|
||||
this.eventDispatcher = eventDispatcher;
|
||||
this.minLoadableRetryCount = minLoadableRetryCount;
|
||||
loader = new Loader("Loader:ChunkTrackStream");
|
||||
eventDispatcher = new EventDispatcher(eventHandler, eventListener, eventSourceId);
|
||||
nextChunkHolder = new ChunkHolder();
|
||||
mediaChunks = new LinkedList<>();
|
||||
readOnlyMediaChunks = Collections.unmodifiableList(mediaChunks);
|
||||
@ -204,7 +203,8 @@ public class ChunkTrackStream<T extends ChunkSource> implements TrackStream,
|
||||
|
||||
Format format = currentChunk.format;
|
||||
if (!format.equals(downstreamFormat)) {
|
||||
eventDispatcher.downstreamFormatChanged(format, currentChunk.trigger,
|
||||
eventDispatcher.downstreamFormatChanged(trackType, format,
|
||||
currentChunk.formatEvaluatorTrigger, currentChunk.formatEvaluatorData,
|
||||
currentChunk.startTimeUs);
|
||||
}
|
||||
downstreamFormat = format;
|
||||
@ -215,35 +215,34 @@ public class ChunkTrackStream<T extends ChunkSource> implements TrackStream,
|
||||
// Loader.Callback implementation.
|
||||
|
||||
@Override
|
||||
public void onLoadCompleted(Chunk loadable, long elapsedMs) {
|
||||
long now = SystemClock.elapsedRealtime();
|
||||
public void onLoadCompleted(Chunk loadable, long elapsedRealtimeMs, long loadDurationMs) {
|
||||
chunkSource.onChunkLoadCompleted(loadable);
|
||||
if (isMediaChunk(loadable)) {
|
||||
BaseMediaChunk mediaChunk = (BaseMediaChunk) loadable;
|
||||
eventDispatcher.loadCompleted(loadable.bytesLoaded(), mediaChunk.type,
|
||||
mediaChunk.trigger, mediaChunk.format, mediaChunk.startTimeUs, mediaChunk.endTimeUs, now,
|
||||
elapsedMs);
|
||||
} else {
|
||||
eventDispatcher.loadCompleted(loadable.bytesLoaded(), loadable.type, loadable.trigger,
|
||||
loadable.format, -1, -1, now, elapsedMs);
|
||||
}
|
||||
eventDispatcher.loadCompleted(loadable.dataSpec, loadable.type, trackType, loadable.format,
|
||||
loadable.formatEvaluatorTrigger, loadable.formatEvaluatorData, loadable.startTimeUs,
|
||||
loadable.endTimeUs, elapsedRealtimeMs, loadDurationMs, loadable.bytesLoaded());
|
||||
maybeStartLoading();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadCanceled(Chunk loadable, long elapsedMs, boolean released) {
|
||||
eventDispatcher.loadCanceled(loadable.bytesLoaded());
|
||||
public void onLoadCanceled(Chunk loadable, long elapsedRealtimeMs, long loadDurationMs,
|
||||
boolean released) {
|
||||
eventDispatcher.loadCanceled(loadable.dataSpec, loadable.type, trackType, loadable.format,
|
||||
loadable.formatEvaluatorTrigger, loadable.formatEvaluatorData, loadable.startTimeUs,
|
||||
loadable.endTimeUs, elapsedRealtimeMs, loadDurationMs, loadable.bytesLoaded());
|
||||
if (!released) {
|
||||
restartFrom(pendingResetPositionUs);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onLoadError(Chunk loadable, long elapsedMs, IOException e) {
|
||||
public int onLoadError(Chunk loadable, long elapsedRealtimeMs, long loadDurationMs,
|
||||
IOException error) {
|
||||
long bytesLoaded = loadable.bytesLoaded();
|
||||
boolean isMediaChunk = isMediaChunk(loadable);
|
||||
boolean cancelable = !isMediaChunk || bytesLoaded == 0 || mediaChunks.size() > 1;
|
||||
if (chunkSource.onChunkLoadError(loadable, cancelable, e)) {
|
||||
boolean canceled = false;
|
||||
if (chunkSource.onChunkLoadError(loadable, cancelable, error)) {
|
||||
canceled = true;
|
||||
if (isMediaChunk) {
|
||||
BaseMediaChunk removed = mediaChunks.removeLast();
|
||||
Assertions.checkState(removed == loadable);
|
||||
@ -252,12 +251,15 @@ public class ChunkTrackStream<T extends ChunkSource> implements TrackStream,
|
||||
pendingResetPositionUs = lastSeekPositionUs;
|
||||
}
|
||||
}
|
||||
eventDispatcher.loadError(e);
|
||||
eventDispatcher.loadCanceled(bytesLoaded);
|
||||
}
|
||||
eventDispatcher.loadError(loadable.dataSpec, loadable.type, trackType, loadable.format,
|
||||
loadable.formatEvaluatorTrigger, loadable.formatEvaluatorData, loadable.startTimeUs,
|
||||
loadable.endTimeUs, elapsedRealtimeMs, loadDurationMs, bytesLoaded, error,
|
||||
canceled);
|
||||
if (canceled) {
|
||||
maybeStartLoading();
|
||||
return Loader.DONT_RETRY;
|
||||
} else {
|
||||
eventDispatcher.loadError(e);
|
||||
return Loader.RETRY;
|
||||
}
|
||||
}
|
||||
@ -312,13 +314,11 @@ public class ChunkTrackStream<T extends ChunkSource> implements TrackStream,
|
||||
BaseMediaChunk mediaChunk = (BaseMediaChunk) loadable;
|
||||
mediaChunk.init(sampleQueue);
|
||||
mediaChunks.add(mediaChunk);
|
||||
eventDispatcher.loadStarted(mediaChunk.dataSpec.length, mediaChunk.type, mediaChunk.trigger,
|
||||
mediaChunk.format, mediaChunk.startTimeUs, mediaChunk.endTimeUs);
|
||||
} else {
|
||||
eventDispatcher.loadStarted(loadable.dataSpec.length, loadable.type, loadable.trigger,
|
||||
loadable.format, -1, -1);
|
||||
}
|
||||
loader.startLoading(loadable, this, minLoadableRetryCount);
|
||||
long elapsedRealtimeMs = loader.startLoading(loadable, this, minLoadableRetryCount);
|
||||
eventDispatcher.loadStarted(loadable.dataSpec, loadable.type, trackType, loadable.format,
|
||||
loadable.formatEvaluatorTrigger, loadable.formatEvaluatorData, loadable.startTimeUs,
|
||||
loadable.endTimeUs, elapsedRealtimeMs);
|
||||
// Update the load control again to indicate that we're now loading.
|
||||
loadControl.update(this, downstreamPositionUs, getNextLoadPositionUs(), true);
|
||||
}
|
||||
@ -363,7 +363,7 @@ public class ChunkTrackStream<T extends ChunkSource> implements TrackStream,
|
||||
loadingFinished = false;
|
||||
}
|
||||
sampleQueue.discardUpstreamSamples(removed.getFirstSampleIndex());
|
||||
eventDispatcher.upstreamDiscarded(startTimeUs, endTimeUs);
|
||||
eventDispatcher.upstreamDiscarded(trackType, startTimeUs, endTimeUs);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -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));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -25,7 +25,33 @@ import java.util.Random;
|
||||
* Selects from a number of available formats during playback.
|
||||
*/
|
||||
public interface FormatEvaluator {
|
||||
|
||||
|
||||
/**
|
||||
* A trigger for a load whose reason is unknown or unspecified.
|
||||
*/
|
||||
int TRIGGER_UNKNOWN = 0;
|
||||
/**
|
||||
* A trigger for a load triggered by an initial format selection.
|
||||
*/
|
||||
int TRIGGER_INITIAL = 1;
|
||||
/**
|
||||
* A trigger for a load triggered by a user initiated format selection.
|
||||
*/
|
||||
int TRIGGER_MANUAL = 2;
|
||||
/**
|
||||
* A trigger for a load triggered by an adaptive format selection.
|
||||
*/
|
||||
int TRIGGER_ADAPTIVE = 3;
|
||||
/**
|
||||
* A trigger for a load triggered whilst in a trick play mode.
|
||||
*/
|
||||
int TRIGGER_TRICK_PLAY = 4;
|
||||
/**
|
||||
* Applications or extensions may define custom {@code TRIGGER_*} constants greater than or equal
|
||||
* to this value.
|
||||
*/
|
||||
int TRIGGER_CUSTOM_BASE = 10000;
|
||||
|
||||
/**
|
||||
* Enables the evaluator.
|
||||
*
|
||||
@ -42,8 +68,8 @@ public interface FormatEvaluator {
|
||||
* Update the supplied evaluation.
|
||||
* <p>
|
||||
* When invoked, {@code evaluation} must contain the currently selected format (null for an
|
||||
* initial evaluation) and the most recent trigger {@link Chunk#TRIGGER_INITIAL} for an initial
|
||||
* evaluation).
|
||||
* initial evaluation), the most recent trigger {@link #TRIGGER_INITIAL} for an initial
|
||||
* evaluation) and the most recent evaluation data (null for an initial evaluation).
|
||||
*
|
||||
* @param bufferedDurationUs The duration of media currently buffered in microseconds.
|
||||
* @param blacklistFlags An array whose length is equal to the number of available formats. A
|
||||
@ -82,16 +108,13 @@ public interface FormatEvaluator {
|
||||
*/
|
||||
public int trigger;
|
||||
|
||||
public Evaluation() {
|
||||
trigger = Chunk.TRIGGER_INITIAL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears {@link #format} and sets {@link #trigger} to {@link Chunk#TRIGGER_INITIAL}.
|
||||
* Sticky optional data relating to the evaluation.
|
||||
*/
|
||||
public void clear() {
|
||||
format = null;
|
||||
trigger = Chunk.TRIGGER_INITIAL;
|
||||
public Object data;
|
||||
|
||||
public Evaluation() {
|
||||
trigger = TRIGGER_INITIAL;
|
||||
}
|
||||
|
||||
}
|
||||
@ -150,7 +173,7 @@ public interface FormatEvaluator {
|
||||
}
|
||||
Format newFormat = formats[formatIndex];
|
||||
if (evaluation.format != null && evaluation.format != newFormat) {
|
||||
evaluation.trigger = Chunk.TRIGGER_ADAPTIVE;
|
||||
evaluation.trigger = TRIGGER_ADAPTIVE;
|
||||
}
|
||||
evaluation.format = newFormat;
|
||||
}
|
||||
@ -173,12 +196,12 @@ public interface FormatEvaluator {
|
||||
*/
|
||||
final class AdaptiveEvaluator implements FormatEvaluator {
|
||||
|
||||
public static final int DEFAULT_MAX_INITIAL_BITRATE = 800000;
|
||||
private static final int DEFAULT_MAX_INITIAL_BITRATE = 800000;
|
||||
|
||||
public static final int DEFAULT_MIN_DURATION_FOR_QUALITY_INCREASE_MS = 10000;
|
||||
public static final int DEFAULT_MAX_DURATION_FOR_QUALITY_DECREASE_MS = 25000;
|
||||
public static final int DEFAULT_MIN_DURATION_TO_RETAIN_AFTER_DISCARD_MS = 25000;
|
||||
public static final float DEFAULT_BANDWIDTH_FRACTION = 0.75f;
|
||||
private static final int DEFAULT_MIN_DURATION_FOR_QUALITY_INCREASE_MS = 10000;
|
||||
private static final int DEFAULT_MAX_DURATION_FOR_QUALITY_DECREASE_MS = 25000;
|
||||
private static final int DEFAULT_MIN_DURATION_TO_RETAIN_AFTER_DISCARD_MS = 25000;
|
||||
private static final float DEFAULT_BANDWIDTH_FRACTION = 0.75f;
|
||||
|
||||
private final BandwidthMeter bandwidthMeter;
|
||||
private final int maxInitialBitrate;
|
||||
@ -257,7 +280,7 @@ public interface FormatEvaluator {
|
||||
ideal = current;
|
||||
}
|
||||
if (current != null && ideal != current) {
|
||||
evaluation.trigger = Chunk.TRIGGER_ADAPTIVE;
|
||||
evaluation.trigger = TRIGGER_ADAPTIVE;
|
||||
}
|
||||
evaluation.format = ideal;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -15,6 +15,8 @@
|
||||
*/
|
||||
package com.google.android.exoplayer.dash;
|
||||
|
||||
import com.google.android.exoplayer.AdaptiveSourceEventListener;
|
||||
import com.google.android.exoplayer.AdaptiveSourceEventListener.EventDispatcher;
|
||||
import com.google.android.exoplayer.C;
|
||||
import com.google.android.exoplayer.DefaultLoadControl;
|
||||
import com.google.android.exoplayer.Format;
|
||||
@ -26,7 +28,6 @@ import com.google.android.exoplayer.TrackGroupArray;
|
||||
import com.google.android.exoplayer.TrackSelection;
|
||||
import com.google.android.exoplayer.TrackStream;
|
||||
import com.google.android.exoplayer.chunk.ChunkTrackStream;
|
||||
import com.google.android.exoplayer.chunk.ChunkTrackStreamEventListener;
|
||||
import com.google.android.exoplayer.chunk.FormatEvaluator;
|
||||
import com.google.android.exoplayer.chunk.FormatEvaluator.AdaptiveEvaluator;
|
||||
import com.google.android.exoplayer.dash.mpd.AdaptationSet;
|
||||
@ -40,7 +41,8 @@ import com.google.android.exoplayer.upstream.DataSource;
|
||||
import com.google.android.exoplayer.upstream.DataSourceFactory;
|
||||
import com.google.android.exoplayer.upstream.DefaultAllocator;
|
||||
import com.google.android.exoplayer.upstream.Loader;
|
||||
import com.google.android.exoplayer.upstream.UriLoadable;
|
||||
import com.google.android.exoplayer.upstream.Loader.Callback;
|
||||
import com.google.android.exoplayer.upstream.ParsingLoadable;
|
||||
import com.google.android.exoplayer.util.Util;
|
||||
|
||||
import android.net.Uri;
|
||||
@ -73,8 +75,7 @@ public final class DashSampleSource implements SampleSource {
|
||||
|
||||
private final DataSourceFactory dataSourceFactory;
|
||||
private final BandwidthMeter bandwidthMeter;
|
||||
private final Handler eventHandler;
|
||||
private final ChunkTrackStreamEventListener eventListener;
|
||||
private final EventDispatcher eventDispatcher;
|
||||
private final LoadControl loadControl;
|
||||
private final Loader loader;
|
||||
private final DataSource dataSource;
|
||||
@ -97,12 +98,11 @@ public final class DashSampleSource implements SampleSource {
|
||||
|
||||
public DashSampleSource(Uri manifestUri, DataSourceFactory dataSourceFactory,
|
||||
BandwidthMeter bandwidthMeter, Handler eventHandler,
|
||||
ChunkTrackStreamEventListener eventListener) {
|
||||
AdaptiveSourceEventListener eventListener) {
|
||||
this.manifestUri = manifestUri;
|
||||
this.dataSourceFactory = dataSourceFactory;
|
||||
this.bandwidthMeter = bandwidthMeter;
|
||||
this.eventHandler = eventHandler;
|
||||
this.eventListener = eventListener;
|
||||
eventDispatcher = new EventDispatcher(eventHandler, eventListener);
|
||||
loadControl = new DefaultLoadControl(new DefaultAllocator(C.DEFAULT_BUFFER_SEGMENT_SIZE));
|
||||
loader = new Loader("Loader:DashSampleSource");
|
||||
dataSource = dataSourceFactory.createDataSource();
|
||||
@ -227,11 +227,13 @@ public final class DashSampleSource implements SampleSource {
|
||||
|
||||
// Loadable callbacks.
|
||||
|
||||
/* package */ void onManifestLoadCompleted(MediaPresentationDescription manifest,
|
||||
long elapsedMs) {
|
||||
this.manifest = manifest;
|
||||
manifestLoadEndTimestamp = SystemClock.elapsedRealtime();
|
||||
manifestLoadStartTimestamp = manifestLoadEndTimestamp - elapsedMs;
|
||||
/* package */ void onManifestLoadCompleted(ParsingLoadable<MediaPresentationDescription> loadable,
|
||||
long elapsedRealtimeMs, long loadDurationMs) {
|
||||
eventDispatcher.loadCompleted(loadable.dataSpec, loadable.type, elapsedRealtimeMs,
|
||||
loadDurationMs, loadable.bytesLoaded());
|
||||
manifest = loadable.getResult();
|
||||
manifestLoadStartTimestamp = elapsedRealtimeMs - loadDurationMs;
|
||||
manifestLoadEndTimestamp = elapsedRealtimeMs;
|
||||
if (manifest.location != null) {
|
||||
manifestUri = manifest.location;
|
||||
}
|
||||
@ -250,22 +252,40 @@ public final class DashSampleSource implements SampleSource {
|
||||
}
|
||||
}
|
||||
|
||||
/* package */ void onUtcTimestampLoadCompleted(long elapsedRealtimeOffset) {
|
||||
this.elapsedRealtimeOffset = elapsedRealtimeOffset;
|
||||
prepared = true;
|
||||
/* package */ int onManifestLoadError(ParsingLoadable<MediaPresentationDescription> loadable,
|
||||
long elapsedRealtimeMs, long loadDurationMs, IOException error) {
|
||||
boolean isFatal = error instanceof ParserException;
|
||||
eventDispatcher.loadError(loadable.dataSpec, loadable.type, elapsedRealtimeMs, loadDurationMs,
|
||||
loadable.bytesLoaded(), error, isFatal);
|
||||
return isFatal ? Loader.DONT_RETRY_FATAL : Loader.RETRY;
|
||||
}
|
||||
|
||||
/* package */ void onUtcTimestampLoadError(IOException e) {
|
||||
Log.e(TAG, "Failed to resolve UtcTiming element.", e);
|
||||
// Be optimistic and continue in the hope that the device clock is correct.
|
||||
prepared = true;
|
||||
/* package */ void onUtcTimestampLoadCompleted(ParsingLoadable<Long> loadable,
|
||||
long elapsedRealtimeMs, long loadDurationMs) {
|
||||
eventDispatcher.loadCompleted(loadable.dataSpec, loadable.type, elapsedRealtimeMs,
|
||||
loadDurationMs, loadable.bytesLoaded());
|
||||
onUtcTimestampResolved(loadable.getResult() - elapsedRealtimeMs);
|
||||
}
|
||||
|
||||
/* package */ int onUtcTimestampLoadError(ParsingLoadable<Long> loadable, long elapsedRealtimeMs,
|
||||
long loadDurationMs, IOException error) {
|
||||
eventDispatcher.loadError(loadable.dataSpec, loadable.type, elapsedRealtimeMs, loadDurationMs,
|
||||
loadable.bytesLoaded(), error, true);
|
||||
onUtcTimestampResolutionError(error);
|
||||
return Loader.DONT_RETRY;
|
||||
}
|
||||
|
||||
/* package */ void onLoadCanceled(ParsingLoadable<?> loadable, long elapsedRealtimeMs,
|
||||
long loadDurationMs) {
|
||||
eventDispatcher.loadCanceled(loadable.dataSpec, loadable.type, elapsedRealtimeMs,
|
||||
loadDurationMs, loadable.bytesLoaded());
|
||||
}
|
||||
|
||||
// Internal methods.
|
||||
|
||||
private void startLoadingManifest() {
|
||||
loader.startLoading(new UriLoadable<>(manifestUri, dataSource, manifestParser),
|
||||
manifestCallback, MIN_LOADABLE_RETRY_COUNT);
|
||||
startLoading(new ParsingLoadable<>(dataSource, manifestUri, C.DATA_TYPE_MANIFEST,
|
||||
manifestParser), manifestCallback, MIN_LOADABLE_RETRY_COUNT);
|
||||
}
|
||||
|
||||
private void resolveUtcTimingElement(UtcTimingElement timingElement) {
|
||||
@ -279,24 +299,40 @@ public final class DashSampleSource implements SampleSource {
|
||||
resolveUtcTimingElementHttp(timingElement, new XsDateTimeParser());
|
||||
} else {
|
||||
// Unsupported scheme.
|
||||
onUtcTimestampLoadError(new IOException("Unsupported UTC timing scheme"));
|
||||
onUtcTimestampResolutionError(new IOException("Unsupported UTC timing scheme"));
|
||||
}
|
||||
}
|
||||
|
||||
private void resolveUtcTimingElementDirect(UtcTimingElement timingElement) {
|
||||
try {
|
||||
long utcTimestamp = Util.parseXsDateTime(timingElement.value);
|
||||
long elapsedRealtimeOffset = utcTimestamp - manifestLoadEndTimestamp;
|
||||
onUtcTimestampLoadCompleted(elapsedRealtimeOffset);
|
||||
onUtcTimestampResolved(utcTimestamp - manifestLoadEndTimestamp);
|
||||
} catch (ParseException e) {
|
||||
onUtcTimestampLoadError(new ParserException(e));
|
||||
onUtcTimestampResolutionError(new ParserException(e));
|
||||
}
|
||||
}
|
||||
|
||||
private void resolveUtcTimingElementHttp(UtcTimingElement timingElement,
|
||||
UriLoadable.Parser<Long> parser) {
|
||||
loader.startLoading(new UriLoadable<>(Uri.parse(timingElement.value), dataSource, parser),
|
||||
new UtcTimestampCallback(), 1);
|
||||
ParsingLoadable.Parser<Long> parser) {
|
||||
startLoading(new ParsingLoadable<>(dataSource, Uri.parse(timingElement.value),
|
||||
C.DATA_TYPE_TIME_SYNCHRONIZATION, parser), new UtcTimestampCallback(), 1);
|
||||
}
|
||||
|
||||
private void onUtcTimestampResolved(long elapsedRealtimeOffsetMs) {
|
||||
this.elapsedRealtimeOffset = elapsedRealtimeOffsetMs;
|
||||
prepared = true;
|
||||
}
|
||||
|
||||
private void onUtcTimestampResolutionError(IOException error) {
|
||||
Log.e(TAG, "Failed to resolve UtcTiming element.", error);
|
||||
// Be optimistic and continue in the hope that the device clock is correct.
|
||||
prepared = true;
|
||||
}
|
||||
|
||||
private <T> void startLoading(ParsingLoadable<T> loadable, Callback<ParsingLoadable<T>> callback,
|
||||
int minRetryCount) {
|
||||
long elapsedRealtimeMs = loader.startLoading(loadable, callback, minRetryCount);
|
||||
eventDispatcher.loadStarted(loadable.dataSpec, loadable.type, elapsedRealtimeMs);
|
||||
}
|
||||
|
||||
private void buildTrackGroups(MediaPresentationDescription manifest) {
|
||||
@ -341,8 +377,8 @@ public final class DashSampleSource implements SampleSource {
|
||||
DashChunkSource chunkSource = new DashChunkSource(loader, manifest, adaptationSetIndex,
|
||||
trackGroups.get(selection.group), selectedTracks, dataSource, adaptiveEvaluator,
|
||||
elapsedRealtimeOffset);
|
||||
return new ChunkTrackStream<>(chunkSource, loadControl, bufferSize, positionUs, eventHandler,
|
||||
eventListener, adaptationSetType, MIN_LOADABLE_RETRY_COUNT);
|
||||
return new ChunkTrackStream<>(adaptationSetType, chunkSource, loadControl, bufferSize,
|
||||
positionUs, MIN_LOADABLE_RETRY_COUNT, eventDispatcher);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@ -351,49 +387,51 @@ public final class DashSampleSource implements SampleSource {
|
||||
}
|
||||
|
||||
private final class ManifestCallback implements
|
||||
Loader.Callback<UriLoadable<MediaPresentationDescription>> {
|
||||
Loader.Callback<ParsingLoadable<MediaPresentationDescription>> {
|
||||
|
||||
@Override
|
||||
public void onLoadCompleted(UriLoadable<MediaPresentationDescription> loadable,
|
||||
long elapsedMs) {
|
||||
onManifestLoadCompleted(loadable.getResult(), elapsedMs);
|
||||
public void onLoadCompleted(ParsingLoadable<MediaPresentationDescription> loadable,
|
||||
long elapsedRealtimeMs, long loadDurationMs) {
|
||||
onManifestLoadCompleted(loadable, elapsedRealtimeMs, loadDurationMs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadCanceled(UriLoadable<MediaPresentationDescription> loadable, long elapsedMs,
|
||||
boolean released) {
|
||||
// Do nothing.
|
||||
public void onLoadCanceled(ParsingLoadable<MediaPresentationDescription> loadable,
|
||||
long elapsedRealtimeMs, long loadDurationMs, boolean released) {
|
||||
DashSampleSource.this.onLoadCanceled(loadable, elapsedRealtimeMs, loadDurationMs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onLoadError(UriLoadable<MediaPresentationDescription> loadable, long elapsedMs,
|
||||
IOException exception) {
|
||||
return (exception instanceof ParserException) ? Loader.DONT_RETRY_FATAL : Loader.RETRY;
|
||||
public int onLoadError(ParsingLoadable<MediaPresentationDescription> loadable,
|
||||
long elapsedRealtimeMs, long loadDurationMs, IOException error) {
|
||||
return onManifestLoadError(loadable, elapsedRealtimeMs, loadDurationMs, error);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private final class UtcTimestampCallback implements Loader.Callback<UriLoadable<Long>> {
|
||||
private final class UtcTimestampCallback implements Loader.Callback<ParsingLoadable<Long>> {
|
||||
|
||||
@Override
|
||||
public void onLoadCompleted(UriLoadable<Long> loadable, long elapsedMs) {
|
||||
onUtcTimestampLoadCompleted(loadable.getResult() - SystemClock.elapsedRealtime());
|
||||
public void onLoadCompleted(ParsingLoadable<Long> loadable, long elapsedRealtimeMs,
|
||||
long loadDurationMs) {
|
||||
onUtcTimestampLoadCompleted(loadable, elapsedRealtimeMs, loadDurationMs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadCanceled(UriLoadable<Long> loadable, long elapsedMs, boolean released) {
|
||||
// Do nothing.
|
||||
public void onLoadCanceled(ParsingLoadable<Long> loadable, long elapsedRealtimeMs,
|
||||
long loadDurationMs, boolean released) {
|
||||
DashSampleSource.this.onLoadCanceled(loadable, elapsedRealtimeMs, loadDurationMs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onLoadError(UriLoadable<Long> loadable, long elapsedMs, IOException exception) {
|
||||
onUtcTimestampLoadError(exception);
|
||||
return Loader.DONT_RETRY;
|
||||
public int onLoadError(ParsingLoadable<Long> loadable, long elapsedRealtimeMs,
|
||||
long loadDurationMs, IOException error) {
|
||||
return onUtcTimestampLoadError(loadable, elapsedRealtimeMs, loadDurationMs, error);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final class XsDateTimeParser implements UriLoadable.Parser<Long> {
|
||||
private static final class XsDateTimeParser implements ParsingLoadable.Parser<Long> {
|
||||
|
||||
@Override
|
||||
public Long parse(Uri uri, InputStream inputStream) throws IOException {
|
||||
@ -407,7 +445,7 @@ public final class DashSampleSource implements SampleSource {
|
||||
|
||||
}
|
||||
|
||||
private static final class Iso8601Parser implements UriLoadable.Parser<Long> {
|
||||
private static final class Iso8601Parser implements ParsingLoadable.Parser<Long> {
|
||||
|
||||
@Override
|
||||
public Long parse(Uri uri, InputStream inputStream) throws IOException {
|
||||
|
@ -25,7 +25,7 @@ import com.google.android.exoplayer.dash.mpd.SegmentBase.SingleSegmentBase;
|
||||
import com.google.android.exoplayer.drm.DrmInitData;
|
||||
import com.google.android.exoplayer.drm.DrmInitData.SchemeData;
|
||||
import com.google.android.exoplayer.extractor.mp4.PsshAtomUtil;
|
||||
import com.google.android.exoplayer.upstream.UriLoadable;
|
||||
import com.google.android.exoplayer.upstream.ParsingLoadable;
|
||||
import com.google.android.exoplayer.util.Assertions;
|
||||
import com.google.android.exoplayer.util.MimeTypes;
|
||||
import com.google.android.exoplayer.util.ParserUtil;
|
||||
@ -56,7 +56,7 @@ import java.util.regex.Pattern;
|
||||
* A parser of media presentation description files.
|
||||
*/
|
||||
public class MediaPresentationDescriptionParser extends DefaultHandler
|
||||
implements UriLoadable.Parser<MediaPresentationDescription> {
|
||||
implements ParsingLoadable.Parser<MediaPresentationDescription> {
|
||||
|
||||
private static final String TAG = "MpdParser";
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -15,6 +15,8 @@
|
||||
*/
|
||||
package com.google.android.exoplayer.hls;
|
||||
|
||||
import com.google.android.exoplayer.AdaptiveSourceEventListener;
|
||||
import com.google.android.exoplayer.AdaptiveSourceEventListener.EventDispatcher;
|
||||
import com.google.android.exoplayer.C;
|
||||
import com.google.android.exoplayer.DefaultLoadControl;
|
||||
import com.google.android.exoplayer.Format;
|
||||
@ -25,7 +27,6 @@ import com.google.android.exoplayer.TrackGroup;
|
||||
import com.google.android.exoplayer.TrackGroupArray;
|
||||
import com.google.android.exoplayer.TrackSelection;
|
||||
import com.google.android.exoplayer.TrackStream;
|
||||
import com.google.android.exoplayer.chunk.ChunkTrackStreamEventListener;
|
||||
import com.google.android.exoplayer.chunk.FormatEvaluator;
|
||||
import com.google.android.exoplayer.hls.playlist.HlsMasterPlaylist;
|
||||
import com.google.android.exoplayer.hls.playlist.HlsMediaPlaylist;
|
||||
@ -37,7 +38,7 @@ import com.google.android.exoplayer.upstream.DataSource;
|
||||
import com.google.android.exoplayer.upstream.DataSourceFactory;
|
||||
import com.google.android.exoplayer.upstream.DefaultAllocator;
|
||||
import com.google.android.exoplayer.upstream.Loader;
|
||||
import com.google.android.exoplayer.upstream.UriLoadable;
|
||||
import com.google.android.exoplayer.upstream.ParsingLoadable;
|
||||
import com.google.android.exoplayer.util.MimeTypes;
|
||||
|
||||
import android.net.Uri;
|
||||
@ -54,7 +55,7 @@ import java.util.List;
|
||||
* A {@link SampleSource} for HLS streams.
|
||||
*/
|
||||
public final class HlsSampleSource implements SampleSource,
|
||||
Loader.Callback<UriLoadable<HlsPlaylist>> {
|
||||
Loader.Callback<ParsingLoadable<HlsPlaylist>> {
|
||||
|
||||
/**
|
||||
* The minimum number of times to retry loading data prior to failing.
|
||||
@ -64,8 +65,7 @@ public final class HlsSampleSource implements SampleSource,
|
||||
private final Uri manifestUri;
|
||||
private final DataSourceFactory dataSourceFactory;
|
||||
private final BandwidthMeter bandwidthMeter;
|
||||
private final Handler eventHandler;
|
||||
private final ChunkTrackStreamEventListener eventListener;
|
||||
private final EventDispatcher eventDispatcher;
|
||||
private final LoadControl loadControl;
|
||||
private final IdentityHashMap<TrackStream, HlsTrackStreamWrapper> trackStreamSources;
|
||||
private final PtsTimestampAdjusterProvider timestampAdjusterProvider;
|
||||
@ -85,13 +85,12 @@ public final class HlsSampleSource implements SampleSource,
|
||||
|
||||
public HlsSampleSource(Uri manifestUri, DataSourceFactory dataSourceFactory,
|
||||
BandwidthMeter bandwidthMeter, Handler eventHandler,
|
||||
ChunkTrackStreamEventListener eventListener) {
|
||||
AdaptiveSourceEventListener eventListener) {
|
||||
this.manifestUri = manifestUri;
|
||||
this.dataSourceFactory = dataSourceFactory;
|
||||
this.bandwidthMeter = bandwidthMeter;
|
||||
this.eventHandler = eventHandler;
|
||||
this.eventListener = eventListener;
|
||||
|
||||
eventDispatcher = new EventDispatcher(eventHandler, eventListener);
|
||||
loadControl = new DefaultLoadControl(new DefaultAllocator(C.DEFAULT_BUFFER_SEGMENT_SIZE));
|
||||
timestampAdjusterProvider = new PtsTimestampAdjusterProvider();
|
||||
trackStreamSources = new IdentityHashMap<>();
|
||||
@ -110,9 +109,11 @@ public final class HlsSampleSource implements SampleSource,
|
||||
if (trackStreamWrappers == null) {
|
||||
manifestFetcher.maybeThrowError();
|
||||
if (!manifestFetcher.isLoading()) {
|
||||
manifestFetcher.startLoading(
|
||||
new UriLoadable<>(manifestUri, manifestDataSource, manifestParser), this,
|
||||
ParsingLoadable<HlsPlaylist> loadable = new ParsingLoadable<>(manifestDataSource,
|
||||
manifestUri, C.DATA_TYPE_MANIFEST, manifestParser);
|
||||
long elapsedRealtimeMs = manifestFetcher.startLoading(loadable, this,
|
||||
MIN_LOADABLE_RETRY_COUNT);
|
||||
eventDispatcher.loadStarted(loadable.dataSpec, loadable.type, elapsedRealtimeMs);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@ -240,7 +241,10 @@ public final class HlsSampleSource implements SampleSource,
|
||||
// Loader.Callback implementation.
|
||||
|
||||
@Override
|
||||
public void onLoadCompleted(UriLoadable<HlsPlaylist> loadable, long elapsedMs) {
|
||||
public void onLoadCompleted(ParsingLoadable<HlsPlaylist> loadable, long elapsedRealtimeMs,
|
||||
long loadDurationMs) {
|
||||
eventDispatcher.loadCompleted(loadable.dataSpec, loadable.type, elapsedRealtimeMs,
|
||||
loadDurationMs, loadable.bytesLoaded());
|
||||
HlsPlaylist playlist = loadable.getResult();
|
||||
List<HlsTrackStreamWrapper> trackStreamWrapperList = buildTrackStreamWrappers(playlist);
|
||||
trackStreamWrappers = new HlsTrackStreamWrapper[trackStreamWrapperList.size()];
|
||||
@ -249,13 +253,19 @@ public final class HlsSampleSource implements SampleSource,
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadCanceled(UriLoadable<HlsPlaylist> loadable, long elapsedMs, boolean released) {
|
||||
// Do nothing.
|
||||
public void onLoadCanceled(ParsingLoadable<HlsPlaylist> loadable, long elapsedRealtimeMs,
|
||||
long loadDurationMs, boolean released) {
|
||||
eventDispatcher.loadCompleted(loadable.dataSpec, loadable.type, elapsedRealtimeMs,
|
||||
loadDurationMs, loadable.bytesLoaded());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onLoadError(UriLoadable<HlsPlaylist> loadable, long elapsedMs, IOException exception) {
|
||||
return exception instanceof ParserException ? Loader.DONT_RETRY_FATAL : Loader.RETRY;
|
||||
public int onLoadError(ParsingLoadable<HlsPlaylist> loadable, long elapsedRealtimeMs,
|
||||
long loadDurationMs, IOException error) {
|
||||
boolean isFatal = error instanceof ParserException;
|
||||
eventDispatcher.loadError(loadable.dataSpec, loadable.type, elapsedRealtimeMs, loadDurationMs,
|
||||
loadable.bytesLoaded(), error, isFatal);
|
||||
return isFatal ? Loader.DONT_RETRY_FATAL : Loader.RETRY;
|
||||
}
|
||||
|
||||
// Internal methods.
|
||||
@ -333,8 +343,8 @@ public final class HlsSampleSource implements SampleSource,
|
||||
DataSource dataSource = dataSourceFactory.createDataSource(bandwidthMeter);
|
||||
HlsChunkSource defaultChunkSource = new HlsChunkSource(baseUri, variants, dataSource,
|
||||
timestampAdjusterProvider, formatEvaluator);
|
||||
return new HlsTrackStreamWrapper(defaultChunkSource, loadControl, bufferSize, muxedAudioFormat,
|
||||
muxedCaptionFormat, eventHandler, eventListener, trackType, MIN_LOADABLE_RETRY_COUNT);
|
||||
return new HlsTrackStreamWrapper(trackType, defaultChunkSource, loadControl, bufferSize,
|
||||
muxedAudioFormat, muxedCaptionFormat, MIN_LOADABLE_RETRY_COUNT, eventDispatcher);
|
||||
}
|
||||
|
||||
private int selectTracks(HlsTrackStreamWrapper trackStreamWrapper,
|
||||
|
@ -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);
|
||||
|
@ -19,7 +19,7 @@ import com.google.android.exoplayer.C;
|
||||
import com.google.android.exoplayer.Format;
|
||||
import com.google.android.exoplayer.ParserException;
|
||||
import com.google.android.exoplayer.hls.playlist.HlsMediaPlaylist.Segment;
|
||||
import com.google.android.exoplayer.upstream.UriLoadable;
|
||||
import com.google.android.exoplayer.upstream.ParsingLoadable;
|
||||
import com.google.android.exoplayer.util.MimeTypes;
|
||||
|
||||
import android.net.Uri;
|
||||
@ -38,7 +38,7 @@ import java.util.regex.Pattern;
|
||||
/**
|
||||
* HLS playlists parsing logic.
|
||||
*/
|
||||
public final class HlsPlaylistParser implements UriLoadable.Parser<HlsPlaylist> {
|
||||
public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlaylist> {
|
||||
|
||||
private static final String VERSION_TAG = "#EXT-X-VERSION";
|
||||
private static final String STREAM_INF_TAG = "#EXT-X-STREAM-INF";
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ import com.google.android.exoplayer.drm.DrmInitData.SchemeData;
|
||||
import com.google.android.exoplayer.extractor.mp4.PsshAtomUtil;
|
||||
import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.ProtectionElement;
|
||||
import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.StreamElement;
|
||||
import com.google.android.exoplayer.upstream.UriLoadable;
|
||||
import com.google.android.exoplayer.upstream.ParsingLoadable;
|
||||
import com.google.android.exoplayer.util.Assertions;
|
||||
import com.google.android.exoplayer.util.CodecSpecificDataUtil;
|
||||
import com.google.android.exoplayer.util.MimeTypes;
|
||||
@ -52,7 +52,8 @@ import java.util.UUID;
|
||||
* @see <a href="http://msdn.microsoft.com/en-us/library/ee673436(v=vs.90).aspx">
|
||||
* IIS Smooth Streaming Client Manifest Format</a>
|
||||
*/
|
||||
public class SmoothStreamingManifestParser implements UriLoadable.Parser<SmoothStreamingManifest> {
|
||||
public class SmoothStreamingManifestParser implements
|
||||
ParsingLoadable.Parser<SmoothStreamingManifest> {
|
||||
|
||||
private final XmlPullParserFactory xmlParserFactory;
|
||||
|
||||
|
@ -15,6 +15,8 @@
|
||||
*/
|
||||
package com.google.android.exoplayer.smoothstreaming;
|
||||
|
||||
import com.google.android.exoplayer.AdaptiveSourceEventListener;
|
||||
import com.google.android.exoplayer.AdaptiveSourceEventListener.EventDispatcher;
|
||||
import com.google.android.exoplayer.C;
|
||||
import com.google.android.exoplayer.DefaultLoadControl;
|
||||
import com.google.android.exoplayer.Format;
|
||||
@ -26,7 +28,6 @@ import com.google.android.exoplayer.TrackGroupArray;
|
||||
import com.google.android.exoplayer.TrackSelection;
|
||||
import com.google.android.exoplayer.TrackStream;
|
||||
import com.google.android.exoplayer.chunk.ChunkTrackStream;
|
||||
import com.google.android.exoplayer.chunk.ChunkTrackStreamEventListener;
|
||||
import com.google.android.exoplayer.chunk.FormatEvaluator;
|
||||
import com.google.android.exoplayer.chunk.FormatEvaluator.AdaptiveEvaluator;
|
||||
import com.google.android.exoplayer.extractor.mp4.TrackEncryptionBox;
|
||||
@ -37,7 +38,7 @@ import com.google.android.exoplayer.upstream.DataSource;
|
||||
import com.google.android.exoplayer.upstream.DataSourceFactory;
|
||||
import com.google.android.exoplayer.upstream.DefaultAllocator;
|
||||
import com.google.android.exoplayer.upstream.Loader;
|
||||
import com.google.android.exoplayer.upstream.UriLoadable;
|
||||
import com.google.android.exoplayer.upstream.ParsingLoadable;
|
||||
import com.google.android.exoplayer.util.Util;
|
||||
|
||||
import android.net.Uri;
|
||||
@ -53,7 +54,7 @@ import java.util.List;
|
||||
* A {@link SampleSource} for SmoothStreaming media.
|
||||
*/
|
||||
public final class SmoothStreamingSampleSource implements SampleSource,
|
||||
Loader.Callback<UriLoadable<SmoothStreamingManifest>> {
|
||||
Loader.Callback<ParsingLoadable<SmoothStreamingManifest>> {
|
||||
|
||||
/**
|
||||
* The minimum number of times to retry loading data prior to failing.
|
||||
@ -66,8 +67,7 @@ public final class SmoothStreamingSampleSource implements SampleSource,
|
||||
private final Uri manifestUri;
|
||||
private final DataSourceFactory dataSourceFactory;
|
||||
private final BandwidthMeter bandwidthMeter;
|
||||
private final Handler eventHandler;
|
||||
private final ChunkTrackStreamEventListener eventListener;
|
||||
private final EventDispatcher eventDispatcher;
|
||||
private final LoadControl loadControl;
|
||||
private final Loader manifestLoader;
|
||||
private final DataSource manifestDataSource;
|
||||
@ -88,13 +88,12 @@ public final class SmoothStreamingSampleSource implements SampleSource,
|
||||
|
||||
public SmoothStreamingSampleSource(Uri manifestUri, DataSourceFactory dataSourceFactory,
|
||||
BandwidthMeter bandwidthMeter, Handler eventHandler,
|
||||
ChunkTrackStreamEventListener eventListener) {
|
||||
AdaptiveSourceEventListener eventListener) {
|
||||
this.manifestUri = Util.toLowerInvariant(manifestUri.getLastPathSegment()).equals("manifest")
|
||||
? manifestUri : Uri.withAppendedPath(manifestUri, "Manifest");
|
||||
this.dataSourceFactory = dataSourceFactory;
|
||||
this.bandwidthMeter = bandwidthMeter;
|
||||
this.eventHandler = eventHandler;
|
||||
this.eventListener = eventListener;
|
||||
this.eventDispatcher = new EventDispatcher(eventHandler, eventListener);
|
||||
loadControl = new DefaultLoadControl(new DefaultAllocator(C.DEFAULT_BUFFER_SEGMENT_SIZE));
|
||||
trackStreams = newTrackStreamArray(0);
|
||||
manifestDataSource = dataSourceFactory.createDataSource();
|
||||
@ -216,9 +215,12 @@ public final class SmoothStreamingSampleSource implements SampleSource,
|
||||
// Loader.Callback implementation
|
||||
|
||||
@Override
|
||||
public void onLoadCompleted(UriLoadable<SmoothStreamingManifest> loadable, long elapsedMs) {
|
||||
public void onLoadCompleted(ParsingLoadable<SmoothStreamingManifest> loadable,
|
||||
long elapsedRealtimeMs, long loadDurationMs) {
|
||||
eventDispatcher.loadCompleted(loadable.dataSpec, loadable.type, elapsedRealtimeMs,
|
||||
loadDurationMs, loadable.bytesLoaded());
|
||||
manifest = loadable.getResult();
|
||||
manifestLoadStartTimestamp = SystemClock.elapsedRealtime() - elapsedMs;
|
||||
manifestLoadStartTimestamp = elapsedRealtimeMs - loadDurationMs;
|
||||
if (!prepared) {
|
||||
durationUs = manifest.durationUs;
|
||||
buildTrackGroups(manifest);
|
||||
@ -237,22 +239,28 @@ public final class SmoothStreamingSampleSource implements SampleSource,
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadCanceled(UriLoadable<SmoothStreamingManifest> loadable, long elapsedMs,
|
||||
boolean released) {
|
||||
// Do nothing.
|
||||
public void onLoadCanceled(ParsingLoadable<SmoothStreamingManifest> loadable,
|
||||
long elapsedRealtimeMs, long loadDurationMs, boolean released) {
|
||||
eventDispatcher.loadCompleted(loadable.dataSpec, loadable.type, elapsedRealtimeMs,
|
||||
loadDurationMs, loadable.bytesLoaded());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onLoadError(UriLoadable<SmoothStreamingManifest> loadable, long elapsedMs,
|
||||
IOException exception) {
|
||||
return (exception instanceof ParserException) ? Loader.DONT_RETRY_FATAL : Loader.RETRY;
|
||||
public int onLoadError(ParsingLoadable<SmoothStreamingManifest> loadable, long elapsedRealtimeMs,
|
||||
long loadDurationMs, IOException error) {
|
||||
boolean isFatal = error instanceof ParserException;
|
||||
eventDispatcher.loadError(loadable.dataSpec, loadable.type, elapsedRealtimeMs, loadDurationMs,
|
||||
loadable.bytesLoaded(), error, isFatal);
|
||||
return isFatal ? Loader.DONT_RETRY_FATAL : Loader.RETRY;
|
||||
}
|
||||
|
||||
// Internal methods
|
||||
|
||||
private void startLoadingManifest() {
|
||||
manifestLoader.startLoading(new UriLoadable<>(manifestUri, manifestDataSource, manifestParser),
|
||||
this, MIN_LOADABLE_RETRY_COUNT);
|
||||
ParsingLoadable<SmoothStreamingManifest> loadable = new ParsingLoadable<>(manifestDataSource,
|
||||
manifestUri, C.DATA_TYPE_MANIFEST, manifestParser);
|
||||
long elapsedRealtimeMs = manifestLoader.startLoading(loadable, this, MIN_LOADABLE_RETRY_COUNT);
|
||||
eventDispatcher.loadStarted(loadable.dataSpec, loadable.type, elapsedRealtimeMs);
|
||||
}
|
||||
|
||||
private void buildTrackGroups(SmoothStreamingManifest manifest) {
|
||||
@ -290,8 +298,8 @@ public final class SmoothStreamingSampleSource implements SampleSource,
|
||||
SmoothStreamingChunkSource chunkSource = new SmoothStreamingChunkSource(manifestLoader,
|
||||
manifest, streamElementIndex, trackGroups.get(selection.group), selectedTracks, dataSource,
|
||||
adaptiveEvaluator, trackEncryptionBoxes);
|
||||
return new ChunkTrackStream<>(chunkSource, loadControl, bufferSize, positionUs, eventHandler,
|
||||
eventListener, streamElementType, MIN_LOADABLE_RETRY_COUNT);
|
||||
return new ChunkTrackStream<>(streamElementType, chunkSource, loadControl, bufferSize,
|
||||
positionUs, MIN_LOADABLE_RETRY_COUNT, eventDispatcher);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
|
@ -33,6 +33,7 @@ public final class DataSourceInputStream extends InputStream {
|
||||
|
||||
private boolean opened = false;
|
||||
private boolean closed = false;
|
||||
private long totalBytesRead;
|
||||
|
||||
/**
|
||||
* @param dataSource The {@link DataSource} from which the data should be read.
|
||||
@ -44,6 +45,13 @@ public final class DataSourceInputStream extends InputStream {
|
||||
singleByteArray = new byte[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total number of bytes that have been read or skipped.
|
||||
*/
|
||||
public long bytesRead() {
|
||||
return totalBytesRead;
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional call to open the underlying {@link DataSource}.
|
||||
* <p>
|
||||
@ -63,12 +71,17 @@ public final class DataSourceInputStream extends InputStream {
|
||||
if (length == -1) {
|
||||
return -1;
|
||||
}
|
||||
totalBytesRead++;
|
||||
return singleByteArray[0] & 0xFF;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] buffer) throws IOException {
|
||||
return read(buffer, 0, buffer.length);
|
||||
int bytesRead = read(buffer, 0, buffer.length);
|
||||
if (bytesRead != -1) {
|
||||
totalBytesRead += bytesRead;
|
||||
}
|
||||
return bytesRead;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -76,14 +89,21 @@ public final class DataSourceInputStream extends InputStream {
|
||||
Assertions.checkState(!closed);
|
||||
checkOpened();
|
||||
int bytesRead = dataSource.read(buffer, offset, length);
|
||||
return bytesRead == C.RESULT_END_OF_INPUT ? -1 : bytesRead;
|
||||
if (bytesRead == C.RESULT_END_OF_INPUT) {
|
||||
return -1;
|
||||
} else {
|
||||
totalBytesRead += bytesRead;
|
||||
return bytesRead;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long skip(long byteCount) throws IOException {
|
||||
Assertions.checkState(!closed);
|
||||
checkOpened();
|
||||
return super.skip(byteCount);
|
||||
long bytesSkipped = super.skip(byteCount);
|
||||
totalBytesRead += bytesSkipped;
|
||||
return bytesSkipped;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -77,6 +77,18 @@ public final class Loader {
|
||||
*/
|
||||
public interface Callback<T extends Loadable> {
|
||||
|
||||
/**
|
||||
* Invoked when a load has completed.
|
||||
* <p>
|
||||
* There is guaranteed to exist a memory barrier between {@link Loadable#load()} exiting and
|
||||
* this callback being invoked.
|
||||
*
|
||||
* @param loadable The loadable whose load has completed.
|
||||
* @param elapsedRealtimeMs {@link SystemClock#elapsedRealtime} when the load ended.
|
||||
* @param loadDurationMs The duration of the load.
|
||||
*/
|
||||
void onLoadCompleted(T loadable, long elapsedRealtimeMs, long loadDurationMs);
|
||||
|
||||
/**
|
||||
* Invoked when a load has been canceled.
|
||||
* <p>
|
||||
@ -86,22 +98,12 @@ public final class Loader {
|
||||
* {@link Loadable#load()} exits.
|
||||
*
|
||||
* @param loadable The loadable whose load has been canceled.
|
||||
* @param elapsedMs The elapsed time in milliseconds since loading started.
|
||||
* @param elapsedRealtimeMs {@link SystemClock#elapsedRealtime} when the load was canceled.
|
||||
* @param loadDurationMs The duration of the load up to the point at which it was canceled.
|
||||
* @param released True if the load was canceled because the {@link Loader} was released. False
|
||||
* otherwise.
|
||||
*/
|
||||
void onLoadCanceled(T loadable, long elapsedMs, boolean released);
|
||||
|
||||
/**
|
||||
* Invoked when a load has completed.
|
||||
* <p>
|
||||
* There is guaranteed to exist a memory barrier between {@link Loadable#load()} exiting and
|
||||
* this callback being invoked.
|
||||
*
|
||||
* @param loadable The loadable whose load has completed.
|
||||
* @param elapsedMs The elapsed time in milliseconds since loading started.
|
||||
*/
|
||||
void onLoadCompleted(T loadable, long elapsedMs);
|
||||
void onLoadCanceled(T loadable, long elapsedRealtimeMs, long loadDurationMs, boolean released);
|
||||
|
||||
/**
|
||||
* Invoked when a load encounters an error.
|
||||
@ -110,13 +112,14 @@ public final class Loader {
|
||||
* this callback being invoked.
|
||||
*
|
||||
* @param loadable The loadable whose load has encountered an error.
|
||||
* @param elapsedMs The elapsed time in milliseconds since loading started.
|
||||
* @param exception The error.
|
||||
* @param elapsedRealtimeMs {@link SystemClock#elapsedRealtime} when the error occurred.
|
||||
* @param loadDurationMs The duration of the load up to the point at which the error occurred.
|
||||
* @param error The load error.
|
||||
* @return The desired retry action. One of {@link Loader#RETRY},
|
||||
* {@link Loader#RETRY_RESET_ERROR_COUNT}, {@link Loader#DONT_RETRY} and
|
||||
* {@link Loader#DONT_RETRY_FATAL}.
|
||||
*/
|
||||
int onLoadError(T loadable, long elapsedMs, IOException exception);
|
||||
int onLoadError(T loadable, long elapsedRealtimeMs, long loadDurationMs, IOException error);
|
||||
|
||||
}
|
||||
|
||||
@ -154,12 +157,15 @@ public final class Loader {
|
||||
* @param minRetryCount The minimum number of times the load must be retried before
|
||||
* {@link #maybeThrowError()} will propagate an error.
|
||||
* @throws IllegalStateException If the calling thread does not have an associated {@link Looper}.
|
||||
* @return {@link SystemClock#elapsedRealtime} when the load started.
|
||||
*/
|
||||
public <T extends Loadable> void startLoading(T loadable, Callback<T> callback,
|
||||
public <T extends Loadable> long startLoading(T loadable, Callback<T> callback,
|
||||
int minRetryCount) {
|
||||
Looper looper = Looper.myLooper();
|
||||
Assertions.checkState(looper != null);
|
||||
new LoadTask<>(looper, loadable, callback, minRetryCount).start(0);
|
||||
long startTimeMs = SystemClock.elapsedRealtime();
|
||||
new LoadTask<>(looper, loadable, callback, minRetryCount, startTimeMs).start(0);
|
||||
return startTimeMs;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -214,8 +220,8 @@ public final class Loader {
|
||||
|
||||
private final T loadable;
|
||||
private final Loader.Callback<T> callback;
|
||||
private final long startTimeMs;
|
||||
private final int minRetryCount;
|
||||
private final long startTimeMs;
|
||||
|
||||
private IOException currentError;
|
||||
private int errorCount;
|
||||
@ -223,12 +229,13 @@ public final class Loader {
|
||||
private volatile Thread executorThread;
|
||||
private volatile boolean released;
|
||||
|
||||
public LoadTask(Looper looper, T loadable, Loader.Callback<T> callback, int minRetryCount) {
|
||||
public LoadTask(Looper looper, T loadable, Loader.Callback<T> callback, int minRetryCount,
|
||||
long startTimeMs) {
|
||||
super(looper);
|
||||
this.loadable = loadable;
|
||||
this.callback = callback;
|
||||
this.minRetryCount = minRetryCount;
|
||||
this.startTimeMs = SystemClock.elapsedRealtime();
|
||||
this.startTimeMs = startTimeMs;
|
||||
}
|
||||
|
||||
public void maybeThrowError() throws IOException {
|
||||
@ -263,7 +270,8 @@ public final class Loader {
|
||||
}
|
||||
if (released) {
|
||||
finish();
|
||||
callback.onLoadCanceled(loadable, SystemClock.elapsedRealtime() - startTimeMs, true);
|
||||
long nowMs = SystemClock.elapsedRealtime();
|
||||
callback.onLoadCanceled(loadable, nowMs, nowMs - startTimeMs, true);
|
||||
}
|
||||
}
|
||||
|
||||
@ -323,21 +331,22 @@ public final class Loader {
|
||||
throw (Error) msg.obj;
|
||||
}
|
||||
finish();
|
||||
long elapsedMs = SystemClock.elapsedRealtime() - startTimeMs;
|
||||
long nowMs = SystemClock.elapsedRealtime();
|
||||
long durationMs = nowMs - startTimeMs;
|
||||
if (loadable.isLoadCanceled()) {
|
||||
callback.onLoadCanceled(loadable, elapsedMs, false);
|
||||
callback.onLoadCanceled(loadable, nowMs, durationMs, false);
|
||||
return;
|
||||
}
|
||||
switch (msg.what) {
|
||||
case MSG_CANCEL:
|
||||
callback.onLoadCanceled(loadable, elapsedMs, false);
|
||||
callback.onLoadCanceled(loadable, nowMs, durationMs, false);
|
||||
break;
|
||||
case MSG_END_OF_SOURCE:
|
||||
callback.onLoadCompleted(loadable, elapsedMs);
|
||||
callback.onLoadCompleted(loadable, nowMs, durationMs);
|
||||
break;
|
||||
case MSG_IO_EXCEPTION:
|
||||
currentError = (IOException) msg.obj;
|
||||
int retryAction = callback.onLoadError(loadable, elapsedMs, currentError);
|
||||
int retryAction = callback.onLoadError(loadable, nowMs, durationMs, currentError);
|
||||
if (retryAction == DONT_RETRY_FATAL) {
|
||||
fatalError = currentError;
|
||||
} else if (retryAction != DONT_RETRY) {
|
||||
|
@ -15,6 +15,7 @@
|
||||
*/
|
||||
package com.google.android.exoplayer.upstream;
|
||||
|
||||
import com.google.android.exoplayer.C;
|
||||
import com.google.android.exoplayer.ParserException;
|
||||
import com.google.android.exoplayer.upstream.Loader.Loadable;
|
||||
|
||||
@ -24,11 +25,11 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* A {@link Loadable} for loading an object from a URI.
|
||||
* A {@link Loadable} for objects that can be parsed from binary data using a {@link Parser}.
|
||||
*
|
||||
* @param <T> The type of the object being loaded.
|
||||
*/
|
||||
public final class UriLoadable<T> implements Loadable {
|
||||
public final class ParsingLoadable<T> implements Loadable {
|
||||
|
||||
/**
|
||||
* Parses an object from loaded data.
|
||||
@ -48,22 +49,34 @@ public final class UriLoadable<T> implements Loadable {
|
||||
|
||||
}
|
||||
|
||||
private final DataSpec dataSpec;
|
||||
/**
|
||||
* The {@link DataSpec} that defines the data to be loaded.
|
||||
*/
|
||||
public final DataSpec dataSpec;
|
||||
/**
|
||||
* The type of the data. One of the {@code DATA_TYPE_*} constants defined in {@link C}. For
|
||||
* reporting only.
|
||||
*/
|
||||
public final int type;
|
||||
|
||||
private final DataSource dataSource;
|
||||
private final Parser<T> parser;
|
||||
|
||||
private volatile T result;
|
||||
private volatile boolean isCanceled;
|
||||
private volatile long bytesLoaded;
|
||||
|
||||
/**
|
||||
* @param uri The {@link Uri} from which the object should be loaded.
|
||||
* @param dataSource A {@link DataSource} to use when loading the data.
|
||||
* @param uri The {@link Uri} from which the object should be loaded.
|
||||
* @param type See {@link #type}.
|
||||
* @param parser Parses the object from the response.
|
||||
*/
|
||||
public UriLoadable(Uri uri, DataSource dataSource, Parser<T> parser) {
|
||||
public ParsingLoadable(DataSource dataSource, Uri uri, int type, Parser<T> parser) {
|
||||
this.dataSource = dataSource;
|
||||
this.dataSpec = new DataSpec(uri, DataSpec.FLAG_ALLOW_GZIP);
|
||||
this.type = type;
|
||||
this.parser = parser;
|
||||
dataSpec = new DataSpec(uri, DataSpec.FLAG_ALLOW_GZIP);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -73,6 +86,18 @@ public final class UriLoadable<T> implements Loadable {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of bytes loaded.
|
||||
* <p>
|
||||
* In the case that the network response was compressed, the value returned is the size of the
|
||||
* data <em>after</em> decompression.
|
||||
*
|
||||
* @return The number of bytes loaded.
|
||||
*/
|
||||
public long bytesLoaded() {
|
||||
return bytesLoaded;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void cancelLoad() {
|
||||
// We don't actually cancel anything, but we need to record the cancellation so that
|
||||
@ -88,11 +113,12 @@ public final class UriLoadable<T> implements Loadable {
|
||||
@Override
|
||||
public final void load() throws IOException, InterruptedException {
|
||||
DataSourceInputStream inputStream = new DataSourceInputStream(dataSource, dataSpec);
|
||||
inputStream.open();
|
||||
try {
|
||||
inputStream.open();
|
||||
result = parser.parse(dataSource.getUri(), inputStream);
|
||||
} finally {
|
||||
inputStream.close();
|
||||
bytesLoaded = inputStream.bytesRead();
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
*
|
||||
|
Loading…
x
Reference in New Issue
Block a user