close
The Wayback Machine - https://web.archive.org/web/20171110012112/https://developer.android.com/samples/MediaBrowserService/src/com.example.android.mediabrowserservice/MusicService.html
Skip to content

Most visited

Recently visited

navigation
MediaBrowserService / src / com.example.android.mediabrowserservice /

MusicService.java

1
 /*
2
 * Copyright (C) 2014 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
 
17
package com.example.android.mediabrowserservice;
18
 
19
 import android.app.PendingIntent;
20
 import android.content.Context;
21
 import android.content.Intent;
22
 import android.graphics.Bitmap;
23
 import android.media.MediaDescription;
24
 import android.media.MediaMetadata;
25
 import android.media.browse.MediaBrowser.MediaItem;
26
 import android.media.session.MediaSession;
27
 import android.media.session.PlaybackState;
28
 import android.net.Uri;
29
 import android.os.Bundle;
30
 import android.os.Handler;
31
 import android.os.Message;
32
 import android.os.SystemClock;
33
 import android.service.media.MediaBrowserService;
34
 import android.text.TextUtils;
35
 
36
 import com.example.android.mediabrowserservice.model.MusicProvider;
37
 import com.example.android.mediabrowserservice.utils.CarHelper;
38
 import com.example.android.mediabrowserservice.utils.LogHelper;
39
 import com.example.android.mediabrowserservice.utils.MediaIDHelper;
40
 import com.example.android.mediabrowserservice.utils.QueueHelper;
41
 
42
 import java.lang.ref.WeakReference;
43
 import java.util.ArrayList;
44
 import java.util.Collections;
45
 import java.util.List;
46
 
47
 import static com.example.android.mediabrowserservice.utils.MediaIDHelper.MEDIA_ID_MUSICS_BY_GENRE;
48
 import static com.example.android.mediabrowserservice.utils.MediaIDHelper.MEDIA_ID_ROOT;
49
 import static com.example.android.mediabrowserservice.utils.MediaIDHelper.createBrowseCategoryMediaID;
50
 
51
 /**
52
  * This class provides a MediaBrowser through a service. It exposes the media library to a browsing
53
  * client, through the onGetRoot and onLoadChildren methods. It also creates a MediaSession and
54
  * exposes it through its MediaSession.Token, which allows the client to create a MediaController
55
  * that connects to and send control commands to the MediaSession remotely. This is useful for
56
  * user interfaces that need to interact with your media session, like Android Auto. You can
57
  * (should) also use the same service from your app's UI, which gives a seamless playback
58
  * experience to the user.
59
  *
60
  * To implement a MediaBrowserService, you need to:
61
  *
62
  * <ul>
63
  *
64
  * <li> Extend {@link android.service.media.MediaBrowserService}, implementing the media browsing
65
  *      related methods {@link android.service.media.MediaBrowserService#onGetRoot} and
66
  *      {@link android.service.media.MediaBrowserService#onLoadChildren};
67
  * <li> In onCreate, start a new {@link android.media.session.MediaSession} and notify its parent
68
  *      with the session's token {@link android.service.media.MediaBrowserService#setSessionToken};
69
  *
70
  * <li> Set a callback on the
71
  *      {@link android.media.session.MediaSession#setCallback(android.media.session.MediaSession.Callback)}.
72
  *      The callback will receive all the user's actions, like play, pause, etc;
73
  *
74
  * <li> Handle all the actual music playing using any method your app prefers (for example,
75
  *      {@link android.media.MediaPlayer})
76
  *
77
  * <li> Update playbackState, "now playing" metadata and queue, using MediaSession proper methods
78
  *      {@link android.media.session.MediaSession#setPlaybackState(android.media.session.PlaybackState)}
79
  *      {@link android.media.session.MediaSession#setMetadata(android.media.MediaMetadata)} and
80
  *      {@link android.media.session.MediaSession#setQueue(java.util.List)})
81
  *
82
  * <li> Declare and export the service in AndroidManifest with an intent receiver for the action
83
  *      android.media.browse.MediaBrowserService
84
  *
85
  * </ul>
86
  *
87
  * To make your app compatible with Android Auto, you also need to:
88
  *
89
  * <ul>
90
  *
91
  * <li> Declare a meta-data tag in AndroidManifest.xml linking to a xml resource
92
  *      with a &lt;automotiveApp&gt; root element. For a media app, this must include
93
  *      an &lt;uses name="media"/&gt; element as a child.
94
  *      For example, in AndroidManifest.xml:
95
  *          &lt;meta-data android:name="com.google.android.gms.car.application"
96
  *              android:resource="@xml/automotive_app_desc"/&gt;
97
  *      And in res/values/automotive_app_desc.xml:
98
  *          &lt;automotiveApp&gt;
99
  *              &lt;uses name="media"/&gt;
100
  *          &lt;/automotiveApp&gt;
101
  *
102
  * </ul>
103
 
104
  * @see <a href="README.md">README.md</a> for more details.
105
  *
106
  */
