/*
* Copyright 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.
* 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.media2.widget;
import android.view.Surface;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.util.ObjectsCompat;
import androidx.media2.common.BaseResult;
import androidx.media2.common.MediaItem;
import androidx.media2.common.MediaMetadata;
import androidx.media2.common.SessionPlayer;
import androidx.media2.common.SessionPlayer.TrackInfo;
import androidx.media2.common.SubtitleData;
import androidx.media2.common.VideoSize;
import androidx.media2.session.MediaController;
import androidx.media2.session.SessionCommand;
import androidx.media2.session.SessionCommandGroup;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Executor;
/**
* Wrapper for MediaController and SessionPlayer
*/
class PlayerWrapper {
final MediaController mController;
final SessionPlayer mPlayer;
private final Executor mCallbackExecutor;
@SuppressWarnings("WeakerAccess") /* synthetic access */
final PlayerCallback mWrapperCallback;
private final MediaControllerCallback mControllerCallback;
private final SessionPlayerCallback mPlayerCallback;
private boolean mCallbackAttached;
// cached states
@SuppressWarnings("WeakerAccess") /* synthetic access */
int mSavedPlayerState = SessionPlayer.PLAYER_STATE_IDLE;
@SuppressWarnings("WeakerAccess") /* synthetic access */
SessionCommandGroup mAllowedCommands;
@SuppressWarnings("WeakerAccess") /* synthetic access */
MediaMetadata mMediaMetadata;
private final SessionCommandGroup mAllCommands;
PlayerWrapper(@NonNull MediaController controller, @NonNull Executor executor,
@NonNull PlayerCallback callback) {
if (controller == null) throw new NullPointerException("controller must not be null");
if (executor == null) throw new NullPointerException("executor must not be null");
if (callback == null) throw new NullPointerException("callback must not be null");
mController = controller;
mCallbackExecutor = executor;
mWrapperCallback = callback;
mControllerCallback = new MediaControllerCallback();
mPlayer = null;
mPlayerCallback = null;
mAllCommands = null;
}
PlayerWrapper(@NonNull SessionPlayer player, @NonNull Executor executor,
@NonNull PlayerCallback callback) {
if (player == null) throw new NullPointerException("player must not be null");
if (executor == null) throw new NullPointerException("executor must not be null");
if (callback == null) throw new NullPointerException("callback must not be null");
mPlayer = player;
mCallbackExecutor = executor;
mWrapperCallback = callback;
mPlayerCallback = new SessionPlayerCallback();
mController = null;
mControllerCallback = null;
mAllCommands = new SessionCommandGroup.Builder()
.addAllPredefinedCommands(SessionCommand.COMMAND_VERSION_1)
.build();
}
boolean hasDisconnectedController() {
return mController != null && !mController.isConnected();
}
void attachCallback() {
if (mCallbackAttached) return;
if (mController != null) {
mController.registerExtraCallback(mCallbackExecutor, mControllerCallback);
} else if (mPlayer != null) {
mPlayer.registerPlayerCallback(mCallbackExecutor, mPlayerCallback);
}
updateAndNotifyCachedStates();
mCallbackAttached = true;
}
void detachCallback() {
if (!mCallbackAttached) return;
if (mController != null) {
mController.unregisterExtraCallback(mControllerCallback);
} else if (mPlayer != null) {
mPlayer.unregisterPlayerCallback(mPlayerCallback);
}
mCallbackAttached = false;
}
boolean isPlaying() {
return mSavedPlayerState == SessionPlayer.PLAYER_STATE_PLAYING;
}
long getCurrentPosition() {
if (mSavedPlayerState == SessionPlayer.PLAYER_STATE_IDLE) {
return 0;
}
long position = 0;
if (mController != null) {
position = mController.getCurrentPosition();
} else if (mPlayer != null) {
position = mPlayer.getCurrentPosition();
}
return (position < 0) ? 0 : position;
}
long getBufferPercentage() {
if (mSavedPlayerState == SessionPlayer.PLAYER_STATE_IDLE) {
return 0;
}
long duration = getDurationMs();
if (duration == 0) return 0;
long bufferedPos = 0;
if (mController != null) {
bufferedPos = mController.getBufferedPosition();
} else if (mPlayer != null) {
bufferedPos = mPlayer.getBufferedPosition();
}
return (bufferedPos < 0) ? 0 : (bufferedPos * 100 / duration);
}
int getPlayerState() {
if (mController != null) {
return mController.getPlayerState();
} else if (mPlayer != null) {
return mPlayer.getPlayerState();
}
return SessionPlayer.PLAYER_STATE_IDLE;
}
boolean canPause() {
return mAllowedCommands != null && mAllowedCommands.hasCommand(
SessionCommand.COMMAND_CODE_PLAYER_PAUSE);
}
boolean canSeekBackward() {
return mAllowedCommands != null && mAllowedCommands.hasCommand(
SessionCommand.COMMAND_CODE_SESSION_REWIND);
}
boolean canSeekForward() {
return mAllowedCommands != null && mAllowedCommands.hasCommand(
SessionCommand.COMMAND_CODE_SESSION_FAST_FORWARD);
}
boolean canSkipToNext() {
return mAllowedCommands != null && mAllowedCommands.hasCommand(
SessionCommand.COMMAND_CODE_PLAYER_SKIP_TO_NEXT_PLAYLIST_ITEM);
}
boolean canSkipToPrevious() {
return mAllowedCommands != null && mAllowedCommands.hasCommand(
SessionCommand.COMMAND_CODE_PLAYER_SKIP_TO_PREVIOUS_PLAYLIST_ITEM);
}
boolean canSeekTo() {
return mAllowedCommands != null && mAllowedCommands.hasCommand(
SessionCommand.COMMAND_CODE_PLAYER_SEEK_TO);
}
boolean canSelectDeselectTrack() {
return mAllowedCommands != null
&& mAllowedCommands.hasCommand(SessionCommand.COMMAND_CODE_PLAYER_SELECT_TRACK)
&& mAllowedCommands.hasCommand(SessionCommand.COMMAND_CODE_PLAYER_DESELECT_TRACK);
}
// TODO(b/138091975) Do not ignore the returned Future.
@SuppressWarnings("FutureReturnValueIgnored")
void pause() {
if (mController != null) {
mController.pause();
} else if (mPlayer != null) {
mPlayer.pause();
}
}
// TODO(b/138091975) Do not ignore the returned Future.
@SuppressWarnings("FutureReturnValueIgnored")
void play() {
if (mController != null) {
mController.play();
} else if (mPlayer != null) {
mPlayer.play();
}
}
// TODO(b/138091975) Do not ignore the returned Future.
@SuppressWarnings("FutureReturnValueIgnored")
void seekTo(long posMs) {
if (mController != null) {
mController.seekTo(posMs);
} else if (mPlayer != null) {
mPlayer.seekTo(posMs);
}
}
// TODO(b/138091975) Do not ignore the returned Future.
@SuppressWarnings("FutureReturnValueIgnored")
void skipToNextItem() {
if (mController != null) {
mController.skipToNextPlaylistItem();
} else if (mPlayer != null) {
mPlayer.skipToNextPlaylistItem();
}
}
// TODO(b/138091975) Do not ignore the returned Future.
@SuppressWarnings("FutureReturnValueIgnored")
void skipToPreviousItem() {
if (mController != null) {
mController.skipToPreviousPlaylistItem();
} else if (mPlayer != null) {
mPlayer.skipToPreviousPlaylistItem();
}
}
private float getPlaybackSpeed() {
if (mController != null) {
return mController.getPlaybackSpeed();
} else if (mPlayer != null) {
return mPlayer.getPlaybackSpeed();
}
return 1f;
}
// TODO(b/138091975) Do not ignore the returned Future.
@SuppressWarnings("FutureReturnValueIgnored")
void setPlaybackSpeed(float speed) {
if (mController != null) {
mController.setPlaybackSpeed(speed);
} else if (mPlayer != null) {
mPlayer.setPlaybackSpeed(speed);
}
}
// TODO(b/138091975) Do not ignore the returned Future.
@SuppressWarnings("FutureReturnValueIgnored")
void selectTrack(TrackInfo trackInfo) {
if (mController != null) {
mController.selectTrack(trackInfo);
} else if (mPlayer != null) {
mPlayer.selectTrack(trackInfo);
}
}
// TODO(b/138091975) Do not ignore the returned Future.
@SuppressWarnings("FutureReturnValueIgnored")
void deselectTrack(TrackInfo trackInfo) {
if (mController != null) {
mController.deselectTrack(trackInfo);
} else if (mPlayer != null) {
mPlayer.deselectTrack(trackInfo);
}
}
long getDurationMs() {
if (mSavedPlayerState == SessionPlayer.PLAYER_STATE_IDLE) {
return 0;
}
long duration = 0;
if (mController != null) {
duration = mController.getDuration();
} else if (mPlayer != null) {
duration = mPlayer.getDuration();
}
return (duration < 0) ? 0 : duration;
}
CharSequence getTitle() {
if (mMediaMetadata != null) {
if (mMediaMetadata.containsKey(MediaMetadata.METADATA_KEY_TITLE)) {
return mMediaMetadata.getText(MediaMetadata.METADATA_KEY_TITLE);
}
}
return null;
}
CharSequence getArtistText() {
if (mMediaMetadata != null) {
if (mMediaMetadata.containsKey(MediaMetadata.METADATA_KEY_ARTIST)) {
return mMediaMetadata.getText(MediaMetadata.METADATA_KEY_ARTIST);
}
}
return null;
}
@Nullable
MediaItem getCurrentMediaItem() {
if (mController != null) {
return mController.getCurrentMediaItem();
} else if (mPlayer != null) {
return mPlayer.getCurrentMediaItem();
}
return null;
}
@Nullable
private SessionCommandGroup getAllowedCommands() {
if (mController != null) {
return mController.getAllowedCommands();
} else if (mPlayer != null) {
// We can assume direct players allow all commands since no MediaSession is involved.
return mAllCommands;
}
return null;
}
@SuppressWarnings("WeakerAccess") /* synthetic access */
void updateAndNotifyCachedStates() {
boolean playerStateChanged = false;
int playerState = getPlayerState();
if (mSavedPlayerState != playerState) {
mSavedPlayerState = playerState;
playerStateChanged = true;
}
boolean allowedCommandsChanged = false;
SessionCommandGroup allowedCommands = getAllowedCommands();
if (!ObjectsCompat.equals(mAllowedCommands, allowedCommands)) {
mAllowedCommands = allowedCommands;
allowedCommandsChanged = true;
}
MediaItem item = getCurrentMediaItem();
mMediaMetadata = item == null ? null : item.getMetadata();
if (playerStateChanged) {
mWrapperCallback.onPlayerStateChanged(this, playerState);
}
if (allowedCommands != null && allowedCommandsChanged) {
mWrapperCallback.onAllowedCommandsChanged(this, allowedCommands);
}
mWrapperCallback.onCurrentMediaItemChanged(this, item);
notifyNonCachedStates();
}
@NonNull
VideoSize getVideoSize() {
if (mController != null) {
return mController.getVideoSize();
} else if (mPlayer != null) {
return mPlayer.getVideoSize();
}
return new VideoSize(0, 0);
}
@NonNull
List<TrackInfo> getTracks() {
if (mController != null) {
return mController.getTracks();
} else if (mPlayer != null) {
return mPlayer.getTracks();
}
return Collections.emptyList();
}
@Nullable
TrackInfo getSelectedTrack(int trackType) {
if (mController != null) {
return mController.getSelectedTrack(trackType);
} else if (mPlayer != null) {
return mPlayer.getSelectedTrack(trackType);
}
return null;
}
ListenableFuture<? extends BaseResult> setSurface(Surface surface) {
if (mController != null) {
return mController.setSurface(surface);
} else if (mPlayer != null) {
return mPlayer.setSurface(surface);
}
return null;
}
int getCurrentMediaItemIndex() {
if (mController != null) {
return mController.getCurrentMediaItemIndex();
} else if (mPlayer != null) {
return mPlayer.getCurrentMediaItemIndex();
}
return SessionPlayer.INVALID_ITEM_INDEX;
}
int getPreviousMediaItemIndex() {
if (mController != null) {
return mController.getPreviousMediaItemIndex();
} else if (mPlayer != null) {
return mPlayer.getPreviousMediaItemIndex();
}
return SessionPlayer.INVALID_ITEM_INDEX;
}
int getNextMediaItemIndex() {
if (mController != null) {
return mController.getNextMediaItemIndex();
} else if (mPlayer != null) {
return mPlayer.getNextMediaItemIndex();
}
return SessionPlayer.INVALID_ITEM_INDEX;
}
private class MediaControllerCallback extends MediaController.ControllerCallback {
MediaControllerCallback() {
}
@Override
public void onConnected(@NonNull MediaController controller,
@NonNull SessionCommandGroup allowedCommands) {
mWrapperCallback.onConnected(PlayerWrapper.this);
updateAndNotifyCachedStates();
}
@Override
public void onAllowedCommandsChanged(@NonNull MediaController controller,
@NonNull SessionCommandGroup commands) {
if (ObjectsCompat.equals(mAllowedCommands, commands)) return;
mAllowedCommands = commands;
mWrapperCallback.onAllowedCommandsChanged(PlayerWrapper.this, commands);
}
@Override
public void onPlayerStateChanged(@NonNull MediaController controller, int state) {
if (mSavedPlayerState == state) return;
mSavedPlayerState = state;
mWrapperCallback.onPlayerStateChanged(PlayerWrapper.this, state);
}
@Override
public void onPlaybackSpeedChanged(@NonNull MediaController controller, float speed) {
mWrapperCallback.onPlaybackSpeedChanged(PlayerWrapper.this, speed);
}
@Override
public void onSeekCompleted(@NonNull MediaController controller, long position) {
mWrapperCallback.onSeekCompleted(PlayerWrapper.this, position);
}
@Override
public void onCurrentMediaItemChanged(@NonNull MediaController controller,
@Nullable MediaItem item) {
mMediaMetadata = item == null ? null : item.getMetadata();
mWrapperCallback.onCurrentMediaItemChanged(PlayerWrapper.this, item);
}
@Override
public void onPlaylistChanged(@NonNull MediaController controller,
@Nullable List<MediaItem> list, @Nullable MediaMetadata metadata) {
mWrapperCallback.onPlaylistChanged(PlayerWrapper.this, list, metadata);
}
@Override
public void onPlaybackCompleted(@NonNull MediaController controller) {
mWrapperCallback.onPlaybackCompleted(PlayerWrapper.this);
}
@Override
public void onVideoSizeChanged(@NonNull MediaController controller,
@NonNull VideoSize videoSize) {
mWrapperCallback.onVideoSizeChanged(PlayerWrapper.this, videoSize);
}
@Override
public void onSubtitleData(@NonNull MediaController controller, @NonNull MediaItem item,
@NonNull TrackInfo track, @NonNull SubtitleData data) {
mWrapperCallback.onSubtitleData(PlayerWrapper.this, item, track, data);
}
@Override
public void onTracksChanged(@NonNull MediaController controller,
@NonNull List<TrackInfo> tracks) {
mWrapperCallback.onTracksChanged(PlayerWrapper.this, tracks);
}
@Override
public void onTrackSelected(@NonNull MediaController controller,
@NonNull TrackInfo trackInfo) {
mWrapperCallback.onTrackSelected(PlayerWrapper.this, trackInfo);
}
@Override
public void onTrackDeselected(@NonNull MediaController controller,
@NonNull TrackInfo trackInfo) {
mWrapperCallback.onTrackDeselected(PlayerWrapper.this, trackInfo);
}
}
private void notifyNonCachedStates() {
mWrapperCallback.onPlaybackSpeedChanged(this, getPlaybackSpeed());
List<TrackInfo> trackInfos = getTracks();
if (trackInfos != null) {
mWrapperCallback.onTracksChanged(PlayerWrapper.this, trackInfos);
}
MediaItem item = getCurrentMediaItem();
if (item != null) {
mWrapperCallback.onVideoSizeChanged(PlayerWrapper.this, getVideoSize());
}
}
private class SessionPlayerCallback extends SessionPlayer.PlayerCallback {
SessionPlayerCallback() {
}
@Override
public void onPlayerStateChanged(@NonNull SessionPlayer player, int playerState) {
if (mSavedPlayerState == playerState) return;
mSavedPlayerState = playerState;
mWrapperCallback.onPlayerStateChanged(PlayerWrapper.this, playerState);
}
@Override
public void onPlaybackSpeedChanged(@NonNull SessionPlayer player, float playbackSpeed) {
mWrapperCallback.onPlaybackSpeedChanged(PlayerWrapper.this, playbackSpeed);
}
@Override
public void onSeekCompleted(@NonNull SessionPlayer player, long position) {
mWrapperCallback.onSeekCompleted(PlayerWrapper.this, position);
}
@Override
public void onCurrentMediaItemChanged(@NonNull SessionPlayer player,
@NonNull MediaItem item) {
mMediaMetadata = item == null ? null : item.getMetadata();
mWrapperCallback.onCurrentMediaItemChanged(PlayerWrapper.this, item);
}
@Override
public void onPlaylistChanged(@NonNull SessionPlayer player, @Nullable List<MediaItem> list,
@Nullable MediaMetadata metadata) {
mWrapperCallback.onPlaylistChanged(PlayerWrapper.this, list, metadata);
}
@Override
public void onPlaybackCompleted(@NonNull SessionPlayer player) {
mWrapperCallback.onPlaybackCompleted(PlayerWrapper.this);
}
@Override
public void onVideoSizeChanged(@NonNull SessionPlayer player, @NonNull VideoSize size) {
mWrapperCallback.onVideoSizeChanged(PlayerWrapper.this, size);
}
@Override
public void onSubtitleData(@NonNull SessionPlayer player, @NonNull MediaItem item,
@NonNull TrackInfo track, @NonNull SubtitleData data) {
mWrapperCallback.onSubtitleData(PlayerWrapper.this, item, track, data);
}
@Override
public void onTracksChanged(@NonNull SessionPlayer player,
@NonNull List<TrackInfo> tracks) {
mWrapperCallback.onTracksChanged(PlayerWrapper.this, tracks);
}
@Override
public void onTrackSelected(@NonNull SessionPlayer player, @NonNull TrackInfo trackInfo) {
mWrapperCallback.onTrackSelected(PlayerWrapper.this, trackInfo);
}
@Override
public void onTrackDeselected(@NonNull SessionPlayer player, @NonNull TrackInfo trackInfo) {
mWrapperCallback.onTrackDeselected(PlayerWrapper.this, trackInfo);
}
}
abstract static class PlayerCallback {
void onConnected(@NonNull PlayerWrapper player) {
}
void onAllowedCommandsChanged(@NonNull PlayerWrapper player,
@NonNull SessionCommandGroup commands) {
}
void onCurrentMediaItemChanged(@NonNull PlayerWrapper player, @Nullable MediaItem item) {
}
void onPlaylistChanged(@NonNull PlayerWrapper player, @Nullable List<MediaItem> list,
@Nullable MediaMetadata metadata) {
}
void onPlayerStateChanged(@NonNull PlayerWrapper player, int state) {
}
void onPlaybackSpeedChanged(@NonNull PlayerWrapper player, float speed) {
}
void onSeekCompleted(@NonNull PlayerWrapper player, long position) {
}
void onPlaybackCompleted(@NonNull PlayerWrapper player) {
}
void onVideoSizeChanged(@NonNull PlayerWrapper player, @NonNull VideoSize videoSize) {
}
void onTracksChanged(@NonNull PlayerWrapper player, @NonNull List<TrackInfo> tracks) {
}
void onTrackSelected(@NonNull PlayerWrapper player, @NonNull TrackInfo trackInfo) {
}
void onTrackDeselected(@NonNull PlayerWrapper player, @NonNull TrackInfo trackInfo) {
}
void onSubtitleData(@NonNull PlayerWrapper player, @NonNull MediaItem item,
@NonNull TrackInfo track, @NonNull SubtitleData data) {
}
}
}