diff --git a/demos/cast/build.gradle b/demos/cast/build.gradle
index 7e504b6c43..49a5e80985 100644
--- a/demos/cast/build.gradle
+++ b/demos/cast/build.gradle
@@ -47,6 +47,22 @@ android {
// The demo app isn't indexed and doesn't have translations.
disable 'GoogleAppIndexingWarning','MissingTranslation'
}
+
+ flavorDimensions "receiver"
+
+ productFlavors {
+ exoCast {
+ dimension "receiver"
+ manifestPlaceholders =
+ [castOptionsProvider: "com.google.android.exoplayer2.ext.cast.ExoCastOptionsProvider"]
+ }
+ defaultCast {
+ dimension "receiver"
+ manifestPlaceholders =
+ [castOptionsProvider: "com.google.android.exoplayer2.ext.cast.DefaultCastOptionsProvider"]
+ }
+ }
+
}
dependencies {
diff --git a/demos/cast/src/main/AndroidManifest.xml b/demos/cast/src/main/AndroidManifest.xml
index 69887485cb..856b0b1235 100644
--- a/demos/cast/src/main/AndroidManifest.xml
+++ b/demos/cast/src/main/AndroidManifest.xml
@@ -25,7 +25,7 @@
android:largeHeap="true" android:allowBackup="false">
+ android:value="${castOptionsProvider}" />
mediaQueue;
+ private final Listener listener;
+ private final ConcatenatingMediaSource concatenatingMediaSource;
+
+ private boolean castMediaQueueCreationPending;
+ private int currentItemIndex;
+ private Player currentPlayer;
+
+ /**
+ * Creates a new manager for {@link SimpleExoPlayer} and {@link CastPlayer}.
+ *
+ * @param listener A {@link Listener} for queue position changes.
+ * @param localPlayerView The {@link PlayerView} for local playback.
+ * @param castControlView The {@link PlayerControlView} to control remote playback.
+ * @param context A {@link Context}.
+ * @param castContext The {@link CastContext}.
+ */
+ public DefaultReceiverPlayerManager(
+ Listener listener,
+ PlayerView localPlayerView,
+ PlayerControlView castControlView,
+ Context context,
+ CastContext castContext) {
+ this.listener = listener;
+ this.localPlayerView = localPlayerView;
+ this.castControlView = castControlView;
+ mediaQueue = new ArrayList<>();
+ currentItemIndex = C.INDEX_UNSET;
+ concatenatingMediaSource = new ConcatenatingMediaSource();
+
+ DefaultTrackSelector trackSelector = new DefaultTrackSelector();
+ RenderersFactory renderersFactory = new DefaultRenderersFactory(context);
+ exoPlayer = ExoPlayerFactory.newSimpleInstance(context, renderersFactory, trackSelector);
+ exoPlayer.addListener(this);
+ localPlayerView.setPlayer(exoPlayer);
+
+ castPlayer = new CastPlayer(castContext);
+ castPlayer.addListener(this);
+ castPlayer.setSessionAvailabilityListener(this);
+ castControlView.setPlayer(castPlayer);
+
+ setCurrentPlayer(castPlayer.isCastSessionAvailable() ? castPlayer : exoPlayer);
+ }
+
+ // Queue manipulation methods.
+
+ /**
+ * Plays a specified queue item in the current player.
+ *
+ * @param itemIndex The index of the item to play.
+ */
+ @Override
+ public void selectQueueItem(int itemIndex) {
+ setCurrentItem(itemIndex, C.TIME_UNSET, true);
+ }
+
+ /** Returns the index of the currently played item. */
+ @Override
+ public int getCurrentItemIndex() {
+ return currentItemIndex;
+ }
+
+ /**
+ * Appends {@code item} to the media queue.
+ *
+ * @param item The {@link MediaItem} to append.
+ */
+ @Override
+ public void addItem(MediaItem item) {
+ mediaQueue.add(item);
+ concatenatingMediaSource.addMediaSource(buildMediaSource(item));
+ if (currentPlayer == castPlayer) {
+ castPlayer.addItems(buildMediaQueueItem(item));
+ }
+ }
+
+ /** Returns the size of the media queue. */
+ @Override
+ public int getMediaQueueSize() {
+ return mediaQueue.size();
+ }
+
+ /**
+ * Returns the item at the given index in the media queue.
+ *
+ * @param position The index of the item.
+ * @return The item at the given index in the media queue.
+ */
+ @Override
+ public MediaItem getItem(int position) {
+ return mediaQueue.get(position);
+ }
+
+ /**
+ * Removes the item at the given index from the media queue.
+ *
+ * @param item The item to remove.
+ * @return Whether the removal was successful.
+ */
+ @Override
+ public boolean removeItem(MediaItem item) {
+ int itemIndex = mediaQueue.indexOf(item);
+ if (itemIndex == -1) {
+ return false;
+ }
+ concatenatingMediaSource.removeMediaSource(itemIndex);
+ if (currentPlayer == castPlayer) {
+ if (castPlayer.getPlaybackState() != Player.STATE_IDLE) {
+ Timeline castTimeline = castPlayer.getCurrentTimeline();
+ if (castTimeline.getPeriodCount() <= itemIndex) {
+ return false;
+ }
+ castPlayer.removeItem((int) castTimeline.getPeriod(itemIndex, new Period()).id);
+ }
+ }
+ mediaQueue.remove(itemIndex);
+ if (itemIndex == currentItemIndex && itemIndex == mediaQueue.size()) {
+ maybeSetCurrentItemAndNotify(C.INDEX_UNSET);
+ } else if (itemIndex < currentItemIndex) {
+ maybeSetCurrentItemAndNotify(currentItemIndex - 1);
+ }
+ return true;
+ }
+
+ /**
+ * Moves an item within the queue.
+ *
+ * @param item The item to move.
+ * @param toIndex The target index of the item in the queue.
+ * @return Whether the item move was successful.
+ */
+ @Override
+ public boolean moveItem(MediaItem item, int toIndex) {
+ int fromIndex = mediaQueue.indexOf(item);
+ if (fromIndex == -1) {
+ return false;
+ }
+ // Player update.
+ concatenatingMediaSource.moveMediaSource(fromIndex, toIndex);
+ if (currentPlayer == castPlayer && castPlayer.getPlaybackState() != Player.STATE_IDLE) {
+ Timeline castTimeline = castPlayer.getCurrentTimeline();
+ int periodCount = castTimeline.getPeriodCount();
+ if (periodCount <= fromIndex || periodCount <= toIndex) {
+ return false;
+ }
+ int elementId = (int) castTimeline.getPeriod(fromIndex, new Period()).id;
+ castPlayer.moveItem(elementId, toIndex);
+ }
+
+ mediaQueue.add(toIndex, mediaQueue.remove(fromIndex));
+
+ // Index update.
+ if (fromIndex == currentItemIndex) {
+ maybeSetCurrentItemAndNotify(toIndex);
+ } else if (fromIndex < currentItemIndex && toIndex >= currentItemIndex) {
+ maybeSetCurrentItemAndNotify(currentItemIndex - 1);
+ } else if (fromIndex > currentItemIndex && toIndex <= currentItemIndex) {
+ maybeSetCurrentItemAndNotify(currentItemIndex + 1);
+ }
+
+ return true;
+ }
+
+ /**
+ * Dispatches a given {@link KeyEvent} to the corresponding view of the current player.
+ *
+ * @param event The {@link KeyEvent}.
+ * @return Whether the event was handled by the target view.
+ */
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ if (currentPlayer == exoPlayer) {
+ return localPlayerView.dispatchKeyEvent(event);
+ } else /* currentPlayer == castPlayer */ {
+ return castControlView.dispatchKeyEvent(event);
+ }
+ }
+
+ /** Releases the manager and the players that it holds. */
+ @Override
+ public void release() {
+ currentItemIndex = C.INDEX_UNSET;
+ mediaQueue.clear();
+ concatenatingMediaSource.clear();
+ castPlayer.setSessionAvailabilityListener(null);
+ castPlayer.release();
+ localPlayerView.setPlayer(null);
+ exoPlayer.release();
+ }
+
+ // Player.EventListener implementation.
+
+ @Override
+ public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
+ updateCurrentItemIndex();
+ }
+
+ @Override
+ public void onPositionDiscontinuity(@DiscontinuityReason int reason) {
+ updateCurrentItemIndex();
+ }
+
+ @Override
+ public void onTimelineChanged(
+ Timeline timeline, @Nullable Object manifest, @TimelineChangeReason int reason) {
+ updateCurrentItemIndex();
+ if (currentPlayer == castPlayer && timeline.isEmpty()) {
+ castMediaQueueCreationPending = true;
+ }
+ }
+
+ // CastPlayer.SessionAvailabilityListener implementation.
+
+ @Override
+ public void onCastSessionAvailable() {
+ setCurrentPlayer(castPlayer);
+ }
+
+ @Override
+ public void onCastSessionUnavailable() {
+ setCurrentPlayer(exoPlayer);
+ }
+
+ // Internal methods.
+
+ private void updateCurrentItemIndex() {
+ int playbackState = currentPlayer.getPlaybackState();
+ maybeSetCurrentItemAndNotify(
+ playbackState != Player.STATE_IDLE && playbackState != Player.STATE_ENDED
+ ? currentPlayer.getCurrentWindowIndex()
+ : C.INDEX_UNSET);
+ }
+
+ private void setCurrentPlayer(Player currentPlayer) {
+ if (this.currentPlayer == currentPlayer) {
+ return;
+ }
+
+ // View management.
+ if (currentPlayer == exoPlayer) {
+ localPlayerView.setVisibility(View.VISIBLE);
+ castControlView.hide();
+ } else /* currentPlayer == castPlayer */ {
+ localPlayerView.setVisibility(View.GONE);
+ castControlView.show();
+ }
+
+ // Player state management.
+ long playbackPositionMs = C.TIME_UNSET;
+ int windowIndex = C.INDEX_UNSET;
+ boolean playWhenReady = false;
+ if (this.currentPlayer != null) {
+ int playbackState = this.currentPlayer.getPlaybackState();
+ if (playbackState != Player.STATE_ENDED) {
+ playbackPositionMs = this.currentPlayer.getCurrentPosition();
+ playWhenReady = this.currentPlayer.getPlayWhenReady();
+ windowIndex = this.currentPlayer.getCurrentWindowIndex();
+ if (windowIndex != currentItemIndex) {
+ playbackPositionMs = C.TIME_UNSET;
+ windowIndex = currentItemIndex;
+ }
+ }
+ this.currentPlayer.stop(true);
+ } else {
+ // This is the initial setup. No need to save any state.
+ }
+
+ this.currentPlayer = currentPlayer;
+
+ // Media queue management.
+ castMediaQueueCreationPending = currentPlayer == castPlayer;
+ if (currentPlayer == exoPlayer) {
+ exoPlayer.prepare(concatenatingMediaSource);
+ }
+
+ // Playback transition.
+ if (windowIndex != C.INDEX_UNSET) {
+ setCurrentItem(windowIndex, playbackPositionMs, playWhenReady);
+ }
+ }
+
+ /**
+ * Starts playback of the item at the given position.
+ *
+ * @param itemIndex The index of the item to play.
+ * @param positionMs The position at which playback should start.
+ * @param playWhenReady Whether the player should proceed when ready to do so.
+ */
+ private void setCurrentItem(int itemIndex, long positionMs, boolean playWhenReady) {
+ maybeSetCurrentItemAndNotify(itemIndex);
+ if (castMediaQueueCreationPending) {
+ MediaQueueItem[] items = new MediaQueueItem[mediaQueue.size()];
+ for (int i = 0; i < items.length; i++) {
+ items[i] = buildMediaQueueItem(mediaQueue.get(i));
+ }
+ castMediaQueueCreationPending = false;
+ castPlayer.loadItems(items, itemIndex, positionMs, Player.REPEAT_MODE_OFF);
+ } else {
+ currentPlayer.seekTo(itemIndex, positionMs);
+ currentPlayer.setPlayWhenReady(playWhenReady);
+ }
+ }
+
+ private void maybeSetCurrentItemAndNotify(int currentItemIndex) {
+ if (this.currentItemIndex != currentItemIndex) {
+ int oldIndex = this.currentItemIndex;
+ this.currentItemIndex = currentItemIndex;
+ listener.onQueuePositionChanged(oldIndex, currentItemIndex);
+ }
+ }
+
+ private static MediaSource buildMediaSource(MediaItem item) {
+ Uri uri = item.media.uri;
+ switch (item.mimeType) {
+ case DemoUtil.MIME_TYPE_SS:
+ return new SsMediaSource.Factory(DATA_SOURCE_FACTORY).createMediaSource(uri);
+ case DemoUtil.MIME_TYPE_DASH:
+ return new DashMediaSource.Factory(DATA_SOURCE_FACTORY).createMediaSource(uri);
+ case DemoUtil.MIME_TYPE_HLS:
+ return new HlsMediaSource.Factory(DATA_SOURCE_FACTORY).createMediaSource(uri);
+ case DemoUtil.MIME_TYPE_VIDEO_MP4:
+ return new ProgressiveMediaSource.Factory(DATA_SOURCE_FACTORY).createMediaSource(uri);
+ default:
+ {
+ throw new IllegalStateException("Unsupported type: " + item.mimeType);
+ }
+ }
+ }
+
+ private static MediaQueueItem buildMediaQueueItem(MediaItem item) {
+ MediaMetadata movieMetadata = new MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE);
+ movieMetadata.putString(MediaMetadata.KEY_TITLE, item.title);
+ MediaInfo mediaInfo =
+ new MediaInfo.Builder(item.media.uri.toString())
+ .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
+ .setContentType(item.mimeType)
+ .setMetadata(movieMetadata)
+ .build();
+ return new MediaQueueItem.Builder(mediaInfo).build();
+ }
+}
diff --git a/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/MainActivity.java b/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/MainActivity.java
index a69b32788d..efda40a913 100644
--- a/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/MainActivity.java
+++ b/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/MainActivity.java
@@ -39,6 +39,7 @@ import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.ext.cast.MediaItem;
import com.google.android.exoplayer2.ui.PlayerControlView;
import com.google.android.exoplayer2.ui.PlayerView;
+import com.google.android.gms.cast.CastMediaControlIntent;
import com.google.android.gms.cast.framework.CastButtonFactory;
import com.google.android.gms.cast.framework.CastContext;
import com.google.android.gms.dynamite.DynamiteModule;
@@ -117,13 +118,29 @@ public class MainActivity extends AppCompatActivity
// There is no Cast context to work with. Do nothing.
return;
}
- playerManager =
- new PlayerManager(
- /* listener= */ this,
- localPlayerView,
- castControlView,
- /* context= */ this,
- castContext);
+ String applicationId = castContext.getCastOptions().getReceiverApplicationId();
+ switch (applicationId) {
+ case CastMediaControlIntent.DEFAULT_MEDIA_RECEIVER_APPLICATION_ID:
+ playerManager =
+ new DefaultReceiverPlayerManager(
+ /* listener= */ this,
+ localPlayerView,
+ castControlView,
+ /* context= */ this,
+ castContext);
+ break;
+ case ExoCastOptionsProvider.RECEIVER_ID:
+ playerManager =
+ new ExoCastPlayerManager(
+ /* listener= */ this,
+ localPlayerView,
+ castControlView,
+ /* context= */ this,
+ castContext);
+ break;
+ default:
+ throw new IllegalStateException("Illegal receiver app id: " + applicationId);
+ }
mediaQueueList.setAdapter(mediaQueueListAdapter);
}
diff --git a/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/PlayerManager.java b/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/PlayerManager.java
index 28dd808fdf..c9a728b3ff 100644
--- a/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/PlayerManager.java
+++ b/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/PlayerManager.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018 The Android Open Source Project
+ * Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,46 +15,15 @@
*/
package com.google.android.exoplayer2.castdemo;
-import android.content.Context;
-import android.net.Uri;
-import android.support.annotation.Nullable;
import android.view.KeyEvent;
-import android.view.View;
import com.google.android.exoplayer2.C;
-import com.google.android.exoplayer2.DefaultRenderersFactory;
-import com.google.android.exoplayer2.ExoPlaybackException;
-import com.google.android.exoplayer2.ExoPlayerFactory;
-import com.google.android.exoplayer2.Player;
-import com.google.android.exoplayer2.Player.DiscontinuityReason;
-import com.google.android.exoplayer2.Player.EventListener;
-import com.google.android.exoplayer2.Player.TimelineChangeReason;
-import com.google.android.exoplayer2.RenderersFactory;
-import com.google.android.exoplayer2.SimpleExoPlayer;
-import com.google.android.exoplayer2.Timeline;
-import com.google.android.exoplayer2.ext.cast.DefaultCastSessionManager;
-import com.google.android.exoplayer2.ext.cast.ExoCastPlayer;
import com.google.android.exoplayer2.ext.cast.MediaItem;
-import com.google.android.exoplayer2.ext.cast.SessionAvailabilityListener;
-import com.google.android.exoplayer2.source.ConcatenatingMediaSource;
-import com.google.android.exoplayer2.source.MediaSource;
-import com.google.android.exoplayer2.source.ProgressiveMediaSource;
-import com.google.android.exoplayer2.source.dash.DashMediaSource;
-import com.google.android.exoplayer2.source.hls.HlsMediaSource;
-import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource;
-import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
-import com.google.android.exoplayer2.ui.PlayerControlView;
-import com.google.android.exoplayer2.ui.PlayerView;
-import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
-import com.google.android.exoplayer2.util.Assertions;
-import com.google.android.exoplayer2.util.Log;
-import com.google.android.gms.cast.framework.CastContext;
-import java.util.ArrayList;
-/** Manages players and an internal media queue for the Cast demo app. */
-/* package */ class PlayerManager implements EventListener, SessionAvailabilityListener {
+/** Manages the players in the Cast demo app. */
+/* package */ interface PlayerManager {
/** Listener for events. */
- public interface Listener {
+ interface Listener {
/** Called when the currently played item of the media queue changes. */
void onQueuePositionChanged(int previousIndex, int newIndex);
@@ -66,361 +35,33 @@ import java.util.ArrayList;
void onPlayerError();
}
- private static final String TAG = "PlayerManager";
- private static final String USER_AGENT = "ExoCastDemoPlayer";
- private static final DefaultHttpDataSourceFactory DATA_SOURCE_FACTORY =
- new DefaultHttpDataSourceFactory(USER_AGENT);
+ /** Redirects the given {@code keyEvent} to the active player. */
+ boolean dispatchKeyEvent(KeyEvent keyEvent);
- private final PlayerView localPlayerView;
- private final PlayerControlView castControlView;
- private final SimpleExoPlayer exoPlayer;
- private final ExoCastPlayer exoCastPlayer;
- private final ArrayList mediaQueue;
- private final Listener listener;
- private final ConcatenatingMediaSource concatenatingMediaSource;
+ /** Appends the given {@link MediaItem} to the media queue. */
+ void addItem(MediaItem mediaItem);
- private int currentItemIndex;
- private Player currentPlayer;
+ /** Returns the number of items in the media queue. */
+ int getMediaQueueSize();
+
+ /** Selects the item at the given position for playback. */
+ void selectQueueItem(int position);
/**
- * Creates a new manager for {@link SimpleExoPlayer} and {@link ExoCastPlayer}.
- *
- * @param listener A {@link Listener}.
- * @param localPlayerView The {@link PlayerView} for local playback.
- * @param castControlView The {@link PlayerControlView} to control remote playback.
- * @param context A {@link Context}.
- * @param castContext The {@link CastContext}.
+ * Returns the position of the item currently being played, or {@link C#INDEX_UNSET} if no item is
+ * being played.
*/
- public PlayerManager(
- Listener listener,
- PlayerView localPlayerView,
- PlayerControlView castControlView,
- Context context,
- CastContext castContext) {
- this.listener = listener;
- this.localPlayerView = localPlayerView;
- this.castControlView = castControlView;
- mediaQueue = new ArrayList<>();
- currentItemIndex = C.INDEX_UNSET;
- concatenatingMediaSource = new ConcatenatingMediaSource();
+ int getCurrentItemIndex();
- DefaultTrackSelector trackSelector = new DefaultTrackSelector();
- RenderersFactory renderersFactory = new DefaultRenderersFactory(context);
- exoPlayer = ExoPlayerFactory.newSimpleInstance(context, renderersFactory, trackSelector);
- exoPlayer.addListener(this);
- localPlayerView.setPlayer(exoPlayer);
+ /** Returns the {@link MediaItem} at the given {@code position}. */
+ MediaItem getItem(int position);
- exoCastPlayer =
- new ExoCastPlayer(
- sessionManagerListener ->
- new DefaultCastSessionManager(castContext, sessionManagerListener));
- exoCastPlayer.addListener(this);
- exoCastPlayer.setSessionAvailabilityListener(this);
- castControlView.setPlayer(exoCastPlayer);
+ /** Moves the item at position {@code from} to position {@code to}. */
+ boolean moveItem(MediaItem item, int to);
- setCurrentPlayer(exoCastPlayer.isCastSessionAvailable() ? exoCastPlayer : exoPlayer);
- }
+ /** Removes the item at position {@code index}. */
+ boolean removeItem(MediaItem item);
- // Queue manipulation methods.
-
- /**
- * Plays a specified queue item in the current player.
- *
- * @param itemIndex The index of the item to play.
- */
- public void selectQueueItem(int itemIndex) {
- setCurrentItem(itemIndex, C.TIME_UNSET, true);
- }
-
- /** Returns the index of the currently played item. */
- public int getCurrentItemIndex() {
- return currentItemIndex;
- }
-
- /**
- * Appends {@code item} to the media queue.
- *
- * @param item The {@link MediaItem} to append.
- */
- public void addItem(MediaItem item) {
- mediaQueue.add(item);
- concatenatingMediaSource.addMediaSource(buildMediaSource(item));
- if (currentPlayer == exoCastPlayer) {
- exoCastPlayer.addItemsToQueue(item);
- }
- }
-
- /** Returns the size of the media queue. */
- public int getMediaQueueSize() {
- return mediaQueue.size();
- }
-
- /**
- * Returns the item at the given index in the media queue.
- *
- * @param position The index of the item.
- * @return The item at the given index in the media queue.
- */
- public MediaItem getItem(int position) {
- return mediaQueue.get(position);
- }
-
- /**
- * Removes the item at the given index from the media queue.
- *
- * @param item The item to remove.
- * @return Whether the removal was successful.
- */
- public boolean removeItem(MediaItem item) {
- int itemIndex = mediaQueue.indexOf(item);
- if (itemIndex == -1) {
- // This may happen if another sender app removes items while this sender app is in "swiping
- // an item" state.
- return false;
- }
- concatenatingMediaSource.removeMediaSource(itemIndex);
- mediaQueue.remove(itemIndex);
- if (currentPlayer == exoCastPlayer) {
- exoCastPlayer.removeItemFromQueue(itemIndex);
- }
- if (itemIndex == currentItemIndex && itemIndex == mediaQueue.size()) {
- maybeSetCurrentItemAndNotify(C.INDEX_UNSET);
- } else if (itemIndex < currentItemIndex) {
- maybeSetCurrentItemAndNotify(currentItemIndex - 1);
- }
- return true;
- }
-
- /**
- * Moves an item within the queue.
- *
- * @param item The item to move. This method does nothing if {@code item} is not contained in the
- * queue.
- * @param toIndex The target index of the item in the queue. If {@code toIndex} exceeds the last
- * position in the queue, {@code toIndex} is clamped to match the largest possible value.
- * @return True if {@code item} was contained in the queue, and {@code toIndex} was a valid
- * position. False otherwise.
- */
- public boolean moveItem(MediaItem item, int toIndex) {
- int indexOfItem = mediaQueue.indexOf(item);
- if (indexOfItem == -1) {
- // This may happen if another sender app removes items while this sender app is in "dragging
- // an item" state.
- return false;
- }
- int clampedToIndex = Math.min(toIndex, mediaQueue.size() - 1);
- mediaQueue.add(clampedToIndex, mediaQueue.remove(indexOfItem));
- concatenatingMediaSource.moveMediaSource(indexOfItem, clampedToIndex);
- if (currentPlayer == exoCastPlayer) {
- exoCastPlayer.moveItemInQueue(indexOfItem, clampedToIndex);
- }
- // Index update.
- maybeSetCurrentItemAndNotify(currentPlayer.getCurrentWindowIndex());
- return clampedToIndex == toIndex;
- }
-
- // Miscellaneous methods.
-
- public boolean dispatchKeyEvent(KeyEvent event) {
- if (currentPlayer == exoPlayer) {
- return localPlayerView.dispatchKeyEvent(event);
- } else /* currentPlayer == exoCastPlayer */ {
- return castControlView.dispatchKeyEvent(event);
- }
- }
-
- /** Releases the manager and the players that it holds. */
- public void release() {
- currentItemIndex = C.INDEX_UNSET;
- mediaQueue.clear();
- concatenatingMediaSource.clear();
- exoCastPlayer.setSessionAvailabilityListener(null);
- exoCastPlayer.release();
- localPlayerView.setPlayer(null);
- exoPlayer.release();
- }
-
- // Player.EventListener implementation.
-
- @Override
- public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
- updateCurrentItemIndex();
- }
-
- @Override
- public void onPositionDiscontinuity(@DiscontinuityReason int reason) {
- updateCurrentItemIndex();
- }
-
- @Override
- public void onTimelineChanged(
- Timeline timeline, @Nullable Object manifest, @TimelineChangeReason int reason) {
- if (currentPlayer == exoCastPlayer && reason != Player.TIMELINE_CHANGE_REASON_RESET) {
- maybeUpdateLocalQueueWithRemoteQueueAndNotify();
- }
- updateCurrentItemIndex();
- }
-
- @Override
- public void onPlayerError(ExoPlaybackException error) {
- Log.e(TAG, "The player encountered an error.", error);
- listener.onPlayerError();
- }
-
- // CastPlayer.SessionAvailabilityListener implementation.
-
- @Override
- public void onCastSessionAvailable() {
- setCurrentPlayer(exoCastPlayer);
- }
-
- @Override
- public void onCastSessionUnavailable() {
- setCurrentPlayer(exoPlayer);
- }
-
- // Internal methods.
-
- private void maybeUpdateLocalQueueWithRemoteQueueAndNotify() {
- Assertions.checkState(currentPlayer == exoCastPlayer);
- boolean mediaQueuesMatch = mediaQueue.size() == exoCastPlayer.getQueueSize();
- for (int i = 0; mediaQueuesMatch && i < mediaQueue.size(); i++) {
- mediaQueuesMatch = mediaQueue.get(i).uuid.equals(exoCastPlayer.getQueueItem(i).uuid);
- }
- if (mediaQueuesMatch) {
- // The media queues match. Do nothing.
- return;
- }
- mediaQueue.clear();
- concatenatingMediaSource.clear();
- for (int i = 0; i < exoCastPlayer.getQueueSize(); i++) {
- MediaItem item = exoCastPlayer.getQueueItem(i);
- mediaQueue.add(item);
- concatenatingMediaSource.addMediaSource(buildMediaSource(item));
- }
- listener.onQueueContentsExternallyChanged();
- }
-
- private void updateCurrentItemIndex() {
- int playbackState = currentPlayer.getPlaybackState();
- maybeSetCurrentItemAndNotify(
- playbackState != Player.STATE_IDLE && playbackState != Player.STATE_ENDED
- ? currentPlayer.getCurrentWindowIndex()
- : C.INDEX_UNSET);
- }
-
- private void setCurrentPlayer(Player currentPlayer) {
- if (this.currentPlayer == currentPlayer) {
- return;
- }
-
- // View management.
- if (currentPlayer == exoPlayer) {
- localPlayerView.setVisibility(View.VISIBLE);
- castControlView.hide();
- } else /* currentPlayer == exoCastPlayer */ {
- localPlayerView.setVisibility(View.GONE);
- castControlView.show();
- }
-
- // Player state management.
- long playbackPositionMs = C.TIME_UNSET;
- int windowIndex = C.INDEX_UNSET;
- boolean playWhenReady = false;
- if (this.currentPlayer != null) {
- int playbackState = this.currentPlayer.getPlaybackState();
- if (playbackState != Player.STATE_ENDED) {
- playbackPositionMs = this.currentPlayer.getCurrentPosition();
- playWhenReady = this.currentPlayer.getPlayWhenReady();
- windowIndex = this.currentPlayer.getCurrentWindowIndex();
- if (windowIndex != currentItemIndex) {
- playbackPositionMs = C.TIME_UNSET;
- windowIndex = currentItemIndex;
- }
- }
- this.currentPlayer.stop(true);
- } else {
- // This is the initial setup. No need to save any state.
- }
-
- this.currentPlayer = currentPlayer;
-
- // Media queue management.
- boolean shouldSeekInNewCurrentPlayer;
- if (currentPlayer == exoPlayer) {
- exoPlayer.prepare(concatenatingMediaSource);
- shouldSeekInNewCurrentPlayer = true;
- } else /* currentPlayer == exoCastPlayer */ {
- if (exoCastPlayer.getPlaybackState() == Player.STATE_IDLE) {
- exoCastPlayer.prepare();
- }
- if (mediaQueue.isEmpty()) {
- // Casting started with no local queue. We take the receiver app's queue as our own.
- maybeUpdateLocalQueueWithRemoteQueueAndNotify();
- shouldSeekInNewCurrentPlayer = false;
- } else {
- // Casting started when the sender app had no queue. We just load our items into the
- // receiver app's queue. If the receiver had no items in its queue, we also seek to wherever
- // the sender app was playing.
- int currentExoCastPlayerState = exoCastPlayer.getPlaybackState();
- shouldSeekInNewCurrentPlayer =
- currentExoCastPlayerState == Player.STATE_IDLE
- || currentExoCastPlayerState == Player.STATE_ENDED;
- exoCastPlayer.addItemsToQueue(mediaQueue.toArray(new MediaItem[0]));
- }
- }
-
- // Playback transition.
- if (shouldSeekInNewCurrentPlayer && windowIndex != C.INDEX_UNSET) {
- setCurrentItem(windowIndex, playbackPositionMs, playWhenReady);
- } else if (getMediaQueueSize() > 0) {
- maybeSetCurrentItemAndNotify(currentPlayer.getCurrentWindowIndex());
- }
- }
-
- /**
- * Starts playback of the item at the given position.
- *
- * @param itemIndex The index of the item to play.
- * @param positionMs The position at which playback should start.
- * @param playWhenReady Whether the player should proceed when ready to do so.
- */
- private void setCurrentItem(int itemIndex, long positionMs, boolean playWhenReady) {
- maybeSetCurrentItemAndNotify(itemIndex);
- currentPlayer.seekTo(itemIndex, positionMs);
- if (currentPlayer.getPlaybackState() == Player.STATE_IDLE) {
- if (currentPlayer == exoCastPlayer) {
- exoCastPlayer.prepare();
- } else {
- exoPlayer.prepare(concatenatingMediaSource);
- }
- }
- currentPlayer.setPlayWhenReady(playWhenReady);
- }
-
- private void maybeSetCurrentItemAndNotify(int currentItemIndex) {
- if (this.currentItemIndex != currentItemIndex) {
- int oldIndex = this.currentItemIndex;
- this.currentItemIndex = currentItemIndex;
- listener.onQueuePositionChanged(oldIndex, currentItemIndex);
- }
- }
-
- private static MediaSource buildMediaSource(MediaItem item) {
- Uri uri = item.media.uri;
- switch (item.mimeType) {
- case DemoUtil.MIME_TYPE_SS:
- return new SsMediaSource.Factory(DATA_SOURCE_FACTORY).createMediaSource(uri);
- case DemoUtil.MIME_TYPE_DASH:
- return new DashMediaSource.Factory(DATA_SOURCE_FACTORY).createMediaSource(uri);
- case DemoUtil.MIME_TYPE_HLS:
- return new HlsMediaSource.Factory(DATA_SOURCE_FACTORY).createMediaSource(uri);
- case DemoUtil.MIME_TYPE_VIDEO_MP4:
- return new ProgressiveMediaSource.Factory(DATA_SOURCE_FACTORY).createMediaSource(uri);
- default:
- {
- throw new IllegalStateException("Unsupported type: " + item.mimeType);
- }
- }
- }
+ /** Releases any acquired resources. */
+ void release();
}