107
 
108
 public class MusicService extends MediaBrowserService implements Playback.Callback {
109
 
110
     // The action of the incoming Intent indicating that it contains a command
111
     // to be executed (see {@link #onStartCommand})
112
     public static final String ACTION_CMD = "com.example.android.mediabrowserservice.ACTION_CMD";
113
     // The key in the extras of the incoming Intent indicating the command that
114
     // should be executed (see {@link #onStartCommand})
115
     public static final String CMD_NAME = "CMD_NAME";
116
     // A value of a CMD_NAME key in the extras of the incoming Intent that
117
     // indicates that the music playback should be paused (see {@link #onStartCommand})
118
     public static final String CMD_PAUSE = "CMD_PAUSE";
119
 
120
     private static final String TAG = LogHelper.makeLogTag(MusicService.class);
121
     // Action to thumbs up a media item
122
     private static final String CUSTOM_ACTION_THUMBS_UP =
123
         "com.example.android.mediabrowserservice.THUMBS_UP";
124
     // Delay stopSelf by using a handler.
125
     private static final int STOP_DELAY = 30000;
126
 
127
     // Music catalog manager
128
     private MusicProvider mMusicProvider;
129
     private MediaSession mSession;
130
     // "Now playing" queue:
131
     private List<MediaSession.QueueItem> mPlayingQueue;
132
     private int mCurrentIndexOnQueue;
133
     private MediaNotificationManager mMediaNotificationManager;
134
     // Indicates whether the service was started.
135
     private boolean mServiceStarted;
136
     private DelayedStopHandler mDelayedStopHandler = new DelayedStopHandler(this);
137
     private Playback mPlayback;
138
     private PackageValidator mPackageValidator;
139
 
140
     /*
141
      * (non-Javadoc)
142
      * @see android.app.Service#onCreate()
143
      */
144
     @Override
145
     public void onCreate() {
146
         super.onCreate();
147
         LogHelper.d(TAG, "onCreate");
148
 
149
         mPlayingQueue = new ArrayList<>();
150
         mMusicProvider = new MusicProvider();
151
         mPackageValidator = new PackageValidator(this);
152
 
153
         // Start a new MediaSession
154
         mSession = new MediaSession(this, "MusicService");
155
         setSessionToken(mSession.getSessionToken());
156
         mSession.setCallback(new MediaSessionCallback());
157
         mSession.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS |
158
             MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
159
 
160
         mPlayback = new Playback(this, mMusicProvider);
161
         mPlayback.setState(PlaybackState.STATE_NONE);
162
         mPlayback.setCallback(this);
163
         mPlayback.start();
164
 
165
         Context context = getApplicationContext();
166
         Intent intent = new Intent(context, MusicPlayerActivity.class);
167
         PendingIntent pi = PendingIntent.getActivity(context, 99 /*request code*/,
168
                 intent, PendingIntent.FLAG_UPDATE_CURRENT);
169
         mSession.setSessionActivity(pi);
170
 
171
         Bundle extras = new Bundle();
172
         CarHelper.setSlotReservationFlags(extras, true, true, true);
173
         mSession.setExtras(extras);
174
 
175
         updatePlaybackState(null);
176
 
177
         mMediaNotificationManager = new MediaNotificationManager(this);
178
     }
179
 
180
     /**
181
      * (non-Javadoc)
182
      * @see android.app.Service#onStartCommand(android.content.Intent, int, int)
183
      */
184
     @Override
185
     public int onStartCommand(Intent startIntent, int flags, int startId) {
186
         if (startIntent != null) {
187
             String action = startIntent.getAction();
188
             String command = startIntent.getStringExtra(CMD_NAME);
189
             if (ACTION_CMD.equals(action)) {
190
                 if (CMD_PAUSE.equals(command)) {
191
                     if (mPlayback != null && mPlayback.isPlaying()) {
192
                         handlePauseRequest();
193
                     }
194
                 }
195
             }
196
         }
197
         return START_STICKY;
198
     }
199
 
200
     /**
201
      * (non-Javadoc)
202
      * @see android.app.Service#onDestroy()
203
      */
204
     @Override
205
     public void onDestroy() {
206
         LogHelper.d(TAG, "onDestroy");
207
         // Service is being killed, so make sure we release our resources
208
         handleStopRequest(null);
209
 
210
         mDelayedStopHandler.removeCallbacksAndMessages(null);
211
         // Always release the MediaSession to clean up resources
212
         // and notify associated MediaController(s).
213
         mSession.release();
214
     }
215
 
216
     @Override
217
     public BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) {
218
         LogHelper.d(TAG, "OnGetRoot: clientPackageName=" + clientPackageName,
219
                 "; clientUid=" + clientUid + " ; rootHints=", rootHints);
220
         // To ensure you are not allowing any arbitrary app to browse your app's contents, you
221
         // need to check the origin:
222
         if (!mPackageValidator.isCallerAllowed(this, clientPackageName, clientUid)) {
223
             // If the request comes from an untrusted package, return null. No further calls will
224
             // be made to other media browsing methods.
225
             LogHelper.w(TAG, "OnGetRoot: IGNORING request from untrusted package "
226
                     + clientPackageName);
227
             return null;
228
         }
229
         //noinspection StatementWithEmptyBody
230
         if (CarHelper.isValidCarPackage(clientPackageName)) {
231
             // Optional: if your app needs to adapt ads, music library or anything else that
232
             // needs to run differently when connected to the car, this is where you should handle
233
             // it.
234
         }
235
         return new BrowserRoot(MEDIA_ID_ROOT, null);
236
     }
