| /* |
| * 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 static androidx.media.MediaPlayerBase.BUFFERING_STATE_UNKNOWN; |
| import static androidx.media.MediaSession2.ControllerInfo; |
| import static androidx.media.MediaSession2.ErrorCode; |
| import static androidx.media.MediaSession2.OnDataSourceMissingHelper; |
| import static androidx.media.MediaSession2.SessionCallback; |
| import static androidx.media.SessionToken2.TYPE_LIBRARY_SERVICE; |
| import static androidx.media.SessionToken2.TYPE_SESSION; |
| import static androidx.media.SessionToken2.TYPE_SESSION_SERVICE; |
| |
| import android.annotation.TargetApi; |
| import android.app.PendingIntent; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.pm.PackageManager; |
| import android.content.pm.ResolveInfo; |
| import android.media.AudioFocusRequest; |
| import android.media.AudioManager; |
| import android.os.Build; |
| import android.os.Bundle; |
| import android.os.Handler; |
| import android.os.HandlerThread; |
| import android.os.Process; |
| import android.os.ResultReceiver; |
| import android.support.v4.media.session.MediaSessionCompat; |
| import android.support.v4.media.session.PlaybackStateCompat; |
| import android.text.TextUtils; |
| import android.util.Log; |
| |
| import androidx.annotation.GuardedBy; |
| import androidx.annotation.NonNull; |
| import androidx.annotation.Nullable; |
| import androidx.media.MediaController2.PlaybackInfo; |
| import androidx.media.MediaPlayerBase.PlayerEventCallback; |
| import androidx.media.MediaPlaylistAgent.PlaylistEventCallback; |
| |
| import java.lang.ref.WeakReference; |
| import java.util.List; |
| import java.util.NoSuchElementException; |
| import java.util.concurrent.Executor; |
| import java.util.concurrent.RejectedExecutionException; |
| |
| @TargetApi(Build.VERSION_CODES.KITKAT) |
| class MediaSession2ImplBase extends MediaSession2.SupportLibraryImpl { |
| static final String TAG = "MS2ImplBase"; |
| static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); |
| |
| private final Object mLock = new Object(); |
| |
| private final Context mContext; |
| private final HandlerThread mHandlerThread; |
| private final Handler mHandler; |
| private final MediaSessionCompat mSessionCompat; |
| private final MediaSession2StubImplBase mSession2Stub; |
| private final String mId; |
| private final Executor mCallbackExecutor; |
| private final SessionCallback mCallback; |
| private final SessionToken2 mSessionToken; |
| private final AudioManager mAudioManager; |
| private final MediaPlayerBase.PlayerEventCallback mPlayerEventCallback; |
| private final MediaPlaylistAgent.PlaylistEventCallback mPlaylistEventCallback; |
| |
| private WeakReference<MediaSession2> mInstance; |
| |
| @GuardedBy("mLock") |
| private MediaPlayerBase mPlayer; |
| @GuardedBy("mLock") |
| private MediaPlaylistAgent mPlaylistAgent; |
| @GuardedBy("mLock") |
| private SessionPlaylistAgentImplBase mSessionPlaylistAgent; |
| @GuardedBy("mLock") |
| private VolumeProviderCompat mVolumeProvider; |
| @GuardedBy("mLock") |
| private OnDataSourceMissingHelper mDsmHelper; |
| @GuardedBy("mLock") |
| private PlaybackStateCompat mPlaybackStateCompat; |
| @GuardedBy("mLock") |
| private PlaybackInfo mPlaybackInfo; |
| |
| MediaSession2ImplBase(Context context, MediaSessionCompat sessionCompat, String id, |
| MediaPlayerBase player, MediaPlaylistAgent playlistAgent, |
| VolumeProviderCompat volumeProvider, PendingIntent sessionActivity, |
| Executor callbackExecutor, SessionCallback callback) { |
| mContext = context; |
| mHandlerThread = new HandlerThread("MediaController2_Thread"); |
| mHandlerThread.start(); |
| mHandler = new Handler(mHandlerThread.getLooper()); |
| |
| mSessionCompat = sessionCompat; |
| mSession2Stub = new MediaSession2StubImplBase(this); |
| mSessionCompat.setCallback(mSession2Stub, mHandler); |
| mSessionCompat.setSessionActivity(sessionActivity); |
| |
| mId = id; |
| mCallback = callback; |
| mCallbackExecutor = callbackExecutor; |
| mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); |
| |
| // TODO: Set callback values properly |
| mPlayerEventCallback = new MyPlayerEventCallback(this); |
| mPlaylistEventCallback = new MyPlaylistEventCallback(this); |
| |
| // Infer type from the id and package name. |
| String libraryService = getServiceName(context, MediaLibraryService2.SERVICE_INTERFACE, id); |
| String sessionService = getServiceName(context, MediaSessionService2.SERVICE_INTERFACE, id); |
| if (sessionService != null && libraryService != null) { |
| throw new IllegalArgumentException("Ambiguous session type. Multiple" |
| + " session services define the same id=" + id); |
| } else if (libraryService != null) { |
| mSessionToken = new SessionToken2(Process.myUid(), TYPE_LIBRARY_SERVICE, |
| context.getPackageName(), libraryService, id, mSessionCompat.getSessionToken()); |
| } else if (sessionService != null) { |
| mSessionToken = new SessionToken2(Process.myUid(), TYPE_SESSION_SERVICE, |
| context.getPackageName(), sessionService, id, mSessionCompat.getSessionToken()); |
| } else { |
| mSessionToken = new SessionToken2(Process.myUid(), TYPE_SESSION, |
| context.getPackageName(), null, id, mSessionCompat.getSessionToken()); |
| } |
| updatePlayer(player, playlistAgent, volumeProvider); |
| } |
| |
| @Override |
| public void updatePlayer(@NonNull MediaPlayerBase player, |
| @Nullable MediaPlaylistAgent playlistAgent, |
| @Nullable VolumeProviderCompat volumeProvider) { |
| if (player == null) { |
| throw new IllegalArgumentException("player shouldn't be null"); |
| } |
| final MediaPlayerBase oldPlayer; |
| final MediaPlaylistAgent oldAgent; |
| final PlaybackInfo info = createPlaybackInfo(volumeProvider, player.getAudioAttributes()); |
| synchronized (mLock) { |
| oldPlayer = mPlayer; |
| oldAgent = mPlaylistAgent; |
| mPlayer = player; |
| if (playlistAgent == null) { |
| mSessionPlaylistAgent = new SessionPlaylistAgentImplBase(this, mPlayer); |
| if (mDsmHelper != null) { |
| mSessionPlaylistAgent.setOnDataSourceMissingHelper(mDsmHelper); |
| } |
| playlistAgent = mSessionPlaylistAgent; |
| } |
| mPlaylistAgent = playlistAgent; |
| mVolumeProvider = volumeProvider; |
| mPlaybackInfo = info; |
| } |
| if (player != oldPlayer) { |
| player.registerPlayerEventCallback(mCallbackExecutor, mPlayerEventCallback); |
| if (oldPlayer != null) { |
| // Warning: Poorly implement player may ignore this |
| oldPlayer.unregisterPlayerEventCallback(mPlayerEventCallback); |
| } |
| } |
| if (playlistAgent != oldAgent) { |
| playlistAgent.registerPlaylistEventCallback(mCallbackExecutor, mPlaylistEventCallback); |
| if (oldAgent != null) { |
| // Warning: Poorly implement player may ignore this |
| oldAgent.unregisterPlaylistEventCallback(mPlaylistEventCallback); |
| } |
| } |
| |
| if (oldPlayer != null) { |
| mSession2Stub.notifyPlaybackInfoChanged(info); |
| notifyPlayerUpdatedNotLocked(oldPlayer); |
| } |
| // TODO(jaewan): Repeat the same thing for the playlist agent. |
| } |
| |
| private PlaybackInfo createPlaybackInfo(VolumeProviderCompat volumeProvider, |
| AudioAttributesCompat attrs) { |
| PlaybackInfo info; |
| if (volumeProvider == null) { |
| int stream; |
| if (attrs == null) { |
| stream = AudioManager.STREAM_MUSIC; |
| } else { |
| stream = attrs.getVolumeControlStream(); |
| if (stream == AudioManager.USE_DEFAULT_STREAM_TYPE) { |
| // It may happen if the AudioAttributes doesn't have usage. |
| // Change it to the STREAM_MUSIC because it's not supported by audio manager |
| // for querying volume level. |
| stream = AudioManager.STREAM_MUSIC; |
| } |
| } |
| |
| int controlType = VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE; |
| if (Build.VERSION.SDK_INT >= 21 && mAudioManager.isVolumeFixed()) { |
| controlType = VolumeProviderCompat.VOLUME_CONTROL_FIXED; |
| } |
| info = PlaybackInfo.createPlaybackInfo( |
| PlaybackInfo.PLAYBACK_TYPE_LOCAL, |
| attrs, |
| controlType, |
| mAudioManager.getStreamMaxVolume(stream), |
| mAudioManager.getStreamVolume(stream)); |
| } else { |
| info = PlaybackInfo.createPlaybackInfo( |
| PlaybackInfo.PLAYBACK_TYPE_REMOTE, |
| attrs, |
| volumeProvider.getVolumeControl(), |
| volumeProvider.getMaxVolume(), |
| volumeProvider.getCurrentVolume()); |
| } |
| return info; |
| } |
| |
| @Override |
| public void close() { |
| synchronized (mLock) { |
| if (mPlayer == null) { |
| return; |
| } |
| mPlayer.unregisterPlayerEventCallback(mPlayerEventCallback); |
| mPlayer = null; |
| mSessionCompat.release(); |
| mHandler.removeCallbacksAndMessages(null); |
| if (mHandlerThread.isAlive()) { |
| mHandlerThread.quitSafely(); |
| } |
| } |
| } |
| |
| @Override |
| public @NonNull MediaPlayerBase getPlayer() { |
| synchronized (mLock) { |
| return mPlayer; |
| } |
| } |
| |
| @Override |
| public @NonNull MediaPlaylistAgent getPlaylistAgent() { |
| synchronized (mLock) { |
| return mPlaylistAgent; |
| } |
| } |
| |
| @Override |
| public @Nullable VolumeProviderCompat getVolumeProvider() { |
| synchronized (mLock) { |
| return mVolumeProvider; |
| } |
| } |
| |
| @Override |
| public @NonNull SessionToken2 getToken() { |
| return mSessionToken; |
| } |
| |
| @Override |
| public @NonNull List<MediaSession2.ControllerInfo> getConnectedControllers() { |
| return mSession2Stub.getConnectedControllers(); |
| } |
| |
| @Override |
| public void setAudioFocusRequest(@Nullable AudioFocusRequest afr) { |
| // TODO(jaewan): implement this (b/72529899) |
| // mProvider.setAudioFocusRequest_impl(focusGain); |
| } |
| |
| @Override |
| public void setCustomLayout(@NonNull ControllerInfo controller, |
| @NonNull List<MediaSession2.CommandButton> layout) { |
| if (controller == null) { |
| throw new IllegalArgumentException("controller shouldn't be null"); |
| } |
| if (layout == null) { |
| throw new IllegalArgumentException("layout shouldn't be null"); |
| } |
| mSession2Stub.notifyCustomLayout(controller, layout); |
| } |
| |
| @Override |
| public void setAllowedCommands(@NonNull ControllerInfo controller, |
| @NonNull SessionCommandGroup2 commands) { |
| if (controller == null) { |
| throw new IllegalArgumentException("controller shouldn't be null"); |
| } |
| if (commands == null) { |
| throw new IllegalArgumentException("commands shouldn't be null"); |
| } |
| mSession2Stub.setAllowedCommands(controller, commands); |
| } |
| |
| @Override |
| public void sendCustomCommand(@NonNull SessionCommand2 command, @Nullable Bundle args) { |
| if (command == null) { |
| throw new IllegalArgumentException("command shouldn't be null"); |
| } |
| mSession2Stub.sendCustomCommand(command, args); |
| } |
| |
| @Override |
| public void sendCustomCommand(@NonNull ControllerInfo controller, |
| @NonNull SessionCommand2 command, @Nullable Bundle args, |
| @Nullable ResultReceiver receiver) { |
| if (controller == null) { |
| throw new IllegalArgumentException("controller shouldn't be null"); |
| } |
| if (command == null) { |
| throw new IllegalArgumentException("command shouldn't be null"); |
| } |
| mSession2Stub.sendCustomCommand(controller, command, args, receiver); |
| } |
| |
| @Override |
| public void play() { |
| MediaPlayerBase player; |
| synchronized (mLock) { |
| player = mPlayer; |
| } |
| if (player != null) { |
| player.play(); |
| } else if (DEBUG) { |
| Log.d(TAG, "API calls after the close()", new IllegalStateException()); |
| } |
| } |
| |
| @Override |
| public void pause() { |
| MediaPlayerBase player; |
| synchronized (mLock) { |
| player = mPlayer; |
| } |
| if (player != null) { |
| player.pause(); |
| } else if (DEBUG) { |
| Log.d(TAG, "API calls after the close()", new IllegalStateException()); |
| } |
| } |
| |
| @Override |
| public void reset() { |
| MediaPlayerBase player; |
| synchronized (mLock) { |
| player = mPlayer; |
| } |
| if (player != null) { |
| player.reset(); |
| } else if (DEBUG) { |
| Log.d(TAG, "API calls after the close()", new IllegalStateException()); |
| } |
| } |
| |
| @Override |
| public void prepare() { |
| MediaPlayerBase player; |
| synchronized (mLock) { |
| player = mPlayer; |
| } |
| if (player != null) { |
| player.prepare(); |
| } else if (DEBUG) { |
| Log.d(TAG, "API calls after the close()", new IllegalStateException()); |
| } |
| } |
| |
| @Override |
| public void seekTo(long pos) { |
| MediaPlayerBase player; |
| synchronized (mLock) { |
| player = mPlayer; |
| } |
| if (player != null) { |
| player.seekTo(pos); |
| } else if (DEBUG) { |
| Log.d(TAG, "API calls after the close()", new IllegalStateException()); |
| } |
| } |
| |
| @Override |
| public void skipForward() { |
| // To match with KEYCODE_MEDIA_SKIP_FORWARD |
| } |
| |
| @Override |
| public void skipBackward() { |
| // To match with KEYCODE_MEDIA_SKIP_BACKWARD |
| } |
| |
| @Override |
| public void notifyError(@ErrorCode int errorCode, @Nullable Bundle extras) { |
| mSession2Stub.notifyError(errorCode, extras); |
| } |
| |
| @Override |
| public void notifyRoutesInfoChanged(@NonNull ControllerInfo controller, |
| @Nullable List<Bundle> routes) { |
| mSession2Stub.notifyRoutesInfoChanged(controller, routes); |
| } |
| |
| @Override |
| public @MediaPlayerBase.PlayerState int getPlayerState() { |
| MediaPlayerBase player; |
| synchronized (mLock) { |
| player = mPlayer; |
| } |
| if (player != null) { |
| return player.getPlayerState(); |
| } else if (DEBUG) { |
| Log.d(TAG, "API calls after the close()", new IllegalStateException()); |
| } |
| return MediaPlayerBase.PLAYER_STATE_ERROR; |
| } |
| |
| @Override |
| public long getCurrentPosition() { |
| MediaPlayerBase player; |
| synchronized (mLock) { |
| player = mPlayer; |
| } |
| if (player != null) { |
| return player.getCurrentPosition(); |
| } else if (DEBUG) { |
| Log.d(TAG, "API calls after the close()", new IllegalStateException()); |
| } |
| return MediaPlayerBase.UNKNOWN_TIME; |
| } |
| |
| @Override |
| public long getDuration() { |
| // TODO: implement |
| return 0; |
| } |
| |
| @Override |
| public long getBufferedPosition() { |
| MediaPlayerBase player; |
| synchronized (mLock) { |
| player = mPlayer; |
| } |
| if (player != null) { |
| return player.getBufferedPosition(); |
| } else if (DEBUG) { |
| Log.d(TAG, "API calls after the close()", new IllegalStateException()); |
| } |
| return MediaPlayerBase.UNKNOWN_TIME; |
| } |
| |
| @Override |
| public @MediaPlayerBase.BuffState int getBufferingState() { |
| MediaPlayerBase player; |
| synchronized (mLock) { |
| player = mPlayer; |
| } |
| if (player != null) { |
| return player.getBufferingState(); |
| } else if (DEBUG) { |
| Log.d(TAG, "API calls after the close()", new IllegalStateException()); |
| } |
| return BUFFERING_STATE_UNKNOWN; |
| } |
| |
| @Override |
| public float getPlaybackSpeed() { |
| MediaPlayerBase player; |
| synchronized (mLock) { |
| player = mPlayer; |
| } |
| if (player != null) { |
| return player.getPlaybackSpeed(); |
| } else if (DEBUG) { |
| Log.d(TAG, "API calls after the close()", new IllegalStateException()); |
| } |
| return 1.0f; |
| } |
| |
| @Override |
| public void setPlaybackSpeed(float speed) { |
| MediaPlayerBase player; |
| synchronized (mLock) { |
| player = mPlayer; |
| } |
| if (player != null) { |
| player.setPlaybackSpeed(speed); |
| } else if (DEBUG) { |
| Log.d(TAG, "API calls after the close()", new IllegalStateException()); |
| } |
| } |
| |
| @Override |
| public void setOnDataSourceMissingHelper( |
| @NonNull OnDataSourceMissingHelper helper) { |
| if (helper == null) { |
| throw new IllegalArgumentException("helper shouldn't be null"); |
| } |
| synchronized (mLock) { |
| mDsmHelper = helper; |
| if (mSessionPlaylistAgent != null) { |
| mSessionPlaylistAgent.setOnDataSourceMissingHelper(helper); |
| } |
| } |
| } |
| |
| @Override |
| public void clearOnDataSourceMissingHelper() { |
| synchronized (mLock) { |
| mDsmHelper = null; |
| if (mSessionPlaylistAgent != null) { |
| mSessionPlaylistAgent.clearOnDataSourceMissingHelper(); |
| } |
| } |
| } |
| |
| @Override |
| public List<MediaItem2> getPlaylist() { |
| MediaPlaylistAgent agent; |
| synchronized (mLock) { |
| agent = mPlaylistAgent; |
| } |
| if (agent != null) { |
| return agent.getPlaylist(); |
| } else if (DEBUG) { |
| Log.d(TAG, "API calls after the close()", new IllegalStateException()); |
| } |
| return null; |
| } |
| |
| @Override |
| public void setPlaylist(@NonNull List<MediaItem2> list, @Nullable MediaMetadata2 metadata) { |
| if (list == null) { |
| throw new IllegalArgumentException("list shouldn't be null"); |
| } |
| MediaPlaylistAgent agent; |
| synchronized (mLock) { |
| agent = mPlaylistAgent; |
| } |
| if (agent != null) { |
| agent.setPlaylist(list, metadata); |
| } else if (DEBUG) { |
| Log.d(TAG, "API calls after the close()", new IllegalStateException()); |
| } |
| } |
| |
| @Override |
| public void skipToPlaylistItem(@NonNull MediaItem2 item) { |
| if (item == null) { |
| throw new IllegalArgumentException("item shouldn't be null"); |
| } |
| MediaPlaylistAgent agent; |
| synchronized (mLock) { |
| agent = mPlaylistAgent; |
| } |
| if (agent != null) { |
| agent.skipToPlaylistItem(item); |
| } else if (DEBUG) { |
| Log.d(TAG, "API calls after the close()", new IllegalStateException()); |
| } |
| } |
| |
| @Override |
| public void skipToPreviousItem() { |
| MediaPlaylistAgent agent; |
| synchronized (mLock) { |
| agent = mPlaylistAgent; |
| } |
| if (agent != null) { |
| agent.skipToPreviousItem(); |
| } else if (DEBUG) { |
| Log.d(TAG, "API calls after the close()", new IllegalStateException()); |
| } |
| } |
| |
| @Override |
| public void skipToNextItem() { |
| MediaPlaylistAgent agent; |
| synchronized (mLock) { |
| agent = mPlaylistAgent; |
| } |
| if (agent != null) { |
| agent.skipToNextItem(); |
| } else if (DEBUG) { |
| Log.d(TAG, "API calls after the close()", new IllegalStateException()); |
| } |
| } |
| |
| @Override |
| public MediaMetadata2 getPlaylistMetadata() { |
| MediaPlaylistAgent agent; |
| synchronized (mLock) { |
| agent = mPlaylistAgent; |
| } |
| if (agent != null) { |
| return agent.getPlaylistMetadata(); |
| } else if (DEBUG) { |
| Log.d(TAG, "API calls after the close()", new IllegalStateException()); |
| } |
| return null; |
| } |
| |
| @Override |
| public void addPlaylistItem(int index, @NonNull MediaItem2 item) { |
| if (index < 0) { |
| throw new IllegalArgumentException("index shouldn't be negative"); |
| } |
| if (item == null) { |
| throw new IllegalArgumentException("item shouldn't be null"); |
| } |
| MediaPlaylistAgent agent; |
| synchronized (mLock) { |
| agent = mPlaylistAgent; |
| } |
| if (agent != null) { |
| agent.addPlaylistItem(index, item); |
| } else if (DEBUG) { |
| Log.d(TAG, "API calls after the close()", new IllegalStateException()); |
| } |
| } |
| |
| @Override |
| public void removePlaylistItem(@NonNull MediaItem2 item) { |
| if (item == null) { |
| throw new IllegalArgumentException("item shouldn't be null"); |
| } |
| MediaPlaylistAgent agent; |
| synchronized (mLock) { |
| agent = mPlaylistAgent; |
| } |
| if (agent != null) { |
| agent.removePlaylistItem(item); |
| } else if (DEBUG) { |
| Log.d(TAG, "API calls after the close()", new IllegalStateException()); |
| } |
| } |
| |
| @Override |
| public void replacePlaylistItem(int index, @NonNull MediaItem2 item) { |
| if (index < 0) { |
| throw new IllegalArgumentException("index shouldn't be negative"); |
| } |
| if (item == null) { |
| throw new IllegalArgumentException("item shouldn't be null"); |
| } |
| MediaPlaylistAgent agent; |
| synchronized (mLock) { |
| agent = mPlaylistAgent; |
| } |
| if (agent != null) { |
| agent.replacePlaylistItem(index, item); |
| } else if (DEBUG) { |
| Log.d(TAG, "API calls after the close()", new IllegalStateException()); |
| } |
| } |
| |
| @Override |
| public MediaItem2 getCurrentMediaItem() { |
| MediaPlaylistAgent agent; |
| synchronized (mLock) { |
| agent = mPlaylistAgent; |
| } |
| if (agent != null) { |
| return agent.getCurrentMediaItem(); |
| } else if (DEBUG) { |
| Log.d(TAG, "API calls after the close()", new IllegalStateException()); |
| } |
| return null; |
| } |
| |
| @Override |
| public void updatePlaylistMetadata(@Nullable MediaMetadata2 metadata) { |
| MediaPlaylistAgent agent; |
| synchronized (mLock) { |
| agent = mPlaylistAgent; |
| } |
| if (agent != null) { |
| agent.updatePlaylistMetadata(metadata); |
| } else if (DEBUG) { |
| Log.d(TAG, "API calls after the close()", new IllegalStateException()); |
| } |
| } |
| |
| @Override |
| public @MediaPlaylistAgent.RepeatMode int getRepeatMode() { |
| MediaPlaylistAgent agent; |
| synchronized (mLock) { |
| agent = mPlaylistAgent; |
| } |
| if (agent != null) { |
| return agent.getRepeatMode(); |
| } else if (DEBUG) { |
| Log.d(TAG, "API calls after the close()", new IllegalStateException()); |
| } |
| return MediaPlaylistAgent.REPEAT_MODE_NONE; |
| } |
| |
| @Override |
| public void setRepeatMode(@MediaPlaylistAgent.RepeatMode int repeatMode) { |
| MediaPlaylistAgent agent; |
| synchronized (mLock) { |
| agent = mPlaylistAgent; |
| } |
| if (agent != null) { |
| agent.setRepeatMode(repeatMode); |
| } else if (DEBUG) { |
| Log.d(TAG, "API calls after the close()", new IllegalStateException()); |
| } |
| } |
| |
| @Override |
| public @MediaPlaylistAgent.ShuffleMode int getShuffleMode() { |
| MediaPlaylistAgent agent; |
| synchronized (mLock) { |
| agent = mPlaylistAgent; |
| } |
| if (agent != null) { |
| return agent.getShuffleMode(); |
| } else if (DEBUG) { |
| Log.d(TAG, "API calls after the close()", new IllegalStateException()); |
| } |
| return MediaPlaylistAgent.SHUFFLE_MODE_NONE; |
| } |
| |
| @Override |
| public void setShuffleMode(int shuffleMode) { |
| MediaPlaylistAgent agent; |
| synchronized (mLock) { |
| agent = mPlaylistAgent; |
| } |
| if (agent != null) { |
| agent.setShuffleMode(shuffleMode); |
| } else if (DEBUG) { |
| Log.d(TAG, "API calls after the close()", new IllegalStateException()); |
| } |
| } |
| |
| /////////////////////////////////////////////////// |
| // package private and private methods |
| /////////////////////////////////////////////////// |
| |
| @Override |
| void setInstance(MediaSession2 session) { |
| mInstance = new WeakReference<>(session); |
| |
| } |
| |
| @Override |
| MediaSession2 getInstance() { |
| return mInstance.get(); |
| } |
| |
| @Override |
| Context getContext() { |
| return mContext; |
| } |
| |
| @Override |
| Executor getCallbackExecutor() { |
| return mCallbackExecutor; |
| } |
| |
| @Override |
| SessionCallback getCallback() { |
| return mCallback; |
| } |
| |
| @Override |
| boolean isClosed() { |
| return !mHandlerThread.isAlive(); |
| } |
| |
| @Override |
| PlaybackStateCompat getPlaybackStateCompat() { |
| synchronized (mLock) { |
| int state = MediaUtils2.createPlaybackStateCompatState(getPlayerState(), |
| getBufferingState()); |
| // TODO: Consider following missing stuff |
| // - setCustomAction(): Fill custom layout |
| // - setErrorMessage(): Fill error message when notifyError() is called. |
| // - setActiveQueueItemId(): Fill here with the current media item... |
| // - setExtra(): No idea at this moment. |
| // TODO: generate actions from the allowed commands. |
| long allActions = PlaybackStateCompat.ACTION_STOP | PlaybackStateCompat.ACTION_PAUSE |
| | PlaybackStateCompat.ACTION_PLAY | PlaybackStateCompat.ACTION_REWIND |
| | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS |
| | PlaybackStateCompat.ACTION_SKIP_TO_NEXT |
| | PlaybackStateCompat.ACTION_FAST_FORWARD |
| | PlaybackStateCompat.ACTION_SET_RATING |
| | PlaybackStateCompat.ACTION_SEEK_TO | PlaybackStateCompat.ACTION_PLAY_PAUSE |
| | PlaybackStateCompat.ACTION_PLAY_FROM_MEDIA_ID |
| | PlaybackStateCompat.ACTION_PLAY_FROM_SEARCH |
| | PlaybackStateCompat.ACTION_SKIP_TO_QUEUE_ITEM |
| | PlaybackStateCompat.ACTION_PLAY_FROM_URI | PlaybackStateCompat.ACTION_PREPARE |
| | PlaybackStateCompat.ACTION_PREPARE_FROM_MEDIA_ID |
| | PlaybackStateCompat.ACTION_PREPARE_FROM_SEARCH |
| | PlaybackStateCompat.ACTION_PREPARE_FROM_URI |
| | PlaybackStateCompat.ACTION_SET_REPEAT_MODE |
| | PlaybackStateCompat.ACTION_SET_SHUFFLE_MODE |
| | PlaybackStateCompat.ACTION_SET_CAPTIONING_ENABLED; |
| return new PlaybackStateCompat.Builder() |
| .setState(state, getCurrentPosition(), getPlaybackSpeed()) |
| .setActions(allActions) |
| .setBufferedPosition(getBufferedPosition()) |
| .build(); |
| } |
| } |
| |
| @Override |
| PlaybackInfo getPlaybackInfo() { |
| synchronized (mLock) { |
| return mPlaybackInfo; |
| } |
| } |
| |
| MediaSession2StubImplBase getSession2Stub() { |
| return mSession2Stub; |
| } |
| |
| private static String getServiceName(Context context, String serviceAction, String id) { |
| PackageManager manager = context.getPackageManager(); |
| Intent serviceIntent = new Intent(serviceAction); |
| serviceIntent.setPackage(context.getPackageName()); |
| List<ResolveInfo> services = manager.queryIntentServices(serviceIntent, |
| PackageManager.GET_META_DATA); |
| String serviceName = null; |
| if (services != null) { |
| for (int i = 0; i < services.size(); i++) { |
| String serviceId = SessionToken2.getSessionId(services.get(i)); |
| if (serviceId != null && TextUtils.equals(id, serviceId)) { |
| if (services.get(i).serviceInfo == null) { |
| continue; |
| } |
| if (serviceName != null) { |
| throw new IllegalArgumentException("Ambiguous session type. Multiple" |
| + " session services define the same id=" + id); |
| } |
| serviceName = services.get(i).serviceInfo.name; |
| } |
| } |
| } |
| return serviceName; |
| } |
| |
| private void notifyPlayerUpdatedNotLocked(MediaPlayerBase oldPlayer) { |
| MediaPlayerBase player; |
| synchronized (mLock) { |
| player = mPlayer; |
| } |
| // TODO(jaewan): (Can be post-P) Find better way for player.getPlayerState() // |
| // In theory, Session.getXXX() may not be the same as Player.getXXX() |
| // and we should notify information of the session.getXXX() instead of |
| // player.getXXX() |
| // Notify to controllers as well. |
| final int state = player.getPlayerState(); |
| if (state != oldPlayer.getPlayerState()) { |
| // TODO: implement |
| mSession2Stub.notifyPlayerStateChanged(state); |
| } |
| |
| final long currentTimeMs = System.currentTimeMillis(); |
| final long position = player.getCurrentPosition(); |
| if (position != oldPlayer.getCurrentPosition()) { |
| // TODO: implement |
| //mSession2Stub.notifyPositionChangedNotLocked(currentTimeMs, position); |
| } |
| |
| final float speed = player.getPlaybackSpeed(); |
| if (speed != oldPlayer.getPlaybackSpeed()) { |
| // TODO: implement |
| //mSession2Stub.notifyPlaybackSpeedChangedNotLocked(speed); |
| } |
| |
| final long bufferedPosition = player.getBufferedPosition(); |
| if (bufferedPosition != oldPlayer.getBufferedPosition()) { |
| // TODO: implement |
| //mSession2Stub.notifyBufferedPositionChangedNotLocked(bufferedPosition); |
| } |
| } |
| |
| private void notifyPlaylistChangedOnExecutor(MediaPlaylistAgent playlistAgent, |
| List<MediaItem2> list, MediaMetadata2 metadata) { |
| synchronized (mLock) { |
| if (playlistAgent != mPlaylistAgent) { |
| // Ignore calls from the old agent. |
| return; |
| } |
| } |
| MediaSession2 session2 = mInstance.get(); |
| if (session2 != null) { |
| mCallback.onPlaylistChanged(session2, playlistAgent, list, metadata); |
| mSession2Stub.notifyPlaylistChanged(list, metadata); |
| } |
| } |
| |
| private void notifyPlaylistMetadataChangedOnExecutor(MediaPlaylistAgent playlistAgent, |
| MediaMetadata2 metadata) { |
| synchronized (mLock) { |
| if (playlistAgent != mPlaylistAgent) { |
| // Ignore calls from the old agent. |
| return; |
| } |
| } |
| MediaSession2 session2 = mInstance.get(); |
| if (session2 != null) { |
| mCallback.onPlaylistMetadataChanged(session2, playlistAgent, metadata); |
| mSession2Stub.notifyPlaylistMetadataChanged(metadata); |
| } |
| } |
| |
| private void notifyRepeatModeChangedOnExecutor(MediaPlaylistAgent playlistAgent, |
| int repeatMode) { |
| synchronized (mLock) { |
| if (playlistAgent != mPlaylistAgent) { |
| // Ignore calls from the old agent. |
| return; |
| } |
| } |
| MediaSession2 session2 = mInstance.get(); |
| if (session2 != null) { |
| mCallback.onRepeatModeChanged(session2, playlistAgent, repeatMode); |
| mSession2Stub.notifyRepeatModeChanged(repeatMode); |
| } |
| } |
| |
| private void notifyShuffleModeChangedOnExecutor(MediaPlaylistAgent playlistAgent, |
| int shuffleMode) { |
| synchronized (mLock) { |
| if (playlistAgent != mPlaylistAgent) { |
| // Ignore calls from the old agent. |
| return; |
| } |
| } |
| MediaSession2 session2 = mInstance.get(); |
| if (session2 != null) { |
| mCallback.onShuffleModeChanged(session2, playlistAgent, shuffleMode); |
| mSession2Stub.notifyShuffleModeChanged(shuffleMode); |
| } |
| } |
| |
| /////////////////////////////////////////////////// |
| // Inner classes |
| /////////////////////////////////////////////////// |
| |
| private static class MyPlayerEventCallback extends PlayerEventCallback { |
| private final WeakReference<MediaSession2ImplBase> mSession; |
| |
| private MyPlayerEventCallback(MediaSession2ImplBase session) { |
| mSession = new WeakReference<>(session); |
| } |
| |
| @Override |
| public void onCurrentDataSourceChanged(final MediaPlayerBase mpb, |
| final DataSourceDesc dsd) { |
| final MediaSession2ImplBase session = getSession(); |
| // TODO: handle properly when dsd == null |
| if (session == null || dsd == null) { |
| return; |
| } |
| session.getCallbackExecutor().execute(new Runnable() { |
| @Override |
| public void run() { |
| MediaItem2 item = MyPlayerEventCallback.this.getMediaItem(session, dsd); |
| if (item == null) { |
| return; |
| } |
| session.getCallback().onCurrentMediaItemChanged(session.getInstance(), mpb, |
| item); |
| if (item.equals(session.getCurrentMediaItem())) { |
| session.getSession2Stub().notifyCurrentMediaItemChanged(item); |
| } |
| } |
| }); |
| } |
| |
| @Override |
| public void onMediaPrepared(final MediaPlayerBase mpb, final DataSourceDesc dsd) { |
| final MediaSession2ImplBase session = getSession(); |
| if (session == null || dsd == null) { |
| return; |
| } |
| session.getCallbackExecutor().execute(new Runnable() { |
| @Override |
| public void run() { |
| MediaItem2 item = MyPlayerEventCallback.this.getMediaItem(session, dsd); |
| if (item == null) { |
| return; |
| } |
| session.getCallback().onMediaPrepared(session.getInstance(), mpb, item); |
| // TODO (jaewan): Notify controllers through appropriate callback. (b/74505936) |
| } |
| }); |
| } |
| |
| @Override |
| public void onPlayerStateChanged(final MediaPlayerBase mpb, final int state) { |
| final MediaSession2ImplBase session = getSession(); |
| if (session == null) { |
| return; |
| } |
| session.getCallbackExecutor().execute(new Runnable() { |
| @Override |
| public void run() { |
| session.getCallback().onPlayerStateChanged(session.getInstance(), mpb, state); |
| session.getSession2Stub().notifyPlayerStateChanged(state); |
| } |
| }); |
| } |
| |
| @Override |
| public void onBufferingStateChanged(final MediaPlayerBase mpb, final DataSourceDesc dsd, |
| final int state) { |
| final MediaSession2ImplBase session = getSession(); |
| if (session == null || dsd == null) { |
| return; |
| } |
| session.getCallbackExecutor().execute(new Runnable() { |
| @Override |
| public void run() { |
| MediaItem2 item = MyPlayerEventCallback.this.getMediaItem(session, dsd); |
| if (item == null) { |
| return; |
| } |
| session.getCallback().onBufferingStateChanged( |
| session.getInstance(), mpb, item, state); |
| session.getSession2Stub().notifyBufferingStateChanged(item, state); |
| } |
| }); |
| } |
| |
| @Override |
| public void onPlaybackSpeedChanged(final MediaPlayerBase mpb, final float speed) { |
| final MediaSession2ImplBase session = getSession(); |
| if (session == null) { |
| return; |
| } |
| session.getCallbackExecutor().execute(new Runnable() { |
| @Override |
| public void run() { |
| session.getCallback().onPlaybackSpeedChanged(session.getInstance(), mpb, speed); |
| session.getSession2Stub().notifyPlaybackSpeedChanged(speed); |
| } |
| }); |
| } |
| |
| private MediaSession2ImplBase getSession() { |
| final MediaSession2ImplBase session = mSession.get(); |
| if (session == null && DEBUG) { |
| Log.d(TAG, "Session is closed", new IllegalStateException()); |
| } |
| return session; |
| } |
| |
| private MediaItem2 getMediaItem(MediaSession2ImplBase session, DataSourceDesc dsd) { |
| MediaPlaylistAgent agent = session.getPlaylistAgent(); |
| if (agent == null) { |
| if (DEBUG) { |
| Log.d(TAG, "Session is closed", new IllegalStateException()); |
| } |
| return null; |
| } |
| MediaItem2 item = agent.getMediaItem(dsd); |
| if (item == null) { |
| if (DEBUG) { |
| Log.d(TAG, "Could not find matching item for dsd=" + dsd, |
| new NoSuchElementException()); |
| } |
| } |
| return item; |
| } |
| } |
| |
| private static class MyPlaylistEventCallback extends PlaylistEventCallback { |
| private final WeakReference<MediaSession2ImplBase> mSession; |
| |
| private MyPlaylistEventCallback(MediaSession2ImplBase session) { |
| mSession = new WeakReference<>(session); |
| } |
| |
| @Override |
| public void onPlaylistChanged(MediaPlaylistAgent playlistAgent, List<MediaItem2> list, |
| MediaMetadata2 metadata) { |
| final MediaSession2ImplBase session = mSession.get(); |
| if (session == null) { |
| return; |
| } |
| session.notifyPlaylistChangedOnExecutor(playlistAgent, list, metadata); |
| } |
| |
| @Override |
| public void onPlaylistMetadataChanged(MediaPlaylistAgent playlistAgent, |
| MediaMetadata2 metadata) { |
| final MediaSession2ImplBase session = mSession.get(); |
| if (session == null) { |
| return; |
| } |
| session.notifyPlaylistMetadataChangedOnExecutor(playlistAgent, metadata); |
| } |
| |
| @Override |
| public void onRepeatModeChanged(MediaPlaylistAgent playlistAgent, int repeatMode) { |
| final MediaSession2ImplBase session = mSession.get(); |
| if (session == null) { |
| return; |
| } |
| session.notifyRepeatModeChangedOnExecutor(playlistAgent, repeatMode); |
| } |
| |
| @Override |
| public void onShuffleModeChanged(MediaPlaylistAgent playlistAgent, int shuffleMode) { |
| final MediaSession2ImplBase session = mSession.get(); |
| if (session == null) { |
| return; |
| } |
| session.notifyShuffleModeChangedOnExecutor(playlistAgent, shuffleMode); |
| } |
| } |
| |
| abstract static class BuilderBase |
| <T extends MediaSession2, C extends SessionCallback> { |
| final Context mContext; |
| MediaPlayerBase mPlayer; |
| String mId; |
| Executor mCallbackExecutor; |
| C mCallback; |
| MediaPlaylistAgent mPlaylistAgent; |
| VolumeProviderCompat mVolumeProvider; |
| PendingIntent mSessionActivity; |
| |
| BuilderBase(Context context) { |
| if (context == null) { |
| throw new IllegalArgumentException("context shouldn't be null"); |
| } |
| mContext = context; |
| // Ensure MediaSessionCompat non-null or empty |
| mId = TAG; |
| } |
| |
| void setPlayer(@NonNull MediaPlayerBase player) { |
| if (player == null) { |
| throw new IllegalArgumentException("player shouldn't be null"); |
| } |
| mPlayer = player; |
| } |
| |
| void setPlaylistAgent(@NonNull MediaPlaylistAgent playlistAgent) { |
| if (playlistAgent == null) { |
| throw new IllegalArgumentException("playlistAgent shouldn't be null"); |
| } |
| mPlaylistAgent = playlistAgent; |
| } |
| |
| void setVolumeProvider(@Nullable VolumeProviderCompat volumeProvider) { |
| mVolumeProvider = volumeProvider; |
| } |
| |
| void setSessionActivity(@Nullable PendingIntent pi) { |
| mSessionActivity = pi; |
| } |
| |
| void setId(@NonNull String id) { |
| if (id == null) { |
| throw new IllegalArgumentException("id shouldn't be null"); |
| } |
| mId = id; |
| } |
| |
| void setSessionCallback(@NonNull Executor executor, @NonNull C callback) { |
| if (executor == null) { |
| throw new IllegalArgumentException("executor shouldn't be null"); |
| } |
| if (callback == null) { |
| throw new IllegalArgumentException("callback shouldn't be null"); |
| } |
| mCallbackExecutor = executor; |
| mCallback = callback; |
| } |
| |
| abstract @NonNull T build(); |
| } |
| |
| static final class Builder extends |
| BuilderBase<MediaSession2, MediaSession2.SessionCallback> { |
| Builder(Context context) { |
| super(context); |
| } |
| |
| @Override |
| public @NonNull MediaSession2 build() { |
| if (mCallbackExecutor == null) { |
| mCallbackExecutor = new MainHandlerExecutor(mContext); |
| } |
| if (mCallback == null) { |
| mCallback = new SessionCallback() {}; |
| } |
| return new MediaSession2(new MediaSession2ImplBase(mContext, |
| new MediaSessionCompat(mContext, mId), mId, mPlayer, mPlaylistAgent, |
| mVolumeProvider, mSessionActivity, mCallbackExecutor, mCallback)); |
| } |
| } |
| |
| static class MainHandlerExecutor implements Executor { |
| private final Handler mHandler; |
| |
| MainHandlerExecutor(Context context) { |
| mHandler = new Handler(context.getMainLooper()); |
| } |
| |
| @Override |
| public void execute(Runnable command) { |
| if (!mHandler.post(command)) { |
| throw new RejectedExecutionException(mHandler + " is shutting down"); |
| } |
| } |
| } |
| } |