mirror of
https://github.com/samsonjs/media.git
synced 2026-03-26 09:35:47 +00:00
Add onEvents callback.
This callback allows listeners to know when all simultanous changes have been handled and the values reported through callbacks are again completely consistent with value obtained from Player getter calls. PiperOrigin-RevId: 343476639
This commit is contained in:
parent
a6b53d24af
commit
bd631a6ce2
15 changed files with 1329 additions and 227 deletions
|
|
@ -26,6 +26,11 @@
|
|||
* Fix issue that could cause playback to freeze when selecting tracks, if
|
||||
extension audio renderers are being used
|
||||
([#8203](https://github.com/google/ExoPlayer/issues/8203)).
|
||||
* Add `onEvents` callback to `Player.EventListener` and
|
||||
`AnalyticsListener` to notify when all simultaneous state changes have
|
||||
been handled and the values reported through callbacks are again
|
||||
completely consistent with the values obtained from the `Player`
|
||||
getters.
|
||||
* Track selection:
|
||||
* Add option to specify multiple preferred audio or text languages.
|
||||
* Forward `Timeline` and `MediaPeriodId` to `TrackSelection.Factory`.
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ public final class CastPlayer extends BasePlayer {
|
|||
private final SeekResultCallback seekResultCallback;
|
||||
|
||||
// Listeners and notification.
|
||||
private final ListenerSet<Player.EventListener> listeners;
|
||||
private final ListenerSet<Player.EventListener, Player.Events> listeners;
|
||||
@Nullable private SessionAvailabilityListener sessionAvailabilityListener;
|
||||
|
||||
// Internal state.
|
||||
|
|
@ -135,7 +135,11 @@ public final class CastPlayer extends BasePlayer {
|
|||
period = new Timeline.Period();
|
||||
statusListener = new StatusListener();
|
||||
seekResultCallback = new SeekResultCallback();
|
||||
listeners = new ListenerSet<>();
|
||||
listeners =
|
||||
new ListenerSet<>(
|
||||
Looper.getMainLooper(),
|
||||
Player.Events::new,
|
||||
(listener, eventFlags) -> listener.onEvents(/* player= */ this, eventFlags));
|
||||
|
||||
playWhenReady = new StateHolder<>(false);
|
||||
repeatMode = new StateHolder<>(REPEAT_MODE_OFF);
|
||||
|
|
@ -445,9 +449,11 @@ public final class CastPlayer extends BasePlayer {
|
|||
pendingSeekCount++;
|
||||
pendingSeekWindowIndex = windowIndex;
|
||||
pendingSeekPositionMs = positionMs;
|
||||
listeners.queueEvent(listener -> listener.onPositionDiscontinuity(DISCONTINUITY_REASON_SEEK));
|
||||
listeners.queueEvent(
|
||||
Player.EVENT_POSITION_DISCONTINUITY,
|
||||
listener -> listener.onPositionDiscontinuity(DISCONTINUITY_REASON_SEEK));
|
||||
} else if (pendingSeekCount == 0) {
|
||||
listeners.queueEvent(EventListener::onSeekProcessed);
|
||||
listeners.queueEvent(/* eventFlag= */ C.INDEX_UNSET, EventListener::onSeekProcessed);
|
||||
}
|
||||
listeners.flushEvents();
|
||||
}
|
||||
|
|
@ -647,7 +653,8 @@ public final class CastPlayer extends BasePlayer {
|
|||
updatePlayerStateAndNotifyIfChanged(/* resultCallback= */ null);
|
||||
boolean isPlaying = playbackState == Player.STATE_READY && playWhenReady.value;
|
||||
if (wasPlaying != isPlaying) {
|
||||
listeners.queueEvent(listener -> listener.onIsPlayingChanged(isPlaying));
|
||||
listeners.queueEvent(
|
||||
Player.EVENT_IS_PLAYING_CHANGED, listener -> listener.onIsPlayingChanged(isPlaying));
|
||||
}
|
||||
updateRepeatModeAndNotifyIfChanged(/* resultCallback= */ null);
|
||||
updateTimelineAndNotifyIfChanged();
|
||||
|
|
@ -664,10 +671,12 @@ public final class CastPlayer extends BasePlayer {
|
|||
if (this.currentWindowIndex != currentWindowIndex && pendingSeekCount == 0) {
|
||||
this.currentWindowIndex = currentWindowIndex;
|
||||
listeners.queueEvent(
|
||||
Player.EVENT_POSITION_DISCONTINUITY,
|
||||
listener -> listener.onPositionDiscontinuity(DISCONTINUITY_REASON_PERIOD_TRANSITION));
|
||||
}
|
||||
if (updateTracksAndSelectionsAndNotifyIfChanged()) {
|
||||
listeners.queueEvent(
|
||||
Player.EVENT_TRACKS_CHANGED,
|
||||
listener -> listener.onTracksChanged(currentTrackGroups, currentTrackSelection));
|
||||
}
|
||||
listeners.flushEvents();
|
||||
|
|
@ -710,6 +719,7 @@ public final class CastPlayer extends BasePlayer {
|
|||
// TODO: Differentiate TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED and
|
||||
// TIMELINE_CHANGE_REASON_SOURCE_UPDATE [see internal: b/65152553].
|
||||
listeners.queueEvent(
|
||||
Player.EVENT_TIMELINE_CHANGED,
|
||||
listener ->
|
||||
listener.onTimelineChanged(
|
||||
currentTimeline, Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE));
|
||||
|
|
@ -831,7 +841,8 @@ public final class CastPlayer extends BasePlayer {
|
|||
private void setRepeatModeAndNotifyIfChanged(@Player.RepeatMode int repeatMode) {
|
||||
if (this.repeatMode.value != repeatMode) {
|
||||
this.repeatMode.value = repeatMode;
|
||||
listeners.queueEvent(listener -> listener.onRepeatModeChanged(repeatMode));
|
||||
listeners.queueEvent(
|
||||
Player.EVENT_REPEAT_MODE_CHANGED, listener -> listener.onRepeatModeChanged(repeatMode));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -846,15 +857,18 @@ public final class CastPlayer extends BasePlayer {
|
|||
this.playbackState = playbackState;
|
||||
this.playWhenReady.value = playWhenReady;
|
||||
listeners.queueEvent(
|
||||
listener -> {
|
||||
listener.onPlayerStateChanged(playWhenReady, playbackState);
|
||||
if (playbackStateChanged) {
|
||||
listener.onPlaybackStateChanged(playbackState);
|
||||
}
|
||||
if (playWhenReadyChanged) {
|
||||
listener.onPlayWhenReadyChanged(playWhenReady, playWhenReadyChangeReason);
|
||||
}
|
||||
});
|
||||
/* eventFlag= */ C.INDEX_UNSET,
|
||||
listener -> listener.onPlayerStateChanged(playWhenReady, playbackState));
|
||||
if (playbackStateChanged) {
|
||||
listeners.queueEvent(
|
||||
Player.EVENT_PLAYBACK_STATE_CHANGED,
|
||||
listener -> listener.onPlaybackStateChanged(playbackState));
|
||||
}
|
||||
if (playWhenReadyChanged) {
|
||||
listeners.queueEvent(
|
||||
Player.EVENT_PLAY_WHEN_READY_CHANGED,
|
||||
listener -> listener.onPlayWhenReadyChanged(playWhenReady, playWhenReadyChangeReason));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1072,7 +1086,7 @@ public final class CastPlayer extends BasePlayer {
|
|||
if (--pendingSeekCount == 0) {
|
||||
pendingSeekWindowIndex = C.INDEX_UNSET;
|
||||
pendingSeekPositionMs = C.TIME_UNSET;
|
||||
listeners.sendEvent(EventListener::onSeekProcessed);
|
||||
listeners.sendEvent(/* eventFlag= */ C.INDEX_UNSET, EventListener::onSeekProcessed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -454,7 +454,8 @@ public interface ExoPlayer extends Player {
|
|||
releaseTimeoutMs,
|
||||
pauseAtEndOfMediaItems,
|
||||
clock,
|
||||
looper);
|
||||
looper,
|
||||
/* wrappingPlayer= */ null);
|
||||
|
||||
player.experimentalSetForegroundModeTimeoutMs(setForegroundModeTimeoutMs);
|
||||
if (!throwWhenStuckBuffering) {
|
||||
|
|
|
|||
|
|
@ -260,6 +260,7 @@ public final class ExoPlayerFactory {
|
|||
ExoPlayer.DEFAULT_RELEASE_TIMEOUT_MS,
|
||||
/* pauseAtEndOfMediaItems= */ false,
|
||||
Clock.DEFAULT,
|
||||
applicationLooper);
|
||||
applicationLooper,
|
||||
/* wrappingPlayer= */ null);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ import java.util.concurrent.TimeoutException;
|
|||
private final ExoPlayerImplInternal.PlaybackInfoUpdateListener playbackInfoUpdateListener;
|
||||
private final ExoPlayerImplInternal internalPlayer;
|
||||
private final Handler internalPlayerHandler;
|
||||
private final ListenerSet<Player.EventListener> listeners;
|
||||
private final ListenerSet<Player.EventListener, Player.Events> listeners;
|
||||
private final Timeline.Period period;
|
||||
private final List<MediaSourceHolderSnapshot> mediaSourceHolderSnapshots;
|
||||
private final boolean useLazyPreparation;
|
||||
|
|
@ -121,6 +121,8 @@ import java.util.concurrent.TimeoutException;
|
|||
* @param clock The {@link Clock}.
|
||||
* @param applicationLooper The {@link Looper} that must be used for all calls to the player and
|
||||
* which is used to call listeners on.
|
||||
* @param wrappingPlayer The {@link Player} wrapping this one if applicable. This player instance
|
||||
* should be used for all externally visible callbacks.
|
||||
*/
|
||||
@SuppressLint("HandlerLeak")
|
||||
public ExoPlayerImpl(
|
||||
|
|
@ -136,7 +138,8 @@ import java.util.concurrent.TimeoutException;
|
|||
long releaseTimeoutMs,
|
||||
boolean pauseAtEndOfMediaItems,
|
||||
Clock clock,
|
||||
Looper applicationLooper) {
|
||||
Looper applicationLooper,
|
||||
@Nullable Player wrappingPlayer) {
|
||||
Log.i(TAG, "Init " + Integer.toHexString(System.identityHashCode(this)) + " ["
|
||||
+ ExoPlayerLibraryInfo.VERSION_SLASHY + "] [" + Util.DEVICE_DEBUG_INFO + "]");
|
||||
checkState(renderers.length > 0);
|
||||
|
|
@ -150,7 +153,12 @@ import java.util.concurrent.TimeoutException;
|
|||
this.pauseAtEndOfMediaItems = pauseAtEndOfMediaItems;
|
||||
this.applicationLooper = applicationLooper;
|
||||
repeatMode = Player.REPEAT_MODE_OFF;
|
||||
listeners = new ListenerSet<>();
|
||||
Player playerForListeners = wrappingPlayer != null ? wrappingPlayer : this;
|
||||
listeners =
|
||||
new ListenerSet<>(
|
||||
applicationLooper,
|
||||
Player.Events::new,
|
||||
(listener, eventFlags) -> listener.onEvents(playerForListeners, eventFlags));
|
||||
mediaSourceHolderSnapshots = new ArrayList<>();
|
||||
shuffleOrder = new ShuffleOrder.DefaultShuffleOrder(/* length= */ 0);
|
||||
emptyTrackSelectorResult =
|
||||
|
|
@ -166,7 +174,7 @@ import java.util.concurrent.TimeoutException;
|
|||
playbackInfoUpdateHandler.post(() -> handlePlaybackInfo(playbackInfoUpdate));
|
||||
playbackInfo = PlaybackInfo.createDummy(emptyTrackSelectorResult);
|
||||
if (analyticsCollector != null) {
|
||||
analyticsCollector.setPlayer(this);
|
||||
analyticsCollector.setPlayer(playerForListeners, applicationLooper);
|
||||
addListener(analyticsCollector);
|
||||
bandwidthMeter.addEventListener(new Handler(applicationLooper), analyticsCollector);
|
||||
}
|
||||
|
|
@ -565,7 +573,8 @@ import java.util.concurrent.TimeoutException;
|
|||
if (this.repeatMode != repeatMode) {
|
||||
this.repeatMode = repeatMode;
|
||||
internalPlayer.setRepeatMode(repeatMode);
|
||||
listeners.sendEvent(listener -> listener.onRepeatModeChanged(repeatMode));
|
||||
listeners.sendEvent(
|
||||
Player.EVENT_REPEAT_MODE_CHANGED, listener -> listener.onRepeatModeChanged(repeatMode));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -579,7 +588,9 @@ import java.util.concurrent.TimeoutException;
|
|||
if (this.shuffleModeEnabled != shuffleModeEnabled) {
|
||||
this.shuffleModeEnabled = shuffleModeEnabled;
|
||||
internalPlayer.setShuffleModeEnabled(shuffleModeEnabled);
|
||||
listeners.sendEvent(listener -> listener.onShuffleModeEnabledChanged(shuffleModeEnabled));
|
||||
listeners.sendEvent(
|
||||
Player.EVENT_SHUFFLE_MODE_ENABLED_CHANGED,
|
||||
listener -> listener.onShuffleModeEnabledChanged(shuffleModeEnabled));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -729,6 +740,7 @@ import java.util.concurrent.TimeoutException;
|
|||
+ ExoPlayerLibraryInfo.registeredModules() + "]");
|
||||
if (!internalPlayer.release()) {
|
||||
listeners.sendEvent(
|
||||
Player.EVENT_PLAYER_ERROR,
|
||||
listener ->
|
||||
listener.onPlayerError(
|
||||
ExoPlaybackException.createForTimeout(
|
||||
|
|
@ -974,10 +986,12 @@ import java.util.concurrent.TimeoutException;
|
|||
int mediaItemTransitionReason = mediaItemTransitionInfo.second;
|
||||
if (!previousPlaybackInfo.timeline.equals(newPlaybackInfo.timeline)) {
|
||||
listeners.queueEvent(
|
||||
Player.EVENT_TIMELINE_CHANGED,
|
||||
listener -> listener.onTimelineChanged(newPlaybackInfo.timeline, timelineChangeReason));
|
||||
}
|
||||
if (positionDiscontinuity) {
|
||||
listeners.queueEvent(
|
||||
Player.EVENT_POSITION_DISCONTINUITY,
|
||||
listener -> listener.onPositionDiscontinuity(positionDiscontinuityReason));
|
||||
}
|
||||
if (mediaItemTransitioned) {
|
||||
|
|
@ -991,39 +1005,49 @@ import java.util.concurrent.TimeoutException;
|
|||
mediaItem = null;
|
||||
}
|
||||
listeners.queueEvent(
|
||||
Player.EVENT_MEDIA_ITEM_TRANSITION,
|
||||
listener -> listener.onMediaItemTransition(mediaItem, mediaItemTransitionReason));
|
||||
}
|
||||
if (previousPlaybackInfo.playbackError != newPlaybackInfo.playbackError
|
||||
&& newPlaybackInfo.playbackError != null) {
|
||||
listeners.queueEvent(listener -> listener.onPlayerError(newPlaybackInfo.playbackError));
|
||||
listeners.queueEvent(
|
||||
Player.EVENT_PLAYER_ERROR,
|
||||
listener -> listener.onPlayerError(newPlaybackInfo.playbackError));
|
||||
}
|
||||
if (previousPlaybackInfo.trackSelectorResult != newPlaybackInfo.trackSelectorResult) {
|
||||
trackSelector.onSelectionActivated(newPlaybackInfo.trackSelectorResult.info);
|
||||
listeners.queueEvent(
|
||||
Player.EVENT_TRACKS_CHANGED,
|
||||
listener ->
|
||||
listener.onTracksChanged(
|
||||
newPlaybackInfo.trackGroups, newPlaybackInfo.trackSelectorResult.selections));
|
||||
}
|
||||
if (!previousPlaybackInfo.staticMetadata.equals(newPlaybackInfo.staticMetadata)) {
|
||||
listeners.queueEvent(
|
||||
Player.EVENT_STATIC_METADATA_CHANGED,
|
||||
listener -> listener.onStaticMetadataChanged(newPlaybackInfo.staticMetadata));
|
||||
}
|
||||
if (previousPlaybackInfo.isLoading != newPlaybackInfo.isLoading) {
|
||||
listeners.queueEvent(listener -> listener.onIsLoadingChanged(newPlaybackInfo.isLoading));
|
||||
listeners.queueEvent(
|
||||
Player.EVENT_IS_LOADING_CHANGED,
|
||||
listener -> listener.onIsLoadingChanged(newPlaybackInfo.isLoading));
|
||||
}
|
||||
if (previousPlaybackInfo.playbackState != newPlaybackInfo.playbackState
|
||||
|| previousPlaybackInfo.playWhenReady != newPlaybackInfo.playWhenReady) {
|
||||
listeners.queueEvent(
|
||||
/* eventFlag= */ C.INDEX_UNSET,
|
||||
listener ->
|
||||
listener.onPlayerStateChanged(
|
||||
newPlaybackInfo.playWhenReady, newPlaybackInfo.playbackState));
|
||||
}
|
||||
if (previousPlaybackInfo.playbackState != newPlaybackInfo.playbackState) {
|
||||
listeners.queueEvent(
|
||||
Player.EVENT_PLAYBACK_STATE_CHANGED,
|
||||
listener -> listener.onPlaybackStateChanged(newPlaybackInfo.playbackState));
|
||||
}
|
||||
if (previousPlaybackInfo.playWhenReady != newPlaybackInfo.playWhenReady) {
|
||||
listeners.queueEvent(
|
||||
Player.EVENT_PLAY_WHEN_READY_CHANGED,
|
||||
listener ->
|
||||
listener.onPlayWhenReadyChanged(
|
||||
newPlaybackInfo.playWhenReady, playWhenReadyChangeReason));
|
||||
|
|
@ -1031,28 +1055,34 @@ import java.util.concurrent.TimeoutException;
|
|||
if (previousPlaybackInfo.playbackSuppressionReason
|
||||
!= newPlaybackInfo.playbackSuppressionReason) {
|
||||
listeners.queueEvent(
|
||||
Player.EVENT_PLAYBACK_SUPPRESSION_REASON_CHANGED,
|
||||
listener ->
|
||||
listener.onPlaybackSuppressionReasonChanged(
|
||||
newPlaybackInfo.playbackSuppressionReason));
|
||||
}
|
||||
if (isPlaying(previousPlaybackInfo) != isPlaying(newPlaybackInfo)) {
|
||||
listeners.queueEvent(listener -> listener.onIsPlayingChanged(isPlaying(newPlaybackInfo)));
|
||||
listeners.queueEvent(
|
||||
Player.EVENT_IS_PLAYING_CHANGED,
|
||||
listener -> listener.onIsPlayingChanged(isPlaying(newPlaybackInfo)));
|
||||
}
|
||||
if (!previousPlaybackInfo.playbackParameters.equals(newPlaybackInfo.playbackParameters)) {
|
||||
listeners.queueEvent(
|
||||
Player.EVENT_PLAYBACK_PARAMETERS_CHANGED,
|
||||
listener -> listener.onPlaybackParametersChanged(newPlaybackInfo.playbackParameters));
|
||||
}
|
||||
if (seekProcessed) {
|
||||
listeners.queueEvent(EventListener::onSeekProcessed);
|
||||
listeners.queueEvent(/* eventFlag= */ C.INDEX_UNSET, EventListener::onSeekProcessed);
|
||||
}
|
||||
if (previousPlaybackInfo.offloadSchedulingEnabled != newPlaybackInfo.offloadSchedulingEnabled) {
|
||||
listeners.queueEvent(
|
||||
/* eventFlag= */ C.INDEX_UNSET,
|
||||
listener ->
|
||||
listener.onExperimentalOffloadSchedulingEnabledChanged(
|
||||
newPlaybackInfo.offloadSchedulingEnabled));
|
||||
}
|
||||
if (previousPlaybackInfo.sleepingForOffload != newPlaybackInfo.sleepingForOffload) {
|
||||
listeners.queueEvent(
|
||||
/* eventFlag= */ C.INDEX_UNSET,
|
||||
listener ->
|
||||
listener.onExperimentalSleepingForOffloadChanged(newPlaybackInfo.sleepingForOffload));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ import com.google.android.exoplayer2.text.Cue;
|
|||
import com.google.android.exoplayer2.text.TextOutput;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelector;
|
||||
import com.google.android.exoplayer2.util.MutableFlags;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
import com.google.android.exoplayer2.video.VideoDecoderOutputBufferRenderer;
|
||||
import com.google.android.exoplayer2.video.VideoFrameMetadataListener;
|
||||
|
|
@ -421,8 +422,13 @@ public interface Player {
|
|||
}
|
||||
|
||||
/**
|
||||
* Listener of changes in player state. All methods have no-op default implementations to allow
|
||||
* selective overrides.
|
||||
* Listener of changes in player state.
|
||||
*
|
||||
* <p>All methods have no-op default implementations to allow selective overrides.
|
||||
*
|
||||
* <p>Listeners can choose to implement individual events (e.g. {@link
|
||||
* #onIsPlayingChanged(boolean)}) or {@link #onEvents(Player, Events)}, which is called after one
|
||||
* or more events occurred together.
|
||||
*/
|
||||
interface EventListener {
|
||||
|
||||
|
|
@ -621,12 +627,41 @@ public interface Player {
|
|||
* <p>This method is experimental, and will be renamed or removed in a future release.
|
||||
*/
|
||||
default void onExperimentalOffloadSchedulingEnabledChanged(boolean offloadSchedulingEnabled) {}
|
||||
|
||||
/**
|
||||
* Called when the player has started or finished sleeping for offload.
|
||||
*
|
||||
* <p>This method is experimental, and will be renamed or removed in a future release.
|
||||
*/
|
||||
default void onExperimentalSleepingForOffloadChanged(boolean sleepingForOffload) {}
|
||||
|
||||
/**
|
||||
* Called when one or more player states changed.
|
||||
*
|
||||
* <p>State changes and events that happen within one {@link Looper} message queue iteration are
|
||||
* reported together and only after all individual callbacks were triggered.
|
||||
*
|
||||
* <p>Listeners should prefer this method over individual callbacks in the following cases:
|
||||
*
|
||||
* <ul>
|
||||
* <li>They intend to use multiple state values together (e.g. using {@link
|
||||
* #getCurrentWindowIndex()} to query in {@link #getCurrentTimeline()}).
|
||||
* <li>The same logic should be triggered for multiple events (e.g. when updating a UI for
|
||||
* both {@link #onPlaybackStateChanged(int)} and {@link #onPlayWhenReadyChanged(boolean,
|
||||
* int)}).
|
||||
* <li>They need access to the {@link Player} object to trigger further events (e.g. to call
|
||||
* {@link Player#seekTo(long)} after a {@link #onMediaItemTransition(MediaItem, int)}).
|
||||
* <li>They are interested in events that logically happened together (e.g {@link
|
||||
* #onPlaybackStateChanged(int)} to {@link #STATE_BUFFERING} because of {@link
|
||||
* #onMediaItemTransition(MediaItem, int)}).
|
||||
* </ul>
|
||||
*
|
||||
* @param player The {@link Player} whose state changed. Use the getters to obtain the latest
|
||||
* states.
|
||||
* @param events The {@link Events} that happened in this iteration, indicating which player
|
||||
* states changed.
|
||||
*/
|
||||
default void onEvents(Player player, Events events) {}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -663,6 +698,37 @@ public interface Player {
|
|||
}
|
||||
}
|
||||
|
||||
/** A set of {@link EventFlags}. */
|
||||
final class Events extends MutableFlags {
|
||||
/**
|
||||
* Returns whether the given event occurred.
|
||||
*
|
||||
* @param event The {@link EventFlags event}.
|
||||
* @return Whether the event occurred.
|
||||
*/
|
||||
@Override
|
||||
public boolean contains(@EventFlags int event) {
|
||||
// Overridden to add IntDef compiler enforcement and new JavaDoc.
|
||||
return super.contains(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link EventFlags event} at the given index.
|
||||
*
|
||||
* <p>Although index-based access is possible, it doesn't imply a particular order of these
|
||||
* events.
|
||||
*
|
||||
* @param index The index. Must be between 0 (inclusive) and {@link #size()} (exclusive).
|
||||
* @return The {@link EventFlags event} at the given index.
|
||||
*/
|
||||
@Override
|
||||
@EventFlags
|
||||
public int get(int index) {
|
||||
// Overridden to add IntDef compiler enforcement and new JavaDoc.
|
||||
return super.get(index);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Playback state. One of {@link #STATE_IDLE}, {@link #STATE_BUFFERING}, {@link #STATE_READY} or
|
||||
* {@link #STATE_ENDED}.
|
||||
|
|
@ -802,7 +868,11 @@ public interface Player {
|
|||
/** Timeline changed as a result of a dynamic update introduced by the played media. */
|
||||
int TIMELINE_CHANGE_REASON_SOURCE_UPDATE = 1;
|
||||
|
||||
/** Reasons for media item transitions. */
|
||||
/**
|
||||
* Reasons for media item transitions. One of {@link #MEDIA_ITEM_TRANSITION_REASON_REPEAT}, {@link
|
||||
* #MEDIA_ITEM_TRANSITION_REASON_AUTO}, {@link #MEDIA_ITEM_TRANSITION_REASON_SEEK} or {@link
|
||||
* #MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED}.
|
||||
*/
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef({
|
||||
|
|
@ -825,6 +895,59 @@ public interface Player {
|
|||
*/
|
||||
int MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED = 3;
|
||||
|
||||
/**
|
||||
* Events that can be reported via {@link EventListener#onEvents(Player, Events)}.
|
||||
*
|
||||
* <p>One of the {@link Player}{@code .EVENT_*} flags.
|
||||
*/
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef({
|
||||
EVENT_TIMELINE_CHANGED,
|
||||
EVENT_MEDIA_ITEM_TRANSITION,
|
||||
EVENT_TRACKS_CHANGED,
|
||||
EVENT_STATIC_METADATA_CHANGED,
|
||||
EVENT_IS_LOADING_CHANGED,
|
||||
EVENT_PLAYBACK_STATE_CHANGED,
|
||||
EVENT_PLAY_WHEN_READY_CHANGED,
|
||||
EVENT_PLAYBACK_SUPPRESSION_REASON_CHANGED,
|
||||
EVENT_IS_PLAYING_CHANGED,
|
||||
EVENT_REPEAT_MODE_CHANGED,
|
||||
EVENT_SHUFFLE_MODE_ENABLED_CHANGED,
|
||||
EVENT_PLAYER_ERROR,
|
||||
EVENT_POSITION_DISCONTINUITY,
|
||||
EVENT_PLAYBACK_PARAMETERS_CHANGED
|
||||
})
|
||||
@interface EventFlags {}
|
||||
/** {@link #getCurrentTimeline()} changed. */
|
||||
int EVENT_TIMELINE_CHANGED = 0;
|
||||
/** {@link #getCurrentMediaItem()} changed or the player started repeating the current item. */
|
||||
int EVENT_MEDIA_ITEM_TRANSITION = 1;
|
||||
/** {@link #getCurrentTrackGroups()} or {@link #getCurrentTrackSelections()} changed. */
|
||||
int EVENT_TRACKS_CHANGED = 2;
|
||||
/** {@link #getCurrentStaticMetadata()} changed. */
|
||||
int EVENT_STATIC_METADATA_CHANGED = 3;
|
||||
/** {@link #isLoading()} ()} changed. */
|
||||
int EVENT_IS_LOADING_CHANGED = 4;
|
||||
/** {@link #getPlaybackState()} changed. */
|
||||
int EVENT_PLAYBACK_STATE_CHANGED = 5;
|
||||
/** {@link #getPlayWhenReady()} changed. */
|
||||
int EVENT_PLAY_WHEN_READY_CHANGED = 6;
|
||||
/** {@link #getPlaybackSuppressionReason()} changed. */
|
||||
int EVENT_PLAYBACK_SUPPRESSION_REASON_CHANGED = 7;
|
||||
/** {@link #isPlaying()} changed. */
|
||||
int EVENT_IS_PLAYING_CHANGED = 8;
|
||||
/** {@link #getRepeatMode()} changed. */
|
||||
int EVENT_REPEAT_MODE_CHANGED = 9;
|
||||
/** {@link #getShuffleModeEnabled()} changed. */
|
||||
int EVENT_SHUFFLE_MODE_ENABLED_CHANGED = 10;
|
||||
/** {@link #getPlayerError()} changed. */
|
||||
int EVENT_PLAYER_ERROR = 11;
|
||||
/** A position discontinuity occurred. See {@link EventListener#onPositionDiscontinuity(int)}. */
|
||||
int EVENT_POSITION_DISCONTINUITY = 12;
|
||||
/** {@link #getPlaybackParameters()} changed. */
|
||||
int EVENT_PLAYBACK_PARAMETERS_CHANGED = 13;
|
||||
|
||||
/** Returns the component of this player for audio output, or null if audio is not supported. */
|
||||
@Nullable
|
||||
AudioComponent getAudioComponent();
|
||||
|
|
|
|||
|
|
@ -701,7 +701,8 @@ public class SimpleExoPlayer extends BasePlayer
|
|||
builder.releaseTimeoutMs,
|
||||
builder.pauseAtEndOfMediaItems,
|
||||
builder.clock,
|
||||
builder.looper);
|
||||
builder.looper,
|
||||
/* wrappingPlayer= */ this);
|
||||
player.addListener(componentListener);
|
||||
videoDebugListeners.add(analyticsCollector);
|
||||
videoListeners.add(analyticsCollector);
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ package com.google.android.exoplayer2.analytics;
|
|||
|
||||
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
|
||||
|
||||
import android.os.Looper;
|
||||
import android.view.Surface;
|
||||
import androidx.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.C;
|
||||
|
|
@ -48,6 +49,7 @@ import com.google.android.exoplayer2.upstream.BandwidthMeter;
|
|||
import com.google.android.exoplayer2.util.Assertions;
|
||||
import com.google.android.exoplayer2.util.Clock;
|
||||
import com.google.android.exoplayer2.util.ListenerSet;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
import com.google.android.exoplayer2.video.VideoListener;
|
||||
import com.google.android.exoplayer2.video.VideoRendererEventListener;
|
||||
import com.google.common.base.Objects;
|
||||
|
|
@ -74,12 +76,12 @@ public class AnalyticsCollector
|
|||
VideoListener,
|
||||
AudioListener {
|
||||
|
||||
private final ListenerSet<AnalyticsListener> listeners;
|
||||
private final Clock clock;
|
||||
private final Period period;
|
||||
private final Window window;
|
||||
private final MediaPeriodQueueTracker mediaPeriodQueueTracker;
|
||||
|
||||
private ListenerSet<AnalyticsListener, AnalyticsListener.Events> listeners;
|
||||
private @MonotonicNonNull Player player;
|
||||
private boolean isSeeking;
|
||||
|
||||
|
|
@ -90,7 +92,11 @@ public class AnalyticsCollector
|
|||
*/
|
||||
public AnalyticsCollector(Clock clock) {
|
||||
this.clock = checkNotNull(clock);
|
||||
listeners = new ListenerSet<>();
|
||||
listeners =
|
||||
new ListenerSet<>(
|
||||
Util.getCurrentOrMainLooper(),
|
||||
AnalyticsListener.Events::new,
|
||||
(listener, eventFlags) -> {});
|
||||
period = new Period();
|
||||
window = new Window();
|
||||
mediaPeriodQueueTracker = new MediaPeriodQueueTracker(period);
|
||||
|
|
@ -120,11 +126,14 @@ public class AnalyticsCollector
|
|||
* yet or the current player is idle.
|
||||
*
|
||||
* @param player The {@link Player} for which data will be collected.
|
||||
* @param looper The {@link Looper} used for listener callbacks.
|
||||
*/
|
||||
public void setPlayer(Player player) {
|
||||
public void setPlayer(Player player, Looper looper) {
|
||||
Assertions.checkState(
|
||||
this.player == null || mediaPeriodQueueTracker.mediaPeriodQueue.isEmpty());
|
||||
this.player = checkNotNull(player);
|
||||
listeners =
|
||||
listeners.copy(looper, (listener, eventFlags) -> listener.onEvents(player, eventFlags));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -151,7 +160,8 @@ public class AnalyticsCollector
|
|||
if (!isSeeking) {
|
||||
EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime();
|
||||
isSeeking = true;
|
||||
listeners.sendEvent(listener -> listener.onSeekStarted(eventTime));
|
||||
listeners.sendEvent(
|
||||
/* eventFlag= */ C.INDEX_UNSET, listener -> listener.onSeekStarted(eventTime));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -165,7 +175,8 @@ public class AnalyticsCollector
|
|||
@Override
|
||||
public final void onMetadata(Metadata metadata) {
|
||||
EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime();
|
||||
listeners.sendEvent(listener -> listener.onMetadata(eventTime, metadata));
|
||||
listeners.sendEvent(
|
||||
AnalyticsListener.EVENT_METADATA, listener -> listener.onMetadata(eventTime, metadata));
|
||||
}
|
||||
|
||||
// AudioRendererEventListener implementation.
|
||||
|
|
@ -175,6 +186,7 @@ public class AnalyticsCollector
|
|||
public final void onAudioEnabled(DecoderCounters counters) {
|
||||
EventTime eventTime = generateReadingMediaPeriodEventTime();
|
||||
listeners.sendEvent(
|
||||
AnalyticsListener.EVENT_AUDIO_ENABLED,
|
||||
listener -> {
|
||||
listener.onAudioEnabled(eventTime, counters);
|
||||
listener.onDecoderEnabled(eventTime, C.TRACK_TYPE_AUDIO, counters);
|
||||
|
|
@ -187,6 +199,7 @@ public class AnalyticsCollector
|
|||
String decoderName, long initializedTimestampMs, long initializationDurationMs) {
|
||||
EventTime eventTime = generateReadingMediaPeriodEventTime();
|
||||
listeners.sendEvent(
|
||||
AnalyticsListener.EVENT_AUDIO_DECODER_INITIALIZED,
|
||||
listener -> {
|
||||
listener.onAudioDecoderInitialized(eventTime, decoderName, initializationDurationMs);
|
||||
listener.onDecoderInitialized(
|
||||
|
|
@ -200,6 +213,7 @@ public class AnalyticsCollector
|
|||
Format format, @Nullable DecoderReuseEvaluation decoderReuseEvaluation) {
|
||||
EventTime eventTime = generateReadingMediaPeriodEventTime();
|
||||
listeners.sendEvent(
|
||||
AnalyticsListener.EVENT_AUDIO_INPUT_FORMAT_CHANGED,
|
||||
listener -> {
|
||||
listener.onAudioInputFormatChanged(eventTime, format, decoderReuseEvaluation);
|
||||
listener.onDecoderInputFormatChanged(eventTime, C.TRACK_TYPE_AUDIO, format);
|
||||
|
|
@ -210,6 +224,7 @@ public class AnalyticsCollector
|
|||
public final void onAudioPositionAdvancing(long playoutStartSystemTimeMs) {
|
||||
EventTime eventTime = generateReadingMediaPeriodEventTime();
|
||||
listeners.sendEvent(
|
||||
AnalyticsListener.EVENT_AUDIO_POSITION_ADVANCING,
|
||||
listener -> listener.onAudioPositionAdvancing(eventTime, playoutStartSystemTimeMs));
|
||||
}
|
||||
|
||||
|
|
@ -218,6 +233,7 @@ public class AnalyticsCollector
|
|||
int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) {
|
||||
EventTime eventTime = generateReadingMediaPeriodEventTime();
|
||||
listeners.sendEvent(
|
||||
AnalyticsListener.EVENT_AUDIO_UNDERRUN,
|
||||
listener ->
|
||||
listener.onAudioUnderrun(eventTime, bufferSize, bufferSizeMs, elapsedSinceLastFeedMs));
|
||||
}
|
||||
|
|
@ -225,7 +241,9 @@ public class AnalyticsCollector
|
|||
@Override
|
||||
public final void onAudioDecoderReleased(String decoderName) {
|
||||
EventTime eventTime = generateReadingMediaPeriodEventTime();
|
||||
listeners.sendEvent(listener -> listener.onAudioDecoderReleased(eventTime, decoderName));
|
||||
listeners.sendEvent(
|
||||
AnalyticsListener.EVENT_AUDIO_DECODER_RELEASED,
|
||||
listener -> listener.onAudioDecoderReleased(eventTime, decoderName));
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
|
|
@ -233,6 +251,7 @@ public class AnalyticsCollector
|
|||
public final void onAudioDisabled(DecoderCounters counters) {
|
||||
EventTime eventTime = generatePlayingMediaPeriodEventTime();
|
||||
listeners.sendEvent(
|
||||
AnalyticsListener.EVENT_AUDIO_DISABLED,
|
||||
listener -> {
|
||||
listener.onAudioDisabled(eventTime, counters);
|
||||
listener.onDecoderDisabled(eventTime, C.TRACK_TYPE_AUDIO, counters);
|
||||
|
|
@ -244,32 +263,41 @@ public class AnalyticsCollector
|
|||
@Override
|
||||
public final void onAudioSessionId(int audioSessionId) {
|
||||
EventTime eventTime = generateReadingMediaPeriodEventTime();
|
||||
listeners.sendEvent(listener -> listener.onAudioSessionId(eventTime, audioSessionId));
|
||||
listeners.sendEvent(
|
||||
AnalyticsListener.EVENT_AUDIO_SESSION_ID,
|
||||
listener -> listener.onAudioSessionId(eventTime, audioSessionId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAudioAttributesChanged(AudioAttributes audioAttributes) {
|
||||
EventTime eventTime = generateReadingMediaPeriodEventTime();
|
||||
listeners.sendEvent(listener -> listener.onAudioAttributesChanged(eventTime, audioAttributes));
|
||||
listeners.sendEvent(
|
||||
AnalyticsListener.EVENT_AUDIO_ATTRIBUTES_CHANGED,
|
||||
listener -> listener.onAudioAttributesChanged(eventTime, audioAttributes));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSkipSilenceEnabledChanged(boolean skipSilenceEnabled) {
|
||||
EventTime eventTime = generateReadingMediaPeriodEventTime();
|
||||
listeners.sendEvent(
|
||||
AnalyticsListener.EVENT_SKIP_SILENCE_ENABLED_CHANGED,
|
||||
listener -> listener.onSkipSilenceEnabledChanged(eventTime, skipSilenceEnabled));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAudioSinkError(Exception audioSinkError) {
|
||||
EventTime eventTime = generateReadingMediaPeriodEventTime();
|
||||
listeners.sendEvent(listener -> listener.onAudioSinkError(eventTime, audioSinkError));
|
||||
listeners.sendEvent(
|
||||
AnalyticsListener.EVENT_AUDIO_SINK_ERROR,
|
||||
listener -> listener.onAudioSinkError(eventTime, audioSinkError));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVolumeChanged(float audioVolume) {
|
||||
EventTime eventTime = generateReadingMediaPeriodEventTime();
|
||||
listeners.sendEvent(listener -> listener.onVolumeChanged(eventTime, audioVolume));
|
||||
listeners.sendEvent(
|
||||
AnalyticsListener.EVENT_VOLUME_CHANGED,
|
||||
listener -> listener.onVolumeChanged(eventTime, audioVolume));
|
||||
}
|
||||
|
||||
// VideoRendererEventListener implementation.
|
||||
|
|
@ -279,6 +307,7 @@ public class AnalyticsCollector
|
|||
public final void onVideoEnabled(DecoderCounters counters) {
|
||||
EventTime eventTime = generateReadingMediaPeriodEventTime();
|
||||
listeners.sendEvent(
|
||||
AnalyticsListener.EVENT_VIDEO_ENABLED,
|
||||
listener -> {
|
||||
listener.onVideoEnabled(eventTime, counters);
|
||||
listener.onDecoderEnabled(eventTime, C.TRACK_TYPE_VIDEO, counters);
|
||||
|
|
@ -291,6 +320,7 @@ public class AnalyticsCollector
|
|||
String decoderName, long initializedTimestampMs, long initializationDurationMs) {
|
||||
EventTime eventTime = generateReadingMediaPeriodEventTime();
|
||||
listeners.sendEvent(
|
||||
AnalyticsListener.EVENT_VIDEO_DECODER_INITIALIZED,
|
||||
listener -> {
|
||||
listener.onVideoDecoderInitialized(eventTime, decoderName, initializationDurationMs);
|
||||
listener.onDecoderInitialized(
|
||||
|
|
@ -304,6 +334,7 @@ public class AnalyticsCollector
|
|||
Format format, @Nullable DecoderReuseEvaluation decoderReuseEvaluation) {
|
||||
EventTime eventTime = generateReadingMediaPeriodEventTime();
|
||||
listeners.sendEvent(
|
||||
AnalyticsListener.EVENT_VIDEO_INPUT_FORMAT_CHANGED,
|
||||
listener -> {
|
||||
listener.onVideoInputFormatChanged(eventTime, format, decoderReuseEvaluation);
|
||||
listener.onDecoderInputFormatChanged(eventTime, C.TRACK_TYPE_VIDEO, format);
|
||||
|
|
@ -313,13 +344,17 @@ public class AnalyticsCollector
|
|||
@Override
|
||||
public final void onDroppedFrames(int count, long elapsedMs) {
|
||||
EventTime eventTime = generatePlayingMediaPeriodEventTime();
|
||||
listeners.sendEvent(listener -> listener.onDroppedVideoFrames(eventTime, count, elapsedMs));
|
||||
listeners.sendEvent(
|
||||
AnalyticsListener.EVENT_DROPPED_VIDEO_FRAMES,
|
||||
listener -> listener.onDroppedVideoFrames(eventTime, count, elapsedMs));
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onVideoDecoderReleased(String decoderName) {
|
||||
EventTime eventTime = generateReadingMediaPeriodEventTime();
|
||||
listeners.sendEvent(listener -> listener.onVideoDecoderReleased(eventTime, decoderName));
|
||||
listeners.sendEvent(
|
||||
AnalyticsListener.EVENT_VIDEO_DECODER_RELEASED,
|
||||
listener -> listener.onVideoDecoderReleased(eventTime, decoderName));
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
|
|
@ -327,6 +362,7 @@ public class AnalyticsCollector
|
|||
public final void onVideoDisabled(DecoderCounters counters) {
|
||||
EventTime eventTime = generatePlayingMediaPeriodEventTime();
|
||||
listeners.sendEvent(
|
||||
AnalyticsListener.EVENT_VIDEO_DISABLED,
|
||||
listener -> {
|
||||
listener.onVideoDisabled(eventTime, counters);
|
||||
listener.onDecoderDisabled(eventTime, C.TRACK_TYPE_VIDEO, counters);
|
||||
|
|
@ -336,13 +372,16 @@ public class AnalyticsCollector
|
|||
@Override
|
||||
public final void onRenderedFirstFrame(@Nullable Surface surface) {
|
||||
EventTime eventTime = generateReadingMediaPeriodEventTime();
|
||||
listeners.sendEvent(listener -> listener.onRenderedFirstFrame(eventTime, surface));
|
||||
listeners.sendEvent(
|
||||
AnalyticsListener.EVENT_RENDERED_FIRST_FRAME,
|
||||
listener -> listener.onRenderedFirstFrame(eventTime, surface));
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onVideoFrameProcessingOffset(long totalProcessingOffsetUs, int frameCount) {
|
||||
EventTime eventTime = generatePlayingMediaPeriodEventTime();
|
||||
listeners.sendEvent(
|
||||
AnalyticsListener.EVENT_VIDEO_FRAME_PROCESSING_OFFSET,
|
||||
listener ->
|
||||
listener.onVideoFrameProcessingOffset(eventTime, totalProcessingOffsetUs, frameCount));
|
||||
}
|
||||
|
|
@ -359,6 +398,7 @@ public class AnalyticsCollector
|
|||
int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) {
|
||||
EventTime eventTime = generateReadingMediaPeriodEventTime();
|
||||
listeners.sendEvent(
|
||||
AnalyticsListener.EVENT_VIDEO_SIZE_CHANGED,
|
||||
listener ->
|
||||
listener.onVideoSizeChanged(
|
||||
eventTime, width, height, unappliedRotationDegrees, pixelWidthHeightRatio));
|
||||
|
|
@ -367,7 +407,9 @@ public class AnalyticsCollector
|
|||
@Override
|
||||
public void onSurfaceSizeChanged(int width, int height) {
|
||||
EventTime eventTime = generateReadingMediaPeriodEventTime();
|
||||
listeners.sendEvent(listener -> listener.onSurfaceSizeChanged(eventTime, width, height));
|
||||
listeners.sendEvent(
|
||||
AnalyticsListener.EVENT_SURFACE_SIZE_CHANGED,
|
||||
listener -> listener.onSurfaceSizeChanged(eventTime, width, height));
|
||||
}
|
||||
|
||||
// MediaSourceEventListener implementation.
|
||||
|
|
@ -380,6 +422,7 @@ public class AnalyticsCollector
|
|||
MediaLoadData mediaLoadData) {
|
||||
EventTime eventTime = generateMediaPeriodEventTime(windowIndex, mediaPeriodId);
|
||||
listeners.sendEvent(
|
||||
AnalyticsListener.EVENT_LOAD_STARTED,
|
||||
listener -> listener.onLoadStarted(eventTime, loadEventInfo, mediaLoadData));
|
||||
}
|
||||
|
||||
|
|
@ -391,6 +434,7 @@ public class AnalyticsCollector
|
|||
MediaLoadData mediaLoadData) {
|
||||
EventTime eventTime = generateMediaPeriodEventTime(windowIndex, mediaPeriodId);
|
||||
listeners.sendEvent(
|
||||
AnalyticsListener.EVENT_LOAD_COMPLETED,
|
||||
listener -> listener.onLoadCompleted(eventTime, loadEventInfo, mediaLoadData));
|
||||
}
|
||||
|
||||
|
|
@ -402,6 +446,7 @@ public class AnalyticsCollector
|
|||
MediaLoadData mediaLoadData) {
|
||||
EventTime eventTime = generateMediaPeriodEventTime(windowIndex, mediaPeriodId);
|
||||
listeners.sendEvent(
|
||||
AnalyticsListener.EVENT_LOAD_CANCELED,
|
||||
listener -> listener.onLoadCanceled(eventTime, loadEventInfo, mediaLoadData));
|
||||
}
|
||||
|
||||
|
|
@ -415,6 +460,7 @@ public class AnalyticsCollector
|
|||
boolean wasCanceled) {
|
||||
EventTime eventTime = generateMediaPeriodEventTime(windowIndex, mediaPeriodId);
|
||||
listeners.sendEvent(
|
||||
AnalyticsListener.EVENT_LOAD_ERROR,
|
||||
listener ->
|
||||
listener.onLoadError(eventTime, loadEventInfo, mediaLoadData, error, wasCanceled));
|
||||
}
|
||||
|
|
@ -423,34 +469,42 @@ public class AnalyticsCollector
|
|||
public final void onUpstreamDiscarded(
|
||||
int windowIndex, @Nullable MediaPeriodId mediaPeriodId, MediaLoadData mediaLoadData) {
|
||||
EventTime eventTime = generateMediaPeriodEventTime(windowIndex, mediaPeriodId);
|
||||
listeners.sendEvent(listener -> listener.onUpstreamDiscarded(eventTime, mediaLoadData));
|
||||
listeners.sendEvent(
|
||||
AnalyticsListener.EVENT_UPSTREAM_DISCARDED,
|
||||
listener -> listener.onUpstreamDiscarded(eventTime, mediaLoadData));
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onDownstreamFormatChanged(
|
||||
int windowIndex, @Nullable MediaPeriodId mediaPeriodId, MediaLoadData mediaLoadData) {
|
||||
EventTime eventTime = generateMediaPeriodEventTime(windowIndex, mediaPeriodId);
|
||||
listeners.sendEvent(listener -> listener.onDownstreamFormatChanged(eventTime, mediaLoadData));
|
||||
listeners.sendEvent(
|
||||
AnalyticsListener.EVENT_DOWNSTREAM_FORMAT_CHANGED,
|
||||
listener -> listener.onDownstreamFormatChanged(eventTime, mediaLoadData));
|
||||
}
|
||||
|
||||
// Player.EventListener implementation.
|
||||
|
||||
// TODO: Add onFinishedReportingChanges to Player.EventListener to know when a set of simultaneous
|
||||
// callbacks finished. This helps to assign exactly the same EventTime to all of them instead of
|
||||
// having slightly different real times.
|
||||
// TODO: Use Player.EventListener.onEvents to know when a set of simultaneous callbacks finished.
|
||||
// This helps to assign exactly the same EventTime to all of them instead of having slightly
|
||||
// different real times.
|
||||
|
||||
@Override
|
||||
public final void onTimelineChanged(Timeline timeline, @Player.TimelineChangeReason int reason) {
|
||||
mediaPeriodQueueTracker.onTimelineChanged(checkNotNull(player));
|
||||
EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime();
|
||||
listeners.sendEvent(listener -> listener.onTimelineChanged(eventTime, reason));
|
||||
listeners.sendEvent(
|
||||
AnalyticsListener.EVENT_TIMELINE_CHANGED,
|
||||
listener -> listener.onTimelineChanged(eventTime, reason));
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onMediaItemTransition(
|
||||
@Nullable MediaItem mediaItem, @Player.MediaItemTransitionReason int reason) {
|
||||
EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime();
|
||||
listeners.sendEvent(listener -> listener.onMediaItemTransition(eventTime, mediaItem, reason));
|
||||
listeners.sendEvent(
|
||||
AnalyticsListener.EVENT_MEDIA_ITEM_TRANSITION,
|
||||
listener -> listener.onMediaItemTransition(eventTime, mediaItem, reason));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -458,19 +512,24 @@ public class AnalyticsCollector
|
|||
TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
|
||||
EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime();
|
||||
listeners.sendEvent(
|
||||
AnalyticsListener.EVENT_TRACKS_CHANGED,
|
||||
listener -> listener.onTracksChanged(eventTime, trackGroups, trackSelections));
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onStaticMetadataChanged(List<Metadata> metadataList) {
|
||||
EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime();
|
||||
listeners.sendEvent(listener -> listener.onStaticMetadataChanged(eventTime, metadataList));
|
||||
listeners.sendEvent(
|
||||
AnalyticsListener.EVENT_STATIC_METADATA_CHANGED,
|
||||
listener -> listener.onStaticMetadataChanged(eventTime, metadataList));
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onIsLoadingChanged(boolean isLoading) {
|
||||
EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime();
|
||||
listeners.sendEvent(listener -> listener.onIsLoadingChanged(eventTime, isLoading));
|
||||
listeners.sendEvent(
|
||||
AnalyticsListener.EVENT_IS_LOADING_CHANGED,
|
||||
listener -> listener.onIsLoadingChanged(eventTime, isLoading));
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
|
|
@ -478,13 +537,16 @@ public class AnalyticsCollector
|
|||
public final void onPlayerStateChanged(boolean playWhenReady, @Player.State int playbackState) {
|
||||
EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime();
|
||||
listeners.sendEvent(
|
||||
/* eventFlag= */ C.INDEX_UNSET,
|
||||
listener -> listener.onPlayerStateChanged(eventTime, playWhenReady, playbackState));
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onPlaybackStateChanged(@Player.State int state) {
|
||||
EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime();
|
||||
listeners.sendEvent(listener -> listener.onPlaybackStateChanged(eventTime, state));
|
||||
listeners.sendEvent(
|
||||
AnalyticsListener.EVENT_PLAYBACK_STATE_CHANGED,
|
||||
listener -> listener.onPlaybackStateChanged(eventTime, state));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -492,6 +554,7 @@ public class AnalyticsCollector
|
|||
boolean playWhenReady, @Player.PlayWhenReadyChangeReason int reason) {
|
||||
EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime();
|
||||
listeners.sendEvent(
|
||||
AnalyticsListener.EVENT_PLAY_WHEN_READY_CHANGED,
|
||||
listener -> listener.onPlayWhenReadyChanged(eventTime, playWhenReady, reason));
|
||||
}
|
||||
|
||||
|
|
@ -500,6 +563,7 @@ public class AnalyticsCollector
|
|||
@PlaybackSuppressionReason int playbackSuppressionReason) {
|
||||
EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime();
|
||||
listeners.sendEvent(
|
||||
AnalyticsListener.EVENT_PLAYBACK_SUPPRESSION_REASON_CHANGED,
|
||||
listener ->
|
||||
listener.onPlaybackSuppressionReasonChanged(eventTime, playbackSuppressionReason));
|
||||
}
|
||||
|
|
@ -507,19 +571,25 @@ public class AnalyticsCollector
|
|||
@Override
|
||||
public void onIsPlayingChanged(boolean isPlaying) {
|
||||
EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime();
|
||||
listeners.sendEvent(listener -> listener.onIsPlayingChanged(eventTime, isPlaying));
|
||||
listeners.sendEvent(
|
||||
AnalyticsListener.EVENT_IS_PLAYING_CHANGED,
|
||||
listener -> listener.onIsPlayingChanged(eventTime, isPlaying));
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onRepeatModeChanged(@Player.RepeatMode int repeatMode) {
|
||||
EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime();
|
||||
listeners.sendEvent(listener -> listener.onRepeatModeChanged(eventTime, repeatMode));
|
||||
listeners.sendEvent(
|
||||
AnalyticsListener.EVENT_REPEAT_MODE_CHANGED,
|
||||
listener -> listener.onRepeatModeChanged(eventTime, repeatMode));
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {
|
||||
EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime();
|
||||
listeners.sendEvent(listener -> listener.onShuffleModeChanged(eventTime, shuffleModeEnabled));
|
||||
listeners.sendEvent(
|
||||
AnalyticsListener.EVENT_SHUFFLE_MODE_ENABLED_CHANGED,
|
||||
listener -> listener.onShuffleModeChanged(eventTime, shuffleModeEnabled));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -528,7 +598,8 @@ public class AnalyticsCollector
|
|||
error.mediaPeriodId != null
|
||||
? generateEventTime(error.mediaPeriodId)
|
||||
: generateCurrentPlayerMediaPeriodEventTime();
|
||||
listeners.sendEvent(listener -> listener.onPlayerError(eventTime, error));
|
||||
listeners.sendEvent(
|
||||
AnalyticsListener.EVENT_PLAYER_ERROR, listener -> listener.onPlayerError(eventTime, error));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -538,13 +609,16 @@ public class AnalyticsCollector
|
|||
}
|
||||
mediaPeriodQueueTracker.onPositionDiscontinuity(checkNotNull(player));
|
||||
EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime();
|
||||
listeners.sendEvent(listener -> listener.onPositionDiscontinuity(eventTime, reason));
|
||||
listeners.sendEvent(
|
||||
AnalyticsListener.EVENT_POSITION_DISCONTINUITY,
|
||||
listener -> listener.onPositionDiscontinuity(eventTime, reason));
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
|
||||
EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime();
|
||||
listeners.sendEvent(
|
||||
AnalyticsListener.EVENT_PLAYBACK_PARAMETERS_CHANGED,
|
||||
listener -> listener.onPlaybackParametersChanged(eventTime, playbackParameters));
|
||||
}
|
||||
|
||||
|
|
@ -552,7 +626,8 @@ public class AnalyticsCollector
|
|||
@Override
|
||||
public final void onSeekProcessed() {
|
||||
EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime();
|
||||
listeners.sendEvent(listener -> listener.onSeekProcessed(eventTime));
|
||||
listeners.sendEvent(
|
||||
/* eventFlag= */ C.INDEX_UNSET, listener -> listener.onSeekProcessed(eventTime));
|
||||
}
|
||||
|
||||
// BandwidthMeter.Listener implementation.
|
||||
|
|
@ -561,6 +636,7 @@ public class AnalyticsCollector
|
|||
public final void onBandwidthSample(int elapsedMs, long bytes, long bitrate) {
|
||||
EventTime eventTime = generateLoadingMediaPeriodEventTime();
|
||||
listeners.sendEvent(
|
||||
AnalyticsListener.EVENT_BANDWIDTH_ESTIMATE,
|
||||
listener -> listener.onBandwidthEstimate(eventTime, elapsedMs, bytes, bitrate));
|
||||
}
|
||||
|
||||
|
|
@ -569,43 +645,52 @@ public class AnalyticsCollector
|
|||
@Override
|
||||
public final void onDrmSessionAcquired(int windowIndex, @Nullable MediaPeriodId mediaPeriodId) {
|
||||
EventTime eventTime = generateMediaPeriodEventTime(windowIndex, mediaPeriodId);
|
||||
listeners.sendEvent(listener -> listener.onDrmSessionAcquired(eventTime));
|
||||
listeners.sendEvent(
|
||||
AnalyticsListener.EVENT_DRM_SESSION_ACQUIRED,
|
||||
listener -> listener.onDrmSessionAcquired(eventTime));
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onDrmKeysLoaded(int windowIndex, @Nullable MediaPeriodId mediaPeriodId) {
|
||||
EventTime eventTime = generateMediaPeriodEventTime(windowIndex, mediaPeriodId);
|
||||
listeners.sendEvent(listener -> listener.onDrmKeysLoaded(eventTime));
|
||||
listeners.sendEvent(
|
||||
AnalyticsListener.EVENT_DRM_KEYS_LOADED, listener -> listener.onDrmKeysLoaded(eventTime));
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onDrmSessionManagerError(
|
||||
int windowIndex, @Nullable MediaPeriodId mediaPeriodId, Exception error) {
|
||||
EventTime eventTime = generateMediaPeriodEventTime(windowIndex, mediaPeriodId);
|
||||
listeners.sendEvent(listener -> listener.onDrmSessionManagerError(eventTime, error));
|
||||
listeners.sendEvent(
|
||||
AnalyticsListener.EVENT_DRM_SESSION_MANAGER_ERROR,
|
||||
listener -> listener.onDrmSessionManagerError(eventTime, error));
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onDrmKeysRestored(int windowIndex, @Nullable MediaPeriodId mediaPeriodId) {
|
||||
EventTime eventTime = generateMediaPeriodEventTime(windowIndex, mediaPeriodId);
|
||||
listeners.sendEvent(listener -> listener.onDrmKeysRestored(eventTime));
|
||||
listeners.sendEvent(
|
||||
AnalyticsListener.EVENT_DRM_KEYS_RESTORED,
|
||||
listener -> listener.onDrmKeysRestored(eventTime));
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onDrmKeysRemoved(int windowIndex, @Nullable MediaPeriodId mediaPeriodId) {
|
||||
EventTime eventTime = generateMediaPeriodEventTime(windowIndex, mediaPeriodId);
|
||||
listeners.sendEvent(listener -> listener.onDrmKeysRemoved(eventTime));
|
||||
listeners.sendEvent(
|
||||
AnalyticsListener.EVENT_DRM_KEYS_REMOVED, listener -> listener.onDrmKeysRemoved(eventTime));
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onDrmSessionReleased(int windowIndex, @Nullable MediaPeriodId mediaPeriodId) {
|
||||
EventTime eventTime = generateMediaPeriodEventTime(windowIndex, mediaPeriodId);
|
||||
listeners.sendEvent(listener -> listener.onDrmSessionReleased(eventTime));
|
||||
listeners.sendEvent(
|
||||
AnalyticsListener.EVENT_DRM_SESSION_RELEASED,
|
||||
listener -> listener.onDrmSessionReleased(eventTime));
|
||||
}
|
||||
|
||||
// Internal methods.
|
||||
|
||||
|
||||
/** Returns a new {@link EventTime} for the specified timeline, window and media period id. */
|
||||
@RequiresNonNull("player")
|
||||
protected EventTime generateEventTime(
|
||||
|
|
|
|||
|
|
@ -15,7 +15,9 @@
|
|||
*/
|
||||
package com.google.android.exoplayer2.analytics;
|
||||
|
||||
import android.os.Looper;
|
||||
import android.view.Surface;
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||
|
|
@ -37,8 +39,12 @@ import com.google.android.exoplayer2.source.MediaLoadData;
|
|||
import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
|
||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
|
||||
import com.google.android.exoplayer2.util.MutableFlags;
|
||||
import com.google.common.base.Objects;
|
||||
import java.io.IOException;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
|
|
@ -48,9 +54,215 @@ import java.util.List;
|
|||
* time at the time of the event.
|
||||
*
|
||||
* <p>All methods have no-op default implementations to allow selective overrides.
|
||||
*
|
||||
* <p>Listeners can choose to implement individual events (e.g. {@link
|
||||
* #onIsPlayingChanged(EventTime, boolean)}) or {@link #onEvents(Player, Events)}, which is called
|
||||
* after one or more events occurred together.
|
||||
*/
|
||||
public interface AnalyticsListener {
|
||||
|
||||
/** A set of {@link EventFlags}. */
|
||||
final class Events extends MutableFlags {
|
||||
/**
|
||||
* Returns whether the given event occurred.
|
||||
*
|
||||
* @param event The {@link EventFlags event}.
|
||||
* @return Whether the event occurred.
|
||||
*/
|
||||
@Override
|
||||
public boolean contains(@EventFlags int event) {
|
||||
// Overridden to add IntDef compiler enforcement and new JavaDoc.
|
||||
return super.contains(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link EventFlags event} at the given index.
|
||||
*
|
||||
* <p>Although index-based access is possible, it doesn't imply a particular order of these
|
||||
* events.
|
||||
*
|
||||
* @param index The index. Must be between 0 (inclusive) and {@link #size()} (exclusive).
|
||||
* @return The {@link EventFlags event} at the given index.
|
||||
*/
|
||||
@Override
|
||||
@EventFlags
|
||||
public int get(int index) {
|
||||
// Overridden to add IntDef compiler enforcement and new JavaDoc.
|
||||
return super.get(index);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Events that can be reported via {@link #onEvents(Player, Events)}.
|
||||
*
|
||||
* <p>One of the {@link AnalyticsListener}{@code .EVENT_*} flags.
|
||||
*/
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef({
|
||||
EVENT_TIMELINE_CHANGED,
|
||||
EVENT_MEDIA_ITEM_TRANSITION,
|
||||
EVENT_TRACKS_CHANGED,
|
||||
EVENT_STATIC_METADATA_CHANGED,
|
||||
EVENT_IS_LOADING_CHANGED,
|
||||
EVENT_PLAYBACK_STATE_CHANGED,
|
||||
EVENT_PLAY_WHEN_READY_CHANGED,
|
||||
EVENT_PLAYBACK_SUPPRESSION_REASON_CHANGED,
|
||||
EVENT_IS_PLAYING_CHANGED,
|
||||
EVENT_REPEAT_MODE_CHANGED,
|
||||
EVENT_SHUFFLE_MODE_ENABLED_CHANGED,
|
||||
EVENT_PLAYER_ERROR,
|
||||
EVENT_POSITION_DISCONTINUITY,
|
||||
EVENT_PLAYBACK_PARAMETERS_CHANGED,
|
||||
EVENT_LOAD_STARTED,
|
||||
EVENT_LOAD_COMPLETED,
|
||||
EVENT_LOAD_CANCELED,
|
||||
EVENT_LOAD_ERROR,
|
||||
EVENT_DOWNSTREAM_FORMAT_CHANGED,
|
||||
EVENT_UPSTREAM_DISCARDED,
|
||||
EVENT_BANDWIDTH_ESTIMATE,
|
||||
EVENT_METADATA,
|
||||
EVENT_AUDIO_ENABLED,
|
||||
EVENT_AUDIO_DECODER_INITIALIZED,
|
||||
EVENT_AUDIO_INPUT_FORMAT_CHANGED,
|
||||
EVENT_AUDIO_POSITION_ADVANCING,
|
||||
EVENT_AUDIO_UNDERRUN,
|
||||
EVENT_AUDIO_DECODER_RELEASED,
|
||||
EVENT_AUDIO_DISABLED,
|
||||
EVENT_AUDIO_SESSION_ID,
|
||||
EVENT_AUDIO_ATTRIBUTES_CHANGED,
|
||||
EVENT_SKIP_SILENCE_ENABLED_CHANGED,
|
||||
EVENT_AUDIO_SINK_ERROR,
|
||||
EVENT_VOLUME_CHANGED,
|
||||
EVENT_VIDEO_ENABLED,
|
||||
EVENT_VIDEO_DECODER_INITIALIZED,
|
||||
EVENT_VIDEO_INPUT_FORMAT_CHANGED,
|
||||
EVENT_DROPPED_VIDEO_FRAMES,
|
||||
EVENT_VIDEO_DECODER_RELEASED,
|
||||
EVENT_VIDEO_DISABLED,
|
||||
EVENT_VIDEO_FRAME_PROCESSING_OFFSET,
|
||||
EVENT_RENDERED_FIRST_FRAME,
|
||||
EVENT_VIDEO_SIZE_CHANGED,
|
||||
EVENT_SURFACE_SIZE_CHANGED,
|
||||
EVENT_DRM_SESSION_ACQUIRED,
|
||||
EVENT_DRM_KEYS_LOADED,
|
||||
EVENT_DRM_SESSION_MANAGER_ERROR,
|
||||
EVENT_DRM_KEYS_RESTORED,
|
||||
EVENT_DRM_KEYS_REMOVED,
|
||||
EVENT_DRM_SESSION_RELEASED
|
||||
})
|
||||
@interface EventFlags {}
|
||||
/** {@link Player#getCurrentTimeline()} changed. */
|
||||
int EVENT_TIMELINE_CHANGED = Player.EVENT_TIMELINE_CHANGED;
|
||||
/**
|
||||
* {@link Player#getCurrentMediaItem()} changed or the player started repeating the current item.
|
||||
*/
|
||||
int EVENT_MEDIA_ITEM_TRANSITION = Player.EVENT_MEDIA_ITEM_TRANSITION;
|
||||
/**
|
||||
* {@link Player#getCurrentTrackGroups()} or {@link Player#getCurrentTrackSelections()} changed.
|
||||
*/
|
||||
int EVENT_TRACKS_CHANGED = Player.EVENT_TRACKS_CHANGED;
|
||||
/** {@link Player#getCurrentStaticMetadata()} changed. */
|
||||
int EVENT_STATIC_METADATA_CHANGED = Player.EVENT_STATIC_METADATA_CHANGED;
|
||||
/** {@link Player#isLoading()} ()} changed. */
|
||||
int EVENT_IS_LOADING_CHANGED = Player.EVENT_IS_LOADING_CHANGED;
|
||||
/** {@link Player#getPlaybackState()} changed. */
|
||||
int EVENT_PLAYBACK_STATE_CHANGED = Player.EVENT_PLAYBACK_STATE_CHANGED;
|
||||
/** {@link Player#getPlayWhenReady()} changed. */
|
||||
int EVENT_PLAY_WHEN_READY_CHANGED = Player.EVENT_PLAY_WHEN_READY_CHANGED;
|
||||
/** {@link Player#getPlaybackSuppressionReason()} changed. */
|
||||
int EVENT_PLAYBACK_SUPPRESSION_REASON_CHANGED = Player.EVENT_PLAYBACK_SUPPRESSION_REASON_CHANGED;
|
||||
/** {@link Player#isPlaying()} changed. */
|
||||
int EVENT_IS_PLAYING_CHANGED = Player.EVENT_IS_PLAYING_CHANGED;
|
||||
/** {@link Player#getRepeatMode()} changed. */
|
||||
int EVENT_REPEAT_MODE_CHANGED = Player.EVENT_REPEAT_MODE_CHANGED;
|
||||
/** {@link Player#getShuffleModeEnabled()} changed. */
|
||||
int EVENT_SHUFFLE_MODE_ENABLED_CHANGED = Player.EVENT_SHUFFLE_MODE_ENABLED_CHANGED;
|
||||
/** {@link Player#getPlayerError()} changed. */
|
||||
int EVENT_PLAYER_ERROR = Player.EVENT_PLAYER_ERROR;
|
||||
/**
|
||||
* A position discontinuity occurred. See {@link
|
||||
* Player.EventListener#onPositionDiscontinuity(int)}.
|
||||
*/
|
||||
int EVENT_POSITION_DISCONTINUITY = Player.EVENT_POSITION_DISCONTINUITY;
|
||||
/** {@link Player#getPlaybackParameters()} changed. */
|
||||
int EVENT_PLAYBACK_PARAMETERS_CHANGED = Player.EVENT_PLAYBACK_PARAMETERS_CHANGED;
|
||||
/** A source started loading data. */
|
||||
int EVENT_LOAD_STARTED = 1000; // Intentional gap to leave space for new Player events
|
||||
/** A source started completed loading data. */
|
||||
int EVENT_LOAD_COMPLETED = 1001;
|
||||
/** A source canceled loading data. */
|
||||
int EVENT_LOAD_CANCELED = 1002;
|
||||
/** A source had a non-fatal error loading data. */
|
||||
int EVENT_LOAD_ERROR = 1003;
|
||||
/** The downstream format sent to renderers changed. */
|
||||
int EVENT_DOWNSTREAM_FORMAT_CHANGED = 1004;
|
||||
/** Data was removed from the end of the media buffer. */
|
||||
int EVENT_UPSTREAM_DISCARDED = 1005;
|
||||
/** The bandwidth estimate has been updated. */
|
||||
int EVENT_BANDWIDTH_ESTIMATE = 1006;
|
||||
/** Metadata associated with the current playback time was reported. */
|
||||
int EVENT_METADATA = 1007;
|
||||
/** An audio renderer was enabled. */
|
||||
int EVENT_AUDIO_ENABLED = 1008;
|
||||
/** An audio renderer created a decoder. */
|
||||
int EVENT_AUDIO_DECODER_INITIALIZED = 1009;
|
||||
/** The format consumed by an audio renderer changed. */
|
||||
int EVENT_AUDIO_INPUT_FORMAT_CHANGED = 1010;
|
||||
/** The audio position has increased for the first time since the last pause or position reset. */
|
||||
int EVENT_AUDIO_POSITION_ADVANCING = 1011;
|
||||
/** An audio underrun occurred. */
|
||||
int EVENT_AUDIO_UNDERRUN = 1012;
|
||||
/** An audio renderer released a decoder. */
|
||||
int EVENT_AUDIO_DECODER_RELEASED = 1013;
|
||||
/** An audio renderer was disabled. */
|
||||
int EVENT_AUDIO_DISABLED = 1014;
|
||||
/** An audio session id was set. */
|
||||
int EVENT_AUDIO_SESSION_ID = 1015;
|
||||
/** Audio attributes changed. */
|
||||
int EVENT_AUDIO_ATTRIBUTES_CHANGED = 1016;
|
||||
/** Skipping silences was enabled or disabled in the audio stream. */
|
||||
int EVENT_SKIP_SILENCE_ENABLED_CHANGED = 1017;
|
||||
/** The audio sink encountered a non-fatal error. */
|
||||
int EVENT_AUDIO_SINK_ERROR = 1018;
|
||||
/** The volume changed. */
|
||||
int EVENT_VOLUME_CHANGED = 1019;
|
||||
/** A video renderer was enabled. */
|
||||
int EVENT_VIDEO_ENABLED = 1020;
|
||||
/** A video renderer created a decoder. */
|
||||
int EVENT_VIDEO_DECODER_INITIALIZED = 1021;
|
||||
/** The format consumed by a video renderer changed. */
|
||||
int EVENT_VIDEO_INPUT_FORMAT_CHANGED = 1022;
|
||||
/** Video frames have been dropped. */
|
||||
int EVENT_DROPPED_VIDEO_FRAMES = 1023;
|
||||
/** A video renderer released a decoder. */
|
||||
int EVENT_VIDEO_DECODER_RELEASED = 1024;
|
||||
/** A video renderer was disabled. */
|
||||
int EVENT_VIDEO_DISABLED = 1025;
|
||||
/** Video frame processing offset data has been reported. */
|
||||
int EVENT_VIDEO_FRAME_PROCESSING_OFFSET = 1026;
|
||||
/**
|
||||
* The first frame has been rendered since setting the surface, since the renderer was reset or
|
||||
* since the stream changed.
|
||||
*/
|
||||
int EVENT_RENDERED_FIRST_FRAME = 1027;
|
||||
/** The video size changed. */
|
||||
int EVENT_VIDEO_SIZE_CHANGED = 1028;
|
||||
/** The surface size changed. */
|
||||
int EVENT_SURFACE_SIZE_CHANGED = 1029;
|
||||
/** A DRM session has been acquired. */
|
||||
int EVENT_DRM_SESSION_ACQUIRED = 1030;
|
||||
/** DRM keys were loaded. */
|
||||
int EVENT_DRM_KEYS_LOADED = 1031;
|
||||
/** A non-fatal DRM session manager error occurred. */
|
||||
int EVENT_DRM_SESSION_MANAGER_ERROR = 1032;
|
||||
/** DRM keys were restored. */
|
||||
int EVENT_DRM_KEYS_RESTORED = 1033;
|
||||
/** DRM keys were removed. */
|
||||
int EVENT_DRM_KEYS_REMOVED = 1034;
|
||||
/** A DRM session has been released. */
|
||||
int EVENT_DRM_SESSION_RELEASED = 1035;
|
||||
|
||||
/** Time information of an event. */
|
||||
final class EventTime {
|
||||
|
||||
|
|
@ -753,4 +965,31 @@ public interface AnalyticsListener {
|
|||
* @param eventTime The event time.
|
||||
*/
|
||||
default void onDrmSessionReleased(EventTime eventTime) {}
|
||||
|
||||
/**
|
||||
* Called after one or more events occurred.
|
||||
*
|
||||
* <p>State changes and events that happen within one {@link Looper} message queue iteration are
|
||||
* reported together and only after all individual callbacks were triggered.
|
||||
*
|
||||
* <p>Listeners should prefer this method over individual callbacks in the following cases:
|
||||
*
|
||||
* <ul>
|
||||
* <li>They intend to use multiple state values together (e.g. using {@link
|
||||
* Player#getCurrentWindowIndex()} to query in {@link Player#getCurrentTimeline()}).
|
||||
* <li>The same logic should be triggered for multiple events (e.g. when updating a UI for both
|
||||
* {@link #onPlaybackStateChanged(EventTime, int)} and {@link
|
||||
* #onPlayWhenReadyChanged(EventTime, boolean, int)}).
|
||||
* <li>They need access to the {@link Player} object to trigger further events (e.g. to call
|
||||
* {@link Player#seekTo(long)} after a {@link
|
||||
* AnalyticsListener#onMediaItemTransition(EventTime, MediaItem, int)}).
|
||||
* <li>They are interested in events that logically happened together (e.g {@link
|
||||
* #onPlaybackStateChanged(EventTime, int)} to {@link Player#STATE_BUFFERING} because of
|
||||
* {@link #onMediaItemTransition(EventTime, MediaItem, int)}).
|
||||
* </ul>
|
||||
*
|
||||
* @param player The {@link Player}.
|
||||
* @param events The {@link Events} that occurred in this iteration.
|
||||
*/
|
||||
default void onEvents(Player player, Events events) {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,13 @@
|
|||
*/
|
||||
package com.google.android.exoplayer2.util;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import androidx.annotation.CheckResult;
|
||||
import androidx.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.common.base.Supplier;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
import javax.annotation.Nonnull;
|
||||
|
|
@ -30,8 +36,9 @@ import javax.annotation.Nonnull;
|
|||
* was enqueued and haven't been removed since.
|
||||
*
|
||||
* @param <T> The listener type.
|
||||
* @param <E> The {@link MutableFlags} type used to indicate which events occurred.
|
||||
*/
|
||||
public final class ListenerSet<T> {
|
||||
public final class ListenerSet<T, E extends MutableFlags> {
|
||||
|
||||
/**
|
||||
* An event sent to a listener.
|
||||
|
|
@ -44,17 +51,84 @@ public final class ListenerSet<T> {
|
|||
void invoke(T listener);
|
||||
}
|
||||
|
||||
private final CopyOnWriteArraySet<ListenerHolder<T>> listeners;
|
||||
/**
|
||||
* An event sent to a listener when all other events sent during one {@link Looper} message queue
|
||||
* iteration were handled by the listener.
|
||||
*
|
||||
* @param <T> The listener type.
|
||||
* @param <E> The {@link MutableFlags} type used to indicate which events occurred.
|
||||
*/
|
||||
public interface IterationFinishedEvent<T, E extends MutableFlags> {
|
||||
|
||||
/**
|
||||
* Invokes the iteration finished event.
|
||||
*
|
||||
* @param listener The listener to invoke the event on.
|
||||
* @param eventFlags The combined event flags of all events sent in this iteration.
|
||||
*/
|
||||
void invoke(T listener, E eventFlags);
|
||||
}
|
||||
|
||||
private static final int MSG_ITERATION_FINISHED = 0;
|
||||
|
||||
private final Handler iterationFinishedHandler;
|
||||
private final Supplier<E> eventFlagsSupplier;
|
||||
private final IterationFinishedEvent<T, E> iterationFinishedEvent;
|
||||
private final CopyOnWriteArraySet<ListenerHolder<T, E>> listeners;
|
||||
private final ArrayDeque<Runnable> flushingEvents;
|
||||
private final ArrayDeque<Runnable> queuedEvents;
|
||||
|
||||
private boolean released;
|
||||
|
||||
/** Creates the listener set. */
|
||||
public ListenerSet() {
|
||||
listeners = new CopyOnWriteArraySet<>();
|
||||
/**
|
||||
* Creates a new listener set.
|
||||
*
|
||||
* @param looper A {@link Looper} used to call listeners on. The same {@link Looper} must be used
|
||||
* to call all other methods of this class.
|
||||
* @param eventFlagsSupplier A {@link Supplier} for new instances of {@link E the event flags
|
||||
* type}.
|
||||
* @param iterationFinishedEvent An {@link IterationFinishedEvent} sent when all other events sent
|
||||
* during one {@link Looper} message queue iteration were handled by the listeners.
|
||||
*/
|
||||
public ListenerSet(
|
||||
Looper looper,
|
||||
Supplier<E> eventFlagsSupplier,
|
||||
IterationFinishedEvent<T, E> iterationFinishedEvent) {
|
||||
this(
|
||||
/* listeners= */ new CopyOnWriteArraySet<>(),
|
||||
looper,
|
||||
eventFlagsSupplier,
|
||||
iterationFinishedEvent);
|
||||
}
|
||||
|
||||
private ListenerSet(
|
||||
CopyOnWriteArraySet<ListenerHolder<T, E>> listeners,
|
||||
Looper looper,
|
||||
Supplier<E> eventFlagsSupplier,
|
||||
IterationFinishedEvent<T, E> iterationFinishedEvent) {
|
||||
this.listeners = listeners;
|
||||
this.eventFlagsSupplier = eventFlagsSupplier;
|
||||
this.iterationFinishedEvent = iterationFinishedEvent;
|
||||
flushingEvents = new ArrayDeque<>();
|
||||
queuedEvents = new ArrayDeque<>();
|
||||
// It's safe to use "this" because we don't send a message before exiting the constructor.
|
||||
@SuppressWarnings("methodref.receiver.bound.invalid")
|
||||
Handler handler = Util.createHandler(looper, this::handleIterationFinished);
|
||||
iterationFinishedHandler = handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies the listener set.
|
||||
*
|
||||
* @param looper The new {@link Looper} for the copied listener set.
|
||||
* @param iterationFinishedEvent The new {@link IterationFinishedEvent} sent when all other events
|
||||
* sent during one {@link Looper} message queue iteration were handled by the listeners.
|
||||
* @return The copied listener set.
|
||||
*/
|
||||
@CheckResult
|
||||
public ListenerSet<T, E> copy(
|
||||
Looper looper, IterationFinishedEvent<T, E> iterationFinishedEvent) {
|
||||
return new ListenerSet<>(listeners, looper, eventFlagsSupplier, iterationFinishedEvent);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -69,7 +143,7 @@ public final class ListenerSet<T> {
|
|||
return;
|
||||
}
|
||||
Assertions.checkNotNull(listener);
|
||||
listeners.add(new ListenerHolder<T>(listener));
|
||||
listeners.add(new ListenerHolder<>(listener, eventFlagsSupplier));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -80,7 +154,7 @@ public final class ListenerSet<T> {
|
|||
* @param listener The listener to be removed.
|
||||
*/
|
||||
public void remove(T listener) {
|
||||
for (ListenerHolder<T> listenerHolder : listeners) {
|
||||
for (ListenerHolder<T, E> listenerHolder : listeners) {
|
||||
if (listenerHolder.listener.equals(listener)) {
|
||||
listenerHolder.release();
|
||||
listeners.remove(listenerHolder);
|
||||
|
|
@ -91,20 +165,29 @@ public final class ListenerSet<T> {
|
|||
/**
|
||||
* Adds an event that is sent to the listeners when {@link #flushEvents} is called.
|
||||
*
|
||||
* @param eventFlag An integer indicating the type of the event, or {@link C#INDEX_UNSET} to not
|
||||
* report this event with a flag.
|
||||
* @param event The event.
|
||||
*/
|
||||
public void queueEvent(Event<T> event) {
|
||||
CopyOnWriteArraySet<ListenerHolder<T>> listenerSnapshot = new CopyOnWriteArraySet<>(listeners);
|
||||
public void queueEvent(int eventFlag, Event<T> event) {
|
||||
CopyOnWriteArraySet<ListenerHolder<T, E>> listenerSnapshot =
|
||||
new CopyOnWriteArraySet<>(listeners);
|
||||
queuedEvents.add(
|
||||
() -> {
|
||||
for (ListenerHolder<T> holder : listenerSnapshot) {
|
||||
holder.invoke(event);
|
||||
for (ListenerHolder<T, E> holder : listenerSnapshot) {
|
||||
holder.invoke(eventFlag, event);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Notifies listeners of events previously enqueued with {@link #queueEvent(Event)}. */
|
||||
/** Notifies listeners of events previously enqueued with {@link #queueEvent(int, Event)}. */
|
||||
public void flushEvents() {
|
||||
if (queuedEvents.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (!iterationFinishedHandler.hasMessages(MSG_ITERATION_FINISHED)) {
|
||||
iterationFinishedHandler.obtainMessage(MSG_ITERATION_FINISHED).sendToTarget();
|
||||
}
|
||||
boolean recursiveFlushInProgress = !flushingEvents.isEmpty();
|
||||
flushingEvents.addAll(queuedEvents);
|
||||
queuedEvents.clear();
|
||||
|
|
@ -119,13 +202,15 @@ public final class ListenerSet<T> {
|
|||
}
|
||||
|
||||
/**
|
||||
* {@link #queueEvent(Event) Queues} a single event and immediately {@link #flushEvents() flushes}
|
||||
* the event queue to notify all listeners.
|
||||
* {@link #queueEvent(int, Event) Queues} a single event and immediately {@link #flushEvents()
|
||||
* flushes} the event queue to notify all listeners.
|
||||
*
|
||||
* @param eventFlag An integer flag indicating the type of the event, or {@link C#INDEX_UNSET} to
|
||||
* not report this event with a flag.
|
||||
* @param event The event.
|
||||
*/
|
||||
public void sendEvent(Event<T> event) {
|
||||
queueEvent(event);
|
||||
public void sendEvent(int eventFlag, Event<T> event) {
|
||||
queueEvent(eventFlag, event);
|
||||
flushEvents();
|
||||
}
|
||||
|
||||
|
|
@ -135,30 +220,59 @@ public final class ListenerSet<T> {
|
|||
* <p>This will ensure no events are sent to any listener after this method has been called.
|
||||
*/
|
||||
public void release() {
|
||||
for (ListenerHolder<T> listenerHolder : listeners) {
|
||||
for (ListenerHolder<T, E> listenerHolder : listeners) {
|
||||
listenerHolder.release();
|
||||
}
|
||||
listeners.clear();
|
||||
released = true;
|
||||
}
|
||||
|
||||
private static final class ListenerHolder<T> {
|
||||
private boolean handleIterationFinished(Message message) {
|
||||
for (ListenerHolder<T, E> holder : listeners) {
|
||||
holder.iterationFinished(eventFlagsSupplier, iterationFinishedEvent);
|
||||
if (iterationFinishedHandler.hasMessages(MSG_ITERATION_FINISHED)) {
|
||||
// The invocation above triggered new events (and thus scheduled a new message). We need to
|
||||
// stop here because this new message will take care of informing every listener about the
|
||||
// new update (including the ones already called here).
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static final class ListenerHolder<T, E extends MutableFlags> {
|
||||
|
||||
@Nonnull public final T listener;
|
||||
|
||||
private E eventsFlags;
|
||||
private boolean released;
|
||||
|
||||
public ListenerHolder(@Nonnull T listener) {
|
||||
public ListenerHolder(@Nonnull T listener, Supplier<E> eventFlagSupplier) {
|
||||
this.listener = listener;
|
||||
this.eventsFlags = eventFlagSupplier.get();
|
||||
}
|
||||
|
||||
public void release() {
|
||||
released = true;
|
||||
}
|
||||
|
||||
public void invoke(Event<T> event) {
|
||||
public void invoke(int eventFlag, Event<T> event) {
|
||||
if (!released) {
|
||||
event.invoke(listener);
|
||||
if (eventFlag != C.INDEX_UNSET) {
|
||||
eventsFlags.add(eventFlag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void iterationFinished(
|
||||
Supplier<E> eventFlagSupplier, IterationFinishedEvent<T, E> event) {
|
||||
if (!released) {
|
||||
// Reset flags before invoking the listener to ensure we keep all new flags that are set by
|
||||
// recursive events triggered from this callback.
|
||||
E flagToNotify = eventsFlags;
|
||||
eventsFlags = eventFlagSupplier.get();
|
||||
event.invoke(listener, flagToNotify);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -170,7 +284,7 @@ public final class ListenerSet<T> {
|
|||
if (other == null || getClass() != other.getClass()) {
|
||||
return false;
|
||||
}
|
||||
return listener.equals(((ListenerHolder<?>) other).listener);
|
||||
return listener.equals(((ListenerHolder<?, ?>) other).listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
* Copyright (C) 2020 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.exoplayer2.util;
|
||||
|
||||
import android.util.SparseBooleanArray;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* A set of integer flags.
|
||||
*
|
||||
* <p>Intended for usages where the number of flags may exceed 32 and can no longer be represented
|
||||
* by an IntDef.
|
||||
*/
|
||||
public class MutableFlags {
|
||||
|
||||
private final SparseBooleanArray flags;
|
||||
|
||||
/** Creates the set of flags. */
|
||||
public MutableFlags() {
|
||||
flags = new SparseBooleanArray();
|
||||
}
|
||||
|
||||
/** Clears all previously set flags. */
|
||||
public void clear() {
|
||||
flags.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a flag to the set.
|
||||
*
|
||||
* @param flag The flag to add.
|
||||
*/
|
||||
public void add(int flag) {
|
||||
flags.append(flag, /* value= */ true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the set contains the given flag.
|
||||
*
|
||||
* @param flag The flag.
|
||||
* @return Whether the set contains the flag.
|
||||
*/
|
||||
public boolean contains(int flag) {
|
||||
return flags.get(flag);
|
||||
}
|
||||
|
||||
/** Returns the number of flags in this set. */
|
||||
public int size() {
|
||||
return flags.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the flag at the given index.
|
||||
*
|
||||
* @param index The index. Must be between 0 (inclusive) and {@link #size()} (exclusive).
|
||||
* @return The flag at the given index.
|
||||
* @throws IllegalArgumentException If index is outside the allowed range.
|
||||
*/
|
||||
public int get(int index) {
|
||||
Assertions.checkArgument(index >= 0 && index < size());
|
||||
return flags.keyAt(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof MutableFlags)) {
|
||||
return false;
|
||||
}
|
||||
MutableFlags that = (MutableFlags) o;
|
||||
return flags.equals(that.flags);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return flags.hashCode();
|
||||
}
|
||||
}
|
||||
|
|
@ -28,9 +28,11 @@ import static org.junit.Assert.assertArrayEquals;
|
|||
import static org.junit.Assert.assertThrows;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.argThat;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.atLeastOnce;
|
||||
import static org.mockito.Mockito.inOrder;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
|
|
@ -57,6 +59,7 @@ import com.google.android.exoplayer2.audio.AudioAttributes;
|
|||
import com.google.android.exoplayer2.drm.DrmSessionEventListener;
|
||||
import com.google.android.exoplayer2.drm.DrmSessionManager;
|
||||
import com.google.android.exoplayer2.metadata.Metadata;
|
||||
import com.google.android.exoplayer2.metadata.id3.BinaryFrame;
|
||||
import com.google.android.exoplayer2.metadata.id3.TextInformationFrame;
|
||||
import com.google.android.exoplayer2.robolectric.TestPlayerRunHelper;
|
||||
import com.google.android.exoplayer2.source.ClippingMediaSource;
|
||||
|
|
@ -110,6 +113,7 @@ import com.google.android.exoplayer2.util.Assertions;
|
|||
import com.google.android.exoplayer2.util.Clock;
|
||||
import com.google.android.exoplayer2.util.MimeTypes;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Range;
|
||||
import java.io.IOException;
|
||||
|
|
@ -132,6 +136,7 @@ import org.mockito.ArgumentMatcher;
|
|||
import org.mockito.InOrder;
|
||||
import org.mockito.Mockito;
|
||||
import org.robolectric.shadows.ShadowAudioManager;
|
||||
import org.robolectric.shadows.ShadowLooper;
|
||||
|
||||
/** Unit test for {@link ExoPlayer}. */
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
|
|
@ -8843,6 +8848,96 @@ public final class ExoPlayerTest {
|
|||
assertThat(liveOffsetAtEnd).isIn(Range.closed(11_900L, 12_100L));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onStateChangedFlags_correspondToListenerCalls() throws Exception {
|
||||
ExoPlayer player = new TestExoPlayerBuilder(context).build();
|
||||
EventListener listener = mock(EventListener.class);
|
||||
player.addListener(listener);
|
||||
Format formatWithStaticMetadata =
|
||||
new Format.Builder()
|
||||
.setSampleMimeType(MimeTypes.VIDEO_H264)
|
||||
.setMetadata(new Metadata(new BinaryFrame(/* id= */ "", /* data= */ new byte[0])))
|
||||
.build();
|
||||
|
||||
// Set multiple values together.
|
||||
player.setMediaSource(
|
||||
new FakeMediaSource(new FakeTimeline(/* windowCount= */ 1), formatWithStaticMetadata));
|
||||
player.seekTo(2_000);
|
||||
player.setPlaybackParameters(new PlaybackParameters(/* speed= */ 2.0f));
|
||||
ShadowLooper.runMainLooperToNextTask();
|
||||
|
||||
verify(listener).onTimelineChanged(any(), anyInt());
|
||||
verify(listener).onMediaItemTransition(any(), anyInt());
|
||||
verify(listener).onPositionDiscontinuity(anyInt());
|
||||
verify(listener).onPlaybackParametersChanged(any());
|
||||
ArgumentCaptor<Player.Events> eventCaptor = ArgumentCaptor.forClass(Player.Events.class);
|
||||
verify(listener).onEvents(eq(player), eventCaptor.capture());
|
||||
Player.Events events = eventCaptor.getValue();
|
||||
assertThat(events.contains(Player.EVENT_TIMELINE_CHANGED)).isTrue();
|
||||
assertThat(events.contains(Player.EVENT_MEDIA_ITEM_TRANSITION)).isTrue();
|
||||
assertThat(events.contains(Player.EVENT_POSITION_DISCONTINUITY)).isTrue();
|
||||
assertThat(events.contains(Player.EVENT_PLAYBACK_PARAMETERS_CHANGED)).isTrue();
|
||||
|
||||
// Set values recursively.
|
||||
player.addListener(
|
||||
new EventListener() {
|
||||
@Override
|
||||
public void onRepeatModeChanged(int repeatMode) {
|
||||
player.setShuffleModeEnabled(true);
|
||||
}
|
||||
});
|
||||
player.setRepeatMode(Player.REPEAT_MODE_ONE);
|
||||
ShadowLooper.runMainLooperToNextTask();
|
||||
|
||||
verify(listener).onRepeatModeChanged(anyInt());
|
||||
verify(listener).onShuffleModeEnabledChanged(anyBoolean());
|
||||
verify(listener, times(2)).onEvents(eq(player), eventCaptor.capture());
|
||||
events = Iterables.getLast(eventCaptor.getAllValues());
|
||||
assertThat(events.contains(Player.EVENT_REPEAT_MODE_CHANGED)).isTrue();
|
||||
assertThat(events.contains(Player.EVENT_SHUFFLE_MODE_ENABLED_CHANGED)).isTrue();
|
||||
|
||||
// Ensure all other events are called (even though we can't control how exactly they are
|
||||
// combined together in onStateChanged calls).
|
||||
player.prepare();
|
||||
TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_READY);
|
||||
player.play();
|
||||
player.setMediaItem(MediaItem.fromUri("http://this-will-throw-an-exception.mp4"));
|
||||
TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_IDLE);
|
||||
ShadowLooper.runMainLooperToNextTask();
|
||||
|
||||
// Verify that all callbacks have been called at least once.
|
||||
verify(listener, atLeastOnce()).onTimelineChanged(any(), anyInt());
|
||||
verify(listener, atLeastOnce()).onMediaItemTransition(any(), anyInt());
|
||||
verify(listener, atLeastOnce()).onPositionDiscontinuity(anyInt());
|
||||
verify(listener, atLeastOnce()).onPlaybackParametersChanged(any());
|
||||
verify(listener, atLeastOnce()).onRepeatModeChanged(anyInt());
|
||||
verify(listener, atLeastOnce()).onShuffleModeEnabledChanged(anyBoolean());
|
||||
verify(listener, atLeastOnce()).onPlaybackStateChanged(anyInt());
|
||||
verify(listener, atLeastOnce()).onIsLoadingChanged(anyBoolean());
|
||||
verify(listener, atLeastOnce()).onTracksChanged(any(), any());
|
||||
verify(listener, atLeastOnce()).onStaticMetadataChanged(any());
|
||||
verify(listener, atLeastOnce()).onPlayWhenReadyChanged(anyBoolean(), anyInt());
|
||||
verify(listener, atLeastOnce()).onIsPlayingChanged(anyBoolean());
|
||||
verify(listener, atLeastOnce()).onPlayerError(any());
|
||||
|
||||
// Verify all the same events have been recorded with onStateChanged.
|
||||
verify(listener, atLeastOnce()).onEvents(eq(player), eventCaptor.capture());
|
||||
List<Player.Events> allEvents = eventCaptor.getAllValues();
|
||||
assertThat(containsEvent(allEvents, Player.EVENT_TIMELINE_CHANGED)).isTrue();
|
||||
assertThat(containsEvent(allEvents, Player.EVENT_MEDIA_ITEM_TRANSITION)).isTrue();
|
||||
assertThat(containsEvent(allEvents, Player.EVENT_POSITION_DISCONTINUITY)).isTrue();
|
||||
assertThat(containsEvent(allEvents, Player.EVENT_PLAYBACK_PARAMETERS_CHANGED)).isTrue();
|
||||
assertThat(containsEvent(allEvents, Player.EVENT_REPEAT_MODE_CHANGED)).isTrue();
|
||||
assertThat(containsEvent(allEvents, Player.EVENT_SHUFFLE_MODE_ENABLED_CHANGED)).isTrue();
|
||||
assertThat(containsEvent(allEvents, Player.EVENT_PLAYBACK_STATE_CHANGED)).isTrue();
|
||||
assertThat(containsEvent(allEvents, Player.EVENT_IS_LOADING_CHANGED)).isTrue();
|
||||
assertThat(containsEvent(allEvents, Player.EVENT_TRACKS_CHANGED)).isTrue();
|
||||
assertThat(containsEvent(allEvents, Player.EVENT_STATIC_METADATA_CHANGED)).isTrue();
|
||||
assertThat(containsEvent(allEvents, Player.EVENT_PLAY_WHEN_READY_CHANGED)).isTrue();
|
||||
assertThat(containsEvent(allEvents, Player.EVENT_IS_PLAYING_CHANGED)).isTrue();
|
||||
assertThat(containsEvent(allEvents, Player.EVENT_PLAYER_ERROR)).isTrue();
|
||||
}
|
||||
|
||||
// Internal methods.
|
||||
|
||||
private static ActionSchedule.Builder addSurfaceSwitch(ActionSchedule.Builder builder) {
|
||||
|
|
@ -8870,6 +8965,16 @@ public final class ExoPlayerTest {
|
|||
shadowOf(Looper.getMainLooper()).idle();
|
||||
}
|
||||
|
||||
private static boolean containsEvent(
|
||||
List<Player.Events> eventsList, @Player.EventFlags int event) {
|
||||
for (Player.Events events : eventsList) {
|
||||
if (events.contains(event)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Internal classes.
|
||||
|
||||
/* {@link FakeRenderer} that can sleep and be woken-up. */
|
||||
|
|
|
|||
|
|
@ -15,6 +15,32 @@
|
|||
*/
|
||||
package com.google.android.exoplayer2.analytics;
|
||||
|
||||
import static com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_AUDIO_DECODER_INITIALIZED;
|
||||
import static com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_AUDIO_DISABLED;
|
||||
import static com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_AUDIO_ENABLED;
|
||||
import static com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_AUDIO_INPUT_FORMAT_CHANGED;
|
||||
import static com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_AUDIO_POSITION_ADVANCING;
|
||||
import static com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_AUDIO_SESSION_ID;
|
||||
import static com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_DOWNSTREAM_FORMAT_CHANGED;
|
||||
import static com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_DRM_KEYS_LOADED;
|
||||
import static com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_DRM_SESSION_ACQUIRED;
|
||||
import static com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_DRM_SESSION_MANAGER_ERROR;
|
||||
import static com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_DRM_SESSION_RELEASED;
|
||||
import static com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_DROPPED_VIDEO_FRAMES;
|
||||
import static com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_IS_LOADING_CHANGED;
|
||||
import static com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_LOAD_COMPLETED;
|
||||
import static com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_LOAD_STARTED;
|
||||
import static com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_PLAYER_ERROR;
|
||||
import static com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_POSITION_DISCONTINUITY;
|
||||
import static com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_RENDERED_FIRST_FRAME;
|
||||
import static com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_TIMELINE_CHANGED;
|
||||
import static com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_TRACKS_CHANGED;
|
||||
import static com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_VIDEO_DECODER_INITIALIZED;
|
||||
import static com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_VIDEO_DISABLED;
|
||||
import static com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_VIDEO_ENABLED;
|
||||
import static com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_VIDEO_FRAME_PROCESSING_OFFSET;
|
||||
import static com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_VIDEO_INPUT_FORMAT_CHANGED;
|
||||
import static com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_VIDEO_SIZE_CHANGED;
|
||||
import static com.google.android.exoplayer2.testutil.FakeSampleStream.FakeSampleStreamItem.END_OF_STREAM_ITEM;
|
||||
import static com.google.android.exoplayer2.testutil.FakeSampleStream.FakeSampleStreamItem.oneByteSample;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
|
@ -23,6 +49,7 @@ import static org.mockito.ArgumentMatchers.eq;
|
|||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.spy;
|
||||
|
||||
import android.os.Looper;
|
||||
import android.view.Surface;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
|
@ -85,51 +112,14 @@ public final class AnalyticsCollectorTest {
|
|||
|
||||
private static final String TAG = "AnalyticsCollectorTest";
|
||||
|
||||
private static final int EVENT_PLAYER_STATE_CHANGED = 0;
|
||||
private static final int EVENT_TIMELINE_CHANGED = 1;
|
||||
private static final int EVENT_POSITION_DISCONTINUITY = 2;
|
||||
private static final int EVENT_SEEK_STARTED = 3;
|
||||
private static final int EVENT_SEEK_PROCESSED = 4;
|
||||
private static final int EVENT_PLAYBACK_PARAMETERS_CHANGED = 5;
|
||||
private static final int EVENT_REPEAT_MODE_CHANGED = 6;
|
||||
private static final int EVENT_SHUFFLE_MODE_CHANGED = 7;
|
||||
private static final int EVENT_LOADING_CHANGED = 8;
|
||||
private static final int EVENT_PLAYER_ERROR = 9;
|
||||
private static final int EVENT_TRACKS_CHANGED = 10;
|
||||
private static final int EVENT_LOAD_STARTED = 11;
|
||||
private static final int EVENT_LOAD_COMPLETED = 12;
|
||||
private static final int EVENT_LOAD_CANCELED = 13;
|
||||
private static final int EVENT_LOAD_ERROR = 14;
|
||||
private static final int EVENT_DOWNSTREAM_FORMAT_CHANGED = 15;
|
||||
private static final int EVENT_UPSTREAM_DISCARDED = 16;
|
||||
private static final int EVENT_BANDWIDTH_ESTIMATE = 17;
|
||||
private static final int EVENT_SURFACE_SIZE_CHANGED = 18;
|
||||
private static final int EVENT_METADATA = 19;
|
||||
private static final int EVENT_DECODER_ENABLED = 20;
|
||||
private static final int EVENT_DECODER_INIT = 21;
|
||||
private static final int EVENT_DECODER_FORMAT_CHANGED = 22;
|
||||
private static final int EVENT_DECODER_DISABLED = 23;
|
||||
private static final int EVENT_AUDIO_ENABLED = 24;
|
||||
private static final int EVENT_AUDIO_DECODER_INIT = 25;
|
||||
private static final int EVENT_AUDIO_INPUT_FORMAT_CHANGED = 26;
|
||||
private static final int EVENT_AUDIO_DISABLED = 27;
|
||||
private static final int EVENT_AUDIO_SESSION_ID = 28;
|
||||
private static final int EVENT_AUDIO_POSITION_ADVANCING = 29;
|
||||
private static final int EVENT_AUDIO_UNDERRUN = 30;
|
||||
private static final int EVENT_VIDEO_ENABLED = 31;
|
||||
private static final int EVENT_VIDEO_DECODER_INIT = 32;
|
||||
private static final int EVENT_VIDEO_INPUT_FORMAT_CHANGED = 33;
|
||||
private static final int EVENT_DROPPED_FRAMES = 34;
|
||||
private static final int EVENT_VIDEO_DISABLED = 35;
|
||||
private static final int EVENT_RENDERED_FIRST_FRAME = 36;
|
||||
private static final int EVENT_VIDEO_FRAME_PROCESSING_OFFSET = 37;
|
||||
private static final int EVENT_VIDEO_SIZE_CHANGED = 38;
|
||||
private static final int EVENT_DRM_KEYS_LOADED = 39;
|
||||
private static final int EVENT_DRM_ERROR = 40;
|
||||
private static final int EVENT_DRM_KEYS_RESTORED = 41;
|
||||
private static final int EVENT_DRM_KEYS_REMOVED = 42;
|
||||
private static final int EVENT_DRM_SESSION_ACQUIRED = 43;
|
||||
private static final int EVENT_DRM_SESSION_RELEASED = 44;
|
||||
// Deprecated event constants.
|
||||
private static final long EVENT_PLAYER_STATE_CHANGED = 1L << 63;
|
||||
private static final long EVENT_SEEK_STARTED = 1L << 62;
|
||||
private static final long EVENT_SEEK_PROCESSED = 1L << 61;
|
||||
private static final long EVENT_DECODER_ENABLED = 1L << 60;
|
||||
private static final long EVENT_DECODER_INIT = 1L << 59;
|
||||
private static final long EVENT_DECODER_FORMAT_CHANGED = 1L << 58;
|
||||
private static final long EVENT_DECODER_DISABLED = 1L << 57;
|
||||
|
||||
private static final UUID DRM_SCHEME_UUID =
|
||||
UUID.nameUUIDFromBytes(TestUtil.createByteArray(7, 8, 9));
|
||||
|
|
@ -209,7 +199,7 @@ public final class AnalyticsCollectorTest {
|
|||
assertThat(listener.getEvents(EVENT_TIMELINE_CHANGED))
|
||||
.containsExactly(WINDOW_0 /* PLAYLIST_CHANGED */, WINDOW_0 /* SOURCE_UPDATE */)
|
||||
.inOrder();
|
||||
assertThat(listener.getEvents(EVENT_LOADING_CHANGED))
|
||||
assertThat(listener.getEvents(EVENT_IS_LOADING_CHANGED))
|
||||
.containsExactly(period0 /* started */, period0 /* stopped */)
|
||||
.inOrder();
|
||||
assertThat(listener.getEvents(EVENT_TRACKS_CHANGED)).containsExactly(period0);
|
||||
|
|
@ -232,14 +222,14 @@ public final class AnalyticsCollectorTest {
|
|||
.containsExactly(period0 /* audio */, period0 /* video */)
|
||||
.inOrder();
|
||||
assertThat(listener.getEvents(EVENT_AUDIO_ENABLED)).containsExactly(period0);
|
||||
assertThat(listener.getEvents(EVENT_AUDIO_DECODER_INIT)).containsExactly(period0);
|
||||
assertThat(listener.getEvents(EVENT_AUDIO_DECODER_INITIALIZED)).containsExactly(period0);
|
||||
assertThat(listener.getEvents(EVENT_AUDIO_INPUT_FORMAT_CHANGED)).containsExactly(period0);
|
||||
assertThat(listener.getEvents(EVENT_AUDIO_SESSION_ID)).containsExactly(period0);
|
||||
assertThat(listener.getEvents(EVENT_AUDIO_POSITION_ADVANCING)).containsExactly(period0);
|
||||
assertThat(listener.getEvents(EVENT_VIDEO_ENABLED)).containsExactly(period0);
|
||||
assertThat(listener.getEvents(EVENT_VIDEO_DECODER_INIT)).containsExactly(period0);
|
||||
assertThat(listener.getEvents(EVENT_VIDEO_DECODER_INITIALIZED)).containsExactly(period0);
|
||||
assertThat(listener.getEvents(EVENT_VIDEO_INPUT_FORMAT_CHANGED)).containsExactly(period0);
|
||||
assertThat(listener.getEvents(EVENT_DROPPED_FRAMES)).containsExactly(period0);
|
||||
assertThat(listener.getEvents(EVENT_DROPPED_VIDEO_FRAMES)).containsExactly(period0);
|
||||
assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED)).containsExactly(period0);
|
||||
assertThat(listener.getEvents(EVENT_RENDERED_FIRST_FRAME)).containsExactly(period0);
|
||||
assertThat(listener.getEvents(EVENT_VIDEO_FRAME_PROCESSING_OFFSET)).containsExactly(period0);
|
||||
|
|
@ -272,7 +262,7 @@ public final class AnalyticsCollectorTest {
|
|||
.containsExactly(WINDOW_0 /* PLAYLIST_CHANGED */, period0 /* SOURCE_UPDATE */)
|
||||
.inOrder();
|
||||
assertThat(listener.getEvents(EVENT_POSITION_DISCONTINUITY)).containsExactly(period1);
|
||||
assertThat(listener.getEvents(EVENT_LOADING_CHANGED))
|
||||
assertThat(listener.getEvents(EVENT_IS_LOADING_CHANGED))
|
||||
.containsExactly(period0, period0, period0, period0)
|
||||
.inOrder();
|
||||
assertThat(listener.getEvents(EVENT_TRACKS_CHANGED))
|
||||
|
|
@ -308,7 +298,7 @@ public final class AnalyticsCollectorTest {
|
|||
period0 /* audio */, period0 /* video */, period1 /* audio */, period1 /* video */)
|
||||
.inOrder();
|
||||
assertThat(listener.getEvents(EVENT_AUDIO_ENABLED)).containsExactly(period0);
|
||||
assertThat(listener.getEvents(EVENT_AUDIO_DECODER_INIT))
|
||||
assertThat(listener.getEvents(EVENT_AUDIO_DECODER_INITIALIZED))
|
||||
.containsExactly(period0, period1)
|
||||
.inOrder();
|
||||
assertThat(listener.getEvents(EVENT_AUDIO_INPUT_FORMAT_CHANGED))
|
||||
|
|
@ -317,13 +307,13 @@ public final class AnalyticsCollectorTest {
|
|||
assertThat(listener.getEvents(EVENT_AUDIO_SESSION_ID)).containsExactly(period0);
|
||||
assertThat(listener.getEvents(EVENT_AUDIO_POSITION_ADVANCING)).containsExactly(period0);
|
||||
assertThat(listener.getEvents(EVENT_VIDEO_ENABLED)).containsExactly(period0);
|
||||
assertThat(listener.getEvents(EVENT_VIDEO_DECODER_INIT))
|
||||
assertThat(listener.getEvents(EVENT_VIDEO_DECODER_INITIALIZED))
|
||||
.containsExactly(period0, period1)
|
||||
.inOrder();
|
||||
assertThat(listener.getEvents(EVENT_VIDEO_INPUT_FORMAT_CHANGED))
|
||||
.containsExactly(period0, period1)
|
||||
.inOrder();
|
||||
assertThat(listener.getEvents(EVENT_DROPPED_FRAMES)).containsExactly(period1);
|
||||
assertThat(listener.getEvents(EVENT_DROPPED_VIDEO_FRAMES)).containsExactly(period1);
|
||||
assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED))
|
||||
.containsExactly(period0, period1)
|
||||
.inOrder();
|
||||
|
|
@ -354,7 +344,7 @@ public final class AnalyticsCollectorTest {
|
|||
.containsExactly(WINDOW_0 /* PLAYLIST_CHANGED */, period0 /* SOURCE_UPDATE */)
|
||||
.inOrder();
|
||||
assertThat(listener.getEvents(EVENT_POSITION_DISCONTINUITY)).containsExactly(period1);
|
||||
assertThat(listener.getEvents(EVENT_LOADING_CHANGED))
|
||||
assertThat(listener.getEvents(EVENT_IS_LOADING_CHANGED))
|
||||
.containsExactly(period0, period0, period0, period0)
|
||||
.inOrder();
|
||||
assertThat(listener.getEvents(EVENT_TRACKS_CHANGED))
|
||||
|
|
@ -388,15 +378,15 @@ public final class AnalyticsCollectorTest {
|
|||
.inOrder();
|
||||
assertThat(listener.getEvents(EVENT_DECODER_DISABLED)).containsExactly(period0 /* video */);
|
||||
assertThat(listener.getEvents(EVENT_AUDIO_ENABLED)).containsExactly(period1);
|
||||
assertThat(listener.getEvents(EVENT_AUDIO_DECODER_INIT)).containsExactly(period1);
|
||||
assertThat(listener.getEvents(EVENT_AUDIO_DECODER_INITIALIZED)).containsExactly(period1);
|
||||
assertThat(listener.getEvents(EVENT_AUDIO_INPUT_FORMAT_CHANGED)).containsExactly(period1);
|
||||
assertThat(listener.getEvents(EVENT_AUDIO_SESSION_ID)).containsExactly(period1);
|
||||
assertThat(listener.getEvents(EVENT_AUDIO_POSITION_ADVANCING)).containsExactly(period1);
|
||||
assertThat(listener.getEvents(EVENT_VIDEO_ENABLED)).containsExactly(period0);
|
||||
assertThat(listener.getEvents(EVENT_VIDEO_DECODER_INIT)).containsExactly(period0);
|
||||
assertThat(listener.getEvents(EVENT_VIDEO_DECODER_INITIALIZED)).containsExactly(period0);
|
||||
assertThat(listener.getEvents(EVENT_VIDEO_INPUT_FORMAT_CHANGED)).containsExactly(period0);
|
||||
assertThat(listener.getEvents(EVENT_VIDEO_DISABLED)).containsExactly(period0);
|
||||
assertThat(listener.getEvents(EVENT_DROPPED_FRAMES)).containsExactly(period0);
|
||||
assertThat(listener.getEvents(EVENT_DROPPED_VIDEO_FRAMES)).containsExactly(period0);
|
||||
assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED)).containsExactly(period0);
|
||||
assertThat(listener.getEvents(EVENT_RENDERED_FIRST_FRAME)).containsExactly(period0);
|
||||
assertThat(listener.getEvents(EVENT_VIDEO_FRAME_PROCESSING_OFFSET)).containsExactly(period0);
|
||||
|
|
@ -443,7 +433,7 @@ public final class AnalyticsCollectorTest {
|
|||
assertThat(listener.getEvents(EVENT_POSITION_DISCONTINUITY)).containsExactly(period1);
|
||||
assertThat(listener.getEvents(EVENT_SEEK_STARTED)).containsExactly(period0);
|
||||
assertThat(listener.getEvents(EVENT_SEEK_PROCESSED)).containsExactly(period1);
|
||||
List<EventWindowAndPeriodId> loadingEvents = listener.getEvents(EVENT_LOADING_CHANGED);
|
||||
List<EventWindowAndPeriodId> loadingEvents = listener.getEvents(EVENT_IS_LOADING_CHANGED);
|
||||
assertThat(loadingEvents).hasSize(4);
|
||||
assertThat(loadingEvents).containsAtLeast(period0, period0).inOrder();
|
||||
assertThat(listener.getEvents(EVENT_TRACKS_CHANGED))
|
||||
|
|
@ -479,7 +469,7 @@ public final class AnalyticsCollectorTest {
|
|||
.containsExactly(period0 /* video */, period0 /* audio */)
|
||||
.inOrder();
|
||||
assertThat(listener.getEvents(EVENT_AUDIO_ENABLED)).containsExactly(period0, period1).inOrder();
|
||||
assertThat(listener.getEvents(EVENT_AUDIO_DECODER_INIT))
|
||||
assertThat(listener.getEvents(EVENT_AUDIO_DECODER_INITIALIZED))
|
||||
.containsExactly(period0, period1)
|
||||
.inOrder();
|
||||
assertThat(listener.getEvents(EVENT_AUDIO_INPUT_FORMAT_CHANGED))
|
||||
|
|
@ -493,7 +483,7 @@ public final class AnalyticsCollectorTest {
|
|||
.inOrder();
|
||||
assertThat(listener.getEvents(EVENT_AUDIO_DISABLED)).containsExactly(period0);
|
||||
assertThat(listener.getEvents(EVENT_VIDEO_ENABLED)).containsExactly(period0);
|
||||
assertThat(listener.getEvents(EVENT_VIDEO_DECODER_INIT)).containsExactly(period0);
|
||||
assertThat(listener.getEvents(EVENT_VIDEO_DECODER_INITIALIZED)).containsExactly(period0);
|
||||
assertThat(listener.getEvents(EVENT_VIDEO_INPUT_FORMAT_CHANGED)).containsExactly(period0);
|
||||
assertThat(listener.getEvents(EVENT_VIDEO_DISABLED)).containsExactly(period0);
|
||||
assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED)).containsExactly(period0);
|
||||
|
|
@ -544,7 +534,7 @@ public final class AnalyticsCollectorTest {
|
|||
.inOrder();
|
||||
assertThat(listener.getEvents(EVENT_SEEK_STARTED)).containsExactly(period0);
|
||||
assertThat(listener.getEvents(EVENT_SEEK_PROCESSED)).containsExactly(period0);
|
||||
assertThat(listener.getEvents(EVENT_LOADING_CHANGED))
|
||||
assertThat(listener.getEvents(EVENT_IS_LOADING_CHANGED))
|
||||
.containsExactly(period0, period0, period0, period0, period0, period0)
|
||||
.inOrder();
|
||||
assertThat(listener.getEvents(EVENT_TRACKS_CHANGED))
|
||||
|
|
@ -582,7 +572,7 @@ public final class AnalyticsCollectorTest {
|
|||
assertThat(listener.getEvents(EVENT_AUDIO_ENABLED))
|
||||
.containsExactly(period1, period1Seq2)
|
||||
.inOrder();
|
||||
assertThat(listener.getEvents(EVENT_AUDIO_DECODER_INIT))
|
||||
assertThat(listener.getEvents(EVENT_AUDIO_DECODER_INITIALIZED))
|
||||
.containsExactly(period1Seq1, period1Seq2)
|
||||
.inOrder();
|
||||
assertThat(listener.getEvents(EVENT_AUDIO_INPUT_FORMAT_CHANGED))
|
||||
|
|
@ -596,14 +586,14 @@ public final class AnalyticsCollectorTest {
|
|||
.inOrder();
|
||||
assertThat(listener.getEvents(EVENT_AUDIO_DISABLED)).containsExactly(period0);
|
||||
assertThat(listener.getEvents(EVENT_VIDEO_ENABLED)).containsExactly(period0, period0);
|
||||
assertThat(listener.getEvents(EVENT_VIDEO_DECODER_INIT))
|
||||
assertThat(listener.getEvents(EVENT_VIDEO_DECODER_INITIALIZED))
|
||||
.containsExactly(period0, period1Seq1, period1Seq2)
|
||||
.inOrder();
|
||||
assertThat(listener.getEvents(EVENT_VIDEO_INPUT_FORMAT_CHANGED))
|
||||
.containsExactly(period0, period1Seq1, period1Seq2)
|
||||
.inOrder();
|
||||
assertThat(listener.getEvents(EVENT_VIDEO_DISABLED)).containsExactly(period0);
|
||||
assertThat(listener.getEvents(EVENT_DROPPED_FRAMES))
|
||||
assertThat(listener.getEvents(EVENT_DROPPED_VIDEO_FRAMES))
|
||||
.containsExactly(period0, period1Seq2)
|
||||
.inOrder();
|
||||
assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED))
|
||||
|
|
@ -662,7 +652,7 @@ public final class AnalyticsCollectorTest {
|
|||
WINDOW_0 /* SOURCE_UPDATE */,
|
||||
WINDOW_0 /* PLAYLIST_CHANGE */,
|
||||
WINDOW_0 /* SOURCE_UPDATE */);
|
||||
assertThat(listener.getEvents(EVENT_LOADING_CHANGED))
|
||||
assertThat(listener.getEvents(EVENT_IS_LOADING_CHANGED))
|
||||
.containsExactly(period0Seq0, period0Seq0, period0Seq1, period0Seq1)
|
||||
.inOrder();
|
||||
assertThat(listener.getEvents(EVENT_TRACKS_CHANGED))
|
||||
|
|
@ -699,14 +689,14 @@ public final class AnalyticsCollectorTest {
|
|||
assertThat(listener.getEvents(EVENT_VIDEO_ENABLED))
|
||||
.containsExactly(period0Seq0, period0Seq1)
|
||||
.inOrder();
|
||||
assertThat(listener.getEvents(EVENT_VIDEO_DECODER_INIT))
|
||||
assertThat(listener.getEvents(EVENT_VIDEO_DECODER_INITIALIZED))
|
||||
.containsExactly(period0Seq0, period0Seq1)
|
||||
.inOrder();
|
||||
assertThat(listener.getEvents(EVENT_VIDEO_INPUT_FORMAT_CHANGED))
|
||||
.containsExactly(period0Seq0, period0Seq1)
|
||||
.inOrder();
|
||||
assertThat(listener.getEvents(EVENT_VIDEO_DISABLED)).containsExactly(period0Seq0);
|
||||
assertThat(listener.getEvents(EVENT_DROPPED_FRAMES)).containsExactly(period0Seq1);
|
||||
assertThat(listener.getEvents(EVENT_DROPPED_VIDEO_FRAMES)).containsExactly(period0Seq1);
|
||||
assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED))
|
||||
.containsExactly(period0Seq0, period0Seq1)
|
||||
.inOrder();
|
||||
|
|
@ -755,7 +745,7 @@ public final class AnalyticsCollectorTest {
|
|||
assertThat(listener.getEvents(EVENT_POSITION_DISCONTINUITY)).containsExactly(period0Seq0);
|
||||
assertThat(listener.getEvents(EVENT_SEEK_STARTED)).containsExactly(period0Seq0);
|
||||
assertThat(listener.getEvents(EVENT_SEEK_PROCESSED)).containsExactly(period0Seq0);
|
||||
assertThat(listener.getEvents(EVENT_LOADING_CHANGED))
|
||||
assertThat(listener.getEvents(EVENT_IS_LOADING_CHANGED))
|
||||
.containsExactly(period0Seq0, period0Seq0, period0Seq0, period0Seq0);
|
||||
assertThat(listener.getEvents(EVENT_PLAYER_ERROR)).containsExactly(period0Seq0);
|
||||
assertThat(listener.getEvents(EVENT_TRACKS_CHANGED)).containsExactly(period0Seq0, period0Seq0);
|
||||
|
|
@ -781,12 +771,12 @@ public final class AnalyticsCollectorTest {
|
|||
.containsExactly(period0Seq0, period0Seq0);
|
||||
assertThat(listener.getEvents(EVENT_DECODER_DISABLED)).containsExactly(period0Seq0);
|
||||
assertThat(listener.getEvents(EVENT_VIDEO_ENABLED)).containsExactly(period0Seq0, period0Seq0);
|
||||
assertThat(listener.getEvents(EVENT_VIDEO_DECODER_INIT))
|
||||
assertThat(listener.getEvents(EVENT_VIDEO_DECODER_INITIALIZED))
|
||||
.containsExactly(period0Seq0, period0Seq0);
|
||||
assertThat(listener.getEvents(EVENT_VIDEO_INPUT_FORMAT_CHANGED))
|
||||
.containsExactly(period0Seq0, period0Seq0);
|
||||
assertThat(listener.getEvents(EVENT_VIDEO_DISABLED)).containsExactly(period0Seq0);
|
||||
assertThat(listener.getEvents(EVENT_DROPPED_FRAMES)).containsExactly(period0Seq0);
|
||||
assertThat(listener.getEvents(EVENT_DROPPED_VIDEO_FRAMES)).containsExactly(period0Seq0);
|
||||
assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED))
|
||||
.containsExactly(period0Seq0, period0Seq0);
|
||||
assertThat(listener.getEvents(EVENT_RENDERED_FIRST_FRAME))
|
||||
|
|
@ -840,7 +830,7 @@ public final class AnalyticsCollectorTest {
|
|||
window0Period1Seq0 /* SOURCE_UPDATE (concatenated timeline replaces placeholder) */,
|
||||
period1Seq0 /* SOURCE_UPDATE (child sources in concatenating source moved) */)
|
||||
.inOrder();
|
||||
assertThat(listener.getEvents(EVENT_LOADING_CHANGED))
|
||||
assertThat(listener.getEvents(EVENT_IS_LOADING_CHANGED))
|
||||
.containsExactly(
|
||||
window0Period1Seq0, window0Period1Seq0, window0Period1Seq0, window0Period1Seq0);
|
||||
assertThat(listener.getEvents(EVENT_TRACKS_CHANGED)).containsExactly(window0Period1Seq0);
|
||||
|
|
@ -868,14 +858,14 @@ public final class AnalyticsCollectorTest {
|
|||
assertThat(listener.getEvents(EVENT_VIDEO_ENABLED))
|
||||
.containsExactly(window0Period1Seq0, window0Period1Seq0)
|
||||
.inOrder();
|
||||
assertThat(listener.getEvents(EVENT_VIDEO_DECODER_INIT))
|
||||
assertThat(listener.getEvents(EVENT_VIDEO_DECODER_INITIALIZED))
|
||||
.containsExactly(window0Period1Seq0, window1Period0Seq1)
|
||||
.inOrder();
|
||||
assertThat(listener.getEvents(EVENT_VIDEO_INPUT_FORMAT_CHANGED))
|
||||
.containsExactly(window0Period1Seq0, window1Period0Seq1)
|
||||
.inOrder();
|
||||
assertThat(listener.getEvents(EVENT_VIDEO_DISABLED)).containsExactly(window0Period1Seq0);
|
||||
assertThat(listener.getEvents(EVENT_DROPPED_FRAMES))
|
||||
assertThat(listener.getEvents(EVENT_DROPPED_VIDEO_FRAMES))
|
||||
.containsExactly(window0Period1Seq0, period1Seq0)
|
||||
.inOrder();
|
||||
assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED))
|
||||
|
|
@ -938,7 +928,7 @@ public final class AnalyticsCollectorTest {
|
|||
period0Seq0 /* SOURCE_UPDATE (second item) */,
|
||||
period0Seq1 /* PLAYLIST_CHANGED (remove) */)
|
||||
.inOrder();
|
||||
assertThat(listener.getEvents(EVENT_LOADING_CHANGED))
|
||||
assertThat(listener.getEvents(EVENT_IS_LOADING_CHANGED))
|
||||
.containsExactly(period0Seq0, period0Seq0, period0Seq0, period0Seq0);
|
||||
assertThat(listener.getEvents(EVENT_TRACKS_CHANGED))
|
||||
.containsExactly(period0Seq0, period0Seq1, period0Seq1)
|
||||
|
|
@ -966,14 +956,14 @@ public final class AnalyticsCollectorTest {
|
|||
assertThat(listener.getEvents(EVENT_VIDEO_ENABLED))
|
||||
.containsExactly(period0Seq0, period0Seq1, period0Seq1)
|
||||
.inOrder();
|
||||
assertThat(listener.getEvents(EVENT_VIDEO_DECODER_INIT))
|
||||
assertThat(listener.getEvents(EVENT_VIDEO_DECODER_INITIALIZED))
|
||||
.containsExactly(period0Seq0, period0Seq1)
|
||||
.inOrder();
|
||||
assertThat(listener.getEvents(EVENT_VIDEO_INPUT_FORMAT_CHANGED))
|
||||
.containsExactly(period0Seq0, period0Seq1)
|
||||
.inOrder();
|
||||
assertThat(listener.getEvents(EVENT_VIDEO_DISABLED)).containsExactly(period0Seq0, period0Seq0);
|
||||
assertThat(listener.getEvents(EVENT_DROPPED_FRAMES)).containsExactly(period0Seq1);
|
||||
assertThat(listener.getEvents(EVENT_DROPPED_VIDEO_FRAMES)).containsExactly(period0Seq1);
|
||||
assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED))
|
||||
.containsExactly(period0Seq0, period0Seq1)
|
||||
.inOrder();
|
||||
|
|
@ -1150,7 +1140,7 @@ public final class AnalyticsCollectorTest {
|
|||
.containsExactly(
|
||||
contentAfterPreroll, midrollAd, contentAfterMidroll, postrollAd, contentAfterPostroll)
|
||||
.inOrder();
|
||||
assertThat(listener.getEvents(EVENT_LOADING_CHANGED))
|
||||
assertThat(listener.getEvents(EVENT_IS_LOADING_CHANGED))
|
||||
.containsExactly(
|
||||
prerollAd, prerollAd, prerollAd, prerollAd, prerollAd, prerollAd, prerollAd, prerollAd,
|
||||
prerollAd, prerollAd, prerollAd, prerollAd)
|
||||
|
|
@ -1213,7 +1203,7 @@ public final class AnalyticsCollectorTest {
|
|||
contentAfterPostroll)
|
||||
.inOrder();
|
||||
assertThat(listener.getEvents(EVENT_VIDEO_ENABLED)).containsExactly(prerollAd);
|
||||
assertThat(listener.getEvents(EVENT_VIDEO_DECODER_INIT))
|
||||
assertThat(listener.getEvents(EVENT_VIDEO_DECODER_INITIALIZED))
|
||||
.containsExactly(
|
||||
prerollAd,
|
||||
contentAfterPreroll,
|
||||
|
|
@ -1231,7 +1221,7 @@ public final class AnalyticsCollectorTest {
|
|||
postrollAd,
|
||||
contentAfterPostroll)
|
||||
.inOrder();
|
||||
assertThat(listener.getEvents(EVENT_DROPPED_FRAMES))
|
||||
assertThat(listener.getEvents(EVENT_DROPPED_VIDEO_FRAMES))
|
||||
.containsExactly(contentAfterPreroll, contentAfterMidroll, contentAfterPostroll)
|
||||
.inOrder();
|
||||
assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED))
|
||||
|
|
@ -1347,7 +1337,7 @@ public final class AnalyticsCollectorTest {
|
|||
.inOrder();
|
||||
assertThat(listener.getEvents(EVENT_SEEK_STARTED)).containsExactly(contentBeforeMidroll);
|
||||
assertThat(listener.getEvents(EVENT_SEEK_PROCESSED)).containsExactly(contentAfterMidroll);
|
||||
assertThat(listener.getEvents(EVENT_LOADING_CHANGED))
|
||||
assertThat(listener.getEvents(EVENT_IS_LOADING_CHANGED))
|
||||
.containsExactly(
|
||||
contentBeforeMidroll,
|
||||
contentBeforeMidroll,
|
||||
|
|
@ -1392,14 +1382,14 @@ public final class AnalyticsCollectorTest {
|
|||
assertThat(listener.getEvents(EVENT_VIDEO_ENABLED))
|
||||
.containsExactly(contentBeforeMidroll, midrollAd)
|
||||
.inOrder();
|
||||
assertThat(listener.getEvents(EVENT_VIDEO_DECODER_INIT))
|
||||
assertThat(listener.getEvents(EVENT_VIDEO_DECODER_INITIALIZED))
|
||||
.containsExactly(contentBeforeMidroll, midrollAd, contentAfterMidroll)
|
||||
.inOrder();
|
||||
assertThat(listener.getEvents(EVENT_VIDEO_INPUT_FORMAT_CHANGED))
|
||||
.containsExactly(contentBeforeMidroll, midrollAd, contentAfterMidroll)
|
||||
.inOrder();
|
||||
assertThat(listener.getEvents(EVENT_VIDEO_DISABLED)).containsExactly(contentBeforeMidroll);
|
||||
assertThat(listener.getEvents(EVENT_DROPPED_FRAMES)).containsExactly(contentAfterMidroll);
|
||||
assertThat(listener.getEvents(EVENT_DROPPED_VIDEO_FRAMES)).containsExactly(contentAfterMidroll);
|
||||
assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED))
|
||||
.containsExactly(contentBeforeMidroll, midrollAd, contentAfterMidroll)
|
||||
.inOrder();
|
||||
|
|
@ -1442,7 +1432,7 @@ public final class AnalyticsCollectorTest {
|
|||
TestAnalyticsListener listener = runAnalyticsTest(mediaSource);
|
||||
|
||||
populateEventIds(listener.lastReportedTimeline);
|
||||
assertThat(listener.getEvents(EVENT_DRM_ERROR)).isEmpty();
|
||||
assertThat(listener.getEvents(EVENT_DRM_SESSION_MANAGER_ERROR)).isEmpty();
|
||||
assertThat(listener.getEvents(EVENT_DRM_SESSION_ACQUIRED)).containsExactly(period0);
|
||||
assertThat(listener.getEvents(EVENT_DRM_KEYS_LOADED)).containsExactly(period0);
|
||||
// The release event is lost because it's posted to "ExoPlayerTest thread" after that thread
|
||||
|
|
@ -1459,7 +1449,7 @@ public final class AnalyticsCollectorTest {
|
|||
TestAnalyticsListener listener = runAnalyticsTest(mediaSource);
|
||||
|
||||
populateEventIds(listener.lastReportedTimeline);
|
||||
assertThat(listener.getEvents(EVENT_DRM_ERROR)).isEmpty();
|
||||
assertThat(listener.getEvents(EVENT_DRM_SESSION_MANAGER_ERROR)).isEmpty();
|
||||
assertThat(listener.getEvents(EVENT_DRM_SESSION_ACQUIRED))
|
||||
.containsExactly(period0, period1)
|
||||
.inOrder();
|
||||
|
|
@ -1481,7 +1471,7 @@ public final class AnalyticsCollectorTest {
|
|||
TestAnalyticsListener listener = runAnalyticsTest(mediaSource);
|
||||
|
||||
populateEventIds(listener.lastReportedTimeline);
|
||||
assertThat(listener.getEvents(EVENT_DRM_ERROR)).isEmpty();
|
||||
assertThat(listener.getEvents(EVENT_DRM_SESSION_MANAGER_ERROR)).isEmpty();
|
||||
assertThat(listener.getEvents(EVENT_DRM_SESSION_ACQUIRED))
|
||||
.containsExactly(period0, period1)
|
||||
.inOrder();
|
||||
|
|
@ -1502,7 +1492,7 @@ public final class AnalyticsCollectorTest {
|
|||
TestAnalyticsListener listener = runAnalyticsTest(mediaSource);
|
||||
|
||||
populateEventIds(listener.lastReportedTimeline);
|
||||
assertThat(listener.getEvents(EVENT_DRM_ERROR)).containsExactly(period0);
|
||||
assertThat(listener.getEvents(EVENT_DRM_SESSION_MANAGER_ERROR)).containsExactly(period0);
|
||||
assertThat(listener.getEvents(EVENT_PLAYER_ERROR)).containsExactly(period0);
|
||||
}
|
||||
|
||||
|
|
@ -1657,7 +1647,8 @@ public final class AnalyticsCollectorTest {
|
|||
public void recursiveListenerInvocation_arrivesInCorrectOrder() {
|
||||
AnalyticsCollector analyticsCollector = new AnalyticsCollector(Clock.DEFAULT);
|
||||
analyticsCollector.setPlayer(
|
||||
new SimpleExoPlayer.Builder(ApplicationProvider.getApplicationContext()).build());
|
||||
new SimpleExoPlayer.Builder(ApplicationProvider.getApplicationContext()).build(),
|
||||
Looper.myLooper());
|
||||
AnalyticsListener listener1 = mock(AnalyticsListener.class);
|
||||
AnalyticsListener listener2 =
|
||||
spy(
|
||||
|
|
@ -1785,7 +1776,7 @@ public final class AnalyticsCollectorTest {
|
|||
lastReportedTimeline = Timeline.EMPTY;
|
||||
}
|
||||
|
||||
public List<EventWindowAndPeriodId> getEvents(int eventType) {
|
||||
public List<EventWindowAndPeriodId> getEvents(long eventType) {
|
||||
ArrayList<EventWindowAndPeriodId> eventTimes = new ArrayList<>();
|
||||
Iterator<ReportedEvent> eventIterator = reportedEvents.iterator();
|
||||
while (eventIterator.hasNext()) {
|
||||
|
|
@ -1845,12 +1836,12 @@ public final class AnalyticsCollectorTest {
|
|||
|
||||
@Override
|
||||
public void onShuffleModeChanged(EventTime eventTime, boolean shuffleModeEnabled) {
|
||||
reportedEvents.add(new ReportedEvent(EVENT_SHUFFLE_MODE_CHANGED, eventTime));
|
||||
reportedEvents.add(new ReportedEvent(EVENT_SHUFFLE_MODE_ENABLED_CHANGED, eventTime));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onIsLoadingChanged(EventTime eventTime, boolean isLoading) {
|
||||
reportedEvents.add(new ReportedEvent(EVENT_LOADING_CHANGED, eventTime));
|
||||
reportedEvents.add(new ReportedEvent(EVENT_IS_LOADING_CHANGED, eventTime));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -1953,7 +1944,7 @@ public final class AnalyticsCollectorTest {
|
|||
@Override
|
||||
public void onAudioDecoderInitialized(
|
||||
EventTime eventTime, String decoderName, long initializationDurationMs) {
|
||||
reportedEvents.add(new ReportedEvent(EVENT_AUDIO_DECODER_INIT, eventTime));
|
||||
reportedEvents.add(new ReportedEvent(EVENT_AUDIO_DECODER_INITIALIZED, eventTime));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -1990,7 +1981,7 @@ public final class AnalyticsCollectorTest {
|
|||
@Override
|
||||
public void onVideoDecoderInitialized(
|
||||
EventTime eventTime, String decoderName, long initializationDurationMs) {
|
||||
reportedEvents.add(new ReportedEvent(EVENT_VIDEO_DECODER_INIT, eventTime));
|
||||
reportedEvents.add(new ReportedEvent(EVENT_VIDEO_DECODER_INITIALIZED, eventTime));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -2000,7 +1991,7 @@ public final class AnalyticsCollectorTest {
|
|||
|
||||
@Override
|
||||
public void onDroppedVideoFrames(EventTime eventTime, int droppedFrames, long elapsedMs) {
|
||||
reportedEvents.add(new ReportedEvent(EVENT_DROPPED_FRAMES, eventTime));
|
||||
reportedEvents.add(new ReportedEvent(EVENT_DROPPED_VIDEO_FRAMES, eventTime));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -2015,7 +2006,7 @@ public final class AnalyticsCollectorTest {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onRenderedFirstFrame(EventTime eventTime, Surface surface) {
|
||||
public void onRenderedFirstFrame(EventTime eventTime, @Nullable Surface surface) {
|
||||
reportedEvents.add(new ReportedEvent(EVENT_RENDERED_FIRST_FRAME, eventTime));
|
||||
}
|
||||
|
||||
|
|
@ -2041,7 +2032,7 @@ public final class AnalyticsCollectorTest {
|
|||
|
||||
@Override
|
||||
public void onDrmSessionManagerError(EventTime eventTime, Exception error) {
|
||||
reportedEvents.add(new ReportedEvent(EVENT_DRM_ERROR, eventTime));
|
||||
reportedEvents.add(new ReportedEvent(EVENT_DRM_SESSION_MANAGER_ERROR, eventTime));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -2061,10 +2052,10 @@ public final class AnalyticsCollectorTest {
|
|||
|
||||
private static final class ReportedEvent {
|
||||
|
||||
public final int eventType;
|
||||
public final long eventType;
|
||||
public final EventWindowAndPeriodId eventWindowAndPeriodId;
|
||||
|
||||
public ReportedEvent(int eventType, EventTime eventTime) {
|
||||
public ReportedEvent(long eventType, EventTime eventTime) {
|
||||
this.eventType = eventType;
|
||||
this.eventWindowAndPeriodId =
|
||||
new EventWindowAndPeriodId(eventTime.windowIndex, eventTime.mediaPeriodId);
|
||||
|
|
@ -2072,7 +2063,12 @@ public final class AnalyticsCollectorTest {
|
|||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "{" + "type=" + eventType + ", windowAndPeriodId=" + eventWindowAndPeriodId + '}';
|
||||
return "{"
|
||||
+ "type="
|
||||
+ Long.numberOfTrailingZeros(eventType)
|
||||
+ ", windowAndPeriodId="
|
||||
+ eventWindowAndPeriodId
|
||||
+ '}';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,39 +22,49 @@ import static org.mockito.Mockito.times;
|
|||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
|
||||
import android.os.Looper;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.InOrder;
|
||||
import org.mockito.Mockito;
|
||||
import org.robolectric.shadows.ShadowLooper;
|
||||
|
||||
/** Unit test for {@link ListenerSet}. */
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class ListenerSetTest {
|
||||
|
||||
private static final int EVENT_ID_1 = 0;
|
||||
private static final int EVENT_ID_2 = 1;
|
||||
private static final int EVENT_ID_3 = 2;
|
||||
|
||||
@Test
|
||||
public void queueEvent_isNotSentWithoutFlush() {
|
||||
ListenerSet<TestListener> listenerSet = new ListenerSet<>();
|
||||
public void queueEvent_withoutFlush_sendsNoEvents() {
|
||||
ListenerSet<TestListener, Flags> listenerSet =
|
||||
new ListenerSet<>(Looper.myLooper(), Flags::new, TestListener::iterationFinished);
|
||||
TestListener listener = mock(TestListener.class);
|
||||
listenerSet.add(listener);
|
||||
|
||||
listenerSet.queueEvent(TestListener::callback1);
|
||||
listenerSet.queueEvent(TestListener::callback2);
|
||||
listenerSet.queueEvent(EVENT_ID_1, TestListener::callback1);
|
||||
listenerSet.queueEvent(EVENT_ID_2, TestListener::callback2);
|
||||
ShadowLooper.runMainLooperToNextTask();
|
||||
|
||||
verifyNoMoreInteractions(listener);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void flushEvents_sendsPreviouslyQueuedEventsToAllListeners() {
|
||||
ListenerSet<TestListener> listenerSet = new ListenerSet<>();
|
||||
ListenerSet<TestListener, Flags> listenerSet =
|
||||
new ListenerSet<>(Looper.myLooper(), Flags::new, TestListener::iterationFinished);
|
||||
TestListener listener1 = mock(TestListener.class);
|
||||
TestListener listener2 = mock(TestListener.class);
|
||||
listenerSet.add(listener1);
|
||||
listenerSet.add(listener2);
|
||||
|
||||
listenerSet.queueEvent(TestListener::callback1);
|
||||
listenerSet.queueEvent(TestListener::callback2);
|
||||
listenerSet.queueEvent(TestListener::callback1);
|
||||
listenerSet.queueEvent(EVENT_ID_1, TestListener::callback1);
|
||||
listenerSet.queueEvent(EVENT_ID_2, TestListener::callback2);
|
||||
listenerSet.queueEvent(EVENT_ID_1, TestListener::callback1);
|
||||
listenerSet.flushEvents();
|
||||
|
||||
InOrder inOrder = Mockito.inOrder(listener1, listener2);
|
||||
|
|
@ -69,14 +79,15 @@ public class ListenerSetTest {
|
|||
|
||||
@Test
|
||||
public void flushEvents_recursive_sendsEventsInCorrectOrder() {
|
||||
ListenerSet<TestListener> listenerSet = new ListenerSet<>();
|
||||
ListenerSet<TestListener, Flags> listenerSet =
|
||||
new ListenerSet<>(Looper.myLooper(), Flags::new, TestListener::iterationFinished);
|
||||
// Listener1 sends callback3 recursively when receiving callback1.
|
||||
TestListener listener1 =
|
||||
spy(
|
||||
new TestListener() {
|
||||
@Override
|
||||
public void callback1() {
|
||||
listenerSet.queueEvent(TestListener::callback3);
|
||||
listenerSet.queueEvent(EVENT_ID_3, TestListener::callback3);
|
||||
listenerSet.flushEvents();
|
||||
}
|
||||
});
|
||||
|
|
@ -84,8 +95,8 @@ public class ListenerSetTest {
|
|||
listenerSet.add(listener1);
|
||||
listenerSet.add(listener2);
|
||||
|
||||
listenerSet.queueEvent(TestListener::callback1);
|
||||
listenerSet.queueEvent(TestListener::callback2);
|
||||
listenerSet.queueEvent(EVENT_ID_1, TestListener::callback1);
|
||||
listenerSet.queueEvent(EVENT_ID_2, TestListener::callback2);
|
||||
listenerSet.flushEvents();
|
||||
|
||||
InOrder inOrder = Mockito.inOrder(listener1, listener2);
|
||||
|
|
@ -98,9 +109,121 @@ public class ListenerSetTest {
|
|||
inOrder.verifyNoMoreInteractions();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void
|
||||
flushEvents_withMultipleMessageQueueIterations_sendsIterationFinishedEventPerIteration() {
|
||||
ListenerSet<TestListener, Flags> listenerSet =
|
||||
new ListenerSet<>(Looper.myLooper(), Flags::new, TestListener::iterationFinished);
|
||||
// Listener1 sends callback1 recursively when receiving callback3.
|
||||
TestListener listener1 =
|
||||
spy(
|
||||
new TestListener() {
|
||||
@Override
|
||||
public void callback3() {
|
||||
listenerSet.sendEvent(EVENT_ID_1, TestListener::callback1);
|
||||
}
|
||||
});
|
||||
TestListener listener2 = mock(TestListener.class);
|
||||
listenerSet.add(listener1);
|
||||
listenerSet.add(listener2);
|
||||
|
||||
// Iteration with single flush.
|
||||
listenerSet.queueEvent(EVENT_ID_2, TestListener::callback2);
|
||||
listenerSet.flushEvents();
|
||||
ShadowLooper.runMainLooperToNextTask();
|
||||
|
||||
// Iteration with multiple flushes.
|
||||
listenerSet.queueEvent(EVENT_ID_1, TestListener::callback1);
|
||||
listenerSet.queueEvent(EVENT_ID_2, TestListener::callback2);
|
||||
listenerSet.flushEvents();
|
||||
listenerSet.queueEvent(EVENT_ID_1, TestListener::callback1);
|
||||
listenerSet.flushEvents();
|
||||
ShadowLooper.runMainLooperToNextTask();
|
||||
|
||||
// Iteration with recursive call.
|
||||
listenerSet.sendEvent(EVENT_ID_3, TestListener::callback3);
|
||||
ShadowLooper.runMainLooperToNextTask();
|
||||
|
||||
InOrder inOrder = Mockito.inOrder(listener1, listener2);
|
||||
inOrder.verify(listener1).callback2();
|
||||
inOrder.verify(listener2).callback2();
|
||||
inOrder.verify(listener1).iterationFinished(Flags.create(EVENT_ID_2));
|
||||
inOrder.verify(listener2).iterationFinished(Flags.create(EVENT_ID_2));
|
||||
inOrder.verify(listener1).callback1();
|
||||
inOrder.verify(listener2).callback1();
|
||||
inOrder.verify(listener1).callback2();
|
||||
inOrder.verify(listener2).callback2();
|
||||
inOrder.verify(listener1).callback1();
|
||||
inOrder.verify(listener2).callback1();
|
||||
inOrder.verify(listener1).iterationFinished(Flags.create(EVENT_ID_1, EVENT_ID_2));
|
||||
inOrder.verify(listener2).iterationFinished(Flags.create(EVENT_ID_1, EVENT_ID_2));
|
||||
inOrder.verify(listener1).callback3();
|
||||
inOrder.verify(listener2).callback3();
|
||||
inOrder.verify(listener1).callback1();
|
||||
inOrder.verify(listener2).callback1();
|
||||
inOrder.verify(listener1).iterationFinished(Flags.create(EVENT_ID_1, EVENT_ID_3));
|
||||
inOrder.verify(listener2).iterationFinished(Flags.create(EVENT_ID_1, EVENT_ID_3));
|
||||
inOrder.verifyNoMoreInteractions();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void flushEvents_calledFromIterationFinishedCallback_restartsIterationFinishedEvents() {
|
||||
ListenerSet<TestListener, Flags> listenerSet =
|
||||
new ListenerSet<>(Looper.myLooper(), Flags::new, TestListener::iterationFinished);
|
||||
// Listener2 sends callback1 recursively when receiving the iteration finished event.
|
||||
TestListener listener2 =
|
||||
spy(
|
||||
new TestListener() {
|
||||
boolean eventSent;
|
||||
|
||||
@Override
|
||||
public void iterationFinished(Flags flags) {
|
||||
if (!eventSent) {
|
||||
listenerSet.sendEvent(EVENT_ID_1, TestListener::callback1);
|
||||
eventSent = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
TestListener listener1 = mock(TestListener.class);
|
||||
TestListener listener3 = mock(TestListener.class);
|
||||
listenerSet.add(listener1);
|
||||
listenerSet.add(listener2);
|
||||
listenerSet.add(listener3);
|
||||
|
||||
listenerSet.sendEvent(EVENT_ID_2, TestListener::callback2);
|
||||
ShadowLooper.runMainLooperToNextTask();
|
||||
|
||||
InOrder inOrder = Mockito.inOrder(listener1, listener2, listener3);
|
||||
inOrder.verify(listener1).callback2();
|
||||
inOrder.verify(listener2).callback2();
|
||||
inOrder.verify(listener3).callback2();
|
||||
inOrder.verify(listener1).iterationFinished(Flags.create(EVENT_ID_2));
|
||||
inOrder.verify(listener2).iterationFinished(Flags.create(EVENT_ID_2));
|
||||
inOrder.verify(listener1).callback1();
|
||||
inOrder.verify(listener2).callback1();
|
||||
inOrder.verify(listener3).callback1();
|
||||
inOrder.verify(listener1).iterationFinished(Flags.create(EVENT_ID_1));
|
||||
inOrder.verify(listener2).iterationFinished(Flags.create(EVENT_ID_1));
|
||||
inOrder.verify(listener3).iterationFinished(Flags.create(EVENT_ID_1, EVENT_ID_2));
|
||||
inOrder.verifyNoMoreInteractions();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void flushEvents_withUnsetEventFlag_doesNotThrow() {
|
||||
ListenerSet<TestListener, Flags> listenerSet =
|
||||
new ListenerSet<>(Looper.myLooper(), Flags::new, TestListener::iterationFinished);
|
||||
|
||||
listenerSet.queueEvent(/* eventFlag= */ C.INDEX_UNSET, TestListener::callback1);
|
||||
listenerSet.flushEvents();
|
||||
ShadowLooper.runMainLooperToNextTask();
|
||||
|
||||
// Asserts that negative event flag (INDEX_UNSET) can be used without throwing.
|
||||
}
|
||||
|
||||
@Test
|
||||
public void add_withRecursion_onlyReceivesUpdatesForFutureEvents() {
|
||||
ListenerSet<TestListener> listenerSet = new ListenerSet<>();
|
||||
ListenerSet<TestListener, Flags> listenerSet =
|
||||
new ListenerSet<>(Looper.myLooper(), Flags::new, TestListener::iterationFinished);
|
||||
TestListener listener2 = mock(TestListener.class);
|
||||
// Listener1 adds listener2 recursively.
|
||||
TestListener listener1 =
|
||||
|
|
@ -112,38 +235,52 @@ public class ListenerSetTest {
|
|||
}
|
||||
});
|
||||
|
||||
listenerSet.sendEvent(TestListener::callback1);
|
||||
listenerSet.sendEvent(EVENT_ID_1, TestListener::callback1);
|
||||
listenerSet.add(listener1);
|
||||
// This should add listener2, but the event should not be received yet as it happened before
|
||||
// listener2 was added.
|
||||
listenerSet.sendEvent(TestListener::callback1);
|
||||
listenerSet.sendEvent(TestListener::callback1);
|
||||
listenerSet.sendEvent(EVENT_ID_1, TestListener::callback1);
|
||||
listenerSet.sendEvent(EVENT_ID_2, TestListener::callback2);
|
||||
ShadowLooper.runMainLooperToNextTask();
|
||||
|
||||
verify(listener1, times(2)).callback1();
|
||||
verify(listener2).callback1();
|
||||
InOrder inOrder = Mockito.inOrder(listener1, listener2);
|
||||
inOrder.verify(listener1).callback1();
|
||||
inOrder.verify(listener1).callback2();
|
||||
inOrder.verify(listener2).callback2();
|
||||
inOrder.verify(listener1).iterationFinished(Flags.create(EVENT_ID_1, EVENT_ID_2));
|
||||
inOrder.verify(listener2).iterationFinished(Flags.create(EVENT_ID_2));
|
||||
inOrder.verifyNoMoreInteractions();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void add_withQueueing_onlyReceivesUpdatesForFutureEvents() {
|
||||
ListenerSet<TestListener> listenerSet = new ListenerSet<>();
|
||||
ListenerSet<TestListener, Flags> listenerSet =
|
||||
new ListenerSet<>(Looper.myLooper(), Flags::new, TestListener::iterationFinished);
|
||||
TestListener listener1 = mock(TestListener.class);
|
||||
TestListener listener2 = mock(TestListener.class);
|
||||
|
||||
// This event is flushed after listener2 was added, but shouldn't be sent to listener2 because
|
||||
// the event itself occurred before the listener was added.
|
||||
listenerSet.add(listener1);
|
||||
listenerSet.queueEvent(TestListener::callback2);
|
||||
listenerSet.queueEvent(EVENT_ID_1, TestListener::callback1);
|
||||
listenerSet.add(listener2);
|
||||
listenerSet.queueEvent(TestListener::callback2);
|
||||
listenerSet.queueEvent(EVENT_ID_2, TestListener::callback2);
|
||||
listenerSet.flushEvents();
|
||||
ShadowLooper.runMainLooperToNextTask();
|
||||
|
||||
verify(listener1, times(2)).callback2();
|
||||
verify(listener2).callback2();
|
||||
InOrder inOrder = Mockito.inOrder(listener1, listener2);
|
||||
inOrder.verify(listener1).callback1();
|
||||
inOrder.verify(listener1).callback2();
|
||||
inOrder.verify(listener2).callback2();
|
||||
inOrder.verify(listener1).iterationFinished(Flags.create(EVENT_ID_1, EVENT_ID_2));
|
||||
inOrder.verify(listener2).iterationFinished(Flags.create(EVENT_ID_2));
|
||||
inOrder.verifyNoMoreInteractions();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void remove_withRecursion_stopsReceivingEventsImmediately() {
|
||||
ListenerSet<TestListener> listenerSet = new ListenerSet<>();
|
||||
ListenerSet<TestListener, Flags> listenerSet =
|
||||
new ListenerSet<>(Looper.myLooper(), Flags::new, TestListener::iterationFinished);
|
||||
TestListener listener2 = mock(TestListener.class);
|
||||
// Listener1 removes listener2 recursively.
|
||||
TestListener listener1 =
|
||||
|
|
@ -158,35 +295,40 @@ public class ListenerSetTest {
|
|||
listenerSet.add(listener2);
|
||||
|
||||
// Listener2 shouldn't even get this event as it's removed before the event can be invoked.
|
||||
listenerSet.sendEvent(TestListener::callback1);
|
||||
listenerSet.sendEvent(EVENT_ID_1, TestListener::callback1);
|
||||
listenerSet.remove(listener1);
|
||||
listenerSet.sendEvent(TestListener::callback1);
|
||||
listenerSet.sendEvent(EVENT_ID_1, TestListener::callback1);
|
||||
ShadowLooper.runMainLooperToNextTask();
|
||||
|
||||
verify(listener1).callback1();
|
||||
verify(listener2, never()).callback1();
|
||||
verifyNoMoreInteractions(listener1, listener2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void remove_withQueueing_stopsReceivingEventsImmediately() {
|
||||
ListenerSet<TestListener> listenerSet = new ListenerSet<>();
|
||||
ListenerSet<TestListener, Flags> listenerSet =
|
||||
new ListenerSet<>(Looper.myLooper(), Flags::new, TestListener::iterationFinished);
|
||||
TestListener listener1 = mock(TestListener.class);
|
||||
TestListener listener2 = mock(TestListener.class);
|
||||
listenerSet.add(listener1);
|
||||
listenerSet.add(listener2);
|
||||
|
||||
// Listener1 shouldn't even get this event as it's removed before the event can be invoked.
|
||||
listenerSet.queueEvent(TestListener::callback1);
|
||||
listenerSet.queueEvent(EVENT_ID_1, TestListener::callback1);
|
||||
listenerSet.remove(listener1);
|
||||
listenerSet.queueEvent(TestListener::callback1);
|
||||
listenerSet.queueEvent(EVENT_ID_1, TestListener::callback1);
|
||||
listenerSet.flushEvents();
|
||||
ShadowLooper.runMainLooperToNextTask();
|
||||
|
||||
verify(listener1, never()).callback1();
|
||||
verify(listener2, times(2)).callback1();
|
||||
verify(listener2).iterationFinished(Flags.create(EVENT_ID_1));
|
||||
verifyNoMoreInteractions(listener1, listener2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void release_stopsForwardingEventsImmediately() {
|
||||
ListenerSet<TestListener> listenerSet = new ListenerSet<>();
|
||||
ListenerSet<TestListener, Flags> listenerSet =
|
||||
new ListenerSet<>(Looper.myLooper(), Flags::new, TestListener::iterationFinished);
|
||||
TestListener listener2 = mock(TestListener.class);
|
||||
// Listener1 releases the set from within the callback.
|
||||
TestListener listener1 =
|
||||
|
|
@ -201,23 +343,23 @@ public class ListenerSetTest {
|
|||
listenerSet.add(listener2);
|
||||
|
||||
// Listener2 shouldn't even get this event as it's released before the event can be invoked.
|
||||
listenerSet.sendEvent(TestListener::callback1);
|
||||
listenerSet.sendEvent(TestListener::callback2);
|
||||
listenerSet.sendEvent(EVENT_ID_1, TestListener::callback1);
|
||||
listenerSet.sendEvent(EVENT_ID_2, TestListener::callback2);
|
||||
ShadowLooper.runMainLooperToNextTask();
|
||||
|
||||
verify(listener1).callback1();
|
||||
verify(listener2, never()).callback1();
|
||||
verify(listener1, never()).callback2();
|
||||
verify(listener2, never()).callback2();
|
||||
verifyNoMoreInteractions(listener1, listener2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void release_preventsRegisteringNewListeners() {
|
||||
ListenerSet<TestListener> listenerSet = new ListenerSet<>();
|
||||
ListenerSet<TestListener, Flags> listenerSet =
|
||||
new ListenerSet<>(Looper.myLooper(), Flags::new, TestListener::iterationFinished);
|
||||
TestListener listener = mock(TestListener.class);
|
||||
|
||||
listenerSet.release();
|
||||
listenerSet.add(listener);
|
||||
listenerSet.sendEvent(TestListener::callback1);
|
||||
listenerSet.sendEvent(EVENT_ID_1, TestListener::callback1);
|
||||
|
||||
verify(listener, never()).callback1();
|
||||
}
|
||||
|
|
@ -228,5 +370,18 @@ public class ListenerSetTest {
|
|||
default void callback2() {}
|
||||
|
||||
default void callback3() {}
|
||||
|
||||
default void iterationFinished(Flags flags) {}
|
||||
}
|
||||
|
||||
private static final class Flags extends MutableFlags {
|
||||
|
||||
public static Flags create(int... flagValues) {
|
||||
Flags flags = new Flags();
|
||||
for (int value : flagValues) {
|
||||
flags.add(value);
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,140 @@
|
|||
/*
|
||||
* Copyright (C) 2020 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.exoplayer2.util;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
/** Unit test for {@link MutableFlags}. */
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public final class MutableFlagsTest {
|
||||
|
||||
@Test
|
||||
public void contains_withoutAdd_returnsFalseForAllValues() {
|
||||
MutableFlags flags = new MutableFlags();
|
||||
|
||||
assertThat(flags.contains(/* flag= */ -1234)).isFalse();
|
||||
assertThat(flags.contains(/* flag= */ 0)).isFalse();
|
||||
assertThat(flags.contains(/* flag= */ 2)).isFalse();
|
||||
assertThat(flags.contains(/* flag= */ Integer.MAX_VALUE)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void contains_afterAdd_returnsTrueForAddedValues() {
|
||||
MutableFlags flags = new MutableFlags();
|
||||
|
||||
flags.add(/* flag= */ -1234);
|
||||
flags.add(/* flag= */ 0);
|
||||
flags.add(/* flag= */ 2);
|
||||
flags.add(/* flag= */ Integer.MAX_VALUE);
|
||||
|
||||
assertThat(flags.contains(/* flag= */ -1235)).isFalse();
|
||||
assertThat(flags.contains(/* flag= */ -1234)).isTrue();
|
||||
assertThat(flags.contains(/* flag= */ 0)).isTrue();
|
||||
assertThat(flags.contains(/* flag= */ 1)).isFalse();
|
||||
assertThat(flags.contains(/* flag= */ 2)).isTrue();
|
||||
assertThat(flags.contains(/* flag= */ Integer.MAX_VALUE - 1)).isFalse();
|
||||
assertThat(flags.contains(/* flag= */ Integer.MAX_VALUE)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void contains_afterClear_returnsFalseForAllValues() {
|
||||
MutableFlags flags = new MutableFlags();
|
||||
flags.add(/* flag= */ -1234);
|
||||
flags.add(/* flag= */ 0);
|
||||
flags.add(/* flag= */ 2);
|
||||
flags.add(/* flag= */ Integer.MAX_VALUE);
|
||||
|
||||
flags.clear();
|
||||
|
||||
assertThat(flags.contains(/* flag= */ -1234)).isFalse();
|
||||
assertThat(flags.contains(/* flag= */ 0)).isFalse();
|
||||
assertThat(flags.contains(/* flag= */ 2)).isFalse();
|
||||
assertThat(flags.contains(/* flag= */ Integer.MAX_VALUE)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void size_withoutAdd_returnsZero() {
|
||||
MutableFlags flags = new MutableFlags();
|
||||
|
||||
assertThat(flags.size()).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void size_afterAdd_returnsNumberUniqueOfElements() {
|
||||
MutableFlags flags = new MutableFlags();
|
||||
|
||||
flags.add(/* flag= */ 0);
|
||||
flags.add(/* flag= */ 0);
|
||||
flags.add(/* flag= */ 0);
|
||||
flags.add(/* flag= */ 123);
|
||||
flags.add(/* flag= */ 123);
|
||||
|
||||
assertThat(flags.size()).isEqualTo(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void size_afterClear_returnsZero() {
|
||||
MutableFlags flags = new MutableFlags();
|
||||
|
||||
flags.add(/* flag= */ 0);
|
||||
flags.add(/* flag= */ 123);
|
||||
flags.clear();
|
||||
|
||||
assertThat(flags.size()).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void get_withNegativeIndex_throwsIllegalArgumentException() {
|
||||
MutableFlags flags = new MutableFlags();
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () -> flags.get(/* index= */ -1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void get_withIndexExceedingSize_throwsIllegalArgumentException() {
|
||||
MutableFlags flags = new MutableFlags();
|
||||
|
||||
flags.add(/* flag= */ 0);
|
||||
flags.add(/* flag= */ 123);
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () -> flags.get(/* index= */ 2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void get_afterAdd_returnsAllUniqueValues() {
|
||||
MutableFlags flags = new MutableFlags();
|
||||
|
||||
flags.add(/* flag= */ 0);
|
||||
flags.add(/* flag= */ 0);
|
||||
flags.add(/* flag= */ 0);
|
||||
flags.add(/* flag= */ 123);
|
||||
flags.add(/* flag= */ 123);
|
||||
flags.add(/* flag= */ 456);
|
||||
|
||||
List<Integer> values = new ArrayList<>();
|
||||
for (int i = 0; i < flags.size(); i++) {
|
||||
values.add(flags.get(i));
|
||||
}
|
||||
assertThat(values).containsExactly(0, 123, 456);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue