PageRenderTime 97ms CodeModel.GetById 36ms RepoModel.GetById 0ms app.codeStats 0ms

/samples/SupportLeanbackDemos/src/main/java/com/example/android/leanback/MediaSessionService.java

https://gitlab.com/SkyDragon-OSP/platform_frameworks_support
Java | 1021 lines | 586 code | 117 blank | 318 comment | 90 complexity | 38512d8f5a3d1bb1b697b1a8b5f9c5fa MD5 | raw file
  1. /*
  2. * Copyright (C) 2017 The Android Open Source Project
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package com.example.android.leanback;
  17. import android.app.Service;
  18. import android.content.Context;
  19. import android.content.Intent;
  20. import android.graphics.Bitmap;
  21. import android.graphics.BitmapFactory;
  22. import android.media.AudioManager;
  23. import android.media.MediaPlayer;
  24. import android.os.Binder;
  25. import android.os.Handler;
  26. import android.os.IBinder;
  27. import android.os.Message;
  28. import android.os.SystemClock;
  29. import android.support.v4.media.MediaMetadataCompat;
  30. import android.support.v4.media.session.MediaSessionCompat;
  31. import android.support.v4.media.session.PlaybackStateCompat;
  32. import android.util.Log;
  33. import androidx.annotation.Nullable;
  34. import java.io.IOException;
  35. import java.util.ArrayList;
  36. import java.util.List;
  37. import java.util.Random;
  38. /**
  39. * The service to play music. It also contains the media session.
  40. */
  41. public class MediaSessionService extends Service {
  42. public static final String CANNOT_SET_DATA_SOURCE = "Cannot set data source";
  43. private static final float NORMAL_SPEED = 1.0f;
  44. /**
  45. * When media player is prepared, our service can send notification to UI side through this
  46. * callback. So UI will have chance to prepare/ pre-processing the UI status.
  47. */
  48. interface MediaPlayerListener {
  49. void onPrepared();
  50. }
  51. /**
  52. * This LocalBinder class contains the getService() method which will return the service object.
  53. */
  54. public class LocalBinder extends Binder {
  55. MediaSessionService getService() {
  56. return MediaSessionService.this;
  57. }
  58. }
  59. /**
  60. * Constant used in this class.
  61. */
  62. private static final String MUSIC_PLAYER_SESSION_TOKEN = "MusicPlayer Session token";
  63. private static final int MEDIA_ACTION_NO_REPEAT = 0;
  64. private static final int MEDIA_ACTION_REPEAT_ONE = 1;
  65. private static final int MEDIA_ACTION_REPEAT_ALL = 2;
  66. public static final String MEDIA_PLAYER_ERROR_MESSAGE = "Media player error message";
  67. public static final String PLAYER_NOT_INITIALIZED = "Media player not initialized";
  68. public static final String PLAYER_IS_PLAYING = "Media player is playing";
  69. public static final String PLAYER_SET_DATA_SOURCE_ERROR =
  70. "Media player set new data source error";
  71. private static final boolean DEBUG = false;
  72. private static final String TAG = "MusicPlaybackService";
  73. private static final int FOCUS_CHANGE = 0;
  74. // This handler can control media player through audio's status.
  75. private class MediaPlayerAudioHandler extends Handler {
  76. @Override
  77. public void handleMessage(Message msg) {
  78. switch (msg.what) {
  79. case FOCUS_CHANGE:
  80. switch (msg.arg1) {
  81. // pause media item when audio focus is lost
  82. case AudioManager.AUDIOFOCUS_LOSS:
  83. if (isPlaying()) {
  84. audioFocusLossHandler();
  85. }
  86. break;
  87. case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
  88. if (isPlaying()) {
  89. audioLossFocusTransientHandler();
  90. }
  91. break;
  92. case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
  93. if (isPlaying()) {
  94. audioLossFocusTransientCanDuckHanlder();
  95. }
  96. break;
  97. case AudioManager.AUDIOFOCUS_GAIN:
  98. if (!isPlaying()) {
  99. audioFocusGainHandler();
  100. }
  101. break;
  102. }
  103. }
  104. }
  105. }
  106. // The callbacks' collection which can be notified by this service.
  107. private List<MediaPlayerListener> mCallbacks = new ArrayList<>();
  108. // audio manager obtained from system to gain audio focus
  109. private AudioManager mAudioManager;
  110. // record user defined repeat mode.
  111. private int mRepeatState = MEDIA_ACTION_NO_REPEAT;
  112. // record user defined shuffle mode.
  113. private int mShuffleMode = PlaybackStateCompat.SHUFFLE_MODE_NONE;
  114. private MediaPlayer mPlayer;
  115. private MediaSessionCompat mMediaSession;
  116. // set -1 as invalid media item for playing.
  117. private int mCurrentIndex = -1;
  118. // media item in media playlist.
  119. private MusicItem mCurrentMediaItem;
  120. // media player's current progress.
  121. private int mCurrentPosition;
  122. // Buffered Position which will be updated inside of OnBufferingUpdateListener
  123. private long mBufferedProgress;
  124. List<MusicItem> mMediaItemList = new ArrayList<>();
  125. private boolean mInitialized;
  126. // fast forward/ rewind speed factors and indexes
  127. private float[] mFastForwardSpeedFactors;
  128. private float[] mRewindSpeedFactors;
  129. private int mFastForwardSpeedFactorIndex = 0;
  130. private int mRewindSpeedFactorIndex = 0;
  131. // Flags to indicate if current state is fast forwarding/ rewinding.
  132. private boolean mIsFastForwarding;
  133. private boolean mIsRewinding;
  134. // handle audio related event.
  135. private Handler mMediaPlayerHandler = new MediaPlayerAudioHandler();
  136. // The volume we set the media player to when we lose audio focus, but are
  137. // allowed to reduce the volume and continue playing.
  138. private static final float REDUCED_VOLUME = 0.1f;
  139. // The volume we set the media player when we have audio focus.
  140. private static final float FULL_VOLUME = 1.0f;
  141. // Record position when current rewind action begins.
  142. private long mRewindStartPosition;
  143. // Record the time stamp when current rewind action is ended.
  144. private long mRewindEndTime;
  145. // Record the time stamp when current rewind action is started.
  146. private long mRewindStartTime;
  147. // Flag to represent the beginning of rewind operation.
  148. private boolean mIsRewindBegin;
  149. // A runnable object which will delay the execution of mPlayer.stop()
  150. private Runnable mDelayedStopRunnable = new Runnable() {
  151. @Override
  152. public void run() {
  153. mPlayer.stop();
  154. mMediaSession.setPlaybackState(createPlaybackStateBuilder(
  155. PlaybackStateCompat.STATE_STOPPED).build());
  156. }
  157. };
  158. // Listener for audio focus.
  159. private AudioManager.OnAudioFocusChangeListener mOnAudioFocusChangeListener = new
  160. AudioManager.OnAudioFocusChangeListener() {
  161. @Override
  162. public void onAudioFocusChange(int focusChange) {
  163. if (DEBUG) {
  164. Log.d(TAG, "onAudioFocusChange. focusChange=" + focusChange);
  165. }
  166. mMediaPlayerHandler.obtainMessage(FOCUS_CHANGE, focusChange, 0).sendToTarget();
  167. }
  168. };
  169. private final IBinder mBinder = new LocalBinder();
  170. /**
  171. * The public API to gain media session instance from service.
  172. *
  173. * @return Media Session Instance.
  174. */
  175. public MediaSessionCompat getMediaSession() {
  176. return mMediaSession;
  177. }
  178. @Nullable
  179. @Override
  180. public IBinder onBind(Intent intent) {
  181. return mBinder;
  182. }
  183. @Override
  184. public void onCreate() {
  185. super.onCreate();
  186. // This service can be created for multiple times, the objects will only be created when
  187. // it is null
  188. if (mMediaSession == null) {
  189. mMediaSession = new MediaSessionCompat(this, MUSIC_PLAYER_SESSION_TOKEN);
  190. mMediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS
  191. | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
  192. mMediaSession.setCallback(new MediaSessionCallback());
  193. }
  194. if (mAudioManager == null) {
  195. // Create audio manager through system service
  196. mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
  197. }
  198. // initialize the player (including activate media session, request audio focus and
  199. // set up the listener to listen to player's state)
  200. initializePlayer();
  201. }
  202. @Override
  203. public void onDestroy() {
  204. super.onDestroy();
  205. stopForeground(true);
  206. mAudioManager.abandonAudioFocus(mOnAudioFocusChangeListener);
  207. mMediaPlayerHandler.removeCallbacksAndMessages(null);
  208. if (mPlayer != null) {
  209. // stop and release the media player since it's no longer in use
  210. mPlayer.reset();
  211. mPlayer.release();
  212. mPlayer = null;
  213. }
  214. if (mMediaSession != null) {
  215. mMediaSession.release();
  216. mMediaSession = null;
  217. }
  218. }
  219. /**
  220. * After binding to this service, other component can set Media Item List and prepare
  221. * the first item in the list through this function.
  222. *
  223. * @param mediaItemList A list of media item to play.
  224. * @param isQueue When this parameter is true, that meas new items should be appended to
  225. * original media item list.
  226. * If this parameter is false, the original playlist will be cleared and
  227. * replaced with a new media item list.
  228. */
  229. public void setMediaList(List<MusicItem> mediaItemList, boolean isQueue) {
  230. if (!isQueue) {
  231. mMediaItemList.clear();
  232. }
  233. mMediaItemList.addAll(mediaItemList);
  234. /**
  235. * Points to the first media item in play list.
  236. */
  237. mCurrentIndex = 0;
  238. mCurrentMediaItem = mMediaItemList.get(0);
  239. try {
  240. mPlayer.setDataSource(this.getApplicationContext(),
  241. mCurrentMediaItem.getMediaSourceUri(getApplicationContext()));
  242. // Prepare the player asynchronously, use onPrepared listener as signal.
  243. mPlayer.prepareAsync();
  244. } catch (IOException e) {
  245. PlaybackStateCompat.Builder ret = createPlaybackStateBuilder(
  246. PlaybackStateCompat.STATE_ERROR);
  247. ret.setErrorMessage(PlaybackStateCompat.ERROR_CODE_APP_ERROR,
  248. PLAYER_SET_DATA_SOURCE_ERROR);
  249. }
  250. }
  251. /**
  252. * Set Fast Forward Speeds for this media session service.
  253. *
  254. * @param fastForwardSpeeds The array contains all fast forward speeds.
  255. */
  256. public void setFastForwardSpeedFactors(int[] fastForwardSpeeds) {
  257. mFastForwardSpeedFactors = new float[fastForwardSpeeds.length + 1];
  258. // Put normal speed factor at the beginning of the array
  259. mFastForwardSpeedFactors[0] = 1.0f;
  260. for (int index = 1; index < mFastForwardSpeedFactors.length; ++index) {
  261. mFastForwardSpeedFactors[index] = fastForwardSpeeds[index - 1];
  262. }
  263. }
  264. /**
  265. * Set Rewind Speeds for this media session service.
  266. *
  267. * @param rewindSpeeds The array contains all rewind speeds.
  268. */
  269. public void setRewindSpeedFactors(int[] rewindSpeeds) {
  270. mRewindSpeedFactors = new float[rewindSpeeds.length];
  271. for (int index = 0; index < mRewindSpeedFactors.length; ++index) {
  272. mRewindSpeedFactors[index] = -rewindSpeeds[index];
  273. }
  274. }
  275. /**
  276. * Prepare the first item in the list. And setup the listener for media player.
  277. */
  278. private void initializePlayer() {
  279. // This service can be created for multiple times, the objects will only be created when
  280. // it is null
  281. if (mPlayer != null) {
  282. return;
  283. }
  284. mPlayer = new MediaPlayer();
  285. // Set playback state to none to create a valid playback state. So controls row can get
  286. // information about the supported actions.
  287. mMediaSession.setPlaybackState(createPlaybackStateBuilder(
  288. PlaybackStateCompat.STATE_NONE).build());
  289. // Activate media session
  290. if (!mMediaSession.isActive()) {
  291. mMediaSession.setActive(true);
  292. }
  293. // Set up listener and audio stream type for underlying music player.
  294. mPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
  295. // set up listener when the player is prepared.
  296. mPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
  297. @Override
  298. public void onPrepared(MediaPlayer mp) {
  299. mInitialized = true;
  300. // Every time when the player is prepared (when new data source is set),
  301. // all listeners will be notified to toggle the UI to "pause" status.
  302. notifyUiWhenPlayerIsPrepared();
  303. // When media player is prepared, the callback functions will be executed to update
  304. // the meta data and playback state.
  305. onMediaSessionMetaDataChanged();
  306. mMediaSession.setPlaybackState(createPlaybackStateBuilder(
  307. PlaybackStateCompat.STATE_PAUSED).build());
  308. }
  309. });
  310. // set up listener for player's error.
  311. mPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
  312. @Override
  313. public boolean onError(MediaPlayer mediaPlayer, int what, int extra) {
  314. if (DEBUG) {
  315. PlaybackStateCompat.Builder builder = createPlaybackStateBuilder(
  316. PlaybackStateCompat.STATE_ERROR);
  317. builder.setErrorMessage(PlaybackStateCompat.ERROR_CODE_APP_ERROR,
  318. MEDIA_PLAYER_ERROR_MESSAGE);
  319. mMediaSession.setPlaybackState(builder.build());
  320. }
  321. return true;
  322. }
  323. });
  324. // set up listener to respond the event when current music item is finished
  325. mPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
  326. /**
  327. * Expected Interaction Behavior:
  328. * 1. If current media item's playing speed not equal to normal speed.
  329. *
  330. * A. MEDIA_ACTION_REPEAT_ALL
  331. * a. If current media item is the last one. The first music item in the list will
  332. * be prepared, but it won't play until user press play button.
  333. *
  334. * When user press the play button, the speed will be reset to normal (1.0f)
  335. * no matter what the previous media item's playing speed is.
  336. *
  337. * b. If current media item isn't the last one, next media item will be prepared,
  338. * but it won't play.
  339. *
  340. * When user press the play button, the speed will be reset to normal (1.0f)
  341. * no matter what the previous media item's playing speed is.
  342. *
  343. * B. MEDIA_ACTION_REPEAT_ONE
  344. * Different with previous scenario, current item will go back to the start point
  345. * again and play automatically. (The reason to enable auto play here is for
  346. * testing purpose and to make sure our designed API is flexible enough to support
  347. * different situations.)
  348. *
  349. * No matter what the previous media item's playing speed is, in this situation
  350. * current media item will be replayed in normal speed.
  351. *
  352. * C. MEDIA_ACTION_REPEAT_NONE
  353. * a. If current media is the last one. The service will be closed, no music item
  354. * will be prepared to play. From the UI perspective, the progress bar will not
  355. * be reset to the starting point.
  356. *
  357. * b. If current media item isn't the last one, next media item will be prepared,
  358. * but it won't play.
  359. *
  360. * When user press the play button, the speed will be reset to normal (1.0f)
  361. * no matter what the previous media item's playing speed is.
  362. *
  363. * @param mp Object of MediaPlayer。
  364. */
  365. @Override
  366. public void onCompletion(MediaPlayer mp) {
  367. // When current media item finishes playing, always reset rewind/ fastforward state
  368. mFastForwardSpeedFactorIndex = 0;
  369. mRewindSpeedFactorIndex = 0;
  370. // Set player's playback speed back to normal
  371. mPlayer.setPlaybackParams(mPlayer.getPlaybackParams().setSpeed(
  372. mFastForwardSpeedFactors[mFastForwardSpeedFactorIndex]));
  373. // Pause the player, and update the status accordingly.
  374. mPlayer.pause();
  375. mMediaSession.setPlaybackState(createPlaybackStateBuilder(
  376. PlaybackStateCompat.STATE_PAUSED).build());
  377. if (mRepeatState == MEDIA_ACTION_REPEAT_ALL
  378. && mCurrentIndex == mMediaItemList.size() - 1) {
  379. // if the repeat mode is enabled but the shuffle mode is not enabled,
  380. // will go back to the first music item to play
  381. if (mShuffleMode == PlaybackStateCompat.SHUFFLE_MODE_NONE) {
  382. mCurrentIndex = 0;
  383. } else {
  384. // Or will choose a music item from playing list randomly.
  385. mCurrentIndex = generateMediaItemIndex();
  386. }
  387. mCurrentMediaItem = mMediaItemList.get(mCurrentIndex);
  388. // The ui will also be changed from playing state to pause state through
  389. // setDataSource() operation
  390. setDataSource();
  391. } else if (mRepeatState == MEDIA_ACTION_REPEAT_ONE) {
  392. // Play current music item again.
  393. // The ui will stay to be "playing" status for the reason that there is no
  394. // setDataSource() function call.
  395. mPlayer.start();
  396. mMediaSession.setPlaybackState(createPlaybackStateBuilder(
  397. PlaybackStateCompat.STATE_PLAYING).build());
  398. } else if (mCurrentIndex < mMediaItemList.size() - 1) {
  399. if (mShuffleMode == PlaybackStateCompat.SHUFFLE_MODE_NONE) {
  400. mCurrentIndex++;
  401. } else {
  402. mCurrentIndex = generateMediaItemIndex();
  403. }
  404. mCurrentMediaItem = mMediaItemList.get(mCurrentIndex);
  405. // The ui will also be changed from playing state to pause state through
  406. // setDataSource() operation
  407. setDataSource();
  408. } else {
  409. // close the service when the playlist is finished
  410. // The PlaybackState will be updated to STATE_STOPPED. And onPlayComplete
  411. // callback will be called by attached glue.
  412. mMediaSession.setPlaybackState(createPlaybackStateBuilder(
  413. PlaybackStateCompat.STATE_STOPPED).build());
  414. stopSelf();
  415. }
  416. }
  417. });
  418. final MediaPlayer.OnBufferingUpdateListener mOnBufferingUpdateListener =
  419. new MediaPlayer.OnBufferingUpdateListener() {
  420. @Override
  421. public void onBufferingUpdate(MediaPlayer mp, int percent) {
  422. mBufferedProgress = getDuration() * percent / 100;
  423. PlaybackStateCompat.Builder builder = createPlaybackStateBuilder(
  424. PlaybackStateCompat.STATE_BUFFERING);
  425. builder.setBufferedPosition(mBufferedProgress);
  426. mMediaSession.setPlaybackState(builder.build());
  427. }
  428. };
  429. mPlayer.setOnBufferingUpdateListener(mOnBufferingUpdateListener);
  430. }
  431. /**
  432. * Public API to register listener for this service.
  433. *
  434. * @param listener The listener which will keep tracking current service's status
  435. */
  436. public void registerCallback(MediaPlayerListener listener) {
  437. mCallbacks.add(listener);
  438. }
  439. /**
  440. * Instead of shuffling the who music list, we will generate a media item index randomly
  441. * and return it as the index for next media item to play.
  442. *
  443. * @return The index of next media item to play.
  444. */
  445. private int generateMediaItemIndex() {
  446. return new Random().nextInt(mMediaItemList.size());
  447. }
  448. /**
  449. * When player is prepared, service will send notification to UI through calling the callback's
  450. * method
  451. */
  452. private void notifyUiWhenPlayerIsPrepared() {
  453. for (MediaPlayerListener callback : mCallbacks) {
  454. callback.onPrepared();
  455. }
  456. }
  457. /**
  458. * Set up media session callback to associate with player's operation.
  459. */
  460. private class MediaSessionCallback extends MediaSessionCompat.Callback {
  461. @Override
  462. public void onPlay() {
  463. play();
  464. }
  465. @Override
  466. public void onPause() {
  467. pause();
  468. }
  469. @Override
  470. public void onSkipToNext() {
  471. next();
  472. }
  473. @Override
  474. public void onSkipToPrevious() {
  475. previous();
  476. }
  477. @Override
  478. public void onStop() {
  479. stop();
  480. }
  481. @Override
  482. public void onSeekTo(long pos) {
  483. // media player's seekTo method can only take integer as the parameter
  484. // so the data type need to be casted as int
  485. seekTo((int) pos);
  486. }
  487. @Override
  488. public void onFastForward() {
  489. fastForward();
  490. }
  491. @Override
  492. public void onRewind() {
  493. rewind();
  494. }
  495. @Override
  496. public void onSetRepeatMode(int repeatMode) {
  497. setRepeatState(repeatMode);
  498. }
  499. @Override
  500. public void onSetShuffleMode(int shuffleMode) {
  501. setShuffleMode(shuffleMode);
  502. }
  503. }
  504. /**
  505. * Set new data source and prepare the music player asynchronously.
  506. */
  507. private void setDataSource() {
  508. reset();
  509. try {
  510. mPlayer.setDataSource(this.getApplicationContext(),
  511. mCurrentMediaItem.getMediaSourceUri(getApplicationContext()));
  512. mPlayer.prepareAsync();
  513. } catch (IOException e) {
  514. PlaybackStateCompat.Builder builder = createPlaybackStateBuilder(
  515. PlaybackStateCompat.STATE_ERROR);
  516. builder.setErrorMessage(PlaybackStateCompat.ERROR_CODE_APP_ERROR,
  517. CANNOT_SET_DATA_SOURCE);
  518. mMediaSession.setPlaybackState(builder.build());
  519. }
  520. }
  521. /**
  522. * This function will return a playback state builder based on playbackState and current
  523. * media position.
  524. *
  525. * @param playState current playback state.
  526. * @return Object of PlaybackStateBuilder.
  527. */
  528. private PlaybackStateCompat.Builder createPlaybackStateBuilder(int playState) {
  529. PlaybackStateCompat.Builder playbackStateBuilder = new PlaybackStateCompat.Builder();
  530. long currentPosition = getCurrentPosition();
  531. float playbackSpeed = NORMAL_SPEED;
  532. if (mIsFastForwarding) {
  533. playbackSpeed = mFastForwardSpeedFactors[mFastForwardSpeedFactorIndex];
  534. // After setting the playback speed, reset mIsFastForwarding flag.
  535. mIsFastForwarding = false;
  536. } else if (mIsRewinding) {
  537. playbackSpeed = mRewindSpeedFactors[mRewindSpeedFactorIndex];
  538. // After setting the playback speed, reset mIsRewinding flag.
  539. mIsRewinding = false;
  540. }
  541. playbackStateBuilder.setState(playState, currentPosition, playbackSpeed
  542. ).setActions(
  543. getPlaybackStateActions()
  544. );
  545. return playbackStateBuilder;
  546. }
  547. /**
  548. * Return supported actions related to current playback state.
  549. * Currently the return value from this function is a constant.
  550. * For demonstration purpose, the customized fast forward action and customized rewind action
  551. * are supported in our case.
  552. *
  553. * @return playback state actions.
  554. */
  555. private long getPlaybackStateActions() {
  556. long res = PlaybackStateCompat.ACTION_PLAY
  557. | PlaybackStateCompat.ACTION_PAUSE
  558. | PlaybackStateCompat.ACTION_PLAY_PAUSE
  559. | PlaybackStateCompat.ACTION_SKIP_TO_NEXT
  560. | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS
  561. | PlaybackStateCompat.ACTION_FAST_FORWARD
  562. | PlaybackStateCompat.ACTION_REWIND
  563. | PlaybackStateCompat.ACTION_SET_SHUFFLE_MODE
  564. | PlaybackStateCompat.ACTION_SET_REPEAT_MODE;
  565. return res;
  566. }
  567. /**
  568. * Callback function when media session's meta data is changed.
  569. * When this function is returned, the callback function onMetaDataChanged will be
  570. * executed to address the new playback state.
  571. */
  572. private void onMediaSessionMetaDataChanged() {
  573. if (mCurrentMediaItem == null) {
  574. throw new IllegalArgumentException(
  575. "mCurrentMediaItem is null in onMediaSessionMetaDataChanged!");
  576. }
  577. MediaMetadataCompat.Builder metaDataBuilder = new MediaMetadataCompat.Builder();
  578. if (mCurrentMediaItem.getMediaTitle() != null) {
  579. metaDataBuilder.putString(MediaMetadataCompat.METADATA_KEY_TITLE,
  580. mCurrentMediaItem.getMediaTitle());
  581. }
  582. if (mCurrentMediaItem.getMediaDescription() != null) {
  583. metaDataBuilder.putString(MediaMetadataCompat.METADATA_KEY_ARTIST,
  584. mCurrentMediaItem.getMediaDescription());
  585. }
  586. if (mCurrentMediaItem.getMediaAlbumArtResId(getApplicationContext()) != 0) {
  587. Bitmap albumArtBitmap = BitmapFactory.decodeResource(getResources(),
  588. mCurrentMediaItem.getMediaAlbumArtResId(getApplicationContext()));
  589. metaDataBuilder.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, albumArtBitmap);
  590. }
  591. // duration information will be fetched from player.
  592. metaDataBuilder.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, getDuration());
  593. mMediaSession.setMetadata(metaDataBuilder.build());
  594. }
  595. // Reset player. will be executed when new data source is assigned.
  596. private void reset() {
  597. if (mPlayer != null) {
  598. mPlayer.reset();
  599. mInitialized = false;
  600. }
  601. }
  602. // Control the player to play the music item.
  603. private void play() {
  604. // Only when player is not null (meaning the player has been created), the player is
  605. // prepared (using the mInitialized as the flag to represent it,
  606. // this boolean variable will only be assigned to true inside of the onPrepared callback)
  607. // and the media item is not currently playing (!isPlaying()), then the player can be
  608. // started.
  609. // If the player has not been prepared, but this function is fired, it is an error state
  610. // from the app side
  611. if (!mInitialized) {
  612. PlaybackStateCompat.Builder builder = createPlaybackStateBuilder(
  613. PlaybackStateCompat.STATE_ERROR);
  614. builder.setErrorMessage(PlaybackStateCompat.ERROR_CODE_APP_ERROR,
  615. PLAYER_NOT_INITIALIZED);
  616. mMediaSession.setPlaybackState(builder.build());
  617. // If the player has is playing, and this function is fired again, it is an error state
  618. // from the app side
  619. } else {
  620. // Request audio focus only when needed
  621. if (mAudioManager.requestAudioFocus(mOnAudioFocusChangeListener,
  622. AudioManager.STREAM_MUSIC,
  623. AudioManager.AUDIOFOCUS_GAIN) != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
  624. return;
  625. }
  626. if (mPlayer.getPlaybackParams().getSpeed() != NORMAL_SPEED) {
  627. // Reset to normal speed and play
  628. resetSpeedAndPlay();
  629. } else {
  630. // Continue play.
  631. mPlayer.start();
  632. mMediaSession.setPlaybackState(createPlaybackStateBuilder(
  633. PlaybackStateCompat.STATE_PLAYING).build());
  634. }
  635. }
  636. }
  637. // Control the player to pause current music item.
  638. private void pause() {
  639. if (mPlayer != null && mPlayer.isPlaying()) {
  640. // abandon audio focus immediately when the music item is paused.
  641. mAudioManager.abandonAudioFocus(mOnAudioFocusChangeListener);
  642. mPlayer.pause();
  643. // Update playbackState.
  644. mMediaSession.setPlaybackState(createPlaybackStateBuilder(
  645. PlaybackStateCompat.STATE_PAUSED).build());
  646. }
  647. }
  648. // Control the player to stop.
  649. private void stop() {
  650. if (mPlayer != null) {
  651. mPlayer.stop();
  652. // Update playbackState.
  653. mMediaSession.setPlaybackState(createPlaybackStateBuilder(
  654. PlaybackStateCompat.STATE_STOPPED).build());
  655. }
  656. }
  657. /**
  658. * Control the player to play next music item.
  659. * Expected Interaction Behavior:
  660. * No matter current media item is playing or not, when use hit next button, next item will be
  661. * prepared but won't play unless user hit play button
  662. *
  663. * Also no matter current media item is fast forwarding or rewinding. Next music item will
  664. * be played in normal speed.
  665. */
  666. private void next() {
  667. if (mMediaItemList.isEmpty()) {
  668. return;
  669. }
  670. mCurrentIndex = (mCurrentIndex + 1) % mMediaItemList.size();
  671. mCurrentMediaItem = mMediaItemList.get(mCurrentIndex);
  672. // Reset FastForward/ Rewind state to normal state
  673. mFastForwardSpeedFactorIndex = 0;
  674. mRewindSpeedFactorIndex = 0;
  675. // Set player's playback speed back to normal
  676. mPlayer.setPlaybackParams(mPlayer.getPlaybackParams().setSpeed(
  677. mFastForwardSpeedFactors[mFastForwardSpeedFactorIndex]));
  678. // Pause the player and update the play state.
  679. // The ui will also be changed from "playing" state to "pause" state.
  680. mPlayer.pause();
  681. mMediaSession.setPlaybackState(createPlaybackStateBuilder(
  682. PlaybackStateCompat.STATE_PAUSED).build());
  683. // set new data source to play based on mCurrentIndex and prepare the player.
  684. // The ui will also be changed from "playing" state to "pause" state through setDataSource()
  685. // operation
  686. setDataSource();
  687. }
  688. /**
  689. * Control the player to play next music item.
  690. * Expected Interaction Behavior:
  691. * No matter current media item is playing or not, when use hit previous button, previous item
  692. * will be prepared but won't play unless user hit play button
  693. *
  694. * Also no matter current media item is fast forwarding or rewinding. Previous music item will
  695. * be played in normal speed.
  696. */
  697. private void previous() {
  698. if (mMediaItemList.isEmpty()) {
  699. return;
  700. }
  701. mCurrentIndex = (mCurrentIndex - 1 + mMediaItemList.size()) % mMediaItemList.size();
  702. mCurrentMediaItem = mMediaItemList.get(mCurrentIndex);
  703. // Reset FastForward/ Rewind state to normal state
  704. mFastForwardSpeedFactorIndex = 0;
  705. mRewindSpeedFactorIndex = 0;
  706. // Set player's playback speed back to normal
  707. mPlayer.setPlaybackParams(mPlayer.getPlaybackParams().setSpeed(
  708. mFastForwardSpeedFactors[mFastForwardSpeedFactorIndex]));
  709. // Pause the player and update the play state.
  710. // The ui will also be changed from "playing" state to "pause" state.
  711. mPlayer.pause();
  712. // Update playbackState.
  713. mMediaSession.setPlaybackState(createPlaybackStateBuilder(
  714. PlaybackStateCompat.STATE_PAUSED).build());
  715. // set new data source to play based on mCurrentIndex and prepare the player.
  716. // The ui will also be changed from "playing" state to "pause" state through setDataSource()
  717. // operation
  718. setDataSource();
  719. }
  720. // Get is playing information from underlying player.
  721. private boolean isPlaying() {
  722. return mPlayer != null && mPlayer.isPlaying();
  723. }
  724. // Play media item in a fast forward speed.
  725. private void fastForward() {
  726. // To support fast forward action, the mRewindSpeedFactors must be provided through
  727. // setFastForwardSpeedFactors() method;
  728. if (mFastForwardSpeedFactors == null) {
  729. if (DEBUG) {
  730. Log.d(TAG, "FastForwardSpeedFactors are not set");
  731. }
  732. return;
  733. }
  734. // Toggle the flag to indicate fast forward status.
  735. mIsFastForwarding = true;
  736. // The first element in mFastForwardSpeedFactors is used to represent the normal speed.
  737. // Will always be incremented by 1 firstly before setting the speed.
  738. mFastForwardSpeedFactorIndex += 1;
  739. if (mFastForwardSpeedFactorIndex > mFastForwardSpeedFactors.length - 1) {
  740. mFastForwardSpeedFactorIndex = mFastForwardSpeedFactors.length - 1;
  741. }
  742. // In our customized fast forward operation, the media player will not be paused,
  743. // But the player's speed will be changed accordingly.
  744. mPlayer.setPlaybackParams(mPlayer.getPlaybackParams().setSpeed(
  745. mFastForwardSpeedFactors[mFastForwardSpeedFactorIndex]));
  746. // Update playback state, mIsFastForwarding will be reset to false inside of it.
  747. mMediaSession.setPlaybackState(
  748. createPlaybackStateBuilder(PlaybackStateCompat.STATE_FAST_FORWARDING).build());
  749. }
  750. // Play media item in a rewind speed.
  751. // Android media player doesn't support negative speed. So for customized rewind operation,
  752. // the player will be paused internally, but the pause state will not be published. So from
  753. // the UI perspective, the player is still in playing status.
  754. // Every time when the rewind speed is changed, the position will be computed through previous
  755. // rewind speed then media player will seek to that position for seamless playing.
  756. private void rewind() {
  757. // To support rewind action, the mRewindSpeedFactors must be provided through
  758. // setRewindSpeedFactors() method;
  759. if (mRewindSpeedFactors == null) {
  760. if (DEBUG) {
  761. Log.d(TAG, "RewindSpeedFactors are not set");
  762. }
  763. return;
  764. }
  765. // Perform rewind operation using different speed.
  766. if (mIsRewindBegin) {
  767. // record end time stamp for previous rewind operation.
  768. mRewindEndTime = SystemClock.elapsedRealtime();
  769. long position = mRewindStartPosition
  770. + (long) mRewindSpeedFactors[mRewindSpeedFactorIndex - 1] * (
  771. mRewindEndTime - mRewindStartTime);
  772. if (DEBUG) {
  773. Log.e(TAG, "Last Rewind Operation Position" + position);
  774. }
  775. mPlayer.seekTo((int) position);
  776. // Set new start status
  777. mRewindStartPosition = position;
  778. mRewindStartTime = mRewindEndTime;
  779. // It is still in rewind state, so mIsRewindBegin remains to be true.
  780. }
  781. // Perform rewind operation using the first speed set.
  782. if (!mIsRewindBegin) {
  783. mRewindStartPosition = getCurrentPosition();
  784. Log.e("REWIND_BEGIN", "REWIND BEGIN PLACE " + mRewindStartPosition);
  785. mIsRewindBegin = true;
  786. mRewindStartTime = SystemClock.elapsedRealtime();
  787. }
  788. // Toggle the flag to indicate rewind status.
  789. mIsRewinding = true;
  790. // Pause the player but won't update the UI status.
  791. mPlayer.pause();
  792. // Update playback state, mIsRewinding will be reset to false inside of it.
  793. mMediaSession.setPlaybackState(
  794. createPlaybackStateBuilder(PlaybackStateCompat.STATE_REWINDING).build());
  795. mRewindSpeedFactorIndex += 1;
  796. if (mRewindSpeedFactorIndex > mRewindSpeedFactors.length - 1) {
  797. mRewindSpeedFactorIndex = mRewindSpeedFactors.length - 1;
  798. }
  799. }
  800. // Reset the playing speed to normal.
  801. // From PlaybackBannerGlue's key dispatching mechanism. If the player is currently in rewinding
  802. // or fast forwarding status, moving from the rewinding/ FastForwarindg button will trigger
  803. // the fastForwarding/ rewinding ending event.
  804. // When customized fast forwarding or rewinding actions are supported, this function will be
  805. // called.
  806. // If we are in rewind mode, this function will compute the new position through rewinding
  807. // speed and compare the start/ end rewinding time stamp.
  808. private void resetSpeedAndPlay() {
  809. if (mIsRewindBegin) {
  810. mIsRewindBegin = false;
  811. mRewindEndTime = SystemClock.elapsedRealtime();
  812. long position = mRewindStartPosition
  813. + (long) mRewindSpeedFactors[mRewindSpeedFactorIndex ] * (
  814. mRewindEndTime - mRewindStartTime);
  815. // Seek to the computed position for seamless playing.
  816. mPlayer.seekTo((int) position);
  817. }
  818. // Reset the state to normal state.
  819. mFastForwardSpeedFactorIndex = 0;
  820. mRewindSpeedFactorIndex = 0;
  821. mPlayer.setPlaybackParams(mPlayer.getPlaybackParams().setSpeed(
  822. mFastForwardSpeedFactors[mFastForwardSpeedFactorIndex]));
  823. // Update the playback status from rewinding/ fast forwardindg to STATE_PLAYING.
  824. // Which indicates current media item is played in the normal speed.
  825. mMediaSession.setPlaybackState(
  826. createPlaybackStateBuilder(PlaybackStateCompat.STATE_PLAYING).build());
  827. }
  828. // Get current playing progress from media player.
  829. private int getCurrentPosition() {
  830. if (mInitialized && mPlayer != null) {
  831. // Always record current position for seekTo operation.
  832. mCurrentPosition = mPlayer.getCurrentPosition();
  833. return mPlayer.getCurrentPosition();
  834. }
  835. return 0;
  836. }
  837. // get music duration from underlying music player
  838. private int getDuration() {
  839. return (mInitialized && mPlayer != null) ? mPlayer.getDuration() : 0;
  840. }
  841. // seek to specific position through underlying music player.
  842. private void seekTo(int newPosition) {
  843. if (mPlayer != null) {
  844. mPlayer.seekTo(newPosition);
  845. }
  846. }
  847. // set shuffle mode through passed parameter.
  848. private void setShuffleMode(int shuffleMode) {
  849. mShuffleMode = shuffleMode;
  850. }
  851. // set shuffle mode through passed parameter.
  852. public void setRepeatState(int repeatState) {
  853. mRepeatState = repeatState;
  854. }
  855. private void audioFocusLossHandler() {
  856. // Permanent loss of audio focus
  857. // Pause playback immediately
  858. mPlayer.pause();
  859. // Wait 30 seconds before stopping playback
  860. mMediaPlayerHandler.postDelayed(mDelayedStopRunnable, 30);
  861. // Update playback state.
  862. mMediaSession.setPlaybackState(createPlaybackStateBuilder(
  863. PlaybackStateCompat.STATE_PAUSED).build());
  864. // Will record current player progress when losing the audio focus.
  865. mCurrentPosition = getCurrentPosition();
  866. }
  867. private void audioLossFocusTransientHandler() {
  868. // In this case, we already have lost the audio focus, and we cannot duck.
  869. // So the player will be paused immediately, but different with the previous state, there is
  870. // no need to stop the player.
  871. mPlayer.pause();
  872. // update playback state
  873. mMediaSession.setPlaybackState(createPlaybackStateBuilder(
  874. PlaybackStateCompat.STATE_PAUSED).build());
  875. // Will record current player progress when lossing the audio focus.
  876. mCurrentPosition = getCurrentPosition();
  877. }
  878. private void audioLossFocusTransientCanDuckHanlder() {
  879. // In this case, we have lots the audio focus, but since we can duck
  880. // the music item can continue to play but the volume will be reduced
  881. mPlayer.setVolume(REDUCED_VOLUME, REDUCED_VOLUME);
  882. }
  883. private void audioFocusGainHandler() {
  884. // In this case the app has been granted audio focus again
  885. // Firstly, raise volume to normal
  886. mPlayer.setVolume(FULL_VOLUME, FULL_VOLUME);
  887. // If the recorded position is the same as current position
  888. // Start the player directly
  889. if (mCurrentPosition == mPlayer.getCurrentPosition()) {
  890. mPlayer.start();
  891. mMediaSession.setPlaybackState(createPlaybackStateBuilder(
  892. PlaybackStateCompat.STATE_PLAYING).build());
  893. // If the recorded position is not equal to current position
  894. // The player will seek to the last recorded position firstly to continue playing the
  895. // last music item
  896. } else {
  897. mPlayer.seekTo(mCurrentPosition);
  898. PlaybackStateCompat.Builder builder = createPlaybackStateBuilder(
  899. PlaybackStateCompat.STATE_BUFFERING);
  900. builder.setBufferedPosition(mBufferedProgress);
  901. mMediaSession.setPlaybackState(builder.build());
  902. }
  903. }
  904. }