Automated g4 rollback of changelist 192816182.

*** Reason for rollback ***

Added the missing initialization to Timeline.EMPTY.

*** Original change description ***

Automated g4 rollback of changelist 192742299.

*** Reason for rollback ***

Culprit for b/78018932.

*** Original change description ***

Auto-register analytics collector in SimpleExoPlayer.

This automatically registers and deregisters an analytics collector in
SimpleExoPlayer. Doing this also allows to write integration tests checking
whether the reported window indices and media period ids are correct.

***

***

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=193006701
This commit is contained in:
tonihei 2018-04-16 02:23:22 -07:00 committed by Oliver Woodman
parent 752b90027c
commit 92dd708ef8
7 changed files with 1321 additions and 8 deletions

View File

@ -7,6 +7,8 @@
* Moved initial bitrate estimate from `AdaptiveTrackSelection` to * Moved initial bitrate estimate from `AdaptiveTrackSelection` to
`DefaultBandwidthMeter`. `DefaultBandwidthMeter`.
* Updated default max buffer length in `DefaultLoadControl`. * Updated default max buffer length in `DefaultLoadControl`.
* Added `AnalyticsListener` interface which can be registered in
`SimpleExoPlayer` to receive detailed meta data for each ExoPlayer event.
* UI components: * UI components:
* Add support for listening to `AspectRatioFrameLayout`'s aspect ratio update * Add support for listening to `AspectRatioFrameLayout`'s aspect ratio update
([#3736](https://github.com/google/ExoPlayer/issues/3736)). ([#3736](https://github.com/google/ExoPlayer/issues/3736)).

View File

@ -17,6 +17,7 @@ package com.google.android.exoplayer2;
import android.content.Context; import android.content.Context;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import com.google.android.exoplayer2.analytics.AnalyticsCollector;
import com.google.android.exoplayer2.drm.DrmSessionManager; import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.drm.FrameworkMediaCrypto; import com.google.android.exoplayer2.drm.FrameworkMediaCrypto;
import com.google.android.exoplayer2.trackselection.TrackSelector; import com.google.android.exoplayer2.trackselection.TrackSelector;
@ -175,6 +176,27 @@ public final class ExoPlayerFactory {
return new SimpleExoPlayer(renderersFactory, trackSelector, loadControl, drmSessionManager); return new SimpleExoPlayer(renderersFactory, trackSelector, loadControl, drmSessionManager);
} }
/**
* Creates a {@link SimpleExoPlayer} instance.
*
* @param renderersFactory A factory for creating {@link Renderer}s to be used by the instance.
* @param trackSelector The {@link TrackSelector} that will be used by the instance.
* @param loadControl The {@link LoadControl} that will be used by the instance.
* @param drmSessionManager An optional {@link DrmSessionManager}. May be null if the instance
* will not be used for DRM protected playbacks.
* @param analyticsCollectorFactory A factory for creating the {@link AnalyticsCollector} that
* will collect and forward all player events.
*/
public static SimpleExoPlayer newSimpleInstance(
RenderersFactory renderersFactory,
TrackSelector trackSelector,
LoadControl loadControl,
@Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
AnalyticsCollector.Factory analyticsCollectorFactory) {
return new SimpleExoPlayer(
renderersFactory, trackSelector, loadControl, drmSessionManager, analyticsCollectorFactory);
}
/** /**
* Creates an {@link ExoPlayer} instance. * Creates an {@link ExoPlayer} instance.
* *

View File

@ -27,9 +27,12 @@ import android.view.Surface;
import android.view.SurfaceHolder; import android.view.SurfaceHolder;
import android.view.SurfaceView; import android.view.SurfaceView;
import android.view.TextureView; import android.view.TextureView;
import com.google.android.exoplayer2.analytics.AnalyticsCollector;
import com.google.android.exoplayer2.analytics.AnalyticsListener;
import com.google.android.exoplayer2.audio.AudioAttributes; import com.google.android.exoplayer2.audio.AudioAttributes;
import com.google.android.exoplayer2.audio.AudioRendererEventListener; import com.google.android.exoplayer2.audio.AudioRendererEventListener;
import com.google.android.exoplayer2.decoder.DecoderCounters; import com.google.android.exoplayer2.decoder.DecoderCounters;
import com.google.android.exoplayer2.drm.DefaultDrmSessionManager;
import com.google.android.exoplayer2.drm.DrmSessionManager; import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.drm.FrameworkMediaCrypto; import com.google.android.exoplayer2.drm.FrameworkMediaCrypto;
import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.metadata.Metadata;
@ -63,6 +66,7 @@ public class SimpleExoPlayer implements ExoPlayer, Player.VideoComponent, Player
protected final Renderer[] renderers; protected final Renderer[] renderers;
private final ExoPlayer player; private final ExoPlayer player;
private final Handler eventHandler;
private final ComponentListener componentListener; private final ComponentListener componentListener;
private final CopyOnWriteArraySet<com.google.android.exoplayer2.video.VideoListener> private final CopyOnWriteArraySet<com.google.android.exoplayer2.video.VideoListener>
videoListeners; videoListeners;
@ -70,6 +74,7 @@ public class SimpleExoPlayer implements ExoPlayer, Player.VideoComponent, Player
private final CopyOnWriteArraySet<MetadataOutput> metadataOutputs; private final CopyOnWriteArraySet<MetadataOutput> metadataOutputs;
private final CopyOnWriteArraySet<VideoRendererEventListener> videoDebugListeners; private final CopyOnWriteArraySet<VideoRendererEventListener> videoDebugListeners;
private final CopyOnWriteArraySet<AudioRendererEventListener> audioDebugListeners; private final CopyOnWriteArraySet<AudioRendererEventListener> audioDebugListeners;
private final AnalyticsCollector analyticsCollector;
private Format videoFormat; private Format videoFormat;
private Format audioFormat; private Format audioFormat;
@ -85,6 +90,7 @@ public class SimpleExoPlayer implements ExoPlayer, Player.VideoComponent, Player
private int audioSessionId; private int audioSessionId;
private AudioAttributes audioAttributes; private AudioAttributes audioAttributes;
private float audioVolume; private float audioVolume;
private MediaSource mediaSource;
/** /**
* @param renderersFactory A factory for creating {@link Renderer}s to be used by the instance. * @param renderersFactory A factory for creating {@link Renderer}s to be used by the instance.
@ -98,7 +104,12 @@ public class SimpleExoPlayer implements ExoPlayer, Player.VideoComponent, Player
TrackSelector trackSelector, TrackSelector trackSelector,
LoadControl loadControl, LoadControl loadControl,
@Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager) { @Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager) {
this(renderersFactory, trackSelector, loadControl, drmSessionManager, Clock.DEFAULT); this(
renderersFactory,
trackSelector,
loadControl,
drmSessionManager,
new AnalyticsCollector.Factory());
} }
/** /**
@ -107,6 +118,32 @@ public class SimpleExoPlayer implements ExoPlayer, Player.VideoComponent, Player
* @param loadControl The {@link LoadControl} that will be used by the instance. * @param loadControl The {@link LoadControl} that will be used by the instance.
* @param drmSessionManager An optional {@link DrmSessionManager}. May be null if the instance * @param drmSessionManager An optional {@link DrmSessionManager}. May be null if the instance
* will not be used for DRM protected playbacks. * will not be used for DRM protected playbacks.
* @param analyticsCollectorFactory A factory for creating the {@link AnalyticsCollector} that
* will collect and forward all player events.
*/
protected SimpleExoPlayer(
RenderersFactory renderersFactory,
TrackSelector trackSelector,
LoadControl loadControl,
@Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
AnalyticsCollector.Factory analyticsCollectorFactory) {
this(
renderersFactory,
trackSelector,
loadControl,
drmSessionManager,
analyticsCollectorFactory,
Clock.DEFAULT);
}
/**
* @param renderersFactory A factory for creating {@link Renderer}s to be used by the instance.
* @param trackSelector The {@link TrackSelector} that will be used by the instance.
* @param loadControl The {@link LoadControl} that will be used by the instance.
* @param drmSessionManager An optional {@link DrmSessionManager}. May be null if the instance
* will not be used for DRM protected playbacks.
* @param analyticsCollectorFactory A factory for creating the {@link AnalyticsCollector} that
* will collect and forward all player events.
* @param clock The {@link Clock} that will be used by the instance. Should always be {@link * @param clock The {@link Clock} that will be used by the instance. Should always be {@link
* Clock#DEFAULT}, unless the player is being used from a test. * Clock#DEFAULT}, unless the player is being used from a test.
*/ */
@ -115,6 +152,7 @@ public class SimpleExoPlayer implements ExoPlayer, Player.VideoComponent, Player
TrackSelector trackSelector, TrackSelector trackSelector,
LoadControl loadControl, LoadControl loadControl,
@Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager, @Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
AnalyticsCollector.Factory analyticsCollectorFactory,
Clock clock) { Clock clock) {
componentListener = new ComponentListener(); componentListener = new ComponentListener();
videoListeners = new CopyOnWriteArraySet<>(); videoListeners = new CopyOnWriteArraySet<>();
@ -123,7 +161,7 @@ public class SimpleExoPlayer implements ExoPlayer, Player.VideoComponent, Player
videoDebugListeners = new CopyOnWriteArraySet<>(); videoDebugListeners = new CopyOnWriteArraySet<>();
audioDebugListeners = new CopyOnWriteArraySet<>(); audioDebugListeners = new CopyOnWriteArraySet<>();
Looper eventLooper = Looper.myLooper() != null ? Looper.myLooper() : Looper.getMainLooper(); Looper eventLooper = Looper.myLooper() != null ? Looper.myLooper() : Looper.getMainLooper();
Handler eventHandler = new Handler(eventLooper); eventHandler = new Handler(eventLooper);
renderers = renderers =
renderersFactory.createRenderers( renderersFactory.createRenderers(
eventHandler, eventHandler,
@ -141,6 +179,14 @@ public class SimpleExoPlayer implements ExoPlayer, Player.VideoComponent, Player
// Build the player and associated objects. // Build the player and associated objects.
player = createExoPlayerImpl(renderers, trackSelector, loadControl, clock); player = createExoPlayerImpl(renderers, trackSelector, loadControl, clock);
analyticsCollector = analyticsCollectorFactory.createAnalyticsCollector(player, clock);
addListener(analyticsCollector);
addVideoDebugListener(analyticsCollector);
addAudioDebugListener(analyticsCollector);
addMetadataOutput(analyticsCollector);
if (drmSessionManager instanceof DefaultDrmSessionManager) {
((DefaultDrmSessionManager) drmSessionManager).addListener(eventHandler, analyticsCollector);
}
} }
@Override @Override
@ -283,6 +329,29 @@ public class SimpleExoPlayer implements ExoPlayer, Player.VideoComponent, Player
return Util.getStreamTypeForAudioUsage(audioAttributes.usage); return Util.getStreamTypeForAudioUsage(audioAttributes.usage);
} }
/** Returns the {@link AnalyticsCollector} used for collecting analytics events. */
public AnalyticsCollector getAnalyticsCollector() {
return analyticsCollector;
}
/**
* Adds an {@link AnalyticsListener} to receive analytics events.
*
* @param listener The listener to be added.
*/
public void addAnalyticsListener(AnalyticsListener listener) {
analyticsCollector.addListener(listener);
}
/**
* Removes an {@link AnalyticsListener}.
*
* @param listener The listener to be removed.
*/
public void removeAnalyticsListener(AnalyticsListener listener) {
analyticsCollector.removeListener(listener);
}
/** /**
* Sets the attributes for audio playback, used by the underlying audio track. If not set, the * Sets the attributes for audio playback, used by the underlying audio track. If not set, the
* default audio attributes will be used. They are suitable for general media playback. * default audio attributes will be used. They are suitable for general media playback.
@ -586,11 +655,19 @@ public class SimpleExoPlayer implements ExoPlayer, Player.VideoComponent, Player
@Override @Override
public void prepare(MediaSource mediaSource) { public void prepare(MediaSource mediaSource) {
player.prepare(mediaSource); prepare(mediaSource, /* resetPosition= */ true, /* resetState= */ true);
} }
@Override @Override
public void prepare(MediaSource mediaSource, boolean resetPosition, boolean resetState) { public void prepare(MediaSource mediaSource, boolean resetPosition, boolean resetState) {
if (this.mediaSource != mediaSource) {
if (this.mediaSource != null) {
this.mediaSource.removeEventListener(analyticsCollector);
analyticsCollector.resetForNewMediaSource();
}
mediaSource.addEventListener(eventHandler, analyticsCollector);
this.mediaSource = mediaSource;
}
player.prepare(mediaSource, resetPosition, resetState); player.prepare(mediaSource, resetPosition, resetState);
} }
@ -631,21 +708,25 @@ public class SimpleExoPlayer implements ExoPlayer, Player.VideoComponent, Player
@Override @Override
public void seekToDefaultPosition() { public void seekToDefaultPosition() {
analyticsCollector.notifySeekStarted();
player.seekToDefaultPosition(); player.seekToDefaultPosition();
} }
@Override @Override
public void seekToDefaultPosition(int windowIndex) { public void seekToDefaultPosition(int windowIndex) {
analyticsCollector.notifySeekStarted();
player.seekToDefaultPosition(windowIndex); player.seekToDefaultPosition(windowIndex);
} }
@Override @Override
public void seekTo(long positionMs) { public void seekTo(long positionMs) {
analyticsCollector.notifySeekStarted();
player.seekTo(positionMs); player.seekTo(positionMs);
} }
@Override @Override
public void seekTo(int windowIndex, long positionMs) { public void seekTo(int windowIndex, long positionMs) {
analyticsCollector.notifySeekStarted();
player.seekTo(windowIndex, positionMs); player.seekTo(windowIndex, positionMs);
} }
@ -671,12 +752,17 @@ public class SimpleExoPlayer implements ExoPlayer, Player.VideoComponent, Player
@Override @Override
public void stop() { public void stop() {
player.stop(); stop(/* reset= */ false);
} }
@Override @Override
public void stop(boolean reset) { public void stop(boolean reset) {
player.stop(reset); player.stop(reset);
if (mediaSource != null) {
mediaSource.removeEventListener(analyticsCollector);
mediaSource = null;
analyticsCollector.resetForNewMediaSource();
}
} }
@Override @Override
@ -689,6 +775,9 @@ public class SimpleExoPlayer implements ExoPlayer, Player.VideoComponent, Player
} }
surface = null; surface = null;
} }
if (mediaSource != null) {
mediaSource.removeEventListener(analyticsCollector);
}
} }
@Override @Override

