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