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.PendingIntent;
21 import android.app.Service;
22 import android.appwidget.AppWidgetManager;
23 import android.content.ComponentName;
24 import android.content.ContentResolver;
25 import android.content.ContentUris;
26 import android.content.ContentValues;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.content.IntentFilter;
30 import android.content.BroadcastReceiver;
31 import android.content.SharedPreferences;
32 import android.content.SharedPreferences.Editor;
33 import android.database.Cursor;
34 import android.database.sqlite.SQLiteException;
35 import android.media.AudioManager;
36 import android.media.AudioManager.OnAudioFocusChangeListener;
37 import android.media.MediaPlayer;
38 import android.net.Uri;
39 import android.os.Handler;
40 import android.os.IBinder;
41 import android.os.Message;
42 import android.os.PowerManager;
43 import android.os.SystemClock;
44 import android.os.PowerManager.WakeLock;
45 import android.provider.MediaStore;
46 import android.telephony.PhoneStateListener;
47 import android.telephony.TelephonyManager;
48 import android.util.Log;
49 import android.widget.RemoteViews;
50 import android.widget.Toast;
52 import java.io.FileDescriptor;
53 import java.io.IOException;
54 import java.io.PrintWriter;
55 import java.lang.ref.WeakReference;
56 import java.util.Random;
57 import java.util.Vector;
60 * Provides "background" audio playback capabilities, allowing the
61 * user to switch between activities without stopping playback.
63 public class MediaPlaybackService extends Service {
64 /** used to specify whether enqueue() should start playing
65 * the new list of files right away, next or once all the currently
66 * queued files have been played
68 public static final int NOW = 1;
69 public static final int NEXT = 2;
70 public static final int LAST = 3;
71 public static final int PLAYBACKSERVICE_STATUS = 1;
73 public static final int SHUFFLE_NONE = 0;
74 public static final int SHUFFLE_NORMAL = 1;
75 public static final int SHUFFLE_AUTO = 2;
77 public static final int REPEAT_NONE = 0;
78 public static final int REPEAT_CURRENT = 1;
79 public static final int REPEAT_ALL = 2;
81 public static final String PLAYSTATE_CHANGED = "com.android.music.playstatechanged";
82 public static final String META_CHANGED = "com.android.music.metachanged";
83 public static final String QUEUE_CHANGED = "com.android.music.queuechanged";
84 public static final String PLAYBACK_COMPLETE = "com.android.music.playbackcomplete";
85 public static final String ASYNC_OPEN_COMPLETE = "com.android.music.asyncopencomplete";
87 public static final String SERVICECMD = "com.android.music.musicservicecommand";
88 public static final String CMDNAME = "command";
89 public static final String CMDTOGGLEPAUSE = "togglepause";
90 public static final String CMDSTOP = "stop";
91 public static final String CMDPAUSE = "pause";
92 public static final String CMDPREVIOUS = "previous";
93 public static final String CMDNEXT = "next";
95 public static final String TOGGLEPAUSE_ACTION = "com.android.music.musicservicecommand.togglepause";
96 public static final String PAUSE_ACTION = "com.android.music.musicservicecommand.pause";
97 public static final String PREVIOUS_ACTION = "com.android.music.musicservicecommand.previous";
98 public static final String NEXT_ACTION = "com.android.music.musicservicecommand.next";
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 = 100;
106 private MultiPlayer mPlayer;
107 private String mFileToPlay;
108 private int mShuffleMode = SHUFFLE_NONE;
109 private int mRepeatMode = REPEAT_NONE;
110 private int mMediaMountedCount = 0;
111 private long [] mAutoShuffleList = null;
112 private boolean mOneShot;
113 private long [] mPlayList = null;
114 private int mPlayListLen = 0;
115 private Vector<Integer> mHistory = new Vector<Integer>(MAX_HISTORY_SIZE);
116 private Cursor mCursor;
117 private int mPlayPos = -1;
118 private static final String LOGTAG = "MediaPlaybackService";
119 private final Shuffler mRand = new Shuffler();
120 private int mOpenFailedCounter = 0;
121 String[] mCursorCols = new String[] {
122 "audio._id AS _id", // index must match IDCOLIDX below
123 MediaStore.Audio.Media.ARTIST,
124 MediaStore.Audio.Media.ALBUM,
125 MediaStore.Audio.Media.TITLE,
126 MediaStore.Audio.Media.DATA,
127 MediaStore.Audio.Media.MIME_TYPE,
128 MediaStore.Audio.Media.ALBUM_ID,
129 MediaStore.Audio.Media.ARTIST_ID,
130 MediaStore.Audio.Media.IS_PODCAST, // index must match PODCASTCOLIDX below
131 MediaStore.Audio.Media.BOOKMARK // index must match BOOKMARKCOLIDX below
133 private final static int IDCOLIDX = 0;
134 private final static int PODCASTCOLIDX = 8;
135 private final static int BOOKMARKCOLIDX = 9;
136 private BroadcastReceiver mUnmountReceiver = null;
137 private WakeLock mWakeLock;
138 private int mServiceStartId = -1;
139 private boolean mServiceInUse = false;
140 private boolean mResumeAfterCall = false;
141 private boolean mIsSupposedToBePlaying = false;
142 private boolean mQuietMode = false;
143 private AudioManager mAudioManager;
144 // used to track what type of audio focus loss caused the playback to pause
145 private boolean mPausedByTransientLossOfFocus = false;
147 private SharedPreferences mPreferences;
148 // We use this to distinguish between different cards when saving/restoring playlists.
149 // This will have to change if we want to support multiple simultaneous cards.
152 private MediaAppWidgetProvider mAppWidgetProvider = MediaAppWidgetProvider.getInstance();
154 // interval after which we stop the service when idle
155 private static final int IDLE_DELAY = 60000;
157 private PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
159 public void onCallStateChanged(int state, String incomingNumber) {
160 if (state == TelephonyManager.CALL_STATE_RINGING) {
161 AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
162 int ringvolume = audioManager.getStreamVolume(AudioManager.STREAM_RING);
163 if (ringvolume > 0) {
164 mResumeAfterCall = (isPlaying() || mResumeAfterCall) && (getAudioId() >= 0);
167 } else if (state == TelephonyManager.CALL_STATE_OFFHOOK) {
168 // pause the music while a conversation is in progress
169 mResumeAfterCall = (isPlaying() || mResumeAfterCall) && (getAudioId() >= 0);
171 } else if (state == TelephonyManager.CALL_STATE_IDLE) {
172 // start playing again
173 if (mResumeAfterCall) {
174 // resume playback only if music was playing
175 // when the call was answered
177 mResumeAfterCall = false;
183 private void startAndFadeIn() {
184 mMediaplayerHandler.sendEmptyMessageDelayed(FADEIN, 10);
187 private Handler mMediaplayerHandler = new Handler() {
188 float mCurrentVolume = 1.0f;
190 public void handleMessage(Message msg) {
191 MusicUtils.debugLog("mMediaplayerHandler.handleMessage " + msg.what);
196 mPlayer.setVolume(mCurrentVolume);
198 mMediaplayerHandler.sendEmptyMessageDelayed(FADEIN, 10);
200 mCurrentVolume += 0.01f;
201 if (mCurrentVolume < 1.0f) {
202 mMediaplayerHandler.sendEmptyMessageDelayed(FADEIN, 10);
204 mCurrentVolume = 1.0f;
206 mPlayer.setVolume(mCurrentVolume);
210 if (mIsSupposedToBePlaying) {
213 // the server died when we were idle, so just
214 // reopen the same song (it will start again
215 // from the beginning though when the user
221 if (mRepeatMode == REPEAT_CURRENT) {
224 } else if (!mOneShot) {
227 notifyChange(PLAYBACK_COMPLETE);
228 mIsSupposedToBePlaying = false;
231 case RELEASE_WAKELOCK:
240 private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
242 public void onReceive(Context context, Intent intent) {
243 String action = intent.getAction();
244 String cmd = intent.getStringExtra("command");
245 MusicUtils.debugLog("mIntentReceiver.onReceive " + action + " / " + cmd);
246 if (CMDNEXT.equals(cmd) || NEXT_ACTION.equals(action)) {
248 } else if (CMDPREVIOUS.equals(cmd) || PREVIOUS_ACTION.equals(action)) {
250 } else if (CMDTOGGLEPAUSE.equals(cmd) || TOGGLEPAUSE_ACTION.equals(action)) {
256 } else if (CMDPAUSE.equals(cmd) || PAUSE_ACTION.equals(action)) {
258 } else if (CMDSTOP.equals(cmd)) {
261 } else if (MediaAppWidgetProvider.CMDAPPWIDGETUPDATE.equals(cmd)) {
262 // Someone asked us to refresh a set of specific widgets, probably
263 // because they were just added.
264 int[] appWidgetIds = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS);
265 mAppWidgetProvider.performUpdate(MediaPlaybackService.this, appWidgetIds);
270 private OnAudioFocusChangeListener mAudioFocusListener = new OnAudioFocusChangeListener() {
271 public void onAudioFocusChanged(int focusChange) {
272 // AudioFocus is a new feature: focus updates are made verbose on purpose
273 switch (focusChange) {
274 case AudioManager.AUDIOFOCUS_LOSS:
275 Log.v(LOGTAG, "AudioFocus: received AUDIOFOCUS_LOSS");
277 mPausedByTransientLossOfFocus = false;
281 case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
282 case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
283 Log.v(LOGTAG, "AudioFocus: received AUDIOFOCUS_LOSS_TRANSIENT");
285 mPausedByTransientLossOfFocus = true;
289 case AudioManager.AUDIOFOCUS_GAIN:
290 Log.v(LOGTAG, "AudioFocus: received AUDIOFOCUS_GAIN");
291 if(!isPlaying() && mPausedByTransientLossOfFocus) {
292 mPausedByTransientLossOfFocus = false;
297 Log.e(LOGTAG, "Unknown audio focus change code");
302 public MediaPlaybackService() {
306 public void onCreate() {
309 mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
310 mAudioManager.registerAudioFocusListener(mAudioFocusListener);
311 mAudioManager.registerMediaButtonEventReceiver(new ComponentName(getPackageName(),
312 MediaButtonIntentReceiver.class.getName()));
314 mPreferences = getSharedPreferences("Music", MODE_WORLD_READABLE | MODE_WORLD_WRITEABLE);
315 mCardId = MusicUtils.getCardId(this);
317 registerExternalStorageListener();
319 // Needs to be done in this thread, since otherwise ApplicationContext.getPowerManager() crashes.
320 mPlayer = new MultiPlayer();
321 mPlayer.setHandler(mMediaplayerHandler);
325 IntentFilter commandFilter = new IntentFilter();
326 commandFilter.addAction(SERVICECMD);
327 commandFilter.addAction(TOGGLEPAUSE_ACTION);
328 commandFilter.addAction(PAUSE_ACTION);
329 commandFilter.addAction(NEXT_ACTION);
330 commandFilter.addAction(PREVIOUS_ACTION);
331 registerReceiver(mIntentReceiver, commandFilter);
333 TelephonyManager tmgr = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
334 tmgr.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
335 PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
336 mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, this.getClass().getName());
337 mWakeLock.setReferenceCounted(false);
339 // If the service was idle, but got killed before it stopped itself, the
340 // system will relaunch it. Make sure it gets stopped again in that case.
341 Message msg = mDelayedStopHandler.obtainMessage();
342 mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
346 public void onDestroy() {
347 // Check that we're not being destroyed while something is still playing.
349 Log.e(LOGTAG, "Service being destroyed while still playing.");
351 // release all MediaPlayer resources, including the native player and wakelocks
355 mAudioManager.abandonAudioFocus(mAudioFocusListener);
356 mAudioManager.unregisterAudioFocusListener(mAudioFocusListener);
358 // make sure there aren't any other messages coming
359 mDelayedStopHandler.removeCallbacksAndMessages(null);
360 mMediaplayerHandler.removeCallbacksAndMessages(null);
362 if (mCursor != null) {
367 TelephonyManager tmgr = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
368 tmgr.listen(mPhoneStateListener, 0);
370 unregisterReceiver(mIntentReceiver);
371 if (mUnmountReceiver != null) {
372 unregisterReceiver(mUnmountReceiver);
373 mUnmountReceiver = null;
379 private final char hexdigits [] = new char [] {
386 private void saveQueue(boolean full) {
390 Editor ed = mPreferences.edit();
391 //long start = System.currentTimeMillis();
393 StringBuilder q = new StringBuilder();
395 // The current playlist is saved as a list of "reverse hexadecimal"
396 // numbers, which we can generate faster than normal decimal or
397 // hexadecimal numbers, which in turn allows us to save the playlist
398 // more often without worrying too much about performance.
399 // (saving the full state takes about 40 ms under no-load conditions
401 int len = mPlayListLen;
402 for (int i = 0; i < len; i++) {
403 long n = mPlayList[i];
408 int digit = (int)(n & 0xf);
410 q.append(hexdigits[digit]);
415 //Log.i("@@@@ service", "created queue string in " + (System.currentTimeMillis() - start) + " ms");
416 ed.putString("queue", q.toString());
417 ed.putInt("cardid", mCardId);
418 if (mShuffleMode != SHUFFLE_NONE) {
419 // In shuffle mode we need to save the history too
420 len = mHistory.size();
422 for (int i = 0; i < len; i++) {
423 int n = mHistory.get(i);
428 int digit = (n & 0xf);
430 q.append(hexdigits[digit]);
435 ed.putString("history", q.toString());
438 ed.putInt("curpos", mPlayPos);
439 if (mPlayer.isInitialized()) {
440 ed.putLong("seekpos", mPlayer.position());
442 ed.putInt("repeatmode", mRepeatMode);
443 ed.putInt("shufflemode", mShuffleMode);
446 //Log.i("@@@@ service", "saved state in " + (System.currentTimeMillis() - start) + " ms");
449 private void reloadQueue() {
452 boolean newstyle = false;
454 if (mPreferences.contains("cardid")) {
456 id = mPreferences.getInt("cardid", ~mCardId);
459 // Only restore the saved playlist if the card is still
460 // the same one as when the playlist was saved
461 q = mPreferences.getString("queue", "");
463 int qlen = q != null ? q.length() : 0;
465 //Log.i("@@@@ service", "loaded queue: " + q);
469 for (int i = 0; i < qlen; i++) {
470 char c = q.charAt(i);
472 ensurePlayListCapacity(plen + 1);
478 if (c >= '0' && c <= '9') {
479 n += ((c - '0') << shift);
480 } else if (c >= 'a' && c <= 'f') {
481 n += ((10 + c - 'a') << shift);
483 // bogus playlist data
492 int pos = mPreferences.getInt("curpos", 0);
493 if (pos < 0 || pos >= mPlayListLen) {
494 // The saved playlist is bogus, discard it
500 // When reloadQueue is called in response to a card-insertion,
501 // we might not be able to query the media provider right away.
502 // To deal with this, try querying for the current file, and if
503 // that fails, wait a while and try again. If that too fails,
504 // assume there is a problem and don't restore the state.
505 Cursor crsr = MusicUtils.query(this,
506 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
507 new String [] {"_id"}, "_id=" + mPlayList[mPlayPos] , null, null);
508 if (crsr == null || crsr.getCount() == 0) {
509 // wait a bit and try again
510 SystemClock.sleep(3000);
511 crsr = getContentResolver().query(
512 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
513 mCursorCols, "_id=" + mPlayList[mPlayPos] , null, null);
519 // Make sure we don't auto-skip to the next song, since that
520 // also starts playback. What could happen in that case is:
522 // - go to UMS and delete some files, including the currently playing one
523 // - come back from UMS
525 // - music app is killed for some reason (out of memory)
526 // - music service is restarted, service restores state, doesn't find
527 // the "current" file, goes to the next and: playback starts on its
528 // own, potentially at some random inconvenient time.
529 mOpenFailedCounter = 20;
533 if (!mPlayer.isInitialized()) {
534 // couldn't restore the saved state
539 long seekpos = mPreferences.getLong("seekpos", 0);
540 seek(seekpos >= 0 && seekpos < duration() ? seekpos : 0);
541 Log.d(LOGTAG, "restored queue, currently at position "
542 + position() + "/" + duration()
543 + " (requested " + seekpos + ")");
545 int repmode = mPreferences.getInt("repeatmode", REPEAT_NONE);
546 if (repmode != REPEAT_ALL && repmode != REPEAT_CURRENT) {
547 repmode = REPEAT_NONE;
549 mRepeatMode = repmode;
551 int shufmode = mPreferences.getInt("shufflemode", SHUFFLE_NONE);
552 if (shufmode != SHUFFLE_AUTO && shufmode != SHUFFLE_NORMAL) {
553 shufmode = SHUFFLE_NONE;
555 if (shufmode != SHUFFLE_NONE) {
556 // in shuffle mode we need to restore the history too
557 q = mPreferences.getString("history", "");
558 qlen = q != null ? q.length() : 0;
564 for (int i = 0; i < qlen; i++) {
565 char c = q.charAt(i);
567 if (n >= mPlayListLen) {
568 // bogus history data
576 if (c >= '0' && c <= '9') {
577 n += ((c - '0') << shift);
578 } else if (c >= 'a' && c <= 'f') {
579 n += ((10 + c - 'a') << shift);
581 // bogus history data
590 if (shufmode == SHUFFLE_AUTO) {
591 if (! makeAutoShuffleList()) {
592 shufmode = SHUFFLE_NONE;
595 mShuffleMode = shufmode;
600 public IBinder onBind(Intent intent) {
601 mDelayedStopHandler.removeCallbacksAndMessages(null);
602 mServiceInUse = true;
607 public void onRebind(Intent intent) {
608 mDelayedStopHandler.removeCallbacksAndMessages(null);
609 mServiceInUse = true;
613 public int onStartCommand(Intent intent, int flags, int startId) {
614 mServiceStartId = startId;
615 mDelayedStopHandler.removeCallbacksAndMessages(null);
617 if (intent != null) {
618 String action = intent.getAction();
619 String cmd = intent.getStringExtra("command");
620 MusicUtils.debugLog("onStartCommand " + action + " / " + cmd);
622 if (CMDNEXT.equals(cmd) || NEXT_ACTION.equals(action)) {
624 } else if (CMDPREVIOUS.equals(cmd) || PREVIOUS_ACTION.equals(action)) {
625 if (position() < 2000) {
631 } else if (CMDTOGGLEPAUSE.equals(cmd) || TOGGLEPAUSE_ACTION.equals(action)) {
637 } else if (CMDPAUSE.equals(cmd) || PAUSE_ACTION.equals(action)) {
639 } else if (CMDSTOP.equals(cmd)) {
645 // make sure the service will shut down on its own if it was
646 // just started but not bound to and nothing is playing
647 mDelayedStopHandler.removeCallbacksAndMessages(null);
648 Message msg = mDelayedStopHandler.obtainMessage();
649 mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
654 public boolean onUnbind(Intent intent) {
655 mServiceInUse = false;
657 // Take a snapshot of the current playlist
660 if (isPlaying() || mResumeAfterCall) {
661 // something is currently playing, or will be playing once
662 // an in-progress call ends, so don't stop the service now.
666 // If there is a playlist but playback is paused, then wait a while
667 // before stopping the service, so that pause/resume isn't slow.
668 // Also delay stopping the service if we're transitioning between tracks.
669 if (mPlayListLen > 0 || mMediaplayerHandler.hasMessages(TRACK_ENDED)) {
670 Message msg = mDelayedStopHandler.obtainMessage();
671 mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
675 // No active playlist, OK to stop the service right now
676 stopSelf(mServiceStartId);
680 private Handler mDelayedStopHandler = new Handler() {
682 public void handleMessage(Message msg) {
683 // Check again to make sure nothing is playing right now
684 if (isPlaying() || mResumeAfterCall || mServiceInUse
685 || mMediaplayerHandler.hasMessages(TRACK_ENDED)) {
688 // save the queue again, because it might have changed
689 // since the user exited the music app (because of
690 // party-shuffle or because the play-position changed)
692 stopSelf(mServiceStartId);
697 * Called when we receive a ACTION_MEDIA_EJECT notification.
699 * @param storagePath path to mount point for the removed media
701 public void closeExternalStorageFiles(String storagePath) {
702 // stop playback and clean up if the SD card is going to be unmounted.
704 notifyChange(QUEUE_CHANGED);
705 notifyChange(META_CHANGED);
709 * Registers an intent to listen for ACTION_MEDIA_EJECT notifications.
710 * The intent will call closeExternalStorageFiles() if the external media
711 * is going to be ejected, so applications can clean up any files they have open.
713 public void registerExternalStorageListener() {
714 if (mUnmountReceiver == null) {
715 mUnmountReceiver = new BroadcastReceiver() {
717 public void onReceive(Context context, Intent intent) {
718 String action = intent.getAction();
719 if (action.equals(Intent.ACTION_MEDIA_EJECT)) {
721 mOneShot = true; // This makes us not save the state again later,
722 // which would be wrong because the song ids and
723 // card id might not match.
724 closeExternalStorageFiles(intent.getData().getPath());
725 } else if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) {
726 mMediaMountedCount++;
727 mCardId = MusicUtils.getCardId(MediaPlaybackService.this);
729 notifyChange(QUEUE_CHANGED);
730 notifyChange(META_CHANGED);
734 IntentFilter iFilter = new IntentFilter();
735 iFilter.addAction(Intent.ACTION_MEDIA_EJECT);
736 iFilter.addAction(Intent.ACTION_MEDIA_MOUNTED);
737 iFilter.addDataScheme("file");
738 registerReceiver(mUnmountReceiver, iFilter);
743 * Notify the change-receivers that something has changed.
744 * The intent that is sent contains the following data
745 * for the currently playing track:
746 * "id" - Integer: the database row ID
747 * "artist" - String: the name of the artist
748 * "album" - String: the name of the album
749 * "track" - String: the name of the track
750 * The intent has an action that is one of
751 * "com.android.music.metachanged"
752 * "com.android.music.queuechanged",
753 * "com.android.music.playbackcomplete"
754 * "com.android.music.playstatechanged"
755 * respectively indicating that a new track has
756 * started playing, that the playback queue has
757 * changed, that playback has stopped because
758 * the last file in the list has been played,
759 * or that the play-state changed (paused/resumed).
761 private void notifyChange(String what) {
763 Intent i = new Intent(what);
764 i.putExtra("id", Long.valueOf(getAudioId()));
765 i.putExtra("artist", getArtistName());
766 i.putExtra("album",getAlbumName());
767 i.putExtra("track", getTrackName());
770 if (what.equals(QUEUE_CHANGED)) {
776 // Share this notification directly with our widgets
777 mAppWidgetProvider.notifyChange(this, what);
780 private void ensurePlayListCapacity(int size) {
781 if (mPlayList == null || size > mPlayList.length) {
782 // reallocate at 2x requested size so we don't
783 // need to grow and copy the array for every
785 long [] newlist = new long[size * 2];
786 int len = mPlayList != null ? mPlayList.length : mPlayListLen;
787 for (int i = 0; i < len; i++) {
788 newlist[i] = mPlayList[i];
792 // FIXME: shrink the array when the needed size is much smaller
793 // than the allocated size
796 // insert the list of songs at the specified position in the playlist
797 private void addToPlayList(long [] list, int position) {
798 int addlen = list.length;
799 if (position < 0) { // overwrite
803 ensurePlayListCapacity(mPlayListLen + addlen);
804 if (position > mPlayListLen) {
805 position = mPlayListLen;
808 // move part of list after insertion point
809 int tailsize = mPlayListLen - position;
810 for (int i = tailsize ; i > 0 ; i--) {
811 mPlayList[position + i] = mPlayList[position + i - addlen];
814 // copy list into playlist
815 for (int i = 0; i < addlen; i++) {
816 mPlayList[position + i] = list[i];
818 mPlayListLen += addlen;
822 * Appends a list of tracks to the current playlist.
823 * If nothing is playing currently, playback will be started at
825 * If the action is NOW, playback will switch to the first of
826 * the new tracks immediately.
827 * @param list The list of tracks to append.
828 * @param action NOW, NEXT or LAST
830 public void enqueue(long [] list, int action) {
832 if (action == NEXT && mPlayPos + 1 < mPlayListLen) {
833 addToPlayList(list, mPlayPos + 1);
834 notifyChange(QUEUE_CHANGED);
836 // action == LAST || action == NOW || mPlayPos + 1 == mPlayListLen
837 addToPlayList(list, Integer.MAX_VALUE);
838 notifyChange(QUEUE_CHANGED);
840 mPlayPos = mPlayListLen - list.length;
843 notifyChange(META_CHANGED);
851 notifyChange(META_CHANGED);
857 * Replaces the current playlist with a new list,
858 * and prepares for starting playback at the specified
859 * position in the list, or a random position if the
860 * specified position is 0.
861 * @param list The new list of tracks.
863 public void open(long [] list, int position) {
864 synchronized (this) {
865 if (mShuffleMode == SHUFFLE_AUTO) {
866 mShuffleMode = SHUFFLE_NORMAL;
868 long oldId = getAudioId();
869 int listlength = list.length;
870 boolean newlist = true;
871 if (mPlayListLen == listlength) {
872 // possible fast path: list might be the same
874 for (int i = 0; i < listlength; i++) {
875 if (list[i] != mPlayList[i]) {
882 addToPlayList(list, -1);
883 notifyChange(QUEUE_CHANGED);
885 int oldpos = mPlayPos;
889 mPlayPos = mRand.nextInt(mPlayListLen);
893 saveBookmarkIfNeeded();
895 if (oldId != getAudioId()) {
896 notifyChange(META_CHANGED);
902 * Moves the item at index1 to index2.
906 public void moveQueueItem(int index1, int index2) {
907 synchronized (this) {
908 if (index1 >= mPlayListLen) {
909 index1 = mPlayListLen - 1;
911 if (index2 >= mPlayListLen) {
912 index2 = mPlayListLen - 1;
914 if (index1 < index2) {
915 long tmp = mPlayList[index1];
916 for (int i = index1; i < index2; i++) {
917 mPlayList[i] = mPlayList[i+1];
919 mPlayList[index2] = tmp;
920 if (mPlayPos == index1) {
922 } else if (mPlayPos >= index1 && mPlayPos <= index2) {
925 } else if (index2 < index1) {
926 long tmp = mPlayList[index1];
927 for (int i = index1; i > index2; i--) {
928 mPlayList[i] = mPlayList[i-1];
930 mPlayList[index2] = tmp;
931 if (mPlayPos == index1) {
933 } else if (mPlayPos >= index2 && mPlayPos <= index1) {
937 notifyChange(QUEUE_CHANGED);
942 * Returns the current play list
943 * @return An array of integers containing the IDs of the tracks in the play list
945 public long [] getQueue() {
946 synchronized (this) {
947 int len = mPlayListLen;
948 long [] list = new long[len];
949 for (int i = 0; i < len; i++) {
950 list[i] = mPlayList[i];
956 private void openCurrent() {
957 synchronized (this) {
958 if (mCursor != null) {
962 if (mPlayListLen == 0) {
967 String id = String.valueOf(mPlayList[mPlayPos]);
969 mCursor = getContentResolver().query(
970 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
971 mCursorCols, "_id=" + id , null, null);
972 if (mCursor != null) {
973 mCursor.moveToFirst();
974 open(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + "/" + id, false);
975 // go to bookmark if needed
977 long bookmark = getBookmark();
978 // Start playing a little bit before the bookmark,
979 // so it's easier to get back in to the narrative.
980 seek(bookmark - 5000);
986 public void openAsync(String path) {
987 synchronized (this) {
992 mRepeatMode = REPEAT_NONE;
993 ensurePlayListCapacity(1);
999 mPlayer.setDataSourceAsync(mFileToPlay);
1005 * Opens the specified file and readies it for playback.
1007 * @param path The full path of the file to be opened.
1008 * @param oneshot when set to true, playback will stop after this file completes, instead
1009 * of moving on to the next track in the list
1011 public void open(String path, boolean oneshot) {
1012 synchronized (this) {
1018 mRepeatMode = REPEAT_NONE;
1019 ensurePlayListCapacity(1);
1024 // if mCursor is null, try to associate path with a database cursor
1025 if (mCursor == null) {
1027 ContentResolver resolver = getContentResolver();
1030 String selectionArgs[];
1031 if (path.startsWith("content://media/")) {
1032 uri = Uri.parse(path);
1034 selectionArgs = null;
1036 uri = MediaStore.Audio.Media.getContentUriForPath(path);
1037 where = MediaStore.Audio.Media.DATA + "=?";
1038 selectionArgs = new String[] { path };
1042 mCursor = resolver.query(uri, mCursorCols, where, selectionArgs, null);
1043 if (mCursor != null) {
1044 if (mCursor.getCount() == 0) {
1048 mCursor.moveToNext();
1049 ensurePlayListCapacity(1);
1051 mPlayList[0] = mCursor.getLong(IDCOLIDX);
1055 } catch (UnsupportedOperationException ex) {
1059 mPlayer.setDataSource(mFileToPlay);
1061 if (! mPlayer.isInitialized()) {
1063 if (mOpenFailedCounter++ < 10 && mPlayListLen > 1) {
1064 // beware: this ends up being recursive because next() calls open() again.
1067 if (! mPlayer.isInitialized() && mOpenFailedCounter != 0) {
1068 // need to make sure we only shows this once
1069 mOpenFailedCounter = 0;
1071 Toast.makeText(this, R.string.playback_failed, Toast.LENGTH_SHORT).show();
1073 Log.d(LOGTAG, "Failed to open file for playback");
1076 mOpenFailedCounter = 0;
1082 * Starts playback of a previously opened file.
1084 public void play() {
1085 mAudioManager.requestAudioFocus(mAudioFocusListener, AudioManager.STREAM_MUSIC,
1086 AudioManager.AUDIOFOCUS_GAIN);
1087 mAudioManager.registerMediaButtonEventReceiver(new ComponentName(this.getPackageName(),
1088 MediaButtonIntentReceiver.class.getName()));
1090 if (mPlayer.isInitialized()) {
1091 // if we are at the end of the song, go to the next song first
1092 long duration = mPlayer.duration();
1093 if (mRepeatMode != REPEAT_CURRENT && duration > 2000 &&
1094 mPlayer.position() >= duration - 2000) {
1100 RemoteViews views = new RemoteViews(getPackageName(), R.layout.statusbar);
1101 views.setImageViewResource(R.id.icon, R.drawable.stat_notify_musicplayer);
1102 if (getAudioId() < 0) {
1104 views.setTextViewText(R.id.trackname, getPath());
1105 views.setTextViewText(R.id.artistalbum, null);
1107 String artist = getArtistName();
1108 views.setTextViewText(R.id.trackname, getTrackName());
1109 if (artist == null || artist.equals(MediaStore.UNKNOWN_STRING)) {
1110 artist = getString(R.string.unknown_artist_name);
1112 String album = getAlbumName();
1113 if (album == null || album.equals(MediaStore.UNKNOWN_STRING)) {
1114 album = getString(R.string.unknown_album_name);
1117 views.setTextViewText(R.id.artistalbum,
1118 getString(R.string.notification_artist_album, artist, album)
1122 Notification status = new Notification();
1123 status.contentView = views;
1124 status.flags |= Notification.FLAG_ONGOING_EVENT;
1125 status.icon = R.drawable.stat_notify_musicplayer;
1126 status.contentIntent = PendingIntent.getActivity(this, 0,
1127 new Intent("com.android.music.PLAYBACK_VIEWER")
1128 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK), 0);
1129 startForeground(PLAYBACKSERVICE_STATUS, status);
1130 if (!mIsSupposedToBePlaying) {
1131 mIsSupposedToBePlaying = true;
1132 notifyChange(PLAYSTATE_CHANGED);
1135 } else if (mPlayListLen <= 0) {
1136 // This is mostly so that if you press 'play' on a bluetooth headset
1137 // without every having played anything before, it will still play
1139 setShuffleMode(SHUFFLE_AUTO);
1143 private void stop(boolean remove_status_icon) {
1144 if (mPlayer.isInitialized()) {
1148 if (mCursor != null) {
1152 if (remove_status_icon) {
1155 stopForeground(false);
1157 if (remove_status_icon) {
1158 mIsSupposedToBePlaying = false;
1165 public void stop() {
1170 * Pauses playback (call play() to resume)
1172 public void pause() {
1173 synchronized(this) {
1177 mIsSupposedToBePlaying = false;
1178 notifyChange(PLAYSTATE_CHANGED);
1179 saveBookmarkIfNeeded();
1184 /** Returns whether something is currently playing
1186 * @return true if something is playing (or will be playing shortly, in case
1187 * we're currently transitioning between tracks), false if not.
1189 public boolean isPlaying() {
1190 return mIsSupposedToBePlaying;
1194 Desired behavior for prev/next/shuffle:
1196 - NEXT will move to the next track in the list when not shuffling, and to
1197 a track randomly picked from the not-yet-played tracks when shuffling.
1198 If all tracks have already been played, pick from the full set, but
1199 avoid picking the previously played track if possible.
1200 - when shuffling, PREV will go to the previously played track. Hitting PREV
1201 again will go to the track played before that, etc. When the start of the
1202 history has been reached, PREV is a no-op.
1203 When not shuffling, PREV will go to the sequentially previous track (the
1204 difference with the shuffle-case is mainly that when not shuffling, the
1205 user can back up to tracks that are not in the history).
1208 When playing an album with 10 tracks from the start, and enabling shuffle
1209 while playing track 5, the remaining tracks (6-10) will be shuffled, e.g.
1210 the final play order might be 1-2-3-4-5-8-10-6-9-7.
1211 When hitting 'prev' 8 times while playing track 7 in this example, the
1212 user will go to tracks 9-6-10-8-5-4-3-2. If the user then hits 'next',
1213 a random track will be picked again. If at any time user disables shuffling
1214 the next/previous track will be picked in sequential order again.
1217 public void prev() {
1218 synchronized (this) {
1220 // we were playing a specific file not part of a playlist, so there is no 'previous'
1225 if (mShuffleMode == SHUFFLE_NORMAL) {
1226 // go to previously-played track and remove it from the history
1227 int histsize = mHistory.size();
1228 if (histsize == 0) {
1232 Integer pos = mHistory.remove(histsize - 1);
1233 mPlayPos = pos.intValue();
1238 mPlayPos = mPlayListLen - 1;
1241 saveBookmarkIfNeeded();
1245 notifyChange(META_CHANGED);
1249 public void next(boolean force) {
1250 synchronized (this) {
1252 // we were playing a specific file not part of a playlist, so there is no 'next'
1258 if (mPlayListLen <= 0) {
1259 Log.d(LOGTAG, "No play queue");
1263 // Store the current file in the history, but keep the history at a
1265 if (mPlayPos >= 0) {
1266 mHistory.add(Integer.valueOf(mPlayPos));
1268 if (mHistory.size() > MAX_HISTORY_SIZE) {
1269 mHistory.removeElementAt(0);
1272 if (mShuffleMode == SHUFFLE_NORMAL) {
1273 // Pick random next track from the not-yet-played ones
1274 // TODO: make it work right after adding/removing items in the queue.
1276 int numTracks = mPlayListLen;
1277 int[] tracks = new int[numTracks];
1278 for (int i=0;i < numTracks; i++) {
1282 int numHistory = mHistory.size();
1283 int numUnplayed = numTracks;
1284 for (int i=0;i < numHistory; i++) {
1285 int idx = mHistory.get(i).intValue();
1286 if (idx < numTracks && tracks[idx] >= 0) {
1292 // 'numUnplayed' now indicates how many tracks have not yet
1293 // been played, and 'tracks' contains the indices of those
1295 if (numUnplayed <=0) {
1296 // everything's already been played
1297 if (mRepeatMode == REPEAT_ALL || force) {
1298 //pick from full set
1299 numUnplayed = numTracks;
1300 for (int i=0;i < numTracks; i++) {
1306 if (mIsSupposedToBePlaying) {
1307 mIsSupposedToBePlaying = false;
1308 notifyChange(PLAYSTATE_CHANGED);
1313 int skip = mRand.nextInt(numUnplayed);
1316 while (tracks[++cnt] < 0)
1324 } else if (mShuffleMode == SHUFFLE_AUTO) {
1325 doAutoShuffleUpdate();
1328 if (mPlayPos >= mPlayListLen - 1) {
1329 // we're at the end of the list
1330 if (mRepeatMode == REPEAT_NONE && !force) {
1333 notifyChange(PLAYBACK_COMPLETE);
1334 mIsSupposedToBePlaying = false;
1336 } else if (mRepeatMode == REPEAT_ALL || force) {
1343 saveBookmarkIfNeeded();
1347 notifyChange(META_CHANGED);
1351 private void gotoIdleState() {
1352 mDelayedStopHandler.removeCallbacksAndMessages(null);
1353 Message msg = mDelayedStopHandler.obtainMessage();
1354 mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
1355 stopForeground(true);
1358 private void saveBookmarkIfNeeded() {
1361 long pos = position();
1362 long bookmark = getBookmark();
1363 long duration = duration();
1364 if ((pos < bookmark && (pos + 10000) > bookmark) ||
1365 (pos > bookmark && (pos - 10000) < bookmark)) {
1366 // The existing bookmark is close to the current
1367 // position, so don't update it.
1370 if (pos < 15000 || (pos + 10000) > duration) {
1371 // if we're near the start or end, clear the bookmark
1375 // write 'pos' to the bookmark field
1376 ContentValues values = new ContentValues();
1377 values.put(MediaStore.Audio.Media.BOOKMARK, pos);
1378 Uri uri = ContentUris.withAppendedId(
1379 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, mCursor.getLong(IDCOLIDX));
1380 getContentResolver().update(uri, values, null, null);
1382 } catch (SQLiteException ex) {
1386 // Make sure there are at least 5 items after the currently playing item
1387 // and no more than 10 items before.
1388 private void doAutoShuffleUpdate() {
1389 boolean notify = false;
1390 // remove old entries
1391 if (mPlayPos > 10) {
1392 removeTracks(0, mPlayPos - 9);
1395 // add new entries if needed
1396 int to_add = 7 - (mPlayListLen - (mPlayPos < 0 ? -1 : mPlayPos));
1397 for (int i = 0; i < to_add; i++) {
1398 // pick something at random from the list
1399 int idx = mRand.nextInt(mAutoShuffleList.length);
1400 long which = mAutoShuffleList[idx];
1401 ensurePlayListCapacity(mPlayListLen + 1);
1402 mPlayList[mPlayListLen++] = which;
1406 notifyChange(QUEUE_CHANGED);
1410 // A simple variation of Random that makes sure that the
1411 // value it returns is not equal to the value it returned
1412 // previously, unless the interval is 1.
1413 private static class Shuffler {
1414 private int mPrevious;
1415 private Random mRandom = new Random();
1416 public int nextInt(int interval) {
1419 ret = mRandom.nextInt(interval);
1420 } while (ret == mPrevious && interval > 1);
1426 private boolean makeAutoShuffleList() {
1427 ContentResolver res = getContentResolver();
1430 c = res.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
1431 new String[] {MediaStore.Audio.Media._ID}, MediaStore.Audio.Media.IS_MUSIC + "=1",
1433 if (c == null || c.getCount() == 0) {
1436 int len = c.getCount();
1437 long [] list = new long[len];
1438 for (int i = 0; i < len; i++) {
1440 list[i] = c.getLong(0);
1442 mAutoShuffleList = list;
1444 } catch (RuntimeException ex) {
1454 * Removes the range of tracks specified from the play list. If a file within the range is
1455 * the file currently being played, playback will move to the next file after the
1457 * @param first The first file to be removed
1458 * @param last The last file to be removed
1459 * @return the number of tracks deleted
1461 public int removeTracks(int first, int last) {
1462 int numremoved = removeTracksInternal(first, last);
1463 if (numremoved > 0) {
1464 notifyChange(QUEUE_CHANGED);
1469 private int removeTracksInternal(int first, int last) {
1470 synchronized (this) {
1471 if (last < first) return 0;
1472 if (first < 0) first = 0;
1473 if (last >= mPlayListLen) last = mPlayListLen - 1;
1475 boolean gotonext = false;
1476 if (first <= mPlayPos && mPlayPos <= last) {
1479 } else if (mPlayPos > last) {
1480 mPlayPos -= (last - first + 1);
1482 int num = mPlayListLen - last - 1;
1483 for (int i = 0; i < num; i++) {
1484 mPlayList[first + i] = mPlayList[last + 1 + i];
1486 mPlayListLen -= last - first + 1;
1489 if (mPlayListLen == 0) {
1493 if (mPlayPos >= mPlayListLen) {
1496 boolean wasPlaying = isPlaying();
1504 return last - first + 1;
1509 * Removes all instances of the track with the given id
1510 * from the playlist.
1511 * @param id The id to be removed
1512 * @return how many instances of the track were removed
1514 public int removeTrack(long id) {
1516 synchronized (this) {
1517 for (int i = 0; i < mPlayListLen; i++) {
1518 if (mPlayList[i] == id) {
1519 numremoved += removeTracksInternal(i, i);
1524 if (numremoved > 0) {
1525 notifyChange(QUEUE_CHANGED);
1530 public void setShuffleMode(int shufflemode) {
1531 synchronized(this) {
1532 if (mShuffleMode == shufflemode && mPlayListLen > 0) {
1535 mShuffleMode = shufflemode;
1536 if (mShuffleMode == SHUFFLE_AUTO) {
1537 if (makeAutoShuffleList()) {
1539 doAutoShuffleUpdate();
1543 notifyChange(META_CHANGED);
1546 // failed to build a list of files to shuffle
1547 mShuffleMode = SHUFFLE_NONE;
1553 public int getShuffleMode() {
1554 return mShuffleMode;
1557 public void setRepeatMode(int repeatmode) {
1558 synchronized(this) {
1559 mRepeatMode = repeatmode;
1563 public int getRepeatMode() {
1567 public int getMediaMountedCount() {
1568 return mMediaMountedCount;
1572 * Returns the path of the currently playing file, or null if
1573 * no file is currently playing.
1575 public String getPath() {
1580 * Returns the rowid of the currently playing file, or -1 if
1581 * no file is currently playing.
1583 public long getAudioId() {
1584 synchronized (this) {
1585 if (mPlayPos >= 0 && mPlayer.isInitialized()) {
1586 return mPlayList[mPlayPos];
1593 * Returns the position in the queue
1594 * @return the position in the queue
1596 public int getQueuePosition() {
1597 synchronized(this) {
1603 * Starts playing the track at the given position in the queue.
1604 * @param pos The position in the queue of the track that will be played.
1606 public void setQueuePosition(int pos) {
1607 synchronized(this) {
1612 notifyChange(META_CHANGED);
1613 if (mShuffleMode == SHUFFLE_AUTO) {
1614 doAutoShuffleUpdate();
1619 public String getArtistName() {
1620 synchronized(this) {
1621 if (mCursor == null) {
1624 return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST));
1628 public long getArtistId() {
1629 synchronized (this) {
1630 if (mCursor == null) {
1633 return mCursor.getLong(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST_ID));
1637 public String getAlbumName() {
1638 synchronized (this) {
1639 if (mCursor == null) {
1642 return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM));
1646 public long getAlbumId() {
1647 synchronized (this) {
1648 if (mCursor == null) {
1651 return mCursor.getLong(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM_ID));
1655 public String getTrackName() {
1656 synchronized (this) {
1657 if (mCursor == null) {
1660 return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE));
1664 private boolean isPodcast() {
1665 synchronized (this) {
1666 if (mCursor == null) {
1669 return (mCursor.getInt(PODCASTCOLIDX) > 0);
1673 private long getBookmark() {
1674 synchronized (this) {
1675 if (mCursor == null) {
1678 return mCursor.getLong(BOOKMARKCOLIDX);
1683 * Returns the duration of the file in milliseconds.
1684 * Currently this method returns -1 for the duration of MIDI files.
1686 public long duration() {
1687 if (mPlayer.isInitialized()) {
1688 return mPlayer.duration();
1694 * Returns the current playback position in milliseconds
1696 public long position() {
1697 if (mPlayer.isInitialized()) {
1698 return mPlayer.position();
1704 * Seeks to the position specified.
1706 * @param pos The position to seek to, in milliseconds
1708 public long seek(long pos) {
1709 if (mPlayer.isInitialized()) {
1710 if (pos < 0) pos = 0;
1711 if (pos > mPlayer.duration()) pos = mPlayer.duration();
1712 return mPlayer.seek(pos);
1718 * Provides a unified interface for dealing with midi files and
1719 * other media files.
1721 private class MultiPlayer {
1722 private MediaPlayer mMediaPlayer = new MediaPlayer();
1723 private Handler mHandler;
1724 private boolean mIsInitialized = false;
1726 public MultiPlayer() {
1727 mMediaPlayer.setWakeMode(MediaPlaybackService.this, PowerManager.PARTIAL_WAKE_LOCK);
1730 public void setDataSourceAsync(String path) {
1732 mMediaPlayer.reset();
1733 mMediaPlayer.setDataSource(path);
1734 mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
1735 mMediaPlayer.setOnPreparedListener(preparedlistener);
1736 mMediaPlayer.prepareAsync();
1737 } catch (IOException ex) {
1738 // TODO: notify the user why the file couldn't be opened
1739 mIsInitialized = false;
1741 } catch (IllegalArgumentException ex) {
1742 // TODO: notify the user why the file couldn't be opened
1743 mIsInitialized = false;
1746 mMediaPlayer.setOnCompletionListener(listener);
1747 mMediaPlayer.setOnErrorListener(errorListener);
1749 mIsInitialized = true;
1752 public void setDataSource(String path) {
1754 mMediaPlayer.reset();
1755 mMediaPlayer.setOnPreparedListener(null);
1756 if (path.startsWith("content://")) {
1757 mMediaPlayer.setDataSource(MediaPlaybackService.this, Uri.parse(path));
1759 mMediaPlayer.setDataSource(path);
1761 mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
1762 mMediaPlayer.prepare();
1763 } catch (IOException ex) {
1764 // TODO: notify the user why the file couldn't be opened
1765 mIsInitialized = false;
1767 } catch (IllegalArgumentException ex) {
1768 // TODO: notify the user why the file couldn't be opened
1769 mIsInitialized = false;
1772 mMediaPlayer.setOnCompletionListener(listener);
1773 mMediaPlayer.setOnErrorListener(errorListener);
1775 mIsInitialized = true;
1778 public boolean isInitialized() {
1779 return mIsInitialized;
1782 public void start() {
1783 MusicUtils.debugLog(new Exception("MultiPlayer.start called"));
1784 mMediaPlayer.start();
1787 public void stop() {
1788 mMediaPlayer.reset();
1789 mIsInitialized = false;
1793 * You CANNOT use this player anymore after calling release()
1795 public void release() {
1797 mMediaPlayer.release();
1800 public void pause() {
1801 mMediaPlayer.pause();
1804 public void setHandler(Handler handler) {
1808 MediaPlayer.OnCompletionListener listener = new MediaPlayer.OnCompletionListener() {
1809 public void onCompletion(MediaPlayer mp) {
1810 // Acquire a temporary wakelock, since when we return from
1811 // this callback the MediaPlayer will release its wakelock
1812 // and allow the device to go to sleep.
1813 // This temporary wakelock is released when the RELEASE_WAKELOCK
1814 // message is processed, but just in case, put a timeout on it.
1815 mWakeLock.acquire(30000);
1816 mHandler.sendEmptyMessage(TRACK_ENDED);
1817 mHandler.sendEmptyMessage(RELEASE_WAKELOCK);
1821 MediaPlayer.OnPreparedListener preparedlistener = new MediaPlayer.OnPreparedListener() {
1822 public void onPrepared(MediaPlayer mp) {
1823 notifyChange(ASYNC_OPEN_COMPLETE);
1827 MediaPlayer.OnErrorListener errorListener = new MediaPlayer.OnErrorListener() {
1828 public boolean onError(MediaPlayer mp, int what, int extra) {
1830 case MediaPlayer.MEDIA_ERROR_SERVER_DIED:
1831 mIsInitialized = false;
1832 mMediaPlayer.release();
1833 // Creating a new MediaPlayer and settings its wakemode does not
1834 // require the media service, so it's OK to do this now, while the
1835 // service is still being restarted
1836 mMediaPlayer = new MediaPlayer();
1837 mMediaPlayer.setWakeMode(MediaPlaybackService.this, PowerManager.PARTIAL_WAKE_LOCK);
1838 mHandler.sendMessageDelayed(mHandler.obtainMessage(SERVER_DIED), 2000);
1841 Log.d("MultiPlayer", "Error: " + what + "," + extra);
1848 public long duration() {
1849 return mMediaPlayer.getDuration();
1852 public long position() {
1853 return mMediaPlayer.getCurrentPosition();
1856 public long seek(long whereto) {
1857 mMediaPlayer.seekTo((int) whereto);
1861 public void setVolume(float vol) {
1862 mMediaPlayer.setVolume(vol, vol);
1867 * By making this a static class with a WeakReference to the Service, we
1868 * ensure that the Service can be GCd even when the system process still
1869 * has a remote reference to the stub.
1871 static class ServiceStub extends IMediaPlaybackService.Stub {
1872 WeakReference<MediaPlaybackService> mService;
1874 ServiceStub(MediaPlaybackService service) {
1875 mService = new WeakReference<MediaPlaybackService>(service);
1878 public void openFileAsync(String path)
1880 mService.get().openAsync(path);
1882 public void openFile(String path, boolean oneShot)
1884 mService.get().open(path, oneShot);
1886 public void open(long [] list, int position) {
1887 mService.get().open(list, position);
1889 public int getQueuePosition() {
1890 return mService.get().getQueuePosition();
1892 public void setQueuePosition(int index) {
1893 mService.get().setQueuePosition(index);
1895 public boolean isPlaying() {
1896 return mService.get().isPlaying();
1898 public void stop() {
1899 mService.get().stop();
1901 public void pause() {
1902 mService.get().pause();
1904 public void play() {
1905 mService.get().play();
1907 public void prev() {
1908 mService.get().prev();
1910 public void next() {
1911 mService.get().next(true);
1913 public String getTrackName() {
1914 return mService.get().getTrackName();
1916 public String getAlbumName() {
1917 return mService.get().getAlbumName();
1919 public long getAlbumId() {
1920 return mService.get().getAlbumId();
1922 public String getArtistName() {
1923 return mService.get().getArtistName();
1925 public long getArtistId() {
1926 return mService.get().getArtistId();
1928 public void enqueue(long [] list , int action) {
1929 mService.get().enqueue(list, action);
1931 public long [] getQueue() {
1932 return mService.get().getQueue();
1934 public void moveQueueItem(int from, int to) {
1935 mService.get().moveQueueItem(from, to);
1937 public String getPath() {
1938 return mService.get().getPath();
1940 public long getAudioId() {
1941 return mService.get().getAudioId();
1943 public long position() {
1944 return mService.get().position();
1946 public long duration() {
1947 return mService.get().duration();
1949 public long seek(long pos) {
1950 return mService.get().seek(pos);
1952 public void setShuffleMode(int shufflemode) {
1953 mService.get().setShuffleMode(shufflemode);
1955 public int getShuffleMode() {
1956 return mService.get().getShuffleMode();
1958 public int removeTracks(int first, int last) {
1959 return mService.get().removeTracks(first, last);
1961 public int removeTrack(long id) {
1962 return mService.get().removeTrack(id);
1964 public void setRepeatMode(int repeatmode) {
1965 mService.get().setRepeatMode(repeatmode);
1967 public int getRepeatMode() {
1968 return mService.get().getRepeatMode();
1970 public int getMediaMountedCount() {
1971 return mService.get().getMediaMountedCount();
1977 protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
1978 writer.println("" + mPlayListLen + " items in queue, currently at index " + mPlayPos);
1979 writer.println("Currently loaded:");
1980 writer.println(getArtistName());
1981 writer.println(getAlbumName());
1982 writer.println(getTrackName());
1983 writer.println(getPath());
1984 writer.println("playing: " + mIsSupposedToBePlaying);
1985 writer.println("actual: " + mPlayer.mMediaPlayer.isPlaying());
1986 writer.println("shuffle mode: " + mShuffleMode);
1987 MusicUtils.debugDump(writer);
1990 private final IBinder mBinder = new ServiceStub(this);