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