View File

@ -44,6 +44,7 @@ import com.google.android.exoplayer2.video.VideoRendererEventListener;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.CopyOnWriteArraySet;
@ -157,6 +158,22 @@ public class AnalyticsCollector
} }
} }
/**
* Resets the analytics collector for a new media source. Should be called before the player is
* prepared with a new media source.
*/
public final void resetForNewMediaSource() {
// Copying the list is needed because onMediaPeriodReleased will modify the list.
List<MediaPeriodId> activeMediaPeriods =
new ArrayList<>(mediaPeriodQueueTracker.activeMediaPeriods);
Timeline timeline = mediaPeriodQueueTracker.timeline;
for (MediaPeriodId mediaPeriod : activeMediaPeriods) {
int windowIndex =
timeline.isEmpty() ? 0 : timeline.getPeriod(mediaPeriod.periodIndex, period).windowIndex;
onMediaPeriodReleased(windowIndex, mediaPeriod);
}
}
// MetadataOutput implementation. // MetadataOutput implementation.
@Override @Override
@ -631,6 +648,9 @@ public class AnalyticsCollector
/** Keeps track of the active media periods and currently playing and reading media period. */ /** Keeps track of the active media periods and currently playing and reading media period. */
private static final class MediaPeriodQueueTracker { private static final class MediaPeriodQueueTracker {
// TODO: Investigate reporting MediaPeriodId in renderer events and adding a listener of queue
// changes, which would hopefully remove the need to track the queue here.
private final ArrayList<MediaPeriodId> activeMediaPeriods; private final ArrayList<MediaPeriodId> activeMediaPeriods;
private final Period period; private final Period period;
@ -642,6 +662,7 @@ public class AnalyticsCollector
public MediaPeriodQueueTracker() { public MediaPeriodQueueTracker() {
activeMediaPeriods = new ArrayList<>(); activeMediaPeriods = new ArrayList<>();
period = new Period(); period = new Period();
timeline = Timeline.EMPTY;
} }
/** /**
@ -763,13 +784,14 @@ public class AnalyticsCollector
} }
private void updateLastReportedPlayingMediaPeriod() { private void updateLastReportedPlayingMediaPeriod() {
lastReportedPlayingMediaPeriod = if (!activeMediaPeriods.isEmpty()) {
activeMediaPeriods.isEmpty() ? null : activeMediaPeriods.get(0); lastReportedPlayingMediaPeriod = activeMediaPeriods.get(0);
}
} }
private MediaPeriodId updateMediaPeriodIdToNewTimeline( private MediaPeriodId updateMediaPeriodIdToNewTimeline(
MediaPeriodId mediaPeriodId, Timeline newTimeline) { MediaPeriodId mediaPeriodId, Timeline newTimeline) {
if (newTimeline.isEmpty()) { if (newTimeline.isEmpty() || timeline.isEmpty()) {
return mediaPeriodId; return mediaPeriodId;
} }
Object uid = timeline.getPeriod(mediaPeriodId.periodIndex, period, /* setIds= */ true).uid; Object uid = timeline.getPeriod(mediaPeriodId.periodIndex, period, /* setIds= */ true).uid;

View File

@ -28,6 +28,8 @@ import com.google.android.exoplayer2.Renderer;
import com.google.android.exoplayer2.RenderersFactory; import com.google.android.exoplayer2.RenderersFactory;
import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.analytics.AnalyticsCollector;
import com.google.android.exoplayer2.analytics.AnalyticsListener;
import com.google.android.exoplayer2.audio.AudioRendererEventListener; import com.google.android.exoplayer2.audio.AudioRendererEventListener;
import com.google.android.exoplayer2.drm.DrmSessionManager; import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.drm.FrameworkMediaCrypto; import com.google.android.exoplayer2.drm.FrameworkMediaCrypto;
@ -86,6 +88,7 @@ public final class ExoPlayerTestRunner extends Player.DefaultEventListener
private Player.EventListener eventListener; private Player.EventListener eventListener;
private VideoRendererEventListener videoRendererEventListener; private VideoRendererEventListener videoRendererEventListener;
private AudioRendererEventListener audioRendererEventListener; private AudioRendererEventListener audioRendererEventListener;
private AnalyticsListener analyticsListener;
private Integer expectedPlayerEndedCount; private Integer expectedPlayerEndedCount;
/** /**
@ -261,6 +264,17 @@ public final class ExoPlayerTestRunner extends Player.DefaultEventListener
return this; return this;
} }
/**
* Sets an {@link AnalyticsListener} to be registered.
*
* @param analyticsListener An {@link AnalyticsListener} to be registered.
* @return This builder.
*/
public Builder setAnalyticsListener(AnalyticsListener analyticsListener) {
this.analyticsListener = analyticsListener;
return this;
}
/** /**
* Sets the number of times the test runner is expected to reach the {@link Player#STATE_ENDED} * Sets the number of times the test runner is expected to reach the {@link Player#STATE_ENDED}
* or {@link Player#STATE_IDLE}. The default is 1. This affects how long * or {@link Player#STATE_IDLE}. The default is 1. This affects how long
@ -330,6 +344,7 @@ public final class ExoPlayerTestRunner extends Player.DefaultEventListener
eventListener, eventListener,
videoRendererEventListener, videoRendererEventListener,
audioRendererEventListener, audioRendererEventListener,
analyticsListener,
expectedPlayerEndedCount); expectedPlayerEndedCount);
} }
} }
@ -343,6 +358,7 @@ public final class ExoPlayerTestRunner extends Player.DefaultEventListener
private final @Nullable Player.EventListener eventListener; private final @Nullable Player.EventListener eventListener;
private final @Nullable VideoRendererEventListener videoRendererEventListener; private final @Nullable VideoRendererEventListener videoRendererEventListener;
private final @Nullable AudioRendererEventListener audioRendererEventListener; private final @Nullable AudioRendererEventListener audioRendererEventListener;
private final @Nullable AnalyticsListener analyticsListener;
private final HandlerThread playerThread; private final HandlerThread playerThread;
private final HandlerWrapper handler; private final HandlerWrapper handler;
@ -369,6 +385,7 @@ public final class ExoPlayerTestRunner extends Player.DefaultEventListener
@Nullable Player.EventListener eventListener, @Nullable Player.EventListener eventListener,
@Nullable VideoRendererEventListener videoRendererEventListener, @Nullable VideoRendererEventListener videoRendererEventListener,
@Nullable AudioRendererEventListener audioRendererEventListener, @Nullable AudioRendererEventListener audioRendererEventListener,
@Nullable AnalyticsListener analyticsListener,
int expectedPlayerEndedCount) { int expectedPlayerEndedCount) {
this.clock = clock; this.clock = clock;
this.mediaSource = mediaSource; this.mediaSource = mediaSource;
@ -379,6 +396,7 @@ public final class ExoPlayerTestRunner extends Player.DefaultEventListener
this.eventListener = eventListener; this.eventListener = eventListener;
this.videoRendererEventListener = videoRendererEventListener; this.videoRendererEventListener = videoRendererEventListener;
this.audioRendererEventListener = audioRendererEventListener; this.audioRendererEventListener = audioRendererEventListener;
this.analyticsListener = analyticsListener;
this.timelines = new ArrayList<>(); this.timelines = new ArrayList<>();
this.manifests = new ArrayList<>(); this.manifests = new ArrayList<>();
this.timelineChangeReasons = new ArrayList<>(); this.timelineChangeReasons = new ArrayList<>();
@ -417,6 +435,9 @@ public final class ExoPlayerTestRunner extends Player.DefaultEventListener
if (audioRendererEventListener != null) { if (audioRendererEventListener != null) {
player.addAudioDebugListener(audioRendererEventListener); player.addAudioDebugListener(audioRendererEventListener);
} }
if (analyticsListener != null) {
player.addAnalyticsListener(analyticsListener);
}
player.setPlayWhenReady(true); player.setPlayWhenReady(true);
if (actionSchedule != null) { if (actionSchedule != null) {
actionSchedule.start( actionSchedule.start(
@ -636,7 +657,13 @@ public final class ExoPlayerTestRunner extends Player.DefaultEventListener
TrackSelector trackSelector, TrackSelector trackSelector,
LoadControl loadControl, LoadControl loadControl,
Clock clock) { Clock clock) {
super(renderersFactory, trackSelector, loadControl, /* drmSessionManager= */ null, clock); super(
renderersFactory,
trackSelector,
loadControl,
/* drmSessionManager= */ null,
new AnalyticsCollector.Factory(),
clock);
} }
} }
} }

