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.Looper; 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.Player; 24 import com.google.android.exoplayer2.PlayerMessage; 25 import com.google.android.exoplayer2.PlayerMessage.Target; 26 import com.google.android.exoplayer2.SimpleExoPlayer; 27 import com.google.android.exoplayer2.Timeline; 28 import com.google.android.exoplayer2.audio.AudioAttributes; 29 import com.google.android.exoplayer2.source.MediaSource; 30 import com.google.android.exoplayer2.source.ShuffleOrder; 31 import com.google.android.exoplayer2.testutil.Action.ClearVideoSurface; 32 import com.google.android.exoplayer2.testutil.Action.ExecuteRunnable; 33 import com.google.android.exoplayer2.testutil.Action.PlayUntilPosition; 34 import com.google.android.exoplayer2.testutil.Action.Seek; 35 import com.google.android.exoplayer2.testutil.Action.SendMessages; 36 import com.google.android.exoplayer2.testutil.Action.SetAudioAttributes; 37 import com.google.android.exoplayer2.testutil.Action.SetPlayWhenReady; 38 import com.google.android.exoplayer2.testutil.Action.SetPlaybackSpeed; 39 import com.google.android.exoplayer2.testutil.Action.SetRendererDisabled; 40 import com.google.android.exoplayer2.testutil.Action.SetRepeatMode; 41 import com.google.android.exoplayer2.testutil.Action.SetShuffleModeEnabled; 42 import com.google.android.exoplayer2.testutil.Action.SetShuffleOrder; 43 import com.google.android.exoplayer2.testutil.Action.SetVideoSurface; 44 import com.google.android.exoplayer2.testutil.Action.Stop; 45 import com.google.android.exoplayer2.testutil.Action.ThrowPlaybackException; 46 import com.google.android.exoplayer2.testutil.Action.WaitForIsLoading; 47 import com.google.android.exoplayer2.testutil.Action.WaitForMessage; 48 import com.google.android.exoplayer2.testutil.Action.WaitForPendingPlayerCommands; 49 import com.google.android.exoplayer2.testutil.Action.WaitForPlayWhenReady; 50 import com.google.android.exoplayer2.testutil.Action.WaitForPlaybackState; 51 import com.google.android.exoplayer2.testutil.Action.WaitForPositionDiscontinuity; 52 import com.google.android.exoplayer2.testutil.Action.WaitForTimelineChanged; 53 import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; 54 import com.google.android.exoplayer2.util.Assertions; 55 import com.google.android.exoplayer2.util.HandlerWrapper; 56 import org.checkerframework.checker.nullness.qual.MonotonicNonNull; 57 58 /** 59 * Schedules a sequence of {@link Action}s for execution during a test. 60 */ 61 public final class ActionSchedule { 62 63 /** 64 * Callback to notify listener that the action schedule has finished. 65 */ 66 public interface Callback { 67 68 /** 69 * Called when action schedule finished executing all its actions. 70 */ onActionScheduleFinished()71 void onActionScheduleFinished(); 72 73 } 74 75 private final ActionNode rootNode; 76 private final CallbackAction callbackAction; 77 78 /** 79 * @param rootNode The first node in the sequence. 80 * @param callbackAction The final action which can be used to trigger a callback. 81 */ ActionSchedule(ActionNode rootNode, CallbackAction callbackAction)82 private ActionSchedule(ActionNode rootNode, CallbackAction callbackAction) { 83 this.rootNode = rootNode; 84 this.callbackAction = callbackAction; 85 } 86 87 /** 88 * Starts execution of the schedule. 89 * 90 * @param player The player to which actions should be applied. 91 * @param trackSelector The track selector to which actions should be applied. 92 * @param surface The surface to use when applying actions, or {@code null} if no surface is 93 * needed. 94 * @param mainHandler A handler associated with the main thread of the host activity. 95 * @param callback A {@link Callback} to notify when the action schedule finishes, or null if no 96 * notification is needed. 97 */ start( SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface, HandlerWrapper mainHandler, @Nullable Callback callback)98 /* package */ void start( 99 SimpleExoPlayer player, 100 DefaultTrackSelector trackSelector, 101 @Nullable Surface surface, 102 HandlerWrapper mainHandler, 103 @Nullable Callback callback) { 104 callbackAction.setCallback(callback); 105 rootNode.schedule(player, trackSelector, surface, mainHandler); 106 } 107 108 /** 109 * A builder for {@link ActionSchedule} instances. 110 */ 111 public static final class Builder { 112 113 private final String tag; 114 private final ActionNode rootNode; 115 116 private long currentDelayMs; 117 private ActionNode previousNode; 118 119 /** 120 * @param tag A tag to use for logging. 121 */ Builder(String tag)122 public Builder(String tag) { 123 this.tag = tag; 124 rootNode = new ActionNode(new RootAction(tag), 0); 125 previousNode = rootNode; 126 } 127 128 /** 129 * Schedules a delay between executing any previous actions and any subsequent ones. 130 * 131 * @param delayMs The delay in milliseconds. 132 * @return The builder, for convenience. 133 */ delay(long delayMs)134 public Builder delay(long delayMs) { 135 currentDelayMs += delayMs; 136 return this; 137 } 138 139 /** 140 * Schedules an action. 141 * 142 * @param action The action to schedule. 143 * @return The builder, for convenience. 144 */ apply(Action action)145 public Builder apply(Action action) { 146 return appendActionNode(new ActionNode(action, currentDelayMs)); 147 } 148 149 /** 150 * Schedules an action repeatedly. 151 * 152 * @param action The action to schedule. 153 * @param intervalMs The interval between each repetition in milliseconds. 154 * @return The builder, for convenience. 155 */ repeat(Action action, long intervalMs)156 public Builder repeat(Action action, long intervalMs) { 157 return appendActionNode(new ActionNode(action, currentDelayMs, intervalMs)); 158 } 159 160 /** 161 * Schedules a seek action. 162 * 163 * @param positionMs The seek position. 164 * @return The builder, for convenience. 165 */ seek(long positionMs)166 public Builder seek(long positionMs) { 167 return apply(new Seek(tag, positionMs)); 168 } 169 170 /** 171 * Schedules a seek action. 172 * 173 * @param windowIndex The window to seek to. 174 * @param positionMs The seek position. 175 * @return The builder, for convenience. 176 */ seek(int windowIndex, long positionMs)177 public Builder seek(int windowIndex, long positionMs) { 178 return apply(new Seek(tag, windowIndex, positionMs, /* catchIllegalSeekException= */ false)); 179 } 180 181 /** 182 * Schedules a seek action to be executed. 183 * 184 * @param windowIndex The window to seek to. 185 * @param positionMs The seek position. 186 * @param catchIllegalSeekException Whether an illegal seek position should be caught or not. 187 * @return The builder, for convenience. 188 */ seek(int windowIndex, long positionMs, boolean catchIllegalSeekException)189 public Builder seek(int windowIndex, long positionMs, boolean catchIllegalSeekException) { 190 return apply(new Seek(tag, windowIndex, positionMs, catchIllegalSeekException)); 191 } 192 193 /** 194 * Schedules a seek action and waits until playback resumes after the seek. 195 * 196 * @param positionMs The seek position. 197 * @return The builder, for convenience. 198 */ seekAndWait(long positionMs)199 public Builder seekAndWait(long positionMs) { 200 return apply(new Seek(tag, positionMs)) 201 .apply(new WaitForPlaybackState(tag, Player.STATE_READY)); 202 } 203 204 /** 205 * Schedules a delay until all pending player commands have been handled. 206 * 207 * <p>A command is considered as having been handled if it arrived on the playback thread and 208 * the player acknowledged that it received the command back to the app thread. 209 * 210 * @return The builder, for convenience. 211 */ waitForPendingPlayerCommands()212 public Builder waitForPendingPlayerCommands() { 213 return apply(new WaitForPendingPlayerCommands(tag)); 214 } 215 216 /** 217 * Schedules a playback speed setting action. 218 * 219 * @param playbackSpeed The playback speed to set. 220 * @return The builder, for convenience. 221 * @see Player#setPlaybackSpeed(float) 222 */ setPlaybackSpeed(float playbackSpeed)223 public Builder setPlaybackSpeed(float playbackSpeed) { 224 return apply(new SetPlaybackSpeed(tag, playbackSpeed)); 225 } 226 227 /** 228 * Schedules a stop action. 229 * 230 * @return The builder, for convenience. 231 */ stop()232 public Builder stop() { 233 return apply(new Stop(tag)); 234 } 235 236 /** 237 * Schedules a stop action. 238 * 239 * @param reset Whether the player should be reset. 240 * @return The builder, for convenience. 241 */ stop(boolean reset)242 public Builder stop(boolean reset) { 243 return apply(new Stop(tag, reset)); 244 } 245 246 /** 247 * Schedules a play action. 248 * 249 * @return The builder, for convenience. 250 */ play()251 public Builder play() { 252 return apply(new SetPlayWhenReady(tag, true)); 253 } 254 255 /** 256 * Schedules a play action, waits until the player reaches the specified position, and pauses 257 * the player again. 258 * 259 * @param windowIndex The window index at which the player should be paused again. 260 * @param positionMs The position in that window at which the player should be paused again. 261 * @return The builder, for convenience. 262 */ playUntilPosition(int windowIndex, long positionMs)263 public Builder playUntilPosition(int windowIndex, long positionMs) { 264 return apply(new PlayUntilPosition(tag, windowIndex, positionMs)); 265 } 266 267 /** 268 * Schedules a play action, waits until the player reaches the start of the specified window, 269 * and pauses the player again. 270 * 271 * @param windowIndex The window index at which the player should be paused again. 272 * @return The builder, for convenience. 273 */ playUntilStartOfWindow(int windowIndex)274 public Builder playUntilStartOfWindow(int windowIndex) { 275 return apply(new PlayUntilPosition(tag, windowIndex, /* positionMs= */ 0)); 276 } 277 278 /** 279 * Schedules a pause action. 280 * 281 * @return The builder, for convenience. 282 */ pause()283 public Builder pause() { 284 return apply(new SetPlayWhenReady(tag, false)); 285 } 286 287 /** 288 * Schedules a renderer enable action. 289 * 290 * @return The builder, for convenience. 291 */ enableRenderer(int index)292 public Builder enableRenderer(int index) { 293 return apply(new SetRendererDisabled(tag, index, false)); 294 } 295 296 /** 297 * Schedules a renderer disable action. 298 * 299 * @return The builder, for convenience. 300 */ disableRenderer(int index)301 public Builder disableRenderer(int index) { 302 return apply(new SetRendererDisabled(tag, index, true)); 303 } 304 305 /** 306 * Schedules a clear video surface action. 307 * 308 * @return The builder, for convenience. 309 */ clearVideoSurface()310 public Builder clearVideoSurface() { 311 return apply(new ClearVideoSurface(tag)); 312 } 313 314 /** 315 * Schedules a set video surface action. 316 * 317 * @return The builder, for convenience. 318 */ setVideoSurface()319 public Builder setVideoSurface() { 320 return apply(new SetVideoSurface(tag)); 321 } 322 323 /** 324 * Schedules application of audio attributes. 325 * 326 * @return The builder, for convenience. 327 */ setAudioAttributes(AudioAttributes audioAttributes, boolean handleAudioFocus)328 public Builder setAudioAttributes(AudioAttributes audioAttributes, boolean handleAudioFocus) { 329 return apply(new SetAudioAttributes(tag, audioAttributes, handleAudioFocus)); 330 } 331 332 /** 333 * Schedules a set media items action to be executed. 334 * 335 * @param windowIndex The window index to start playback from or {@link C#INDEX_UNSET} if the 336 * playback position should not be reset. 337 * @param positionMs The position in milliseconds from where playback should start. If {@link 338 * C#TIME_UNSET} is passed the default position is used. In any case, if {@code windowIndex} 339 * is set to {@link C#INDEX_UNSET} the position is not reset at all and this parameter is 340 * ignored. 341 * @return The builder, for convenience. 342 */ setMediaSources(int windowIndex, long positionMs, MediaSource... sources)343 public Builder setMediaSources(int windowIndex, long positionMs, MediaSource... sources) { 344 return apply(new Action.SetMediaItems(tag, windowIndex, positionMs, sources)); 345 } 346 347 /** 348 * Schedules a set media items action to be executed. 349 * 350 * @param resetPosition Whether the playback position should be reset. 351 * @return The builder, for convenience. 352 */ setMediaSources(boolean resetPosition, MediaSource... sources)353 public Builder setMediaSources(boolean resetPosition, MediaSource... sources) { 354 return apply(new Action.SetMediaItemsResetPosition(tag, resetPosition, sources)); 355 } 356 357 /** 358 * Schedules a set media items action to be executed. 359 * 360 * @param mediaSources The media sources to add. 361 * @return The builder, for convenience. 362 */ setMediaSources(MediaSource... mediaSources)363 public Builder setMediaSources(MediaSource... mediaSources) { 364 return apply( 365 new Action.SetMediaItems( 366 tag, /* windowIndex= */ C.INDEX_UNSET, /* positionMs= */ C.TIME_UNSET, mediaSources)); 367 } 368 /** 369 * Schedules a add media items action to be executed. 370 * 371 * @param mediaSources The media sources to add. 372 * @return The builder, for convenience. 373 */ addMediaSources(MediaSource... mediaSources)374 public Builder addMediaSources(MediaSource... mediaSources) { 375 return apply(new Action.AddMediaItems(tag, mediaSources)); 376 } 377 378 /** 379 * Schedules a move media item action to be executed. 380 * 381 * @param currentIndex The current index of the item to move. 382 * @param newIndex The index after the item has been moved. 383 * @return The builder, for convenience. 384 */ moveMediaItem(int currentIndex, int newIndex)385 public Builder moveMediaItem(int currentIndex, int newIndex) { 386 return apply(new Action.MoveMediaItem(tag, currentIndex, newIndex)); 387 } 388 389 /** 390 * Schedules a remove media item action to be executed. 391 * 392 * @param index The index of the media item to be removed. 393 * @return The builder, for convenience. 394 */ removeMediaItem(int index)395 public Builder removeMediaItem(int index) { 396 return apply(new Action.RemoveMediaItem(tag, index)); 397 } 398 399 /** 400 * Schedules a remove media items action to be executed. 401 * 402 * @param fromIndex The start of the range of media items to be removed. 403 * @param toIndex The end of the range of media items to be removed (exclusive). 404 * @return The builder, for convenience. 405 */ removeMediaItems(int fromIndex, int toIndex)406 public Builder removeMediaItems(int fromIndex, int toIndex) { 407 return apply(new Action.RemoveMediaItems(tag, fromIndex, toIndex)); 408 } 409 410 /** 411 * Schedules a prepare action to be executed. 412 * 413 * @return The builder, for convenience. 414 */ prepare()415 public Builder prepare() { 416 return apply(new Action.Prepare(tag)); 417 } 418 419 /** 420 * Schedules a clear media items action to be created. 421 * 422 * @return The builder. for convenience, 423 */ clearMediaItems()424 public Builder clearMediaItems() { 425 return apply(new Action.ClearMediaItems(tag)); 426 } 427 428 /** 429 * Schedules a repeat mode setting action. 430 * 431 * @return The builder, for convenience. 432 */ setRepeatMode(@layer.RepeatMode int repeatMode)433 public Builder setRepeatMode(@Player.RepeatMode int repeatMode) { 434 return apply(new SetRepeatMode(tag, repeatMode)); 435 } 436 437 /** 438 * Schedules a set shuffle order action to be executed. 439 * 440 * @param shuffleOrder The shuffle order. 441 * @return The builder, for convenience. 442 */ setShuffleOrder(ShuffleOrder shuffleOrder)443 public Builder setShuffleOrder(ShuffleOrder shuffleOrder) { 444 return apply(new SetShuffleOrder(tag, shuffleOrder)); 445 } 446 447 /** 448 * Schedules a shuffle setting action to be executed. 449 * 450 * @return The builder, for convenience. 451 */ setShuffleModeEnabled(boolean shuffleModeEnabled)452 public Builder setShuffleModeEnabled(boolean shuffleModeEnabled) { 453 return apply(new SetShuffleModeEnabled(tag, shuffleModeEnabled)); 454 } 455 456 /** 457 * Schedules sending a {@link PlayerMessage}. 458 * 459 * @param positionMs The position in the current window at which the message should be sent, in 460 * milliseconds. 461 * @return The builder, for convenience. 462 */ sendMessage(Target target, long positionMs)463 public Builder sendMessage(Target target, long positionMs) { 464 return apply(new SendMessages(tag, target, positionMs)); 465 } 466 467 /** 468 * Schedules sending a {@link PlayerMessage}. 469 * 470 * @param target A message target. 471 * @param windowIndex The window index at which the message should be sent. 472 * @param positionMs The position at which the message should be sent, in milliseconds. 473 * @return The builder, for convenience. 474 */ sendMessage(Target target, int windowIndex, long positionMs)475 public Builder sendMessage(Target target, int windowIndex, long positionMs) { 476 return apply( 477 new SendMessages(tag, target, windowIndex, positionMs, /* deleteAfterDelivery= */ true)); 478 } 479 480 /** 481 * Schedules to send a {@link PlayerMessage}. 482 * 483 * @param target A message target. 484 * @param windowIndex The window index at which the message should be sent. 485 * @param positionMs The position at which the message should be sent, in milliseconds. 486 * @param deleteAfterDelivery Whether the message will be deleted after delivery. 487 * @return The builder, for convenience. 488 */ sendMessage( Target target, int windowIndex, long positionMs, boolean deleteAfterDelivery)489 public Builder sendMessage( 490 Target target, int windowIndex, long positionMs, boolean deleteAfterDelivery) { 491 return apply(new SendMessages(tag, target, windowIndex, positionMs, deleteAfterDelivery)); 492 } 493 494 /** 495 * Schedules a delay until any timeline change. 496 * 497 * @return The builder, for convenience. 498 */ waitForTimelineChanged()499 public Builder waitForTimelineChanged() { 500 return apply(new WaitForTimelineChanged(tag)); 501 } 502 503 /** 504 * Schedules a delay until the timeline changed to a specified expected timeline. 505 * 506 * @param expectedTimeline The expected timeline. 507 * @param expectedReason The expected reason of the timeline change. 508 * @return The builder, for convenience. 509 */ waitForTimelineChanged( Timeline expectedTimeline, @Player.TimelineChangeReason int expectedReason)510 public Builder waitForTimelineChanged( 511 Timeline expectedTimeline, @Player.TimelineChangeReason int expectedReason) { 512 return apply(new WaitForTimelineChanged(tag, expectedTimeline, expectedReason)); 513 } 514 515 /** 516 * Schedules a delay until the next position discontinuity. 517 * 518 * @return The builder, for convenience. 519 */ waitForPositionDiscontinuity()520 public Builder waitForPositionDiscontinuity() { 521 return apply(new WaitForPositionDiscontinuity(tag)); 522 } 523 524 /** 525 * Schedules a delay until playWhenReady has the specified value. 526 * 527 * @param targetPlayWhenReady The target playWhenReady value. 528 * @return The builder, for convenience. 529 */ waitForPlayWhenReady(boolean targetPlayWhenReady)530 public Builder waitForPlayWhenReady(boolean targetPlayWhenReady) { 531 return apply(new WaitForPlayWhenReady(tag, targetPlayWhenReady)); 532 } 533 534 /** 535 * Schedules a delay until the playback state changed to the specified state. 536 * 537 * @param targetPlaybackState The target playback state. 538 * @return The builder, for convenience. 539 */ waitForPlaybackState(int targetPlaybackState)540 public Builder waitForPlaybackState(int targetPlaybackState) { 541 return apply(new WaitForPlaybackState(tag, targetPlaybackState)); 542 } 543 544 /** 545 * Schedules a delay until {@code player.isLoading()} changes to the specified value. 546 * 547 * @param targetIsLoading The target value of {@code player.isLoading()}. 548 * @return The builder, for convenience. 549 */ waitForIsLoading(boolean targetIsLoading)550 public Builder waitForIsLoading(boolean targetIsLoading) { 551 return apply(new WaitForIsLoading(tag, targetIsLoading)); 552 } 553 554 /** 555 * Schedules a delay until a message arrives at the {@link PlayerMessage.Target}. 556 * 557 * @param playerTarget The target to observe. 558 * @return The builder, for convenience. 559 */ waitForMessage(PlayerTarget playerTarget)560 public Builder waitForMessage(PlayerTarget playerTarget) { 561 return apply(new WaitForMessage(tag, playerTarget)); 562 } 563 564 /** 565 * Schedules a {@link Runnable}. 566 * 567 * @return The builder, for convenience. 568 */ executeRunnable(Runnable runnable)569 public Builder executeRunnable(Runnable runnable) { 570 return apply(new ExecuteRunnable(tag, runnable)); 571 } 572 573 /** 574 * Schedules to throw a playback exception on the playback thread. 575 * 576 * @param exception The exception to throw. 577 * @return The builder, for convenience. 578 */ throwPlaybackException(ExoPlaybackException exception)579 public Builder throwPlaybackException(ExoPlaybackException exception) { 580 return apply(new ThrowPlaybackException(tag, exception)); 581 } 582 583 /** Builds the schedule. */ build()584 public ActionSchedule build() { 585 CallbackAction callbackAction = new CallbackAction(tag); 586 apply(callbackAction); 587 return new ActionSchedule(rootNode, callbackAction); 588 } 589 appendActionNode(ActionNode actionNode)590 private Builder appendActionNode(ActionNode actionNode) { 591 previousNode.setNext(actionNode); 592 previousNode = actionNode; 593 currentDelayMs = 0; 594 return this; 595 } 596 } 597 598 /** 599 * Provides a wrapper for a {@link Target} which has access to the player when handling messages. 600 * Can be used with {@link Builder#sendMessage(Target, long)}. 601 * 602 * <p>The target can be passed to {@link ActionSchedule.Builder#waitForMessage(PlayerTarget)} to 603 * wait for a message to arrive at the target. 604 */ 605 public abstract static class PlayerTarget implements Target { 606 607 /** Callback to be called when message arrives. */ 608 public interface Callback { 609 /** Notifies about the arrival of the message. */ onMessageArrived()610 void onMessageArrived(); 611 } 612 613 @Nullable private SimpleExoPlayer player; 614 private boolean hasArrived; 615 @Nullable private Callback callback; 616 setCallback(Callback callback)617 public void setCallback(Callback callback) { 618 this.callback = callback; 619 if (hasArrived) { 620 callback.onMessageArrived(); 621 } 622 } 623 624 /** Handles the message send to the component and additionally provides access to the player. */ handleMessage( SimpleExoPlayer player, int messageType, @Nullable Object message)625 public abstract void handleMessage( 626 SimpleExoPlayer player, int messageType, @Nullable Object message); 627 628 /** Sets the player to be passed to {@link #handleMessage(SimpleExoPlayer, int, Object)}. */ setPlayer(SimpleExoPlayer player)629 /* package */ void setPlayer(SimpleExoPlayer player) { 630 this.player = player; 631 } 632 633 @Override handleMessage(int messageType, @Nullable Object message)634 public final void handleMessage(int messageType, @Nullable Object message) { 635 handleMessage(Assertions.checkStateNotNull(player), messageType, message); 636 if (callback != null) { 637 hasArrived = true; 638 callback.onMessageArrived(); 639 } 640 } 641 } 642 643 /** 644 * Provides a wrapper for a {@link Runnable} which has access to the player. Can be used with 645 * {@link Builder#executeRunnable(Runnable)}. 646 */ 647 public abstract static class PlayerRunnable implements Runnable { 648 649 @Nullable private SimpleExoPlayer player; 650 651 /** Executes Runnable with reference to player. */ run(SimpleExoPlayer player)652 public abstract void run(SimpleExoPlayer player); 653 654 /** Sets the player to be passed to {@link #run(SimpleExoPlayer)} . */ setPlayer(SimpleExoPlayer player)655 /* package */ void setPlayer(SimpleExoPlayer player) { 656 this.player = player; 657 } 658 659 @Override run()660 public final void run() { 661 run(Assertions.checkStateNotNull(player)); 662 } 663 } 664 665 /** Wraps an {@link Action}, allowing a delay and a next {@link Action} to be specified. */ 666 /* package */ static final class ActionNode implements Runnable { 667 668 private final Action action; 669 private final long delayMs; 670 private final long repeatIntervalMs; 671 672 @Nullable private ActionNode next; 673 674 private @MonotonicNonNull SimpleExoPlayer player; 675 private @MonotonicNonNull DefaultTrackSelector trackSelector; 676 @Nullable private Surface surface; 677 private @MonotonicNonNull HandlerWrapper mainHandler; 678 679 /** 680 * @param action The wrapped action. 681 * @param delayMs The delay between the node being scheduled and the action being executed. 682 */ ActionNode(Action action, long delayMs)683 public ActionNode(Action action, long delayMs) { 684 this(action, delayMs, C.TIME_UNSET); 685 } 686 687 /** 688 * @param action The wrapped action. 689 * @param delayMs The delay between the node being scheduled and the action being executed. 690 * @param repeatIntervalMs The interval between one execution and the next repetition. If set to 691 * {@link C#TIME_UNSET}, the action is executed once only. 692 */ ActionNode(Action action, long delayMs, long repeatIntervalMs)693 public ActionNode(Action action, long delayMs, long repeatIntervalMs) { 694 this.action = action; 695 this.delayMs = delayMs; 696 this.repeatIntervalMs = repeatIntervalMs; 697 } 698 699 /** 700 * Sets the next action. 701 * 702 * @param next The next {@link Action}. 703 */ setNext(ActionNode next)704 public void setNext(ActionNode next) { 705 this.next = next; 706 } 707 708 /** 709 * Schedules {@link #action} after {@link #delayMs}. The {@link #next} node will be scheduled 710 * immediately after {@link #action} is executed. 711 * 712 * @param player The player to which actions should be applied. 713 * @param trackSelector The track selector to which actions should be applied. 714 * @param surface The surface to use when applying actions, or {@code null}. 715 * @param mainHandler A handler associated with the main thread of the host activity. 716 */ schedule( SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface, HandlerWrapper mainHandler)717 public void schedule( 718 SimpleExoPlayer player, 719 DefaultTrackSelector trackSelector, 720 @Nullable Surface surface, 721 HandlerWrapper mainHandler) { 722 this.player = player; 723 this.trackSelector = trackSelector; 724 this.surface = surface; 725 this.mainHandler = mainHandler; 726 if (delayMs == 0 && Looper.myLooper() == mainHandler.getLooper()) { 727 run(); 728 } else { 729 mainHandler.postDelayed(this, delayMs); 730 } 731 } 732 733 @Override run()734 public void run() { 735 action.doActionAndScheduleNext( 736 Assertions.checkStateNotNull(player), 737 Assertions.checkStateNotNull(trackSelector), 738 surface, 739 Assertions.checkStateNotNull(mainHandler), 740 next); 741 if (repeatIntervalMs != C.TIME_UNSET) { 742 mainHandler.postDelayed( 743 new Runnable() { 744 @Override 745 public void run() { 746 action.doActionAndScheduleNext( 747 player, trackSelector, surface, mainHandler, /* nextAction= */ null); 748 mainHandler.postDelayed(/* runnable= */ this, repeatIntervalMs); 749 } 750 }, 751 repeatIntervalMs); 752 } 753 } 754 755 } 756 757 /** 758 * A no-op root action. 759 */ 760 private static final class RootAction extends Action { 761 RootAction(String tag)762 public RootAction(String tag) { 763 super(tag, "Root"); 764 } 765 766 @Override doActionImpl( SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface)767 protected void doActionImpl( 768 SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface) { 769 // Do nothing. 770 } 771 } 772 773 /** 774 * An action calling a specified {@link ActionSchedule.Callback}. 775 */ 776 private static final class CallbackAction extends Action { 777 778 @Nullable private Callback callback; 779 CallbackAction(String tag)780 public CallbackAction(String tag) { 781 super(tag, "FinishedCallback"); 782 } 783 setCallback(@ullable Callback callback)784 public void setCallback(@Nullable Callback callback) { 785 this.callback = callback; 786 } 787 788 @Override doActionAndScheduleNextImpl( SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface, HandlerWrapper handler, @Nullable ActionNode nextAction)789 protected void doActionAndScheduleNextImpl( 790 SimpleExoPlayer player, 791 DefaultTrackSelector trackSelector, 792 @Nullable Surface surface, 793 HandlerWrapper handler, 794 @Nullable ActionNode nextAction) { 795 Assertions.checkArgument(nextAction == null); 796 @Nullable Callback callback = this.callback; 797 if (callback != null) { 798 handler.post(callback::onActionScheduleFinished); 799 } 800 } 801 802 @Override doActionImpl( SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface)803 protected void doActionImpl( 804 SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface) { 805 // Not triggered. 806 } 807 } 808 809 } 810