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