View File

@ -85,6 +85,7 @@ public class FakeRenderer extends BaseRenderer {
if (result == C.RESULT_FORMAT_READ) { if (result == C.RESULT_FORMAT_READ) {
formatReadCount++; formatReadCount++;
assertThat(expectedFormats).contains(formatHolder.format); assertThat(expectedFormats).contains(formatHolder.format);
onFormatChanged(formatHolder.format);
} else if (result == C.RESULT_BUFFER_READ) { } else if (result == C.RESULT_BUFFER_READ) {
if (buffer.isEndOfStream()) { if (buffer.isEndOfStream()) {
isEnded = true; isEnded = true;
@ -92,6 +93,7 @@ public class FakeRenderer extends BaseRenderer {
} }
lastSamplePositionUs = buffer.timeUs; lastSamplePositionUs = buffer.timeUs;
sampleBufferReadCount++; sampleBufferReadCount++;
onBufferRead();
} else { } else {
Assertions.checkState(result == C.RESULT_NOTHING_READ); Assertions.checkState(result == C.RESULT_NOTHING_READ);
return; return;
@ -115,4 +117,9 @@ public class FakeRenderer extends BaseRenderer {
? (FORMAT_HANDLED | ADAPTIVE_SEAMLESS) : FORMAT_UNSUPPORTED_TYPE; ? (FORMAT_HANDLED | ADAPTIVE_SEAMLESS) : FORMAT_UNSUPPORTED_TYPE;
} }
/** Called when the renderer reads a new format. */
protected void onFormatChanged(Format format) {}
/** Called when the renderer read a sample from the buffer. */
protected void onBufferRead() {}
} }