1 /* 2 * Copyright (C) 2017 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; 17 18 import android.os.Handler; 19 import androidx.annotation.Nullable; 20 import androidx.annotation.VisibleForTesting; 21 import com.google.android.exoplayer2.util.Assertions; 22 import com.google.android.exoplayer2.util.Clock; 23 import java.util.concurrent.TimeoutException; 24 25 /** 26 * Defines a player message which can be sent with a {@link Sender} and received by a {@link 27 * Target}. 28 */ 29 public final class PlayerMessage { 30 31 /** A target for messages. */ 32 public interface Target { 33 34 /** 35 * Handles a message delivered to the target. 36 * 37 * @param messageType The message type. 38 * @param payload The message payload. 39 * @throws ExoPlaybackException If an error occurred whilst handling the message. Should only be 40 * thrown by targets that handle messages on the playback thread. 41 */ handleMessage(int messageType, @Nullable Object payload)42 void handleMessage(int messageType, @Nullable Object payload) throws ExoPlaybackException; 43 } 44 45 /** A sender for messages. */ 46 public interface Sender { 47 48 /** 49 * Sends a message. 50 * 51 * @param message The message to be sent. 52 */ sendMessage(PlayerMessage message)53 void sendMessage(PlayerMessage message); 54 } 55 56 private final Target target; 57 private final Sender sender; 58 private final Timeline timeline; 59 60 private int type; 61 @Nullable private Object payload; 62 private Handler handler; 63 private int windowIndex; 64 private long positionMs; 65 private boolean deleteAfterDelivery; 66 private boolean isSent; 67 private boolean isDelivered; 68 private boolean isProcessed; 69 private boolean isCanceled; 70 71 /** 72 * Creates a new message. 73 * 74 * @param sender The {@link Sender} used to send the message. 75 * @param target The {@link Target} the message is sent to. 76 * @param timeline The timeline used when setting the position with {@link #setPosition(long)}. If 77 * set to {@link Timeline#EMPTY}, any position can be specified. 78 * @param defaultWindowIndex The default window index in the {@code timeline} when no other window 79 * index is specified. 80 * @param defaultHandler The default handler to send the message on when no other handler is 81 * specified. 82 */ PlayerMessage( Sender sender, Target target, Timeline timeline, int defaultWindowIndex, Handler defaultHandler)83 public PlayerMessage( 84 Sender sender, 85 Target target, 86 Timeline timeline, 87 int defaultWindowIndex, 88 Handler defaultHandler) { 89 this.sender = sender; 90 this.target = target; 91 this.timeline = timeline; 92 this.handler = defaultHandler; 93 this.windowIndex = defaultWindowIndex; 94 this.positionMs = C.TIME_UNSET; 95 this.deleteAfterDelivery = true; 96 } 97 98 /** Returns the timeline used for setting the position with {@link #setPosition(long)}. */ getTimeline()99 public Timeline getTimeline() { 100 return timeline; 101 } 102 103 /** Returns the target the message is sent to. */ getTarget()104 public Target getTarget() { 105 return target; 106 } 107 108 /** 109 * Sets the message type forwarded to {@link Target#handleMessage(int, Object)}. 110 * 111 * @param messageType The message type. 112 * @return This message. 113 * @throws IllegalStateException If {@link #send()} has already been called. 114 */ setType(int messageType)115 public PlayerMessage setType(int messageType) { 116 Assertions.checkState(!isSent); 117 this.type = messageType; 118 return this; 119 } 120 121 /** Returns the message type forwarded to {@link Target#handleMessage(int, Object)}. */ getType()122 public int getType() { 123 return type; 124 } 125 126 /** 127 * Sets the message payload forwarded to {@link Target#handleMessage(int, Object)}. 128 * 129 * @param payload The message payload. 130 * @return This message. 131 * @throws IllegalStateException If {@link #send()} has already been called. 132 */ setPayload(@ullable Object payload)133 public PlayerMessage setPayload(@Nullable Object payload) { 134 Assertions.checkState(!isSent); 135 this.payload = payload; 136 return this; 137 } 138 139 /** Returns the message payload forwarded to {@link Target#handleMessage(int, Object)}. */ 140 @Nullable getPayload()141 public Object getPayload() { 142 return payload; 143 } 144 145 /** 146 * Sets the handler the message is delivered on. 147 * 148 * @param handler A {@link Handler}. 149 * @return This message. 150 * @throws IllegalStateException If {@link #send()} has already been called. 151 */ setHandler(Handler handler)152 public PlayerMessage setHandler(Handler handler) { 153 Assertions.checkState(!isSent); 154 this.handler = handler; 155 return this; 156 } 157 158 /** Returns the handler the message is delivered on. */ getHandler()159 public Handler getHandler() { 160 return handler; 161 } 162 163 /** 164 * Returns position in window at {@link #getWindowIndex()} at which the message will be delivered, 165 * in milliseconds. If {@link C#TIME_UNSET}, the message will be delivered immediately. If {@link 166 * C#TIME_END_OF_SOURCE}, the message will be delivered at the end of the window at {@link 167 * #getWindowIndex()}. 168 */ getPositionMs()169 public long getPositionMs() { 170 return positionMs; 171 } 172 173 /** 174 * Sets a position in the current window at which the message will be delivered. 175 * 176 * @param positionMs The position in the current window at which the message will be sent, in 177 * milliseconds, or {@link C#TIME_END_OF_SOURCE} to deliver the message at the end of the 178 * current window. 179 * @return This message. 180 * @throws IllegalStateException If {@link #send()} has already been called. 181 */ setPosition(long positionMs)182 public PlayerMessage setPosition(long positionMs) { 183 Assertions.checkState(!isSent); 184 this.positionMs = positionMs; 185 return this; 186 } 187 188 /** 189 * Sets a position in a window at which the message will be delivered. 190 * 191 * @param windowIndex The index of the window at which the message will be sent. 192 * @param positionMs The position in the window with index {@code windowIndex} at which the 193 * message will be sent, in milliseconds, or {@link C#TIME_END_OF_SOURCE} to deliver the 194 * message at the end of the window with index {@code windowIndex}. 195 * @return This message. 196 * @throws IllegalSeekPositionException If the timeline returned by {@link #getTimeline()} is not 197 * empty and the provided window index is not within the bounds of the timeline. 198 * @throws IllegalStateException If {@link #send()} has already been called. 199 */ setPosition(int windowIndex, long positionMs)200 public PlayerMessage setPosition(int windowIndex, long positionMs) { 201 Assertions.checkState(!isSent); 202 Assertions.checkArgument(positionMs != C.TIME_UNSET); 203 if (windowIndex < 0 || (!timeline.isEmpty() && windowIndex >= timeline.getWindowCount())) { 204 throw new IllegalSeekPositionException(timeline, windowIndex, positionMs); 205 } 206 this.windowIndex = windowIndex; 207 this.positionMs = positionMs; 208 return this; 209 } 210 211 /** Returns window index at which the message will be delivered. */ getWindowIndex()212 public int getWindowIndex() { 213 return windowIndex; 214 } 215 216 /** 217 * Sets whether the message will be deleted after delivery. If false, the message will be resent 218 * if playback reaches the specified position again. Only allowed to be false if a position is set 219 * with {@link #setPosition(long)}. 220 * 221 * @param deleteAfterDelivery Whether the message is deleted after delivery. 222 * @return This message. 223 * @throws IllegalStateException If {@link #send()} has already been called. 224 */ setDeleteAfterDelivery(boolean deleteAfterDelivery)225 public PlayerMessage setDeleteAfterDelivery(boolean deleteAfterDelivery) { 226 Assertions.checkState(!isSent); 227 this.deleteAfterDelivery = deleteAfterDelivery; 228 return this; 229 } 230 231 /** Returns whether the message will be deleted after delivery. */ getDeleteAfterDelivery()232 public boolean getDeleteAfterDelivery() { 233 return deleteAfterDelivery; 234 } 235 236 /** 237 * Sends the message. If the target throws an {@link ExoPlaybackException} then it is propagated 238 * out of the player as an error using {@link 239 * Player.EventListener#onPlayerError(ExoPlaybackException)}. 240 * 241 * @return This message. 242 * @throws IllegalStateException If this message has already been sent. 243 */ send()244 public PlayerMessage send() { 245 Assertions.checkState(!isSent); 246 if (positionMs == C.TIME_UNSET) { 247 Assertions.checkArgument(deleteAfterDelivery); 248 } 249 isSent = true; 250 sender.sendMessage(this); 251 return this; 252 } 253 254 /** 255 * Cancels the message delivery. 256 * 257 * @return This message. 258 * @throws IllegalStateException If this method is called before {@link #send()}. 259 */ cancel()260 public synchronized PlayerMessage cancel() { 261 Assertions.checkState(isSent); 262 isCanceled = true; 263 markAsProcessed(/* isDelivered= */ false); 264 return this; 265 } 266 267 /** Returns whether the message delivery has been canceled. */ isCanceled()268 public synchronized boolean isCanceled() { 269 return isCanceled; 270 } 271 272 /** 273 * Blocks until after the message has been delivered or the player is no longer able to deliver 274 * the message. 275 * 276 * <p>Note that this method can't be called if the current thread is the same thread used by the 277 * message handler set with {@link #setHandler(Handler)} as it would cause a deadlock. 278 * 279 * @return Whether the message was delivered successfully. 280 * @throws IllegalStateException If this method is called before {@link #send()}. 281 * @throws IllegalStateException If this method is called on the same thread used by the message 282 * handler set with {@link #setHandler(Handler)}. 283 * @throws InterruptedException If the current thread is interrupted while waiting for the message 284 * to be delivered. 285 */ blockUntilDelivered()286 public synchronized boolean blockUntilDelivered() throws InterruptedException { 287 Assertions.checkState(isSent); 288 Assertions.checkState(handler.getLooper().getThread() != Thread.currentThread()); 289 while (!isProcessed) { 290 wait(); 291 } 292 return isDelivered; 293 } 294 295 /** 296 * Blocks until after the message has been delivered or the player is no longer able to deliver 297 * the message or the specified waiting time elapses. 298 * 299 * <p>Note that this method can't be called if the current thread is the same thread used by the 300 * message handler set with {@link #setHandler(Handler)} as it would cause a deadlock. 301 * 302 * @param timeoutMs the maximum time to wait in milliseconds. 303 * @return Whether the message was delivered successfully. 304 * @throws IllegalStateException If this method is called before {@link #send()}. 305 * @throws IllegalStateException If this method is called on the same thread used by the message 306 * handler set with {@link #setHandler(Handler)}. 307 * @throws TimeoutException If the waiting time elapsed and this message has not been delivered 308 * and the player is still able to deliver the message. 309 * @throws InterruptedException If the current thread is interrupted while waiting for the message 310 * to be delivered. 311 */ experimental_blockUntilDelivered(long timeoutMs)312 public synchronized boolean experimental_blockUntilDelivered(long timeoutMs) 313 throws InterruptedException, TimeoutException { 314 return experimental_blockUntilDelivered(timeoutMs, Clock.DEFAULT); 315 } 316 317 /** 318 * Marks the message as processed. Should only be called by a {@link Sender} and may be called 319 * multiple times. 320 * 321 * @param isDelivered Whether the message has been delivered to its target. The message is 322 * considered as being delivered when this method has been called with {@code isDelivered} set 323 * to true at least once. 324 */ markAsProcessed(boolean isDelivered)325 public synchronized void markAsProcessed(boolean isDelivered) { 326 this.isDelivered |= isDelivered; 327 isProcessed = true; 328 notifyAll(); 329 } 330 331 @VisibleForTesting() experimental_blockUntilDelivered(long timeoutMs, Clock clock)332 /* package */ synchronized boolean experimental_blockUntilDelivered(long timeoutMs, Clock clock) 333 throws InterruptedException, TimeoutException { 334 Assertions.checkState(isSent); 335 Assertions.checkState(handler.getLooper().getThread() != Thread.currentThread()); 336 337 long deadlineMs = clock.elapsedRealtime() + timeoutMs; 338 long remainingMs = timeoutMs; 339 while (!isProcessed && remainingMs > 0) { 340 wait(remainingMs); 341 remainingMs = deadlineMs - clock.elapsedRealtime(); 342 } 343 344 if (!isProcessed) { 345 throw new TimeoutException("Message delivery timed out."); 346 } 347 348 return isDelivered; 349 } 350 } 351