Replace MutableFlags by immutable class

PiperOrigin-RevId: 362043183
This commit is contained in:
kimvde 2021-03-10 15:23:40 +00:00 committed by Ian Baker
parent 851c915e38
commit 4a6859e5e2
10 changed files with 289 additions and 279 deletions

View File

@ -95,7 +95,7 @@ public final class CastPlayer extends BasePlayer {
private final SeekResultCallback seekResultCallback;
// Listeners and notification.
private final ListenerSet<Player.EventListener, Player.Events> listeners;
private final ListenerSet<Player.EventListener> 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);

View File

@ -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<EventListener, Events> listeners;
private final ListenerSet<EventListener> 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;

View File

@ -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();
}
}

View File

@ -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;
*
* <p>Intended for usages where the number of flags may exceed 32 and can no longer be represented
* by an IntDef.
*
* <p>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;
/** Creates the set of flags. */
public MutableFlags() {
private boolean buildCalled;
/** Creates a builder. */
public Builder() {
flags = new SparseBooleanArray();
}
/** Clears all previously set flags. */
public void clear() {
flags.clear();
/**
* 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 to the set.
* Adds a flag if the provided condition is true. Does nothing otherwise.
*
* @param flag The flag to add.
* @param flag A flag.
* @param condition A condition.
* @return This builder.
* @throws IllegalStateException If {@link #build()} has already been called.
*/
public void add(int flag) {
flags.append(flag, /* value= */ true);
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;
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);
}

View File

@ -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 <T> The listener type.
* @param <E> The {@link MutableFlags} type used to indicate which events occurred.
*/
public final class ListenerSet<T, E extends MutableFlags> {
public final class ListenerSet<T> {
/**
* An event sent to a listener.
@ -55,17 +53,17 @@ public final class ListenerSet<T, E extends MutableFlags> {
* 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> {
public interface IterationFinishedEvent<T> {
/**
* 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<T, E extends MutableFlags> {
private final Clock clock;
private final HandlerWrapper handler;
private final Supplier<E> eventFlagsSupplier;
private final IterationFinishedEvent<T, E> iterationFinishedEvent;
private final CopyOnWriteArraySet<ListenerHolder<T, E>> listeners;
private final IterationFinishedEvent<T> iterationFinishedEvent;
private final CopyOnWriteArraySet<ListenerHolder<T>> listeners;
private final ArrayDeque<Runnable> flushingEvents;
private final ArrayDeque<Runnable> queuedEvents;
@ -87,33 +84,24 @@ public final class ListenerSet<T, E extends MutableFlags> {
* @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<E> eventFlagsSupplier,
IterationFinishedEvent<T, E> iterationFinishedEvent) {
public ListenerSet(Looper looper, Clock clock, IterationFinishedEvent<T> iterationFinishedEvent) {
this(
/* listeners= */ new CopyOnWriteArraySet<>(),
looper,
clock,
eventFlagsSupplier,
iterationFinishedEvent);
}
private ListenerSet(
CopyOnWriteArraySet<ListenerHolder<T, E>> listeners,
CopyOnWriteArraySet<ListenerHolder<T>> listeners,
Looper looper,
Clock clock,
Supplier<E> eventFlagsSupplier,
IterationFinishedEvent<T, E> iterationFinishedEvent) {
IterationFinishedEvent<T> 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<T, E extends MutableFlags> {
* @return The copied listener set.
*/
@CheckResult
public ListenerSet<T, E> copy(
Looper looper, IterationFinishedEvent<T, E> iterationFinishedEvent) {
return new ListenerSet<>(listeners, looper, clock, eventFlagsSupplier, iterationFinishedEvent);
public ListenerSet<T> copy(Looper looper, IterationFinishedEvent<T> iterationFinishedEvent) {
return new ListenerSet<>(listeners, looper, clock, iterationFinishedEvent);
}
/**
@ -149,7 +136,7 @@ public final class ListenerSet<T, E extends MutableFlags> {
return;
}
Assertions.checkNotNull(listener);
listeners.add(new ListenerHolder<>(listener, eventFlagsSupplier));
listeners.add(new ListenerHolder<>(listener));
}
/**
@ -160,7 +147,7 @@ public final class ListenerSet<T, E extends MutableFlags> {
* @param listener The listener to be removed.
*/
public void remove(T listener) {
for (ListenerHolder<T, E> listenerHolder : listeners) {
for (ListenerHolder<T> listenerHolder : listeners) {
if (listenerHolder.listener.equals(listener)) {
listenerHolder.release(iterationFinishedEvent);
listeners.remove(listenerHolder);
@ -176,11 +163,10 @@ public final class ListenerSet<T, E extends MutableFlags> {
* @param event The event.
*/
public void queueEvent(int eventFlag, Event<T> event) {
CopyOnWriteArraySet<ListenerHolder<T, E>> listenerSnapshot =
new CopyOnWriteArraySet<>(listeners);
CopyOnWriteArraySet<ListenerHolder<T>> listenerSnapshot = new CopyOnWriteArraySet<>(listeners);
queuedEvents.add(
() -> {
for (ListenerHolder<T, E> holder : listenerSnapshot) {
for (ListenerHolder<T> holder : listenerSnapshot) {
holder.invoke(eventFlag, event);
}
});
@ -226,7 +212,7 @@ public final class ListenerSet<T, E extends MutableFlags> {
* <p>This will ensure no events are sent to any listener after this method has been called.
*/
public void release() {
for (ListenerHolder<T, E> listenerHolder : listeners) {
for (ListenerHolder<T> listenerHolder : listeners) {
listenerHolder.release(iterationFinishedEvent);
}
listeners.clear();
@ -249,8 +235,8 @@ public final class ListenerSet<T, E extends MutableFlags> {
private boolean handleMessage(Message message) {
if (message.what == MSG_ITERATION_FINISHED) {
for (ListenerHolder<T, E> holder : listeners) {
holder.iterationFinished(eventFlagsSupplier, iterationFinishedEvent);
for (ListenerHolder<T> 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<T, E extends MutableFlags> {
return true;
}
private static final class ListenerHolder<T, E extends MutableFlags> {
private static final class ListenerHolder<T> {
@Nonnull public final T listener;
private E eventsFlags;
private ExoFlags.Builder flagsBuilder;
private boolean needsIterationFinishedEvent;
private boolean released;
public ListenerHolder(@Nonnull T listener, Supplier<E> eventFlagSupplier) {
public ListenerHolder(@Nonnull T listener) {
this.listener = listener;
this.eventsFlags = eventFlagSupplier.get();
this.flagsBuilder = new ExoFlags.Builder();
}
public void release(IterationFinishedEvent<T, E> event) {
public void release(IterationFinishedEvent<T> event) {
released = true;
if (needsIterationFinishedEvent) {
event.invoke(listener, eventsFlags);
event.invoke(listener, flagsBuilder.build());
}
}
public void invoke(int eventFlag, Event<T> event) {
if (!released) {
if (eventFlag != C.INDEX_UNSET) {
eventsFlags.add(eventFlag);
flagsBuilder.add(eventFlag);
}
needsIterationFinishedEvent = true;
event.invoke(listener);
}
}
public void iterationFinished(
Supplier<E> eventFlagSupplier, IterationFinishedEvent<T, E> event) {
public void iterationFinished(IterationFinishedEvent<T> 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<T, E extends MutableFlags> {
if (other == null || getClass() != other.getClass()) {
return false;
}
return listener.equals(((ListenerHolder<?, ?>) other).listener);
return listener.equals(((ListenerHolder<?>) other).listener);
}
@Override

View File

@ -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<Integer> values = new ArrayList<>();
for (int i = 0; i < flags.size(); i++) {

View File

@ -42,9 +42,8 @@ public class ListenerSetTest {
@Test
public void queueEvent_withoutFlush_sendsNoEvents() {
ListenerSet<TestListener, Flags> listenerSet =
new ListenerSet<>(
Looper.myLooper(), Clock.DEFAULT, Flags::new, TestListener::iterationFinished);
ListenerSet<TestListener> 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<TestListener, Flags> listenerSet =
new ListenerSet<>(
Looper.myLooper(), Clock.DEFAULT, Flags::new, TestListener::iterationFinished);
ListenerSet<TestListener> 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<TestListener, Flags> listenerSet =
new ListenerSet<>(
Looper.myLooper(), Clock.DEFAULT, Flags::new, TestListener::iterationFinished);
ListenerSet<TestListener> 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<TestListener, Flags> listenerSet =
new ListenerSet<>(
Looper.myLooper(), Clock.DEFAULT, Flags::new, TestListener::iterationFinished);
ListenerSet<TestListener> 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<TestListener, Flags> listenerSet =
new ListenerSet<>(
Looper.myLooper(), Clock.DEFAULT, Flags::new, TestListener::iterationFinished);
ListenerSet<TestListener> 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<TestListener, Flags> listenerSet =
new ListenerSet<>(
Looper.myLooper(), Clock.DEFAULT, Flags::new, TestListener::iterationFinished);
ListenerSet<TestListener> 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<TestListener, Flags> listenerSet =
new ListenerSet<>(
Looper.myLooper(), Clock.DEFAULT, Flags::new, TestListener::iterationFinished);
ListenerSet<TestListener> 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<TestListener, Flags> listenerSet =
new ListenerSet<>(
Looper.myLooper(), Clock.DEFAULT, Flags::new, TestListener::iterationFinished);
ListenerSet<TestListener> 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<TestListener, Flags> listenerSet =
new ListenerSet<>(
Looper.myLooper(), Clock.DEFAULT, Flags::new, TestListener::iterationFinished);
ListenerSet<TestListener> 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<TestListener, Flags> listenerSet =
new ListenerSet<>(
Looper.myLooper(), Clock.DEFAULT, Flags::new, TestListener::iterationFinished);
ListenerSet<TestListener> 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<TestListener, Flags> listenerSet =
new ListenerSet<>(
Looper.myLooper(), Clock.DEFAULT, Flags::new, TestListener::iterationFinished);
ListenerSet<TestListener> 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<TestListener, Flags> listenerSet =
new ListenerSet<>(
Looper.myLooper(), Clock.DEFAULT, Flags::new, TestListener::iterationFinished);
ListenerSet<TestListener> 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<TestListener, Flags> listenerSet =
new ListenerSet<>(
Looper.myLooper(), Clock.DEFAULT, Flags::new, TestListener::iterationFinished);
ListenerSet<TestListener> 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();
private static ExoFlags createExoFlags(int... flagValues) {
ExoFlags.Builder flagsBuilder = new ExoFlags.Builder();
for (int value : flagValues) {
flags.add(value);
}
return flags;
flagsBuilder.add(value);
}
return flagsBuilder.build();
}
}

View File

@ -71,7 +71,7 @@ import java.util.List;
private final HandlerWrapper playbackInfoUpdateHandler;
private final ExoPlayerImplInternal.PlaybackInfoUpdateListener playbackInfoUpdateListener;
private final ExoPlayerImplInternal internalPlayer;
private final ListenerSet<Player.EventListener, Player.Events> listeners;
private final ListenerSet<Player.EventListener> listeners;
private final Timeline.Period period;
private final List<MediaSourceHolderSnapshot> 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 =

View File

@ -78,7 +78,7 @@ public class AnalyticsCollector
private final MediaPeriodQueueTracker mediaPeriodQueueTracker;
private final SparseArray<EventTime> eventTimes;
private ListenerSet<AnalyticsListener, AnalyticsListener.Events> listeners;
private ListenerSet<AnalyticsListener> 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)));
}
/**

View File

@ -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<EventTime> 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<EventTime> eventTimes) {
this.flags = flags;
SparseArray<EventTime> 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<EventTime> 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);
}
}