1 /* 2 * Copyright (C) 2007 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.android.music; 18 19 import android.app.Notification; 20 import android.app.PendingIntent; 21 import android.app.Service; 22 import android.appwidget.AppWidgetManager; 23 import android.content.ComponentName; 24 import android.content.ContentResolver; 25 import android.content.ContentUris; 26 import android.content.ContentValues; 27 import android.content.Context; 28 import android.content.Intent; 29 import android.content.IntentFilter; 30 import android.content.BroadcastReceiver; 31 import android.content.SharedPreferences; 32 import android.content.SharedPreferences.Editor; 33 import android.database.Cursor; 34 import android.database.sqlite.SQLiteException; 35 import android.graphics.Bitmap; 36 import android.media.audiofx.AudioEffect; 37 import android.media.AudioManager; 38 import android.media.AudioManager.OnAudioFocusChangeListener; 39 import android.media.MediaMetadataRetriever; 40 import android.media.MediaPlayer; 41 import android.media.MediaPlayer.OnCompletionListener; 42 import android.media.RemoteControlClient; 43 import android.media.RemoteControlClient.MetadataEditor; 44 import android.net.Uri; 45 import android.os.Handler; 46 import android.os.IBinder; 47 import android.os.Message; 48 import android.os.PowerManager; 49 import android.os.SystemClock; 50 import android.os.PowerManager.WakeLock; 51 import android.provider.MediaStore; 52 import android.util.Log; 53 import android.widget.RemoteViews; 54 import android.widget.Toast; 55 56 import java.io.FileDescriptor; 57 import java.io.IOException; 58 import java.io.PrintWriter; 59 import java.lang.ref.WeakReference; 60 import java.util.Random; 61 import java.util.Vector; 62 63 /** 64 * Provides "background" audio playback capabilities, allowing the 65 * user to switch between activities without stopping playback. 66 */ 67 public class MediaPlaybackService extends Service { 68 /** used to specify whether enqueue() should start playing 69 * the new list of files right away, next or once all the currently 70 * queued files have been played 71 */ 72 public static final int NOW = 1; 73 public static final int NEXT = 2; 74 public static final int LAST = 3; 75 public static final int PLAYBACKSERVICE_STATUS = 1; 76 77 public static final int SHUFFLE_NONE = 0; 78 public static final int SHUFFLE_NORMAL = 1; 79 public static final int SHUFFLE_AUTO = 2; 80 81 public static final int REPEAT_NONE = 0; 82 public static final int REPEAT_CURRENT = 1; 83 public static final int REPEAT_ALL = 2; 84 85 public static final String PLAYSTATE_CHANGED = "com.android.music.playstatechanged"; 86 public static final String META_CHANGED = "com.android.music.metachanged"; 87 public static final String QUEUE_CHANGED = "com.android.music.queuechanged"; 88 89 public static final String SERVICECMD = "com.android.music.musicservicecommand"; 90 public static final String CMDNAME = "command"; 91 public static final String CMDTOGGLEPAUSE = "togglepause"; 92 public static final String CMDSTOP = "stop"; 93 public static final String CMDPAUSE = "pause"; 94 public static final String CMDPLAY = "play"; 95 public static final String CMDPREVIOUS = "previous"; 96 public static final String CMDNEXT = "next"; 97 98 public static final String TOGGLEPAUSE_ACTION = 99 "com.android.music.musicservicecommand.togglepause"; 100 public static final String PAUSE_ACTION = "com.android.music.musicservicecommand.pause"; 101 public static final String PREVIOUS_ACTION = "com.android.music.musicservicecommand.previous"; 102 public static final String NEXT_ACTION = "com.android.music.musicservicecommand.next"; 103 104 private static final int TRACK_ENDED = 1; 105 private static final int RELEASE_WAKELOCK = 2; 106 private static final int SERVER_DIED = 3; 107 private static final int FOCUSCHANGE = 4; 108 private static final int FADEDOWN = 5; 109 private static final int FADEUP = 6; 110 private static final int TRACK_WENT_TO_NEXT = 7; 111 private static final int MAX_HISTORY_SIZE = 100; 112 113 private MultiPlayer mPlayer; 114 private String mFileToPlay; 115 private int mShuffleMode = SHUFFLE_NONE; 116 private int mRepeatMode = REPEAT_NONE; 117 private int mMediaMountedCount = 0; 118 private long[] mAutoShuffleList = null; 119 private long[] mPlayList = null; 120 private int mPlayListLen = 0; 121 private Vector<Integer> mHistory = new Vector<Integer>(MAX_HISTORY_SIZE); 122 private Cursor mCursor; 123 private int mPlayPos = -1; 124 private int mNextPlayPos = -1; 125 private static final String LOGTAG = "MediaPlaybackService"; 126 private final Shuffler mRand = new Shuffler(); 127 private int mOpenFailedCounter = 0; 128 String[] mCursorCols = new String[] { 129 "audio._id AS _id", // index must match IDCOLIDX below 130 MediaStore.Audio.Media.ARTIST, MediaStore.Audio.Media.ALBUM, 131 MediaStore.Audio.Media.TITLE, MediaStore.Audio.Media.DATA, 132 MediaStore.Audio.Media.MIME_TYPE, MediaStore.Audio.Media.ALBUM_ID, 133 MediaStore.Audio.Media.ARTIST_ID, 134 MediaStore.Audio.Media.IS_PODCAST, // index must match PODCASTCOLIDX below 135 MediaStore.Audio.Media.BOOKMARK // index must match BOOKMARKCOLIDX below 136 }; 137 private final static int IDCOLIDX = 0; 138 private final static int PODCASTCOLIDX = 8; 139 private final static int BOOKMARKCOLIDX = 9; 140 private BroadcastReceiver mUnmountReceiver = null; 141 private WakeLock mWakeLock; 142 private int mServiceStartId = -1; 143 private boolean mServiceInUse = false; 144 private boolean mIsSupposedToBePlaying = false; 145 private boolean mQuietMode = false; 146 private AudioManager mAudioManager; 147 private boolean mQueueIsSaveable = true; 148 // used to track what type of audio focus loss caused the playback to pause 149 private boolean mPausedByTransientLossOfFocus = false; 150 151 private SharedPreferences mPreferences; 152 // We use this to distinguish between different cards when saving/restoring playlists. 153 // This will have to change if we want to support multiple simultaneous cards. 154 private int mCardId; 155 156 private MediaAppWidgetProvider mAppWidgetProvider = MediaAppWidgetProvider.getInstance(); 157 158 // interval after which we stop the service when idle 159 private static final int IDLE_DELAY = 60000; 160 161 private RemoteControlClient mRemoteControlClient; 162 163 private Handler mMediaplayerHandler = new Handler() { 164 float mCurrentVolume = 1.0f; 165 @Override 166 public void handleMessage(Message msg) { 167 MusicUtils.debugLog("mMediaplayerHandler.handleMessage " + msg.what); 168 switch (msg.what) { 169 case FADEDOWN: 170 mCurrentVolume -= .05f; 171 if (mCurrentVolume > .2f) { 172 mMediaplayerHandler.sendEmptyMessageDelayed(FADEDOWN, 10); 173 } else { 174 mCurrentVolume = .2f; 175 } 176 mPlayer.setVolume(mCurrentVolume); 177 break; 178 case FADEUP: 179 mCurrentVolume += .01f; 180 if (mCurrentVolume < 1.0f) { 181 mMediaplayerHandler.sendEmptyMessageDelayed(FADEUP, 10); 182 } else { 183 mCurrentVolume = 1.0f; 184 } 185 mPlayer.setVolume(mCurrentVolume); 186 break; 187 case SERVER_DIED: 188 if (mIsSupposedToBePlaying) { 189 gotoNext(true); 190 } else { 191 // the server died when we were idle, so just 192 // reopen the same song (it will start again 193 // from the beginning though when the user 194 // restarts) 195 openCurrentAndNext(); 196 } 197 break; 198 case TRACK_WENT_TO_NEXT: 199 mPlayPos = mNextPlayPos; 200 if (mCursor != null) { 201 mCursor.close(); 202 mCursor = null; 203 } 204 if (mPlayPos >= 0 && mPlayPos < mPlayList.length) { 205 mCursor = getCursorForId(mPlayList[mPlayPos]); 206 } 207 notifyChange(META_CHANGED); 208 updateNotification(); 209 setNextTrack(); 210 break; 211 case TRACK_ENDED: 212 if (mRepeatMode == REPEAT_CURRENT) { 213 seek(0); 214 play(); 215 } else { 216 gotoNext(false); 217 } 218 break; 219 case RELEASE_WAKELOCK: 220 mWakeLock.release(); 221 break; 222 223 case FOCUSCHANGE: 224 // This code is here so we can better synchronize it with the code that 225 // handles fade-in 226 switch (msg.arg1) { 227 case AudioManager.AUDIOFOCUS_LOSS: 228 Log.v(LOGTAG, "AudioFocus: received AUDIOFOCUS_LOSS"); 229 if (isPlaying()) { 230 mPausedByTransientLossOfFocus = false; 231 } 232 pause(); 233 break; 234 case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: 235 mMediaplayerHandler.removeMessages(FADEUP); 236 mMediaplayerHandler.sendEmptyMessage(FADEDOWN); 237 break; 238 case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: 239 Log.v(LOGTAG, "AudioFocus: received AUDIOFOCUS_LOSS_TRANSIENT"); 240 if (isPlaying()) { 241 mPausedByTransientLossOfFocus = true; 242 } 243 pause(); 244 break; 245 case AudioManager.AUDIOFOCUS_GAIN: 246 Log.v(LOGTAG, "AudioFocus: received AUDIOFOCUS_GAIN"); 247 if (!isPlaying() && mPausedByTransientLossOfFocus) { 248 mPausedByTransientLossOfFocus = false; 249 mCurrentVolume = 0f; 250 mPlayer.setVolume(mCurrentVolume); 251 play(); // also queues a fade-in 252 } else { 253 mMediaplayerHandler.removeMessages(FADEDOWN); 254 mMediaplayerHandler.sendEmptyMessage(FADEUP); 255 } 256 break; 257 default: 258 Log.e(LOGTAG, "Unknown audio focus change code"); 259 } 260 break; 261 262 default: 263 break; 264 } 265 } 266 }; 267 268 private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { 269 @Override 270 public void onReceive(Context context, Intent intent) { 271 String action = intent.getAction(); 272 String cmd = intent.getStringExtra("command"); 273 MusicUtils.debugLog("mIntentReceiver.onReceive " + action + " / " + cmd); 274 if (CMDNEXT.equals(cmd) || NEXT_ACTION.equals(action)) { 275 gotoNext(true); 276 } else if (CMDPREVIOUS.equals(cmd) || PREVIOUS_ACTION.equals(action)) { 277 prev(); 278 } else if (CMDTOGGLEPAUSE.equals(cmd) || TOGGLEPAUSE_ACTION.equals(action)) { 279 if (isPlaying()) { 280 pause(); 281 mPausedByTransientLossOfFocus = false; 282 } else { 283 play(); 284 } 285 } else if (CMDPAUSE.equals(cmd) || PAUSE_ACTION.equals(action)) { 286 pause(); 287 mPausedByTransientLossOfFocus = false; 288 } else if (CMDPLAY.equals(cmd)) { 289 play(); 290 } else if (CMDSTOP.equals(cmd)) { 291 pause(); 292 mPausedByTransientLossOfFocus = false; 293 seek(0); 294 } else if (MediaAppWidgetProvider.CMDAPPWIDGETUPDATE.equals(cmd)) { 295 // Someone asked us to refresh a set of specific widgets, probably 296 // because they were just added. 297 int[] appWidgetIds = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS); 298 mAppWidgetProvider.performUpdate(MediaPlaybackService.this, appWidgetIds); 299 } 300 } 301 }; 302 303 private OnAudioFocusChangeListener mAudioFocusListener = new OnAudioFocusChangeListener() { 304 public void onAudioFocusChange(int focusChange) { 305 mMediaplayerHandler.obtainMessage(FOCUSCHANGE, focusChange, 0).sendToTarget(); 306 } 307 }; 308 MediaPlaybackService()309 public MediaPlaybackService() {} 310 311 @Override onCreate()312 public void onCreate() { 313 super.onCreate(); 314 315 mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); 316 ComponentName rec = 317 new ComponentName(getPackageName(), MediaButtonIntentReceiver.class.getName()); 318 mAudioManager.registerMediaButtonEventReceiver(rec); 319 320 Intent i = new Intent(Intent.ACTION_MEDIA_BUTTON); 321 i.setComponent(rec); 322 PendingIntent pi = PendingIntent.getBroadcast( 323 this /*context*/, 0 /*requestCode, ignored*/, i /*intent*/, 0 /*flags*/); 324 mRemoteControlClient = new RemoteControlClient(pi); 325 mAudioManager.registerRemoteControlClient(mRemoteControlClient); 326 327 int flags = RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS 328 | RemoteControlClient.FLAG_KEY_MEDIA_NEXT | RemoteControlClient.FLAG_KEY_MEDIA_PLAY 329 | RemoteControlClient.FLAG_KEY_MEDIA_PAUSE 330 | RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE 331 | RemoteControlClient.FLAG_KEY_MEDIA_STOP; 332 mRemoteControlClient.setTransportControlFlags(flags); 333 334 mPreferences = getSharedPreferences("Music", MODE_PRIVATE); 335 mCardId = MusicUtils.getCardId(this); 336 337 registerExternalStorageListener(); 338 339 // Needs to be done in this thread, since otherwise ApplicationContext.getPowerManager() 340 // crashes. 341 mPlayer = new MultiPlayer(); 342 mPlayer.setHandler(mMediaplayerHandler); 343 344 reloadQueue(); 345 notifyChange(QUEUE_CHANGED); 346 notifyChange(META_CHANGED); 347 348 IntentFilter commandFilter = new IntentFilter(); 349 commandFilter.addAction(SERVICECMD); 350 commandFilter.addAction(TOGGLEPAUSE_ACTION); 351 commandFilter.addAction(PAUSE_ACTION); 352 commandFilter.addAction(NEXT_ACTION); 353 commandFilter.addAction(PREVIOUS_ACTION); 354 registerReceiver(mIntentReceiver, commandFilter); 355 356 PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); 357 mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, this.getClass().getName()); 358 mWakeLock.setReferenceCounted(false); 359 360 // If the service was idle, but got killed before it stopped itself, the 361 // system will relaunch it. Make sure it gets stopped again in that case. 362 Message msg = mDelayedStopHandler.obtainMessage(); 363 mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY); 364 } 365 366 @Override onDestroy()367 public void onDestroy() { 368 // Check that we're not being destroyed while something is still playing. 369 if (isPlaying()) { 370 Log.e(LOGTAG, "Service being destroyed while still playing."); 371 } 372 // release all MediaPlayer resources, including the native player and wakelocks 373 Intent i = new Intent(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION); 374 i.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, getAudioSessionId()); 375 i.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, getPackageName()); 376 sendBroadcast(i); 377 mPlayer.release(); 378 mPlayer = null; 379 380 mAudioManager.abandonAudioFocus(mAudioFocusListener); 381 mAudioManager.unregisterRemoteControlClient(mRemoteControlClient); 382 383 // make sure there aren't any other messages coming 384 mDelayedStopHandler.removeCallbacksAndMessages(null); 385 mMediaplayerHandler.removeCallbacksAndMessages(null); 386 387 if (mCursor != null) { 388 mCursor.close(); 389 mCursor = null; 390 } 391 392 unregisterReceiver(mIntentReceiver); 393 if (mUnmountReceiver != null) { 394 unregisterReceiver(mUnmountReceiver); 395 mUnmountReceiver = null; 396 } 397 mWakeLock.release(); 398 super.onDestroy(); 399 } 400 401 private final char hexdigits[] = new char[] { 402 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; 403 saveQueue(boolean full)404 private void saveQueue(boolean full) { 405 if (!mQueueIsSaveable) { 406 return; 407 } 408 409 Editor ed = mPreferences.edit(); 410 // long start = System.currentTimeMillis(); 411 if (full) { 412 StringBuilder q = new StringBuilder(); 413 414 // The current playlist is saved as a list of "reverse hexadecimal" 415 // numbers, which we can generate faster than normal decimal or 416 // hexadecimal numbers, which in turn allows us to save the playlist 417 // more often without worrying too much about performance. 418 // (saving the full state takes about 40 ms under no-load conditions 419 // on the phone) 420 int len = mPlayListLen; 421 for (int i = 0; i < len; i++) { 422 long n = mPlayList[i]; 423 if (n < 0) { 424 continue; 425 } else if (n == 0) { 426 q.append("0;"); 427 } else { 428 while (n != 0) { 429 int digit = (int) (n & 0xf); 430 n >>>= 4; 431 q.append(hexdigits[digit]); 432 } 433 q.append(";"); 434 } 435 } 436 // Log.i("@@@@ service", "created queue string in " + (System.currentTimeMillis() - 437 // start) + " ms"); 438 ed.putString("queue", q.toString()); 439 ed.putInt("cardid", mCardId); 440 if (mShuffleMode != SHUFFLE_NONE) { 441 // In shuffle mode we need to save the history too 442 len = mHistory.size(); 443 q.setLength(0); 444 for (int i = 0; i < len; i++) { 445 int n = mHistory.get(i); 446 if (n == 0) { 447 q.append("0;"); 448 } else { 449 while (n != 0) { 450 int digit = (n & 0xf); 451 n >>>= 4; 452 q.append(hexdigits[digit]); 453 } 454 q.append(";"); 455 } 456 } 457 ed.putString("history", q.toString()); 458 } 459 } 460 ed.putInt("curpos", mPlayPos); 461 if (mPlayer.isInitialized()) { 462 ed.putLong("seekpos", mPlayer.position()); 463 } 464 ed.putInt("repeatmode", mRepeatMode); 465 ed.putInt("shufflemode", mShuffleMode); 466 SharedPreferencesCompat.apply(ed); 467 468 // Log.i("@@@@ service", "saved state in " + (System.currentTimeMillis() - start) + " ms"); 469 } 470 reloadQueue()471 private void reloadQueue() { 472 String q = null; 473 474 boolean newstyle = false; 475 int id = mCardId; 476 if (mPreferences.contains("cardid")) { 477 newstyle = true; 478 id = mPreferences.getInt("cardid", ~mCardId); 479 } 480 if (id == mCardId) { 481 // Only restore the saved playlist if the card is still 482 // the same one as when the playlist was saved 483 q = mPreferences.getString("queue", ""); 484 } 485 int qlen = q != null ? q.length() : 0; 486 if (qlen > 1) { 487 // Log.i("@@@@ service", "loaded queue: " + q); 488 int plen = 0; 489 int n = 0; 490 int shift = 0; 491 for (int i = 0; i < qlen; i++) { 492 char c = q.charAt(i); 493 if (c == ';') { 494 ensurePlayListCapacity(plen + 1); 495 mPlayList[plen] = n; 496 plen++; 497 n = 0; 498 shift = 0; 499 } else { 500 if (c >= '0' && c <= '9') { 501 n += ((c - '0') << shift); 502 } else if (c >= 'a' && c <= 'f') { 503 n += ((10 + c - 'a') << shift); 504 } else { 505 // bogus playlist data 506 plen = 0; 507 break; 508 } 509 shift += 4; 510 } 511 } 512 mPlayListLen = plen; 513 514 int pos = mPreferences.getInt("curpos", 0); 515 if (pos < 0 || pos >= mPlayListLen) { 516 // The saved playlist is bogus, discard it 517 mPlayListLen = 0; 518 return; 519 } 520 mPlayPos = pos; 521 522 // When reloadQueue is called in response to a card-insertion, 523 // we might not be able to query the media provider right away. 524 // To deal with this, try querying for the current file, and if 525 // that fails, wait a while and try again. If that too fails, 526 // assume there is a problem and don't restore the state. 527 Cursor crsr = MusicUtils.query(this, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, 528 new String[] {"_id"}, "_id=" + mPlayList[mPlayPos], null, null); 529 if (crsr == null || crsr.getCount() == 0) { 530 // wait a bit and try again 531 SystemClock.sleep(3000); 532 crsr = getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, 533 mCursorCols, "_id=" + mPlayList[mPlayPos], null, null); 534 } 535 if (crsr != null) { 536 crsr.close(); 537 } 538 539 // Make sure we don't auto-skip to the next song, since that 540 // also starts playback. What could happen in that case is: 541 // - music is paused 542 // - go to UMS and delete some files, including the currently playing one 543 // - come back from UMS 544 // (time passes) 545 // - music app is killed for some reason (out of memory) 546 // - music service is restarted, service restores state, doesn't find 547 // the "current" file, goes to the next and: playback starts on its 548 // own, potentially at some random inconvenient time. 549 mOpenFailedCounter = 20; 550 mQuietMode = true; 551 openCurrentAndNext(); 552 mQuietMode = false; 553 if (!mPlayer.isInitialized()) { 554 // couldn't restore the saved state 555 mPlayListLen = 0; 556 return; 557 } 558 559 long seekpos = mPreferences.getLong("seekpos", 0); 560 seek(seekpos >= 0 && seekpos < duration() ? seekpos : 0); 561 Log.d(LOGTAG, "restored queue, currently at position " + position() + "/" + duration() 562 + " (requested " + seekpos + ")"); 563 564 int repmode = mPreferences.getInt("repeatmode", REPEAT_NONE); 565 if (repmode != REPEAT_ALL && repmode != REPEAT_CURRENT) { 566 repmode = REPEAT_NONE; 567 } 568 mRepeatMode = repmode; 569 570 int shufmode = mPreferences.getInt("shufflemode", SHUFFLE_NONE); 571 if (shufmode != SHUFFLE_AUTO && shufmode != SHUFFLE_NORMAL) { 572 shufmode = SHUFFLE_NONE; 573 } 574 if (shufmode != SHUFFLE_NONE) { 575 // in shuffle mode we need to restore the history too 576 q = mPreferences.getString("history", ""); 577 qlen = q != null ? q.length() : 0; 578 if (qlen > 1) { 579 plen = 0; 580 n = 0; 581 shift = 0; 582 mHistory.clear(); 583 for (int i = 0; i < qlen; i++) { 584 char c = q.charAt(i); 585 if (c == ';') { 586 if (n >= mPlayListLen) { 587 // bogus history data 588 mHistory.clear(); 589 break; 590 } 591 mHistory.add(n); 592 n = 0; 593 shift = 0; 594 } else { 595 if (c >= '0' && c <= '9') { 596 n += ((c - '0') << shift); 597 } else if (c >= 'a' && c <= 'f') { 598 n += ((10 + c - 'a') << shift); 599 } else { 600 // bogus history data 601 mHistory.clear(); 602 break; 603 } 604 shift += 4; 605 } 606 } 607 } 608 } 609 if (shufmode == SHUFFLE_AUTO) { 610 if (!makeAutoShuffleList()) { 611 shufmode = SHUFFLE_NONE; 612 } 613 } 614 mShuffleMode = shufmode; 615 } 616 } 617 618 @Override onBind(Intent intent)619 public IBinder onBind(Intent intent) { 620 mDelayedStopHandler.removeCallbacksAndMessages(null); 621 mServiceInUse = true; 622 return mBinder; 623 } 624 625 @Override onRebind(Intent intent)626 public void onRebind(Intent intent) { 627 mDelayedStopHandler.removeCallbacksAndMessages(null); 628 mServiceInUse = true; 629 } 630 631 @Override onStartCommand(Intent intent, int flags, int startId)632 public int onStartCommand(Intent intent, int flags, int startId) { 633 mServiceStartId = startId; 634 mDelayedStopHandler.removeCallbacksAndMessages(null); 635 636 if (intent != null) { 637 String action = intent.getAction(); 638 String cmd = intent.getStringExtra("command"); 639 MusicUtils.debugLog("onStartCommand " + action + " / " + cmd); 640 641 if (CMDNEXT.equals(cmd) || NEXT_ACTION.equals(action)) { 642 gotoNext(true); 643 } else if (CMDPREVIOUS.equals(cmd) || PREVIOUS_ACTION.equals(action)) { 644 if (position() < 2000) { 645 prev(); 646 } else { 647 seek(0); 648 play(); 649 } 650 } else if (CMDTOGGLEPAUSE.equals(cmd) || TOGGLEPAUSE_ACTION.equals(action)) { 651 if (isPlaying()) { 652 pause(); 653 mPausedByTransientLossOfFocus = false; 654 } else { 655 play(); 656 } 657 } else if (CMDPAUSE.equals(cmd) || PAUSE_ACTION.equals(action)) { 658 pause(); 659 mPausedByTransientLossOfFocus = false; 660 } else if (CMDPLAY.equals(cmd)) { 661 play(); 662 } else if (CMDSTOP.equals(cmd)) { 663 pause(); 664 mPausedByTransientLossOfFocus = false; 665 seek(0); 666 } 667 } 668 669 // make sure the service will shut down on its own if it was 670 // just started but not bound to and nothing is playing 671 mDelayedStopHandler.removeCallbacksAndMessages(null); 672 Message msg = mDelayedStopHandler.obtainMessage(); 673 mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY); 674 return START_STICKY; 675 } 676 677 @Override onUnbind(Intent intent)678 public boolean onUnbind(Intent intent) { 679 mServiceInUse = false; 680 681 // Take a snapshot of the current playlist 682 saveQueue(true); 683 684 if (isPlaying() || mPausedByTransientLossOfFocus) { 685 // something is currently playing, or will be playing once 686 // an in-progress action requesting audio focus ends, so don't stop the service now. 687 return true; 688 } 689 690 // If there is a playlist but playback is paused, then wait a while 691 // before stopping the service, so that pause/resume isn't slow. 692 // Also delay stopping the service if we're transitioning between tracks. 693 if (mPlayListLen > 0 || mMediaplayerHandler.hasMessages(TRACK_ENDED)) { 694 Message msg = mDelayedStopHandler.obtainMessage(); 695 mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY); 696 return true; 697 } 698 699 // No active playlist, OK to stop the service right now 700 stopSelf(mServiceStartId); 701 return true; 702 } 703 704 private Handler mDelayedStopHandler = new Handler() { 705 @Override 706 public void handleMessage(Message msg) { 707 // Check again to make sure nothing is playing right now 708 if (isPlaying() || mPausedByTransientLossOfFocus || mServiceInUse 709 || mMediaplayerHandler.hasMessages(TRACK_ENDED)) { 710 return; 711 } 712 // save the queue again, because it might have changed 713 // since the user exited the music app (because of 714 // party-shuffle or because the play-position changed) 715 saveQueue(true); 716 stopSelf(mServiceStartId); 717 } 718 }; 719 720 /** 721 * Called when we receive a ACTION_MEDIA_EJECT notification. 722 * 723 * @param storagePath path to mount point for the removed media 724 */ closeExternalStorageFiles(String storagePath)725 public void closeExternalStorageFiles(String storagePath) { 726 // stop playback and clean up if the SD card is going to be unmounted. 727 stop(true); 728 notifyChange(QUEUE_CHANGED); 729 notifyChange(META_CHANGED); 730 } 731 732 /** 733 * Registers an intent to listen for ACTION_MEDIA_EJECT notifications. 734 * The intent will call closeExternalStorageFiles() if the external media 735 * is going to be ejected, so applications can clean up any files they have open. 736 */ registerExternalStorageListener()737 public void registerExternalStorageListener() { 738 if (mUnmountReceiver == null) { 739 mUnmountReceiver = new BroadcastReceiver() { 740 @Override 741 public void onReceive(Context context, Intent intent) { 742 String action = intent.getAction(); 743 if (action.equals(Intent.ACTION_MEDIA_EJECT)) { 744 saveQueue(true); 745 mQueueIsSaveable = false; 746 closeExternalStorageFiles(intent.getData().getPath()); 747 } else if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) { 748 mMediaMountedCount++; 749 mCardId = MusicUtils.getCardId(MediaPlaybackService.this); 750 reloadQueue(); 751 mQueueIsSaveable = true; 752 notifyChange(QUEUE_CHANGED); 753 notifyChange(META_CHANGED); 754 } 755 } 756 }; 757 IntentFilter iFilter = new IntentFilter(); 758 iFilter.addAction(Intent.ACTION_MEDIA_EJECT); 759 iFilter.addAction(Intent.ACTION_MEDIA_MOUNTED); 760 iFilter.addDataScheme("file"); 761 registerReceiver(mUnmountReceiver, iFilter); 762 } 763 } 764 765 /** 766 * Notify the change-receivers that something has changed. 767 * The intent that is sent contains the following data 768 * for the currently playing track: 769 * "id" - Integer: the database row ID 770 * "artist" - String: the name of the artist 771 * "album" - String: the name of the album 772 * "track" - String: the name of the track 773 * The intent has an action that is one of 774 * "com.android.music.metachanged" 775 * "com.android.music.queuechanged", 776 * "com.android.music.playbackcomplete" 777 * "com.android.music.playstatechanged" 778 * respectively indicating that a new track has 779 * started playing, that the playback queue has 780 * changed, that playback has stopped because 781 * the last file in the list has been played, 782 * or that the play-state changed (paused/resumed). 783 */ notifyChange(String what)784 private void notifyChange(String what) { 785 Intent i = new Intent(what); 786 i.putExtra("id", Long.valueOf(getAudioId())); 787 i.putExtra("artist", getArtistName()); 788 i.putExtra("album", getAlbumName()); 789 i.putExtra("track", getTrackName()); 790 i.putExtra("playing", isPlaying()); 791 sendStickyBroadcast(i); 792 793 if (what.equals(PLAYSTATE_CHANGED)) { 794 mRemoteControlClient.setPlaybackState(isPlaying() 795 ? RemoteControlClient.PLAYSTATE_PLAYING 796 : RemoteControlClient.PLAYSTATE_PAUSED); 797 } else if (what.equals(META_CHANGED)) { 798 RemoteControlClient.MetadataEditor ed = mRemoteControlClient.editMetadata(true); 799 ed.putString(MediaMetadataRetriever.METADATA_KEY_TITLE, getTrackName()); 800 ed.putString(MediaMetadataRetriever.METADATA_KEY_ALBUM, getAlbumName()); 801 ed.putString(MediaMetadataRetriever.METADATA_KEY_ARTIST, getArtistName()); 802 ed.putLong(MediaMetadataRetriever.METADATA_KEY_DURATION, duration()); 803 Bitmap b = MusicUtils.getArtwork(this, getAudioId(), getAlbumId(), false); 804 if (b != null) { 805 ed.putBitmap(MetadataEditor.BITMAP_KEY_ARTWORK, b); 806 } 807 ed.apply(); 808 } 809 810 if (what.equals(QUEUE_CHANGED)) { 811 saveQueue(true); 812 } else { 813 saveQueue(false); 814 } 815 816 // Share this notification directly with our widgets 817 mAppWidgetProvider.notifyChange(this, what); 818 } 819 ensurePlayListCapacity(int size)820 private void ensurePlayListCapacity(int size) { 821 if (mPlayList == null || size > mPlayList.length) { 822 // reallocate at 2x requested size so we don't 823 // need to grow and copy the array for every 824 // insert 825 long[] newlist = new long[size * 2]; 826 int len = mPlayList != null ? mPlayList.length : mPlayListLen; 827 for (int i = 0; i < len; i++) { 828 newlist[i] = mPlayList[i]; 829 } 830 mPlayList = newlist; 831 } 832 // FIXME: shrink the array when the needed size is much smaller 833 // than the allocated size 834 } 835 836 // insert the list of songs at the specified position in the playlist addToPlayList(long[] list, int position)837 private void addToPlayList(long[] list, int position) { 838 int addlen = list.length; 839 if (position < 0) { // overwrite 840 mPlayListLen = 0; 841 position = 0; 842 } 843 ensurePlayListCapacity(mPlayListLen + addlen); 844 if (position > mPlayListLen) { 845 position = mPlayListLen; 846 } 847 848 // move part of list after insertion point 849 int tailsize = mPlayListLen - position; 850 for (int i = tailsize; i > 0; i--) { 851 mPlayList[position + i] = mPlayList[position + i - addlen]; 852 } 853 854 // copy list into playlist 855 for (int i = 0; i < addlen; i++) { 856 mPlayList[position + i] = list[i]; 857 } 858 mPlayListLen += addlen; 859 if (mPlayListLen == 0) { 860 mCursor.close(); 861 mCursor = null; 862 notifyChange(META_CHANGED); 863 } 864 } 865 866 /** 867 * Appends a list of tracks to the current playlist. 868 * If nothing is playing currently, playback will be started at 869 * the first track. 870 * If the action is NOW, playback will switch to the first of 871 * the new tracks immediately. 872 * @param list The list of tracks to append. 873 * @param action NOW, NEXT or LAST 874 */ enqueue(long[] list, int action)875 public void enqueue(long[] list, int action) { 876 synchronized (this) { 877 if (action == NEXT && mPlayPos + 1 < mPlayListLen) { 878 addToPlayList(list, mPlayPos + 1); 879 notifyChange(QUEUE_CHANGED); 880 } else { 881 // action == LAST || action == NOW || mPlayPos + 1 == mPlayListLen 882 addToPlayList(list, Integer.MAX_VALUE); 883 notifyChange(QUEUE_CHANGED); 884 if (action == NOW) { 885 mPlayPos = mPlayListLen - list.length; 886 openCurrentAndNext(); 887 play(); 888 notifyChange(META_CHANGED); 889 return; 890 } 891 } 892 if (mPlayPos < 0) { 893 mPlayPos = 0; 894 openCurrentAndNext(); 895 play(); 896 notifyChange(META_CHANGED); 897 } 898 } 899 } 900 901 /** 902 * Replaces the current playlist with a new list, 903 * and prepares for starting playback at the specified 904 * position in the list, or a random position if the 905 * specified position is 0. 906 * @param list The new list of tracks. 907 */ open(long[] list, int position)908 public void open(long[] list, int position) { 909 synchronized (this) { 910 if (mShuffleMode == SHUFFLE_AUTO) { 911 mShuffleMode = SHUFFLE_NORMAL; 912 } 913 long oldId = getAudioId(); 914 int listlength = list.length; 915 boolean newlist = true; 916 if (mPlayListLen == listlength) { 917 // possible fast path: list might be the same 918 newlist = false; 919 for (int i = 0; i < listlength; i++) { 920 if (list[i] != mPlayList[i]) { 921 newlist = true; 922 break; 923 } 924 } 925 } 926 if (newlist) { 927 addToPlayList(list, -1); 928 notifyChange(QUEUE_CHANGED); 929 } 930 int oldpos = mPlayPos; 931 if (position >= 0) { 932 mPlayPos = position; 933 } else { 934 mPlayPos = mRand.nextInt(mPlayListLen); 935 } 936 mHistory.clear(); 937 938 saveBookmarkIfNeeded(); 939 openCurrentAndNext(); 940 if (oldId != getAudioId()) { 941 notifyChange(META_CHANGED); 942 } 943 } 944 } 945 946 /** 947 * Moves the item at index1 to index2. 948 * @param index1 949 * @param index2 950 */ moveQueueItem(int index1, int index2)951 public void moveQueueItem(int index1, int index2) { 952 synchronized (this) { 953 if (index1 >= mPlayListLen) { 954 index1 = mPlayListLen - 1; 955 } 956 if (index2 >= mPlayListLen) { 957 index2 = mPlayListLen - 1; 958 } 959 if (index1 < index2) { 960 long tmp = mPlayList[index1]; 961 for (int i = index1; i < index2; i++) { 962 mPlayList[i] = mPlayList[i + 1]; 963 } 964 mPlayList[index2] = tmp; 965 if (mPlayPos == index1) { 966 mPlayPos = index2; 967 } else if (mPlayPos >= index1 && mPlayPos <= index2) { 968 mPlayPos--; 969 } 970 } else if (index2 < index1) { 971 long tmp = mPlayList[index1]; 972 for (int i = index1; i > index2; i--) { 973 mPlayList[i] = mPlayList[i - 1]; 974 } 975 mPlayList[index2] = tmp; 976 if (mPlayPos == index1) { 977 mPlayPos = index2; 978 } else if (mPlayPos >= index2 && mPlayPos <= index1) { 979 mPlayPos++; 980 } 981 } 982 notifyChange(QUEUE_CHANGED); 983 } 984 } 985 986 /** 987 * Returns the current play list 988 * @return An array of integers containing the IDs of the tracks in the play list 989 */ getQueue()990 public long[] getQueue() { 991 synchronized (this) { 992 int len = mPlayListLen; 993 long[] list = new long[len]; 994 for (int i = 0; i < len; i++) { 995 list[i] = mPlayList[i]; 996 } 997 return list; 998 } 999 } 1000 getCursorForId(long lid)1001 private Cursor getCursorForId(long lid) { 1002 String id = String.valueOf(lid); 1003 1004 Cursor c = getContentResolver().query( 1005 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, mCursorCols, "_id=" + id, null, null); 1006 if (c != null) { 1007 c.moveToFirst(); 1008 } 1009 return c; 1010 } 1011 openCurrentAndNext()1012 private void openCurrentAndNext() { 1013 synchronized (this) { 1014 if (mCursor != null) { 1015 mCursor.close(); 1016 mCursor = null; 1017 } 1018 1019 if (mPlayListLen == 0) { 1020 return; 1021 } 1022 stop(false); 1023 1024 mCursor = getCursorForId(mPlayList[mPlayPos]); 1025 while (true) { 1026 if (mCursor != null && mCursor.getCount() != 0 1027 && open(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + "/" 1028 + mCursor.getLong(IDCOLIDX))) { 1029 break; 1030 } 1031 // if we get here then opening the file failed. We can close the cursor now, because 1032 // we're either going to create a new one next, or stop trying 1033 if (mCursor != null) { 1034 mCursor.close(); 1035 mCursor = null; 1036 } 1037 if (mOpenFailedCounter++ < 10 && mPlayListLen > 1) { 1038 int pos = getNextPosition(false); 1039 if (pos < 0) { 1040 gotoIdleState(); 1041 if (mIsSupposedToBePlaying) { 1042 mIsSupposedToBePlaying = false; 1043 notifyChange(PLAYSTATE_CHANGED); 1044 } 1045 return; 1046 } 1047 mPlayPos = pos; 1048 stop(false); 1049 mPlayPos = pos; 1050 mCursor = getCursorForId(mPlayList[mPlayPos]); 1051 } else { 1052 mOpenFailedCounter = 0; 1053 if (!mQuietMode) { 1054 Toast.makeText(this, R.string.playback_failed, Toast.LENGTH_SHORT).show(); 1055 } 1056 Log.d(LOGTAG, "Failed to open file for playback"); 1057 gotoIdleState(); 1058 if (mIsSupposedToBePlaying) { 1059 mIsSupposedToBePlaying = false; 1060 notifyChange(PLAYSTATE_CHANGED); 1061 } 1062 return; 1063 } 1064 } 1065 1066 // go to bookmark if needed 1067 if (isPodcast()) { 1068 long bookmark = getBookmark(); 1069 // Start playing a little bit before the bookmark, 1070 // so it's easier to get back in to the narrative. 1071 seek(bookmark - 5000); 1072 } 1073 setNextTrack(); 1074 } 1075 } 1076 setNextTrack()1077 private void setNextTrack() { 1078 mNextPlayPos = getNextPosition(false); 1079 if (mNextPlayPos >= 0) { 1080 long id = mPlayList[mNextPlayPos]; 1081 mPlayer.setNextDataSource(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + "/" + id); 1082 } else { 1083 mPlayer.setNextDataSource(null); 1084 } 1085 } 1086 1087 /** 1088 * Opens the specified file and readies it for playback. 1089 * 1090 * @param path The full path of the file to be opened. 1091 */ open(String path)1092 public boolean open(String path) { 1093 synchronized (this) { 1094 if (path == null) { 1095 return false; 1096 } 1097 1098 // if mCursor is null, try to associate path with a database cursor 1099 if (mCursor == null) { 1100 ContentResolver resolver = getContentResolver(); 1101 Uri uri; 1102 String where; 1103 String selectionArgs[]; 1104 if (path.startsWith("content://media/")) { 1105 uri = Uri.parse(path); 1106 where = null; 1107 selectionArgs = null; 1108 } else { 1109 uri = MediaStore.Audio.Media.getContentUriForPath(path); 1110 where = MediaStore.Audio.Media.DATA + "=?"; 1111 selectionArgs = new String[] {path}; 1112 } 1113 1114 try { 1115 mCursor = resolver.query(uri, mCursorCols, where, selectionArgs, null); 1116 if (mCursor != null) { 1117 if (mCursor.getCount() == 0) { 1118 mCursor.close(); 1119 mCursor = null; 1120 } else { 1121 mCursor.moveToNext(); 1122 ensurePlayListCapacity(1); 1123 mPlayListLen = 1; 1124 mPlayList[0] = mCursor.getLong(IDCOLIDX); 1125 mPlayPos = 0; 1126 } 1127 } 1128 } catch (UnsupportedOperationException ex) { 1129 } 1130 } 1131 mFileToPlay = path; 1132 mPlayer.setDataSource(mFileToPlay); 1133 if (mPlayer.isInitialized()) { 1134 mOpenFailedCounter = 0; 1135 return true; 1136 } 1137 stop(true); 1138 return false; 1139 } 1140 } 1141 1142 /** 1143 * Starts playback of a previously opened file. 1144 */ play()1145 public void play() { 1146 mAudioManager.requestAudioFocus( 1147 mAudioFocusListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN); 1148 mAudioManager.registerMediaButtonEventReceiver(new ComponentName( 1149 this.getPackageName(), MediaButtonIntentReceiver.class.getName())); 1150 1151 if (mPlayer.isInitialized()) { 1152 // if we are at the end of the song, go to the next song first 1153 long duration = mPlayer.duration(); 1154 if (mRepeatMode != REPEAT_CURRENT && duration > 2000 1155 && mPlayer.position() >= duration - 2000) { 1156 gotoNext(true); 1157 } 1158 1159 mPlayer.start(); 1160 // make sure we fade in, in case a previous fadein was stopped because 1161 // of another focus loss 1162 mMediaplayerHandler.removeMessages(FADEDOWN); 1163 mMediaplayerHandler.sendEmptyMessage(FADEUP); 1164 1165 updateNotification(); 1166 if (!mIsSupposedToBePlaying) { 1167 mIsSupposedToBePlaying = true; 1168 notifyChange(PLAYSTATE_CHANGED); 1169 } 1170 1171 } else if (mPlayListLen <= 0) { 1172 // This is mostly so that if you press 'play' on a bluetooth headset 1173 // without every having played anything before, it will still play 1174 // something. 1175 setShuffleMode(SHUFFLE_AUTO); 1176 } 1177 } 1178 updateNotification()1179 private void updateNotification() { 1180 RemoteViews views = new RemoteViews(getPackageName(), R.layout.statusbar); 1181 views.setImageViewResource(R.id.icon, R.drawable.stat_notify_musicplayer); 1182 if (getAudioId() < 0) { 1183 // streaming 1184 views.setTextViewText(R.id.trackname, getPath()); 1185 views.setTextViewText(R.id.artistalbum, null); 1186 } else { 1187 String artist = getArtistName(); 1188 views.setTextViewText(R.id.trackname, getTrackName()); 1189 if (artist == null || artist.equals(MediaStore.UNKNOWN_STRING)) { 1190 artist = getString(R.string.unknown_artist_name); 1191 } 1192 String album = getAlbumName(); 1193 if (album == null || album.equals(MediaStore.UNKNOWN_STRING)) { 1194 album = getString(R.string.unknown_album_name); 1195 } 1196 1197 views.setTextViewText( 1198 R.id.artistalbum, getString(R.string.notification_artist_album, artist, album)); 1199 } 1200 Notification status = new Notification(); 1201 status.contentView = views; 1202 status.flags |= Notification.FLAG_ONGOING_EVENT; 1203 status.icon = R.drawable.stat_notify_musicplayer; 1204 status.contentIntent = 1205 PendingIntent.getActivity(this, 0, new Intent("com.android.music.PLAYBACK_VIEWER") 1206 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK), 1207 0); 1208 startForeground(PLAYBACKSERVICE_STATUS, status); 1209 } 1210 stop(boolean remove_status_icon)1211 private void stop(boolean remove_status_icon) { 1212 if (mPlayer != null && mPlayer.isInitialized()) { 1213 mPlayer.stop(); 1214 } 1215 mFileToPlay = null; 1216 if (mCursor != null) { 1217 mCursor.close(); 1218 mCursor = null; 1219 } 1220 if (remove_status_icon) { 1221 gotoIdleState(); 1222 } else { 1223 stopForeground(false); 1224 } 1225 if (remove_status_icon) { 1226 mIsSupposedToBePlaying = false; 1227 } 1228 } 1229 1230 /** 1231 * Stops playback. 1232 */ stop()1233 public void stop() { 1234 stop(true); 1235 } 1236 1237 /** 1238 * Pauses playback (call play() to resume) 1239 */ pause()1240 public void pause() { 1241 synchronized (this) { 1242 mMediaplayerHandler.removeMessages(FADEUP); 1243 if (isPlaying()) { 1244 mPlayer.pause(); 1245 gotoIdleState(); 1246 mIsSupposedToBePlaying = false; 1247 notifyChange(PLAYSTATE_CHANGED); 1248 saveBookmarkIfNeeded(); 1249 } 1250 } 1251 } 1252 1253 /** Returns whether something is currently playing 1254 * 1255 * @return true if something is playing (or will be playing shortly, in case 1256 * we're currently transitioning between tracks), false if not. 1257 */ isPlaying()1258 public boolean isPlaying() { 1259 return mIsSupposedToBePlaying; 1260 } 1261 1262 /* 1263 Desired behavior for prev/next/shuffle: 1264 1265 - NEXT will move to the next track in the list when not shuffling, and to 1266 a track randomly picked from the not-yet-played tracks when shuffling. 1267 If all tracks have already been played, pick from the full set, but 1268 avoid picking the previously played track if possible. 1269 - when shuffling, PREV will go to the previously played track. Hitting PREV 1270 again will go to the track played before that, etc. When the start of the 1271 history has been reached, PREV is a no-op. 1272 When not shuffling, PREV will go to the sequentially previous track (the 1273 difference with the shuffle-case is mainly that when not shuffling, the 1274 user can back up to tracks that are not in the history). 1275 1276 Example: 1277 When playing an album with 10 tracks from the start, and enabling shuffle 1278 while playing track 5, the remaining tracks (6-10) will be shuffled, e.g. 1279 the final play order might be 1-2-3-4-5-8-10-6-9-7. 1280 When hitting 'prev' 8 times while playing track 7 in this example, the 1281 user will go to tracks 9-6-10-8-5-4-3-2. If the user then hits 'next', 1282 a random track will be picked again. If at any time user disables shuffling 1283 the next/previous track will be picked in sequential order again. 1284 */ 1285 prev()1286 public void prev() { 1287 synchronized (this) { 1288 if (mShuffleMode == SHUFFLE_NORMAL) { 1289 // go to previously-played track and remove it from the history 1290 int histsize = mHistory.size(); 1291 if (histsize == 0) { 1292 // prev is a no-op 1293 return; 1294 } 1295 Integer pos = mHistory.remove(histsize - 1); 1296 mPlayPos = pos.intValue(); 1297 } else { 1298 if (mPlayPos > 0) { 1299 mPlayPos--; 1300 } else { 1301 mPlayPos = mPlayListLen - 1; 1302 } 1303 } 1304 saveBookmarkIfNeeded(); 1305 stop(false); 1306 openCurrentAndNext(); 1307 play(); 1308 notifyChange(META_CHANGED); 1309 } 1310 } 1311 1312 /** 1313 * Get the next position to play. Note that this may actually modify mPlayPos 1314 * if playback is in SHUFFLE_AUTO mode and the shuffle list window needed to 1315 * be adjusted. Either way, the return value is the next value that should be 1316 * assigned to mPlayPos; 1317 */ getNextPosition(boolean force)1318 private int getNextPosition(boolean force) { 1319 if (mRepeatMode == REPEAT_CURRENT) { 1320 if (mPlayPos < 0) return 0; 1321 return mPlayPos; 1322 } else if (mShuffleMode == SHUFFLE_NORMAL) { 1323 // Pick random next track from the not-yet-played ones 1324 // TODO: make it work right after adding/removing items in the queue. 1325 1326 // Store the current file in the history, but keep the history at a 1327 // reasonable size 1328 if (mPlayPos >= 0) { 1329 mHistory.add(mPlayPos); 1330 } 1331 if (mHistory.size() > MAX_HISTORY_SIZE) { 1332 mHistory.removeElementAt(0); 1333 } 1334 1335 int numTracks = mPlayListLen; 1336 int[] tracks = new int[numTracks]; 1337 for (int i = 0; i < numTracks; i++) { 1338 tracks[i] = i; 1339 } 1340 1341 int numHistory = mHistory.size(); 1342 int numUnplayed = numTracks; 1343 for (int i = 0; i < numHistory; i++) { 1344 int idx = mHistory.get(i).intValue(); 1345 if (idx < numTracks && tracks[idx] >= 0) { 1346 numUnplayed--; 1347 tracks[idx] = -1; 1348 } 1349 } 1350 1351 // 'numUnplayed' now indicates how many tracks have not yet 1352 // been played, and 'tracks' contains the indices of those 1353 // tracks. 1354 if (numUnplayed <= 0) { 1355 // everything's already been played 1356 if (mRepeatMode == REPEAT_ALL || force) { 1357 // pick from full set 1358 numUnplayed = numTracks; 1359 for (int i = 0; i < numTracks; i++) { 1360 tracks[i] = i; 1361 } 1362 } else { 1363 // all done 1364 return -1; 1365 } 1366 } 1367 int skip = mRand.nextInt(numUnplayed); 1368 int cnt = -1; 1369 while (true) { 1370 while (tracks[++cnt] < 0) 1371 ; 1372 skip--; 1373 if (skip < 0) { 1374 break; 1375 } 1376 } 1377 return cnt; 1378 } else if (mShuffleMode == SHUFFLE_AUTO) { 1379 doAutoShuffleUpdate(); 1380 return mPlayPos + 1; 1381 } else { 1382 if (mPlayPos >= mPlayListLen - 1) { 1383 // we're at the end of the list 1384 if (mRepeatMode == REPEAT_NONE && !force) { 1385 // all done 1386 return -1; 1387 } else if (mRepeatMode == REPEAT_ALL || force) { 1388 return 0; 1389 } 1390 return -1; 1391 } else { 1392 return mPlayPos + 1; 1393 } 1394 } 1395 } 1396 gotoNext(boolean force)1397 public void gotoNext(boolean force) { 1398 synchronized (this) { 1399 if (mPlayListLen <= 0) { 1400 Log.d(LOGTAG, "No play queue"); 1401 return; 1402 } 1403 1404 int pos = getNextPosition(force); 1405 if (pos < 0) { 1406 gotoIdleState(); 1407 if (mIsSupposedToBePlaying) { 1408 mIsSupposedToBePlaying = false; 1409 notifyChange(PLAYSTATE_CHANGED); 1410 } 1411 return; 1412 } 1413 mPlayPos = pos; 1414 saveBookmarkIfNeeded(); 1415 stop(false); 1416 mPlayPos = pos; 1417 openCurrentAndNext(); 1418 play(); 1419 notifyChange(META_CHANGED); 1420 } 1421 } 1422 gotoIdleState()1423 private void gotoIdleState() { 1424 mDelayedStopHandler.removeCallbacksAndMessages(null); 1425 Message msg = mDelayedStopHandler.obtainMessage(); 1426 mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY); 1427 stopForeground(true); 1428 } 1429 saveBookmarkIfNeeded()1430 private void saveBookmarkIfNeeded() { 1431 try { 1432 if (isPodcast()) { 1433 long pos = position(); 1434 long bookmark = getBookmark(); 1435 long duration = duration(); 1436 if ((pos < bookmark && (pos + 10000) > bookmark) 1437 || (pos > bookmark && (pos - 10000) < bookmark)) { 1438 // The existing bookmark is close to the current 1439 // position, so don't update it. 1440 return; 1441 } 1442 if (pos < 15000 || (pos + 10000) > duration) { 1443 // if we're near the start or end, clear the bookmark 1444 pos = 0; 1445 } 1446 1447 // write 'pos' to the bookmark field 1448 ContentValues values = new ContentValues(); 1449 values.put(MediaStore.Audio.Media.BOOKMARK, pos); 1450 Uri uri = ContentUris.withAppendedId( 1451 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, mCursor.getLong(IDCOLIDX)); 1452 getContentResolver().update(uri, values, null, null); 1453 } 1454 } catch (SQLiteException ex) { 1455 } 1456 } 1457 1458 // Make sure there are at least 5 items after the currently playing item 1459 // and no more than 10 items before. doAutoShuffleUpdate()1460 private void doAutoShuffleUpdate() { 1461 boolean notify = false; 1462 1463 // remove old entries 1464 if (mPlayPos > 10) { 1465 removeTracks(0, mPlayPos - 9); 1466 notify = true; 1467 } 1468 // add new entries if needed 1469 int to_add = 7 - (mPlayListLen - (mPlayPos < 0 ? -1 : mPlayPos)); 1470 for (int i = 0; i < to_add; i++) { 1471 // pick something at random from the list 1472 1473 int lookback = mHistory.size(); 1474 int idx = -1; 1475 while (true) { 1476 idx = mRand.nextInt(mAutoShuffleList.length); 1477 if (!wasRecentlyUsed(idx, lookback)) { 1478 break; 1479 } 1480 lookback /= 2; 1481 } 1482 mHistory.add(idx); 1483 if (mHistory.size() > MAX_HISTORY_SIZE) { 1484 mHistory.remove(0); 1485 } 1486 ensurePlayListCapacity(mPlayListLen + 1); 1487 mPlayList[mPlayListLen++] = mAutoShuffleList[idx]; 1488 notify = true; 1489 } 1490 if (notify) { 1491 notifyChange(QUEUE_CHANGED); 1492 } 1493 } 1494 1495 // check that the specified idx is not in the history (but only look at at 1496 // most lookbacksize entries in the history) wasRecentlyUsed(int idx, int lookbacksize)1497 private boolean wasRecentlyUsed(int idx, int lookbacksize) { 1498 // early exit to prevent infinite loops in case idx == mPlayPos 1499 if (lookbacksize == 0) { 1500 return false; 1501 } 1502 1503 int histsize = mHistory.size(); 1504 if (histsize < lookbacksize) { 1505 Log.d(LOGTAG, "lookback too big"); 1506 lookbacksize = histsize; 1507 } 1508 int maxidx = histsize - 1; 1509 for (int i = 0; i < lookbacksize; i++) { 1510 long entry = mHistory.get(maxidx - i); 1511 if (entry == idx) { 1512 return true; 1513 } 1514 } 1515 return false; 1516 } 1517 1518 // A simple variation of Random that makes sure that the 1519 // value it returns is not equal to the value it returned 1520 // previously, unless the interval is 1. 1521 private static class Shuffler { 1522 private int mPrevious; 1523 private Random mRandom = new Random(); nextInt(int interval)1524 public int nextInt(int interval) { 1525 int ret; 1526 do { 1527 ret = mRandom.nextInt(interval); 1528 } while (ret == mPrevious && interval > 1); 1529 mPrevious = ret; 1530 return ret; 1531 } 1532 }; 1533 makeAutoShuffleList()1534 private boolean makeAutoShuffleList() { 1535 ContentResolver res = getContentResolver(); 1536 Cursor c = null; 1537 try { 1538 c = res.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, 1539 new String[] {MediaStore.Audio.Media._ID}, 1540 MediaStore.Audio.Media.IS_MUSIC + "=1", null, null); 1541 if (c == null || c.getCount() == 0) { 1542 return false; 1543 } 1544 int len = c.getCount(); 1545 long[] list = new long[len]; 1546 for (int i = 0; i < len; i++) { 1547 c.moveToNext(); 1548 list[i] = c.getLong(0); 1549 } 1550 mAutoShuffleList = list; 1551 return true; 1552 } catch (RuntimeException ex) { 1553 } finally { 1554 if (c != null) { 1555 c.close(); 1556 } 1557 } 1558 return false; 1559 } 1560 1561 /** 1562 * Removes the range of tracks specified from the play list. If a file within the range is 1563 * the file currently being played, playback will move to the next file after the 1564 * range. 1565 * @param first The first file to be removed 1566 * @param last The last file to be removed 1567 * @return the number of tracks deleted 1568 */ removeTracks(int first, int last)1569 public int removeTracks(int first, int last) { 1570 int numremoved = removeTracksInternal(first, last); 1571 if (numremoved > 0) { 1572 notifyChange(QUEUE_CHANGED); 1573 } 1574 return numremoved; 1575 } 1576 removeTracksInternal(int first, int last)1577 private int removeTracksInternal(int first, int last) { 1578 synchronized (this) { 1579 if (last < first) return 0; 1580 if (first < 0) first = 0; 1581 if (last >= mPlayListLen) last = mPlayListLen - 1; 1582 1583 boolean gotonext = false; 1584 if (first <= mPlayPos && mPlayPos <= last) { 1585 mPlayPos = first; 1586 gotonext = true; 1587 } else if (mPlayPos > last) { 1588 mPlayPos -= (last - first + 1); 1589 } 1590 int num = mPlayListLen - last - 1; 1591 for (int i = 0; i < num; i++) { 1592 mPlayList[first + i] = mPlayList[last + 1 + i]; 1593 } 1594 mPlayListLen -= last - first + 1; 1595 1596 if (gotonext) { 1597 if (mPlayListLen == 0) { 1598 stop(true); 1599 mPlayPos = -1; 1600 if (mCursor != null) { 1601 mCursor.close(); 1602 mCursor = null; 1603 } 1604 } else { 1605 if (mPlayPos >= mPlayListLen) { 1606 mPlayPos = 0; 1607 } 1608 boolean wasPlaying = isPlaying(); 1609 stop(false); 1610 openCurrentAndNext(); 1611 if (wasPlaying) { 1612 play(); 1613 } 1614 } 1615 notifyChange(META_CHANGED); 1616 } 1617 return last - first + 1; 1618 } 1619 } 1620 1621 /** 1622 * Removes all instances of the track with the given id 1623 * from the playlist. 1624 * @param id The id to be removed 1625 * @return how many instances of the track were removed 1626 */ removeTrack(long id)1627 public int removeTrack(long id) { 1628 int numremoved = 0; 1629 synchronized (this) { 1630 for (int i = 0; i < mPlayListLen; i++) { 1631 if (mPlayList[i] == id) { 1632 numremoved += removeTracksInternal(i, i); 1633 i--; 1634 } 1635 } 1636 } 1637 if (numremoved > 0) { 1638 notifyChange(QUEUE_CHANGED); 1639 } 1640 return numremoved; 1641 } 1642 setShuffleMode(int shufflemode)1643 public void setShuffleMode(int shufflemode) { 1644 synchronized (this) { 1645 if (mShuffleMode == shufflemode && mPlayListLen > 0) { 1646 return; 1647 } 1648 mShuffleMode = shufflemode; 1649 if (mShuffleMode == SHUFFLE_AUTO) { 1650 if (makeAutoShuffleList()) { 1651 mPlayListLen = 0; 1652 doAutoShuffleUpdate(); 1653 mPlayPos = 0; 1654 openCurrentAndNext(); 1655 play(); 1656 notifyChange(META_CHANGED); 1657 return; 1658 } else { 1659 // failed to build a list of files to shuffle 1660 mShuffleMode = SHUFFLE_NONE; 1661 } 1662 } 1663 saveQueue(false); 1664 } 1665 } getShuffleMode()1666 public int getShuffleMode() { 1667 return mShuffleMode; 1668 } 1669 setRepeatMode(int repeatmode)1670 public void setRepeatMode(int repeatmode) { 1671 synchronized (this) { 1672 mRepeatMode = repeatmode; 1673 setNextTrack(); 1674 saveQueue(false); 1675 } 1676 } getRepeatMode()1677 public int getRepeatMode() { 1678 return mRepeatMode; 1679 } 1680 getMediaMountedCount()1681 public int getMediaMountedCount() { 1682 return mMediaMountedCount; 1683 } 1684 1685 /** 1686 * Returns the path of the currently playing file, or null if 1687 * no file is currently playing. 1688 */ getPath()1689 public String getPath() { 1690 return mFileToPlay; 1691 } 1692 1693 /** 1694 * Returns the rowid of the currently playing file, or -1 if 1695 * no file is currently playing. 1696 */ getAudioId()1697 public long getAudioId() { 1698 synchronized (this) { 1699 if (mPlayPos >= 0 && mPlayer.isInitialized()) { 1700 return mPlayList[mPlayPos]; 1701 } 1702 } 1703 return -1; 1704 } 1705 1706 /** 1707 * Returns the position in the queue 1708 * @return the position in the queue 1709 */ getQueuePosition()1710 public int getQueuePosition() { 1711 synchronized (this) { 1712 return mPlayPos; 1713 } 1714 } 1715 1716 /** 1717 * Starts playing the track at the given position in the queue. 1718 * @param pos The position in the queue of the track that will be played. 1719 */ setQueuePosition(int pos)1720 public void setQueuePosition(int pos) { 1721 synchronized (this) { 1722 stop(false); 1723 mPlayPos = pos; 1724 openCurrentAndNext(); 1725 play(); 1726 notifyChange(META_CHANGED); 1727 if (mShuffleMode == SHUFFLE_AUTO) { 1728 doAutoShuffleUpdate(); 1729 } 1730 } 1731 } 1732 getArtistName()1733 public String getArtistName() { 1734 synchronized (this) { 1735 if (mCursor == null) { 1736 return null; 1737 } 1738 return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST)); 1739 } 1740 } 1741 getArtistId()1742 public long getArtistId() { 1743 synchronized (this) { 1744 if (mCursor == null) { 1745 return -1; 1746 } 1747 return mCursor.getLong(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST_ID)); 1748 } 1749 } 1750 getAlbumName()1751 public String getAlbumName() { 1752 synchronized (this) { 1753 if (mCursor == null) { 1754 return null; 1755 } 1756 return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM)); 1757 } 1758 } 1759 getAlbumId()1760 public long getAlbumId() { 1761 synchronized (this) { 1762 if (mCursor == null) { 1763 return -1; 1764 } 1765 return mCursor.getLong(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM_ID)); 1766 } 1767 } 1768 getTrackName()1769 public String getTrackName() { 1770 synchronized (this) { 1771 if (mCursor == null) { 1772 return null; 1773 } 1774 return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE)); 1775 } 1776 } 1777 isPodcast()1778 private boolean isPodcast() { 1779 synchronized (this) { 1780 if (mCursor == null) { 1781 return false; 1782 } 1783 return (mCursor.getInt(PODCASTCOLIDX) > 0); 1784 } 1785 } 1786 getBookmark()1787 private long getBookmark() { 1788 synchronized (this) { 1789 if (mCursor == null) { 1790 return 0; 1791 } 1792 return mCursor.getLong(BOOKMARKCOLIDX); 1793 } 1794 } 1795 1796 /** 1797 * Returns the duration of the file in milliseconds. 1798 * Currently this method returns -1 for the duration of MIDI files. 1799 */ duration()1800 public long duration() { 1801 if (mPlayer.isInitialized()) { 1802 return mPlayer.duration(); 1803 } 1804 return -1; 1805 } 1806 1807 /** 1808 * Returns the current playback position in milliseconds 1809 */ position()1810 public long position() { 1811 if (mPlayer.isInitialized()) { 1812 return mPlayer.position(); 1813 } 1814 return -1; 1815 } 1816 1817 /** 1818 * Seeks to the position specified. 1819 * 1820 * @param pos The position to seek to, in milliseconds 1821 */ seek(long pos)1822 public long seek(long pos) { 1823 if (mPlayer.isInitialized()) { 1824 if (pos < 0) pos = 0; 1825 if (pos > mPlayer.duration()) pos = mPlayer.duration(); 1826 return mPlayer.seek(pos); 1827 } 1828 return -1; 1829 } 1830 1831 /** 1832 * Sets the audio session ID. 1833 * 1834 * @param sessionId: the audio session ID. 1835 */ setAudioSessionId(int sessionId)1836 public void setAudioSessionId(int sessionId) { 1837 synchronized (this) { 1838 mPlayer.setAudioSessionId(sessionId); 1839 } 1840 } 1841 1842 /** 1843 * Returns the audio session ID. 1844 */ getAudioSessionId()1845 public int getAudioSessionId() { 1846 synchronized (this) { 1847 return mPlayer.getAudioSessionId(); 1848 } 1849 } 1850 1851 /** 1852 * Provides a unified interface for dealing with midi files and 1853 * other media files. 1854 */ 1855 private class MultiPlayer { 1856 private CompatMediaPlayer mCurrentMediaPlayer = new CompatMediaPlayer(); 1857 private CompatMediaPlayer mNextMediaPlayer; 1858 private Handler mHandler; 1859 private boolean mIsInitialized = false; 1860 MultiPlayer()1861 public MultiPlayer() { 1862 mCurrentMediaPlayer.setWakeMode( 1863 MediaPlaybackService.this, PowerManager.PARTIAL_WAKE_LOCK); 1864 } 1865 setDataSource(String path)1866 public void setDataSource(String path) { 1867 mIsInitialized = setDataSourceImpl(mCurrentMediaPlayer, path); 1868 if (mIsInitialized) { 1869 setNextDataSource(null); 1870 } 1871 } 1872 setDataSourceImpl(MediaPlayer player, String path)1873 private boolean setDataSourceImpl(MediaPlayer player, String path) { 1874 try { 1875 player.reset(); 1876 player.setOnPreparedListener(null); 1877 if (path.startsWith("content://")) { 1878 player.setDataSource(MediaPlaybackService.this, Uri.parse(path)); 1879 } else { 1880 player.setDataSource(path); 1881 } 1882 player.setAudioStreamType(AudioManager.STREAM_MUSIC); 1883 player.prepare(); 1884 } catch (IOException ex) { 1885 // TODO: notify the user why the file couldn't be opened 1886 return false; 1887 } catch (IllegalArgumentException ex) { 1888 // TODO: notify the user why the file couldn't be opened 1889 return false; 1890 } 1891 player.setOnCompletionListener(listener); 1892 player.setOnErrorListener(errorListener); 1893 Intent i = new Intent(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION); 1894 i.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, getAudioSessionId()); 1895 i.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, getPackageName()); 1896 sendBroadcast(i); 1897 return true; 1898 } 1899 setNextDataSource(String path)1900 public void setNextDataSource(String path) { 1901 mCurrentMediaPlayer.setNextMediaPlayer(null); 1902 if (mNextMediaPlayer != null) { 1903 mNextMediaPlayer.release(); 1904 mNextMediaPlayer = null; 1905 } 1906 if (path == null) { 1907 return; 1908 } 1909 mNextMediaPlayer = new CompatMediaPlayer(); 1910 mNextMediaPlayer.setWakeMode(MediaPlaybackService.this, PowerManager.PARTIAL_WAKE_LOCK); 1911 mNextMediaPlayer.setAudioSessionId(getAudioSessionId()); 1912 if (setDataSourceImpl(mNextMediaPlayer, path)) { 1913 mCurrentMediaPlayer.setNextMediaPlayer(mNextMediaPlayer); 1914 } else { 1915 // failed to open next, we'll transition the old fashioned way, 1916 // which will skip over the faulty file 1917 mNextMediaPlayer.release(); 1918 mNextMediaPlayer = null; 1919 } 1920 } 1921 isInitialized()1922 public boolean isInitialized() { 1923 return mIsInitialized; 1924 } 1925 start()1926 public void start() { 1927 MusicUtils.debugLog(new Exception("MultiPlayer.start called")); 1928 mCurrentMediaPlayer.start(); 1929 } 1930 stop()1931 public void stop() { 1932 mCurrentMediaPlayer.reset(); 1933 mIsInitialized = false; 1934 } 1935 1936 /** 1937 * You CANNOT use this player anymore after calling release() 1938 */ release()1939 public void release() { 1940 stop(); 1941 mCurrentMediaPlayer.release(); 1942 } 1943 pause()1944 public void pause() { 1945 mCurrentMediaPlayer.pause(); 1946 } 1947 setHandler(Handler handler)1948 public void setHandler(Handler handler) { 1949 mHandler = handler; 1950 } 1951 1952 MediaPlayer.OnCompletionListener listener = new MediaPlayer.OnCompletionListener() { 1953 public void onCompletion(MediaPlayer mp) { 1954 if (mp == mCurrentMediaPlayer && mNextMediaPlayer != null) { 1955 mCurrentMediaPlayer.release(); 1956 mCurrentMediaPlayer = mNextMediaPlayer; 1957 mNextMediaPlayer = null; 1958 mHandler.sendEmptyMessage(TRACK_WENT_TO_NEXT); 1959 } else { 1960 // Acquire a temporary wakelock, since when we return from 1961 // this callback the MediaPlayer will release its wakelock 1962 // and allow the device to go to sleep. 1963 // This temporary wakelock is released when the RELEASE_WAKELOCK 1964 // message is processed, but just in case, put a timeout on it. 1965 mWakeLock.acquire(30000); 1966 mHandler.sendEmptyMessage(TRACK_ENDED); 1967 mHandler.sendEmptyMessage(RELEASE_WAKELOCK); 1968 } 1969 } 1970 }; 1971 1972 MediaPlayer.OnErrorListener errorListener = new MediaPlayer.OnErrorListener() { 1973 public boolean onError(MediaPlayer mp, int what, int extra) { 1974 switch (what) { 1975 case MediaPlayer.MEDIA_ERROR_SERVER_DIED: 1976 mIsInitialized = false; 1977 mCurrentMediaPlayer.release(); 1978 // Creating a new MediaPlayer and settings its wakemode does not 1979 // require the media service, so it's OK to do this now, while the 1980 // service is still being restarted 1981 mCurrentMediaPlayer = new CompatMediaPlayer(); 1982 mCurrentMediaPlayer.setWakeMode( 1983 MediaPlaybackService.this, PowerManager.PARTIAL_WAKE_LOCK); 1984 mHandler.sendMessageDelayed(mHandler.obtainMessage(SERVER_DIED), 2000); 1985 return true; 1986 default: 1987 Log.d("MultiPlayer", "Error: " + what + "," + extra); 1988 break; 1989 } 1990 return false; 1991 } 1992 }; 1993 duration()1994 public long duration() { 1995 return mCurrentMediaPlayer.getDuration(); 1996 } 1997 position()1998 public long position() { 1999 return mCurrentMediaPlayer.getCurrentPosition(); 2000 } 2001 seek(long whereto)2002 public long seek(long whereto) { 2003 mCurrentMediaPlayer.seekTo((int) whereto); 2004 return whereto; 2005 } 2006 setVolume(float vol)2007 public void setVolume(float vol) { 2008 mCurrentMediaPlayer.setVolume(vol, vol); 2009 } 2010 setAudioSessionId(int sessionId)2011 public void setAudioSessionId(int sessionId) { 2012 mCurrentMediaPlayer.setAudioSessionId(sessionId); 2013 } 2014 getAudioSessionId()2015 public int getAudioSessionId() { 2016 return mCurrentMediaPlayer.getAudioSessionId(); 2017 } 2018 } 2019 2020 static class CompatMediaPlayer extends MediaPlayer implements OnCompletionListener { 2021 private boolean mCompatMode = true; 2022 private MediaPlayer mNextPlayer; 2023 private OnCompletionListener mCompletion; 2024 CompatMediaPlayer()2025 public CompatMediaPlayer() { 2026 try { 2027 MediaPlayer.class.getMethod("setNextMediaPlayer", MediaPlayer.class); 2028 mCompatMode = false; 2029 } catch (NoSuchMethodException e) { 2030 mCompatMode = true; 2031 super.setOnCompletionListener(this); 2032 } 2033 } 2034 setNextMediaPlayer(MediaPlayer next)2035 public void setNextMediaPlayer(MediaPlayer next) { 2036 if (mCompatMode) { 2037 mNextPlayer = next; 2038 } else { 2039 super.setNextMediaPlayer(next); 2040 } 2041 } 2042 2043 @Override setOnCompletionListener(OnCompletionListener listener)2044 public void setOnCompletionListener(OnCompletionListener listener) { 2045 if (mCompatMode) { 2046 mCompletion = listener; 2047 } else { 2048 super.setOnCompletionListener(listener); 2049 } 2050 } 2051 2052 @Override onCompletion(MediaPlayer mp)2053 public void onCompletion(MediaPlayer mp) { 2054 if (mNextPlayer != null) { 2055 // as it turns out, starting a new MediaPlayer on the completion 2056 // of a previous player ends up slightly overlapping the two 2057 // playbacks, so slightly delaying the start of the next player 2058 // gives a better user experience 2059 SystemClock.sleep(50); 2060 mNextPlayer.start(); 2061 } 2062 mCompletion.onCompletion(this); 2063 } 2064 } 2065 2066 /* 2067 * By making this a static class with a WeakReference to the Service, we 2068 * ensure that the Service can be GCd even when the system process still 2069 * has a remote reference to the stub. 2070 */ 2071 static class ServiceStub extends IMediaPlaybackService.Stub { 2072 WeakReference<MediaPlaybackService> mService; 2073 ServiceStub(MediaPlaybackService service)2074 ServiceStub(MediaPlaybackService service) { 2075 mService = new WeakReference<MediaPlaybackService>(service); 2076 } 2077 openFile(String path)2078 public void openFile(String path) { 2079 mService.get().open(path); 2080 } open(long[] list, int position)2081 public void open(long[] list, int position) { 2082 mService.get().open(list, position); 2083 } getQueuePosition()2084 public int getQueuePosition() { 2085 return mService.get().getQueuePosition(); 2086 } setQueuePosition(int index)2087 public void setQueuePosition(int index) { 2088 mService.get().setQueuePosition(index); 2089 } isPlaying()2090 public boolean isPlaying() { 2091 return mService.get().isPlaying(); 2092 } stop()2093 public void stop() { 2094 mService.get().stop(); 2095 } pause()2096 public void pause() { 2097 mService.get().pause(); 2098 } play()2099 public void play() { 2100 mService.get().play(); 2101 } prev()2102 public void prev() { 2103 mService.get().prev(); 2104 } next()2105 public void next() { 2106 mService.get().gotoNext(true); 2107 } getTrackName()2108 public String getTrackName() { 2109 return mService.get().getTrackName(); 2110 } getAlbumName()2111 public String getAlbumName() { 2112 return mService.get().getAlbumName(); 2113 } getAlbumId()2114 public long getAlbumId() { 2115 return mService.get().getAlbumId(); 2116 } getArtistName()2117 public String getArtistName() { 2118 return mService.get().getArtistName(); 2119 } getArtistId()2120 public long getArtistId() { 2121 return mService.get().getArtistId(); 2122 } enqueue(long[] list, int action)2123 public void enqueue(long[] list, int action) { 2124 mService.get().enqueue(list, action); 2125 } getQueue()2126 public long[] getQueue() { 2127 return mService.get().getQueue(); 2128 } moveQueueItem(int from, int to)2129 public void moveQueueItem(int from, int to) { 2130 mService.get().moveQueueItem(from, to); 2131 } getPath()2132 public String getPath() { 2133 return mService.get().getPath(); 2134 } getAudioId()2135 public long getAudioId() { 2136 return mService.get().getAudioId(); 2137 } position()2138 public long position() { 2139 return mService.get().position(); 2140 } duration()2141 public long duration() { 2142 return mService.get().duration(); 2143 } seek(long pos)2144 public long seek(long pos) { 2145 return mService.get().seek(pos); 2146 } setShuffleMode(int shufflemode)2147 public void setShuffleMode(int shufflemode) { 2148 mService.get().setShuffleMode(shufflemode); 2149 } getShuffleMode()2150 public int getShuffleMode() { 2151 return mService.get().getShuffleMode(); 2152 } removeTracks(int first, int last)2153 public int removeTracks(int first, int last) { 2154 return mService.get().removeTracks(first, last); 2155 } removeTrack(long id)2156 public int removeTrack(long id) { 2157 return mService.get().removeTrack(id); 2158 } setRepeatMode(int repeatmode)2159 public void setRepeatMode(int repeatmode) { 2160 mService.get().setRepeatMode(repeatmode); 2161 } getRepeatMode()2162 public int getRepeatMode() { 2163 return mService.get().getRepeatMode(); 2164 } getMediaMountedCount()2165 public int getMediaMountedCount() { 2166 return mService.get().getMediaMountedCount(); 2167 } getAudioSessionId()2168 public int getAudioSessionId() { 2169 return mService.get().getAudioSessionId(); 2170 } 2171 } 2172 2173 @Override dump(FileDescriptor fd, PrintWriter writer, String[] args)2174 protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) { 2175 writer.println("" + mPlayListLen + " items in queue, currently at index " + mPlayPos); 2176 writer.println("Currently loaded:"); 2177 writer.println(getArtistName()); 2178 writer.println(getAlbumName()); 2179 writer.println(getTrackName()); 2180 writer.println(getPath()); 2181 writer.println("playing: " + mIsSupposedToBePlaying); 2182 writer.println("actual: " + mPlayer.mCurrentMediaPlayer.isPlaying()); 2183 writer.println("shuffle mode: " + mShuffleMode); 2184 MusicUtils.debugDump(writer); 2185 } 2186 2187 private final IBinder mBinder = new ServiceStub(this); 2188 } 2189