237
 
238
     @Override
239
     public void onLoadChildren(final String parentMediaId, final Result<List<MediaItem>> result) {
240
         if (!mMusicProvider.isInitialized()) {
241
             // Use result.detach to allow calling result.sendResult from another thread:
242
             result.detach();
243
 
244
             mMusicProvider.retrieveMediaAsync(new MusicProvider.Callback() {
245
                 @Override
246
                 public void onMusicCatalogReady(boolean success) {
247
                     if (success) {
248
                         loadChildrenImpl(parentMediaId, result);
249
                     } else {
250
                         updatePlaybackState(getString(R.string.error_no_metadata));
251
                         result.sendResult(Collections.<MediaItem>emptyList());
252
                     }
253
                 }
254
             });
255
 
256
         } else {
257
             // If our music catalog is already loaded/cached, load them into result immediately
258
             loadChildrenImpl(parentMediaId, result);
259
         }
260
     }
261
 
262
     /**
263
      * Actual implementation of onLoadChildren that assumes that MusicProvider is already
264
      * initialized.
265
      */
266
     private void loadChildrenImpl(final String parentMediaId,
267
                                   final Result<List<MediaItem>> result) {
268
         LogHelper.d(TAG, "OnLoadChildren: parentMediaId=", parentMediaId);
269
 
270
         List<MediaItem> mediaItems = new ArrayList<>();
271
 
272
         if (MEDIA_ID_ROOT.equals(parentMediaId)) {
273
             LogHelper.d(TAG, "OnLoadChildren.ROOT");
274
             mediaItems.add(new MediaItem(
275
                     new MediaDescription.Builder()
276
                         .setMediaId(MEDIA_ID_MUSICS_BY_GENRE)
277
                         .setTitle(getString(R.string.browse_genres))
278
                         .setIconUri(Uri.parse("android.resource://" +
279
                             "com.example.android.mediabrowserservice/drawable/ic_by_genre"))
280
                         .setSubtitle(getString(R.string.browse_genre_subtitle))
281
                         .build(), MediaItem.FLAG_BROWSABLE
282
             ));
283
 
284
         } else if (MEDIA_ID_MUSICS_BY_GENRE.equals(parentMediaId)) {
285
             LogHelper.d(TAG, "OnLoadChildren.GENRES");
286
             for (String genre : mMusicProvider.getGenres()) {
287
                 MediaItem item = new MediaItem(
288
                     new MediaDescription.Builder()
289
                         .setMediaId(createBrowseCategoryMediaID(MEDIA_ID_MUSICS_BY_GENRE, genre))
290
                         .setTitle(genre)
291
                         .setSubtitle(getString(R.string.browse_musics_by_genre_subtitle, genre))
292
                         .build(), MediaItem.FLAG_BROWSABLE
293
                 );
294
                 mediaItems.add(item);
295
             }
296
 
297
         } else if (parentMediaId.startsWith(MEDIA_ID_MUSICS_BY_GENRE)) {
298
             String genre = MediaIDHelper.getHierarchy(parentMediaId)[1];
299
             LogHelper.d(TAG, "OnLoadChildren.SONGS_BY_GENRE  genre=", genre);
300
             for (MediaMetadata track : mMusicProvider.getMusicsByGenre(genre)) {
301
                 // Since mediaMetadata fields are immutable, we need to create a copy, so we
302
                 // can set a hierarchy-aware mediaID. We will need to know the media hierarchy
303
                 // when we get a onPlayFromMusicID call, so we can create the proper queue based
304
                 // on where the music was selected from (by artist, by genre, random, etc)
305
                 String hierarchyAwareMediaID = MediaIDHelper.createMediaID(
306
                         track.getDescription().getMediaId(), MEDIA_ID_MUSICS_BY_GENRE, genre);
307
                 MediaMetadata trackCopy = new MediaMetadata.Builder(track)
308
                         .putString(MediaMetadata.METADATA_KEY_MEDIA_ID, hierarchyAwareMediaID)
309
                         .build();
310
                 MediaItem bItem = new MediaItem(
311
                         trackCopy.getDescription(), MediaItem.FLAG_PLAYABLE);
312
                 mediaItems.add(bItem);
313
             }
314
         } else {
315
             LogHelper.w(TAG, "Skipping unmatched parentMediaId: ", parentMediaId);
316
         }
317
         LogHelper.d(TAG, "OnLoadChildren sending ", mediaItems.size(),
318
                 " results for ", parentMediaId);
319
         result.sendResult(mediaItems);
320
     }
