-
Notifications
You must be signed in to change notification settings - Fork 577
Add listener bookkeeping to ForwardingPlayer #2676
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can probably also remove the ForwardingListener.equals/hashCode
implementations in this same PR?
Please can you also add some tests to ForwardingPlayerTest
for this change.
@@ -78,7 +81,9 @@ public Looper getApplicationLooper() { | |||
*/ | |||
@Override | |||
public void addListener(Listener listener) { | |||
player.addListener(new ForwardingListener(this, listener)); | |||
ForwardingListener forwardingListener = new ForwardingListener(this, listener); | |||
listeners.put(listener, forwardingListener); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Player.addListener()
can be called on any thread, so you need some concurrency protection here.
@@ -78,7 +81,9 @@ public Looper getApplicationLooper() { | |||
*/ | |||
@Override | |||
public void addListener(Listener listener) { | |||
player.addListener(new ForwardingListener(this, listener)); | |||
ForwardingListener forwardingListener = new ForwardingListener(this, listener); | |||
listeners.put(listener, forwardingListener); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You probably want to check whether the map already contains key = listener
or not - otherwise the following operations go wrong:
// Adds listener -> forwardingListener1 mapping & passes
// forwardingListener1 to underlying player.
forwardingPlayer.addListener(listener);
// Replaces listener -> forwardingListener1 mapping with
// listener -> forwardingListener2 and passes
// forwardingListener1 to underlying player.
forwardingPlayer.addListener(listener);
// Removes listener -> forwardingListener2 mapping
// and removes forwardingListener2 from underlying player.
forwardingPlayer.removeListener(listener);
// No way to remove forwardingListener1 from the underlying player...
@@ -54,6 +56,7 @@ | |||
public class ForwardingPlayer implements Player { | |||
|
|||
private final Player player; | |||
protected final Map<Listener, Listener> listeners = new HashMap<>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this be private?
Consider also using IdentityHashMap
to reinforce the idea of identity equality.
player.removeListener(new ForwardingListener(this, listener)); | ||
Listener forwardingListener = listeners.remove(listener); | ||
if (forwardingListener == null) { | ||
throw new IllegalArgumentException("Trying to remove listener that never was registered"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if we can avoid always throwing an exception in this case - atm ExoPlayer
doesn't throw if you remove an unrecognized listener (since it's assumed that the goal state of "this listener isn't registered" is met by doing nothing, so the method can do nothing).
I don't think the Player
interface mandates what happens in this case. We could delegate the decision of "what happens when an unrecognized listener is removed?" to the underlying impl by deliberately passing in a known-unrecognized impl in here:
if (forwardingListener == null) {
// Try and remove a listener instance that is guaranteed not to be
// registered, allowing the underlying impl to either throw or ignore.
player.removeListener(new Listener() {});
}
@@ -78,7 +81,9 @@ public Looper getApplicationLooper() { | |||
*/ | |||
@Override | |||
public void addListener(Listener listener) { | |||
player.addListener(new ForwardingListener(this, listener)); | |||
ForwardingListener forwardingListener = new ForwardingListener(this, listener); | |||
listeners.put(listener, forwardingListener); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if this addition to the bookkeeping set should go after the call to the underlying player (in case it fails for some reason)?
3f70575
to
98fa66f
Compare
Thanks for the thoughful review. :)
Done.
Done.
Done.
Done, and this is tested in the test now.
Yeah, it could, but I think it's better to make it protected. While it's unrelated to the bug that prompted this PR, having access to all registered listeners to be able to send them events is quite useful in the context of a ForwardingPlayer (as it generally has to dispatch events for everything if it changes anything at all).
Done. It's also tested in the test that this works even if player does it differently.
Although not in the way you proposed, this is done as well, and tested as well.
Done. |
libraries/common/src/main/java/androidx/media3/common/ForwardingPlayer.java
Show resolved
Hide resolved
player.addListener(listener1); | ||
player.addListener(listener2); | ||
assertThat(player.listeners).hasSize(1); | ||
player.listeners.clear(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Up to here it looks like you are basically testing your test/fake implementations, and not the actual prod code.
I think the implementation of AllIsEqualPlayerListener
and EqualityBasedFakePlayer
is trivial enough that you don't need to check it every time (so you can remove those assertions here and below).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Point taken, will remove.
libraries/common/src/test/java/androidx/media3/common/ForwardingPlayerTest.java
Show resolved
Hide resolved
// Add listener1 again. | ||
assertThrows(IllegalArgumentException.class, () -> forwardingPlayer.addListener(listener1)); | ||
assertThat(player.listeners).hasSize(1); | ||
assertThrows(IllegalArgumentException.class, () -> forwardingPlayer.removeListener(listener2)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this case of removing an un-registered listener is covered by the assertion below on L159, so it can likely be removed from here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
I understand this desire, but I think we should keep it
|
Point taken, I admit I'm only using ForwardingPlayer for this because ForwardingSimpleBasePlayer didn't work out as ExoPlayer keeps triggering asserts (see #2674). The long term solution is to fix ExoPlayer to not trigger asserts and use ForwardingSimpleBasePlayer. |
libraries/common/src/main/java/androidx/media3/common/ForwardingPlayer.java
Show resolved
Hide resolved
libraries/common/src/test/java/androidx/media3/common/ForwardingPlayerTest.java
Show resolved
Hide resolved
I'm going to send this for internal review now. You may see some more commits being added as I make changes in response to review feedback. Please refrain from pushing any more substantive changes as it will complicate the internal review - thanks! |
Issue: #2675