Flatten listener using existing listeners
Adds a new Listener that extends all other listener. This is part of the component flattening goal. After components have been flattened in Player, and clients transitioned, existing listeners will be deprecated. PiperOrigin-RevId: 362287507
This commit is contained in:
parent
a4ad351fb1
commit
baf1516ae4
@ -32,6 +32,8 @@
|
|||||||
* Fix playback position issue when re-preparing playback after a
|
* Fix playback position issue when re-preparing playback after a
|
||||||
BehindLiveWindowException
|
BehindLiveWindowException
|
||||||
([#8675](https://github.com/google/ExoPlayer/issues/8675)).
|
([#8675](https://github.com/google/ExoPlayer/issues/8675)).
|
||||||
|
* Add a `Listener` interface to receive all player events in a single
|
||||||
|
object.
|
||||||
* Remove deprecated symbols:
|
* Remove deprecated symbols:
|
||||||
* Remove `Player.DefaultEventListener`. Use `Player.EventListener`
|
* Remove `Player.DefaultEventListener`. Use `Player.EventListener`
|
||||||
instead.
|
instead.
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
package com.google.android.exoplayer2;
|
package com.google.android.exoplayer2;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
import com.google.android.exoplayer2.util.Util;
|
import com.google.android.exoplayer2.util.Util;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -29,6 +30,60 @@ public abstract class BasePlayer implements Player {
|
|||||||
window = new Timeline.Window();
|
window = new Timeline.Window();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void addListener(Listener listener) {
|
||||||
|
Assertions.checkNotNull(listener);
|
||||||
|
@Nullable AudioComponent audioComponent = getAudioComponent();
|
||||||
|
if (audioComponent != null) {
|
||||||
|
audioComponent.addAudioListener(listener);
|
||||||
|
}
|
||||||
|
@Nullable VideoComponent videoComponent = getVideoComponent();
|
||||||
|
if (videoComponent != null) {
|
||||||
|
videoComponent.addVideoListener(listener);
|
||||||
|
}
|
||||||
|
@Nullable TextComponent textComponent = getTextComponent();
|
||||||
|
if (textComponent != null) {
|
||||||
|
textComponent.addTextOutput(listener);
|
||||||
|
}
|
||||||
|
@Nullable MetadataComponent metadataComponent = getMetadataComponent();
|
||||||
|
if (metadataComponent != null) {
|
||||||
|
metadataComponent.addMetadataOutput(listener);
|
||||||
|
}
|
||||||
|
@Nullable DeviceComponent deviceComponent = getDeviceComponent();
|
||||||
|
if (deviceComponent != null) {
|
||||||
|
deviceComponent.addDeviceListener(listener);
|
||||||
|
}
|
||||||
|
EventListener eventListener = listener;
|
||||||
|
addListener(eventListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void removeListener(Listener listener) {
|
||||||
|
Assertions.checkNotNull(listener);
|
||||||
|
@Nullable AudioComponent audioComponent = getAudioComponent();
|
||||||
|
if (audioComponent != null) {
|
||||||
|
audioComponent.removeAudioListener(listener);
|
||||||
|
}
|
||||||
|
@Nullable VideoComponent videoComponent = getVideoComponent();
|
||||||
|
if (videoComponent != null) {
|
||||||
|
videoComponent.removeVideoListener(listener);
|
||||||
|
}
|
||||||
|
@Nullable TextComponent textComponent = getTextComponent();
|
||||||
|
if (textComponent != null) {
|
||||||
|
textComponent.removeTextOutput(listener);
|
||||||
|
}
|
||||||
|
@Nullable MetadataComponent metadataComponent = getMetadataComponent();
|
||||||
|
if (metadataComponent != null) {
|
||||||
|
metadataComponent.removeMetadataOutput(listener);
|
||||||
|
}
|
||||||
|
@Nullable DeviceComponent deviceComponent = getDeviceComponent();
|
||||||
|
if (deviceComponent != null) {
|
||||||
|
deviceComponent.removeDeviceListener(listener);
|
||||||
|
}
|
||||||
|
EventListener eventListener = listener;
|
||||||
|
removeListener(eventListener);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final void setMediaItem(MediaItem mediaItem) {
|
public final void setMediaItem(MediaItem mediaItem) {
|
||||||
setMediaItems(Collections.singletonList(mediaItem));
|
setMediaItems(Collections.singletonList(mediaItem));
|
||||||
|
@ -799,6 +799,19 @@ public interface Player {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listener of all changes in the Player.
|
||||||
|
*
|
||||||
|
* <p>All methods have no-op default implementations to allow selective overrides.
|
||||||
|
*/
|
||||||
|
interface Listener
|
||||||
|
extends VideoListener,
|
||||||
|
AudioListener,
|
||||||
|
TextOutput,
|
||||||
|
MetadataOutput,
|
||||||
|
DeviceListener,
|
||||||
|
EventListener {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Playback state. One of {@link #STATE_IDLE}, {@link #STATE_BUFFERING}, {@link #STATE_READY} or
|
* Playback state. One of {@link #STATE_IDLE}, {@link #STATE_BUFFERING}, {@link #STATE_READY} or
|
||||||
* {@link #STATE_ENDED}.
|
* {@link #STATE_ENDED}.
|
||||||
@ -1065,21 +1078,42 @@ public interface Player {
|
|||||||
Looper getApplicationLooper();
|
Looper getApplicationLooper();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register a listener to receive events from the player. The listener's methods will be called on
|
* Registers a listener to receive events from the player. The listener's methods will be called
|
||||||
* the thread that was used to construct the player. However, if the thread used to construct the
|
* on the thread that was used to construct the player. However, if the thread used to construct
|
||||||
* player does not have a {@link Looper}, then the listener will be called on the main thread.
|
* the player does not have a {@link Looper}, then the listener will be called on the main thread.
|
||||||
*
|
*
|
||||||
* @param listener The listener to register.
|
* @param listener The listener to register.
|
||||||
*/
|
*/
|
||||||
void addListener(EventListener listener);
|
void addListener(EventListener listener);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unregister a listener. The listener will no longer receive events from the player.
|
* Registers a listener to receive all events from the player.
|
||||||
|
*
|
||||||
|
* <p>Do not register the listener additionally in individual `Player` components (such as {@link
|
||||||
|
* Player.AudioComponent#addAudioListener(AudioListener)}, {@link
|
||||||
|
* Player.VideoComponent#addVideoListener(VideoListener)}) as it will already receive all their
|
||||||
|
* events.
|
||||||
|
*
|
||||||
|
* @param listener The listener to register.
|
||||||
|
*/
|
||||||
|
void addListener(Listener listener);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unregister a listener registered through {@link #addListener(EventListener)}. The listener will
|
||||||
|
* no longer receive events from the player.
|
||||||
*
|
*
|
||||||
* @param listener The listener to unregister.
|
* @param listener The listener to unregister.
|
||||||
*/
|
*/
|
||||||
void removeListener(EventListener listener);
|
void removeListener(EventListener listener);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unregister a listener registered through {@link #addListener(Listener)}. The listener will no
|
||||||
|
* longer receive events.
|
||||||
|
*
|
||||||
|
* @param listener The listener to unregister.
|
||||||
|
*/
|
||||||
|
void removeListener(Listener listener);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clears the playlist, adds the specified {@link MediaItem MediaItems} and resets the position to
|
* Clears the playlist, adds the specified {@link MediaItem MediaItems} and resets the position to
|
||||||
* the default position.
|
* the default position.
|
||||||
|
@ -55,6 +55,7 @@ import androidx.test.core.app.ApplicationProvider;
|
|||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
import com.google.android.exoplayer2.Player.DiscontinuityReason;
|
import com.google.android.exoplayer2.Player.DiscontinuityReason;
|
||||||
import com.google.android.exoplayer2.Player.EventListener;
|
import com.google.android.exoplayer2.Player.EventListener;
|
||||||
|
import com.google.android.exoplayer2.Player.Listener;
|
||||||
import com.google.android.exoplayer2.Timeline.Window;
|
import com.google.android.exoplayer2.Timeline.Window;
|
||||||
import com.google.android.exoplayer2.analytics.AnalyticsListener;
|
import com.google.android.exoplayer2.analytics.AnalyticsListener;
|
||||||
import com.google.android.exoplayer2.audio.AudioAttributes;
|
import com.google.android.exoplayer2.audio.AudioAttributes;
|
||||||
@ -172,25 +173,25 @@ public final class ExoPlayerTest {
|
|||||||
FakeRenderer renderer = new FakeRenderer(C.TRACK_TYPE_UNKNOWN);
|
FakeRenderer renderer = new FakeRenderer(C.TRACK_TYPE_UNKNOWN);
|
||||||
|
|
||||||
SimpleExoPlayer player = new TestExoPlayerBuilder(context).setRenderers(renderer).build();
|
SimpleExoPlayer player = new TestExoPlayerBuilder(context).setRenderers(renderer).build();
|
||||||
EventListener mockEventListener = mock(EventListener.class);
|
Listener mockListener = mock(Listener.class);
|
||||||
player.addListener(mockEventListener);
|
player.addListener(mockListener);
|
||||||
|
|
||||||
player.setMediaSource(new FakeMediaSource(timeline, ExoPlayerTestRunner.VIDEO_FORMAT));
|
player.setMediaSource(new FakeMediaSource(timeline, ExoPlayerTestRunner.VIDEO_FORMAT));
|
||||||
player.prepare();
|
player.prepare();
|
||||||
player.play();
|
player.play();
|
||||||
runUntilPlaybackState(player, Player.STATE_ENDED);
|
runUntilPlaybackState(player, Player.STATE_ENDED);
|
||||||
|
|
||||||
InOrder inOrder = inOrder(mockEventListener);
|
InOrder inOrder = inOrder(mockListener);
|
||||||
inOrder
|
inOrder
|
||||||
.verify(mockEventListener)
|
.verify(mockListener)
|
||||||
.onTimelineChanged(
|
.onTimelineChanged(
|
||||||
argThat(noUid(expectedMaskingTimeline)),
|
argThat(noUid(expectedMaskingTimeline)),
|
||||||
eq(Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED));
|
eq(Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED));
|
||||||
inOrder
|
inOrder
|
||||||
.verify(mockEventListener)
|
.verify(mockListener)
|
||||||
.onTimelineChanged(
|
.onTimelineChanged(
|
||||||
argThat(noUid(timeline)), eq(Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE));
|
argThat(noUid(timeline)), eq(Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE));
|
||||||
inOrder.verify(mockEventListener, never()).onPositionDiscontinuity(anyInt());
|
inOrder.verify(mockListener, never()).onPositionDiscontinuity(anyInt());
|
||||||
assertThat(renderer.getFormatsRead()).isEmpty();
|
assertThat(renderer.getFormatsRead()).isEmpty();
|
||||||
assertThat(renderer.sampleBufferReadCount).isEqualTo(0);
|
assertThat(renderer.sampleBufferReadCount).isEqualTo(0);
|
||||||
assertThat(renderer.isEnded).isFalse();
|
assertThat(renderer.isEnded).isFalse();
|
||||||
@ -202,29 +203,29 @@ public final class ExoPlayerTest {
|
|||||||
Timeline timeline = new FakeTimeline();
|
Timeline timeline = new FakeTimeline();
|
||||||
FakeRenderer renderer = new FakeRenderer(C.TRACK_TYPE_VIDEO);
|
FakeRenderer renderer = new FakeRenderer(C.TRACK_TYPE_VIDEO);
|
||||||
SimpleExoPlayer player = new TestExoPlayerBuilder(context).setRenderers(renderer).build();
|
SimpleExoPlayer player = new TestExoPlayerBuilder(context).setRenderers(renderer).build();
|
||||||
EventListener mockEventListener = mock(EventListener.class);
|
Listener mockListener = mock(Listener.class);
|
||||||
player.addListener(mockEventListener);
|
player.addListener(mockListener);
|
||||||
|
|
||||||
player.setMediaSource(new FakeMediaSource(timeline, ExoPlayerTestRunner.VIDEO_FORMAT));
|
player.setMediaSource(new FakeMediaSource(timeline, ExoPlayerTestRunner.VIDEO_FORMAT));
|
||||||
player.prepare();
|
player.prepare();
|
||||||
player.play();
|
player.play();
|
||||||
runUntilPlaybackState(player, Player.STATE_ENDED);
|
runUntilPlaybackState(player, Player.STATE_ENDED);
|
||||||
|
|
||||||
InOrder inOrder = Mockito.inOrder(mockEventListener);
|
InOrder inOrder = Mockito.inOrder(mockListener);
|
||||||
inOrder
|
inOrder
|
||||||
.verify(mockEventListener)
|
.verify(mockListener)
|
||||||
.onTimelineChanged(
|
.onTimelineChanged(
|
||||||
argThat(noUid(placeholderTimeline)),
|
argThat(noUid(placeholderTimeline)),
|
||||||
eq(Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED));
|
eq(Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED));
|
||||||
inOrder
|
inOrder
|
||||||
.verify(mockEventListener)
|
.verify(mockListener)
|
||||||
.onTimelineChanged(
|
.onTimelineChanged(
|
||||||
argThat(noUid(timeline)), eq(Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE));
|
argThat(noUid(timeline)), eq(Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE));
|
||||||
inOrder
|
inOrder
|
||||||
.verify(mockEventListener)
|
.verify(mockListener)
|
||||||
.onTracksChanged(
|
.onTracksChanged(
|
||||||
eq(new TrackGroupArray(new TrackGroup(ExoPlayerTestRunner.VIDEO_FORMAT))), any());
|
eq(new TrackGroupArray(new TrackGroup(ExoPlayerTestRunner.VIDEO_FORMAT))), any());
|
||||||
inOrder.verify(mockEventListener, never()).onPositionDiscontinuity(anyInt());
|
inOrder.verify(mockListener, never()).onPositionDiscontinuity(anyInt());
|
||||||
assertThat(renderer.getFormatsRead()).containsExactly(ExoPlayerTestRunner.VIDEO_FORMAT);
|
assertThat(renderer.getFormatsRead()).containsExactly(ExoPlayerTestRunner.VIDEO_FORMAT);
|
||||||
assertThat(renderer.sampleBufferReadCount).isEqualTo(1);
|
assertThat(renderer.sampleBufferReadCount).isEqualTo(1);
|
||||||
assertThat(renderer.isEnded).isTrue();
|
assertThat(renderer.isEnded).isTrue();
|
||||||
|
@ -18,6 +18,11 @@ package com.google.android.exoplayer2;
|
|||||||
import static com.google.android.exoplayer2.robolectric.TestPlayerRunHelper.runUntilPlaybackState;
|
import static com.google.android.exoplayer2.robolectric.TestPlayerRunHelper.runUntilPlaybackState;
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyFloat;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyInt;
|
||||||
|
import static org.mockito.Mockito.atLeast;
|
||||||
|
import static org.mockito.Mockito.atLeastOnce;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
|
|
||||||
@ -79,4 +84,63 @@ public class SimpleExoPlayerTest {
|
|||||||
verify(listener).onVideoDisabled(any(), any());
|
verify(listener).onVideoDisabled(any(), any());
|
||||||
verify(listener).onPlayerReleased(any());
|
verify(listener).onPlayerReleased(any());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void releaseAfterRendererEvents_triggersPendingVideoEventsInListener() throws Exception {
|
||||||
|
SimpleExoPlayer player =
|
||||||
|
new SimpleExoPlayer.Builder(
|
||||||
|
ApplicationProvider.getApplicationContext(),
|
||||||
|
(handler, videoListener, audioListener, textOutput, metadataOutput) ->
|
||||||
|
new Renderer[] {new FakeVideoRenderer(handler, videoListener)})
|
||||||
|
.setClock(new AutoAdvancingFakeClock())
|
||||||
|
.build();
|
||||||
|
Player.Listener listener = mock(Player.Listener.class);
|
||||||
|
player.addListener(listener);
|
||||||
|
player.setMediaSource(
|
||||||
|
new FakeMediaSource(new FakeTimeline(), ExoPlayerTestRunner.VIDEO_FORMAT));
|
||||||
|
player.prepare();
|
||||||
|
player.play();
|
||||||
|
runUntilPlaybackState(player, Player.STATE_READY);
|
||||||
|
|
||||||
|
player.release();
|
||||||
|
ShadowLooper.runMainLooperToNextTask();
|
||||||
|
|
||||||
|
verify(listener, atLeastOnce()).onEvents(any(), any()); // EventListener
|
||||||
|
verify(listener).onRenderedFirstFrame(); // VideoListener
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void releaseAfterVolumeChanges_triggerPendingVolumeEventInListener() throws Exception {
|
||||||
|
SimpleExoPlayer player =
|
||||||
|
new SimpleExoPlayer.Builder(ApplicationProvider.getApplicationContext()).build();
|
||||||
|
Player.Listener listener = mock(Player.Listener.class);
|
||||||
|
player.addListener(listener);
|
||||||
|
|
||||||
|
player.setVolume(0F);
|
||||||
|
player.release();
|
||||||
|
ShadowLooper.runMainLooperToNextTask();
|
||||||
|
|
||||||
|
verify(listener).onVolumeChanged(anyFloat());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void releaseAfterVolumeChanges_triggerPendingDeviceVolumeEventsInListener() {
|
||||||
|
SimpleExoPlayer player =
|
||||||
|
new SimpleExoPlayer.Builder(ApplicationProvider.getApplicationContext()).build();
|
||||||
|
Player.Listener listener = mock(Player.Listener.class);
|
||||||
|
player.addListener(listener);
|
||||||
|
|
||||||
|
int deviceVolume = player.getDeviceVolume();
|
||||||
|
try {
|
||||||
|
player.setDeviceVolume(deviceVolume + 1); // No-op if at max volume.
|
||||||
|
player.setDeviceVolume(deviceVolume - 1); // No-op if at min volume.
|
||||||
|
} finally {
|
||||||
|
player.setDeviceVolume(deviceVolume); // Restore original volume.
|
||||||
|
}
|
||||||
|
|
||||||
|
player.release();
|
||||||
|
ShadowLooper.runMainLooperToNextTask();
|
||||||
|
|
||||||
|
verify(listener, atLeast(2)).onDeviceVolumeChanged(anyInt(), anyBoolean());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user