321
 
322
     private final class MediaSessionCallback extends MediaSession.Callback {
323
         @Override
324
         public void onPlay() {
325
             LogHelper.d(TAG, "play");
326
 
327
             if (mPlayingQueue == null || mPlayingQueue.isEmpty()) {
328
                 mPlayingQueue = QueueHelper.getRandomQueue(mMusicProvider);
329
                 mSession.setQueue(mPlayingQueue);
330
                 mSession.setQueueTitle(getString(R.string.random_queue_title));
331
                 // start playing from the beginning of the queue
332
                 mCurrentIndexOnQueue = 0;
333
             }
334
 
335
             if (mPlayingQueue != null && !mPlayingQueue.isEmpty()) {
336
                 handlePlayRequest();
337
             }
338
         }
339
 
340
         @Override
341
         public void onSkipToQueueItem(long queueId) {
342
             LogHelper.d(TAG, "OnSkipToQueueItem:" + queueId);
343
 
344
             if (mPlayingQueue != null && !mPlayingQueue.isEmpty()) {
345
                 // set the current index on queue from the music Id:
346
                 mCurrentIndexOnQueue = QueueHelper.getMusicIndexOnQueue(mPlayingQueue, queueId);
347
                 // play the music
348
                 handlePlayRequest();
349
             }
350
         }
351
 
352
         @Override
353
         public void onSeekTo(long position) {
354
             LogHelper.d(TAG, "onSeekTo:", position);
355
             mPlayback.seekTo((int) position);
356
         }
357
 
358
         @Override
359
         public void onPlayFromMediaId(String mediaId, Bundle extras) {
360
             LogHelper.d(TAG, "playFromMediaId mediaId:", mediaId, "  extras=", extras);
361
 
362
             // The mediaId used here is not the unique musicId. This one comes from the
363
             // MediaBrowser, and is actually a "hierarchy-aware mediaID": a concatenation of
364
             // the hierarchy in MediaBrowser and the actual unique musicID. This is necessary
365
             // so we can build the correct playing queue, based on where the track was
366
             // selected from.
367
             mPlayingQueue = QueueHelper.getPlayingQueue(mediaId, mMusicProvider);
368
             mSession.setQueue(mPlayingQueue);
369
             String queueTitle = getString(R.string.browse_musics_by_genre_subtitle,
370
                     MediaIDHelper.extractBrowseCategoryValueFromMediaID(mediaId));
371
             mSession.setQueueTitle(queueTitle);
372
 
373
             if (mPlayingQueue != null && !mPlayingQueue.isEmpty()) {
374
                 // set the current index on queue from the media Id:
375
                 mCurrentIndexOnQueue = QueueHelper.getMusicIndexOnQueue(mPlayingQueue, mediaId);
376
 
377
                 if (mCurrentIndexOnQueue < 0) {
378
                     LogHelper.e(TAG, "playFromMediaId: media ID ", mediaId,
379
                             " could not be found on queue. Ignoring.");
380
                 } else {
381
                     // play the music
382
                     handlePlayRequest();
383
                 }
384
             }
385
         }
386
 
387
         @Override
388
         public void onPause() {
389
             LogHelper.d(TAG, "pause. current state=" + mPlayback.getState());
390
             handlePauseRequest();
391
         }
392
 
393
         @Override
394
         public void onStop() {
395
             LogHelper.d(TAG, "stop. current state=" + mPlayback.getState());
396
             handleStopRequest(null);
397
         }
398
 
399
         @Override
400
         public void onSkipToNext() {
401
             LogHelper.d(TAG, "skipToNext");
402
             mCurrentIndexOnQueue++;
403
             if (mPlayingQueue != null && mCurrentIndexOnQueue >= mPlayingQueue.size()) {
404
                 // This sample's behavior: skipping to next when in last song returns to the
405
                 // first song.
406
                 mCurrentIndexOnQueue = 0;
407
             }
408
             if (QueueHelper.isIndexPlayable(mCurrentIndexOnQueue, mPlayingQueue)) {
409
                 handlePlayRequest();
410
             } else {
411
                 LogHelper.e(TAG, "skipToNext: cannot skip to next. next Index=" +
412
                         mCurrentIndexOnQueue + " queue length=" +
413
                         (mPlayingQueue == null ? "null" : mPlayingQueue.size()));
414
                 handleStopRequest("Cannot skip");
415
             }
416
         }
417
 
418
         @Override
419
         public void onSkipToPrevious() {
420
             LogHelper.d(TAG, "skipToPrevious");
421
             mCurrentIndexOnQueue--;
422
             if (mPlayingQueue != null && mCurrentIndexOnQueue < 0) {
423
                 // This sample's behavior: skipping to previous when in first song restarts the
424
                 // first song.
425
                 mCurrentIndexOnQueue = 0;
426
             }
427
             if (QueueHelper.isIndexPlayable(mCurrentIndexOnQueue, mPlayingQueue)) {
428
                 handlePlayRequest();
429
             } else {
430
                 LogHelper.e(TAG, "skipToPrevious: cannot skip to previous. previous Index=" +
431
                         mCurrentIndexOnQueue + " queue length=" +
432
                         (mPlayingQueue == null ? "null" : mPlayingQueue.size()));
433
                 handleStopRequest("Cannot skip");
434
             }
435
         }
436
 
437
         @Override
438
         public void onCustomAction(String action, Bundle extras) {
439
             if (CUSTOM_ACTION_THUMBS_UP.equals(action)) {
440
                 LogHelper.i(TAG, "onCustomAction: favorite for current track");
441
                 MediaMetadata track = getCurrentPlayingMusic();
442
                 if (track != null) {
443
                     String musicId = track.getString(MediaMetadata.METADATA_KEY_MEDIA_ID);
444
                     mMusicProvider.setFavorite(musicId, !mMusicProvider.isFavorite(musicId));
445
                 }
446
                 // playback state needs to be updated because the "Favorite" icon on the
447
                 // custom action will change to reflect the new favorite state.
448
                 updatePlaybackState(null);
449
             } else {
450
                 LogHelper.e(TAG, "Unsupported action: ", action);
451
             }
452
         }
453
 
454
         @Override
455
         public void onPlayFromSearch(String query, Bundle extras) {
456
             LogHelper.d(TAG, "playFromSearch  query=", query);
457
 
458
             if (TextUtils.isEmpty(query)) {
459
                 // A generic search like "Play music" sends an empty query
460
                 // and it's expected that we start playing something. What will be played depends
461
                 // on the app: favorite playlist, "I'm feeling lucky", most recent, etc.
462
                 mPlayingQueue = QueueHelper.getRandomQueue(mMusicProvider);
463
             } else {
464
                 mPlayingQueue = QueueHelper.getPlayingQueueFromSearch(query, mMusicProvider);
465
             }
466
 
467
             LogHelper.d(TAG, "playFromSearch  playqueue.length=" + mPlayingQueue.size());
468
             mSession.setQueue(mPlayingQueue);
469
 
470
             if (mPlayingQueue != null && !mPlayingQueue.isEmpty()) {
471
                 // immediately start playing from the beginning of the search results
472
                 mCurrentIndexOnQueue = 0;
473
 
474
                 handlePlayRequest();
475
             } else {
476
                 // if nothing was found, we need to warn the user and stop playing
477
                 handleStopRequest(getString(R.string.no_search_results));
478
             }
479
         }
480
     }
