• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 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.testutil;
17 
18 import android.os.Handler;
19 import android.view.Surface;
20 import androidx.annotation.Nullable;
21 import com.google.android.exoplayer2.C;
22 import com.google.android.exoplayer2.ExoPlaybackException;
23 import com.google.android.exoplayer2.ExoPlayer;
24 import com.google.android.exoplayer2.IllegalSeekPositionException;
25 import com.google.android.exoplayer2.Player;
26 import com.google.android.exoplayer2.PlayerMessage;
27 import com.google.android.exoplayer2.PlayerMessage.Target;
28 import com.google.android.exoplayer2.SimpleExoPlayer;
29 import com.google.android.exoplayer2.Timeline;
30 import com.google.android.exoplayer2.audio.AudioAttributes;
31 import com.google.android.exoplayer2.source.MediaSource;
32 import com.google.android.exoplayer2.source.ShuffleOrder;
33 import com.google.android.exoplayer2.testutil.ActionSchedule.ActionNode;
34 import com.google.android.exoplayer2.testutil.ActionSchedule.PlayerRunnable;
35 import com.google.android.exoplayer2.testutil.ActionSchedule.PlayerTarget;
36 import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
37 import com.google.android.exoplayer2.trackselection.DefaultTrackSelector.Parameters;
38 import com.google.android.exoplayer2.util.Assertions;
39 import com.google.android.exoplayer2.util.ConditionVariable;
40 import com.google.android.exoplayer2.util.HandlerWrapper;
41 import com.google.android.exoplayer2.util.Log;
42 import com.google.android.exoplayer2.util.Util;
43 import java.util.Arrays;
44 import java.util.List;
45 
46 /** Base class for actions to perform during playback tests. */
47 public abstract class Action {
48 
49   private final String tag;
50   @Nullable private final String description;
51 
52   /**
53    * @param tag A tag to use for logging.
54    * @param description A description to be logged when the action is executed, or null if no
55    *     logging is required.
56    */
Action(String tag, @Nullable String description)57   public Action(String tag, @Nullable String description) {
58     this.tag = tag;
59     this.description = description;
60   }
61 
62   /**
63    * Executes the action and schedules the next.
64    *
65    * @param player The player to which the action should be applied.
66    * @param trackSelector The track selector to which the action should be applied.
67    * @param surface The surface to use when applying actions, or {@code null} if no surface is
68    *     needed.
69    * @param handler The handler to use to pass to the next action.
70    * @param nextAction The next action to schedule immediately after this action finished, or {@code
71    *     null} if there's no next action.
72    */
doActionAndScheduleNext( SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface, HandlerWrapper handler, @Nullable ActionNode nextAction)73   public final void doActionAndScheduleNext(
74       SimpleExoPlayer player,
75       DefaultTrackSelector trackSelector,
76       @Nullable Surface surface,
77       HandlerWrapper handler,
78       @Nullable ActionNode nextAction) {
79     if (description != null) {
80       Log.i(tag, description);
81     }
82     doActionAndScheduleNextImpl(player, trackSelector, surface, handler, nextAction);
83   }
84 
85   /**
86    * Called by {@link #doActionAndScheduleNext(SimpleExoPlayer, DefaultTrackSelector, Surface,
87    * HandlerWrapper, ActionNode)} to perform the action and to schedule the next action node.
88    *
89    * @param player The player to which the action should be applied.
90    * @param trackSelector The track selector to which the action should be applied.
91    * @param surface The surface to use when applying actions, or {@code null} if no surface is
92    *     needed.
93    * @param handler The handler to use to pass to the next action.
94    * @param nextAction The next action to schedule immediately after this action finished, or {@code
95    *     null} if there's no next action.
96    */
doActionAndScheduleNextImpl( SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface, HandlerWrapper handler, @Nullable ActionNode nextAction)97   protected void doActionAndScheduleNextImpl(
98       SimpleExoPlayer player,
99       DefaultTrackSelector trackSelector,
100       @Nullable Surface surface,
101       HandlerWrapper handler,
102       @Nullable ActionNode nextAction) {
103     doActionImpl(player, trackSelector, surface);
104     if (nextAction != null) {
105       nextAction.schedule(player, trackSelector, surface, handler);
106     }
107   }
108 
109   /**
110    * Called by {@link #doActionAndScheduleNextImpl(SimpleExoPlayer, DefaultTrackSelector, Surface,
111    * HandlerWrapper, ActionNode)} to perform the action.
112    *
113    * @param player The player to which the action should be applied.
114    * @param trackSelector The track selector to which the action should be applied.
115    * @param surface The surface to use when applying actions, or {@code null} if no surface is
116    *     needed.
117    */
doActionImpl( SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface)118   protected abstract void doActionImpl(
119       SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface);
120 
121   /** Calls {@link Player#seekTo(long)} or {@link Player#seekTo(int, long)}. */
122   public static final class Seek extends Action {
123 
124     @Nullable private final Integer windowIndex;
125     private final long positionMs;
126     private final boolean catchIllegalSeekException;
127 
128     /**
129      * Action calls {@link Player#seekTo(long)}.
130      *
131      * @param tag A tag to use for logging.
132      * @param positionMs The seek position.
133      */
Seek(String tag, long positionMs)134     public Seek(String tag, long positionMs) {
135       super(tag, "Seek:" + positionMs);
136       this.windowIndex = null;
137       this.positionMs = positionMs;
138       catchIllegalSeekException = false;
139     }
140 
141     /**
142      * Action calls {@link Player#seekTo(int, long)}.
143      *
144      * @param tag A tag to use for logging.
145      * @param windowIndex The window to seek to.
146      * @param positionMs The seek position.
147      * @param catchIllegalSeekException Whether {@link IllegalSeekPositionException} should be
148      *     silently caught or not.
149      */
Seek(String tag, int windowIndex, long positionMs, boolean catchIllegalSeekException)150     public Seek(String tag, int windowIndex, long positionMs, boolean catchIllegalSeekException) {
151       super(tag, "Seek:" + positionMs);
152       this.windowIndex = windowIndex;
153       this.positionMs = positionMs;
154       this.catchIllegalSeekException = catchIllegalSeekException;
155     }
156 
157     @Override
doActionImpl( SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface)158     protected void doActionImpl(
159         SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface) {
160       try {
161         if (windowIndex == null) {
162           player.seekTo(positionMs);
163         } else {
164           player.seekTo(windowIndex, positionMs);
165         }
166       } catch (IllegalSeekPositionException e) {
167         if (!catchIllegalSeekException) {
168           throw e;
169         }
170       }
171     }
172   }
173 
174   /** Calls {@link SimpleExoPlayer#setMediaSources(List, int, long)}. */
175   public static final class SetMediaItems extends Action {
176 
177     private final int windowIndex;
178     private final long positionMs;
179     private final MediaSource[] mediaSources;
180 
181     /**
182      * @param tag A tag to use for logging.
183      * @param windowIndex The window index to start playback from.
184      * @param positionMs The position in milliseconds to start playback from.
185      * @param mediaSources The media sources to populate the playlist with.
186      */
SetMediaItems( String tag, int windowIndex, long positionMs, MediaSource... mediaSources)187     public SetMediaItems(
188         String tag, int windowIndex, long positionMs, MediaSource... mediaSources) {
189       super(tag, "SetMediaItems");
190       this.windowIndex = windowIndex;
191       this.positionMs = positionMs;
192       this.mediaSources = mediaSources;
193     }
194 
195     @Override
doActionImpl( SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface)196     protected void doActionImpl(
197         SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface) {
198       player.setMediaSources(Arrays.asList(mediaSources), windowIndex, positionMs);
199     }
200   }
201 
202   /** Calls {@link SimpleExoPlayer#addMediaSources(List)}. */
203   public static final class AddMediaItems extends Action {
204 
205     private final MediaSource[] mediaSources;
206 
207     /**
208      * @param tag A tag to use for logging.
209      * @param mediaSources The media sources to be added to the playlist.
210      */
AddMediaItems(String tag, MediaSource... mediaSources)211     public AddMediaItems(String tag, MediaSource... mediaSources) {
212       super(tag, /* description= */ "AddMediaItems");
213       this.mediaSources = mediaSources;
214     }
215 
216     @Override
doActionImpl( SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface)217     protected void doActionImpl(
218         SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface) {
219       player.addMediaSources(Arrays.asList(mediaSources));
220     }
221   }
222 
223   /** Calls {@link SimpleExoPlayer#setMediaSources(List, boolean)}. */
224   public static final class SetMediaItemsResetPosition extends Action {
225 
226     private final boolean resetPosition;
227     private final MediaSource[] mediaSources;
228 
229     /**
230      * @param tag A tag to use for logging.
231      * @param resetPosition Whether the position should be reset.
232      * @param mediaSources The media sources to populate the playlist with.
233      */
SetMediaItemsResetPosition( String tag, boolean resetPosition, MediaSource... mediaSources)234     public SetMediaItemsResetPosition(
235         String tag, boolean resetPosition, MediaSource... mediaSources) {
236       super(tag, "SetMediaItems");
237       this.resetPosition = resetPosition;
238       this.mediaSources = mediaSources;
239     }
240 
241     @Override
doActionImpl( SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface)242     protected void doActionImpl(
243         SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface) {
244       player.setMediaSources(Arrays.asList(mediaSources), resetPosition);
245     }
246   }
247 
248   /** Calls {@link SimpleExoPlayer#moveMediaItem(int, int)}. */
249   public static class MoveMediaItem extends Action {
250 
251     private final int currentIndex;
252     private final int newIndex;
253 
254     /**
255      * @param tag A tag to use for logging.
256      * @param currentIndex The current index of the media item.
257      * @param newIndex The new index of the media item.
258      */
MoveMediaItem(String tag, int currentIndex, int newIndex)259     public MoveMediaItem(String tag, int currentIndex, int newIndex) {
260       super(tag, "MoveMediaItem");
261       this.currentIndex = currentIndex;
262       this.newIndex = newIndex;
263     }
264 
265     @Override
doActionImpl( SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface)266     protected void doActionImpl(
267         SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface) {
268       player.moveMediaItem(currentIndex, newIndex);
269     }
270   }
271 
272   /** Calls {@link SimpleExoPlayer#removeMediaItem(int)}. */
273   public static class RemoveMediaItem extends Action {
274 
275     private final int index;
276 
277     /**
278      * @param tag A tag to use for logging.
279      * @param index The index of the item to remove.
280      */
RemoveMediaItem(String tag, int index)281     public RemoveMediaItem(String tag, int index) {
282       super(tag, "RemoveMediaItem");
283       this.index = index;
284     }
285 
286     @Override
doActionImpl( SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface)287     protected void doActionImpl(
288         SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface) {
289       player.removeMediaItem(index);
290     }
291   }
292 
293   /** Calls {@link SimpleExoPlayer#removeMediaItems(int, int)}. */
294   public static class RemoveMediaItems extends Action {
295 
296     private final int fromIndex;
297     private final int toIndex;
298 
299     /**
300      * @param tag A tag to use for logging.
301      * @param fromIndex The start if the range of media items to remove.
302      * @param toIndex The end of the range of media items to remove (exclusive).
303      */
RemoveMediaItems(String tag, int fromIndex, int toIndex)304     public RemoveMediaItems(String tag, int fromIndex, int toIndex) {
305       super(tag, "RemoveMediaItem");
306       this.fromIndex = fromIndex;
307       this.toIndex = toIndex;
308     }
309 
310     @Override
doActionImpl( SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface)311     protected void doActionImpl(
312         SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface) {
313       player.removeMediaItems(fromIndex, toIndex);
314     }
315   }
316 
317   /** Calls {@link SimpleExoPlayer#clearMediaItems()}}. */
318   public static class ClearMediaItems extends Action {
319 
320     /** @param tag A tag to use for logging. */
ClearMediaItems(String tag)321     public ClearMediaItems(String tag) {
322       super(tag, "ClearMediaItems");
323     }
324 
325     @Override
doActionImpl( SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface)326     protected void doActionImpl(
327         SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface) {
328       player.clearMediaItems();
329     }
330   }
331 
332   /** Calls {@link Player#stop()} or {@link Player#stop(boolean)}. */
333   public static final class Stop extends Action {
334 
335     private static final String STOP_ACTION_TAG = "Stop";
336 
337     @Nullable private final Boolean reset;
338 
339     /**
340      * Action will call {@link Player#stop()}.
341      *
342      * @param tag A tag to use for logging.
343      */
Stop(String tag)344     public Stop(String tag) {
345       super(tag, STOP_ACTION_TAG);
346       this.reset = null;
347     }
348 
349     /**
350      * Action will call {@link Player#stop(boolean)}.
351      *
352      * @param tag A tag to use for logging.
353      * @param reset The value to pass to {@link Player#stop(boolean)}.
354      */
Stop(String tag, boolean reset)355     public Stop(String tag, boolean reset) {
356       super(tag, STOP_ACTION_TAG);
357       this.reset = reset;
358     }
359 
360     @Override
doActionImpl( SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface)361     protected void doActionImpl(
362         SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface) {
363       if (reset == null) {
364         player.stop();
365       } else {
366         player.stop(reset);
367       }
368     }
369   }
370 
371   /** Calls {@link Player#setPlayWhenReady(boolean)}. */
372   public static final class SetPlayWhenReady extends Action {
373 
374     private final boolean playWhenReady;
375 
376     /**
377      * @param tag A tag to use for logging.
378      * @param playWhenReady The value to pass.
379      */
SetPlayWhenReady(String tag, boolean playWhenReady)380     public SetPlayWhenReady(String tag, boolean playWhenReady) {
381       super(tag, playWhenReady ? "Play" : "Pause");
382       this.playWhenReady = playWhenReady;
383     }
384 
385     @Override
doActionImpl( SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface)386     protected void doActionImpl(
387         SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface) {
388       player.setPlayWhenReady(playWhenReady);
389     }
390   }
391 
392   /**
393    * Updates the {@link Parameters} of a {@link DefaultTrackSelector} to specify whether the
394    * renderer at a given index should be disabled.
395    */
396   public static final class SetRendererDisabled extends Action {
397 
398     private final int rendererIndex;
399     private final boolean disabled;
400 
401     /**
402      * @param tag A tag to use for logging.
403      * @param rendererIndex The index of the renderer.
404      * @param disabled Whether the renderer should be disabled.
405      */
SetRendererDisabled(String tag, int rendererIndex, boolean disabled)406     public SetRendererDisabled(String tag, int rendererIndex, boolean disabled) {
407       super(tag, "SetRendererDisabled:" + rendererIndex + ":" + disabled);
408       this.rendererIndex = rendererIndex;
409       this.disabled = disabled;
410     }
411 
412     @Override
doActionImpl( SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface)413     protected void doActionImpl(
414         SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface) {
415       trackSelector.setParameters(
416           trackSelector.buildUponParameters().setRendererDisabled(rendererIndex, disabled));
417     }
418   }
419 
420   /** Calls {@link SimpleExoPlayer#clearVideoSurface()}. */
421   public static final class ClearVideoSurface extends Action {
422 
423     /** @param tag A tag to use for logging. */
ClearVideoSurface(String tag)424     public ClearVideoSurface(String tag) {
425       super(tag, "ClearVideoSurface");
426     }
427 
428     @Override
doActionImpl( SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface)429     protected void doActionImpl(
430         SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface) {
431       player.clearVideoSurface();
432     }
433   }
434 
435   /** Calls {@link SimpleExoPlayer#setVideoSurface(Surface)}. */
436   public static final class SetVideoSurface extends Action {
437 
438     /** @param tag A tag to use for logging. */
SetVideoSurface(String tag)439     public SetVideoSurface(String tag) {
440       super(tag, "SetVideoSurface");
441     }
442 
443     @Override
doActionImpl( SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface)444     protected void doActionImpl(
445         SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface) {
446       player.setVideoSurface(Assertions.checkNotNull(surface));
447     }
448   }
449 
450   /** Calls {@link SimpleExoPlayer#setAudioAttributes(AudioAttributes, boolean)}. */
451   public static final class SetAudioAttributes extends Action {
452 
453     private final AudioAttributes audioAttributes;
454     private final boolean handleAudioFocus;
455 
456     /**
457      * @param tag A tag to use for logging.
458      * @param audioAttributes The attributes to use for audio playback.
459      * @param handleAudioFocus True if the player should handle audio focus, false otherwise.
460      */
SetAudioAttributes( String tag, AudioAttributes audioAttributes, boolean handleAudioFocus)461     public SetAudioAttributes(
462         String tag, AudioAttributes audioAttributes, boolean handleAudioFocus) {
463       super(tag, "SetAudioAttributes");
464       this.audioAttributes = audioAttributes;
465       this.handleAudioFocus = handleAudioFocus;
466     }
467 
468     @Override
doActionImpl( SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface)469     protected void doActionImpl(
470         SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface) {
471       player.setAudioAttributes(audioAttributes, handleAudioFocus);
472     }
473   }
474 
475   /** Calls {@link ExoPlayer#prepare()}. */
476   public static final class Prepare extends Action {
477     /** @param tag A tag to use for logging. */
Prepare(String tag)478     public Prepare(String tag) {
479       super(tag, "Prepare");
480     }
481 
482     @Override
doActionImpl( SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface)483     protected void doActionImpl(
484         SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface) {
485       player.prepare();
486     }
487   }
488 
489   /** Calls {@link Player#setRepeatMode(int)}. */
490   public static final class SetRepeatMode extends Action {
491 
492     @Player.RepeatMode private final int repeatMode;
493 
494     /**
495      * @param tag A tag to use for logging.
496      * @param repeatMode The repeat mode.
497      */
SetRepeatMode(String tag, @Player.RepeatMode int repeatMode)498     public SetRepeatMode(String tag, @Player.RepeatMode int repeatMode) {
499       super(tag, "SetRepeatMode:" + repeatMode);
500       this.repeatMode = repeatMode;
501     }
502 
503     @Override
doActionImpl( SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface)504     protected void doActionImpl(
505         SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface) {
506       player.setRepeatMode(repeatMode);
507     }
508   }
509 
510   /** Calls {@link ExoPlayer#setShuffleOrder(ShuffleOrder)} . */
511   public static final class SetShuffleOrder extends Action {
512 
513     private final ShuffleOrder shuffleOrder;
514 
515     /**
516      * @param tag A tag to use for logging.
517      * @param shuffleOrder The shuffle order.
518      */
SetShuffleOrder(String tag, ShuffleOrder shuffleOrder)519     public SetShuffleOrder(String tag, ShuffleOrder shuffleOrder) {
520       super(tag, "SetShufflerOrder");
521       this.shuffleOrder = shuffleOrder;
522     }
523 
524     @Override
doActionImpl( SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface)525     protected void doActionImpl(
526         SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface) {
527       player.setShuffleOrder(shuffleOrder);
528     }
529   }
530 
531   /** Calls {@link Player#setShuffleModeEnabled(boolean)}. */
532   public static final class SetShuffleModeEnabled extends Action {
533 
534     private final boolean shuffleModeEnabled;
535 
536     /**
537      * @param tag A tag to use for logging.
538      * @param shuffleModeEnabled Whether shuffling is enabled.
539      */
SetShuffleModeEnabled(String tag, boolean shuffleModeEnabled)540     public SetShuffleModeEnabled(String tag, boolean shuffleModeEnabled) {
541       super(tag, "SetShuffleModeEnabled:" + shuffleModeEnabled);
542       this.shuffleModeEnabled = shuffleModeEnabled;
543     }
544 
545     @Override
doActionImpl( SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface)546     protected void doActionImpl(
547         SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface) {
548       player.setShuffleModeEnabled(shuffleModeEnabled);
549     }
550   }
551 
552   /** Calls {@link ExoPlayer#createMessage(Target)} and {@link PlayerMessage#send()}. */
553   public static final class SendMessages extends Action {
554 
555     private final Target target;
556     private final int windowIndex;
557     private final long positionMs;
558     private final boolean deleteAfterDelivery;
559 
560     /**
561      * @param tag A tag to use for logging.
562      * @param target A message target.
563      * @param positionMs The position at which the message should be sent, in milliseconds.
564      */
SendMessages(String tag, Target target, long positionMs)565     public SendMessages(String tag, Target target, long positionMs) {
566       this(
567           tag,
568           target,
569           /* windowIndex= */ C.INDEX_UNSET,
570           positionMs,
571           /* deleteAfterDelivery= */ true);
572     }
573 
574     /**
575      * @param tag A tag to use for logging.
576      * @param target A message target.
577      * @param windowIndex The window index at which the message should be sent, or {@link
578      *     C#INDEX_UNSET} for the current window.
579      * @param positionMs The position at which the message should be sent, in milliseconds.
580      * @param deleteAfterDelivery Whether the message will be deleted after delivery.
581      */
SendMessages( String tag, Target target, int windowIndex, long positionMs, boolean deleteAfterDelivery)582     public SendMessages(
583         String tag, Target target, int windowIndex, long positionMs, boolean deleteAfterDelivery) {
584       super(tag, "SendMessages");
585       this.target = target;
586       this.windowIndex = windowIndex;
587       this.positionMs = positionMs;
588       this.deleteAfterDelivery = deleteAfterDelivery;
589     }
590 
591     @Override
doActionImpl( final SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface)592     protected void doActionImpl(
593         final SimpleExoPlayer player,
594         DefaultTrackSelector trackSelector,
595         @Nullable Surface surface) {
596       if (target instanceof PlayerTarget) {
597         ((PlayerTarget) target).setPlayer(player);
598       }
599       PlayerMessage message = player.createMessage(target);
600       if (windowIndex != C.INDEX_UNSET) {
601         message.setPosition(windowIndex, positionMs);
602       } else {
603         message.setPosition(positionMs);
604       }
605       message.setHandler(Util.createHandler());
606       message.setDeleteAfterDelivery(deleteAfterDelivery);
607       message.send();
608     }
609   }
610 
611   /** Calls {@link Player#setPlaybackSpeed(float)}. */
612   public static final class SetPlaybackSpeed extends Action {
613 
614     private final float playbackSpeed;
615 
616     /**
617      * Creates a set playback speed action instance.
618      *
619      * @param tag A tag to use for logging.
620      * @param playbackSpeed The playback speed.
621      */
SetPlaybackSpeed(String tag, float playbackSpeed)622     public SetPlaybackSpeed(String tag, float playbackSpeed) {
623       super(tag, "SetPlaybackSpeed:" + playbackSpeed);
624       this.playbackSpeed = playbackSpeed;
625     }
626 
627     @Override
doActionImpl( SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface)628     protected void doActionImpl(
629         SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface) {
630       player.setPlaybackSpeed(playbackSpeed);
631     }
632   }
633 
634   /** Throws a playback exception on the playback thread. */
635   public static final class ThrowPlaybackException extends Action {
636 
637     private final ExoPlaybackException exception;
638 
639     /**
640      * @param tag A tag to use for logging.
641      * @param exception The exception to throw.
642      */
ThrowPlaybackException(String tag, ExoPlaybackException exception)643     public ThrowPlaybackException(String tag, ExoPlaybackException exception) {
644       super(tag, "ThrowPlaybackException:" + exception);
645       this.exception = exception;
646     }
647 
648     @Override
doActionImpl( SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface)649     protected void doActionImpl(
650         SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface) {
651       player
652           .createMessage(
653               (messageType, payload) -> {
654                 throw exception;
655               })
656           .send();
657     }
658   }
659 
660   /**
661    * Schedules a play action to be executed, waits until the player reaches the specified position,
662    * and pauses the player again.
663    */
664   public static final class PlayUntilPosition extends Action {
665 
666     private final int windowIndex;
667     private final long positionMs;
668 
669     /**
670      * @param tag A tag to use for logging.
671      * @param windowIndex The window index at which the player should be paused again.
672      * @param positionMs The position in that window at which the player should be paused again.
673      */
PlayUntilPosition(String tag, int windowIndex, long positionMs)674     public PlayUntilPosition(String tag, int windowIndex, long positionMs) {
675       super(tag, "PlayUntilPosition:" + windowIndex + ":" + positionMs);
676       this.windowIndex = windowIndex;
677       this.positionMs = positionMs;
678     }
679 
680     @Override
doActionAndScheduleNextImpl( SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface, HandlerWrapper handler, @Nullable ActionNode nextAction)681     protected void doActionAndScheduleNextImpl(
682         SimpleExoPlayer player,
683         DefaultTrackSelector trackSelector,
684         @Nullable Surface surface,
685         HandlerWrapper handler,
686         @Nullable ActionNode nextAction) {
687       Handler testThreadHandler = Util.createHandler();
688       // Schedule a message on the playback thread to ensure the player is paused immediately.
689       player
690           .createMessage(
691               (messageType, payload) -> {
692                 // Block playback thread until pause command has been sent from test thread.
693                 ConditionVariable blockPlaybackThreadCondition = new ConditionVariable();
694                 testThreadHandler.post(
695                     () -> {
696                       player.pause();
697                       blockPlaybackThreadCondition.open();
698                     });
699                 try {
700                   blockPlaybackThreadCondition.block();
701                 } catch (InterruptedException e) {
702                   // Ignore.
703                 }
704               })
705           .setPosition(windowIndex, positionMs)
706           .send();
707       if (nextAction != null) {
708         // Schedule another message on this test thread to continue action schedule.
709         player
710             .createMessage(
711                 (messageType, payload) ->
712                     nextAction.schedule(player, trackSelector, surface, handler))
713             .setPosition(windowIndex, positionMs)
714             .setHandler(testThreadHandler)
715             .send();
716       }
717       player.play();
718     }
719 
720     @Override
doActionImpl( SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface)721     protected void doActionImpl(
722         SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface) {
723       // Not triggered.
724     }
725   }
726 
727   /** Waits for {@link Player.EventListener#onTimelineChanged(Timeline, int)}. */
728   public static final class WaitForTimelineChanged extends Action {
729 
730     @Nullable private final Timeline expectedTimeline;
731     private final boolean ignoreExpectedReason;
732     @Player.TimelineChangeReason private final int expectedReason;
733 
734     /**
735      * Creates action waiting for a timeline change for a given reason.
736      *
737      * @param tag A tag to use for logging.
738      * @param expectedTimeline The expected timeline or {@code null} if any timeline change is
739      *     relevant.
740      * @param expectedReason The expected timeline change reason.
741      */
WaitForTimelineChanged( String tag, @Nullable Timeline expectedTimeline, @Player.TimelineChangeReason int expectedReason)742     public WaitForTimelineChanged(
743         String tag,
744         @Nullable Timeline expectedTimeline,
745         @Player.TimelineChangeReason int expectedReason) {
746       super(tag, "WaitForTimelineChanged");
747       this.expectedTimeline = expectedTimeline != null ? new NoUidTimeline(expectedTimeline) : null;
748       this.ignoreExpectedReason = false;
749       this.expectedReason = expectedReason;
750     }
751 
752     /**
753      * Creates action waiting for any timeline change for any reason.
754      *
755      * @param tag A tag to use for logging.
756      */
WaitForTimelineChanged(String tag)757     public WaitForTimelineChanged(String tag) {
758       super(tag, "WaitForTimelineChanged");
759       this.expectedTimeline = null;
760       this.ignoreExpectedReason = true;
761       this.expectedReason = Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED;
762     }
763 
764     @Override
doActionAndScheduleNextImpl( SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface, HandlerWrapper handler, @Nullable ActionNode nextAction)765     protected void doActionAndScheduleNextImpl(
766         SimpleExoPlayer player,
767         DefaultTrackSelector trackSelector,
768         @Nullable Surface surface,
769         HandlerWrapper handler,
770         @Nullable ActionNode nextAction) {
771       if (nextAction == null) {
772         return;
773       }
774       Player.EventListener listener =
775           new Player.EventListener() {
776             @Override
777             public void onTimelineChanged(
778                 Timeline timeline, @Player.TimelineChangeReason int reason) {
779               if ((expectedTimeline == null || new NoUidTimeline(timeline).equals(expectedTimeline))
780                   && (ignoreExpectedReason || expectedReason == reason)) {
781                 player.removeListener(this);
782                 nextAction.schedule(player, trackSelector, surface, handler);
783               }
784             }
785           };
786       player.addListener(listener);
787       Timeline currentTimeline = new NoUidTimeline(player.getCurrentTimeline());
788       if (currentTimeline.equals(expectedTimeline)) {
789         player.removeListener(listener);
790         nextAction.schedule(player, trackSelector, surface, handler);
791       }
792     }
793 
794     @Override
doActionImpl( SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface)795     protected void doActionImpl(
796         SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface) {
797       // Not triggered.
798     }
799   }
800 
801   /** Waits for {@link Player.EventListener#onPositionDiscontinuity(int)}. */
802   public static final class WaitForPositionDiscontinuity extends Action {
803 
804     /** @param tag A tag to use for logging. */
WaitForPositionDiscontinuity(String tag)805     public WaitForPositionDiscontinuity(String tag) {
806       super(tag, "WaitForPositionDiscontinuity");
807     }
808 
809     @Override
doActionAndScheduleNextImpl( SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface, HandlerWrapper handler, @Nullable ActionNode nextAction)810     protected void doActionAndScheduleNextImpl(
811         SimpleExoPlayer player,
812         DefaultTrackSelector trackSelector,
813         @Nullable Surface surface,
814         HandlerWrapper handler,
815         @Nullable ActionNode nextAction) {
816       if (nextAction == null) {
817         return;
818       }
819       player.addListener(
820           new Player.EventListener() {
821             @Override
822             public void onPositionDiscontinuity(@Player.DiscontinuityReason int reason) {
823               player.removeListener(this);
824               nextAction.schedule(player, trackSelector, surface, handler);
825             }
826           });
827     }
828 
829     @Override
doActionImpl( SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface)830     protected void doActionImpl(
831         SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface) {
832       // Not triggered.
833     }
834   }
835 
836   /**
837    * Waits for a specified playWhenReady value, returning either immediately or after a call to
838    * {@link Player.EventListener#onPlayWhenReadyChanged(boolean, int)}.
839    */
840   public static final class WaitForPlayWhenReady extends Action {
841 
842     private final boolean targetPlayWhenReady;
843 
844     /**
845      * @param tag A tag to use for logging.
846      * @param playWhenReady The playWhenReady value to wait for.
847      */
WaitForPlayWhenReady(String tag, boolean playWhenReady)848     public WaitForPlayWhenReady(String tag, boolean playWhenReady) {
849       super(tag, "WaitForPlayWhenReady");
850       targetPlayWhenReady = playWhenReady;
851     }
852 
853     @Override
doActionAndScheduleNextImpl( SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface, HandlerWrapper handler, @Nullable ActionNode nextAction)854     protected void doActionAndScheduleNextImpl(
855         SimpleExoPlayer player,
856         DefaultTrackSelector trackSelector,
857         @Nullable Surface surface,
858         HandlerWrapper handler,
859         @Nullable ActionNode nextAction) {
860       if (nextAction == null) {
861         return;
862       }
863       if (targetPlayWhenReady == player.getPlayWhenReady()) {
864         nextAction.schedule(player, trackSelector, surface, handler);
865       } else {
866         player.addListener(
867             new Player.EventListener() {
868               @Override
869               public void onPlayWhenReadyChanged(
870                   boolean playWhenReady, @Player.PlayWhenReadyChangeReason int reason) {
871                 if (targetPlayWhenReady == playWhenReady) {
872                   player.removeListener(this);
873                   nextAction.schedule(player, trackSelector, surface, handler);
874                 }
875               }
876             });
877       }
878     }
879 
880     @Override
doActionImpl( SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface)881     protected void doActionImpl(
882         SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface) {
883       // Not triggered.
884     }
885   }
886 
887   /**
888    * Waits for a specified playback state, returning either immediately or after a call to {@link
889    * Player.EventListener#onPlaybackStateChanged(int)}.
890    */
891   public static final class WaitForPlaybackState extends Action {
892 
893     private final int targetPlaybackState;
894 
895     /**
896      * @param tag A tag to use for logging.
897      * @param targetPlaybackState The playback state to wait for.
898      */
WaitForPlaybackState(String tag, int targetPlaybackState)899     public WaitForPlaybackState(String tag, int targetPlaybackState) {
900       super(tag, "WaitForPlaybackState");
901       this.targetPlaybackState = targetPlaybackState;
902     }
903 
904     @Override
doActionAndScheduleNextImpl( SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface, HandlerWrapper handler, @Nullable ActionNode nextAction)905     protected void doActionAndScheduleNextImpl(
906         SimpleExoPlayer player,
907         DefaultTrackSelector trackSelector,
908         @Nullable Surface surface,
909         HandlerWrapper handler,
910         @Nullable ActionNode nextAction) {
911       if (nextAction == null) {
912         return;
913       }
914       if (targetPlaybackState == player.getPlaybackState()) {
915         nextAction.schedule(player, trackSelector, surface, handler);
916       } else {
917         player.addListener(
918             new Player.EventListener() {
919               @Override
920               public void onPlaybackStateChanged(@Player.State int playbackState) {
921                 if (targetPlaybackState == playbackState) {
922                   player.removeListener(this);
923                   nextAction.schedule(player, trackSelector, surface, handler);
924                 }
925               }
926             });
927       }
928     }
929 
930     @Override
doActionImpl( SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface)931     protected void doActionImpl(
932         SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface) {
933       // Not triggered.
934     }
935   }
936 
937   /**
938    * Waits for a player message to arrive. If the target already received a message, the action
939    * returns immediately.
940    */
941   public static final class WaitForMessage extends Action {
942 
943     private final PlayerTarget playerTarget;
944 
945     /**
946      * @param tag A tag to use for logging.
947      * @param playerTarget The target to observe.
948      */
WaitForMessage(String tag, PlayerTarget playerTarget)949     public WaitForMessage(String tag, PlayerTarget playerTarget) {
950       super(tag, "WaitForMessage");
951       this.playerTarget = playerTarget;
952     }
953 
954     @Override
doActionAndScheduleNextImpl( SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface, HandlerWrapper handler, @Nullable ActionNode nextAction)955     protected void doActionAndScheduleNextImpl(
956         SimpleExoPlayer player,
957         DefaultTrackSelector trackSelector,
958         @Nullable Surface surface,
959         HandlerWrapper handler,
960         @Nullable ActionNode nextAction) {
961       if (nextAction == null) {
962         return;
963       }
964       PlayerTarget.Callback callback =
965           () -> nextAction.schedule(player, trackSelector, surface, handler);
966 
967       playerTarget.setCallback(callback);
968     }
969 
970     @Override
doActionImpl( SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface)971     protected void doActionImpl(
972         SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface) {
973       // Not triggered.
974     }
975   }
976 
977   /**
978    * Waits for a specified loading state, returning either immediately or after a call to {@link
979    * Player.EventListener#onIsLoadingChanged(boolean)}.
980    */
981   public static final class WaitForIsLoading extends Action {
982 
983     private final boolean targetIsLoading;
984 
985     /**
986      * @param tag A tag to use for logging.
987      * @param targetIsLoading The loading state to wait for.
988      */
WaitForIsLoading(String tag, boolean targetIsLoading)989     public WaitForIsLoading(String tag, boolean targetIsLoading) {
990       super(tag, "WaitForIsLoading");
991       this.targetIsLoading = targetIsLoading;
992     }
993 
994     @Override
doActionAndScheduleNextImpl( SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface, HandlerWrapper handler, @Nullable ActionNode nextAction)995     protected void doActionAndScheduleNextImpl(
996         SimpleExoPlayer player,
997         DefaultTrackSelector trackSelector,
998         @Nullable Surface surface,
999         HandlerWrapper handler,
1000         @Nullable ActionNode nextAction) {
1001       if (nextAction == null) {
1002         return;
1003       }
1004       if (targetIsLoading == player.isLoading()) {
1005         nextAction.schedule(player, trackSelector, surface, handler);
1006       } else {
1007         player.addListener(
1008             new Player.EventListener() {
1009               @Override
1010               public void onIsLoadingChanged(boolean isLoading) {
1011                 if (targetIsLoading == isLoading) {
1012                   player.removeListener(this);
1013                   nextAction.schedule(player, trackSelector, surface, handler);
1014                 }
1015               }
1016             });
1017       }
1018     }
1019 
1020     @Override
doActionImpl( SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface)1021     protected void doActionImpl(
1022         SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface) {
1023       // Not triggered.
1024     }
1025   }
1026 
1027   /** Waits until the player acknowledged all pending player commands. */
1028   public static final class WaitForPendingPlayerCommands extends Action {
1029 
1030     /** @param tag A tag to use for logging. */
WaitForPendingPlayerCommands(String tag)1031     public WaitForPendingPlayerCommands(String tag) {
1032       super(tag, "WaitForPendingPlayerCommands");
1033     }
1034 
1035     @Override
doActionAndScheduleNextImpl( SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface, HandlerWrapper handler, @Nullable ActionNode nextAction)1036     protected void doActionAndScheduleNextImpl(
1037         SimpleExoPlayer player,
1038         DefaultTrackSelector trackSelector,
1039         @Nullable Surface surface,
1040         HandlerWrapper handler,
1041         @Nullable ActionNode nextAction) {
1042       if (nextAction == null) {
1043         return;
1044       }
1045       // Send message to player that will arrive after all other pending commands. Thus, the message
1046       // execution on the app thread will also happen after all other pending command
1047       // acknowledgements have arrived back on the app thread.
1048       player
1049           .createMessage(
1050               (type, data) -> nextAction.schedule(player, trackSelector, surface, handler))
1051           .setHandler(Util.createHandler())
1052           .send();
1053     }
1054 
1055     @Override
doActionImpl( SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface)1056     protected void doActionImpl(
1057         SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface) {
1058       // Not triggered.
1059     }
1060   }
1061 
1062   /** Calls {@code Runnable.run()}. */
1063   public static final class ExecuteRunnable extends Action {
1064 
1065     private final Runnable runnable;
1066 
1067     /** @param tag A tag to use for logging. */
ExecuteRunnable(String tag, Runnable runnable)1068     public ExecuteRunnable(String tag, Runnable runnable) {
1069       super(tag, "ExecuteRunnable");
1070       this.runnable = runnable;
1071     }
1072 
1073     @Override
doActionImpl( SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface)1074     protected void doActionImpl(
1075         SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface) {
1076       if (runnable instanceof PlayerRunnable) {
1077         ((PlayerRunnable) runnable).setPlayer(player);
1078       }
1079       runnable.run();
1080     }
1081   }
1082 }
1083