Implement event reporting from HlsSampleSource.

Issue: #275
This commit is contained in:
Oliver Woodman 2015-04-13 19:03:04 +01:00
parent e21f7801b5
commit 0d69a2eae8
13 changed files with 180 additions and 50 deletions

View File

@ -217,7 +217,7 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
return new DashRendererBuilder(userAgent, contentUri.toString(), return new DashRendererBuilder(userAgent, contentUri.toString(),
new WidevineTestMediaDrmCallback(contentId), debugTextView, audioCapabilities); new WidevineTestMediaDrmCallback(contentId), debugTextView, audioCapabilities);
case DemoUtil.TYPE_HLS: case DemoUtil.TYPE_HLS:
return new HlsRendererBuilder(userAgent, contentUri.toString()); return new HlsRendererBuilder(userAgent, contentUri.toString(), debugTextView);
case DemoUtil.TYPE_MP4: case DemoUtil.TYPE_MP4:
return new ExtractorRendererBuilder(userAgent, contentUri, debugTextView, return new ExtractorRendererBuilder(userAgent, contentUri, debugTextView,
new Mp4Extractor()); new Mp4Extractor());

View File

@ -248,7 +248,7 @@ public class DashRendererBuilder implements RendererBuilder,
videoRenderer = new MediaCodecVideoTrackRenderer(videoSampleSource, drmSessionManager, true, videoRenderer = new MediaCodecVideoTrackRenderer(videoSampleSource, drmSessionManager, true,
MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000, null, mainHandler, player, 50); MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000, null, mainHandler, player, 50);
debugRenderer = debugTextView != null debugRenderer = debugTextView != null
? new DebugTrackRenderer(debugTextView, videoRenderer, videoSampleSource) : null; ? new DebugTrackRenderer(debugTextView, player, videoRenderer) : null;
} }
// Build the audio chunk sources. // Build the audio chunk sources.

View File

