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