1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 * 16 */ 17 package com.google.android.exoplayer2.util; 18 19 import static org.mockito.ArgumentMatchers.any; 20 import static org.mockito.ArgumentMatchers.anyInt; 21 import static org.mockito.Mockito.mock; 22 import static org.mockito.Mockito.never; 23 import static org.mockito.Mockito.verify; 24 import static org.mockito.Mockito.verifyNoMoreInteractions; 25 26 import android.os.Handler; 27 import android.os.Looper; 28 import androidx.annotation.Nullable; 29 import androidx.test.ext.junit.runners.AndroidJUnit4; 30 import com.google.android.exoplayer2.drm.DrmSessionEventListener; 31 import com.google.android.exoplayer2.source.MediaSource; 32 import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; 33 import com.google.android.exoplayer2.source.MediaSourceEventListener; 34 import org.junit.Before; 35 import org.junit.Rule; 36 import org.junit.Test; 37 import org.junit.runner.RunWith; 38 import org.mockito.Mock; 39 import org.mockito.junit.MockitoJUnit; 40 import org.mockito.junit.MockitoRule; 41 42 /** Tests for {@link MediaSourceEventDispatcher}. */ 43 @RunWith(AndroidJUnit4.class) 44 public class MediaSourceEventDispatcherTest { 45 46 private static final MediaSource.MediaPeriodId MEDIA_PERIOD_ID = 47 new MediaSource.MediaPeriodId("test uid"); 48 private static final int WINDOW_INDEX = 200; 49 private static final int MEDIA_TIME_OFFSET_MS = 1_000; 50 51 @Rule public final MockitoRule mockito = MockitoJUnit.rule(); 52 53 @Mock private MediaSourceEventListener mediaSourceEventListener; 54 @Mock private MediaAndDrmEventListener mediaAndDrmEventListener; 55 56 private MediaSourceEventDispatcher eventDispatcher; 57 58 @Before setupEventDispatcher()59 public void setupEventDispatcher() { 60 eventDispatcher = new MediaSourceEventDispatcher(); 61 eventDispatcher = 62 eventDispatcher.withParameters(WINDOW_INDEX, MEDIA_PERIOD_ID, MEDIA_TIME_OFFSET_MS); 63 } 64 65 @Test listenerReceivesEventPopulatedWithMediaPeriodInfo()66 public void listenerReceivesEventPopulatedWithMediaPeriodInfo() { 67 eventDispatcher.addEventListener( 68 Util.createHandler(), mediaSourceEventListener, MediaSourceEventListener.class); 69 70 eventDispatcher.dispatch( 71 MediaSourceEventListener::onMediaPeriodCreated, MediaSourceEventListener.class); 72 73 verify(mediaSourceEventListener).onMediaPeriodCreated(WINDOW_INDEX, MEDIA_PERIOD_ID); 74 } 75 76 @Test sameListenerObjectRegisteredTwiceOnlyReceivesEventsOnce()77 public void sameListenerObjectRegisteredTwiceOnlyReceivesEventsOnce() { 78 eventDispatcher.addEventListener( 79 Util.createHandler(), mediaSourceEventListener, MediaSourceEventListener.class); 80 eventDispatcher.addEventListener( 81 Util.createHandler(), mediaSourceEventListener, MediaSourceEventListener.class); 82 83 eventDispatcher.dispatch( 84 MediaSourceEventListener::onMediaPeriodCreated, MediaSourceEventListener.class); 85 86 verify(mediaSourceEventListener).onMediaPeriodCreated(WINDOW_INDEX, MEDIA_PERIOD_ID); 87 } 88 89 @Test sameListenerInstanceCanBeRegisteredWithTwoTypes()90 public void sameListenerInstanceCanBeRegisteredWithTwoTypes() { 91 eventDispatcher.addEventListener( 92 new Handler(Looper.getMainLooper()), 93 mediaAndDrmEventListener, 94 MediaSourceEventListener.class); 95 eventDispatcher.addEventListener( 96 new Handler(Looper.getMainLooper()), 97 mediaAndDrmEventListener, 98 DrmSessionEventListener.class); 99 100 eventDispatcher.dispatch( 101 MediaSourceEventListener::onMediaPeriodCreated, MediaSourceEventListener.class); 102 eventDispatcher.dispatch( 103 (listener, windowIndex, mediaPeriodId) -> listener.onDrmKeysLoaded(), 104 DrmSessionEventListener.class); 105 106 verify(mediaAndDrmEventListener).onMediaPeriodCreated(WINDOW_INDEX, MEDIA_PERIOD_ID); 107 verify(mediaAndDrmEventListener).onDrmKeysLoaded(); 108 } 109 110 // If a listener is added that implements multiple types, it should only receive events for the 111 // type specified at registration time. 112 @Test listenerOnlyReceivesEventsForRegisteredType()113 public void listenerOnlyReceivesEventsForRegisteredType() { 114 eventDispatcher.addEventListener( 115 new Handler(Looper.getMainLooper()), 116 mediaAndDrmEventListener, 117 MediaSourceEventListener.class); 118 119 eventDispatcher.dispatch( 120 MediaSourceEventListener::onMediaPeriodCreated, MediaSourceEventListener.class); 121 eventDispatcher.dispatch( 122 (listener, windowIndex, mediaPeriodId) -> listener.onDrmKeysLoaded(), 123 DrmSessionEventListener.class); 124 125 verify(mediaAndDrmEventListener).onMediaPeriodCreated(WINDOW_INDEX, MEDIA_PERIOD_ID); 126 verify(mediaAndDrmEventListener, never()).onDrmKeysLoaded(); 127 } 128 129 @Test listenerDoesntReceiveEventsDispatchedToSubclass()130 public void listenerDoesntReceiveEventsDispatchedToSubclass() { 131 SubclassListener subclassListener = mock(SubclassListener.class); 132 eventDispatcher.addEventListener( 133 new Handler(Looper.getMainLooper()), subclassListener, MediaSourceEventListener.class); 134 135 eventDispatcher.dispatch(SubclassListener::subclassMethod, SubclassListener.class); 136 137 // subclassListener can handle the call to subclassMethod, but it isn't called because 138 // it was registered 'as-a' MediaSourceEventListener, not SubclassListener. 139 verify(subclassListener, never()).subclassMethod(anyInt(), any()); 140 } 141 142 @Test listenerDoesntReceiveEventsDispatchedToSuperclass()143 public void listenerDoesntReceiveEventsDispatchedToSuperclass() { 144 SubclassListener subclassListener = mock(SubclassListener.class); 145 eventDispatcher.addEventListener( 146 new Handler(Looper.getMainLooper()), subclassListener, SubclassListener.class); 147 148 eventDispatcher.dispatch( 149 MediaSourceEventListener::onMediaPeriodCreated, MediaSourceEventListener.class); 150 151 // subclassListener 'is-a' a MediaSourceEventListener, but it isn't called because the event 152 // is dispatched specifically to listeners registered as MediaSourceEventListener. 153 verify(subclassListener, never()).onMediaPeriodCreated(anyInt(), any()); 154 } 155 156 @Test listenersAreCopiedToNewDispatcher()157 public void listenersAreCopiedToNewDispatcher() { 158 eventDispatcher.addEventListener( 159 Util.createHandler(), mediaSourceEventListener, MediaSourceEventListener.class); 160 161 MediaSource.MediaPeriodId newPeriodId = new MediaSource.MediaPeriodId("different uid"); 162 MediaSourceEventDispatcher newEventDispatcher = 163 this.eventDispatcher.withParameters( 164 /* windowIndex= */ 250, newPeriodId, /* mediaTimeOffsetMs= */ 500); 165 166 newEventDispatcher.dispatch( 167 MediaSourceEventListener::onMediaPeriodCreated, MediaSourceEventListener.class); 168 169 verify(mediaSourceEventListener).onMediaPeriodCreated(250, newPeriodId); 170 } 171 172 @Test removingListenerStopsEventDispatch()173 public void removingListenerStopsEventDispatch() { 174 eventDispatcher.addEventListener( 175 Util.createHandler(), mediaSourceEventListener, MediaSourceEventListener.class); 176 eventDispatcher.removeEventListener(mediaSourceEventListener, MediaSourceEventListener.class); 177 178 eventDispatcher.dispatch( 179 MediaSourceEventListener::onMediaPeriodCreated, MediaSourceEventListener.class); 180 181 verify(mediaSourceEventListener, never()).onMediaPeriodCreated(anyInt(), any()); 182 } 183 184 @Test removingListenerWithDifferentTypeToRegistrationDoesntRemove()185 public void removingListenerWithDifferentTypeToRegistrationDoesntRemove() { 186 eventDispatcher.addEventListener( 187 Util.createHandler(), mediaAndDrmEventListener, MediaSourceEventListener.class); 188 eventDispatcher.removeEventListener(mediaAndDrmEventListener, DrmSessionEventListener.class); 189 190 eventDispatcher.dispatch( 191 MediaSourceEventListener::onMediaPeriodCreated, MediaSourceEventListener.class); 192 193 verify(mediaAndDrmEventListener).onMediaPeriodCreated(WINDOW_INDEX, MEDIA_PERIOD_ID); 194 } 195 196 @Test listenersAreCountedBasedOnListenerAndType()197 public void listenersAreCountedBasedOnListenerAndType() { 198 // Add the listener twice and remove it once. 199 eventDispatcher.addEventListener( 200 Util.createHandler(), mediaSourceEventListener, MediaSourceEventListener.class); 201 eventDispatcher.addEventListener( 202 Util.createHandler(), mediaSourceEventListener, MediaSourceEventListener.class); 203 eventDispatcher.removeEventListener(mediaSourceEventListener, MediaSourceEventListener.class); 204 205 eventDispatcher.dispatch( 206 MediaSourceEventListener::onMediaPeriodCreated, MediaSourceEventListener.class); 207 208 verify(mediaSourceEventListener).onMediaPeriodCreated(WINDOW_INDEX, MEDIA_PERIOD_ID); 209 210 // Remove it a second time and confirm the events stop being propagated. 211 eventDispatcher.removeEventListener(mediaSourceEventListener, MediaSourceEventListener.class); 212 213 verifyNoMoreInteractions(mediaSourceEventListener); 214 } 215 216 private interface MediaAndDrmEventListener 217 extends MediaSourceEventListener, DrmSessionEventListener {} 218 219 private interface SubclassListener extends MediaSourceEventListener { subclassMethod(int windowIndex, @Nullable MediaPeriodId mediaPeriodId)220 void subclassMethod(int windowIndex, @Nullable MediaPeriodId mediaPeriodId); 221 } 222 } 223