• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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