@ -18,7 +18,6 @@ package com.google.android.exoplayer.demo.player;
import com.google.android.exoplayer.ExoPlaybackException; import com.google.android.exoplayer.ExoPlaybackException;
import com.google.android.exoplayer.MediaCodecTrackRenderer; import com.google.android.exoplayer.MediaCodecTrackRenderer;
import com.google.android.exoplayer.TrackRenderer; import com.google.android.exoplayer.TrackRenderer;
import com.google.android.exoplayer.chunk.ChunkSampleSource;
import com.google.android.exoplayer.chunk.Format; import com.google.android.exoplayer.chunk.Format;
import android.widget.TextView; import android.widget.TextView;
@ -30,21 +29,17 @@ import android.widget.TextView;
/* package */ class DebugTrackRenderer extends TrackRenderer implements Runnable { /* package */ class DebugTrackRenderer extends TrackRenderer implements Runnable {
private final TextView textView; private final TextView textView;
private final DemoPlayer player;
private final MediaCodecTrackRenderer renderer; private final MediaCodecTrackRenderer renderer;
private final ChunkSampleSource videoSampleSource;
private volatile boolean pendingFailure; private volatile boolean pendingFailure;
private volatile long currentPositionUs; private volatile long currentPositionUs;
public DebugTrackRenderer(TextView textView, MediaCodecTrackRenderer renderer) { public DebugTrackRenderer(TextView textView, DemoPlayer player,
this(textView, renderer, null); MediaCodecTrackRenderer renderer) {
}
public DebugTrackRenderer(TextView textView, MediaCodecTrackRenderer renderer,
ChunkSampleSource videoSampleSource) {
this.textView = textView; this.textView = textView;
this.player = player;
this.renderer = renderer; this.renderer = renderer;
this.videoSampleSource = videoSampleSource;
} }
public void injectFailure() { public void injectFailure() {
@ -82,13 +77,13 @@ import android.widget.TextView;
} }
private String getRenderString() { private String getRenderString() {
return "ms(" + (currentPositionUs / 1000) + "), " + getQualityString() return getQualityString() + " " + renderer.codecCounters.getDebugString();
+ ", " + renderer.codecCounters.getDebugString();
} }
private String getQualityString() { private String getQualityString() {
Format format = videoSampleSource == null ? null : videoSampleSource.getFormat(); Format format = player.getVideoFormat();
return format == null ? "null" : "height(" + format.height + "), itag(" + format.id + ")"; return format == null ? "id:? br:? h:?"
: "id:" + format.id + " br:" + format.bitrate + " h:" + format.height;
} }
@Override @Override

View File

@ -55,8 +55,7 @@ public class DefaultRendererBuilder implements RendererBuilder {
// Build the debug renderer. // Build the debug renderer.
TrackRenderer debugRenderer = debugTextView != null TrackRenderer debugRenderer = debugTextView != null
? new DebugTrackRenderer(debugTextView, videoRenderer) ? new DebugTrackRenderer(debugTextView, player, videoRenderer) : null;
: null;
// Invoke the callback. // Invoke the callback.
TrackRenderer[] renderers = new TrackRenderer[DemoPlayer.RENDERER_COUNT]; TrackRenderer[] renderers = new TrackRenderer[DemoPlayer.RENDERER_COUNT];

View File

@ -28,6 +28,7 @@ import com.google.android.exoplayer.chunk.ChunkSampleSource;
import com.google.android.exoplayer.chunk.Format; import com.google.android.exoplayer.chunk.Format;
import com.google.android.exoplayer.chunk.MultiTrackChunkSource; import com.google.android.exoplayer.chunk.MultiTrackChunkSource;
import com.google.android.exoplayer.drm.StreamingDrmSessionManager; import com.google.android.exoplayer.drm.StreamingDrmSessionManager;
import com.google.android.exoplayer.hls.HlsSampleSource;
import com.google.android.exoplayer.metadata.MetadataTrackRenderer; import com.google.android.exoplayer.metadata.MetadataTrackRenderer;
import com.google.android.exoplayer.text.TextRenderer; import com.google.android.exoplayer.text.TextRenderer;
import com.google.android.exoplayer.upstream.DefaultBandwidthMeter; import com.google.android.exoplayer.upstream.DefaultBandwidthMeter;
@ -48,9 +49,10 @@ import java.util.concurrent.CopyOnWriteArrayList;
* SmoothStreaming and so on). * SmoothStreaming and so on).
*/ */
public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventListener, public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventListener,
DefaultBandwidthMeter.EventListener, MediaCodecVideoTrackRenderer.EventListener, HlsSampleSource.EventListener, DefaultBandwidthMeter.EventListener,
MediaCodecAudioTrackRenderer.EventListener, Ac3PassthroughAudioTrackRenderer.EventListener, MediaCodecVideoTrackRenderer.EventListener, MediaCodecAudioTrackRenderer.EventListener,
TextRenderer, StreamingDrmSessionManager.EventListener { Ac3PassthroughAudioTrackRenderer.EventListener, StreamingDrmSessionManager.EventListener,
TextRenderer {
/** /**
* Builds renderers for the player. * Builds renderers for the player.
@ -181,6 +183,7 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
private Surface surface; private Surface surface;
private InternalRendererBuilderCallback builderCallback; private InternalRendererBuilderCallback builderCallback;
private TrackRenderer videoRenderer; private TrackRenderer videoRenderer;
private Format videoFormat;
private int videoTrackToRestore; private int videoTrackToRestore;
private MultiTrackChunkSource[] multiTrackSources; private MultiTrackChunkSource[] multiTrackSources;
@ -268,6 +271,10 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
} }
} }
public Format getVideoFormat() {
return videoFormat;
}
public void setBackgrounded(boolean backgrounded) { public void setBackgrounded(boolean backgrounded) {
if (this.backgrounded == backgrounded) { if (this.backgrounded == backgrounded) {
return; return;
@ -291,6 +298,7 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
} }
rendererBuildingState = RENDERER_BUILDING_STATE_BUILDING; rendererBuildingState = RENDERER_BUILDING_STATE_BUILDING;
maybeReportPlayerState(); maybeReportPlayerState();
videoFormat = null;
builderCallback = new InternalRendererBuilderCallback(); builderCallback = new InternalRendererBuilderCallback();
rendererBuilder.buildRenderers(this, builderCallback); rendererBuilder.buildRenderers(this, builderCallback);
} }
@ -437,6 +445,7 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
return; return;
} }
if (sourceId == TYPE_VIDEO) { if (sourceId == TYPE_VIDEO) {
videoFormat = format;
infoListener.onVideoFormatEnabled(format, trigger, mediaTimeMs); infoListener.onVideoFormatEnabled(format, trigger, mediaTimeMs);
} else if (sourceId == TYPE_AUDIO) { } else if (sourceId == TYPE_AUDIO) {
infoListener.onAudioFormatEnabled(format, trigger, mediaTimeMs); infoListener.onAudioFormatEnabled(format, trigger, mediaTimeMs);

View File

@ -63,8 +63,7 @@ public class ExtractorRendererBuilder implements RendererBuilder {
// Build the debug renderer. // Build the debug renderer.
TrackRenderer debugRenderer = debugTextView != null TrackRenderer debugRenderer = debugTextView != null
? new DebugTrackRenderer(debugTextView, videoRenderer) ? new DebugTrackRenderer(debugTextView, player, videoRenderer) : null;
: null;
// Invoke the callback. // Invoke the callback.
TrackRenderer[] renderers = new TrackRenderer[DemoPlayer.RENDERER_COUNT]; TrackRenderer[] renderers = new TrackRenderer[DemoPlayer.RENDERER_COUNT];

View File

@ -35,6 +35,8 @@ import com.google.android.exoplayer.util.ManifestFetcher;
import com.google.android.exoplayer.util.ManifestFetcher.ManifestCallback; import com.google.android.exoplayer.util.ManifestFetcher.ManifestCallback;
import android.media.MediaCodec; import android.media.MediaCodec;
import android.os.Handler;
import android.widget.TextView;
import java.io.IOException; import java.io.IOException;
import java.util.Map; import java.util.Map;
@ -46,13 +48,15 @@ public class HlsRendererBuilder implements RendererBuilder, ManifestCallback<Hls
private final String userAgent; private final String userAgent;
private final String url; private final String url;
private final TextView debugTextView;
private DemoPlayer player; private DemoPlayer player;
private RendererBuilderCallback callback; private RendererBuilderCallback callback;
public HlsRendererBuilder(String userAgent, String url) { public HlsRendererBuilder(String userAgent, String url, TextView debugTextView) {
this.userAgent = userAgent; this.userAgent = userAgent;
this.url = url; this.url = url;
this.debugTextView = debugTextView;
} }
@Override @Override
@ -72,28 +76,35 @@ public class HlsRendererBuilder implements RendererBuilder, ManifestCallback<Hls
@Override @Override
public void onSingleManifest(HlsPlaylist manifest) { public void onSingleManifest(HlsPlaylist manifest) {
Handler mainHandler = player.getMainHandler();
DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter(); DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
DataSource dataSource = new UriDataSource(userAgent, bandwidthMeter); DataSource dataSource = new UriDataSource(userAgent, bandwidthMeter);
HlsChunkSource chunkSource = new HlsChunkSource(dataSource, url, manifest, bandwidthMeter, null, HlsChunkSource chunkSource = new HlsChunkSource(dataSource, url, manifest, bandwidthMeter, null,
HlsChunkSource.ADAPTIVE_MODE_SPLICE); HlsChunkSource.ADAPTIVE_MODE_SPLICE);
HlsSampleSource sampleSource = new HlsSampleSource(chunkSource, true, 3); HlsSampleSource sampleSource = new HlsSampleSource(chunkSource, true, 3, mainHandler, player,
DemoPlayer.TYPE_VIDEO);
MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(sampleSource, MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(sampleSource,
MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000, player.getMainHandler(), player, 50); MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000, mainHandler, player, 50);
MediaCodecAudioTrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer(sampleSource); MediaCodecAudioTrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer(sampleSource);
MetadataTrackRenderer<Map<String, Object>> id3Renderer = MetadataTrackRenderer<Map<String, Object>> id3Renderer =
new MetadataTrackRenderer<Map<String, Object>>(sampleSource, new Id3Parser(), new MetadataTrackRenderer<Map<String, Object>>(sampleSource, new Id3Parser(),
player.getId3MetadataRenderer(), player.getMainHandler().getLooper()); player.getId3MetadataRenderer(), mainHandler.getLooper());
Eia608TrackRenderer closedCaptionRenderer = new Eia608TrackRenderer(sampleSource, player, Eia608TrackRenderer closedCaptionRenderer = new Eia608TrackRenderer(sampleSource, player,
player.getMainHandler().getLooper()); mainHandler.getLooper());
// Build the debug renderer.
TrackRenderer debugRenderer = debugTextView != null
? new DebugTrackRenderer(debugTextView, player, videoRenderer) : null;
TrackRenderer[] renderers = new TrackRenderer[DemoPlayer.RENDERER_COUNT]; TrackRenderer[] renderers = new TrackRenderer[DemoPlayer.RENDERER_COUNT];
renderers[DemoPlayer.TYPE_VIDEO] = videoRenderer; renderers[DemoPlayer.TYPE_VIDEO] = videoRenderer;
renderers[DemoPlayer.TYPE_AUDIO] = audioRenderer; renderers[DemoPlayer.TYPE_AUDIO] = audioRenderer;
renderers[DemoPlayer.TYPE_TIMED_METADATA] = id3Renderer; renderers[DemoPlayer.TYPE_TIMED_METADATA] = id3Renderer;
renderers[DemoPlayer.TYPE_TEXT] = closedCaptionRenderer; renderers[DemoPlayer.TYPE_TEXT] = closedCaptionRenderer;
renderers[DemoPlayer.TYPE_DEBUG] = debugRenderer;
callback.onRenderers(null, null, renderers); callback.onRenderers(null, null, renderers);
} }

View File

@ -174,8 +174,7 @@ public class SmoothStreamingRendererBuilder implements RendererBuilder,
videoRenderer = new MediaCodecVideoTrackRenderer(videoSampleSource, drmSessionManager, true, videoRenderer = new MediaCodecVideoTrackRenderer(videoSampleSource, drmSessionManager, true,
MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000, null, mainHandler, player, 50); MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000, null, mainHandler, player, 50);
debugRenderer = debugTextView != null debugRenderer = debugTextView != null
? new DebugTrackRenderer(debugTextView, videoRenderer, videoSampleSource) ? new DebugTrackRenderer(debugTextView, player, videoRenderer) : null;
: null;
} }
// Build the audio renderer. // Build the audio renderer.

View File

@ -45,13 +45,13 @@ public final class CodecCounters {
public String getDebugString() { public String getDebugString() {
ensureUpdated(); ensureUpdated();
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
builder.append("cic(").append(codecInitCount).append(")"); builder.append("cic:").append(codecInitCount);
builder.append("crc(").append(codecReleaseCount).append(")"); builder.append(" crc:").append(codecReleaseCount);
builder.append("ofc(").append(outputFormatChangedCount).append(")"); builder.append(" ofc:").append(outputFormatChangedCount);
builder.append("obc(").append(outputBuffersChangedCount).append(")"); builder.append(" obc:").append(outputBuffersChangedCount);
builder.append("ren(").append(renderedOutputBufferCount).append(")"); builder.append(" ren:").append(renderedOutputBufferCount);
builder.append("sob(").append(skippedOutputBufferCount).append(")"); builder.append(" sob:").append(skippedOutputBufferCount);
builder.append("dob(").append(droppedOutputBufferCount).append(")"); builder.append(" dob:").append(droppedOutputBufferCount);
return builder.toString(); return builder.toString();
} }

View File

@ -124,6 +124,7 @@ public class ChunkSampleSource implements SampleSource, Loader.Callback {
* *
* @return The current downstream format. * @return The current downstream format.
*/ */
@Deprecated
public Format getFormat() { public Format getFormat() {
return downstreamFormat; return downstreamFormat;
} }

View File

@ -17,6 +17,7 @@ package com.google.android.exoplayer.hls;
import com.google.android.exoplayer.C; import com.google.android.exoplayer.C;
import com.google.android.exoplayer.MediaFormat; import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.chunk.BaseChunkSampleSourceEventListener;
import com.google.android.exoplayer.chunk.Chunk; import com.google.android.exoplayer.chunk.Chunk;
import com.google.android.exoplayer.chunk.DataChunk; import com.google.android.exoplayer.chunk.DataChunk;
import com.google.android.exoplayer.chunk.Format; import com.google.android.exoplayer.chunk.Format;
@ -55,6 +56,11 @@ import java.util.Locale;
*/ */
public class HlsChunkSource { public class HlsChunkSource {
/**
* Interface definition for a callback to be notified of {@link HlsChunkSource} events.
*/
public interface EventListener extends BaseChunkSampleSourceEventListener {}
/** /**
* Adaptive switching is disabled. * Adaptive switching is disabled.
* <p> * <p>
@ -349,6 +355,8 @@ public class HlsChunkSource {
} }
long endTimeUs = startTimeUs + (long) (segment.durationSecs * C.MICROS_PER_SECOND); long endTimeUs = startTimeUs + (long) (segment.durationSecs * C.MICROS_PER_SECOND);
boolean isLastChunk = !mediaPlaylist.live && chunkIndex == mediaPlaylist.segments.size() - 1; boolean isLastChunk = !mediaPlaylist.live && chunkIndex == mediaPlaylist.segments.size() - 1;
int trigger = Chunk.TRIGGER_UNSPECIFIED;
Format format = enabledFormats[formatIndex];
// Configure the extractor that will read the chunk. // Configure the extractor that will read the chunk.
HlsExtractorWrapper extractorWrapper; HlsExtractorWrapper extractorWrapper;
@ -356,13 +364,14 @@ public class HlsChunkSource {
Extractor extractor = chunkUri.getLastPathSegment().endsWith(AAC_FILE_EXTENSION) Extractor extractor = chunkUri.getLastPathSegment().endsWith(AAC_FILE_EXTENSION)
? new AdtsExtractor(startTimeUs) ? new AdtsExtractor(startTimeUs)
: new TsExtractor(startTimeUs); : new TsExtractor(startTimeUs);
extractorWrapper = new HlsExtractorWrapper(bufferPool, extractor, switchingVariantSpliced); extractorWrapper = new HlsExtractorWrapper(trigger, format, startTimeUs, bufferPool,
extractor, switchingVariantSpliced);
} else { } else {
extractorWrapper = previousTsChunk.extractorWrapper; extractorWrapper = previousTsChunk.extractorWrapper;
} }
return new TsChunk(dataSource, dataSpec, Chunk.TRIGGER_UNSPECIFIED, enabledFormats[formatIndex], return new TsChunk(dataSource, dataSpec, trigger, format, startTimeUs, endTimeUs,
startTimeUs, endTimeUs, chunkMediaSequence, isLastChunk, extractorWrapper, encryptionKey, chunkMediaSequence, isLastChunk, extractorWrapper, encryptionKey,
encryptionIv); encryptionIv);
} }

View File

@ -17,6 +17,7 @@ package com.google.android.exoplayer.hls;
import com.google.android.exoplayer.MediaFormat; import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.SampleHolder; import com.google.android.exoplayer.SampleHolder;
import com.google.android.exoplayer.chunk.Format;
import com.google.android.exoplayer.drm.DrmInitData; import com.google.android.exoplayer.drm.DrmInitData;
import com.google.android.exoplayer.extractor.DefaultTrackOutput; import com.google.android.exoplayer.extractor.DefaultTrackOutput;
import com.google.android.exoplayer.extractor.Extractor; import com.google.android.exoplayer.extractor.Extractor;
@ -36,6 +37,10 @@ import java.io.IOException;
*/ */
public final class HlsExtractorWrapper implements ExtractorOutput { public final class HlsExtractorWrapper implements ExtractorOutput {
public final int trigger;
public final Format format;
public final long startTimeUs;
private final BufferPool bufferPool; private final BufferPool bufferPool;
private final Extractor extractor; private final Extractor extractor;
private final SparseArray<DefaultTrackOutput> sampleQueues; private final SparseArray<DefaultTrackOutput> sampleQueues;
@ -47,7 +52,11 @@ public final class HlsExtractorWrapper implements ExtractorOutput {
private boolean prepared; private boolean prepared;
private boolean spliceConfigured; private boolean spliceConfigured;
public HlsExtractorWrapper(BufferPool bufferPool, Extractor extractor, boolean shouldSpliceIn) { public HlsExtractorWrapper(int trigger, Format format, long startTimeUs, BufferPool bufferPool,
Extractor extractor, boolean shouldSpliceIn) {
this.trigger = trigger;
this.format = format;
this.startTimeUs = startTimeUs;
this.bufferPool = bufferPool; this.bufferPool = bufferPool;
this.extractor = extractor; this.extractor = extractor;
this.shouldSpliceIn = shouldSpliceIn; this.shouldSpliceIn = shouldSpliceIn;
@ -98,14 +107,14 @@ public final class HlsExtractorWrapper implements ExtractorOutput {
} }
/** /**
* Gets the format of the specified track. * Gets the {@link MediaFormat} of the specified track.
* <p> * <p>
* This method must only be called after the extractor has been prepared. * This method must only be called after the extractor has been prepared.
* *
* @param track The track index. * @param track The track index.
* @return The corresponding format. * @return The corresponding format.
*/ */
public MediaFormat getFormat(int track) { public MediaFormat getMediaFormat(int track) {
return sampleQueues.valueAt(track).getFormat(); return sampleQueues.valueAt(track).getFormat();
} }

View File

@ -22,11 +22,14 @@ import com.google.android.exoplayer.SampleHolder;
import com.google.android.exoplayer.SampleSource; import com.google.android.exoplayer.SampleSource;
import com.google.android.exoplayer.TrackInfo; import com.google.android.exoplayer.TrackInfo;
import com.google.android.exoplayer.TrackRenderer; import com.google.android.exoplayer.TrackRenderer;
import com.google.android.exoplayer.chunk.BaseChunkSampleSourceEventListener;
import com.google.android.exoplayer.chunk.Chunk; import com.google.android.exoplayer.chunk.Chunk;
import com.google.android.exoplayer.chunk.Format;
import com.google.android.exoplayer.upstream.Loader; import com.google.android.exoplayer.upstream.Loader;
import com.google.android.exoplayer.upstream.Loader.Loadable; import com.google.android.exoplayer.upstream.Loader.Loadable;
import com.google.android.exoplayer.util.Assertions; import com.google.android.exoplayer.util.Assertions;
import android.os.Handler;
import android.os.SystemClock; import android.os.SystemClock;
import java.io.IOException; import java.io.IOException;
@ -37,6 +40,11 @@ import java.util.LinkedList;
*/ */
public class HlsSampleSource implements SampleSource, Loader.Callback { public class HlsSampleSource implements SampleSource, Loader.Callback {
/**
* Interface definition for a callback to be notified of {@link HlsSampleSource} events.
*/
public interface EventListener extends BaseChunkSampleSourceEventListener {}
/** /**
* The default minimum number of times to retry loading data prior to failing. * The default minimum number of times to retry loading data prior to failing.
*/ */
@ -49,6 +57,10 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
private final boolean frameAccurateSeeking; private final boolean frameAccurateSeeking;
private final int minLoadableRetryCount; private final int minLoadableRetryCount;
private final int eventSourceId;
private final Handler eventHandler;
private final EventListener eventListener;
private int remainingReleaseCount; private int remainingReleaseCount;
private boolean prepared; private boolean prepared;
private int trackCount; private int trackCount;
@ -57,6 +69,7 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
private boolean[] pendingDiscontinuities; private boolean[] pendingDiscontinuities;
private TrackInfo[] trackInfos; private TrackInfo[] trackInfos;
private MediaFormat[] downstreamMediaFormats; private MediaFormat[] downstreamMediaFormats;
private Format downstreamFormat;
private long downstreamPositionUs; private long downstreamPositionUs;
private long lastSeekPositionUs; private long lastSeekPositionUs;
@ -74,16 +87,26 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
public HlsSampleSource(HlsChunkSource chunkSource, boolean frameAccurateSeeking, public HlsSampleSource(HlsChunkSource chunkSource, boolean frameAccurateSeeking,
int downstreamRendererCount) { int downstreamRendererCount) {
this(chunkSource, frameAccurateSeeking, downstreamRendererCount, this(chunkSource, frameAccurateSeeking, downstreamRendererCount, null, null, 0);
DEFAULT_MIN_LOADABLE_RETRY_COUNT);
} }
public HlsSampleSource(HlsChunkSource chunkSource, boolean frameAccurateSeeking, public HlsSampleSource(HlsChunkSource chunkSource, boolean frameAccurateSeeking,
int downstreamRendererCount, int minLoadableRetryCount) { int downstreamRendererCount, Handler eventHandler, EventListener eventListener,
int eventSourceId) {
this(chunkSource, frameAccurateSeeking, downstreamRendererCount, eventHandler, eventListener,
eventSourceId, DEFAULT_MIN_LOADABLE_RETRY_COUNT);
}
public HlsSampleSource(HlsChunkSource chunkSource, boolean frameAccurateSeeking,
int downstreamRendererCount, Handler eventHandler, EventListener eventListener,
int eventSourceId, int minLoadableRetryCount) {
this.chunkSource = chunkSource; this.chunkSource = chunkSource;
this.frameAccurateSeeking = frameAccurateSeeking; this.frameAccurateSeeking = frameAccurateSeeking;
this.remainingReleaseCount = downstreamRendererCount; this.remainingReleaseCount = downstreamRendererCount;
this.minLoadableRetryCount = minLoadableRetryCount; this.minLoadableRetryCount = minLoadableRetryCount;
this.eventHandler = eventHandler;
this.eventListener = eventListener;
this.eventSourceId = eventSourceId;
this.pendingResetPositionUs = NO_RESET_PENDING; this.pendingResetPositionUs = NO_RESET_PENDING;
extractors = new LinkedList<HlsExtractorWrapper>(); extractors = new LinkedList<HlsExtractorWrapper>();
} }
@ -106,7 +129,7 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
downstreamMediaFormats = new MediaFormat[trackCount]; downstreamMediaFormats = new MediaFormat[trackCount];
trackInfos = new TrackInfo[trackCount]; trackInfos = new TrackInfo[trackCount];
for (int i = 0; i < trackCount; i++) { for (int i = 0; i < trackCount; i++) {
MediaFormat format = extractor.getFormat(i); MediaFormat format = extractor.getMediaFormat(i);
trackInfos[i] = new TrackInfo(format.mimeType, chunkSource.getDurationUs()); trackInfos[i] = new TrackInfo(format.mimeType, chunkSource.getDurationUs());
} }
prepared = true; prepared = true;
@ -137,6 +160,7 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
enabledTrackCount++; enabledTrackCount++;
trackEnabledStates[track] = true; trackEnabledStates[track] = true;
downstreamMediaFormats[track] = null; downstreamMediaFormats[track] = null;
downstreamFormat = null;
if (enabledTrackCount == 1) { if (enabledTrackCount == 1) {
seekToUs(positionUs); seekToUs(positionUs);
} }
@ -202,6 +226,13 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
} }
HlsExtractorWrapper extractor = getCurrentExtractor(); HlsExtractorWrapper extractor = getCurrentExtractor();
if (downstreamFormat == null || !downstreamFormat.equals(extractor.format)) {
// Notify a change in the downstream format.
notifyDownstreamFormatChanged(extractor.format, extractor.trigger, extractor.startTimeUs);
downstreamFormat = extractor.format;
}
if (extractors.size() > 1) { if (extractors.size() > 1) {
// If there's more than one extractor, attempt to configure a seamless splice from the // If there's more than one extractor, attempt to configure a seamless splice from the
// current one to the next one. // current one to the next one.
@ -220,7 +251,7 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
return NOTHING_READ; return NOTHING_READ;
} }
MediaFormat mediaFormat = extractor.getFormat(track); MediaFormat mediaFormat = extractor.getMediaFormat(track);
if (mediaFormat != null && !mediaFormat.equals(downstreamMediaFormats[track], true)) { if (mediaFormat != null && !mediaFormat.equals(downstreamMediaFormats[track], true)) {
chunkSource.getMaxVideoDimensions(mediaFormat); chunkSource.getMaxVideoDimensions(mediaFormat);
formatHolder.format = mediaFormat; formatHolder.format = mediaFormat;
@ -286,6 +317,7 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
@Override @Override
public void onLoadCompleted(Loadable loadable) { public void onLoadCompleted(Loadable loadable) {
chunkSource.onChunkLoadCompleted(currentLoadable); chunkSource.onChunkLoadCompleted(currentLoadable);
notifyLoadCompleted(currentLoadable.bytesLoaded());
if (isTsChunk(currentLoadable)) { if (isTsChunk(currentLoadable)) {
TsChunk tsChunk = (TsChunk) loadable; TsChunk tsChunk = (TsChunk) loadable;
loadingFinished = tsChunk.isLastChunk; loadingFinished = tsChunk.isLastChunk;
@ -298,6 +330,7 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
@Override @Override
public void onLoadCanceled(Loadable loadable) { public void onLoadCanceled(Loadable loadable) {
notifyLoadCanceled(currentLoadable.bytesLoaded());
if (enabledTrackCount > 0) { if (enabledTrackCount > 0) {
restartFrom(pendingResetPositionUs); restartFrom(pendingResetPositionUs);
} else { } else {
@ -315,6 +348,7 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
currentLoadableExceptionCount++; currentLoadableExceptionCount++;
currentLoadableExceptionTimestamp = SystemClock.elapsedRealtime(); currentLoadableExceptionTimestamp = SystemClock.elapsedRealtime();
} }
notifyLoadError(e);
maybeStartLoading(); maybeStartLoading();
} }
@ -418,13 +452,19 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
currentLoadable = nextLoadable; currentLoadable = nextLoadable;
if (isTsChunk(currentLoadable)) { if (isTsChunk(currentLoadable)) {
previousTsLoadable = (TsChunk) currentLoadable; TsChunk tsChunk = (TsChunk) currentLoadable;
if (isPendingReset()) { if (isPendingReset()) {
pendingResetPositionUs = NO_RESET_PENDING; pendingResetPositionUs = NO_RESET_PENDING;
} }
if (extractors.isEmpty() || extractors.getLast() != previousTsLoadable.extractorWrapper) { if (extractors.isEmpty() || extractors.getLast() != tsChunk.extractorWrapper) {
extractors.addLast(previousTsLoadable.extractorWrapper); extractors.addLast(tsChunk.extractorWrapper);
} }
notifyLoadStarted(tsChunk.dataSpec.length, tsChunk.type, tsChunk.trigger, tsChunk.format,
tsChunk.startTimeUs, tsChunk.endTimeUs);
previousTsLoadable = tsChunk;
} else {
notifyLoadStarted(currentLoadable.dataSpec.length, currentLoadable.type,
currentLoadable.trigger, currentLoadable.format, -1, -1);
} }
loader.startLoading(currentLoadable, this); loader.startLoading(currentLoadable, this);
} }
@ -445,4 +485,63 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
return (int) (timeUs / 1000); return (int) (timeUs / 1000);
} }
private void notifyLoadStarted(final long length, final int type, final int trigger,
final Format format, final long mediaStartTimeUs, final long mediaEndTimeUs) {
if (eventHandler != null && eventListener != null) {
eventHandler.post(new Runnable() {
@Override
public void run() {
eventListener.onLoadStarted(eventSourceId, length, type, trigger, format,
usToMs(mediaStartTimeUs), usToMs(mediaEndTimeUs));
}
});
}
}
private void notifyLoadCompleted(final long bytesLoaded) {
if (eventHandler != null && eventListener != null) {
eventHandler.post(new Runnable() {
@Override
public void run() {
eventListener.onLoadCompleted(eventSourceId, bytesLoaded);
}
});
}
}
private void notifyLoadCanceled(final long bytesLoaded) {
if (eventHandler != null && eventListener != null) {
eventHandler.post(new Runnable() {
@Override
public void run() {
eventListener.onLoadCanceled(eventSourceId, bytesLoaded);
}
});
}
}
private void notifyLoadError(final IOException e) {
if (eventHandler != null && eventListener != null) {
eventHandler.post(new Runnable() {
@Override
public void run() {
eventListener.onLoadError(eventSourceId, e);
}
});
}
}
private void notifyDownstreamFormatChanged(final Format format, final int trigger,
final long positionUs) {
if (eventHandler != null && eventListener != null) {
eventHandler.post(new Runnable() {
@Override
public void run() {
eventListener.onDownstreamFormatChanged(eventSourceId, format, trigger,
usToMs(positionUs));
}
});
}
}
} }