• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 package com.google.android.exoplayer2.util;
17 
18 import android.os.Handler;
19 import android.os.Looper;
20 import androidx.annotation.CheckResult;
21 import androidx.annotation.Nullable;
22 import com.google.android.exoplayer2.C;
23 import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
24 
25 /**
26  * Event dispatcher which forwards events to a list of registered listeners.
27  *
28  * <p>Adds the correct {@code windowIndex} and {@code mediaPeriodId} values (and {@code
29  * mediaTimeOffsetMs} if needed).
30  *
31  * <p>Allows listeners of any type to be registered, calls to {@link #dispatch} then provide the
32  * type of listener to forward to, which is used to filter the registered listeners.
33  */
34 // TODO: Make this final when MediaSourceEventListener.EventDispatcher is deleted.
35 public class MediaSourceEventDispatcher {
36 
37   /**
38    * Functional interface to send an event with {@code windowIndex} and {@code mediaPeriodId}
39    * attached.
40    */
41   public interface EventWithPeriodId<T> {
42 
43     /** Sends the event to a listener. */
sendTo(T listener, int windowIndex, @Nullable MediaPeriodId mediaPeriodId)44     void sendTo(T listener, int windowIndex, @Nullable MediaPeriodId mediaPeriodId);
45   }
46 
47   /** The timeline window index reported with the events. */
48   public final int windowIndex;
49   /** The {@link MediaPeriodId} reported with the events. */
50   @Nullable public final MediaPeriodId mediaPeriodId;
51 
52   // TODO: Make these private when MediaSourceEventListener.EventDispatcher is deleted.
53   protected final CopyOnWriteMultiset<ListenerInfo> listenerInfos;
54   // TODO: Define exactly what this means, and check it's always set correctly.
55   protected final long mediaTimeOffsetMs;
56 
57   /** Creates an event dispatcher. */
MediaSourceEventDispatcher()58   public MediaSourceEventDispatcher() {
59     this(
60         /* listenerInfos= */ new CopyOnWriteMultiset<>(),
61         /* windowIndex= */ 0,
62         /* mediaPeriodId= */ null,
63         /* mediaTimeOffsetMs= */ 0);
64   }
65 
MediaSourceEventDispatcher( CopyOnWriteMultiset<ListenerInfo> listenerInfos, int windowIndex, @Nullable MediaPeriodId mediaPeriodId, long mediaTimeOffsetMs)66   protected MediaSourceEventDispatcher(
67       CopyOnWriteMultiset<ListenerInfo> listenerInfos,
68       int windowIndex,
69       @Nullable MediaPeriodId mediaPeriodId,
70       long mediaTimeOffsetMs) {
71     this.listenerInfos = listenerInfos;
72     this.windowIndex = windowIndex;
73     this.mediaPeriodId = mediaPeriodId;
74     this.mediaTimeOffsetMs = mediaTimeOffsetMs;
75   }
76 
77   /**
78    * Creates a view of the event dispatcher with pre-configured window index, media period id, and
79    * media time offset.
80    *
81    * @param windowIndex The timeline window index to be reported with the events.
82    * @param mediaPeriodId The {@link MediaPeriodId} to be reported with the events.
83    * @param mediaTimeOffsetMs The offset to be added to all media times, in milliseconds.
84    * @return A view of the event dispatcher with the pre-configured parameters.
85    */
86   @CheckResult
withParameters( int windowIndex, @Nullable MediaPeriodId mediaPeriodId, long mediaTimeOffsetMs)87   public MediaSourceEventDispatcher withParameters(
88       int windowIndex, @Nullable MediaPeriodId mediaPeriodId, long mediaTimeOffsetMs) {
89     return new MediaSourceEventDispatcher(
90         listenerInfos, windowIndex, mediaPeriodId, mediaTimeOffsetMs);
91   }
92 
93   /**
94    * Adds a listener to the event dispatcher.
95    *
96    * <p>Calls to {@link #dispatch(EventWithPeriodId, Class)} will propagate to {@code eventListener}
97    * if the {@code listenerClass} types are equal.
98    *
99    * <p>The same listener instance can be added multiple times with different {@code listenerClass}
100    * values (i.e. if the instance implements multiple listener interfaces).
101    *
102    * <p>Duplicate {@code {eventListener, listenerClass}} pairs are also permitted. In this case an
103    * event dispatched to {@code listenerClass} will only be passed to the {@code eventListener}
104    * once.
105    *
106    * <p><b>NOTE</b>: This doesn't interact well with hierarchies of listener interfaces. If a
107    * listener is registered with a super-class type then it will only receive events dispatched
108    * directly to that super-class type. Similarly, if a listener is registered with a sub-class type
109    * then it will only receive events dispatched directly to that sub-class.
110    *
111    * @param handler A handler on the which listener events will be posted.
112    * @param eventListener The listener to be added.
113    * @param listenerClass The type used to register the listener. Can be a superclass of {@code
114    *     eventListener}.
115    */
addEventListener(Handler handler, T eventListener, Class<T> listenerClass)116   public <T> void addEventListener(Handler handler, T eventListener, Class<T> listenerClass) {
117     Assertions.checkNotNull(handler);
118     Assertions.checkNotNull(eventListener);
119     listenerInfos.add(new ListenerInfo(handler, eventListener, listenerClass));
120   }
121 
122   /**
123    * Removes a listener from the event dispatcher.
124    *
125    * <p>If there are duplicate registrations of {@code {eventListener, listenerClass}} this will
126    * only remove one (so events dispatched to {@code listenerClass} will still be passed to {@code
127    * eventListener}).
128    *
129    * @param eventListener The listener to be removed.
130    * @param listenerClass The listener type passed to {@link #addEventListener(Handler, Object,
131    *     Class)}.
132    */
removeEventListener(T eventListener, Class<T> listenerClass)133   public <T> void removeEventListener(T eventListener, Class<T> listenerClass) {
134     for (ListenerInfo listenerInfo : listenerInfos) {
135       if (listenerInfo.listener == eventListener
136           && listenerInfo.listenerClass.equals(listenerClass)) {
137         listenerInfos.remove(listenerInfo);
138         return;
139       }
140     }
141   }
142 
143   /** Dispatches {@code event} to all registered listeners of type {@code listenerClass}. */
144   @SuppressWarnings("unchecked") // The cast is gated with listenerClass.isInstance()
dispatch(EventWithPeriodId<T> event, Class<T> listenerClass)145   public <T> void dispatch(EventWithPeriodId<T> event, Class<T> listenerClass) {
146     for (ListenerInfo listenerInfo : listenerInfos.elementSet()) {
147       if (listenerInfo.listenerClass.equals(listenerClass)) {
148         postOrRun(
149             listenerInfo.handler,
150             () -> event.sendTo((T) listenerInfo.listener, windowIndex, mediaPeriodId));
151       }
152     }
153   }
154 
postOrRun(Handler handler, Runnable runnable)155   private static void postOrRun(Handler handler, Runnable runnable) {
156     if (handler.getLooper() == Looper.myLooper()) {
157       runnable.run();
158     } else {
159       handler.post(runnable);
160     }
161   }
162 
adjustMediaTime(long mediaTimeUs, long mediaTimeOffsetMs)163   public static long adjustMediaTime(long mediaTimeUs, long mediaTimeOffsetMs) {
164     long mediaTimeMs = C.usToMs(mediaTimeUs);
165     return mediaTimeMs == C.TIME_UNSET ? C.TIME_UNSET : mediaTimeOffsetMs + mediaTimeMs;
166   }
167 
168   /** Container class for a {@link Handler}, {@code listener} and {@code listenerClass}. */
169   protected static final class ListenerInfo {
170 
171     public final Handler handler;
172     public final Object listener;
173     public final Class<?> listenerClass;
174 
ListenerInfo(Handler handler, Object listener, Class<?> listenerClass)175     public ListenerInfo(Handler handler, Object listener, Class<?> listenerClass) {
176       this.handler = handler;
177       this.listener = listener;
178       this.listenerClass = listenerClass;
179     }
180 
181     @Override
equals(@ullable Object o)182     public boolean equals(@Nullable Object o) {
183       if (this == o) {
184         return true;
185       }
186       if (!(o instanceof ListenerInfo)) {
187         return false;
188       }
189 
190       ListenerInfo that = (ListenerInfo) o;
191 
192       // We deliberately only consider listener and listenerClass (and not handler) in equals() and
193       // hashcode() because the handler used to process the callbacks is an implementation detail.
194       return listener.equals(that.listener) && listenerClass.equals(that.listenerClass);
195     }
196 
197     @Override
hashCode()198     public int hashCode() {
199       int result = 31 * listener.hashCode();
200       return result + 31 * listenerClass.hashCode();
201     }
202   }
203 }
204