diff --git a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java index d4c3523e2a..81e9a80c98 100644 --- a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java +++ b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java @@ -95,7 +95,7 @@ public final class CastPlayer extends BasePlayer { private final SeekResultCallback seekResultCallback; // Listeners and notification. - private final ListenerSet listeners; + private final ListenerSet listeners; @Nullable private SessionAvailabilityListener sessionAvailabilityListener; // Internal state. @@ -139,8 +139,7 @@ public final class CastPlayer extends BasePlayer { new ListenerSet<>( Looper.getMainLooper(), Clock.DEFAULT, - Player.Events::new, - (listener, eventFlags) -> listener.onEvents(/* player= */ this, eventFlags)); + (listener, flags) -> listener.onEvents(/* player= */ this, new Events(flags))); playWhenReady = new StateHolder<>(false); repeatMode = new StateHolder<>(REPEAT_MODE_OFF); diff --git a/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/FakePlayer.java b/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/FakePlayer.java index 6b62af93f3..004db3750d 100644 --- a/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/FakePlayer.java +++ b/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/FakePlayer.java @@ -27,7 +27,7 @@ import com.google.android.exoplayer2.util.ListenerSet; /** A fake player for testing content/ad playback. */ /* package */ final class FakePlayer extends StubExoPlayer { - private final ListenerSet listeners; + private final ListenerSet listeners; private final Timeline.Period period; private Timeline timeline; @@ -45,8 +45,7 @@ import com.google.android.exoplayer2.util.ListenerSet; new ListenerSet<>( Looper.getMainLooper(), Clock.DEFAULT, - Player.Events::new, - (listener, eventFlags) -> listener.onEvents(/* player= */ this, eventFlags)); + (listener, flags) -> listener.onEvents(/* player= */ this, new Events(flags))); period = new Timeline.Period(); state = Player.STATE_IDLE; playWhenReady = true; diff --git a/library/common/src/main/java/com/google/android/exoplayer2/Player.java b/library/common/src/main/java/com/google/android/exoplayer2/Player.java index 5a8fda0b92..df7c83da0b 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/Player.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/Player.java @@ -15,11 +15,9 @@ */ package com.google.android.exoplayer2; -import static com.google.android.exoplayer2.util.Assertions.checkState; import android.content.Context; import android.os.Looper; -import android.util.SparseBooleanArray; import android.view.Surface; import android.view.SurfaceHolder; import android.view.SurfaceView; @@ -37,7 +35,7 @@ import com.google.android.exoplayer2.source.TrackGroupArray; 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.util.MutableFlags; +import com.google.android.exoplayer2.util.ExoFlags; import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.video.VideoFrameMetadataListener; import com.google.android.exoplayer2.video.VideoListener; @@ -664,17 +662,27 @@ public interface Player { } /** A set of {@link EventFlags}. */ - final class Events extends MutableFlags { + final class Events { + + private final ExoFlags flags; + + /** + * Creates an instance. + * + * @param flags The {@link ExoFlags} containing the {@link EventFlags} in the set. + */ + public Events(ExoFlags flags) { + this.flags = flags; + } + /** * 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); + return flags.contains(event); } /** @@ -683,10 +691,13 @@ public interface Player { * @param events The {@link EventFlags events}. * @return Whether any of the events occurred. */ - @Override public boolean containsAny(@EventFlags int... events) { - // Overridden to add IntDef compiler enforcement and new JavaDoc. - return super.containsAny(events); + return flags.containsAny(events); + } + + /** Returns the number of events in the set. */ + public int size() { + return flags.size(); } /** @@ -698,11 +709,9 @@ public interface Player { * @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); + return flags.get(index); } } @@ -716,18 +725,11 @@ public interface Player { /** A builder for {@link Commands} instances. */ public static final class Builder { - private final SparseBooleanArray commandsArray; - - private boolean buildCalled; + private final ExoFlags.Builder flagsBuilder; /** Creates a builder. */ public Builder() { - commandsArray = new SparseBooleanArray(); - } - - /** Creates a builder with the values of the provided {@link Commands}. */ - private Builder(Commands commands) { - this.commandsArray = commands.commandsArray.clone(); + flagsBuilder = new ExoFlags.Builder(); } /** @@ -738,8 +740,7 @@ public interface Player { * @throws IllegalStateException If {@link #build()} has already been called. */ public Builder add(@Command int command) { - checkState(!buildCalled); - commandsArray.append(command, /* value= */ true); + flagsBuilder.add(command); return this; } @@ -752,39 +753,32 @@ public interface Player { * @throws IllegalStateException If {@link #build()} has already been called. */ public Builder addIf(@Command int command, boolean condition) { - checkState(!buildCalled); - if (condition) { - commandsArray.append(command, /* value= */ true); - } + flagsBuilder.addIf(command, condition); return this; } - /** Builds a {@link Commands} instance. */ + /** + * Builds a {@link Commands} instance. + * + * @throws IllegalStateException If this method has already been called. + */ public Commands build() { - checkState(!buildCalled); - buildCalled = true; - return new Commands(commandsArray); + return new Commands(flagsBuilder.build()); } } /** An empty set of commands. */ - public static final Commands EMPTY = new Commands.Builder().build(); + public static final Commands EMPTY = new Builder().build(); - // A SparseBooleanArray is used instead of a Set to avoid auto-boxing the Command values. - private final SparseBooleanArray commandsArray; + private final ExoFlags flags; - private Commands(SparseBooleanArray commandsArray) { - this.commandsArray = commandsArray; - } - - /** Returns a {@link Commands.Builder} initialized with the values of this instance. */ - public Builder buildUpon() { - return new Builder(this); + private Commands(ExoFlags flags) { + this.flags = flags; } /** Returns whether the set of commands contains the specified {@link Command}. */ public boolean contains(@Command int command) { - return commandsArray.get(command); + return flags.contains(command); } @Override @@ -796,12 +790,12 @@ public interface Player { return false; } Commands commands = (Commands) obj; - return this.commandsArray.equals(commands.commandsArray); + return flags.equals(commands.flags); } @Override public int hashCode() { - return commandsArray.hashCode(); + return flags.hashCode(); } } diff --git a/library/common/src/main/java/com/google/android/exoplayer2/util/MutableFlags.java b/library/common/src/main/java/com/google/android/exoplayer2/util/ExoFlags.java similarity index 57% rename from library/common/src/main/java/com/google/android/exoplayer2/util/MutableFlags.java rename to library/common/src/main/java/com/google/android/exoplayer2/util/ExoFlags.java index 6ed425de18..17418aa6b2 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/util/MutableFlags.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/util/ExoFlags.java @@ -15,6 +15,8 @@ */ package com.google.android.exoplayer2.util; +import static com.google.android.exoplayer2.util.Assertions.checkState; + import android.util.SparseBooleanArray; import androidx.annotation.Nullable; @@ -23,28 +25,68 @@ import androidx.annotation.Nullable; * *

Intended for usages where the number of flags may exceed 32 and can no longer be represented * by an IntDef. + * + *

Instances are immutable. */ -public class MutableFlags { +public final class ExoFlags { + /** A builder for {@link ExoFlags} instances. */ + public static final class Builder { + + private final SparseBooleanArray flags; + + private boolean buildCalled; + + /** Creates a builder. */ + public Builder() { + flags = new SparseBooleanArray(); + } + + /** + * Adds a flag. + * + * @param flag A flag. + * @return This builder. + * @throws IllegalStateException If {@link #build()} has already been called. + */ + public Builder add(int flag) { + checkState(!buildCalled); + flags.append(flag, /* value= */ true); + return this; + } + + /** + * Adds a flag if the provided condition is true. Does nothing otherwise. + * + * @param flag A flag. + * @param condition A condition. + * @return This builder. + * @throws IllegalStateException If {@link #build()} has already been called. + */ + public Builder addIf(int flag, boolean condition) { + if (condition) { + return add(flag); + } + return this; + } + + /** + * Builds an {@link ExoFlags} instance. + * + * @throws IllegalStateException If this method has already been called. + */ + public ExoFlags build() { + checkState(!buildCalled); + buildCalled = true; + return new ExoFlags(flags); + } + } + + // A SparseBooleanArray is used instead of a Set to avoid auto-boxing the flag values. 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); + private ExoFlags(SparseBooleanArray flags) { + this.flags = flags; } /** @@ -94,10 +136,10 @@ public class MutableFlags { if (this == o) { return true; } - if (!(o instanceof MutableFlags)) { + if (!(o instanceof ExoFlags)) { return false; } - MutableFlags that = (MutableFlags) o; + ExoFlags that = (ExoFlags) o; return flags.equals(that.flags); } diff --git a/library/common/src/main/java/com/google/android/exoplayer2/util/ListenerSet.java b/library/common/src/main/java/com/google/android/exoplayer2/util/ListenerSet.java index a9a749e47f..fe220b1946 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/util/ListenerSet.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/util/ListenerSet.java @@ -20,7 +20,6 @@ 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; @@ -35,9 +34,8 @@ import javax.annotation.Nonnull; * was enqueued and haven't been removed since. * * @param The listener type. - * @param The {@link MutableFlags} type used to indicate which events occurred. */ -public final class ListenerSet { +public final class ListenerSet { /** * An event sent to a listener. @@ -55,17 +53,17 @@ public final class ListenerSet { * iteration were handled by the listener. * * @param The listener type. - * @param The {@link MutableFlags} type used to indicate which events occurred. */ - public interface IterationFinishedEvent { + public interface IterationFinishedEvent { /** * 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. + * @param eventFlags The combined event {@link ExoFlags flags} of all events sent in this + * iteration. */ - void invoke(T listener, E eventFlags); + void invoke(T listener, ExoFlags eventFlags); } private static final int MSG_ITERATION_FINISHED = 0; @@ -73,9 +71,8 @@ public final class ListenerSet { private final Clock clock; private final HandlerWrapper handler; - private final Supplier eventFlagsSupplier; - private final IterationFinishedEvent iterationFinishedEvent; - private final CopyOnWriteArraySet> listeners; + private final IterationFinishedEvent iterationFinishedEvent; + private final CopyOnWriteArraySet> listeners; private final ArrayDeque flushingEvents; private final ArrayDeque queuedEvents; @@ -87,33 +84,24 @@ public final class ListenerSet { * @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 clock A {@link Clock}. - * @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, - Clock clock, - Supplier eventFlagsSupplier, - IterationFinishedEvent iterationFinishedEvent) { + public ListenerSet(Looper looper, Clock clock, IterationFinishedEvent iterationFinishedEvent) { this( /* listeners= */ new CopyOnWriteArraySet<>(), looper, clock, - eventFlagsSupplier, iterationFinishedEvent); } private ListenerSet( - CopyOnWriteArraySet> listeners, + CopyOnWriteArraySet> listeners, Looper looper, Clock clock, - Supplier eventFlagsSupplier, - IterationFinishedEvent iterationFinishedEvent) { + IterationFinishedEvent iterationFinishedEvent) { this.clock = clock; this.listeners = listeners; - this.eventFlagsSupplier = eventFlagsSupplier; this.iterationFinishedEvent = iterationFinishedEvent; flushingEvents = new ArrayDeque<>(); queuedEvents = new ArrayDeque<>(); @@ -132,9 +120,8 @@ public final class ListenerSet { * @return The copied listener set. */ @CheckResult - public ListenerSet copy( - Looper looper, IterationFinishedEvent iterationFinishedEvent) { - return new ListenerSet<>(listeners, looper, clock, eventFlagsSupplier, iterationFinishedEvent); + public ListenerSet copy(Looper looper, IterationFinishedEvent iterationFinishedEvent) { + return new ListenerSet<>(listeners, looper, clock, iterationFinishedEvent); } /** @@ -149,7 +136,7 @@ public final class ListenerSet { return; } Assertions.checkNotNull(listener); - listeners.add(new ListenerHolder<>(listener, eventFlagsSupplier)); + listeners.add(new ListenerHolder<>(listener)); } /** @@ -160,7 +147,7 @@ public final class ListenerSet { * @param listener The listener to be removed. */ public void remove(T listener) { - for (ListenerHolder listenerHolder : listeners) { + for (ListenerHolder listenerHolder : listeners) { if (listenerHolder.listener.equals(listener)) { listenerHolder.release(iterationFinishedEvent); listeners.remove(listenerHolder); @@ -176,11 +163,10 @@ public final class ListenerSet { * @param event The event. */ public void queueEvent(int eventFlag, Event event) { - CopyOnWriteArraySet> listenerSnapshot = - new CopyOnWriteArraySet<>(listeners); + CopyOnWriteArraySet> listenerSnapshot = new CopyOnWriteArraySet<>(listeners); queuedEvents.add( () -> { - for (ListenerHolder holder : listenerSnapshot) { + for (ListenerHolder holder : listenerSnapshot) { holder.invoke(eventFlag, event); } }); @@ -226,7 +212,7 @@ public final class ListenerSet { *

This will ensure no events are sent to any listener after this method has been called. */ public void release() { - for (ListenerHolder listenerHolder : listeners) { + for (ListenerHolder listenerHolder : listeners) { listenerHolder.release(iterationFinishedEvent); } listeners.clear(); @@ -249,8 +235,8 @@ public final class ListenerSet { private boolean handleMessage(Message message) { if (message.what == MSG_ITERATION_FINISHED) { - for (ListenerHolder holder : listeners) { - holder.iterationFinished(eventFlagsSupplier, iterationFinishedEvent); + for (ListenerHolder holder : listeners) { + holder.iterationFinished(iterationFinishedEvent); if (handler.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 @@ -268,45 +254,44 @@ public final class ListenerSet { return true; } - private static final class ListenerHolder { + private static final class ListenerHolder { @Nonnull public final T listener; - private E eventsFlags; + private ExoFlags.Builder flagsBuilder; private boolean needsIterationFinishedEvent; private boolean released; - public ListenerHolder(@Nonnull T listener, Supplier eventFlagSupplier) { + public ListenerHolder(@Nonnull T listener) { this.listener = listener; - this.eventsFlags = eventFlagSupplier.get(); + this.flagsBuilder = new ExoFlags.Builder(); } - public void release(IterationFinishedEvent event) { + public void release(IterationFinishedEvent event) { released = true; if (needsIterationFinishedEvent) { - event.invoke(listener, eventsFlags); + event.invoke(listener, flagsBuilder.build()); } } public void invoke(int eventFlag, Event event) { if (!released) { if (eventFlag != C.INDEX_UNSET) { - eventsFlags.add(eventFlag); + flagsBuilder.add(eventFlag); } needsIterationFinishedEvent = true; event.invoke(listener); } } - public void iterationFinished( - Supplier eventFlagSupplier, IterationFinishedEvent event) { + public void iterationFinished(IterationFinishedEvent event) { if (!released && needsIterationFinishedEvent) { // 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(); + ExoFlags flagsToNotify = flagsBuilder.build(); + flagsBuilder = new ExoFlags.Builder(); needsIterationFinishedEvent = false; - event.invoke(listener, flagToNotify); + event.invoke(listener, flagsToNotify); } } @@ -318,7 +303,7 @@ public final class ListenerSet { if (other == null || getClass() != other.getClass()) { return false; } - return listener.equals(((ListenerHolder) other).listener); + return listener.equals(((ListenerHolder) other).listener); } @Override diff --git a/library/core/src/test/java/com/google/android/exoplayer2/util/MutableFlagsTest.java b/library/common/src/test/java/com/google/android/exoplayer2/util/ExoFlagsTest.java similarity index 52% rename from library/core/src/test/java/com/google/android/exoplayer2/util/MutableFlagsTest.java rename to library/common/src/test/java/com/google/android/exoplayer2/util/ExoFlagsTest.java index 1e68f80476..11a7240934 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/util/MutableFlagsTest.java +++ b/library/common/src/test/java/com/google/android/exoplayer2/util/ExoFlagsTest.java @@ -24,13 +24,13 @@ import java.util.List; import org.junit.Test; import org.junit.runner.RunWith; -/** Unit test for {@link MutableFlags}. */ +/** Unit test for {@link ExoFlags}. */ @RunWith(AndroidJUnit4.class) -public final class MutableFlagsTest { +public final class ExoFlagsTest { @Test public void contains_withoutAdd_returnsFalseForAllValues() { - MutableFlags flags = new MutableFlags(); + ExoFlags flags = new ExoFlags.Builder().build(); assertThat(flags.contains(/* flag= */ -1234)).isFalse(); assertThat(flags.contains(/* flag= */ 0)).isFalse(); @@ -40,12 +40,13 @@ public final class MutableFlagsTest { @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); + ExoFlags flags = + new ExoFlags.Builder() + .add(/* flag= */ -1234) + .add(/* flag= */ 0) + .add(/* flag= */ 2) + .add(/* flag= */ Integer.MAX_VALUE) + .build(); assertThat(flags.contains(/* flag= */ -1235)).isFalse(); assertThat(flags.contains(/* flag= */ -1234)).isTrue(); @@ -57,79 +58,94 @@ public final class MutableFlagsTest { } @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); + public void contains_afterAddIf_returnsTrueForAddedValues() { + ExoFlags flags = + new ExoFlags.Builder() + .addIf(/* flag= */ -1234, /* condition= */ true) + .addIf(/* flag= */ 0, /* condition= */ false) + .addIf(/* flag= */ 2, /* condition= */ true) + .addIf(/* flag= */ Integer.MAX_VALUE, /* condition= */ false) + .build(); - flags.clear(); - - assertThat(flags.contains(/* flag= */ -1234)).isFalse(); + assertThat(flags.contains(/* flag= */ -1235)).isFalse(); + assertThat(flags.contains(/* flag= */ -1234)).isTrue(); assertThat(flags.contains(/* flag= */ 0)).isFalse(); - assertThat(flags.contains(/* flag= */ 2)).isFalse(); + 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)).isFalse(); } + @Test + public void containsAny_withoutAdd_returnsFalseForAllValues() { + ExoFlags flags = new ExoFlags.Builder().build(); + + assertThat(flags.containsAny(/* flags...= */ -1234, 0, 2, Integer.MAX_VALUE)).isFalse(); + } + + @Test + public void containsAny_afterAdd_returnsTrueForAddedValues() { + ExoFlags flags = + new ExoFlags.Builder() + .add(/* flag= */ -1234) + .add(/* flag= */ 0) + .add(/* flag= */ 2) + .add(/* flag= */ Integer.MAX_VALUE) + .build(); + + assertThat( + flags.containsAny( + /* flags...= */ -1235, -1234, 0, 1, 2, Integer.MAX_VALUE - 1, Integer.MAX_VALUE)) + .isTrue(); + assertThat(flags.containsAny(/* flags...= */ -1235, 1, Integer.MAX_VALUE - 1)).isFalse(); + } + @Test public void size_withoutAdd_returnsZero() { - MutableFlags flags = new MutableFlags(); + ExoFlags flags = new ExoFlags.Builder().build(); 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); + ExoFlags flags = + new ExoFlags.Builder() + .add(/* flag= */ 0) + .add(/* flag= */ 0) + .add(/* flag= */ 0) + .add(/* flag= */ 123) + .add(/* flag= */ 123) + .build(); 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(); + ExoFlags flags = new ExoFlags.Builder().build(); 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); + ExoFlags flags = new ExoFlags.Builder().add(/* flag= */ 0).add(/* flag= */ 123).build(); 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); + ExoFlags flags = + new ExoFlags.Builder() + .add(/* flag= */ 0) + .add(/* flag= */ 0) + .add(/* flag= */ 0) + .add(/* flag= */ 123) + .add(/* flag= */ 123) + .add(/* flag= */ 456) + .build(); List values = new ArrayList<>(); for (int i = 0; i < flags.size(); i++) { diff --git a/library/common/src/test/java/com/google/android/exoplayer2/util/ListenerSetTest.java b/library/common/src/test/java/com/google/android/exoplayer2/util/ListenerSetTest.java index 7d0b77664d..f8e6061998 100644 --- a/library/common/src/test/java/com/google/android/exoplayer2/util/ListenerSetTest.java +++ b/library/common/src/test/java/com/google/android/exoplayer2/util/ListenerSetTest.java @@ -42,9 +42,8 @@ public class ListenerSetTest { @Test public void queueEvent_withoutFlush_sendsNoEvents() { - ListenerSet listenerSet = - new ListenerSet<>( - Looper.myLooper(), Clock.DEFAULT, Flags::new, TestListener::iterationFinished); + ListenerSet listenerSet = + new ListenerSet<>(Looper.myLooper(), Clock.DEFAULT, TestListener::iterationFinished); TestListener listener = mock(TestListener.class); listenerSet.add(listener); @@ -57,9 +56,8 @@ public class ListenerSetTest { @Test public void flushEvents_sendsPreviouslyQueuedEventsToAllListeners() { - ListenerSet listenerSet = - new ListenerSet<>( - Looper.myLooper(), Clock.DEFAULT, Flags::new, TestListener::iterationFinished); + ListenerSet listenerSet = + new ListenerSet<>(Looper.myLooper(), Clock.DEFAULT, TestListener::iterationFinished); TestListener listener1 = mock(TestListener.class); TestListener listener2 = mock(TestListener.class); listenerSet.add(listener1); @@ -82,9 +80,8 @@ public class ListenerSetTest { @Test public void flushEvents_recursive_sendsEventsInCorrectOrder() { - ListenerSet listenerSet = - new ListenerSet<>( - Looper.myLooper(), Clock.DEFAULT, Flags::new, TestListener::iterationFinished); + ListenerSet listenerSet = + new ListenerSet<>(Looper.myLooper(), Clock.DEFAULT, TestListener::iterationFinished); // Listener1 sends callback3 recursively when receiving callback1. TestListener listener1 = spy( @@ -116,9 +113,8 @@ public class ListenerSetTest { @Test public void flushEvents_withMultipleMessageQueueIterations_sendsIterationFinishedEventPerIteration() { - ListenerSet listenerSet = - new ListenerSet<>( - Looper.myLooper(), Clock.DEFAULT, Flags::new, TestListener::iterationFinished); + ListenerSet listenerSet = + new ListenerSet<>(Looper.myLooper(), Clock.DEFAULT, TestListener::iterationFinished); // Listener1 sends callback1 recursively when receiving callback3. TestListener listener1 = spy( @@ -152,30 +148,29 @@ public class ListenerSetTest { 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).iterationFinished(createExoFlags(EVENT_ID_2)); + inOrder.verify(listener2).iterationFinished(createExoFlags(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).iterationFinished(createExoFlags(EVENT_ID_1, EVENT_ID_2)); + inOrder.verify(listener2).iterationFinished(createExoFlags(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.verify(listener1).iterationFinished(createExoFlags(EVENT_ID_1, EVENT_ID_3)); + inOrder.verify(listener2).iterationFinished(createExoFlags(EVENT_ID_1, EVENT_ID_3)); inOrder.verifyNoMoreInteractions(); } @Test public void flushEvents_calledFromIterationFinishedCallback_restartsIterationFinishedEvents() { - ListenerSet listenerSet = - new ListenerSet<>( - Looper.myLooper(), Clock.DEFAULT, Flags::new, TestListener::iterationFinished); + ListenerSet listenerSet = + new ListenerSet<>(Looper.myLooper(), Clock.DEFAULT, TestListener::iterationFinished); // Listener2 sends callback1 recursively when receiving the iteration finished event. TestListener listener2 = spy( @@ -183,7 +178,7 @@ public class ListenerSetTest { boolean eventSent; @Override - public void iterationFinished(Flags flags) { + public void iterationFinished(ExoFlags flags) { if (!eventSent) { listenerSet.sendEvent(EVENT_ID_1, TestListener::callback1); eventSent = true; @@ -203,22 +198,21 @@ public class ListenerSetTest { 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).iterationFinished(createExoFlags(EVENT_ID_2)); + inOrder.verify(listener2).iterationFinished(createExoFlags(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.verify(listener1).iterationFinished(createExoFlags(EVENT_ID_1)); + inOrder.verify(listener2).iterationFinished(createExoFlags(EVENT_ID_1)); + inOrder.verify(listener3).iterationFinished(createExoFlags(EVENT_ID_1, EVENT_ID_2)); inOrder.verifyNoMoreInteractions(); } @Test public void flushEvents_withUnsetEventFlag_doesNotThrow() { - ListenerSet listenerSet = - new ListenerSet<>( - Looper.myLooper(), Clock.DEFAULT, Flags::new, TestListener::iterationFinished); + ListenerSet listenerSet = + new ListenerSet<>(Looper.myLooper(), Clock.DEFAULT, TestListener::iterationFinished); listenerSet.queueEvent(/* eventFlag= */ C.INDEX_UNSET, TestListener::callback1); listenerSet.flushEvents(); @@ -229,9 +223,8 @@ public class ListenerSetTest { @Test public void add_withRecursion_onlyReceivesUpdatesForFutureEvents() { - ListenerSet listenerSet = - new ListenerSet<>( - Looper.myLooper(), Clock.DEFAULT, Flags::new, TestListener::iterationFinished); + ListenerSet listenerSet = + new ListenerSet<>(Looper.myLooper(), Clock.DEFAULT, TestListener::iterationFinished); TestListener listener2 = mock(TestListener.class); // Listener1 adds listener2 recursively. TestListener listener1 = @@ -255,16 +248,15 @@ public class ListenerSetTest { 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.verify(listener1).iterationFinished(createExoFlags(EVENT_ID_1, EVENT_ID_2)); + inOrder.verify(listener2).iterationFinished(createExoFlags(EVENT_ID_2)); inOrder.verifyNoMoreInteractions(); } @Test public void add_withQueueing_onlyReceivesUpdatesForFutureEvents() { - ListenerSet listenerSet = - new ListenerSet<>( - Looper.myLooper(), Clock.DEFAULT, Flags::new, TestListener::iterationFinished); + ListenerSet listenerSet = + new ListenerSet<>(Looper.myLooper(), Clock.DEFAULT, TestListener::iterationFinished); TestListener listener1 = mock(TestListener.class); TestListener listener2 = mock(TestListener.class); @@ -281,16 +273,15 @@ public class ListenerSetTest { 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.verify(listener1).iterationFinished(createExoFlags(EVENT_ID_1, EVENT_ID_2)); + inOrder.verify(listener2).iterationFinished(createExoFlags(EVENT_ID_2)); inOrder.verifyNoMoreInteractions(); } @Test public void remove_withRecursion_stopsReceivingEventsImmediately() { - ListenerSet listenerSet = - new ListenerSet<>( - Looper.myLooper(), Clock.DEFAULT, Flags::new, TestListener::iterationFinished); + ListenerSet listenerSet = + new ListenerSet<>(Looper.myLooper(), Clock.DEFAULT, TestListener::iterationFinished); TestListener listener2 = mock(TestListener.class); // Listener1 removes listener2 recursively. TestListener listener1 = @@ -311,15 +302,14 @@ public class ListenerSetTest { ShadowLooper.runMainLooperToNextTask(); verify(listener1).callback1(); - verify(listener1).iterationFinished(Flags.create(EVENT_ID_1)); + verify(listener1).iterationFinished(createExoFlags(EVENT_ID_1)); verifyNoMoreInteractions(listener1, listener2); } @Test public void remove_withQueueing_stopsReceivingEventsImmediately() { - ListenerSet listenerSet = - new ListenerSet<>( - Looper.myLooper(), Clock.DEFAULT, Flags::new, TestListener::iterationFinished); + ListenerSet listenerSet = + new ListenerSet<>(Looper.myLooper(), Clock.DEFAULT, TestListener::iterationFinished); TestListener listener1 = mock(TestListener.class); TestListener listener2 = mock(TestListener.class); listenerSet.add(listener1); @@ -333,15 +323,14 @@ public class ListenerSetTest { ShadowLooper.runMainLooperToNextTask(); verify(listener2, times(2)).callback1(); - verify(listener2).iterationFinished(Flags.create(EVENT_ID_1)); + verify(listener2).iterationFinished(createExoFlags(EVENT_ID_1)); verifyNoMoreInteractions(listener1, listener2); } @Test public void release_stopsForwardingEventsImmediately() { - ListenerSet listenerSet = - new ListenerSet<>( - Looper.myLooper(), Clock.DEFAULT, Flags::new, TestListener::iterationFinished); + ListenerSet listenerSet = + new ListenerSet<>(Looper.myLooper(), Clock.DEFAULT, TestListener::iterationFinished); TestListener listener2 = mock(TestListener.class); // Listener1 releases the set from within the callback. TestListener listener1 = @@ -361,15 +350,14 @@ public class ListenerSetTest { ShadowLooper.runMainLooperToNextTask(); verify(listener1).callback1(); - verify(listener1).iterationFinished(Flags.create(EVENT_ID_1)); + verify(listener1).iterationFinished(createExoFlags(EVENT_ID_1)); verifyNoMoreInteractions(listener1, listener2); } @Test public void release_preventsRegisteringNewListeners() { - ListenerSet listenerSet = - new ListenerSet<>( - Looper.myLooper(), Clock.DEFAULT, Flags::new, TestListener::iterationFinished); + ListenerSet listenerSet = + new ListenerSet<>(Looper.myLooper(), Clock.DEFAULT, TestListener::iterationFinished); TestListener listener = mock(TestListener.class); listenerSet.release(); @@ -381,9 +369,8 @@ public class ListenerSetTest { @Test public void lazyRelease_stopsForwardingEventsFromNewHandlerMessagesAndCallsReleaseCallback() { - ListenerSet listenerSet = - new ListenerSet<>( - Looper.myLooper(), Clock.DEFAULT, Flags::new, TestListener::iterationFinished); + ListenerSet listenerSet = + new ListenerSet<>(Looper.myLooper(), Clock.DEFAULT, TestListener::iterationFinished); TestListener listener = mock(TestListener.class); listenerSet.add(listener); @@ -403,8 +390,8 @@ public class ListenerSetTest { // lazy release. verify(listener, times(3)).callback1(); verify(listener).callback3(); - verify(listener).iterationFinished(Flags.create(EVENT_ID_1)); - verify(listener).iterationFinished(Flags.create(EVENT_ID_1, EVENT_ID_3)); + verify(listener).iterationFinished(createExoFlags(EVENT_ID_1)); + verify(listener).iterationFinished(createExoFlags(EVENT_ID_1, EVENT_ID_3)); verifyNoMoreInteractions(listener); } @@ -415,17 +402,14 @@ public class ListenerSetTest { default void callback3() {} - default void iterationFinished(Flags flags) {} + default void iterationFinished(ExoFlags 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; + private static ExoFlags createExoFlags(int... flagValues) { + ExoFlags.Builder flagsBuilder = new ExoFlags.Builder(); + for (int value : flagValues) { + flagsBuilder.add(value); } + return flagsBuilder.build(); } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java index 47b9711605..6539b630fd 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java @@ -71,7 +71,7 @@ import java.util.List; private final HandlerWrapper playbackInfoUpdateHandler; private final ExoPlayerImplInternal.PlaybackInfoUpdateListener playbackInfoUpdateListener; private final ExoPlayerImplInternal internalPlayer; - private final ListenerSet listeners; + private final ListenerSet listeners; private final Timeline.Period period; private final List mediaSourceHolderSnapshots; private final boolean useLazyPreparation; @@ -165,8 +165,7 @@ import java.util.List; new ListenerSet<>( applicationLooper, clock, - Player.Events::new, - (listener, eventFlags) -> listener.onEvents(playerForListeners, eventFlags)); + (listener, flags) -> listener.onEvents(playerForListeners, new Events(flags))); mediaSourceHolderSnapshots = new ArrayList<>(); shuffleOrder = new ShuffleOrder.DefaultShuffleOrder(/* length= */ 0); emptyTrackSelectorResult = diff --git a/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsCollector.java b/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsCollector.java index 92f4ff78ff..55c37f754a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsCollector.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsCollector.java @@ -78,7 +78,7 @@ public class AnalyticsCollector private final MediaPeriodQueueTracker mediaPeriodQueueTracker; private final SparseArray eventTimes; - private ListenerSet listeners; + private ListenerSet listeners; private @MonotonicNonNull Player player; private boolean isSeeking; @@ -89,12 +89,7 @@ public class AnalyticsCollector */ public AnalyticsCollector(Clock clock) { this.clock = checkNotNull(clock); - listeners = - new ListenerSet<>( - Util.getCurrentOrMainLooper(), - clock, - AnalyticsListener.Events::new, - (listener, eventFlags) -> {}); + listeners = new ListenerSet<>(Util.getCurrentOrMainLooper(), clock, (listener, flags) -> {}); period = new Period(); window = new Window(); mediaPeriodQueueTracker = new MediaPeriodQueueTracker(period); @@ -137,10 +132,8 @@ public class AnalyticsCollector listeners = listeners.copy( looper, - (listener, events) -> { - events.setEventTimes(eventTimes); - listener.onEvents(player, events); - }); + (listener, flags) -> + listener.onEvents(player, new AnalyticsListener.Events(flags, eventTimes))); } /** diff --git a/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsListener.java b/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsListener.java index d9978ed6a4..b323ed435c 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsListener.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsListener.java @@ -47,7 +47,7 @@ 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.android.exoplayer2.util.ExoFlags; import com.google.common.base.Objects; import java.io.IOException; import java.lang.annotation.Documented; @@ -70,13 +70,27 @@ import java.util.List; public interface AnalyticsListener { /** A set of {@link EventFlags}. */ - final class Events extends MutableFlags { + final class Events { + private final ExoFlags flags; private final SparseArray eventTimes; - /** Creates the set of event flags. */ - public Events() { - eventTimes = new SparseArray<>(/* initialCapacity= */ 0); + /** + * Creates an instance. + * + * @param flags The {@link ExoFlags} containing the {@link EventFlags} in the set. + * @param eventTimes A map from {@link EventFlags} to {@link EventTime}. Must at least contain + * all the events recorded in {@code flags}. Events that are not recorded in {@code flags} + * are ignored. + */ + public Events(ExoFlags flags, SparseArray eventTimes) { + this.flags = flags; + SparseArray flagsToTimes = new SparseArray<>(/* initialCapacity= */ flags.size()); + for (int i = 0; i < flags.size(); i++) { + @EventFlags int eventFlag = flags.get(i); + flagsToTimes.append(eventFlag, checkNotNull(eventTimes.get(eventFlag))); + } + this.eventTimes = flagsToTimes; } /** @@ -89,30 +103,14 @@ public interface AnalyticsListener { return checkNotNull(eventTimes.get(event)); } - /** - * Sets the {@link EventTime} values for events recorded in this set. - * - * @param eventTimes A map from {@link EventFlags} to {@link EventTime}. Must at least contain - * all the events recorded in this set. - */ - public void setEventTimes(SparseArray eventTimes) { - this.eventTimes.clear(); - for (int i = 0; i < size(); i++) { - @EventFlags int eventFlag = get(i); - this.eventTimes.append(eventFlag, checkNotNull(eventTimes.get(eventFlag))); - } - } - /** * 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); + return flags.contains(event); } /** @@ -121,10 +119,13 @@ public interface AnalyticsListener { * @param events The {@link EventFlags events}. * @return Whether any of the events occurred. */ - @Override public boolean containsAny(@EventFlags int... events) { - // Overridden to add IntDef compiler enforcement and new JavaDoc. - return super.containsAny(events); + return flags.containsAny(events); + } + + /** Returns the number of events in the set. */ + public int size() { + return flags.size(); } /** @@ -136,11 +137,9 @@ public interface AnalyticsListener { * @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); + return flags.get(index); } }