2 * Copyright (C) 2007 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.music;
19 import android.app.Notification;
20 import android.app.NotificationManager;
21 import android.app.PendingIntent;
22 import android.app.Service;
23 import android.appwidget.AppWidgetManager;
24 import android.content.ContentResolver;
25 import android.content.ContentUris;
26 import android.content.ContentValues;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.content.IntentFilter;
30 import android.content.BroadcastReceiver;
31 import android.content.SharedPreferences;
32 import android.content.SharedPreferences.Editor;
33 import android.database.Cursor;
34 import android.database.sqlite.SQLiteException;
35 import android.media.AudioManager;
36 import android.media.MediaFile;
37 import android.media.MediaPlayer;
38 import android.net.Uri;
39 import android.os.Environment;
40 import android.os.FileUtils;
41 import android.os.Handler;
42 import android.os.IBinder;
43 import android.os.Message;
44 import android.os.PowerManager;
45 import android.os.SystemClock;
46 import android.os.PowerManager.WakeLock;
47 import android.provider.MediaStore;
48 import android.util.Log;
49 import android.widget.RemoteViews;
50 import android.widget.Toast;
51 import com.android.internal.telephony.Phone;
52 import com.android.internal.telephony.PhoneStateIntentReceiver;
54 import java.io.IOException;
55 import java.util.Random;
56 import java.util.Vector;
59 * Provides "background" audio playback capabilities, allowing the
60 * user to switch between activities without stopping playback.
62 public class MediaPlaybackService extends Service {
63 /** used to specify whether enqueue() should start playing
64 * the new list of files right away, next or once all the currently
65 * queued files have been played
67 public static final int NOW = 1;
68 public static final int NEXT = 2;
69 public static final int LAST = 3;
70 public static final int PLAYBACKSERVICE_STATUS = 1;
72 public static final int SHUFFLE_NONE = 0;
73 public static final int SHUFFLE_NORMAL = 1;
74 public static final int SHUFFLE_AUTO = 2;
76 public static final int REPEAT_NONE = 0;
77 public static final int REPEAT_CURRENT = 1;
78 public static final int REPEAT_ALL = 2;
80 public static final String PLAYSTATE_CHANGED = "com.android.music.playstatechanged";
81 public static final String META_CHANGED = "com.android.music.metachanged";
82 public static final String QUEUE_CHANGED = "com.android.music.queuechanged";
83 public static final String PLAYBACK_COMPLETE = "com.android.music.playbackcomplete";
84 public static final String ASYNC_OPEN_COMPLETE = "com.android.music.asyncopencomplete";
86 public static final String SERVICECMD = "com.android.music.musicservicecommand";
87 public static final String CMDNAME = "command";
88 public static final String CMDTOGGLEPAUSE = "togglepause";
89 public static final String CMDSTOP = "stop";
90 public static final String CMDPAUSE = "pause";
91 public static final String CMDPREVIOUS = "previous";
92 public static final String CMDNEXT = "next";
94 public static final String TOGGLEPAUSE_ACTION = "com.android.music.musicservicecommand.togglepause";
95 public static final String PAUSE_ACTION = "com.android.music.musicservicecommand.pause";
96 public static final String PREVIOUS_ACTION = "com.android.music.musicservicecommand.previous";
97 public static final String NEXT_ACTION = "com.android.music.musicservicecommand.next";
99 private static final int PHONE_CHANGED = 1;
100 private static final int TRACK_ENDED = 1;
101 private static final int RELEASE_WAKELOCK = 2;
102 private static final int SERVER_DIED = 3;
103 private static final int FADEIN = 4;
104 private static final int MAX_HISTORY_SIZE = 10;
106 private MultiPlayer mPlayer;
107 private String mFileToPlay;
108 private PhoneStateIntentReceiver mPsir;
109 private int mShuffleMode = SHUFFLE_NONE;
110 private int mRepeatMode = REPEAT_NONE;
111 private int mMediaMountedCount = 0;
112 private int [] mAutoShuffleList = null;
113 private boolean mOneShot;
114 private int [] mPlayList = null;
115 private int mPlayListLen = 0;
116 private Vector<Integer> mHistory = new Vector<Integer>(MAX_HISTORY_SIZE);
117 private Cursor mCursor;
118 private int mPlayPos = -1;
119 private static final String LOGTAG = "MediaPlaybackService";
120 private final Shuffler mRand = new Shuffler();
121 private int mOpenFailedCounter = 0;
122 String[] mCursorCols = new String[] {
123 "audio._id AS _id", // index must match IDCOLIDX below
124 MediaStore.Audio.Media.ARTIST,
125 MediaStore.Audio.Media.ALBUM,
126 MediaStore.Audio.Media.TITLE,
127 MediaStore.Audio.Media.DATA,
128 MediaStore.Audio.Media.MIME_TYPE,
129 MediaStore.Audio.Media.ALBUM_ID,
130 MediaStore.Audio.Media.ARTIST_ID,
131 MediaStore.Audio.Media.IS_PODCAST, // index must match PODCASTCOLIDX below
132 MediaStore.Audio.Media.BOOKMARK // index must match BOOKMARKCOLIDX below
134 private final static int IDCOLIDX = 0;
135 private final static int PODCASTCOLIDX = 8;
136 private final static int BOOKMARKCOLIDX = 9;
137 private BroadcastReceiver mUnmountReceiver = null;
138 private WakeLock mWakeLock;
139 private int mServiceStartId = -1;
140 private boolean mServiceInUse = false;
141 private boolean mResumeAfterCall = false;
142 private boolean mWasPlaying = false;
143 private boolean mQuietMode = false;
145 private SharedPreferences mPreferences;
146 // We use this to distinguish between different cards when saving/restoring playlists.
147 // This will have to change if we want to support multiple simultaneous cards.
150 private MediaAppWidgetProvider mAppWidgetProvider = MediaAppWidgetProvider.getInstance();
152 // interval after which we stop the service when idle
153 private static final int IDLE_DELAY = 60000;
155 private Handler mPhoneHandler = new Handler() {
157 public void handleMessage(Message msg) {
160 Phone.State state = mPsir.getPhoneState();
161 if (state == Phone.State.RINGING) {
162 AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
163 int ringvolume = audioManager.getStreamVolume(AudioManager.STREAM_RING);
164 if (ringvolume > 0) {
165 mResumeAfterCall = (isPlaying() || mResumeAfterCall) && (getAudioId() >= 0);
168 } else if (state == Phone.State.OFFHOOK) {
169 // pause the music while a conversation is in progress
170 mResumeAfterCall = (isPlaying() || mResumeAfterCall) && (getAudioId() >= 0);
172 } else if (state == Phone.State.IDLE) {
173 // start playing again
174 if (mResumeAfterCall) {
175 // resume playback only if music was playing
176 // when the call was answered
178 mResumeAfterCall = false;
188 private void startAndFadeIn() {
189 mMediaplayerHandler.sendEmptyMessageDelayed(FADEIN, 10);
192 private Handler mMediaplayerHandler = new Handler() {
193 float mCurrentVolume = 1.0f;
195 public void handleMessage(Message msg) {
200 mPlayer.setVolume(mCurrentVolume);
202 mMediaplayerHandler.sendEmptyMessageDelayed(FADEIN, 10);
204 mCurrentVolume += 0.01f;
205 if (mCurrentVolume < 1.0f) {
206 mMediaplayerHandler.sendEmptyMessageDelayed(FADEIN, 10);
208 mCurrentVolume = 1.0f;
210 mPlayer.setVolume(mCurrentVolume);
217 // the server died when we were idle, so just
218 // reopen the same song (it will start again
219 // from the beginning though when the user
225 if (mRepeatMode == REPEAT_CURRENT) {
228 } else if (!mOneShot) {
231 notifyChange(PLAYBACK_COMPLETE);
234 case RELEASE_WAKELOCK:
243 private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
245 public void onReceive(Context context, Intent intent) {
246 String action = intent.getAction();
247 String cmd = intent.getStringExtra("command");
248 if (CMDNEXT.equals(cmd) || NEXT_ACTION.equals(action)) {
250 } else if (CMDPREVIOUS.equals(cmd) || PREVIOUS_ACTION.equals(action)) {
252 } else if (CMDTOGGLEPAUSE.equals(cmd) || TOGGLEPAUSE_ACTION.equals(action)) {
258 } else if (CMDPAUSE.equals(cmd) || PAUSE_ACTION.equals(action)) {
260 } else if (CMDSTOP.equals(cmd)) {
263 } else if (MediaAppWidgetProvider.CMDAPPWIDGETUPDATE.equals(cmd)) {
264 // Someone asked us to refresh a set of specific widgets, probably
265 // because they were just added.
266 int[] appWidgetIds = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS);
267 mAppWidgetProvider.performUpdate(MediaPlaybackService.this, appWidgetIds);
272 public MediaPlaybackService() {
273 mPsir = new PhoneStateIntentReceiver(this, mPhoneHandler);
274 mPsir.notifyPhoneCallState(PHONE_CHANGED);
278 public void onCreate() {
281 mPreferences = getSharedPreferences("Music", MODE_WORLD_READABLE | MODE_WORLD_WRITEABLE);
282 mCardId = FileUtils.getFatVolumeId(Environment.getExternalStorageDirectory().getPath());
284 registerExternalStorageListener();
286 // Needs to be done in this thread, since otherwise ApplicationContext.getPowerManager() crashes.
287 mPlayer = new MultiPlayer();
288 mPlayer.setHandler(mMediaplayerHandler);
290 // Clear leftover notification in case this service previously got killed while playing
291 NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
292 nm.cancel(PLAYBACKSERVICE_STATUS);
296 IntentFilter commandFilter = new IntentFilter();
297 commandFilter.addAction(SERVICECMD);
298 commandFilter.addAction(TOGGLEPAUSE_ACTION);
299 commandFilter.addAction(PAUSE_ACTION);
300 commandFilter.addAction(NEXT_ACTION);
301 commandFilter.addAction(PREVIOUS_ACTION);
302 registerReceiver(mIntentReceiver, commandFilter);
304 mPsir.registerIntent();
305 PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
306 mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, this.getClass().getName());
307 mWakeLock.setReferenceCounted(false);
309 // If the service was idle, but got killed before it stopped itself, the
310 // system will relaunch it. Make sure it gets stopped again in that case.
311 Message msg = mDelayedStopHandler.obtainMessage();
312 mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
316 public void onDestroy() {
317 // Check that we're not being destroyed while something is still playing.
319 Log.e("MediaPlaybackService", "Service being destroyed while still playing.");
321 // and for good measure, call mPlayer.stop(), which calls MediaPlayer.reset(), which
322 // releases the MediaPlayer's wake lock, if any.
325 if (mCursor != null) {
330 unregisterReceiver(mIntentReceiver);
331 if (mUnmountReceiver != null) {
332 unregisterReceiver(mUnmountReceiver);
333 mUnmountReceiver = null;
335 mPsir.unregisterIntent();
340 private final char hexdigits [] = new char [] {
347 private void saveQueue(boolean full) {
351 Editor ed = mPreferences.edit();
352 //long start = System.currentTimeMillis();
354 StringBuilder q = new StringBuilder();
356 // The current playlist is saved as a list of "reverse hexadecimal"
357 // numbers, which we can generate faster than normal decimal or
358 // hexadecimal numbers, which in turn allows us to save the playlist
359 // more often without worrying too much about performance.
360 // (saving the full state takes about 40 ms under no-load conditions
362 int len = mPlayListLen;
363 for (int i = 0; i < len; i++) {
364 int n = mPlayList[i];
371 q.append(hexdigits[digit]);
376 //Log.i("@@@@ service", "created queue string in " + (System.currentTimeMillis() - start) + " ms");
377 ed.putString("queue", q.toString());
378 ed.putInt("cardid", mCardId);
380 ed.putInt("curpos", mPlayPos);
381 if (mPlayer.isInitialized()) {
382 ed.putLong("seekpos", mPlayer.position());
384 ed.putInt("repeatmode", mRepeatMode);
385 ed.putInt("shufflemode", mShuffleMode);
388 //Log.i("@@@@ service", "saved state in " + (System.currentTimeMillis() - start) + " ms");
391 private void reloadQueue() {
394 boolean newstyle = false;
396 if (mPreferences.contains("cardid")) {
398 id = mPreferences.getInt("cardid", ~mCardId);
401 // Only restore the saved playlist if the card is still
402 // the same one as when the playlist was saved
403 q = mPreferences.getString("queue", "");
405 if (q != null && q.length() > 1) {
406 //Log.i("@@@@ service", "loaded queue: " + q);
407 String [] entries = q.split(";");
408 int len = entries.length;
409 ensurePlayListCapacity(len);
410 for (int i = 0; i < len; i++) {
412 String revhex = entries[i];
414 for (int j = revhex.length() - 1; j >= 0 ; j--) {
416 char c = revhex.charAt(j);
417 if (c >= '0' && c <= '9') {
419 } else if (c >= 'a' && c <= 'f') {
422 // bogus playlist data
429 mPlayList[i] = Integer.parseInt(entries[i]);
434 int pos = mPreferences.getInt("curpos", 0);
435 if (pos < 0 || pos >= len) {
436 // The saved playlist is bogus, discard it
442 // When reloadQueue is called in response to a card-insertion,
443 // we might not be able to query the media provider right away.
444 // To deal with this, try querying for the current file, and if
445 // that fails, wait a while and try again. If that too fails,
446 // assume there is a problem and don't restore the state.
447 Cursor c = MusicUtils.query(this,
448 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
449 new String [] {"_id"}, "_id=" + mPlayList[mPlayPos] , null, null);
450 if (c == null || c.getCount() == 0) {
451 // wait a bit and try again
452 SystemClock.sleep(3000);
453 c = getContentResolver().query(
454 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
455 mCursorCols, "_id=" + mPlayList[mPlayPos] , null, null);
461 // Make sure we don't auto-skip to the next song, since that
462 // also starts playback. What could happen in that case is:
464 // - go to UMS and delete some files, including the currently playing one
465 // - come back from UMS
467 // - music app is killed for some reason (out of memory)
468 // - music service is restarted, service restores state, doesn't find
469 // the "current" file, goes to the next and: playback starts on its
470 // own, potentially at some random inconvenient time.
471 mOpenFailedCounter = 20;
475 if (!mPlayer.isInitialized()) {
476 // couldn't restore the saved state
481 long seekpos = mPreferences.getLong("seekpos", 0);
482 seek(seekpos >= 0 && seekpos < duration() ? seekpos : 0);
484 int repmode = mPreferences.getInt("repeatmode", REPEAT_NONE);
485 if (repmode != REPEAT_ALL && repmode != REPEAT_CURRENT) {
486 repmode = REPEAT_NONE;
488 mRepeatMode = repmode;
490 int shufmode = mPreferences.getInt("shufflemode", SHUFFLE_NONE);
491 if (shufmode != SHUFFLE_AUTO && shufmode != SHUFFLE_NORMAL) {
492 shufmode = SHUFFLE_NONE;
494 if (shufmode == SHUFFLE_AUTO) {
495 if (! makeAutoShuffleList()) {
496 shufmode = SHUFFLE_NONE;
499 mShuffleMode = shufmode;
504 public IBinder onBind(Intent intent) {
505 mDelayedStopHandler.removeCallbacksAndMessages(null);
506 mServiceInUse = true;
511 public void onRebind(Intent intent) {
512 mDelayedStopHandler.removeCallbacksAndMessages(null);
513 mServiceInUse = true;
517 public void onStart(Intent intent, int startId) {
518 mServiceStartId = startId;
519 mDelayedStopHandler.removeCallbacksAndMessages(null);
521 String action = intent.getAction();
522 String cmd = intent.getStringExtra("command");
524 if (CMDNEXT.equals(cmd) || NEXT_ACTION.equals(action)) {
526 } else if (CMDPREVIOUS.equals(cmd) || PREVIOUS_ACTION.equals(action)) {
528 } else if (CMDTOGGLEPAUSE.equals(cmd) || TOGGLEPAUSE_ACTION.equals(action)) {
534 } else if (CMDPAUSE.equals(cmd) || PAUSE_ACTION.equals(action)) {
536 } else if (CMDSTOP.equals(cmd)) {
541 // make sure the service will shut down on its own if it was
542 // just started but not bound to and nothing is playing
543 mDelayedStopHandler.removeCallbacksAndMessages(null);
544 Message msg = mDelayedStopHandler.obtainMessage();
545 mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
549 public boolean onUnbind(Intent intent) {
550 mServiceInUse = false;
552 // Take a snapshot of the current playlist
555 if (isPlaying() || mResumeAfterCall) {
556 // something is currently playing, or will be playing once
557 // an in-progress call ends, so don't stop the service now.
561 // If there is a playlist but playback is paused, then wait a while
562 // before stopping the service, so that pause/resume isn't slow.
563 // Also delay stopping the service if we're transitioning between tracks.
564 if (mPlayListLen > 0 || mMediaplayerHandler.hasMessages(TRACK_ENDED)) {
565 Message msg = mDelayedStopHandler.obtainMessage();
566 mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
570 // No active playlist, OK to stop the service right now
571 stopSelf(mServiceStartId);
575 private Handler mDelayedStopHandler = new Handler() {
577 public void handleMessage(Message msg) {
578 // Check again to make sure nothing is playing right now
579 if (isPlaying() || mResumeAfterCall || mServiceInUse
580 || mMediaplayerHandler.hasMessages(TRACK_ENDED)) {
583 // save the queue again, because it might have changed
584 // since the user exited the music app (because of
585 // party-shuffle or because the play-position changed)
587 stopSelf(mServiceStartId);
592 * Called when we receive a ACTION_MEDIA_EJECT notification.
594 * @param storagePath path to mount point for the removed media
596 public void closeExternalStorageFiles(String storagePath) {
597 // stop playback and clean up if the SD card is going to be unmounted.
599 notifyChange(QUEUE_CHANGED);
600 notifyChange(META_CHANGED);
604 * Registers an intent to listen for ACTION_MEDIA_EJECT notifications.
605 * The intent will call closeExternalStorageFiles() if the external media
606 * is going to be ejected, so applications can clean up any files they have open.
608 public void registerExternalStorageListener() {
609 if (mUnmountReceiver == null) {
610 mUnmountReceiver = new BroadcastReceiver() {
612 public void onReceive(Context context, Intent intent) {
613 String action = intent.getAction();
614 if (action.equals(Intent.ACTION_MEDIA_EJECT)) {
616 mOneShot = true; // This makes us not save the state again later,
617 // which would be wrong because the song ids and
618 // card id might not match.
619 closeExternalStorageFiles(intent.getData().getPath());
620 } else if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) {
621 mMediaMountedCount++;
622 mCardId = FileUtils.getFatVolumeId(intent.getData().getPath());
624 notifyChange(QUEUE_CHANGED);
625 notifyChange(META_CHANGED);
629 IntentFilter iFilter = new IntentFilter();
630 iFilter.addAction(Intent.ACTION_MEDIA_EJECT);
631 iFilter.addAction(Intent.ACTION_MEDIA_MOUNTED);
632 iFilter.addDataScheme("file");
633 registerReceiver(mUnmountReceiver, iFilter);
638 * Notify the change-receivers that something has changed.
639 * The intent that is sent contains the following data
640 * for the currently playing track:
641 * "id" - Integer: the database row ID
642 * "artist" - String: the name of the artist
643 * "album" - String: the name of the album
644 * "track" - String: the name of the track
645 * The intent has an action that is one of
646 * "com.android.music.metachanged"
647 * "com.android.music.queuechanged",
648 * "com.android.music.playbackcomplete"
649 * "com.android.music.playstatechanged"
650 * respectively indicating that a new track has
651 * started playing, that the playback queue has
652 * changed, that playback has stopped because
653 * the last file in the list has been played,
654 * or that the play-state changed (paused/resumed).
656 private void notifyChange(String what) {
658 Intent i = new Intent(what);
659 i.putExtra("id", Integer.valueOf(getAudioId()));
660 i.putExtra("artist", getArtistName());
661 i.putExtra("album",getAlbumName());
662 i.putExtra("track", getTrackName());
665 if (what.equals(QUEUE_CHANGED)) {
671 // Share this notification directly with our widgets
672 mAppWidgetProvider.notifyChange(this, what);
675 private void ensurePlayListCapacity(int size) {
676 if (mPlayList == null || size > mPlayList.length) {
677 // reallocate at 2x requested size so we don't
678 // need to grow and copy the array for every
680 int [] newlist = new int[size * 2];
681 int len = mPlayListLen;
682 for (int i = 0; i < len; i++) {
683 newlist[i] = mPlayList[i];
687 // FIXME: shrink the array when the needed size is much smaller
688 // than the allocated size
691 // insert the list of songs at the specified position in the playlist
692 private void addToPlayList(int [] list, int position) {
693 int addlen = list.length;
694 if (position < 0) { // overwrite
698 ensurePlayListCapacity(mPlayListLen + addlen);
699 if (position > mPlayListLen) {
700 position = mPlayListLen;
703 // move part of list after insertion point
704 int tailsize = mPlayListLen - position;
705 for (int i = tailsize ; i > 0 ; i--) {
706 mPlayList[position + i] = mPlayList[position + i - addlen];
709 // copy list into playlist
710 for (int i = 0; i < addlen; i++) {
711 mPlayList[position + i] = list[i];
713 mPlayListLen += addlen;
717 * Appends a list of tracks to the current playlist.
718 * If nothing is playing currently, playback will be started at
720 * If the action is NOW, playback will switch to the first of
721 * the new tracks immediately.
722 * @param list The list of tracks to append.
723 * @param action NOW, NEXT or LAST
725 public void enqueue(int [] list, int action) {
727 if (action == NEXT && mPlayPos + 1 < mPlayListLen) {
728 addToPlayList(list, mPlayPos + 1);
729 notifyChange(QUEUE_CHANGED);
731 // action == LAST || action == NOW || mPlayPos + 1 == mPlayListLen
732 addToPlayList(list, Integer.MAX_VALUE);
733 notifyChange(QUEUE_CHANGED);
735 mPlayPos = mPlayListLen - list.length;
738 notifyChange(META_CHANGED);
746 notifyChange(META_CHANGED);
752 * Replaces the current playlist with a new list,
753 * and prepares for starting playback at the specified
754 * position in the list, or a random position if the
755 * specified position is 0.
756 * @param list The new list of tracks.
758 public void open(int [] list, int position) {
759 synchronized (this) {
760 if (mShuffleMode == SHUFFLE_AUTO) {
761 mShuffleMode = SHUFFLE_NORMAL;
763 int oldId = getAudioId();
764 int listlength = list.length;
765 boolean newlist = true;
766 if (mPlayListLen == listlength) {
767 // possible fast path: list might be the same
769 for (int i = 0; i < listlength; i++) {
770 if (list[i] != mPlayList[i]) {
777 addToPlayList(list, -1);
778 notifyChange(QUEUE_CHANGED);
780 int oldpos = mPlayPos;
784 mPlayPos = mRand.nextInt(mPlayListLen);
788 saveBookmarkIfNeeded();
790 if (oldId != getAudioId()) {
791 notifyChange(META_CHANGED);
797 * Moves the item at index1 to index2.
801 public void moveQueueItem(int index1, int index2) {
802 synchronized (this) {
803 if (index1 >= mPlayListLen) {
804 index1 = mPlayListLen - 1;
806 if (index2 >= mPlayListLen) {
807 index2 = mPlayListLen - 1;
809 if (index1 < index2) {
810 int tmp = mPlayList[index1];
811 for (int i = index1; i < index2; i++) {
812 mPlayList[i] = mPlayList[i+1];
814 mPlayList[index2] = tmp;
815 if (mPlayPos == index1) {
817 } else if (mPlayPos >= index1 && mPlayPos <= index2) {
820 } else if (index2 < index1) {
821 int tmp = mPlayList[index1];
822 for (int i = index1; i > index2; i--) {
823 mPlayList[i] = mPlayList[i-1];
825 mPlayList[index2] = tmp;
826 if (mPlayPos == index1) {
828 } else if (mPlayPos >= index2 && mPlayPos <= index1) {
832 notifyChange(QUEUE_CHANGED);
837 * Returns the current play list
838 * @return An array of integers containing the IDs of the tracks in the play list
840 public int [] getQueue() {
841 synchronized (this) {
842 int len = mPlayListLen;
843 int [] list = new int[len];
844 for (int i = 0; i < len; i++) {
845 list[i] = mPlayList[i];
851 private void openCurrent() {
852 synchronized (this) {
853 if (mCursor != null) {
857 if (mPlayListLen == 0) {
862 String id = String.valueOf(mPlayList[mPlayPos]);
864 mCursor = getContentResolver().query(
865 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
866 mCursorCols, "_id=" + id , null, null);
867 if (mCursor != null) {
868 mCursor.moveToFirst();
869 open(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + "/" + id, false);
870 // go to bookmark if needed
872 long bookmark = getBookmark();
873 // Start playing a little bit before the bookmark,
874 // so it's easier to get back in to the narrative.
875 seek(bookmark - 5000);
881 public void openAsync(String path) {
882 synchronized (this) {
887 mRepeatMode = REPEAT_NONE;
888 ensurePlayListCapacity(1);
894 mPlayer.setDataSourceAsync(mFileToPlay);
900 * Opens the specified file and readies it for playback.
902 * @param path The full path of the file to be opened.
903 * @param oneshot when set to true, playback will stop after this file completes, instead
904 * of moving on to the next track in the list
906 public void open(String path, boolean oneshot) {
907 synchronized (this) {
913 mRepeatMode = REPEAT_NONE;
914 ensurePlayListCapacity(1);
919 // if mCursor is null, try to associate path with a database cursor
920 if (mCursor == null) {
922 ContentResolver resolver = getContentResolver();
925 String selectionArgs[];
926 if (path.startsWith("content://media/")) {
927 uri = Uri.parse(path);
929 selectionArgs = null;
931 uri = MediaStore.Audio.Media.getContentUriForPath(path);
932 where = MediaStore.Audio.Media.DATA + "=?";
933 selectionArgs = new String[] { path };
937 mCursor = resolver.query(uri, mCursorCols, where, selectionArgs, null);
938 if (mCursor != null) {
939 if (mCursor.getCount() == 0) {
943 mCursor.moveToNext();
944 ensurePlayListCapacity(1);
946 mPlayList[0] = mCursor.getInt(IDCOLIDX);
950 } catch (UnsupportedOperationException ex) {
954 mPlayer.setDataSource(mFileToPlay);
956 if (! mPlayer.isInitialized()) {
958 if (mOpenFailedCounter++ < 10 && mPlayListLen > 1) {
959 // beware: this ends up being recursive because next() calls open() again.
962 if (! mPlayer.isInitialized() && mOpenFailedCounter != 0) {
963 // need to make sure we only shows this once
964 mOpenFailedCounter = 0;
966 Toast.makeText(this, R.string.playback_failed, Toast.LENGTH_SHORT).show();
970 mOpenFailedCounter = 0;
976 * Starts playback of a previously opened file.
979 if (mPlayer.isInitialized()) {
983 NotificationManager nm = (NotificationManager)
984 getSystemService(Context.NOTIFICATION_SERVICE);
986 RemoteViews views = new RemoteViews(getPackageName(), R.layout.statusbar);
987 views.setImageViewResource(R.id.icon, R.drawable.stat_notify_musicplayer);
988 if (getAudioId() < 0) {
990 views.setTextViewText(R.id.trackname, getPath());
991 views.setTextViewText(R.id.artistalbum, null);
993 String artist = getArtistName();
994 views.setTextViewText(R.id.trackname, getTrackName());
995 if (artist == null || artist.equals(MediaFile.UNKNOWN_STRING)) {
996 artist = getString(R.string.unknown_artist_name);
998 String album = getAlbumName();
999 if (album == null || album.equals(MediaFile.UNKNOWN_STRING)) {
1000 album = getString(R.string.unknown_album_name);
1003 views.setTextViewText(R.id.artistalbum,
1004 getString(R.string.notification_artist_album, artist, album)
1008 Intent statusintent = new Intent("com.android.music.PLAYBACK_VIEWER");
1009 statusintent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
1010 Notification status = new Notification();
1011 status.contentView = views;
1012 status.flags |= Notification.FLAG_ONGOING_EVENT;
1013 status.icon = R.drawable.stat_notify_musicplayer;
1014 status.contentIntent = PendingIntent.getActivity(this, 0,
1015 new Intent("com.android.music.PLAYBACK_VIEWER"), 0);
1016 nm.notify(PLAYBACKSERVICE_STATUS, status);
1018 notifyChange(PLAYSTATE_CHANGED);
1021 } else if (mPlayListLen <= 0) {
1022 // This is mostly so that if you press 'play' on a bluetooth headset
1023 // without every having played anything before, it will still play
1025 setShuffleMode(SHUFFLE_AUTO);
1029 private void stop(boolean remove_status_icon) {
1030 if (mPlayer.isInitialized()) {
1034 if (mCursor != null) {
1038 if (remove_status_icon) {
1041 setForeground(false);
1042 if (remove_status_icon) {
1043 mWasPlaying = false;
1050 public void stop() {
1055 * Pauses playback (call play() to resume)
1057 public void pause() {
1061 setForeground(false);
1062 mWasPlaying = false;
1063 notifyChange(PLAYSTATE_CHANGED);
1064 saveBookmarkIfNeeded();
1068 /** Returns whether playback is currently paused
1070 * @return true if playback is paused, false if not
1072 public boolean isPlaying() {
1073 if (mPlayer.isInitialized()) {
1074 return mPlayer.isPlaying();
1080 Desired behavior for prev/next/shuffle:
1082 - NEXT will move to the next track in the list when not shuffling, and to
1083 a track randomly picked from the not-yet-played tracks when shuffling.
1084 If all tracks have already been played, pick from the full set, but
1085 avoid picking the previously played track if possible.
1086 - when shuffling, PREV will go to the previously played track. Hitting PREV
1087 again will go to the track played before that, etc. When the start of the
1088 history has been reached, PREV is a no-op.
1089 When not shuffling, PREV will go to the sequentially previous track (the
1090 difference with the shuffle-case is mainly that when not shuffling, the
1091 user can back up to tracks that are not in the history).
1094 When playing an album with 10 tracks from the start, and enabling shuffle
1095 while playing track 5, the remaining tracks (6-10) will be shuffled, e.g.
1096 the final play order might be 1-2-3-4-5-8-10-6-9-7.
1097 When hitting 'prev' 8 times while playing track 7 in this example, the
1098 user will go to tracks 9-6-10-8-5-4-3-2. If the user then hits 'next',
1099 a random track will be picked again. If at any time user disables shuffling
1100 the next/previous track will be picked in sequential order again.
1103 public void prev() {
1104 synchronized (this) {
1106 // we were playing a specific file not part of a playlist, so there is no 'previous'
1111 if (mShuffleMode == SHUFFLE_NORMAL) {
1112 // go to previously-played track and remove it from the history
1113 int histsize = mHistory.size();
1114 if (histsize == 0) {
1118 Integer pos = mHistory.remove(histsize - 1);
1119 mPlayPos = pos.intValue();
1124 mPlayPos = mPlayListLen - 1;
1127 saveBookmarkIfNeeded();
1131 notifyChange(META_CHANGED);
1135 public void next(boolean force) {
1136 synchronized (this) {
1138 // we were playing a specific file not part of a playlist, so there is no 'next'
1144 // Store the current file in the history, but keep the history at a
1146 if (mPlayPos >= 0) {
1147 mHistory.add(Integer.valueOf(mPlayPos));
1149 if (mHistory.size() > MAX_HISTORY_SIZE) {
1150 mHistory.removeElementAt(0);
1153 if (mShuffleMode == SHUFFLE_NORMAL) {
1154 // Pick random next track from the not-yet-played ones
1155 // TODO: make it work right after adding/removing items in the queue.
1157 int numTracks = mPlayListLen;
1158 int[] tracks = new int[numTracks];
1159 for (int i=0;i < numTracks; i++) {
1163 int numHistory = mHistory.size();
1164 int numUnplayed = numTracks;
1165 for (int i=0;i < numHistory; i++) {
1166 int idx = mHistory.get(i).intValue();
1167 if (idx < numTracks && tracks[idx] >= 0) {
1173 // 'numUnplayed' now indicates how many tracks have not yet
1174 // been played, and 'tracks' contains the indices of those
1176 if (numUnplayed <=0) {
1177 // everything's already been played
1178 if (mRepeatMode == REPEAT_ALL || force) {
1179 //pick from full set
1180 numUnplayed = numTracks;
1181 for (int i=0;i < numTracks; i++) {
1190 int skip = mRand.nextInt(numUnplayed);
1193 while (tracks[++cnt] < 0)
1201 } else if (mShuffleMode == SHUFFLE_AUTO) {
1202 doAutoShuffleUpdate();
1205 if (mPlayPos >= mPlayListLen - 1) {
1206 // we're at the end of the list
1207 if (mRepeatMode == REPEAT_NONE && !force) {
1210 notifyChange(PLAYBACK_COMPLETE);
1212 } else if (mRepeatMode == REPEAT_ALL || force) {
1219 saveBookmarkIfNeeded();
1223 notifyChange(META_CHANGED);
1227 private void gotoIdleState() {
1228 NotificationManager nm =
1229 (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
1230 nm.cancel(PLAYBACKSERVICE_STATUS);
1231 mDelayedStopHandler.removeCallbacksAndMessages(null);
1232 Message msg = mDelayedStopHandler.obtainMessage();
1233 mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
1236 private void saveBookmarkIfNeeded() {
1239 long pos = position();
1240 long bookmark = getBookmark();
1241 long duration = duration();
1242 if ((pos < bookmark && (pos + 10000) > bookmark) ||
1243 (pos > bookmark && (pos - 10000) < bookmark)) {
1244 // The existing bookmark is close to the current
1245 // position, so don't update it.
1248 if (pos < 15000 || (pos + 10000) > duration) {
1249 // if we're near the start or end, clear the bookmark
1253 // write 'pos' to the bookmark field
1254 ContentValues values = new ContentValues();
1255 values.put(MediaStore.Audio.Media.BOOKMARK, pos);
1256 Uri uri = ContentUris.withAppendedId(
1257 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, mCursor.getLong(IDCOLIDX));
1258 getContentResolver().update(uri, values, null, null);
1260 } catch (SQLiteException ex) {
1264 // Make sure there are at least 5 items after the currently playing item
1265 // and no more than 10 items before.
1266 private void doAutoShuffleUpdate() {
1267 boolean notify = false;
1268 // remove old entries
1269 if (mPlayPos > 10) {
1270 removeTracks(0, mPlayPos - 9);
1273 // add new entries if needed
1274 int to_add = 7 - (mPlayListLen - (mPlayPos < 0 ? -1 : mPlayPos));
1275 for (int i = 0; i < to_add; i++) {
1276 // pick something at random from the list
1277 int idx = mRand.nextInt(mAutoShuffleList.length);
1278 Integer which = mAutoShuffleList[idx];
1279 ensurePlayListCapacity(mPlayListLen + 1);
1280 mPlayList[mPlayListLen++] = which;
1284 notifyChange(QUEUE_CHANGED);
1288 // A simple variation of Random that makes sure that the
1289 // value it returns is not equal to the value it returned
1290 // previously, unless the interval is 1.
1291 private class Shuffler {
1292 private int mPrevious;
1293 private Random mRandom = new Random();
1294 public int nextInt(int interval) {
1297 ret = mRandom.nextInt(interval);
1298 } while (ret == mPrevious && interval > 1);
1304 private boolean makeAutoShuffleList() {
1305 ContentResolver res = getContentResolver();
1308 c = res.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
1309 new String[] {MediaStore.Audio.Media._ID}, MediaStore.Audio.Media.IS_MUSIC + "=1",
1311 if (c == null || c.getCount() == 0) {
1314 int len = c.getCount();
1315 int[] list = new int[len];
1316 for (int i = 0; i < len; i++) {
1318 list[i] = c.getInt(0);
1320 mAutoShuffleList = list;
1322 } catch (RuntimeException ex) {
1332 * Removes the range of tracks specified from the play list. If a file within the range is
1333 * the file currently being played, playback will move to the next file after the
1335 * @param first The first file to be removed
1336 * @param last The last file to be removed
1337 * @return the number of tracks deleted
1339 public int removeTracks(int first, int last) {
1340 int numremoved = removeTracksInternal(first, last);
1341 if (numremoved > 0) {
1342 notifyChange(QUEUE_CHANGED);
1347 private int removeTracksInternal(int first, int last) {
1348 synchronized (this) {
1349 if (last < first) return 0;
1350 if (first < 0) first = 0;
1351 if (last >= mPlayListLen) last = mPlayListLen - 1;
1353 boolean gotonext = false;
1354 if (first <= mPlayPos && mPlayPos <= last) {
1357 } else if (mPlayPos > last) {
1358 mPlayPos -= (last - first + 1);
1360 int num = mPlayListLen - last - 1;
1361 for (int i = 0; i < num; i++) {
1362 mPlayList[first + i] = mPlayList[last + 1 + i];
1364 mPlayListLen -= last - first + 1;
1367 if (mPlayListLen == 0) {
1371 if (mPlayPos >= mPlayListLen) {
1374 boolean wasPlaying = isPlaying();
1382 return last - first + 1;
1387 * Removes all instances of the track with the given id
1388 * from the playlist.
1389 * @param id The id to be removed
1390 * @return how many instances of the track were removed
1392 public int removeTrack(int id) {
1394 synchronized (this) {
1395 for (int i = 0; i < mPlayListLen; i++) {
1396 if (mPlayList[i] == id) {
1397 numremoved += removeTracksInternal(i, i);
1402 if (numremoved > 0) {
1403 notifyChange(QUEUE_CHANGED);
1408 public void setShuffleMode(int shufflemode) {
1409 synchronized(this) {
1410 if (mShuffleMode == shufflemode && mPlayListLen > 0) {
1413 mShuffleMode = shufflemode;
1414 if (mShuffleMode == SHUFFLE_AUTO) {
1415 if (makeAutoShuffleList()) {
1417 doAutoShuffleUpdate();
1421 notifyChange(META_CHANGED);
1424 // failed to build a list of files to shuffle
1425 mShuffleMode = SHUFFLE_NONE;
1431 public int getShuffleMode() {
1432 return mShuffleMode;
1435 public void setRepeatMode(int repeatmode) {
1436 synchronized(this) {
1437 mRepeatMode = repeatmode;
1441 public int getRepeatMode() {
1445 public int getMediaMountedCount() {
1446 return mMediaMountedCount;
1450 * Returns the path of the currently playing file, or null if
1451 * no file is currently playing.
1453 public String getPath() {
1458 * Returns the rowid of the currently playing file, or -1 if
1459 * no file is currently playing.
1461 public int getAudioId() {
1462 synchronized (this) {
1463 if (mPlayPos >= 0 && mPlayer.isInitialized()) {
1464 return mPlayList[mPlayPos];
1471 * Returns the position in the queue
1472 * @return the position in the queue
1474 public int getQueuePosition() {
1475 synchronized(this) {
1481 * Starts playing the track at the given position in the queue.
1482 * @param pos The position in the queue of the track that will be played.
1484 public void setQueuePosition(int pos) {
1485 synchronized(this) {
1490 notifyChange(META_CHANGED);
1494 public String getArtistName() {
1495 synchronized(this) {
1496 if (mCursor == null) {
1499 return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST));
1503 public int getArtistId() {
1504 synchronized (this) {
1505 if (mCursor == null) {
1508 return mCursor.getInt(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST_ID));
1512 public String getAlbumName() {
1513 synchronized (this) {
1514 if (mCursor == null) {
1517 return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM));
1521 public int getAlbumId() {
1522 synchronized (this) {
1523 if (mCursor == null) {
1526 return mCursor.getInt(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM_ID));
1530 public String getTrackName() {
1531 synchronized (this) {
1532 if (mCursor == null) {
1535 return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE));
1539 private boolean isPodcast() {
1540 synchronized (this) {
1541 if (mCursor == null) {
1544 return (mCursor.getInt(PODCASTCOLIDX) > 0);
1548 private long getBookmark() {
1549 synchronized (this) {
1550 if (mCursor == null) {
1553 return mCursor.getLong(BOOKMARKCOLIDX);
1558 * Returns the duration of the file in milliseconds.
1559 * Currently this method returns -1 for the duration of MIDI files.
1561 public long duration() {
1562 if (mPlayer.isInitialized()) {
1563 return mPlayer.duration();
1569 * Returns the current playback position in milliseconds
1571 public long position() {
1572 if (mPlayer.isInitialized()) {
1573 return mPlayer.position();
1579 * Seeks to the position specified.
1581 * @param pos The position to seek to, in milliseconds
1583 public long seek(long pos) {
1584 if (mPlayer.isInitialized()) {
1585 if (pos < 0) pos = 0;
1586 if (pos > mPlayer.duration()) pos = mPlayer.duration();
1587 return mPlayer.seek(pos);
1593 * Provides a unified interface for dealing with midi files and
1594 * other media files.
1596 private class MultiPlayer {
1597 private MediaPlayer mMediaPlayer = new MediaPlayer();
1598 private Handler mHandler;
1599 private boolean mIsInitialized = false;
1601 public MultiPlayer() {
1602 mMediaPlayer.setWakeMode(MediaPlaybackService.this, PowerManager.PARTIAL_WAKE_LOCK);
1605 public void setDataSourceAsync(String path) {
1607 mMediaPlayer.reset();
1608 mMediaPlayer.setDataSource(path);
1609 mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
1610 mMediaPlayer.setOnPreparedListener(preparedlistener);
1611 mMediaPlayer.prepareAsync();
1612 } catch (IOException ex) {
1613 // TODO: notify the user why the file couldn't be opened
1614 mIsInitialized = false;
1616 } catch (IllegalArgumentException ex) {
1617 // TODO: notify the user why the file couldn't be opened
1618 mIsInitialized = false;
1621 mMediaPlayer.setOnCompletionListener(listener);
1622 mMediaPlayer.setOnErrorListener(errorListener);
1624 mIsInitialized = true;
1627 public void setDataSource(String path) {
1629 mMediaPlayer.reset();
1630 mMediaPlayer.setOnPreparedListener(null);
1631 if (path.startsWith("content://")) {
1632 mMediaPlayer.setDataSource(MediaPlaybackService.this, Uri.parse(path));
1634 mMediaPlayer.setDataSource(path);
1636 mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
1637 mMediaPlayer.prepare();
1638 } catch (IOException ex) {
1639 // TODO: notify the user why the file couldn't be opened
1640 mIsInitialized = false;
1642 } catch (IllegalArgumentException ex) {
1643 // TODO: notify the user why the file couldn't be opened
1644 mIsInitialized = false;
1647 mMediaPlayer.setOnCompletionListener(listener);
1648 mMediaPlayer.setOnErrorListener(errorListener);
1650 mIsInitialized = true;
1653 public boolean isInitialized() {
1654 return mIsInitialized;
1657 public void start() {
1658 mMediaPlayer.start();
1661 public void stop() {
1662 mMediaPlayer.reset();
1663 mIsInitialized = false;
1666 public void pause() {
1667 mMediaPlayer.pause();
1670 public boolean isPlaying() {
1671 return mMediaPlayer.isPlaying();
1674 public void setHandler(Handler handler) {
1678 MediaPlayer.OnCompletionListener listener = new MediaPlayer.OnCompletionListener() {
1679 public void onCompletion(MediaPlayer mp) {
1680 // Acquire a temporary wakelock, since when we return from
1681 // this callback the MediaPlayer will release its wakelock
1682 // and allow the device to go to sleep.
1683 // This temporary wakelock is released when the RELEASE_WAKELOCK
1684 // message is processed, but just in case, put a timeout on it.
1685 mWakeLock.acquire(30000);
1686 mHandler.sendEmptyMessage(TRACK_ENDED);
1687 mHandler.sendEmptyMessage(RELEASE_WAKELOCK);
1691 MediaPlayer.OnPreparedListener preparedlistener = new MediaPlayer.OnPreparedListener() {
1692 public void onPrepared(MediaPlayer mp) {
1693 notifyChange(ASYNC_OPEN_COMPLETE);
1697 MediaPlayer.OnErrorListener errorListener = new MediaPlayer.OnErrorListener() {
1698 public boolean onError(MediaPlayer mp, int what, int extra) {
1700 case MediaPlayer.MEDIA_ERROR_SERVER_DIED:
1701 mIsInitialized = false;
1702 mMediaPlayer.release();
1703 // Creating a new MediaPlayer and settings its wakemode does not
1704 // require the media service, so it's OK to do this now, while the
1705 // service is still being restarted
1706 mMediaPlayer = new MediaPlayer();
1707 mMediaPlayer.setWakeMode(MediaPlaybackService.this, PowerManager.PARTIAL_WAKE_LOCK);
1708 mHandler.sendMessageDelayed(mHandler.obtainMessage(SERVER_DIED), 2000);
1717 public long duration() {
1718 return mMediaPlayer.getDuration();
1721 public long position() {
1722 return mMediaPlayer.getCurrentPosition();
1725 public long seek(long whereto) {
1726 mMediaPlayer.seekTo((int) whereto);
1730 public void setVolume(float vol) {
1731 mMediaPlayer.setVolume(vol, vol);
1735 private final IMediaPlaybackService.Stub mBinder = new IMediaPlaybackService.Stub()
1737 public void openfileAsync(String path)
1739 MediaPlaybackService.this.openAsync(path);
1741 public void openfile(String path)
1743 MediaPlaybackService.this.open(path, true);
1745 public void open(int [] list, int position) {
1746 MediaPlaybackService.this.open(list, position);
1748 public int getQueuePosition() {
1749 return MediaPlaybackService.this.getQueuePosition();
1751 public void setQueuePosition(int index) {
1752 MediaPlaybackService.this.setQueuePosition(index);
1754 public boolean isPlaying() {
1755 return MediaPlaybackService.this.isPlaying();
1757 public void stop() {
1758 MediaPlaybackService.this.stop();
1760 public void pause() {
1761 MediaPlaybackService.this.pause();
1763 public void play() {
1764 MediaPlaybackService.this.play();
1766 public void prev() {
1767 MediaPlaybackService.this.prev();
1769 public void next() {
1770 MediaPlaybackService.this.next(true);
1772 public String getTrackName() {
1773 return MediaPlaybackService.this.getTrackName();
1775 public String getAlbumName() {
1776 return MediaPlaybackService.this.getAlbumName();
1778 public int getAlbumId() {
1779 return MediaPlaybackService.this.getAlbumId();
1781 public String getArtistName() {
1782 return MediaPlaybackService.this.getArtistName();
1784 public int getArtistId() {
1785 return MediaPlaybackService.this.getArtistId();
1787 public void enqueue(int [] list , int action) {
1788 MediaPlaybackService.this.enqueue(list, action);
1790 public int [] getQueue() {
1791 return MediaPlaybackService.this.getQueue();
1793 public void moveQueueItem(int from, int to) {
1794 MediaPlaybackService.this.moveQueueItem(from, to);
1796 public String getPath() {
1797 return MediaPlaybackService.this.getPath();
1799 public int getAudioId() {
1800 return MediaPlaybackService.this.getAudioId();
1802 public long position() {
1803 return MediaPlaybackService.this.position();
1805 public long duration() {
1806 return MediaPlaybackService.this.duration();
1808 public long seek(long pos) {
1809 return MediaPlaybackService.this.seek(pos);
1811 public void setShuffleMode(int shufflemode) {
1812 MediaPlaybackService.this.setShuffleMode(shufflemode);
1814 public int getShuffleMode() {
1815 return MediaPlaybackService.this.getShuffleMode();
1817 public int removeTracks(int first, int last) {
1818 return MediaPlaybackService.this.removeTracks(first, last);
1820 public int removeTrack(int id) {
1821 return MediaPlaybackService.this.removeTrack(id);
1823 public void setRepeatMode(int repeatmode) {
1824 MediaPlaybackService.this.setRepeatMode(repeatmode);
1826 public int getRepeatMode() {
1827 return MediaPlaybackService.this.getRepeatMode();
1829 public int getMediaMountedCount() {
1830 return MediaPlaybackService.this.getMediaMountedCount();