blob: 431b188edd95c5735cc06e77543fb0499fd66980 [file] [log] [blame]
/*
* Copyright 2018 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 androidx.media;
import android.annotation.TargetApi;
import android.os.Build;
import android.util.ArrayMap;
import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.media.MediaPlayerBase.PlayerEventCallback;
import androidx.media.MediaSession2.OnDataSourceMissingHelper;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@TargetApi(Build.VERSION_CODES.KITKAT)
class SessionPlaylistAgentImplBase extends MediaPlaylistAgent {
@VisibleForTesting
static final int END_OF_PLAYLIST = -1;
@VisibleForTesting
static final int NO_VALID_ITEMS = -2;
private final PlayItem mEopPlayItem = new PlayItem(END_OF_PLAYLIST, null);
private final Object mLock = new Object();
private final MediaSession2ImplBase mSession;
private final MyPlayerEventCallback mPlayerCallback;
@GuardedBy("mLock")
private MediaPlayerBase mPlayer;
@GuardedBy("mLock")
private OnDataSourceMissingHelper mDsmHelper;
// TODO: Check if having the same item is okay (b/74090741)
@GuardedBy("mLock")
private ArrayList<MediaItem2> mPlaylist = new ArrayList<>();
@GuardedBy("mLock")
private ArrayList<MediaItem2> mShuffledList = new ArrayList<>();
@GuardedBy("mLock")
private Map<MediaItem2, DataSourceDesc> mItemDsdMap = new ArrayMap<>();
@GuardedBy("mLock")
private MediaMetadata2 mMetadata;
@GuardedBy("mLock")
private int mRepeatMode;
@GuardedBy("mLock")
private int mShuffleMode;
@GuardedBy("mLock")
private PlayItem mCurrent;
// Called on session callback executor.
private class MyPlayerEventCallback extends PlayerEventCallback {
@Override
public void onCurrentDataSourceChanged(@NonNull MediaPlayerBase mpb,
@Nullable DataSourceDesc dsd) {
synchronized (mLock) {
if (mPlayer != mpb) {
return;
}
if (dsd == null && mCurrent != null) {
mCurrent = getNextValidPlayItemLocked(mCurrent.shuffledIdx, 1);
updateCurrentIfNeededLocked();
}
}
}
}
private class PlayItem {
public int shuffledIdx;
public DataSourceDesc dsd;
public MediaItem2 mediaItem;
PlayItem(int shuffledIdx) {
this(shuffledIdx, null);
}
PlayItem(int shuffledIdx, DataSourceDesc dsd) {
this.shuffledIdx = shuffledIdx;
if (shuffledIdx >= 0) {
this.mediaItem = mShuffledList.get(shuffledIdx);
if (dsd == null) {
synchronized (mLock) {
this.dsd = retrieveDataSourceDescLocked(this.mediaItem);
}
} else {
this.dsd = dsd;
}
}
}
@SuppressWarnings("ReferenceEquality")
boolean isValid() {
if (this == mEopPlayItem) {
return true;
}
if (mediaItem == null) {
return false;
}
if (dsd == null) {
return false;
}
if (mediaItem.getDataSourceDesc() != null
&& !mediaItem.getDataSourceDesc().equals(dsd)) {
return false;
}
synchronized (mLock) {
if (shuffledIdx >= mShuffledList.size()) {
return false;
}
if (mediaItem != mShuffledList.get(shuffledIdx)) {
return false;
}
}
return true;
}
}
SessionPlaylistAgentImplBase(@NonNull MediaSession2ImplBase session,
@NonNull MediaPlayerBase player) {
super();
if (session == null) {
throw new IllegalArgumentException("sessionImpl shouldn't be null");
}
if (player == null) {
throw new IllegalArgumentException("player shouldn't be null");
}
mSession = session;
mPlayer = player;
mPlayerCallback = new MyPlayerEventCallback();
mPlayer.registerPlayerEventCallback(mSession.getCallbackExecutor(), mPlayerCallback);
}
public void setPlayer(@NonNull MediaPlayerBase player) {
if (player == null) {
throw new IllegalArgumentException("player shouldn't be null");
}
synchronized (mLock) {
if (player == mPlayer) {
return;
}
mPlayer.unregisterPlayerEventCallback(mPlayerCallback);
mPlayer = player;
mPlayer.registerPlayerEventCallback(mSession.getCallbackExecutor(), mPlayerCallback);
updatePlayerDataSourceLocked();
}
}
public void setOnDataSourceMissingHelper(OnDataSourceMissingHelper helper) {
synchronized (mLock) {
mDsmHelper = helper;
}
}
public void clearOnDataSourceMissingHelper() {
synchronized (mLock) {
mDsmHelper = null;
}
}
@Override
public @Nullable List<MediaItem2> getPlaylist() {
synchronized (mLock) {
return Collections.unmodifiableList(mPlaylist);
}
}
@Override
public void setPlaylist(@NonNull List<MediaItem2> list, @Nullable MediaMetadata2 metadata) {
if (list == null) {
throw new IllegalArgumentException("list shouldn't be null");
}
synchronized (mLock) {
mItemDsdMap.clear();
mPlaylist.clear();
mPlaylist.addAll(list);
applyShuffleModeLocked();
mMetadata = metadata;
mCurrent = getNextValidPlayItemLocked(END_OF_PLAYLIST, 1);
updatePlayerDataSourceLocked();
}
notifyPlaylistChanged();
}
@Override
public @Nullable MediaMetadata2 getPlaylistMetadata() {
synchronized (mLock) {
return mMetadata;
}
}
@Override
public void updatePlaylistMetadata(@Nullable MediaMetadata2 metadata) {
synchronized (mLock) {
if (metadata == mMetadata) {
return;
}
mMetadata = metadata;
}
notifyPlaylistMetadataChanged();
}
@Override
public MediaItem2 getCurrentMediaItem() {
synchronized (mLock) {
return mCurrent == null ? null : mCurrent.mediaItem;
}
}
@Override
public void addPlaylistItem(int index, @NonNull MediaItem2 item) {
if (item == null) {
throw new IllegalArgumentException("item shouldn't be null");
}
synchronized (mLock) {
index = clamp(index, mPlaylist.size());
int shuffledIdx = index;
mPlaylist.add(index, item);
if (mShuffleMode == MediaPlaylistAgent.SHUFFLE_MODE_NONE) {
mShuffledList.add(index, item);
} else {
// Add the item in random position of mShuffledList.
shuffledIdx = (int) (Math.random() * (mShuffledList.size() + 1));
mShuffledList.add(shuffledIdx, item);
}
if (!hasValidItem()) {
mCurrent = getNextValidPlayItemLocked(END_OF_PLAYLIST, 1);
updatePlayerDataSourceLocked();
} else {
updateCurrentIfNeededLocked();
}
}
notifyPlaylistChanged();
}
@Override
public void removePlaylistItem(@NonNull MediaItem2 item) {
if (item == null) {
throw new IllegalArgumentException("item shouldn't be null");
}
synchronized (mLock) {
if (!mPlaylist.remove(item)) {
return;
}
mShuffledList.remove(item);
mItemDsdMap.remove(item);
updateCurrentIfNeededLocked();
}
notifyPlaylistChanged();
}
@Override
public void replacePlaylistItem(int index, @NonNull MediaItem2 item) {
if (item == null) {
throw new IllegalArgumentException("item shouldn't be null");
}
synchronized (mLock) {
if (mPlaylist.size() <= 0) {
return;
}
index = clamp(index, mPlaylist.size() - 1);
int shuffledIdx = mShuffledList.indexOf(mPlaylist.get(index));
mItemDsdMap.remove(mShuffledList.get(shuffledIdx));
mShuffledList.set(shuffledIdx, item);
mPlaylist.set(index, item);
if (!hasValidItem()) {
mCurrent = getNextValidPlayItemLocked(END_OF_PLAYLIST, 1);
updatePlayerDataSourceLocked();
} else {
updateCurrentIfNeededLocked();
}
}
notifyPlaylistChanged();
}
@Override
public void skipToPlaylistItem(@NonNull MediaItem2 item) {
if (item == null) {
throw new IllegalArgumentException("item shouldn't be null");
}
synchronized (mLock) {
if (!hasValidItem() || item.equals(mCurrent.mediaItem)) {
return;
}
int shuffledIdx = mShuffledList.indexOf(item);
if (shuffledIdx < 0) {
return;
}
mCurrent = new PlayItem(shuffledIdx);
updateCurrentIfNeededLocked();
}
}
@Override
public void skipToPreviousItem() {
synchronized (mLock) {
if (!hasValidItem()) {
return;
}
PlayItem prev = getNextValidPlayItemLocked(mCurrent.shuffledIdx, -1);
if (prev != mEopPlayItem) {
mCurrent = prev;
}
updateCurrentIfNeededLocked();
}
}
@Override
public void skipToNextItem() {
synchronized (mLock) {
if (!hasValidItem() || mCurrent == mEopPlayItem) {
return;
}
PlayItem next = getNextValidPlayItemLocked(mCurrent.shuffledIdx, 1);
if (next != mEopPlayItem) {
mCurrent = next;
}
updateCurrentIfNeededLocked();
}
}
@Override
public int getRepeatMode() {
synchronized (mLock) {
return mRepeatMode;
}
}
@Override
@SuppressWarnings("FallThrough")
public void setRepeatMode(int repeatMode) {
if (repeatMode < MediaPlaylistAgent.REPEAT_MODE_NONE
|| repeatMode > MediaPlaylistAgent.REPEAT_MODE_GROUP) {
return;
}
synchronized (mLock) {
if (mRepeatMode == repeatMode) {
return;
}
mRepeatMode = repeatMode;
switch (repeatMode) {
case MediaPlaylistAgent.REPEAT_MODE_ONE:
if (mCurrent != null && mCurrent != mEopPlayItem) {
mPlayer.loopCurrent(true);
}
break;
case MediaPlaylistAgent.REPEAT_MODE_ALL:
case MediaPlaylistAgent.REPEAT_MODE_GROUP:
if (mCurrent == mEopPlayItem) {
mCurrent = getNextValidPlayItemLocked(END_OF_PLAYLIST, 1);
updatePlayerDataSourceLocked();
}
// Fall through
case MediaPlaylistAgent.REPEAT_MODE_NONE:
mPlayer.loopCurrent(false);
break;
}
}
notifyRepeatModeChanged();
}
@Override
public int getShuffleMode() {
synchronized (mLock) {
return mShuffleMode;
}
}
@Override
public void setShuffleMode(int shuffleMode) {
if (shuffleMode < MediaPlaylistAgent.SHUFFLE_MODE_NONE
|| shuffleMode > MediaPlaylistAgent.SHUFFLE_MODE_GROUP) {
return;
}
synchronized (mLock) {
if (mShuffleMode == shuffleMode) {
return;
}
mShuffleMode = shuffleMode;
applyShuffleModeLocked();
updateCurrentIfNeededLocked();
}
notifyShuffleModeChanged();
}
@Override
public MediaItem2 getMediaItem(DataSourceDesc dsd) {
// TODO: implement this
return null;
}
@VisibleForTesting
int getCurShuffledIndex() {
synchronized (mLock) {
return hasValidItem() ? mCurrent.shuffledIdx : NO_VALID_ITEMS;
}
}
private boolean hasValidItem() {
synchronized (mLock) {
return mCurrent != null;
}
}
@SuppressWarnings("GuardedBy")
private DataSourceDesc retrieveDataSourceDescLocked(MediaItem2 item) {
DataSourceDesc dsd = item.getDataSourceDesc();
if (dsd != null) {
mItemDsdMap.put(item, dsd);
return dsd;
}
dsd = mItemDsdMap.get(item);
if (dsd != null) {
return dsd;
}
OnDataSourceMissingHelper helper = mDsmHelper;
if (helper != null) {
// TODO: Do not call onDataSourceMissing with the lock (b/74090741).
dsd = helper.onDataSourceMissing(mSession.getInstance(), item);
if (dsd != null) {
mItemDsdMap.put(item, dsd);
}
}
return dsd;
}
// TODO: consider to call updateCurrentIfNeededLocked inside (b/74090741)
@SuppressWarnings("GuardedBy")
private PlayItem getNextValidPlayItemLocked(int curShuffledIdx, int direction) {
int size = mPlaylist.size();
if (curShuffledIdx == END_OF_PLAYLIST) {
curShuffledIdx = (direction > 0) ? -1 : size;
}
for (int i = 0; i < size; i++) {
curShuffledIdx += direction;
if (curShuffledIdx < 0 || curShuffledIdx >= mPlaylist.size()) {
if (mRepeatMode == REPEAT_MODE_NONE) {
return (i == size - 1) ? null : mEopPlayItem;
} else {
curShuffledIdx = curShuffledIdx < 0 ? mPlaylist.size() - 1 : 0;
}
}
DataSourceDesc dsd = retrieveDataSourceDescLocked(mShuffledList.get(curShuffledIdx));
if (dsd != null) {
return new PlayItem(curShuffledIdx, dsd);
}
}
return null;
}
@SuppressWarnings("GuardedBy")
private void updateCurrentIfNeededLocked() {
if (!hasValidItem() || mCurrent.isValid()) {
return;
}
int shuffledIdx = mShuffledList.indexOf(mCurrent.mediaItem);
if (shuffledIdx >= 0) {
// Added an item.
mCurrent.shuffledIdx = shuffledIdx;
return;
}
if (mCurrent.shuffledIdx >= mShuffledList.size()) {
mCurrent = getNextValidPlayItemLocked(mShuffledList.size() - 1, 1);
} else {
mCurrent.mediaItem = mShuffledList.get(mCurrent.shuffledIdx);
if (retrieveDataSourceDescLocked(mCurrent.mediaItem) == null) {
mCurrent = getNextValidPlayItemLocked(mCurrent.shuffledIdx, 1);
}
}
updatePlayerDataSourceLocked();
return;
}
@SuppressWarnings("GuardedBy")
private void updatePlayerDataSourceLocked() {
if (mCurrent == null || mCurrent == mEopPlayItem) {
return;
}
if (mPlayer.getCurrentDataSource() != mCurrent.dsd) {
mPlayer.setDataSource(mCurrent.dsd);
mPlayer.loopCurrent(mRepeatMode == MediaPlaylistAgent.REPEAT_MODE_ONE);
}
// TODO: Call setNextDataSource (b/74090741)
}
@SuppressWarnings("GuardedBy")
private void applyShuffleModeLocked() {
mShuffledList.clear();
mShuffledList.addAll(mPlaylist);
if (mShuffleMode == MediaPlaylistAgent.SHUFFLE_MODE_ALL
|| mShuffleMode == MediaPlaylistAgent.SHUFFLE_MODE_GROUP) {
Collections.shuffle(mShuffledList);
}
}
// Clamps value to [0, size]
private static int clamp(int value, int size) {
if (value < 0) {
return 0;
}
return (value > size) ? size : value;
}
}