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