From 0d69a2eae8942fb93039c73686740130d115592c Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Mon, 13 Apr 2015 19:03:04 +0100 Subject: [PATCH] Implement event reporting from HlsSampleSource. Issue: #275 --- .../exoplayer/demo/PlayerActivity.java | 2 +- .../demo/player/DashRendererBuilder.java | 2 +- .../demo/player/DebugTrackRenderer.java | 21 ++-- .../demo/player/DefaultRendererBuilder.java | 3 +- .../exoplayer/demo/player/DemoPlayer.java | 15 ++- .../demo/player/ExtractorRendererBuilder.java | 3 +- .../demo/player/HlsRendererBuilder.java | 21 +++- .../SmoothStreamingRendererBuilder.java | 3 +- .../android/exoplayer/CodecCounters.java | 14 +-- .../exoplayer/chunk/ChunkSampleSource.java | 1 + .../android/exoplayer/hls/HlsChunkSource.java | 15 ++- .../exoplayer/hls/HlsExtractorWrapper.java | 15 ++- .../exoplayer/hls/HlsSampleSource.java | 115 ++++++++++++++++-- 13 files changed, 180 insertions(+), 50 deletions(-) diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/PlayerActivity.java b/demo/src/main/java/com/google/android/exoplayer/demo/PlayerActivity.java index 7a21790e76..481587dd40 100644 --- a/demo/src/main/java/com/google/android/exoplayer/demo/PlayerActivity.java +++ b/demo/src/main/java/com/google/android/exoplayer/demo/PlayerActivity.java @@ -217,7 +217,7 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback, return new DashRendererBuilder(userAgent, contentUri.toString(), new WidevineTestMediaDrmCallback(contentId), debugTextView, audioCapabilities); case DemoUtil.TYPE_HLS: - return new HlsRendererBuilder(userAgent, contentUri.toString()); + return new HlsRendererBuilder(userAgent, contentUri.toString(), debugTextView); case DemoUtil.TYPE_MP4: return new ExtractorRendererBuilder(userAgent, contentUri, debugTextView, new Mp4Extractor()); diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/player/DashRendererBuilder.java b/demo/src/main/java/com/google/android/exoplayer/demo/player/DashRendererBuilder.java index 54023fb8bb..c01929bc13 100644 --- a/demo/src/main/java/com/google/android/exoplayer/demo/player/DashRendererBuilder.java +++ b/demo/src/main/java/com/google/android/exoplayer/demo/player/DashRendererBuilder.java @@ -248,7 +248,7 @@ public class DashRendererBuilder implements RendererBuilder, videoRenderer = new MediaCodecVideoTrackRenderer(videoSampleSource, drmSessionManager, true, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000, null, mainHandler, player, 50); debugRenderer = debugTextView != null - ? new DebugTrackRenderer(debugTextView, videoRenderer, videoSampleSource) : null; + ? new DebugTrackRenderer(debugTextView, player, videoRenderer) : null; } // Build the audio chunk sources. diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/player/DebugTrackRenderer.java b/demo/src/main/java/com/google/android/exoplayer/demo/player/DebugTrackRenderer.java index c0b9d50417..fe76f22044 100644 --- a/demo/src/main/java/com/google/android/exoplayer/demo/player/DebugTrackRenderer.java +++ b/demo/src/main/java/com/google/android/exoplayer/demo/player/DebugTrackRenderer.java @@ -18,7 +18,6 @@ package com.google.android.exoplayer.demo.player; import com.google.android.exoplayer.ExoPlaybackException; import com.google.android.exoplayer.MediaCodecTrackRenderer; import com.google.android.exoplayer.TrackRenderer; -import com.google.android.exoplayer.chunk.ChunkSampleSource; import com.google.android.exoplayer.chunk.Format; import android.widget.TextView; @@ -30,21 +29,17 @@ import android.widget.TextView; /* package */ class DebugTrackRenderer extends TrackRenderer implements Runnable { private final TextView textView; + private final DemoPlayer player; private final MediaCodecTrackRenderer renderer; - private final ChunkSampleSource videoSampleSource; private volatile boolean pendingFailure; private volatile long currentPositionUs; - public DebugTrackRenderer(TextView textView, MediaCodecTrackRenderer renderer) { - this(textView, renderer, null); - } - - public DebugTrackRenderer(TextView textView, MediaCodecTrackRenderer renderer, - ChunkSampleSource videoSampleSource) { + public DebugTrackRenderer(TextView textView, DemoPlayer player, + MediaCodecTrackRenderer renderer) { this.textView = textView; + this.player = player; this.renderer = renderer; - this.videoSampleSource = videoSampleSource; } public void injectFailure() { @@ -82,13 +77,13 @@ import android.widget.TextView; } private String getRenderString() { - return "ms(" + (currentPositionUs / 1000) + "), " + getQualityString() - + ", " + renderer.codecCounters.getDebugString(); + return getQualityString() + " " + renderer.codecCounters.getDebugString(); } private String getQualityString() { - Format format = videoSampleSource == null ? null : videoSampleSource.getFormat(); - return format == null ? "null" : "height(" + format.height + "), itag(" + format.id + ")"; + Format format = player.getVideoFormat(); + return format == null ? "id:? br:? h:?" + : "id:" + format.id + " br:" + format.bitrate + " h:" + format.height; } @Override diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/player/DefaultRendererBuilder.java b/demo/src/main/java/com/google/android/exoplayer/demo/player/DefaultRendererBuilder.java index 4931d8ed61..6163d0ad8d 100644 --- a/demo/src/main/java/com/google/android/exoplayer/demo/player/DefaultRendererBuilder.java +++ b/demo/src/main/java/com/google/android/exoplayer/demo/player/DefaultRendererBuilder.java @@ -55,8 +55,7 @@ public class DefaultRendererBuilder implements RendererBuilder { // Build the debug renderer. TrackRenderer debugRenderer = debugTextView != null - ? new DebugTrackRenderer(debugTextView, videoRenderer) - : null; + ? new DebugTrackRenderer(debugTextView, player, videoRenderer) : null; // Invoke the callback. TrackRenderer[] renderers = new TrackRenderer[DemoPlayer.RENDERER_COUNT]; diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/player/DemoPlayer.java b/demo/src/main/java/com/google/android/exoplayer/demo/player/DemoPlayer.java index ed590a36de..0dc625344b 100644 --- a/demo/src/main/java/com/google/android/exoplayer/demo/player/DemoPlayer.java +++ b/demo/src/main/java/com/google/android/exoplayer/demo/player/DemoPlayer.java @@ -28,6 +28,7 @@ import com.google.android.exoplayer.chunk.ChunkSampleSource; import com.google.android.exoplayer.chunk.Format; import com.google.android.exoplayer.chunk.MultiTrackChunkSource; 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.text.TextRenderer; import com.google.android.exoplayer.upstream.DefaultBandwidthMeter; @@ -48,9 +49,10 @@ import java.util.concurrent.CopyOnWriteArrayList; * SmoothStreaming and so on). */ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventListener, - DefaultBandwidthMeter.EventListener, MediaCodecVideoTrackRenderer.EventListener, - MediaCodecAudioTrackRenderer.EventListener, Ac3PassthroughAudioTrackRenderer.EventListener, - TextRenderer, StreamingDrmSessionManager.EventListener { + HlsSampleSource.EventListener, DefaultBandwidthMeter.EventListener, + MediaCodecVideoTrackRenderer.EventListener, MediaCodecAudioTrackRenderer.EventListener, + Ac3PassthroughAudioTrackRenderer.EventListener, StreamingDrmSessionManager.EventListener, + TextRenderer { /** * Builds renderers for the player. @@ -181,6 +183,7 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi private Surface surface; private InternalRendererBuilderCallback builderCallback; private TrackRenderer videoRenderer; + private Format videoFormat; private int videoTrackToRestore; 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) { if (this.backgrounded == backgrounded) { return; @@ -291,6 +298,7 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi } rendererBuildingState = RENDERER_BUILDING_STATE_BUILDING; maybeReportPlayerState(); + videoFormat = null; builderCallback = new InternalRendererBuilderCallback(); rendererBuilder.buildRenderers(this, builderCallback); } @@ -437,6 +445,7 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi return; } if (sourceId == TYPE_VIDEO) { + videoFormat = format; infoListener.onVideoFormatEnabled(format, trigger, mediaTimeMs); } else if (sourceId == TYPE_AUDIO) { infoListener.onAudioFormatEnabled(format, trigger, mediaTimeMs); diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/player/ExtractorRendererBuilder.java b/demo/src/main/java/com/google/android/exoplayer/demo/player/ExtractorRendererBuilder.java index 88302ba064..94d49488f9 100644 --- a/demo/src/main/java/com/google/android/exoplayer/demo/player/ExtractorRendererBuilder.java +++ b/demo/src/main/java/com/google/android/exoplayer/demo/player/ExtractorRendererBuilder.java @@ -63,8 +63,7 @@ public class ExtractorRendererBuilder implements RendererBuilder { // Build the debug renderer. TrackRenderer debugRenderer = debugTextView != null - ? new DebugTrackRenderer(debugTextView, videoRenderer) - : null; + ? new DebugTrackRenderer(debugTextView, player, videoRenderer) : null; // Invoke the callback. TrackRenderer[] renderers = new TrackRenderer[DemoPlayer.RENDERER_COUNT]; diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/player/HlsRendererBuilder.java b/demo/src/main/java/com/google/android/exoplayer/demo/player/HlsRendererBuilder.java index 03afe7190b..2f87c47b37 100644 --- a/demo/src/main/java/com/google/android/exoplayer/demo/player/HlsRendererBuilder.java +++ b/demo/src/main/java/com/google/android/exoplayer/demo/player/HlsRendererBuilder.java @@ -35,6 +35,8 @@ import com.google.android.exoplayer.util.ManifestFetcher; import com.google.android.exoplayer.util.ManifestFetcher.ManifestCallback; import android.media.MediaCodec; +import android.os.Handler; +import android.widget.TextView; import java.io.IOException; import java.util.Map; @@ -46,13 +48,15 @@ public class HlsRendererBuilder implements RendererBuilder, ManifestCallback> id3Renderer = new MetadataTrackRenderer>(sampleSource, new Id3Parser(), - player.getId3MetadataRenderer(), player.getMainHandler().getLooper()); + player.getId3MetadataRenderer(), mainHandler.getLooper()); 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]; renderers[DemoPlayer.TYPE_VIDEO] = videoRenderer; renderers[DemoPlayer.TYPE_AUDIO] = audioRenderer; renderers[DemoPlayer.TYPE_TIMED_METADATA] = id3Renderer; renderers[DemoPlayer.TYPE_TEXT] = closedCaptionRenderer; + renderers[DemoPlayer.TYPE_DEBUG] = debugRenderer; callback.onRenderers(null, null, renderers); } diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/player/SmoothStreamingRendererBuilder.java b/demo/src/main/java/com/google/android/exoplayer/demo/player/SmoothStreamingRendererBuilder.java index 1e8ce37a54..8aa0ab6119 100644 --- a/demo/src/main/java/com/google/android/exoplayer/demo/player/SmoothStreamingRendererBuilder.java +++ b/demo/src/main/java/com/google/android/exoplayer/demo/player/SmoothStreamingRendererBuilder.java @@ -174,8 +174,7 @@ public class SmoothStreamingRendererBuilder implements RendererBuilder, videoRenderer = new MediaCodecVideoTrackRenderer(videoSampleSource, drmSessionManager, true, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000, null, mainHandler, player, 50); debugRenderer = debugTextView != null - ? new DebugTrackRenderer(debugTextView, videoRenderer, videoSampleSource) - : null; + ? new DebugTrackRenderer(debugTextView, player, videoRenderer) : null; } // Build the audio renderer. diff --git a/library/src/main/java/com/google/android/exoplayer/CodecCounters.java b/library/src/main/java/com/google/android/exoplayer/CodecCounters.java index 6b12b9c072..f70c4335f9 100644 --- a/library/src/main/java/com/google/android/exoplayer/CodecCounters.java +++ b/library/src/main/java/com/google/android/exoplayer/CodecCounters.java @@ -45,13 +45,13 @@ public final class CodecCounters { public String getDebugString() { ensureUpdated(); StringBuilder builder = new StringBuilder(); - builder.append("cic(").append(codecInitCount).append(")"); - builder.append("crc(").append(codecReleaseCount).append(")"); - builder.append("ofc(").append(outputFormatChangedCount).append(")"); - builder.append("obc(").append(outputBuffersChangedCount).append(")"); - builder.append("ren(").append(renderedOutputBufferCount).append(")"); - builder.append("sob(").append(skippedOutputBufferCount).append(")"); - builder.append("dob(").append(droppedOutputBufferCount).append(")"); + builder.append("cic:").append(codecInitCount); + builder.append(" crc:").append(codecReleaseCount); + builder.append(" ofc:").append(outputFormatChangedCount); + builder.append(" obc:").append(outputBuffersChangedCount); + builder.append(" ren:").append(renderedOutputBufferCount); + builder.append(" sob:").append(skippedOutputBufferCount); + builder.append(" dob:").append(droppedOutputBufferCount); return builder.toString(); } diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/ChunkSampleSource.java b/library/src/main/java/com/google/android/exoplayer/chunk/ChunkSampleSource.java index 654ccb49b2..b45cfe4280 100644 --- a/library/src/main/java/com/google/android/exoplayer/chunk/ChunkSampleSource.java +++ b/library/src/main/java/com/google/android/exoplayer/chunk/ChunkSampleSource.java @@ -124,6 +124,7 @@ public class ChunkSampleSource implements SampleSource, Loader.Callback { * * @return The current downstream format. */ + @Deprecated public Format getFormat() { return downstreamFormat; } diff --git a/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java b/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java index 9ec349e1d6..eed62a66c5 100644 --- a/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java +++ b/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java @@ -17,6 +17,7 @@ package com.google.android.exoplayer.hls; import com.google.android.exoplayer.C; 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.DataChunk; import com.google.android.exoplayer.chunk.Format; @@ -55,6 +56,11 @@ import java.util.Locale; */ public class HlsChunkSource { + /** + * Interface definition for a callback to be notified of {@link HlsChunkSource} events. + */ + public interface EventListener extends BaseChunkSampleSourceEventListener {} + /** * Adaptive switching is disabled. *

