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