481
 
482
     /**
483
      * Handle a request to play music
484
      */
485
     private void handlePlayRequest() {
486
         LogHelper.d(TAG, "handlePlayRequest: mState=" + mPlayback.getState());
487
 
488
         mDelayedStopHandler.removeCallbacksAndMessages(null);
489
         if (!mServiceStarted) {
490
             LogHelper.v(TAG, "Starting service");
491
             // The MusicService needs to keep running even after the calling MediaBrowser
492
             // is disconnected. Call startService(Intent) and then stopSelf(..) when we no longer
493
             // need to play media.
494
             startService(new Intent(getApplicationContext(), MusicService.class));
495
             mServiceStarted = true;
496
         }
497
 
498
         if (!mSession.isActive()) {
499
             mSession.setActive(true);
500
         }
501
 
502
         if (QueueHelper.isIndexPlayable(mCurrentIndexOnQueue, mPlayingQueue)) {
503
             updateMetadata();
504
             mPlayback.play(mPlayingQueue.get(mCurrentIndexOnQueue));
505
         }
506
     }
507
 
508
     /**
509
      * Handle a request to pause music
510
      */
511
     private void handlePauseRequest() {
512
         LogHelper.d(TAG, "handlePauseRequest: mState=" + mPlayback.getState());
513
         mPlayback.pause();
514
         // reset the delayed stop handler.
515
         mDelayedStopHandler.removeCallbacksAndMessages(null);
516
         mDelayedStopHandler.sendEmptyMessageDelayed(0, STOP_DELAY);
517
     }