@@ -349,6 +355,8 @@ public class HlsChunkSource { } long endTimeUs = startTimeUs + (long) (segment.durationSecs * C.MICROS_PER_SECOND); 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. HlsExtractorWrapper extractorWrapper; @@ -356,13 +364,14 @@ public class HlsChunkSource { Extractor extractor = chunkUri.getLastPathSegment().endsWith(AAC_FILE_EXTENSION) ? new AdtsExtractor(startTimeUs) : new TsExtractor(startTimeUs); - extractorWrapper = new HlsExtractorWrapper(bufferPool, extractor, switchingVariantSpliced); + extractorWrapper = new HlsExtractorWrapper(trigger, format, startTimeUs, bufferPool, + extractor, switchingVariantSpliced); } else { extractorWrapper = previousTsChunk.extractorWrapper; } - return new TsChunk(dataSource, dataSpec, Chunk.TRIGGER_UNSPECIFIED, enabledFormats[formatIndex], - startTimeUs, endTimeUs, chunkMediaSequence, isLastChunk, extractorWrapper, encryptionKey, + return new TsChunk(dataSource, dataSpec, trigger, format, startTimeUs, endTimeUs, + chunkMediaSequence, isLastChunk, extractorWrapper, encryptionKey, encryptionIv); } diff --git a/library/src/main/java/com/google/android/exoplayer/hls/HlsExtractorWrapper.java b/library/src/main/java/com/google/android/exoplayer/hls/HlsExtractorWrapper.java index 61267d5c67..d1fd35b4ab 100644 --- a/library/src/main/java/com/google/android/exoplayer/hls/HlsExtractorWrapper.java +++ b/library/src/main/java/com/google/android/exoplayer/hls/HlsExtractorWrapper.java @@ -17,6 +17,7 @@ package com.google.android.exoplayer.hls; import com.google.android.exoplayer.MediaFormat; 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.extractor.DefaultTrackOutput; import com.google.android.exoplayer.extractor.Extractor; @@ -36,6 +37,10 @@ import java.io.IOException; */ public final class HlsExtractorWrapper implements ExtractorOutput { + public final int trigger; + public final Format format; + public final long startTimeUs; + private final BufferPool bufferPool; private final Extractor extractor; private final SparseArray sampleQueues; @@ -47,7 +52,11 @@ public final class HlsExtractorWrapper implements ExtractorOutput { private boolean prepared; 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.extractor = extractor; 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. *

* This method must only be called after the extractor has been prepared. * * @param track The track index. * @return The corresponding format. */ - public MediaFormat getFormat(int track) { + public MediaFormat getMediaFormat(int track) { return sampleQueues.valueAt(track).getFormat(); } diff --git a/library/src/main/java/com/google/android/exoplayer/hls/HlsSampleSource.java b/library/src/main/java/com/google/android/exoplayer/hls/HlsSampleSource.java index 324d00b402..57d09a69a3 100644 --- a/library/src/main/java/com/google/android/exoplayer/hls/HlsSampleSource.java +++ b/library/src/main/java/com/google/android/exoplayer/hls/HlsSampleSource.java @@ -22,11 +22,14 @@ import com.google.android.exoplayer.SampleHolder; import com.google.android.exoplayer.SampleSource; import com.google.android.exoplayer.TrackInfo; 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.Format; import com.google.android.exoplayer.upstream.Loader; import com.google.android.exoplayer.upstream.Loader.Loadable; import com.google.android.exoplayer.util.Assertions; +import android.os.Handler; import android.os.SystemClock; import java.io.IOException; @@ -37,6 +40,11 @@ import java.util.LinkedList; */ 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. */ @@ -49,6 +57,10 @@ public class HlsSampleSource implements SampleSource, Loader.Callback { private final boolean frameAccurateSeeking; private final int minLoadableRetryCount; + private final int eventSourceId; + private final Handler eventHandler; + private final EventListener eventListener; + private int remainingReleaseCount; private boolean prepared; private int trackCount; @@ -57,6 +69,7 @@ public class HlsSampleSource implements SampleSource, Loader.Callback { private boolean[] pendingDiscontinuities; private TrackInfo[] trackInfos; private MediaFormat[] downstreamMediaFormats; + private Format downstreamFormat; private long downstreamPositionUs; private long lastSeekPositionUs; @@ -74,16 +87,26 @@ public class HlsSampleSource implements SampleSource, Loader.Callback { public HlsSampleSource(HlsChunkSource chunkSource, boolean frameAccurateSeeking, int downstreamRendererCount) { - this(chunkSource, frameAccurateSeeking, downstreamRendererCount, - DEFAULT_MIN_LOADABLE_RETRY_COUNT); + this(chunkSource, frameAccurateSeeking, downstreamRendererCount, null, null, 0); } 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.frameAccurateSeeking = frameAccurateSeeking; this.remainingReleaseCount = downstreamRendererCount; this.minLoadableRetryCount = minLoadableRetryCount; + this.eventHandler = eventHandler; + this.eventListener = eventListener; + this.eventSourceId = eventSourceId; this.pendingResetPositionUs = NO_RESET_PENDING; extractors = new LinkedList(); } @@ -106,7 +129,7 @@ public class HlsSampleSource implements SampleSource, Loader.Callback { downstreamMediaFormats = new MediaFormat[trackCount]; trackInfos = new TrackInfo[trackCount]; 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()); } prepared = true; @@ -137,6 +160,7 @@ public class HlsSampleSource implements SampleSource, Loader.Callback { enabledTrackCount++; trackEnabledStates[track] = true; downstreamMediaFormats[track] = null; + downstreamFormat = null; if (enabledTrackCount == 1) { seekToUs(positionUs); } @@ -202,6 +226,13 @@ public class HlsSampleSource implements SampleSource, Loader.Callback { } 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 there's more than one extractor, attempt to configure a seamless splice from the // current one to the next one. @@ -220,7 +251,7 @@ public class HlsSampleSource implements SampleSource, Loader.Callback { return NOTHING_READ; } - MediaFormat mediaFormat = extractor.getFormat(track); + MediaFormat mediaFormat = extractor.getMediaFormat(track); if (mediaFormat != null && !mediaFormat.equals(downstreamMediaFormats[track], true)) { chunkSource.getMaxVideoDimensions(mediaFormat); formatHolder.format = mediaFormat; @@ -286,6 +317,7 @@ public class HlsSampleSource implements SampleSource, Loader.Callback { @Override public void onLoadCompleted(Loadable loadable) { chunkSource.onChunkLoadCompleted(currentLoadable); + notifyLoadCompleted(currentLoadable.bytesLoaded()); if (isTsChunk(currentLoadable)) { TsChunk tsChunk = (TsChunk) loadable; loadingFinished = tsChunk.isLastChunk; @@ -298,6 +330,7 @@ public class HlsSampleSource implements SampleSource, Loader.Callback { @Override public void onLoadCanceled(Loadable loadable) { + notifyLoadCanceled(currentLoadable.bytesLoaded()); if (enabledTrackCount > 0) { restartFrom(pendingResetPositionUs); } else { @@ -315,6 +348,7 @@ public class HlsSampleSource implements SampleSource, Loader.Callback { currentLoadableExceptionCount++; currentLoadableExceptionTimestamp = SystemClock.elapsedRealtime(); } + notifyLoadError(e); maybeStartLoading(); } @@ -418,13 +452,19 @@ public class HlsSampleSource implements SampleSource, Loader.Callback { currentLoadable = nextLoadable; if (isTsChunk(currentLoadable)) { - previousTsLoadable = (TsChunk) currentLoadable; + TsChunk tsChunk = (TsChunk) currentLoadable; if (isPendingReset()) { pendingResetPositionUs = NO_RESET_PENDING; } - if (extractors.isEmpty() || extractors.getLast() != previousTsLoadable.extractorWrapper) { - extractors.addLast(previousTsLoadable.extractorWrapper); + if (extractors.isEmpty() || extractors.getLast() != tsChunk.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); } @@ -445,4 +485,63 @@ public class HlsSampleSource implements SampleSource, Loader.Callback { 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)); + } + }); + } + } + }