Add media queue support to CastPlayer
Also workaround the non-repeatable queue and fix other minor issues. Issue:#2283 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=166848894
This commit is contained in:
parent
f44e30c754
commit
aafdd2267a
@ -52,17 +52,17 @@ import java.util.List;
|
|||||||
/**
|
/**
|
||||||
* The mime type of the media sample, as required by {@link MediaInfo#setContentType}.
|
* The mime type of the media sample, as required by {@link MediaInfo#setContentType}.
|
||||||
*/
|
*/
|
||||||
public final String type;
|
public final String mimeType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param uri See {@link #uri}.
|
* @param uri See {@link #uri}.
|
||||||
* @param name See {@link #name}.
|
* @param name See {@link #name}.
|
||||||
* @param type See {@link #type}.
|
* @param mimeType See {@link #mimeType}.
|
||||||
*/
|
*/
|
||||||
public Sample(String uri, String name, String type) {
|
public Sample(String uri, String name, String mimeType) {
|
||||||
this.uri = uri;
|
this.uri = uri;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.type = type;
|
this.mimeType = mimeType;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -37,6 +37,9 @@ import com.google.android.exoplayer2.ui.PlaybackControlView;
|
|||||||
import com.google.android.exoplayer2.ui.SimpleExoPlayerView;
|
import com.google.android.exoplayer2.ui.SimpleExoPlayerView;
|
||||||
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
|
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
|
||||||
import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
|
import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
|
||||||
|
import com.google.android.gms.cast.MediaInfo;
|
||||||
|
import com.google.android.gms.cast.MediaMetadata;
|
||||||
|
import com.google.android.gms.cast.MediaQueueItem;
|
||||||
import com.google.android.gms.cast.framework.CastContext;
|
import com.google.android.gms.cast.framework.CastContext;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -95,12 +98,12 @@ import com.google.android.gms.cast.framework.CastContext;
|
|||||||
boolean playWhenReady) {
|
boolean playWhenReady) {
|
||||||
this.currentSample = currentSample;
|
this.currentSample = currentSample;
|
||||||
if (playbackLocation == PLAYBACK_REMOTE) {
|
if (playbackLocation == PLAYBACK_REMOTE) {
|
||||||
castPlayer.load(currentSample.name, currentSample.uri, currentSample.type, positionMs,
|
castPlayer.loadItem(buildMediaQueueItem(currentSample), positionMs);
|
||||||
playWhenReady);
|
castPlayer.setPlayWhenReady(playWhenReady);
|
||||||
} else /* playbackLocation == PLAYBACK_LOCAL */ {
|
} else /* playbackLocation == PLAYBACK_LOCAL */ {
|
||||||
|
exoPlayer.prepare(buildMediaSource(currentSample), true, true);
|
||||||
exoPlayer.setPlayWhenReady(playWhenReady);
|
exoPlayer.setPlayWhenReady(playWhenReady);
|
||||||
exoPlayer.seekTo(positionMs);
|
exoPlayer.seekTo(positionMs);
|
||||||
exoPlayer.prepare(buildMediaSource(currentSample), true, true);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -143,9 +146,18 @@ import com.google.android.gms.cast.framework.CastContext;
|
|||||||
|
|
||||||
// Internal methods.
|
// Internal methods.
|
||||||
|
|
||||||
|
private static MediaQueueItem buildMediaQueueItem(CastDemoUtil.Sample sample) {
|
||||||
|
MediaMetadata movieMetadata = new MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE);
|
||||||
|
movieMetadata.putString(MediaMetadata.KEY_TITLE, sample.name);
|
||||||
|
MediaInfo mediaInfo = new MediaInfo.Builder(sample.uri)
|
||||||
|
.setStreamType(MediaInfo.STREAM_TYPE_BUFFERED).setContentType(sample.mimeType)
|
||||||
|
.setMetadata(movieMetadata).build();
|
||||||
|
return new MediaQueueItem.Builder(mediaInfo).build();
|
||||||
|
}
|
||||||
|
|
||||||
private static MediaSource buildMediaSource(CastDemoUtil.Sample sample) {
|
private static MediaSource buildMediaSource(CastDemoUtil.Sample sample) {
|
||||||
Uri uri = Uri.parse(sample.uri);
|
Uri uri = Uri.parse(sample.uri);
|
||||||
switch (sample.type) {
|
switch (sample.mimeType) {
|
||||||
case CastDemoUtil.MIME_TYPE_SS:
|
case CastDemoUtil.MIME_TYPE_SS:
|
||||||
return new SsMediaSource(uri, DATA_SOURCE_FACTORY,
|
return new SsMediaSource(uri, DATA_SOURCE_FACTORY,
|
||||||
new DefaultSsChunkSource.Factory(DATA_SOURCE_FACTORY), null, null);
|
new DefaultSsChunkSource.Factory(DATA_SOURCE_FACTORY), null, null);
|
||||||
@ -158,7 +170,7 @@ import com.google.android.gms.cast.framework.CastContext;
|
|||||||
return new ExtractorMediaSource(uri, DATA_SOURCE_FACTORY, new DefaultExtractorsFactory(),
|
return new ExtractorMediaSource(uri, DATA_SOURCE_FACTORY, new DefaultExtractorsFactory(),
|
||||||
null, null);
|
null, null);
|
||||||
default: {
|
default: {
|
||||||
throw new IllegalStateException("Unsupported type: " + sample.type);
|
throw new IllegalStateException("Unsupported type: " + sample.mimeType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -177,14 +189,16 @@ import com.google.android.gms.cast.framework.CastContext;
|
|||||||
castControlView.show();
|
castControlView.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
long playbackPositionMs = 0;
|
long playbackPositionMs;
|
||||||
boolean playWhenReady = true;
|
boolean playWhenReady;
|
||||||
if (exoPlayer != null) {
|
if (this.playbackLocation == PLAYBACK_LOCAL) {
|
||||||
playbackPositionMs = exoPlayer.getCurrentPosition();
|
playbackPositionMs = exoPlayer.getCurrentPosition();
|
||||||
playWhenReady = exoPlayer.getPlayWhenReady();
|
playWhenReady = exoPlayer.getPlayWhenReady();
|
||||||
} else if (this.playbackLocation == PLAYBACK_REMOTE) {
|
exoPlayer.stop();
|
||||||
|
} else /* this.playbackLocation == PLAYBACK_REMOTE */ {
|
||||||
playbackPositionMs = castPlayer.getCurrentPosition();
|
playbackPositionMs = castPlayer.getCurrentPosition();
|
||||||
playWhenReady = castPlayer.getPlayWhenReady();
|
playWhenReady = castPlayer.getPlayWhenReady();
|
||||||
|
castPlayer.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.playbackLocation = playbackLocation;
|
this.playbackLocation = playbackLocation;
|
||||||
|
@ -35,7 +35,8 @@
|
|||||||
android:id="@+id/cast_control_view"
|
android:id="@+id/cast_control_view"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
app:show_timeout="-1"
|
|
||||||
android:layout_weight="2"
|
android:layout_weight="2"
|
||||||
android:visibility="gone"/>
|
android:visibility="gone"
|
||||||
|
app:repeat_toggle_modes="all|one"
|
||||||
|
app:show_timeout="-1"/>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
@ -15,23 +15,24 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.ext.cast;
|
package com.google.android.exoplayer2.ext.cast;
|
||||||
|
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.PlaybackParameters;
|
import com.google.android.exoplayer2.PlaybackParameters;
|
||||||
import com.google.android.exoplayer2.Player;
|
import com.google.android.exoplayer2.Player;
|
||||||
import com.google.android.exoplayer2.Timeline;
|
import com.google.android.exoplayer2.Timeline;
|
||||||
import com.google.android.exoplayer2.source.SinglePeriodTimeline;
|
|
||||||
import com.google.android.exoplayer2.source.TrackGroup;
|
import com.google.android.exoplayer2.source.TrackGroup;
|
||||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||||
import com.google.android.exoplayer2.trackselection.FixedTrackSelection;
|
import com.google.android.exoplayer2.trackselection.FixedTrackSelection;
|
||||||
import com.google.android.exoplayer2.trackselection.TrackSelection;
|
import com.google.android.exoplayer2.trackselection.TrackSelection;
|
||||||
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
|
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
|
||||||
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
import com.google.android.exoplayer2.util.MimeTypes;
|
import com.google.android.exoplayer2.util.MimeTypes;
|
||||||
import com.google.android.exoplayer2.util.Util;
|
import com.google.android.exoplayer2.util.Util;
|
||||||
import com.google.android.gms.cast.CastStatusCodes;
|
import com.google.android.gms.cast.CastStatusCodes;
|
||||||
import com.google.android.gms.cast.MediaInfo;
|
import com.google.android.gms.cast.MediaInfo;
|
||||||
import com.google.android.gms.cast.MediaMetadata;
|
import com.google.android.gms.cast.MediaQueueItem;
|
||||||
import com.google.android.gms.cast.MediaStatus;
|
import com.google.android.gms.cast.MediaStatus;
|
||||||
import com.google.android.gms.cast.MediaTrack;
|
import com.google.android.gms.cast.MediaTrack;
|
||||||
import com.google.android.gms.cast.framework.CastContext;
|
import com.google.android.gms.cast.framework.CastContext;
|
||||||
@ -41,6 +42,7 @@ import com.google.android.gms.cast.framework.SessionManagerListener;
|
|||||||
import com.google.android.gms.cast.framework.media.RemoteMediaClient;
|
import com.google.android.gms.cast.framework.media.RemoteMediaClient;
|
||||||
import com.google.android.gms.cast.framework.media.RemoteMediaClient.MediaChannelResult;
|
import com.google.android.gms.cast.framework.media.RemoteMediaClient.MediaChannelResult;
|
||||||
import com.google.android.gms.common.api.CommonStatusCodes;
|
import com.google.android.gms.common.api.CommonStatusCodes;
|
||||||
|
import com.google.android.gms.common.api.PendingResult;
|
||||||
import com.google.android.gms.common.api.ResultCallback;
|
import com.google.android.gms.common.api.ResultCallback;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.CopyOnWriteArraySet;
|
import java.util.concurrent.CopyOnWriteArraySet;
|
||||||
@ -48,19 +50,16 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
|||||||
/**
|
/**
|
||||||
* {@link Player} implementation that communicates with a Cast receiver app.
|
* {@link Player} implementation that communicates with a Cast receiver app.
|
||||||
*
|
*
|
||||||
* <p>Calls to the methods in this class depend on the availability of an underlying cast session.
|
* <p>The behavior of this class depends on the underlying Cast session, which is obtained from the
|
||||||
* If no session is available, method calls have no effect. To keep track of the underyling session,
|
* Cast context passed to {@link #CastPlayer}. To keep track of the session,
|
||||||
* {@link #isCastSessionAvailable()} can be queried and {@link SessionAvailabilityListener} can be
|
* {@link #isCastSessionAvailable()} can be queried and {@link SessionAvailabilityListener} can be
|
||||||
* implemented and attached to the player.
|
* implemented and attached to the player.</p>
|
||||||
*
|
*
|
||||||
* <p>Methods should be called on the application's main thread.
|
* <p> If no session is available, the player state will remain unchanged and calls to methods that
|
||||||
|
* alter it will be ignored. Querying the player state is possible even when no session is
|
||||||
|
* available, in which case, the last observed receiver app state is reported.</p>
|
||||||
*
|
*
|
||||||
* <p>Known issues:
|
* <p>Methods should be called on the application's main thread.</p>
|
||||||
* <ul>
|
|
||||||
* <li>Part of the Cast API is not exposed through this interface. For instance, volume settings
|
|
||||||
* and track selection.</li>
|
|
||||||
* <li> Repeat mode is not working. See [internal: b/64137174].</li>
|
|
||||||
* </ul>
|
|
||||||
*/
|
*/
|
||||||
public final class CastPlayer implements Player {
|
public final class CastPlayer implements Player {
|
||||||
|
|
||||||
@ -95,10 +94,12 @@ public final class CastPlayer implements Player {
|
|||||||
|
|
||||||
private final CastContext castContext;
|
private final CastContext castContext;
|
||||||
private final Timeline.Window window;
|
private final Timeline.Window window;
|
||||||
|
private final Timeline.Period period;
|
||||||
|
|
||||||
|
private RemoteMediaClient remoteMediaClient;
|
||||||
|
|
||||||
// Result callbacks.
|
// Result callbacks.
|
||||||
private final StatusListener statusListener;
|
private final StatusListener statusListener;
|
||||||
private final RepeatModeResultCallback repeatModeResultCallback;
|
|
||||||
private final SeekResultCallback seekResultCallback;
|
private final SeekResultCallback seekResultCallback;
|
||||||
|
|
||||||
// Listeners.
|
// Listeners.
|
||||||
@ -106,11 +107,15 @@ public final class CastPlayer implements Player {
|
|||||||
private SessionAvailabilityListener sessionAvailabilityListener;
|
private SessionAvailabilityListener sessionAvailabilityListener;
|
||||||
|
|
||||||
// Internal state.
|
// Internal state.
|
||||||
private RemoteMediaClient remoteMediaClient;
|
private CastTimeline currentTimeline;
|
||||||
private Timeline currentTimeline;
|
|
||||||
private TrackGroupArray currentTrackGroups;
|
private TrackGroupArray currentTrackGroups;
|
||||||
private TrackSelectionArray currentTrackSelection;
|
private TrackSelectionArray currentTrackSelection;
|
||||||
|
private int playbackState;
|
||||||
|
private int repeatMode;
|
||||||
|
private int currentWindowIndex;
|
||||||
|
private boolean playWhenReady;
|
||||||
private long lastReportedPositionMs;
|
private long lastReportedPositionMs;
|
||||||
|
private int pendingSeekWindowIndex;
|
||||||
private long pendingSeekPositionMs;
|
private long pendingSeekPositionMs;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -119,41 +124,142 @@ public final class CastPlayer implements Player {
|
|||||||
public CastPlayer(CastContext castContext) {
|
public CastPlayer(CastContext castContext) {
|
||||||
this.castContext = castContext;
|
this.castContext = castContext;
|
||||||
window = new Timeline.Window();
|
window = new Timeline.Window();
|
||||||
|
period = new Timeline.Period();
|
||||||
statusListener = new StatusListener();
|
statusListener = new StatusListener();
|
||||||
repeatModeResultCallback = new RepeatModeResultCallback();
|
|
||||||
seekResultCallback = new SeekResultCallback();
|
seekResultCallback = new SeekResultCallback();
|
||||||
listeners = new CopyOnWriteArraySet<>();
|
listeners = new CopyOnWriteArraySet<>();
|
||||||
|
|
||||||
SessionManager sessionManager = castContext.getSessionManager();
|
SessionManager sessionManager = castContext.getSessionManager();
|
||||||
sessionManager.addSessionManagerListener(statusListener, CastSession.class);
|
sessionManager.addSessionManagerListener(statusListener, CastSession.class);
|
||||||
CastSession session = sessionManager.getCurrentCastSession();
|
CastSession session = sessionManager.getCurrentCastSession();
|
||||||
remoteMediaClient = session != null ? session.getRemoteMediaClient() : null;
|
remoteMediaClient = session != null ? session.getRemoteMediaClient() : null;
|
||||||
|
|
||||||
|
playbackState = STATE_IDLE;
|
||||||
|
repeatMode = REPEAT_MODE_OFF;
|
||||||
|
currentTimeline = CastTimeline.EMPTY_CAST_TIMELINE;
|
||||||
|
currentTrackGroups = EMPTY_TRACK_GROUP_ARRAY;
|
||||||
|
currentTrackSelection = EMPTY_TRACK_SELECTION_ARRAY;
|
||||||
|
pendingSeekWindowIndex = C.INDEX_UNSET;
|
||||||
pendingSeekPositionMs = C.TIME_UNSET;
|
pendingSeekPositionMs = C.TIME_UNSET;
|
||||||
updateInternalState();
|
updateInternalState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Media Queue manipulation methods.
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads media into the receiver app.
|
* Loads a single item media queue. If no session is available, does nothing.
|
||||||
*
|
*
|
||||||
* @param title The title of the media sample.
|
* @param item The item to load.
|
||||||
* @param url The url from which the media is obtained.
|
* @param positionMs The position at which the playback should start in milliseconds relative to
|
||||||
* @param contentMimeType The mime type of the content to play.
|
* the start of the item at {@code startIndex}.
|
||||||
* @param positionMs The position at which the playback should start in milliseconds.
|
* @return The Cast {@code PendingResult}, or null if no session is available.
|
||||||
* @param playWhenReady Whether the player should start playback as soon as it is ready to do so.
|
|
||||||
*/
|
*/
|
||||||
public void load(String title, String url, String contentMimeType, long positionMs,
|
public PendingResult<MediaChannelResult> loadItem(MediaQueueItem item, long positionMs) {
|
||||||
boolean playWhenReady) {
|
return loadItems(new MediaQueueItem[] {item}, 0, positionMs, REPEAT_MODE_OFF);
|
||||||
lastReportedPositionMs = 0;
|
|
||||||
if (remoteMediaClient != null) {
|
|
||||||
MediaMetadata movieMetadata = new MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE);
|
|
||||||
movieMetadata.putString(MediaMetadata.KEY_TITLE, title);
|
|
||||||
MediaInfo mediaInfo = new MediaInfo.Builder(url).setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
|
|
||||||
.setContentType(contentMimeType).setMetadata(movieMetadata).build();
|
|
||||||
remoteMediaClient.load(mediaInfo, playWhenReady, positionMs);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether a cast session is available for playback.
|
* Loads a media queue. If no session is available, does nothing.
|
||||||
|
*
|
||||||
|
* @param items The items to load.
|
||||||
|
* @param startIndex The index of the item at which playback should start.
|
||||||
|
* @param positionMs The position at which the playback should start in milliseconds relative to
|
||||||
|
* the start of the item at {@code startIndex}.
|
||||||
|
* @param repeatMode The repeat mode for the created media queue.
|
||||||
|
* @return The Cast {@code PendingResult}, or null if no session is available.
|
||||||
|
*/
|
||||||
|
public PendingResult<MediaChannelResult> loadItems(MediaQueueItem[] items, int startIndex,
|
||||||
|
long positionMs, @RepeatMode int repeatMode) {
|
||||||
|
if (remoteMediaClient != null) {
|
||||||
|
return remoteMediaClient.queueLoad(items, startIndex, getCastRepeatMode(repeatMode),
|
||||||
|
positionMs, null);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends a sequence of items to the media queue. If no media queue exists, does nothing.
|
||||||
|
*
|
||||||
|
* @param items The items to append.
|
||||||
|
* @return The Cast {@code PendingResult}, or null if no media queue exists.
|
||||||
|
*/
|
||||||
|
public PendingResult<MediaChannelResult> addItems(MediaQueueItem... items) {
|
||||||
|
return addItems(MediaQueueItem.INVALID_ITEM_ID, items);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inserts a sequence of items into the media queue. If no media queue or period with id
|
||||||
|
* {@code periodId} exist, does nothing.
|
||||||
|
*
|
||||||
|
* @param periodId The id of the period ({@see #getCurrentTimeline}) that corresponds to the item
|
||||||
|
* that will follow immediately after the inserted items.
|
||||||
|
* @param items The items to insert.
|
||||||
|
* @return The Cast {@code PendingResult}, or null if no media queue or no period with id
|
||||||
|
* {@code periodId} exist.
|
||||||
|
*/
|
||||||
|
public PendingResult<MediaChannelResult> addItems(int periodId, MediaQueueItem... items) {
|
||||||
|
if (getMediaStatus() != null && (periodId == MediaQueueItem.INVALID_ITEM_ID
|
||||||
|
|| currentTimeline.getIndexOfPeriod(periodId) != C.INDEX_UNSET)) {
|
||||||
|
return remoteMediaClient.queueInsertItems(items, periodId, null);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes an item from the media queue. If no media queue or period with id {@code periodId}
|
||||||
|
* exist, does nothing.
|
||||||
|
*
|
||||||
|
* @param periodId The id of the period ({@see #getCurrentTimeline}) that corresponds to the item
|
||||||
|
* to remove.
|
||||||
|
* @return The Cast {@code PendingResult}, or null if no media queue or no period with id
|
||||||
|
* {@code periodId} exist.
|
||||||
|
*/
|
||||||
|
public PendingResult<MediaChannelResult> removeItem(int periodId) {
|
||||||
|
if (getMediaStatus() != null && currentTimeline.getIndexOfPeriod(periodId) != C.INDEX_UNSET) {
|
||||||
|
return remoteMediaClient.queueRemoveItem(periodId, null);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moves an existing item within the media queue. If no media queue or period with id
|
||||||
|
* {@code periodId} exist, does nothing.
|
||||||
|
*
|
||||||
|
* @param periodId The id of the period ({@see #getCurrentTimeline}) that corresponds to the item
|
||||||
|
* to move.
|
||||||
|
* @param newIndex The target index of the item in the media queue. Must be in the range
|
||||||
|
* 0 <= index < {@link Timeline#getPeriodCount()}, as provided by
|
||||||
|
* {@link #getCurrentTimeline()}.
|
||||||
|
* @return The Cast {@code PendingResult}, or null if no media queue or no period with id
|
||||||
|
* {@code periodId} exist.
|
||||||
|
*/
|
||||||
|
public PendingResult<MediaChannelResult> moveItem(int periodId, int newIndex) {
|
||||||
|
Assertions.checkArgument(newIndex >= 0 && newIndex < currentTimeline.getPeriodCount());
|
||||||
|
if (getMediaStatus() != null && currentTimeline.getIndexOfPeriod(periodId) != C.INDEX_UNSET) {
|
||||||
|
return remoteMediaClient.queueMoveItemToNewIndex(periodId, newIndex, null);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the item that corresponds to the period with the given id, or null if no media queue or
|
||||||
|
* period with id {@code periodId} exist.
|
||||||
|
*
|
||||||
|
* @param periodId The id of the period ({@see #getCurrentTimeline}) that corresponds to the item
|
||||||
|
* to get.
|
||||||
|
* @return The item that corresponds to the period with the given id, or null if no media queue or
|
||||||
|
* period with id {@code periodId} exist.
|
||||||
|
*/
|
||||||
|
public MediaQueueItem getItem(int periodId) {
|
||||||
|
MediaStatus mediaStatus = getMediaStatus();
|
||||||
|
return mediaStatus != null && currentTimeline.getIndexOfPeriod(periodId) != C.INDEX_UNSET
|
||||||
|
? mediaStatus.getItemById(periodId) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// CastSession methods.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether a cast session is available.
|
||||||
*/
|
*/
|
||||||
public boolean isCastSessionAvailable() {
|
public boolean isCastSessionAvailable() {
|
||||||
return remoteMediaClient != null;
|
return remoteMediaClient != null;
|
||||||
@ -182,21 +288,7 @@ public final class CastPlayer implements Player {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getPlaybackState() {
|
public int getPlaybackState() {
|
||||||
if (remoteMediaClient == null) {
|
return playbackState;
|
||||||
return STATE_IDLE;
|
|
||||||
}
|
|
||||||
int receiverAppStatus = remoteMediaClient.getPlayerState();
|
|
||||||
switch (receiverAppStatus) {
|
|
||||||
case MediaStatus.PLAYER_STATE_BUFFERING:
|
|
||||||
return STATE_BUFFERING;
|
|
||||||
case MediaStatus.PLAYER_STATE_PLAYING:
|
|
||||||
case MediaStatus.PLAYER_STATE_PAUSED:
|
|
||||||
return STATE_READY;
|
|
||||||
case MediaStatus.PLAYER_STATE_IDLE:
|
|
||||||
case MediaStatus.PLAYER_STATE_UNKNOWN:
|
|
||||||
default:
|
|
||||||
return STATE_IDLE;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -213,7 +305,7 @@ public final class CastPlayer implements Player {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean getPlayWhenReady() {
|
public boolean getPlayWhenReady() {
|
||||||
return remoteMediaClient != null && !remoteMediaClient.isPaused();
|
return playWhenReady;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -228,13 +320,20 @@ public final class CastPlayer implements Player {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void seekTo(long positionMs) {
|
public void seekTo(long positionMs) {
|
||||||
seekTo(0, positionMs);
|
seekTo(getCurrentWindowIndex(), positionMs);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void seekTo(int windowIndex, long positionMs) {
|
public void seekTo(int windowIndex, long positionMs) {
|
||||||
if (remoteMediaClient != null) {
|
MediaStatus mediaStatus = getMediaStatus();
|
||||||
remoteMediaClient.seek(positionMs).setResultCallback(seekResultCallback);
|
if (mediaStatus != null) {
|
||||||
|
if (getCurrentWindowIndex() != windowIndex) {
|
||||||
|
remoteMediaClient.queueJumpToItem((int) currentTimeline.getPeriod(windowIndex, period).uid,
|
||||||
|
positionMs, null).setResultCallback(seekResultCallback);
|
||||||
|
} else {
|
||||||
|
remoteMediaClient.seek(positionMs).setResultCallback(seekResultCallback);
|
||||||
|
}
|
||||||
|
pendingSeekWindowIndex = windowIndex;
|
||||||
pendingSeekPositionMs = positionMs;
|
pendingSeekPositionMs = positionMs;
|
||||||
for (EventListener listener : listeners) {
|
for (EventListener listener : listeners) {
|
||||||
listener.onPositionDiscontinuity();
|
listener.onPositionDiscontinuity();
|
||||||
@ -287,47 +386,13 @@ public final class CastPlayer implements Player {
|
|||||||
@Override
|
@Override
|
||||||
public void setRepeatMode(@RepeatMode int repeatMode) {
|
public void setRepeatMode(@RepeatMode int repeatMode) {
|
||||||
if (remoteMediaClient != null) {
|
if (remoteMediaClient != null) {
|
||||||
int castRepeatMode;
|
remoteMediaClient.queueSetRepeatMode(getCastRepeatMode(repeatMode), null);
|
||||||
switch (repeatMode) {
|
|
||||||
case REPEAT_MODE_ONE:
|
|
||||||
castRepeatMode = MediaStatus.REPEAT_MODE_REPEAT_SINGLE;
|
|
||||||
break;
|
|
||||||
case REPEAT_MODE_ALL:
|
|
||||||
castRepeatMode = MediaStatus.REPEAT_MODE_REPEAT_ALL;
|
|
||||||
break;
|
|
||||||
case REPEAT_MODE_OFF:
|
|
||||||
castRepeatMode = MediaStatus.REPEAT_MODE_REPEAT_OFF;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new IllegalArgumentException();
|
|
||||||
}
|
|
||||||
remoteMediaClient.queueSetRepeatMode(castRepeatMode, null)
|
|
||||||
.setResultCallback(repeatModeResultCallback);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@RepeatMode public int getRepeatMode() {
|
@RepeatMode public int getRepeatMode() {
|
||||||
if (remoteMediaClient == null) {
|
return repeatMode;
|
||||||
return REPEAT_MODE_OFF;
|
|
||||||
}
|
|
||||||
MediaStatus mediaStatus = getMediaStatus();
|
|
||||||
if (mediaStatus == null) {
|
|
||||||
// No media session active, yet.
|
|
||||||
return REPEAT_MODE_OFF;
|
|
||||||
}
|
|
||||||
int castRepeatMode = mediaStatus.getQueueRepeatMode();
|
|
||||||
switch (castRepeatMode) {
|
|
||||||
case MediaStatus.REPEAT_MODE_REPEAT_SINGLE:
|
|
||||||
return REPEAT_MODE_ONE;
|
|
||||||
case MediaStatus.REPEAT_MODE_REPEAT_ALL:
|
|
||||||
case MediaStatus.REPEAT_MODE_REPEAT_ALL_AND_SHUFFLE:
|
|
||||||
return REPEAT_MODE_ALL;
|
|
||||||
case MediaStatus.REPEAT_MODE_REPEAT_OFF:
|
|
||||||
return REPEAT_MODE_OFF;
|
|
||||||
default:
|
|
||||||
throw new IllegalStateException();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -363,12 +428,12 @@ public final class CastPlayer implements Player {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getCurrentPeriodIndex() {
|
public int getCurrentPeriodIndex() {
|
||||||
return 0;
|
return getCurrentWindowIndex();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getCurrentWindowIndex() {
|
public int getCurrentWindowIndex() {
|
||||||
return 0;
|
return pendingSeekWindowIndex != C.INDEX_UNSET ? pendingSeekWindowIndex : currentWindowIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -384,14 +449,14 @@ public final class CastPlayer implements Player {
|
|||||||
@Override
|
@Override
|
||||||
public long getDuration() {
|
public long getDuration() {
|
||||||
return currentTimeline.isEmpty() ? C.TIME_UNSET
|
return currentTimeline.isEmpty() ? C.TIME_UNSET
|
||||||
: currentTimeline.getWindow(0, window).getDurationMs();
|
: currentTimeline.getWindow(getCurrentWindowIndex(), window).getDurationMs();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getCurrentPosition() {
|
public long getCurrentPosition() {
|
||||||
return remoteMediaClient == null ? lastReportedPositionMs
|
return pendingSeekPositionMs != C.TIME_UNSET ? pendingSeekPositionMs
|
||||||
: pendingSeekPositionMs != C.TIME_UNSET ? pendingSeekPositionMs
|
: remoteMediaClient != null ? remoteMediaClient.getApproximateStreamPosition()
|
||||||
: remoteMediaClient.getApproximateStreamPosition();
|
: lastReportedPositionMs;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -447,6 +512,121 @@ public final class CastPlayer implements Player {
|
|||||||
|
|
||||||
// Internal methods.
|
// Internal methods.
|
||||||
|
|
||||||
|
public void updateInternalState() {
|
||||||
|
if (remoteMediaClient == null) {
|
||||||
|
// There is no session. We leave the state of the player as it is now.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int playbackState = fetchPlaybackState(remoteMediaClient);
|
||||||
|
boolean playWhenReady = !remoteMediaClient.isPaused();
|
||||||
|
if (this.playbackState != playbackState
|
||||||
|
|| this.playWhenReady != playWhenReady) {
|
||||||
|
this.playbackState = playbackState;
|
||||||
|
this.playWhenReady = playWhenReady;
|
||||||
|
for (EventListener listener : listeners) {
|
||||||
|
listener.onPlayerStateChanged(this.playWhenReady, this.playbackState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@RepeatMode int repeatMode = fetchRepeatMode(remoteMediaClient);
|
||||||
|
if (this.repeatMode != repeatMode) {
|
||||||
|
this.repeatMode = repeatMode;
|
||||||
|
for (EventListener listener : listeners) {
|
||||||
|
listener.onRepeatModeChanged(repeatMode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int currentWindowIndex = fetchCurrentWindowIndex(getMediaStatus());
|
||||||
|
if (this.currentWindowIndex != currentWindowIndex) {
|
||||||
|
this.currentWindowIndex = currentWindowIndex;
|
||||||
|
for (EventListener listener : listeners) {
|
||||||
|
listener.onPositionDiscontinuity();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (updateTracksAndSelections()) {
|
||||||
|
for (EventListener listener : listeners) {
|
||||||
|
listener.onTracksChanged(currentTrackGroups, currentTrackSelection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
maybeUpdateTimelineAndNotify();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void maybeUpdateTimelineAndNotify() {
|
||||||
|
if (updateTimeline()) {
|
||||||
|
for (EventListener listener : listeners) {
|
||||||
|
listener.onTimelineChanged(currentTimeline, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the current timeline and returns whether it has changed.
|
||||||
|
*/
|
||||||
|
private boolean updateTimeline() {
|
||||||
|
MediaStatus mediaStatus = getMediaStatus();
|
||||||
|
if (mediaStatus == null) {
|
||||||
|
boolean hasChanged = currentTimeline != CastTimeline.EMPTY_CAST_TIMELINE;
|
||||||
|
currentTimeline = CastTimeline.EMPTY_CAST_TIMELINE;
|
||||||
|
return hasChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<MediaQueueItem> items = mediaStatus.getQueueItems();
|
||||||
|
if (!currentTimeline.represents(items)) {
|
||||||
|
currentTimeline = !items.isEmpty() ? new CastTimeline(mediaStatus.getQueueItems())
|
||||||
|
: CastTimeline.EMPTY_CAST_TIMELINE;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the internal tracks and selection and returns whether they have changed.
|
||||||
|
*/
|
||||||
|
private boolean updateTracksAndSelections() {
|
||||||
|
if (remoteMediaClient == null) {
|
||||||
|
// There is no session. We leave the state of the player as it is now.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
MediaStatus mediaStatus = getMediaStatus();
|
||||||
|
MediaInfo mediaInfo = mediaStatus != null ? mediaStatus.getMediaInfo() : null;
|
||||||
|
List<MediaTrack> castMediaTracks = mediaInfo != null ? mediaInfo.getMediaTracks() : null;
|
||||||
|
if (castMediaTracks == null || castMediaTracks.isEmpty()) {
|
||||||
|
boolean hasChanged = currentTrackGroups != EMPTY_TRACK_GROUP_ARRAY;
|
||||||
|
currentTrackGroups = EMPTY_TRACK_GROUP_ARRAY;
|
||||||
|
currentTrackSelection = EMPTY_TRACK_SELECTION_ARRAY;
|
||||||
|
return hasChanged;
|
||||||
|
}
|
||||||
|
long[] activeTrackIds = mediaStatus.getActiveTrackIds();
|
||||||
|
if (activeTrackIds == null) {
|
||||||
|
activeTrackIds = EMPTY_TRACK_ID_ARRAY;
|
||||||
|
}
|
||||||
|
|
||||||
|
TrackGroup[] trackGroups = new TrackGroup[castMediaTracks.size()];
|
||||||
|
TrackSelection[] trackSelections = new TrackSelection[RENDERER_COUNT];
|
||||||
|
for (int i = 0; i < castMediaTracks.size(); i++) {
|
||||||
|
MediaTrack mediaTrack = castMediaTracks.get(i);
|
||||||
|
trackGroups[i] = new TrackGroup(CastUtils.mediaTrackToFormat(mediaTrack));
|
||||||
|
|
||||||
|
long id = mediaTrack.getId();
|
||||||
|
int trackType = MimeTypes.getTrackType(mediaTrack.getContentType());
|
||||||
|
int rendererIndex = getRendererIndexForTrackType(trackType);
|
||||||
|
if (isTrackActive(id, activeTrackIds) && rendererIndex != C.INDEX_UNSET
|
||||||
|
&& trackSelections[rendererIndex] == null) {
|
||||||
|
trackSelections[rendererIndex] = new FixedTrackSelection(trackGroups[i], 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TrackGroupArray newTrackGroups = new TrackGroupArray(trackGroups);
|
||||||
|
TrackSelectionArray newTrackSelections = new TrackSelectionArray(trackSelections);
|
||||||
|
|
||||||
|
if (!newTrackGroups.equals(currentTrackGroups)
|
||||||
|
|| !newTrackSelections.equals(currentTrackSelection)) {
|
||||||
|
currentTrackSelection = new TrackSelectionArray(trackSelections);
|
||||||
|
currentTrackGroups = new TrackGroupArray(trackGroups);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private void setRemoteMediaClient(@Nullable RemoteMediaClient remoteMediaClient) {
|
private void setRemoteMediaClient(@Nullable RemoteMediaClient remoteMediaClient) {
|
||||||
if (this.remoteMediaClient == remoteMediaClient) {
|
if (this.remoteMediaClient == remoteMediaClient) {
|
||||||
// Do nothing.
|
// Do nothing.
|
||||||
@ -463,6 +643,7 @@ public final class CastPlayer implements Player {
|
|||||||
}
|
}
|
||||||
remoteMediaClient.addListener(statusListener);
|
remoteMediaClient.addListener(statusListener);
|
||||||
remoteMediaClient.addProgressListener(statusListener, PROGRESS_REPORT_PERIOD_MS);
|
remoteMediaClient.addProgressListener(statusListener, PROGRESS_REPORT_PERIOD_MS);
|
||||||
|
updateInternalState();
|
||||||
} else {
|
} else {
|
||||||
if (sessionAvailabilityListener != null) {
|
if (sessionAvailabilityListener != null) {
|
||||||
sessionAvailabilityListener.onCastSessionUnavailable();
|
sessionAvailabilityListener.onCastSessionUnavailable();
|
||||||
@ -474,50 +655,58 @@ public final class CastPlayer implements Player {
|
|||||||
return remoteMediaClient != null ? remoteMediaClient.getMediaStatus() : null;
|
return remoteMediaClient != null ? remoteMediaClient.getMediaStatus() : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private @Nullable MediaInfo getMediaInfo() {
|
/**
|
||||||
return remoteMediaClient != null ? remoteMediaClient.getMediaInfo() : null;
|
* Retrieves the playback state from {@code remoteMediaClient} and maps it into a {@link Player}
|
||||||
|
* state
|
||||||
|
*/
|
||||||
|
private static int fetchPlaybackState(RemoteMediaClient remoteMediaClient) {
|
||||||
|
int receiverAppStatus = remoteMediaClient.getPlayerState();
|
||||||
|
switch (receiverAppStatus) {
|
||||||
|
case MediaStatus.PLAYER_STATE_BUFFERING:
|
||||||
|
return STATE_BUFFERING;
|
||||||
|
case MediaStatus.PLAYER_STATE_PLAYING:
|
||||||
|
case MediaStatus.PLAYER_STATE_PAUSED:
|
||||||
|
return STATE_READY;
|
||||||
|
case MediaStatus.PLAYER_STATE_IDLE:
|
||||||
|
case MediaStatus.PLAYER_STATE_UNKNOWN:
|
||||||
|
default:
|
||||||
|
return STATE_IDLE;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateInternalState() {
|
/**
|
||||||
currentTimeline = Timeline.EMPTY;
|
* Retrieves the repeat mode from {@code remoteMediaClient} and maps it into a
|
||||||
currentTrackGroups = EMPTY_TRACK_GROUP_ARRAY;
|
* {@link Player.RepeatMode}.
|
||||||
currentTrackSelection = EMPTY_TRACK_SELECTION_ARRAY;
|
*/
|
||||||
MediaInfo mediaInfo = getMediaInfo();
|
@RepeatMode
|
||||||
if (mediaInfo == null) {
|
private static int fetchRepeatMode(RemoteMediaClient remoteMediaClient) {
|
||||||
return;
|
MediaStatus mediaStatus = remoteMediaClient.getMediaStatus();
|
||||||
|
if (mediaStatus == null) {
|
||||||
|
// No media session active, yet.
|
||||||
|
return REPEAT_MODE_OFF;
|
||||||
}
|
}
|
||||||
long streamDurationMs = mediaInfo.getStreamDuration();
|
int castRepeatMode = mediaStatus.getQueueRepeatMode();
|
||||||
boolean isSeekable = streamDurationMs != MediaInfo.UNKNOWN_DURATION;
|
switch (castRepeatMode) {
|
||||||
currentTimeline = new SinglePeriodTimeline(
|
case MediaStatus.REPEAT_MODE_REPEAT_SINGLE:
|
||||||
isSeekable ? C.msToUs(streamDurationMs) : C.TIME_UNSET, isSeekable);
|
return REPEAT_MODE_ONE;
|
||||||
|
case MediaStatus.REPEAT_MODE_REPEAT_ALL:
|
||||||
List<MediaTrack> tracks = mediaInfo.getMediaTracks();
|
case MediaStatus.REPEAT_MODE_REPEAT_ALL_AND_SHUFFLE:
|
||||||
if (tracks == null) {
|
return REPEAT_MODE_ALL;
|
||||||
return;
|
case MediaStatus.REPEAT_MODE_REPEAT_OFF:
|
||||||
|
return REPEAT_MODE_OFF;
|
||||||
|
default:
|
||||||
|
throw new IllegalStateException();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
MediaStatus mediaStatus = getMediaStatus();
|
/**
|
||||||
long[] activeTrackIds = mediaStatus != null ? mediaStatus.getActiveTrackIds() : null;
|
* Retrieves the current item index from {@code mediaStatus} and maps it into a window index. If
|
||||||
if (activeTrackIds == null) {
|
* there is no media session, returns 0.
|
||||||
activeTrackIds = EMPTY_TRACK_ID_ARRAY;
|
*/
|
||||||
}
|
private static int fetchCurrentWindowIndex(@Nullable MediaStatus mediaStatus) {
|
||||||
|
Integer currentItemId = mediaStatus != null
|
||||||
TrackGroup[] trackGroups = new TrackGroup[tracks.size()];
|
? mediaStatus.getIndexById(mediaStatus.getCurrentItemId()) : null;
|
||||||
TrackSelection[] trackSelections = new TrackSelection[RENDERER_COUNT];
|
return currentItemId != null ? currentItemId : 0;
|
||||||
for (int i = 0; i < tracks.size(); i++) {
|
|
||||||
MediaTrack mediaTrack = tracks.get(i);
|
|
||||||
trackGroups[i] = new TrackGroup(CastUtils.mediaTrackToFormat(mediaTrack));
|
|
||||||
|
|
||||||
long id = mediaTrack.getId();
|
|
||||||
int trackType = MimeTypes.getTrackType(mediaTrack.getContentType());
|
|
||||||
int rendererIndex = getRendererIndexForTrackType(trackType);
|
|
||||||
if (isTrackActive(id, activeTrackIds) && rendererIndex != C.INDEX_UNSET
|
|
||||||
&& trackSelections[rendererIndex] == null) {
|
|
||||||
trackSelections[rendererIndex] = new FixedTrackSelection(trackGroups[i], 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
currentTrackSelection = new TrackSelectionArray(trackSelections);
|
|
||||||
currentTrackGroups = new TrackGroupArray(trackGroups);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isTrackActive(long id, long[] activeTrackIds) {
|
private static boolean isTrackActive(long id, long[] activeTrackIds) {
|
||||||
@ -536,6 +725,19 @@ public final class CastPlayer implements Player {
|
|||||||
: C.INDEX_UNSET;
|
: C.INDEX_UNSET;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static int getCastRepeatMode(@RepeatMode int repeatMode) {
|
||||||
|
switch (repeatMode) {
|
||||||
|
case REPEAT_MODE_ONE:
|
||||||
|
return MediaStatus.REPEAT_MODE_REPEAT_SINGLE;
|
||||||
|
case REPEAT_MODE_ALL:
|
||||||
|
return MediaStatus.REPEAT_MODE_REPEAT_ALL;
|
||||||
|
case REPEAT_MODE_OFF:
|
||||||
|
return MediaStatus.REPEAT_MODE_REPEAT_OFF;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private final class StatusListener implements RemoteMediaClient.Listener,
|
private final class StatusListener implements RemoteMediaClient.Listener,
|
||||||
SessionManagerListener<CastSession>, RemoteMediaClient.ProgressListener {
|
SessionManagerListener<CastSession>, RemoteMediaClient.ProgressListener {
|
||||||
|
|
||||||
@ -550,24 +752,16 @@ public final class CastPlayer implements Player {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStatusUpdated() {
|
public void onStatusUpdated() {
|
||||||
boolean playWhenReady = getPlayWhenReady();
|
|
||||||
int playbackState = getPlaybackState();
|
|
||||||
for (EventListener listener : listeners) {
|
|
||||||
listener.onPlayerStateChanged(playWhenReady, playbackState);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onMetadataUpdated() {
|
|
||||||
updateInternalState();
|
updateInternalState();
|
||||||
for (EventListener listener : listeners) {
|
|
||||||
listener.onTracksChanged(currentTrackGroups, currentTrackSelection);
|
|
||||||
listener.onTimelineChanged(currentTimeline, null);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onQueueStatusUpdated() {}
|
public void onMetadataUpdated() {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onQueueStatusUpdated() {
|
||||||
|
maybeUpdateTimelineAndNotify();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPreloadStatusUpdated() {}
|
public void onPreloadStatusUpdated() {}
|
||||||
@ -632,36 +826,20 @@ public final class CastPlayer implements Player {
|
|||||||
|
|
||||||
// Result callbacks hooks.
|
// Result callbacks hooks.
|
||||||
|
|
||||||
private final class RepeatModeResultCallback implements ResultCallback<MediaChannelResult> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onResult(MediaChannelResult result) {
|
|
||||||
int statusCode = result.getStatus().getStatusCode();
|
|
||||||
if (statusCode == CommonStatusCodes.SUCCESS) {
|
|
||||||
int repeatMode = getRepeatMode();
|
|
||||||
for (EventListener listener : listeners) {
|
|
||||||
listener.onRepeatModeChanged(repeatMode);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Log.e(TAG, "Set repeat mode failed. Error code " + statusCode + ": "
|
|
||||||
+ CastUtils.getLogString(statusCode));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private final class SeekResultCallback implements ResultCallback<MediaChannelResult> {
|
private final class SeekResultCallback implements ResultCallback<MediaChannelResult> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onResult(MediaChannelResult result) {
|
public void onResult(@NonNull MediaChannelResult result) {
|
||||||
int statusCode = result.getStatus().getStatusCode();
|
int statusCode = result.getStatus().getStatusCode();
|
||||||
if (statusCode == CommonStatusCodes.SUCCESS) {
|
if (statusCode == CastStatusCodes.REPLACED) {
|
||||||
pendingSeekPositionMs = C.TIME_UNSET;
|
|
||||||
} else if (statusCode == CastStatusCodes.REPLACED) {
|
|
||||||
// A seek was executed before this one completed. Do nothing.
|
// A seek was executed before this one completed. Do nothing.
|
||||||
} else {
|
} else {
|
||||||
Log.e(TAG, "Seek failed. Error code " + statusCode + ": "
|
pendingSeekWindowIndex = C.INDEX_UNSET;
|
||||||
+ CastUtils.getLogString(statusCode));
|
pendingSeekPositionMs = C.TIME_UNSET;
|
||||||
|
if (statusCode != CommonStatusCodes.SUCCESS) {
|
||||||
|
Log.e(TAG, "Seek failed. Error code " + statusCode + ": "
|
||||||
|
+ CastUtils.getLogString(statusCode));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,114 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2017 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.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package com.google.android.exoplayer2.ext.cast;
|
||||||
|
|
||||||
|
import android.util.SparseIntArray;
|
||||||
|
import com.google.android.exoplayer2.C;
|
||||||
|
import com.google.android.exoplayer2.Timeline;
|
||||||
|
import com.google.android.gms.cast.MediaInfo;
|
||||||
|
import com.google.android.gms.cast.MediaQueueItem;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link Timeline} for Cast media queues.
|
||||||
|
*/
|
||||||
|
/* package */ final class CastTimeline extends Timeline {
|
||||||
|
|
||||||
|
public static final CastTimeline EMPTY_CAST_TIMELINE =
|
||||||
|
new CastTimeline(Collections.<MediaQueueItem>emptyList());
|
||||||
|
|
||||||
|
private final SparseIntArray idsToIndex;
|
||||||
|
private final int[] ids;
|
||||||
|
private final long[] durationsUs;
|
||||||
|
private final long[] defaultPositionsUs;
|
||||||
|
|
||||||
|
public CastTimeline(List<MediaQueueItem> items) {
|
||||||
|
int itemCount = items.size();
|
||||||
|
int index = 0;
|
||||||
|
idsToIndex = new SparseIntArray(itemCount);
|
||||||
|
ids = new int[itemCount];
|
||||||
|
durationsUs = new long[itemCount];
|
||||||
|
defaultPositionsUs = new long[itemCount];
|
||||||
|
for (MediaQueueItem item : items) {
|
||||||
|
int itemId = item.getItemId();
|
||||||
|
ids[index] = itemId;
|
||||||
|
idsToIndex.put(itemId, index);
|
||||||
|
durationsUs[index] = getStreamDurationUs(item.getMedia());
|
||||||
|
defaultPositionsUs[index] = (long) (item.getStartTime() * C.MICROS_PER_SECOND);
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getWindowCount() {
|
||||||
|
return ids.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Window getWindow(int windowIndex, Window window, boolean setIds,
|
||||||
|
long defaultPositionProjectionUs) {
|
||||||
|
long durationUs = durationsUs[windowIndex];
|
||||||
|
boolean isDynamic = durationUs == C.TIME_UNSET;
|
||||||
|
return window.set(ids[windowIndex], C.TIME_UNSET, C.TIME_UNSET, !isDynamic, isDynamic,
|
||||||
|
defaultPositionsUs[windowIndex], durationUs, windowIndex, windowIndex, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPeriodCount() {
|
||||||
|
return ids.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Period getPeriod(int periodIndex, Period period, boolean setIds) {
|
||||||
|
int id = ids[periodIndex];
|
||||||
|
return period.set(id, id, periodIndex, durationsUs[periodIndex], 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getIndexOfPeriod(Object uid) {
|
||||||
|
return uid instanceof Integer ? idsToIndex.get((int) uid, C.INDEX_UNSET) : C.INDEX_UNSET;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the timeline represents a given {@code MediaQueueItem} list.
|
||||||
|
*
|
||||||
|
* @param items The {@code MediaQueueItem} list.
|
||||||
|
* @return Whether the timeline represents {@code items}.
|
||||||
|
*/
|
||||||
|
/* package */ boolean represents(List<MediaQueueItem> items) {
|
||||||
|
if (ids.length != items.size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
int index = 0;
|
||||||
|
for (MediaQueueItem item : items) {
|
||||||
|
if (ids[index] != item.getItemId()
|
||||||
|
|| durationsUs[index] != getStreamDurationUs(item.getMedia())
|
||||||
|
|| defaultPositionsUs[index] != (long) (item.getStartTime() * C.MICROS_PER_SECOND)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long getStreamDurationUs(MediaInfo mediaInfo) {
|
||||||
|
long durationMs = mediaInfo != null ? mediaInfo.getStreamDuration()
|
||||||
|
: MediaInfo.UNKNOWN_DURATION;
|
||||||
|
return durationMs != MediaInfo.UNKNOWN_DURATION ? C.msToUs(durationMs) : C.TIME_UNSET;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -198,7 +198,7 @@ public final class DynamicConcatenatingMediaSource implements MediaSource, ExoPl
|
|||||||
/**
|
/**
|
||||||
* Returns the {@link MediaSource} at a specified index.
|
* Returns the {@link MediaSource} at a specified index.
|
||||||
*
|
*
|
||||||
* @param index A index in the range of 0 <= index <= {@link #getSize()}.
|
* @param index An index in the range of 0 <= index <= {@link #getSize()}.
|
||||||
* @return The {@link MediaSource} at this index.
|
* @return The {@link MediaSource} at this index.
|
||||||
*/
|
*/
|
||||||
public synchronized MediaSource getMediaSource(int index) {
|
public synchronized MediaSource getMediaSource(int index) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user