518
 
519
     /**
520
      * Handle a request to stop music
521
      */
522
     private void handleStopRequest(String withError) {
523
         LogHelper.d(TAG, "handleStopRequest: mState=" + mPlayback.getState() + " error=", withError);
524
         mPlayback.stop(true);
525
         // reset the delayed stop handler.
526
         mDelayedStopHandler.removeCallbacksAndMessages(null);
527
         mDelayedStopHandler.sendEmptyMessageDelayed(0, STOP_DELAY);
528
 
529
         updatePlaybackState(withError);
530
 
531
         // service is no longer necessary. Will be started again if needed.
532
         stopSelf();
533
         mServiceStarted = false;
534
     }
535
 
536
     private void updateMetadata() {
537
         if (!QueueHelper.isIndexPlayable(mCurrentIndexOnQueue, mPlayingQueue)) {
538
             LogHelper.e(TAG, "Can't retrieve current metadata.");
539
             updatePlaybackState(getResources().getString(R.string.error_no_metadata));
540
             return;
541
         }
542
         MediaSession.QueueItem queueItem = mPlayingQueue.get(mCurrentIndexOnQueue);
543
         String musicId = MediaIDHelper.extractMusicIDFromMediaID(
544
                 queueItem.getDescription().getMediaId());
545
         MediaMetadata track = mMusicProvider.getMusic(musicId);
546
         final String trackId = track.getString(MediaMetadata.METADATA_KEY_MEDIA_ID);
547
         if (!musicId.equals(trackId)) {
548
             IllegalStateException e = new IllegalStateException("track ID should match musicId.");
549
             LogHelper.e(TAG, "track ID should match musicId.",
550
                 " musicId=", musicId, " trackId=", trackId,
551
                 " mediaId from queueItem=", queueItem.getDescription().getMediaId(),
552
                 " title from queueItem=", queueItem.getDescription().getTitle(),
553
                 " mediaId from track=", track.getDescription().getMediaId(),
554
                 " title from track=", track.getDescription().getTitle(),
555
                 " source.hashcode from track=", track.getString(
556
                     MusicProvider.CUSTOM_METADATA_TRACK_SOURCE).hashCode(),
557
                 e);
558
             throw e;
559
         }
560
         LogHelper.d(TAG, "Updating metadata for MusicID= " + musicId);
561
         mSession.setMetadata(track);
562
 
563
         // Set the proper album artwork on the media session, so it can be shown in the
564
         // locked screen and in other places.
565
         if (track.getDescription().getIconBitmap() == null &&
566
                 track.getDescription().getIconUri() != null) {
567
             String albumUri = track.getDescription().getIconUri().toString();
568
             AlbumArtCache.getInstance().fetch(albumUri, new AlbumArtCache.FetchListener() {
569
                 @Override
570
                 public void onFetched(String artUrl, Bitmap bitmap, Bitmap icon) {
571
                     MediaSession.QueueItem queueItem = mPlayingQueue.get(mCurrentIndexOnQueue);
572
                     MediaMetadata track = mMusicProvider.getMusic(trackId);
573
                     track = new MediaMetadata.Builder(track)
574
 
575
                         // set high resolution bitmap in METADATA_KEY_ALBUM_ART. This is used, for
576
                         // example, on the lockscreen background when the media session is active.
577
                         .putBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART, bitmap)
578
 
579
                         // set small version of the album art in the DISPLAY_ICON. This is used on
580
                         // the MediaDescription and thus it should be small to be serialized if
581
                         // necessary..
582
                         .putBitmap(MediaMetadata.METADATA_KEY_DISPLAY_ICON, icon)
583
 
584
                         .build();
585
 
586
                     mMusicProvider.updateMusic(trackId, track);
587
 
588
                     // If we are still playing the same music
589
                     String currentPlayingId = MediaIDHelper.extractMusicIDFromMediaID(
590
                         queueItem.getDescription().getMediaId());
591
                     if (trackId.equals(currentPlayingId)) {
592
                         mSession.setMetadata(track);
593
                     }
594
                 }
595
             });
596
         }
597
     }
598
 
599
     /**
600
      * Update the current media player state, optionally showing an error message.
601
      *
602
      * @param error if not null, error message to present to the user.
603
      */
604
     private void updatePlaybackState(String error) {
605
         LogHelper.d(TAG, "updatePlaybackState, playback state=" + mPlayback.getState());
606
         long position = PlaybackState.PLAYBACK_POSITION_UNKNOWN;
607
         if (mPlayback != null && mPlayback.isConnected()) {
608
             position = mPlayback.getCurrentStreamPosition();
609
         }
610
 
611
         PlaybackState.Builder stateBuilder = new PlaybackState.Builder()
612
                 .setActions(getAvailableActions());
613
 
614
         setCustomAction(stateBuilder);
615
         int state = mPlayback.getState();
616
 
617
         // If there is an error message, send it to the playback state:
618
         if (error != null) {
619
             // Error states are really only supposed to be used for errors that cause playback to
620
             // stop unexpectedly and persist until the user takes action to fix it.
621
             stateBuilder.setErrorMessage(error);
622
             state = PlaybackState.STATE_ERROR;
623
         }
624
         stateBuilder.setState(state, position, 1.0f, SystemClock.elapsedRealtime());
625
 
626
         // Set the activeQueueItemId if the current index is valid.
627
         if (QueueHelper.isIndexPlayable(mCurrentIndexOnQueue, mPlayingQueue)) {
628
             MediaSession.QueueItem item = mPlayingQueue.get(mCurrentIndexOnQueue);
629
             stateBuilder.setActiveQueueItemId(item.getQueueId());
630
         }
631
 
632
         mSession.setPlaybackState(stateBuilder.build());
633
 
634
         if (state == PlaybackState.STATE_PLAYING || state == PlaybackState.STATE_PAUSED) {
635
             mMediaNotificationManager.startNotification();
636
         }
637
     }
638
 
639
     private void setCustomAction(PlaybackState.Builder stateBuilder) {
640
         MediaMetadata currentMusic = getCurrentPlayingMusic();
641
         if (currentMusic != null) {
642
             // Set appropriate "Favorite" icon on Custom action:
643
             String musicId = currentMusic.getString(MediaMetadata.METADATA_KEY_MEDIA_ID);
644
             int favoriteIcon = R.drawable.ic_star_off;
645
             if (mMusicProvider.isFavorite(musicId)) {
646
                 favoriteIcon = R.drawable.ic_star_on;
647
             }
648
             LogHelper.d(TAG, "updatePlaybackState, setting Favorite custom action of music ",
649
                     musicId, " current favorite=", mMusicProvider.isFavorite(musicId));
650
             stateBuilder.addCustomAction(CUSTOM_ACTION_THUMBS_UP, getString(R.string.favorite),
651
                 favoriteIcon);
652
         }
653
     }
654
 
655
     private long getAvailableActions() {
656
         long actions = PlaybackState.ACTION_PLAY | PlaybackState.ACTION_PLAY_FROM_MEDIA_ID |
657
                 PlaybackState.ACTION_PLAY_FROM_SEARCH;
658
         if (mPlayingQueue == null || mPlayingQueue.isEmpty()) {
659
             return actions;
660
         }
661
         if (mPlayback.isPlaying()) {
662
             actions |= PlaybackState.ACTION_PAUSE;
663
         }
664
         if (mCurrentIndexOnQueue > 0) {
665
             actions |= PlaybackState.ACTION_SKIP_TO_PREVIOUS;
666
         }
667
         if (mCurrentIndexOnQueue < mPlayingQueue.size() - 1) {
668
             actions |= PlaybackState.ACTION_SKIP_TO_NEXT;
669
         }
670
         return actions;
671
     }
672
 
673
     private MediaMetadata getCurrentPlayingMusic() {
674
         if (QueueHelper.isIndexPlayable(mCurrentIndexOnQueue, mPlayingQueue)) {
675
             MediaSession.QueueItem item = mPlayingQueue.get(mCurrentIndexOnQueue);
676
             if (item != null) {
677
                 LogHelper.d(TAG, "getCurrentPlayingMusic for musicId=",
678
                         item.getDescription().getMediaId());
679
                 return mMusicProvider.getMusic(
680
                         MediaIDHelper.extractMusicIDFromMediaID(item.getDescription().getMediaId()));
681
             }
682
         }
683
         return null;
684
     }
685
 
686
     /**
687
      * Implementation of the Playback.Callback interface
688
      */
689
     @Override
690
     public void onCompletion() {
691
         // The media player finished playing the current song, so we go ahead
692
         // and start the next.
693
         if (mPlayingQueue != null && !mPlayingQueue.isEmpty()) {
694
             // In this sample, we restart the playing queue when it gets to the end:
695
             mCurrentIndexOnQueue++;
696
             if (mCurrentIndexOnQueue >= mPlayingQueue.size()) {
697
                 mCurrentIndexOnQueue = 0;
698
             }
699
             handlePlayRequest();
700
         } else {
701
             // If there is nothing to play, we stop and release the resources:
702
             handleStopRequest(null);
703
         }
704
     }
705
 
706
     @Override
707
     public void onPlaybackStatusChanged(int state) {
708
         updatePlaybackState(null);
709
     }
710
 
711
     @Override
712
     public void onError(String error) {
713
         updatePlaybackState(error);
714
     }
715
 
716
     /**
717
      * A simple handler that stops the service if playback is not active (playing)
718
      */
719
     private static class DelayedStopHandler extends Handler {
720
         private final WeakReference<MusicService> mWeakReference;
721
 
722
         private DelayedStopHandler(MusicService service) {
723
             mWeakReference = new WeakReference<>(service);
724
         }
725
 
726
         @Override
727
         public void handleMessage(Message msg) {
728
             MusicService service = mWeakReference.get();
729
             if (service != null && service.mPlayback != null) {
730
                 if (service.mPlayback.isPlaying()) {
731
                     LogHelper.d(TAG, "Ignoring delayed stop since the media player is in use.");
732
                     return;
733
                 }
734
                 LogHelper.d(TAG, "Stopping service with delay handler.");
735
                 service.stopSelf();
736
                 service.mServiceStarted = false;
737
             }
738
         }
739
     }
740
 }
This site uses cookies to store your preferences for site-specific language and display options.

Get the latest Android developer news and tips that will help you find success on Google Play.

* Required Fields

Hooray!

Follow Google Developers on WeChat

BERJAYA

Browse this site in ?

You requested a page in , but your language preference for this site is .

Would you like to change your language preference and browse this site in ? If you want to change your language preference later, use the language menu at the bottom of each page.

This class requires API level or higher

This doc is hidden because your selected API level for the documentation is . You can change the documentation API level with the selector above the left navigation.

For more information about specifying the API level your app requires, read Supporting Different Platform Versions.

Take a short survey?
Help us improve the Android developer experience.
(Sep 2017 survey)