blob: 10783a449ac643d80020a3f41be504304c37e3f8 [file] [log] [blame]
The Android Open Source Project792a2202009-03-03 19:32:30 -08001/*
2 * Copyright (C) 2007 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.music;
18
19import android.app.Notification;
The Android Open Source Project792a2202009-03-03 19:32:30 -080020import android.app.PendingIntent;
21import android.app.Service;
The Android Open Source Project490384b2009-03-11 12:11:59 -070022import android.appwidget.AppWidgetManager;
Jean-Michel Trivi3d22fc22010-03-17 11:46:58 -070023import android.content.ComponentName;
The Android Open Source Project792a2202009-03-03 19:32:30 -080024import android.content.ContentResolver;
25import android.content.ContentUris;
26import android.content.ContentValues;
27import android.content.Context;
28import android.content.Intent;
29import android.content.IntentFilter;
30import android.content.BroadcastReceiver;
31import android.content.SharedPreferences;
32import android.content.SharedPreferences.Editor;
33import android.database.Cursor;
34import android.database.sqlite.SQLiteException;
The Android Open Source Project792a2202009-03-03 19:32:30 -080035import android.media.AudioManager;
Jean-Michel Trivi085cbfd2010-03-08 17:06:18 -080036import android.media.AudioManager.OnAudioFocusChangeListener;
The Android Open Source Project792a2202009-03-03 19:32:30 -080037import android.media.MediaPlayer;
38import android.net.Uri;
The Android Open Source Project792a2202009-03-03 19:32:30 -080039import android.os.Handler;
40import android.os.IBinder;
41import android.os.Message;
42import android.os.PowerManager;
43import android.os.SystemClock;
44import android.os.PowerManager.WakeLock;
45import android.provider.MediaStore;
Marco Nelissen3d54a512009-06-03 16:09:18 -070046import android.telephony.PhoneStateListener;
47import android.telephony.TelephonyManager;
The Android Open Source Project792a2202009-03-03 19:32:30 -080048import android.util.Log;
49import android.widget.RemoteViews;
50import android.widget.Toast;
The Android Open Source Project792a2202009-03-03 19:32:30 -080051
Marco Nelissen39888902010-03-02 10:27:05 -080052import java.io.FileDescriptor;
The Android Open Source Project792a2202009-03-03 19:32:30 -080053import java.io.IOException;
Marco Nelissen39888902010-03-02 10:27:05 -080054import java.io.PrintWriter;
Marco Nelissen2b0b9132009-05-21 16:26:47 -070055import java.lang.ref.WeakReference;
The Android Open Source Project792a2202009-03-03 19:32:30 -080056import java.util.Random;
57import java.util.Vector;
58
59/**
60 * Provides "background" audio playback capabilities, allowing the
61 * user to switch between activities without stopping playback.
62 */
63public class MediaPlaybackService extends Service {
64 /** used to specify whether enqueue() should start playing
65 * the new list of files right away, next or once all the currently
66 * queued files have been played
67 */
68 public static final int NOW = 1;
69 public static final int NEXT = 2;
70 public static final int LAST = 3;
71 public static final int PLAYBACKSERVICE_STATUS = 1;
72
73 public static final int SHUFFLE_NONE = 0;
74 public static final int SHUFFLE_NORMAL = 1;
75 public static final int SHUFFLE_AUTO = 2;
76
77 public static final int REPEAT_NONE = 0;
78 public static final int REPEAT_CURRENT = 1;
79 public static final int REPEAT_ALL = 2;
80
81 public static final String PLAYSTATE_CHANGED = "com.android.music.playstatechanged";
82 public static final String META_CHANGED = "com.android.music.metachanged";
83 public static final String QUEUE_CHANGED = "com.android.music.queuechanged";
84 public static final String PLAYBACK_COMPLETE = "com.android.music.playbackcomplete";
The Android Open Source Project792a2202009-03-03 19:32:30 -080085
86 public static final String SERVICECMD = "com.android.music.musicservicecommand";
87 public static final String CMDNAME = "command";
88 public static final String CMDTOGGLEPAUSE = "togglepause";
89 public static final String CMDSTOP = "stop";
90 public static final String CMDPAUSE = "pause";
91 public static final String CMDPREVIOUS = "previous";
92 public static final String CMDNEXT = "next";
93
94 public static final String TOGGLEPAUSE_ACTION = "com.android.music.musicservicecommand.togglepause";
95 public static final String PAUSE_ACTION = "com.android.music.musicservicecommand.pause";
96 public static final String PREVIOUS_ACTION = "com.android.music.musicservicecommand.previous";
97 public static final String NEXT_ACTION = "com.android.music.musicservicecommand.next";
98
The Android Open Source Project792a2202009-03-03 19:32:30 -080099 private static final int TRACK_ENDED = 1;
100 private static final int RELEASE_WAKELOCK = 2;
101 private static final int SERVER_DIED = 3;
102 private static final int FADEIN = 4;
Marco Nelissen3ec2ad92009-08-17 08:52:29 -0700103 private static final int MAX_HISTORY_SIZE = 100;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800104
105 private MultiPlayer mPlayer;
106 private String mFileToPlay;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800107 private int mShuffleMode = SHUFFLE_NONE;
108 private int mRepeatMode = REPEAT_NONE;
109 private int mMediaMountedCount = 0;
Marco Nelissenbd447b62009-06-29 14:52:05 -0700110 private long [] mAutoShuffleList = null;
Marco Nelissenbd447b62009-06-29 14:52:05 -0700111 private long [] mPlayList = null;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800112 private int mPlayListLen = 0;
113 private Vector<Integer> mHistory = new Vector<Integer>(MAX_HISTORY_SIZE);
114 private Cursor mCursor;
115 private int mPlayPos = -1;
116 private static final String LOGTAG = "MediaPlaybackService";
117 private final Shuffler mRand = new Shuffler();
118 private int mOpenFailedCounter = 0;
119 String[] mCursorCols = new String[] {
120 "audio._id AS _id", // index must match IDCOLIDX below
121 MediaStore.Audio.Media.ARTIST,
122 MediaStore.Audio.Media.ALBUM,
123 MediaStore.Audio.Media.TITLE,
124 MediaStore.Audio.Media.DATA,
125 MediaStore.Audio.Media.MIME_TYPE,
126 MediaStore.Audio.Media.ALBUM_ID,
127 MediaStore.Audio.Media.ARTIST_ID,
128 MediaStore.Audio.Media.IS_PODCAST, // index must match PODCASTCOLIDX below
129 MediaStore.Audio.Media.BOOKMARK // index must match BOOKMARKCOLIDX below
130 };
131 private final static int IDCOLIDX = 0;
132 private final static int PODCASTCOLIDX = 8;
133 private final static int BOOKMARKCOLIDX = 9;
134 private BroadcastReceiver mUnmountReceiver = null;
135 private WakeLock mWakeLock;
136 private int mServiceStartId = -1;
137 private boolean mServiceInUse = false;
Marco Nelissenc1333372009-05-06 12:54:47 -0700138 private boolean mIsSupposedToBePlaying = false;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800139 private boolean mQuietMode = false;
Jean-Michel Trivi085cbfd2010-03-08 17:06:18 -0800140 private AudioManager mAudioManager;
Marco Nelissen8d08ec22010-05-10 14:05:24 -0700141 private boolean mQueueIsSaveable = true;
Jean-Michel Trivi085cbfd2010-03-08 17:06:18 -0800142 // used to track what type of audio focus loss caused the playback to pause
143 private boolean mPausedByTransientLossOfFocus = false;
144
The Android Open Source Project792a2202009-03-03 19:32:30 -0800145 private SharedPreferences mPreferences;
146 // We use this to distinguish between different cards when saving/restoring playlists.
147 // This will have to change if we want to support multiple simultaneous cards.
148 private int mCardId;
149
The Android Open Source Project490384b2009-03-11 12:11:59 -0700150 private MediaAppWidgetProvider mAppWidgetProvider = MediaAppWidgetProvider.getInstance();
The Android Open Source Project792a2202009-03-03 19:32:30 -0800151
152 // interval after which we stop the service when idle
Jean-Michel Triviaa331872010-04-20 17:00:26 -0700153 private static final int IDLE_DELAY = 60000;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800154
155 private void startAndFadeIn() {
156 mMediaplayerHandler.sendEmptyMessageDelayed(FADEIN, 10);
157 }
158
159 private Handler mMediaplayerHandler = new Handler() {
160 float mCurrentVolume = 1.0f;
161 @Override
162 public void handleMessage(Message msg) {
Marco Nelissen39888902010-03-02 10:27:05 -0800163 MusicUtils.debugLog("mMediaplayerHandler.handleMessage " + msg.what);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800164 switch (msg.what) {
165 case FADEIN:
166 if (!isPlaying()) {
167 mCurrentVolume = 0f;
168 mPlayer.setVolume(mCurrentVolume);
169 play();
170 mMediaplayerHandler.sendEmptyMessageDelayed(FADEIN, 10);
171 } else {
172 mCurrentVolume += 0.01f;
173 if (mCurrentVolume < 1.0f) {
174 mMediaplayerHandler.sendEmptyMessageDelayed(FADEIN, 10);
175 } else {
176 mCurrentVolume = 1.0f;
177 }
178 mPlayer.setVolume(mCurrentVolume);
179 }
180 break;
181 case SERVER_DIED:
Marco Nelissenc1333372009-05-06 12:54:47 -0700182 if (mIsSupposedToBePlaying) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800183 next(true);
184 } else {
185 // the server died when we were idle, so just
186 // reopen the same song (it will start again
187 // from the beginning though when the user
188 // restarts)
189 openCurrent();
190 }
191 break;
192 case TRACK_ENDED:
193 if (mRepeatMode == REPEAT_CURRENT) {
194 seek(0);
195 play();
The Android Open Source Project792a2202009-03-03 19:32:30 -0800196 } else {
Marco Nelissen8d08ec22010-05-10 14:05:24 -0700197 next(false);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800198 }
199 break;
200 case RELEASE_WAKELOCK:
201 mWakeLock.release();
202 break;
203 default:
204 break;
205 }
206 }
207 };
208
209 private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
210 @Override
211 public void onReceive(Context context, Intent intent) {
212 String action = intent.getAction();
213 String cmd = intent.getStringExtra("command");
Marco Nelissen39888902010-03-02 10:27:05 -0800214 MusicUtils.debugLog("mIntentReceiver.onReceive " + action + " / " + cmd);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800215 if (CMDNEXT.equals(cmd) || NEXT_ACTION.equals(action)) {
216 next(true);
217 } else if (CMDPREVIOUS.equals(cmd) || PREVIOUS_ACTION.equals(action)) {
218 prev();
219 } else if (CMDTOGGLEPAUSE.equals(cmd) || TOGGLEPAUSE_ACTION.equals(action)) {
220 if (isPlaying()) {
221 pause();
Jean-Michel Triviaa331872010-04-20 17:00:26 -0700222 mPausedByTransientLossOfFocus = false;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800223 } else {
224 play();
225 }
226 } else if (CMDPAUSE.equals(cmd) || PAUSE_ACTION.equals(action)) {
227 pause();
Jean-Michel Triviaa331872010-04-20 17:00:26 -0700228 mPausedByTransientLossOfFocus = false;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800229 } else if (CMDSTOP.equals(cmd)) {
230 pause();
Jean-Michel Triviaa331872010-04-20 17:00:26 -0700231 mPausedByTransientLossOfFocus = false;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800232 seek(0);
The Android Open Source Project490384b2009-03-11 12:11:59 -0700233 } else if (MediaAppWidgetProvider.CMDAPPWIDGETUPDATE.equals(cmd)) {
234 // Someone asked us to refresh a set of specific widgets, probably
The Android Open Source Project792a2202009-03-03 19:32:30 -0800235 // because they were just added.
The Android Open Source Project490384b2009-03-11 12:11:59 -0700236 int[] appWidgetIds = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS);
237 mAppWidgetProvider.performUpdate(MediaPlaybackService.this, appWidgetIds);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800238 }
239 }
240 };
241
Jean-Michel Trivi085cbfd2010-03-08 17:06:18 -0800242 private OnAudioFocusChangeListener mAudioFocusListener = new OnAudioFocusChangeListener() {
Jean-Michel Trivif4cfdfd2010-03-31 12:12:23 -0700243 public void onAudioFocusChange(int focusChange) {
Jean-Michel Trivi085cbfd2010-03-08 17:06:18 -0800244 // AudioFocus is a new feature: focus updates are made verbose on purpose
245 switch (focusChange) {
246 case AudioManager.AUDIOFOCUS_LOSS:
247 Log.v(LOGTAG, "AudioFocus: received AUDIOFOCUS_LOSS");
248 if(isPlaying()) {
249 mPausedByTransientLossOfFocus = false;
250 pause();
251 }
252 break;
253 case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
Jean-Michel Trivi302a7fd2010-03-19 17:41:49 -0700254 case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
Jean-Michel Trivi085cbfd2010-03-08 17:06:18 -0800255 Log.v(LOGTAG, "AudioFocus: received AUDIOFOCUS_LOSS_TRANSIENT");
256 if(isPlaying()) {
257 mPausedByTransientLossOfFocus = true;
258 pause();
259 }
260 break;
261 case AudioManager.AUDIOFOCUS_GAIN:
262 Log.v(LOGTAG, "AudioFocus: received AUDIOFOCUS_GAIN");
263 if(!isPlaying() && mPausedByTransientLossOfFocus) {
264 mPausedByTransientLossOfFocus = false;
Jean-Michel Triviaa331872010-04-20 17:00:26 -0700265 startAndFadeIn();
Jean-Michel Trivi085cbfd2010-03-08 17:06:18 -0800266 }
267 break;
268 default:
269 Log.e(LOGTAG, "Unknown audio focus change code");
270 }
271 }
272 };
273
The Android Open Source Project792a2202009-03-03 19:32:30 -0800274 public MediaPlaybackService() {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800275 }
276
277 @Override
278 public void onCreate() {
279 super.onCreate();
Jean-Michel Trivi085cbfd2010-03-08 17:06:18 -0800280
281 mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
Jean-Michel Trivi3d22fc22010-03-17 11:46:58 -0700282 mAudioManager.registerMediaButtonEventReceiver(new ComponentName(getPackageName(),
283 MediaButtonIntentReceiver.class.getName()));
The Android Open Source Project792a2202009-03-03 19:32:30 -0800284
285 mPreferences = getSharedPreferences("Music", MODE_WORLD_READABLE | MODE_WORLD_WRITEABLE);
Marco Nelissen87bbf3c2009-12-23 16:18:45 -0800286 mCardId = MusicUtils.getCardId(this);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800287
288 registerExternalStorageListener();
289
290 // Needs to be done in this thread, since otherwise ApplicationContext.getPowerManager() crashes.
291 mPlayer = new MultiPlayer();
292 mPlayer.setHandler(mMediaplayerHandler);
293
The Android Open Source Project792a2202009-03-03 19:32:30 -0800294 reloadQueue();
295
296 IntentFilter commandFilter = new IntentFilter();
297 commandFilter.addAction(SERVICECMD);
298 commandFilter.addAction(TOGGLEPAUSE_ACTION);
299 commandFilter.addAction(PAUSE_ACTION);
300 commandFilter.addAction(NEXT_ACTION);
301 commandFilter.addAction(PREVIOUS_ACTION);
302 registerReceiver(mIntentReceiver, commandFilter);
303
The Android Open Source Project792a2202009-03-03 19:32:30 -0800304 PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
305 mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, this.getClass().getName());
306 mWakeLock.setReferenceCounted(false);
307
308 // If the service was idle, but got killed before it stopped itself, the
309 // system will relaunch it. Make sure it gets stopped again in that case.
310 Message msg = mDelayedStopHandler.obtainMessage();
311 mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
312 }
313
314 @Override
315 public void onDestroy() {
316 // Check that we're not being destroyed while something is still playing.
317 if (isPlaying()) {
Marco Nelissene99341f2009-11-11 11:13:51 -0800318 Log.e(LOGTAG, "Service being destroyed while still playing.");
The Android Open Source Project792a2202009-03-03 19:32:30 -0800319 }
Marco Nelissen2b0b9132009-05-21 16:26:47 -0700320 // release all MediaPlayer resources, including the native player and wakelocks
321 mPlayer.release();
322 mPlayer = null;
Jean-Michel Trivi085cbfd2010-03-08 17:06:18 -0800323
324 mAudioManager.abandonAudioFocus(mAudioFocusListener);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800325
Marco Nelissen2b0b9132009-05-21 16:26:47 -0700326 // make sure there aren't any other messages coming
327 mDelayedStopHandler.removeCallbacksAndMessages(null);
Marco Nelissen49e36ea2009-05-28 10:20:02 -0700328 mMediaplayerHandler.removeCallbacksAndMessages(null);
Marco Nelissen2b0b9132009-05-21 16:26:47 -0700329
The Android Open Source Project792a2202009-03-03 19:32:30 -0800330 if (mCursor != null) {
331 mCursor.close();
332 mCursor = null;
333 }
334
335 unregisterReceiver(mIntentReceiver);
336 if (mUnmountReceiver != null) {
337 unregisterReceiver(mUnmountReceiver);
338 mUnmountReceiver = null;
339 }
The Android Open Source Project792a2202009-03-03 19:32:30 -0800340 mWakeLock.release();
341 super.onDestroy();
342 }
343
344 private final char hexdigits [] = new char [] {
345 '0', '1', '2', '3',
346 '4', '5', '6', '7',
347 '8', '9', 'a', 'b',
348 'c', 'd', 'e', 'f'
349 };
350
351 private void saveQueue(boolean full) {
Marco Nelissen8d08ec22010-05-10 14:05:24 -0700352 if (!mQueueIsSaveable) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800353 return;
354 }
Marco Nelissen8d08ec22010-05-10 14:05:24 -0700355
The Android Open Source Project792a2202009-03-03 19:32:30 -0800356 Editor ed = mPreferences.edit();
357 //long start = System.currentTimeMillis();
358 if (full) {
359 StringBuilder q = new StringBuilder();
360
361 // The current playlist is saved as a list of "reverse hexadecimal"
362 // numbers, which we can generate faster than normal decimal or
363 // hexadecimal numbers, which in turn allows us to save the playlist
364 // more often without worrying too much about performance.
365 // (saving the full state takes about 40 ms under no-load conditions
366 // on the phone)
367 int len = mPlayListLen;
368 for (int i = 0; i < len; i++) {
Marco Nelissenbd447b62009-06-29 14:52:05 -0700369 long n = mPlayList[i];
Marco Nelissen13355022010-08-31 15:13:27 -0700370 if (n < 0) {
371 continue;
372 } else if (n == 0) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800373 q.append("0;");
374 } else {
375 while (n != 0) {
Marco Nelissenbd447b62009-06-29 14:52:05 -0700376 int digit = (int)(n & 0xf);
Marco Nelissen13355022010-08-31 15:13:27 -0700377 n >>>= 4;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800378 q.append(hexdigits[digit]);
379 }
380 q.append(";");
381 }
382 }
383 //Log.i("@@@@ service", "created queue string in " + (System.currentTimeMillis() - start) + " ms");
384 ed.putString("queue", q.toString());
385 ed.putInt("cardid", mCardId);
Marco Nelissen63dbbbb2009-08-17 14:17:27 -0700386 if (mShuffleMode != SHUFFLE_NONE) {
387 // In shuffle mode we need to save the history too
388 len = mHistory.size();
389 q.setLength(0);
390 for (int i = 0; i < len; i++) {
391 int n = mHistory.get(i);
392 if (n == 0) {
393 q.append("0;");
394 } else {
395 while (n != 0) {
396 int digit = (n & 0xf);
Marco Nelissen13355022010-08-31 15:13:27 -0700397 n >>>= 4;
Marco Nelissen63dbbbb2009-08-17 14:17:27 -0700398 q.append(hexdigits[digit]);
399 }
400 q.append(";");
401 }
402 }
403 ed.putString("history", q.toString());
404 }
The Android Open Source Project792a2202009-03-03 19:32:30 -0800405 }
406 ed.putInt("curpos", mPlayPos);
407 if (mPlayer.isInitialized()) {
408 ed.putLong("seekpos", mPlayer.position());
409 }
410 ed.putInt("repeatmode", mRepeatMode);
411 ed.putInt("shufflemode", mShuffleMode);
412 ed.commit();
413
414 //Log.i("@@@@ service", "saved state in " + (System.currentTimeMillis() - start) + " ms");
415 }
416
417 private void reloadQueue() {
418 String q = null;
419
420 boolean newstyle = false;
421 int id = mCardId;
422 if (mPreferences.contains("cardid")) {
423 newstyle = true;
424 id = mPreferences.getInt("cardid", ~mCardId);
425 }
426 if (id == mCardId) {
427 // Only restore the saved playlist if the card is still
428 // the same one as when the playlist was saved
429 q = mPreferences.getString("queue", "");
430 }
Marco Nelissen6c615a22009-06-10 12:43:25 -0700431 int qlen = q != null ? q.length() : 0;
432 if (qlen > 1) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800433 //Log.i("@@@@ service", "loaded queue: " + q);
Marco Nelissenbf0ea142009-06-05 12:46:09 -0700434 int plen = 0;
435 int n = 0;
436 int shift = 0;
437 for (int i = 0; i < qlen; i++) {
438 char c = q.charAt(i);
439 if (c == ';') {
440 ensurePlayListCapacity(plen + 1);
441 mPlayList[plen] = n;
442 plen++;
443 n = 0;
444 shift = 0;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800445 } else {
Marco Nelissenbf0ea142009-06-05 12:46:09 -0700446 if (c >= '0' && c <= '9') {
447 n += ((c - '0') << shift);
448 } else if (c >= 'a' && c <= 'f') {
449 n += ((10 + c - 'a') << shift);
450 } else {
451 // bogus playlist data
452 plen = 0;
453 break;
454 }
455 shift += 4;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800456 }
457 }
Marco Nelissenbf0ea142009-06-05 12:46:09 -0700458 mPlayListLen = plen;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800459
460 int pos = mPreferences.getInt("curpos", 0);
Marco Nelissenbf0ea142009-06-05 12:46:09 -0700461 if (pos < 0 || pos >= mPlayListLen) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800462 // The saved playlist is bogus, discard it
463 mPlayListLen = 0;
464 return;
465 }
466 mPlayPos = pos;
467
468 // When reloadQueue is called in response to a card-insertion,
469 // we might not be able to query the media provider right away.
470 // To deal with this, try querying for the current file, and if
471 // that fails, wait a while and try again. If that too fails,
472 // assume there is a problem and don't restore the state.
Marco Nelissen63dbbbb2009-08-17 14:17:27 -0700473 Cursor crsr = MusicUtils.query(this,
The Android Open Source Project792a2202009-03-03 19:32:30 -0800474 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
475 new String [] {"_id"}, "_id=" + mPlayList[mPlayPos] , null, null);
Marco Nelissen63dbbbb2009-08-17 14:17:27 -0700476 if (crsr == null || crsr.getCount() == 0) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800477 // wait a bit and try again
478 SystemClock.sleep(3000);
Marco Nelissen63dbbbb2009-08-17 14:17:27 -0700479 crsr = getContentResolver().query(
The Android Open Source Project792a2202009-03-03 19:32:30 -0800480 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
481 mCursorCols, "_id=" + mPlayList[mPlayPos] , null, null);
482 }
Marco Nelissen63dbbbb2009-08-17 14:17:27 -0700483 if (crsr != null) {
484 crsr.close();
The Android Open Source Project792a2202009-03-03 19:32:30 -0800485 }
486
487 // Make sure we don't auto-skip to the next song, since that
488 // also starts playback. What could happen in that case is:
489 // - music is paused
490 // - go to UMS and delete some files, including the currently playing one
491 // - come back from UMS
492 // (time passes)
493 // - music app is killed for some reason (out of memory)
494 // - music service is restarted, service restores state, doesn't find
495 // the "current" file, goes to the next and: playback starts on its
496 // own, potentially at some random inconvenient time.
497 mOpenFailedCounter = 20;
498 mQuietMode = true;
499 openCurrent();
500 mQuietMode = false;
501 if (!mPlayer.isInitialized()) {
502 // couldn't restore the saved state
503 mPlayListLen = 0;
504 return;
505 }
506
507 long seekpos = mPreferences.getLong("seekpos", 0);
508 seek(seekpos >= 0 && seekpos < duration() ? seekpos : 0);
Marco Nelissene99341f2009-11-11 11:13:51 -0800509 Log.d(LOGTAG, "restored queue, currently at position "
510 + position() + "/" + duration()
511 + " (requested " + seekpos + ")");
The Android Open Source Project792a2202009-03-03 19:32:30 -0800512
513 int repmode = mPreferences.getInt("repeatmode", REPEAT_NONE);
514 if (repmode != REPEAT_ALL && repmode != REPEAT_CURRENT) {
515 repmode = REPEAT_NONE;
516 }
517 mRepeatMode = repmode;
518
519 int shufmode = mPreferences.getInt("shufflemode", SHUFFLE_NONE);
520 if (shufmode != SHUFFLE_AUTO && shufmode != SHUFFLE_NORMAL) {
521 shufmode = SHUFFLE_NONE;
522 }
Marco Nelissen63dbbbb2009-08-17 14:17:27 -0700523 if (shufmode != SHUFFLE_NONE) {
524 // in shuffle mode we need to restore the history too
525 q = mPreferences.getString("history", "");
526 qlen = q != null ? q.length() : 0;
527 if (qlen > 1) {
528 plen = 0;
529 n = 0;
530 shift = 0;
531 mHistory.clear();
532 for (int i = 0; i < qlen; i++) {
533 char c = q.charAt(i);
534 if (c == ';') {
535 if (n >= mPlayListLen) {
536 // bogus history data
537 mHistory.clear();
538 break;
539 }
540 mHistory.add(n);
541 n = 0;
542 shift = 0;
543 } else {
544 if (c >= '0' && c <= '9') {
545 n += ((c - '0') << shift);
546 } else if (c >= 'a' && c <= 'f') {
547 n += ((10 + c - 'a') << shift);
548 } else {
549 // bogus history data
550 mHistory.clear();
551 break;
552 }
553 shift += 4;
554 }
555 }
556 }
557 }
The Android Open Source Project792a2202009-03-03 19:32:30 -0800558 if (shufmode == SHUFFLE_AUTO) {
559 if (! makeAutoShuffleList()) {
560 shufmode = SHUFFLE_NONE;
561 }
562 }
563 mShuffleMode = shufmode;
564 }
565 }
566
567 @Override
568 public IBinder onBind(Intent intent) {
569 mDelayedStopHandler.removeCallbacksAndMessages(null);
570 mServiceInUse = true;
571 return mBinder;
572 }
573
574 @Override
575 public void onRebind(Intent intent) {
576 mDelayedStopHandler.removeCallbacksAndMessages(null);
577 mServiceInUse = true;
578 }
579
580 @Override
Marco Nelissenc1017e52009-09-24 13:21:06 -0700581 public int onStartCommand(Intent intent, int flags, int startId) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800582 mServiceStartId = startId;
583 mDelayedStopHandler.removeCallbacksAndMessages(null);
Marco Nelissenc1017e52009-09-24 13:21:06 -0700584
585 if (intent != null) {
586 String action = intent.getAction();
587 String cmd = intent.getStringExtra("command");
Marco Nelissen39888902010-03-02 10:27:05 -0800588 MusicUtils.debugLog("onStartCommand " + action + " / " + cmd);
Marco Nelissenc1017e52009-09-24 13:21:06 -0700589
590 if (CMDNEXT.equals(cmd) || NEXT_ACTION.equals(action)) {
591 next(true);
592 } else if (CMDPREVIOUS.equals(cmd) || PREVIOUS_ACTION.equals(action)) {
Marco Nelissenb63b5d12009-11-18 16:04:19 -0800593 if (position() < 2000) {
594 prev();
595 } else {
596 seek(0);
597 play();
598 }
Marco Nelissenc1017e52009-09-24 13:21:06 -0700599 } else if (CMDTOGGLEPAUSE.equals(cmd) || TOGGLEPAUSE_ACTION.equals(action)) {
600 if (isPlaying()) {
601 pause();
Jean-Michel Triviaa331872010-04-20 17:00:26 -0700602 mPausedByTransientLossOfFocus = false;
Marco Nelissenc1017e52009-09-24 13:21:06 -0700603 } else {
604 play();
605 }
606 } else if (CMDPAUSE.equals(cmd) || PAUSE_ACTION.equals(action)) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800607 pause();
Jean-Michel Triviaa331872010-04-20 17:00:26 -0700608 mPausedByTransientLossOfFocus = false;
Marco Nelissenc1017e52009-09-24 13:21:06 -0700609 } else if (CMDSTOP.equals(cmd)) {
610 pause();
Jean-Michel Triviaa331872010-04-20 17:00:26 -0700611 mPausedByTransientLossOfFocus = false;
Marco Nelissenc1017e52009-09-24 13:21:06 -0700612 seek(0);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800613 }
The Android Open Source Project792a2202009-03-03 19:32:30 -0800614 }
615
616 // make sure the service will shut down on its own if it was
617 // just started but not bound to and nothing is playing
618 mDelayedStopHandler.removeCallbacksAndMessages(null);
619 Message msg = mDelayedStopHandler.obtainMessage();
620 mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
Marco Nelissenc1017e52009-09-24 13:21:06 -0700621 return START_STICKY;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800622 }
623
624 @Override
625 public boolean onUnbind(Intent intent) {
626 mServiceInUse = false;
627
628 // Take a snapshot of the current playlist
629 saveQueue(true);
630
Jean-Michel Triviaa331872010-04-20 17:00:26 -0700631 if (isPlaying() || mPausedByTransientLossOfFocus) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800632 // something is currently playing, or will be playing once
Jean-Michel Triviaa331872010-04-20 17:00:26 -0700633 // an in-progress action requesting audio focus ends, so don't stop the service now.
The Android Open Source Project792a2202009-03-03 19:32:30 -0800634 return true;
635 }
636
637 // If there is a playlist but playback is paused, then wait a while
638 // before stopping the service, so that pause/resume isn't slow.
639 // Also delay stopping the service if we're transitioning between tracks.
640 if (mPlayListLen > 0 || mMediaplayerHandler.hasMessages(TRACK_ENDED)) {
641 Message msg = mDelayedStopHandler.obtainMessage();
642 mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
643 return true;
644 }
645
646 // No active playlist, OK to stop the service right now
647 stopSelf(mServiceStartId);
648 return true;
649 }
650
651 private Handler mDelayedStopHandler = new Handler() {
652 @Override
653 public void handleMessage(Message msg) {
654 // Check again to make sure nothing is playing right now
Jean-Michel Triviaa331872010-04-20 17:00:26 -0700655 if (isPlaying() || mPausedByTransientLossOfFocus || mServiceInUse
The Android Open Source Project792a2202009-03-03 19:32:30 -0800656 || mMediaplayerHandler.hasMessages(TRACK_ENDED)) {
657 return;
658 }
659 // save the queue again, because it might have changed
660 // since the user exited the music app (because of
661 // party-shuffle or because the play-position changed)
662 saveQueue(true);
663 stopSelf(mServiceStartId);
664 }
665 };
666
667 /**
668 * Called when we receive a ACTION_MEDIA_EJECT notification.
669 *
670 * @param storagePath path to mount point for the removed media
671 */
672 public void closeExternalStorageFiles(String storagePath) {
673 // stop playback and clean up if the SD card is going to be unmounted.
674 stop(true);
675 notifyChange(QUEUE_CHANGED);
676 notifyChange(META_CHANGED);
677 }
678
679 /**
680 * Registers an intent to listen for ACTION_MEDIA_EJECT notifications.
681 * The intent will call closeExternalStorageFiles() if the external media
682 * is going to be ejected, so applications can clean up any files they have open.
683 */
684 public void registerExternalStorageListener() {
685 if (mUnmountReceiver == null) {
686 mUnmountReceiver = new BroadcastReceiver() {
687 @Override
688 public void onReceive(Context context, Intent intent) {
689 String action = intent.getAction();
690 if (action.equals(Intent.ACTION_MEDIA_EJECT)) {
691 saveQueue(true);
Marco Nelissen8d08ec22010-05-10 14:05:24 -0700692 mQueueIsSaveable = false;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800693 closeExternalStorageFiles(intent.getData().getPath());
694 } else if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) {
695 mMediaMountedCount++;
Marco Nelissen87bbf3c2009-12-23 16:18:45 -0800696 mCardId = MusicUtils.getCardId(MediaPlaybackService.this);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800697 reloadQueue();
Marco Nelissen8d08ec22010-05-10 14:05:24 -0700698 mQueueIsSaveable = true;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800699 notifyChange(QUEUE_CHANGED);
700 notifyChange(META_CHANGED);
701 }
702 }
703 };
704 IntentFilter iFilter = new IntentFilter();
705 iFilter.addAction(Intent.ACTION_MEDIA_EJECT);
706 iFilter.addAction(Intent.ACTION_MEDIA_MOUNTED);
707 iFilter.addDataScheme("file");
708 registerReceiver(mUnmountReceiver, iFilter);
709 }
710 }
711
712 /**
713 * Notify the change-receivers that something has changed.
714 * The intent that is sent contains the following data
715 * for the currently playing track:
716 * "id" - Integer: the database row ID
717 * "artist" - String: the name of the artist
718 * "album" - String: the name of the album
719 * "track" - String: the name of the track
720 * The intent has an action that is one of
721 * "com.android.music.metachanged"
722 * "com.android.music.queuechanged",
723 * "com.android.music.playbackcomplete"
724 * "com.android.music.playstatechanged"
725 * respectively indicating that a new track has
726 * started playing, that the playback queue has
727 * changed, that playback has stopped because
728 * the last file in the list has been played,
729 * or that the play-state changed (paused/resumed).
730 */
731 private void notifyChange(String what) {
732
733 Intent i = new Intent(what);
Marco Nelissenbd447b62009-06-29 14:52:05 -0700734 i.putExtra("id", Long.valueOf(getAudioId()));
The Android Open Source Project792a2202009-03-03 19:32:30 -0800735 i.putExtra("artist", getArtistName());
736 i.putExtra("album",getAlbumName());
737 i.putExtra("track", getTrackName());
738 sendBroadcast(i);
739
740 if (what.equals(QUEUE_CHANGED)) {
741 saveQueue(true);
742 } else {
743 saveQueue(false);
744 }
745
The Android Open Source Project490384b2009-03-11 12:11:59 -0700746 // Share this notification directly with our widgets
747 mAppWidgetProvider.notifyChange(this, what);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800748 }
749
750 private void ensurePlayListCapacity(int size) {
751 if (mPlayList == null || size > mPlayList.length) {
752 // reallocate at 2x requested size so we don't
753 // need to grow and copy the array for every
754 // insert
Marco Nelissenbd447b62009-06-29 14:52:05 -0700755 long [] newlist = new long[size * 2];
Marco Nelissenbf0ea142009-06-05 12:46:09 -0700756 int len = mPlayList != null ? mPlayList.length : mPlayListLen;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800757 for (int i = 0; i < len; i++) {
758 newlist[i] = mPlayList[i];
759 }
760 mPlayList = newlist;
761 }
762 // FIXME: shrink the array when the needed size is much smaller
763 // than the allocated size
764 }
765
766 // insert the list of songs at the specified position in the playlist
Marco Nelissenbd447b62009-06-29 14:52:05 -0700767 private void addToPlayList(long [] list, int position) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800768 int addlen = list.length;
769 if (position < 0) { // overwrite
770 mPlayListLen = 0;
771 position = 0;
772 }
773 ensurePlayListCapacity(mPlayListLen + addlen);
774 if (position > mPlayListLen) {
775 position = mPlayListLen;
776 }
777
778 // move part of list after insertion point
779 int tailsize = mPlayListLen - position;
780 for (int i = tailsize ; i > 0 ; i--) {
781 mPlayList[position + i] = mPlayList[position + i - addlen];
782 }
783
784 // copy list into playlist
785 for (int i = 0; i < addlen; i++) {
786 mPlayList[position + i] = list[i];
787 }
788 mPlayListLen += addlen;
789 }
790
791 /**
792 * Appends a list of tracks to the current playlist.
793 * If nothing is playing currently, playback will be started at
794 * the first track.
795 * If the action is NOW, playback will switch to the first of
796 * the new tracks immediately.
797 * @param list The list of tracks to append.
798 * @param action NOW, NEXT or LAST
799 */
Marco Nelissenbd447b62009-06-29 14:52:05 -0700800 public void enqueue(long [] list, int action) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800801 synchronized(this) {
802 if (action == NEXT && mPlayPos + 1 < mPlayListLen) {
803 addToPlayList(list, mPlayPos + 1);
804 notifyChange(QUEUE_CHANGED);
805 } else {
806 // action == LAST || action == NOW || mPlayPos + 1 == mPlayListLen
807 addToPlayList(list, Integer.MAX_VALUE);
808 notifyChange(QUEUE_CHANGED);
809 if (action == NOW) {
810 mPlayPos = mPlayListLen - list.length;
811 openCurrent();
812 play();
813 notifyChange(META_CHANGED);
814 return;
815 }
816 }
817 if (mPlayPos < 0) {
818 mPlayPos = 0;
819 openCurrent();
820 play();
821 notifyChange(META_CHANGED);
822 }
823 }
824 }
825
826 /**
827 * Replaces the current playlist with a new list,
828 * and prepares for starting playback at the specified
829 * position in the list, or a random position if the
830 * specified position is 0.
831 * @param list The new list of tracks.
832 */
Marco Nelissenbd447b62009-06-29 14:52:05 -0700833 public void open(long [] list, int position) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800834 synchronized (this) {
835 if (mShuffleMode == SHUFFLE_AUTO) {
836 mShuffleMode = SHUFFLE_NORMAL;
837 }
Marco Nelissenbd447b62009-06-29 14:52:05 -0700838 long oldId = getAudioId();
The Android Open Source Project792a2202009-03-03 19:32:30 -0800839 int listlength = list.length;
840 boolean newlist = true;
841 if (mPlayListLen == listlength) {
842 // possible fast path: list might be the same
843 newlist = false;
844 for (int i = 0; i < listlength; i++) {
845 if (list[i] != mPlayList[i]) {
846 newlist = true;
847 break;
848 }
849 }
850 }
851 if (newlist) {
852 addToPlayList(list, -1);
853 notifyChange(QUEUE_CHANGED);
854 }
855 int oldpos = mPlayPos;
856 if (position >= 0) {
857 mPlayPos = position;
858 } else {
859 mPlayPos = mRand.nextInt(mPlayListLen);
860 }
861 mHistory.clear();
862
863 saveBookmarkIfNeeded();
864 openCurrent();
Jeffrey Sharkeyd8c69672009-03-24 17:59:05 -0700865 if (oldId != getAudioId()) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800866 notifyChange(META_CHANGED);
867 }
868 }
869 }
870
871 /**
872 * Moves the item at index1 to index2.
873 * @param index1
874 * @param index2
875 */
876 public void moveQueueItem(int index1, int index2) {
877 synchronized (this) {
878 if (index1 >= mPlayListLen) {
879 index1 = mPlayListLen - 1;
880 }
881 if (index2 >= mPlayListLen) {
882 index2 = mPlayListLen - 1;
883 }
884 if (index1 < index2) {
Marco Nelissenbd447b62009-06-29 14:52:05 -0700885 long tmp = mPlayList[index1];
The Android Open Source Project792a2202009-03-03 19:32:30 -0800886 for (int i = index1; i < index2; i++) {
887 mPlayList[i] = mPlayList[i+1];
888 }
889 mPlayList[index2] = tmp;
890 if (mPlayPos == index1) {
891 mPlayPos = index2;
892 } else if (mPlayPos >= index1 && mPlayPos <= index2) {
893 mPlayPos--;
894 }
895 } else if (index2 < index1) {
Marco Nelissenbd447b62009-06-29 14:52:05 -0700896 long tmp = mPlayList[index1];
The Android Open Source Project792a2202009-03-03 19:32:30 -0800897 for (int i = index1; i > index2; i--) {
898 mPlayList[i] = mPlayList[i-1];
899 }
900 mPlayList[index2] = tmp;
901 if (mPlayPos == index1) {
902 mPlayPos = index2;
903 } else if (mPlayPos >= index2 && mPlayPos <= index1) {
904 mPlayPos++;
905 }
906 }
907 notifyChange(QUEUE_CHANGED);
908 }
909 }
910
911 /**
912 * Returns the current play list
913 * @return An array of integers containing the IDs of the tracks in the play list
914 */
Marco Nelissenbd447b62009-06-29 14:52:05 -0700915 public long [] getQueue() {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800916 synchronized (this) {
917 int len = mPlayListLen;
Marco Nelissenbd447b62009-06-29 14:52:05 -0700918 long [] list = new long[len];
The Android Open Source Project792a2202009-03-03 19:32:30 -0800919 for (int i = 0; i < len; i++) {
920 list[i] = mPlayList[i];
921 }
922 return list;
923 }
924 }
925
926 private void openCurrent() {
927 synchronized (this) {
928 if (mCursor != null) {
929 mCursor.close();
930 mCursor = null;
931 }
Marco Nelissen8d08ec22010-05-10 14:05:24 -0700932
The Android Open Source Project792a2202009-03-03 19:32:30 -0800933 if (mPlayListLen == 0) {
934 return;
935 }
936 stop(false);
937
938 String id = String.valueOf(mPlayList[mPlayPos]);
939
940 mCursor = getContentResolver().query(
941 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
942 mCursorCols, "_id=" + id , null, null);
943 if (mCursor != null) {
944 mCursor.moveToFirst();
Marco Nelissen8d08ec22010-05-10 14:05:24 -0700945 open(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + "/" + id);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800946 // go to bookmark if needed
947 if (isPodcast()) {
948 long bookmark = getBookmark();
949 // Start playing a little bit before the bookmark,
950 // so it's easier to get back in to the narrative.
951 seek(bookmark - 5000);
952 }
953 }
954 }
955 }
956
The Android Open Source Project792a2202009-03-03 19:32:30 -0800957 /**
958 * Opens the specified file and readies it for playback.
959 *
960 * @param path The full path of the file to be opened.
The Android Open Source Project792a2202009-03-03 19:32:30 -0800961 */
Marco Nelissen8d08ec22010-05-10 14:05:24 -0700962 public void open(String path) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800963 synchronized (this) {
964 if (path == null) {
965 return;
966 }
967
The Android Open Source Project792a2202009-03-03 19:32:30 -0800968 // if mCursor is null, try to associate path with a database cursor
969 if (mCursor == null) {
970
971 ContentResolver resolver = getContentResolver();
972 Uri uri;
973 String where;
974 String selectionArgs[];
975 if (path.startsWith("content://media/")) {
976 uri = Uri.parse(path);
977 where = null;
978 selectionArgs = null;
979 } else {
980 uri = MediaStore.Audio.Media.getContentUriForPath(path);
981 where = MediaStore.Audio.Media.DATA + "=?";
982 selectionArgs = new String[] { path };
983 }
984
985 try {
986 mCursor = resolver.query(uri, mCursorCols, where, selectionArgs, null);
987 if (mCursor != null) {
988 if (mCursor.getCount() == 0) {
989 mCursor.close();
990 mCursor = null;
991 } else {
992 mCursor.moveToNext();
993 ensurePlayListCapacity(1);
994 mPlayListLen = 1;
Marco Nelissenbd447b62009-06-29 14:52:05 -0700995 mPlayList[0] = mCursor.getLong(IDCOLIDX);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800996 mPlayPos = 0;
997 }
998 }
999 } catch (UnsupportedOperationException ex) {
1000 }
1001 }
1002 mFileToPlay = path;
1003 mPlayer.setDataSource(mFileToPlay);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001004 if (! mPlayer.isInitialized()) {
1005 stop(true);
1006 if (mOpenFailedCounter++ < 10 && mPlayListLen > 1) {
1007 // beware: this ends up being recursive because next() calls open() again.
1008 next(false);
1009 }
1010 if (! mPlayer.isInitialized() && mOpenFailedCounter != 0) {
1011 // need to make sure we only shows this once
1012 mOpenFailedCounter = 0;
1013 if (!mQuietMode) {
1014 Toast.makeText(this, R.string.playback_failed, Toast.LENGTH_SHORT).show();
1015 }
Marco Nelissene99341f2009-11-11 11:13:51 -08001016 Log.d(LOGTAG, "Failed to open file for playback");
The Android Open Source Project792a2202009-03-03 19:32:30 -08001017 }
1018 } else {
1019 mOpenFailedCounter = 0;
1020 }
1021 }
1022 }
1023
1024 /**
1025 * Starts playback of a previously opened file.
1026 */
1027 public void play() {
Jean-Michel Trivi085cbfd2010-03-08 17:06:18 -08001028 mAudioManager.requestAudioFocus(mAudioFocusListener, AudioManager.STREAM_MUSIC,
1029 AudioManager.AUDIOFOCUS_GAIN);
Jean-Michel Trivi3d22fc22010-03-17 11:46:58 -07001030 mAudioManager.registerMediaButtonEventReceiver(new ComponentName(this.getPackageName(),
1031 MediaButtonIntentReceiver.class.getName()));
Jean-Michel Trivi085cbfd2010-03-08 17:06:18 -08001032
The Android Open Source Project792a2202009-03-03 19:32:30 -08001033 if (mPlayer.isInitialized()) {
Thomas Tuttle272eb782009-01-28 21:06:46 -05001034 // if we are at the end of the song, go to the next song first
Marco Nelissen2f9a1ce2009-06-26 14:23:31 -07001035 long duration = mPlayer.duration();
1036 if (mRepeatMode != REPEAT_CURRENT && duration > 2000 &&
1037 mPlayer.position() >= duration - 2000) {
Thomas Tuttle272eb782009-01-28 21:06:46 -05001038 next(true);
1039 }
1040
The Android Open Source Project792a2202009-03-03 19:32:30 -08001041 mPlayer.start();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001042
The Android Open Source Project792a2202009-03-03 19:32:30 -08001043 RemoteViews views = new RemoteViews(getPackageName(), R.layout.statusbar);
1044 views.setImageViewResource(R.id.icon, R.drawable.stat_notify_musicplayer);
1045 if (getAudioId() < 0) {
1046 // streaming
1047 views.setTextViewText(R.id.trackname, getPath());
1048 views.setTextViewText(R.id.artistalbum, null);
1049 } else {
1050 String artist = getArtistName();
1051 views.setTextViewText(R.id.trackname, getTrackName());
Marco Nelissenf4d4b342010-01-04 15:01:18 -08001052 if (artist == null || artist.equals(MediaStore.UNKNOWN_STRING)) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001053 artist = getString(R.string.unknown_artist_name);
1054 }
1055 String album = getAlbumName();
Marco Nelissenf4d4b342010-01-04 15:01:18 -08001056 if (album == null || album.equals(MediaStore.UNKNOWN_STRING)) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001057 album = getString(R.string.unknown_album_name);
1058 }
1059
1060 views.setTextViewText(R.id.artistalbum,
1061 getString(R.string.notification_artist_album, artist, album)
1062 );
1063 }
1064
The Android Open Source Project792a2202009-03-03 19:32:30 -08001065 Notification status = new Notification();
1066 status.contentView = views;
1067 status.flags |= Notification.FLAG_ONGOING_EVENT;
1068 status.icon = R.drawable.stat_notify_musicplayer;
1069 status.contentIntent = PendingIntent.getActivity(this, 0,
Marco Nelissenec0c57a2009-12-12 12:27:11 -08001070 new Intent("com.android.music.PLAYBACK_VIEWER")
1071 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK), 0);
Dianne Hackbornd5fc5b62009-08-18 11:35:30 -07001072 startForeground(PLAYBACKSERVICE_STATUS, status);
Marco Nelissenc1333372009-05-06 12:54:47 -07001073 if (!mIsSupposedToBePlaying) {
Mike Cleron347fe572009-10-09 12:26:28 -07001074 mIsSupposedToBePlaying = true;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001075 notifyChange(PLAYSTATE_CHANGED);
1076 }
Mike Cleron347fe572009-10-09 12:26:28 -07001077
The Android Open Source Project792a2202009-03-03 19:32:30 -08001078 } else if (mPlayListLen <= 0) {
1079 // This is mostly so that if you press 'play' on a bluetooth headset
1080 // without every having played anything before, it will still play
1081 // something.
1082 setShuffleMode(SHUFFLE_AUTO);
1083 }
1084 }
1085
1086 private void stop(boolean remove_status_icon) {
1087 if (mPlayer.isInitialized()) {
1088 mPlayer.stop();
1089 }
1090 mFileToPlay = null;
1091 if (mCursor != null) {
1092 mCursor.close();
1093 mCursor = null;
1094 }
1095 if (remove_status_icon) {
1096 gotoIdleState();
Dianne Hackbornd5fc5b62009-08-18 11:35:30 -07001097 } else {
1098 stopForeground(false);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001099 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08001100 if (remove_status_icon) {
Marco Nelissenc1333372009-05-06 12:54:47 -07001101 mIsSupposedToBePlaying = false;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001102 }
1103 }
1104
1105 /**
1106 * Stops playback.
1107 */
1108 public void stop() {
1109 stop(true);
1110 }
1111
1112 /**
1113 * Pauses playback (call play() to resume)
1114 */
1115 public void pause() {
Marco Nelissen407cf912009-05-11 09:56:31 -07001116 synchronized(this) {
1117 if (isPlaying()) {
1118 mPlayer.pause();
1119 gotoIdleState();
Marco Nelissen407cf912009-05-11 09:56:31 -07001120 mIsSupposedToBePlaying = false;
1121 notifyChange(PLAYSTATE_CHANGED);
1122 saveBookmarkIfNeeded();
1123 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08001124 }
1125 }
1126
Marco Nelissenb6e7bf72009-05-11 10:28:31 -07001127 /** Returns whether something is currently playing
The Android Open Source Project792a2202009-03-03 19:32:30 -08001128 *
Marco Nelissenb6e7bf72009-05-11 10:28:31 -07001129 * @return true if something is playing (or will be playing shortly, in case
1130 * we're currently transitioning between tracks), false if not.
The Android Open Source Project792a2202009-03-03 19:32:30 -08001131 */
1132 public boolean isPlaying() {
Marco Nelissenc1333372009-05-06 12:54:47 -07001133 return mIsSupposedToBePlaying;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001134 }
1135
1136 /*
1137 Desired behavior for prev/next/shuffle:
1138
1139 - NEXT will move to the next track in the list when not shuffling, and to
1140 a track randomly picked from the not-yet-played tracks when shuffling.
1141 If all tracks have already been played, pick from the full set, but
1142 avoid picking the previously played track if possible.
1143 - when shuffling, PREV will go to the previously played track. Hitting PREV
1144 again will go to the track played before that, etc. When the start of the
1145 history has been reached, PREV is a no-op.
1146 When not shuffling, PREV will go to the sequentially previous track (the
1147 difference with the shuffle-case is mainly that when not shuffling, the
1148 user can back up to tracks that are not in the history).
1149
1150 Example:
1151 When playing an album with 10 tracks from the start, and enabling shuffle
1152 while playing track 5, the remaining tracks (6-10) will be shuffled, e.g.
1153 the final play order might be 1-2-3-4-5-8-10-6-9-7.
1154 When hitting 'prev' 8 times while playing track 7 in this example, the
1155 user will go to tracks 9-6-10-8-5-4-3-2. If the user then hits 'next',
1156 a random track will be picked again. If at any time user disables shuffling
1157 the next/previous track will be picked in sequential order again.
1158 */
1159
1160 public void prev() {
1161 synchronized (this) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001162 if (mShuffleMode == SHUFFLE_NORMAL) {
1163 // go to previously-played track and remove it from the history
1164 int histsize = mHistory.size();
1165 if (histsize == 0) {
1166 // prev is a no-op
1167 return;
1168 }
1169 Integer pos = mHistory.remove(histsize - 1);
1170 mPlayPos = pos.intValue();
1171 } else {
1172 if (mPlayPos > 0) {
1173 mPlayPos--;
1174 } else {
1175 mPlayPos = mPlayListLen - 1;
1176 }
1177 }
1178 saveBookmarkIfNeeded();
1179 stop(false);
1180 openCurrent();
1181 play();
1182 notifyChange(META_CHANGED);
1183 }
1184 }
1185
1186 public void next(boolean force) {
1187 synchronized (this) {
Marco Nelissen663fea32009-06-12 13:39:27 -07001188 if (mPlayListLen <= 0) {
Marco Nelissene99341f2009-11-11 11:13:51 -08001189 Log.d(LOGTAG, "No play queue");
Marco Nelissen663fea32009-06-12 13:39:27 -07001190 return;
1191 }
1192
The Android Open Source Project792a2202009-03-03 19:32:30 -08001193 // Store the current file in the history, but keep the history at a
1194 // reasonable size
1195 if (mPlayPos >= 0) {
1196 mHistory.add(Integer.valueOf(mPlayPos));
1197 }
1198 if (mHistory.size() > MAX_HISTORY_SIZE) {
1199 mHistory.removeElementAt(0);
1200 }
1201
1202 if (mShuffleMode == SHUFFLE_NORMAL) {
1203 // Pick random next track from the not-yet-played ones
1204 // TODO: make it work right after adding/removing items in the queue.
1205
1206 int numTracks = mPlayListLen;
1207 int[] tracks = new int[numTracks];
1208 for (int i=0;i < numTracks; i++) {
1209 tracks[i] = i;
1210 }
1211
1212 int numHistory = mHistory.size();
1213 int numUnplayed = numTracks;
1214 for (int i=0;i < numHistory; i++) {
1215 int idx = mHistory.get(i).intValue();
1216 if (idx < numTracks && tracks[idx] >= 0) {
1217 numUnplayed--;
1218 tracks[idx] = -1;
1219 }
1220 }
1221
1222 // 'numUnplayed' now indicates how many tracks have not yet
1223 // been played, and 'tracks' contains the indices of those
1224 // tracks.
1225 if (numUnplayed <=0) {
1226 // everything's already been played
1227 if (mRepeatMode == REPEAT_ALL || force) {
1228 //pick from full set
1229 numUnplayed = numTracks;
1230 for (int i=0;i < numTracks; i++) {
1231 tracks[i] = i;
1232 }
1233 } else {
1234 // all done
1235 gotoIdleState();
Marco Nelissenab893d72010-02-08 11:00:29 -08001236 if (mIsSupposedToBePlaying) {
1237 mIsSupposedToBePlaying = false;
1238 notifyChange(PLAYSTATE_CHANGED);
1239 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08001240 return;
1241 }
1242 }
1243 int skip = mRand.nextInt(numUnplayed);
1244 int cnt = -1;
1245 while (true) {
1246 while (tracks[++cnt] < 0)
1247 ;
1248 skip--;
1249 if (skip < 0) {
1250 break;
1251 }
1252 }
1253 mPlayPos = cnt;
1254 } else if (mShuffleMode == SHUFFLE_AUTO) {
1255 doAutoShuffleUpdate();
1256 mPlayPos++;
1257 } else {
1258 if (mPlayPos >= mPlayListLen - 1) {
1259 // we're at the end of the list
1260 if (mRepeatMode == REPEAT_NONE && !force) {
1261 // all done
1262 gotoIdleState();
1263 notifyChange(PLAYBACK_COMPLETE);
Marco Nelissenc1333372009-05-06 12:54:47 -07001264 mIsSupposedToBePlaying = false;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001265 return;
1266 } else if (mRepeatMode == REPEAT_ALL || force) {
1267 mPlayPos = 0;
1268 }
1269 } else {
1270 mPlayPos++;
1271 }
1272 }
1273 saveBookmarkIfNeeded();
1274 stop(false);
1275 openCurrent();
1276 play();
1277 notifyChange(META_CHANGED);
1278 }
1279 }
1280
1281 private void gotoIdleState() {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001282 mDelayedStopHandler.removeCallbacksAndMessages(null);
1283 Message msg = mDelayedStopHandler.obtainMessage();
1284 mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
Dianne Hackbornd5fc5b62009-08-18 11:35:30 -07001285 stopForeground(true);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001286 }
1287
1288 private void saveBookmarkIfNeeded() {
1289 try {
1290 if (isPodcast()) {
1291 long pos = position();
1292 long bookmark = getBookmark();
1293 long duration = duration();
1294 if ((pos < bookmark && (pos + 10000) > bookmark) ||
1295 (pos > bookmark && (pos - 10000) < bookmark)) {
1296 // The existing bookmark is close to the current
1297 // position, so don't update it.
1298 return;
1299 }
1300 if (pos < 15000 || (pos + 10000) > duration) {
1301 // if we're near the start or end, clear the bookmark
1302 pos = 0;
1303 }
1304
1305 // write 'pos' to the bookmark field
1306 ContentValues values = new ContentValues();
1307 values.put(MediaStore.Audio.Media.BOOKMARK, pos);
1308 Uri uri = ContentUris.withAppendedId(
1309 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, mCursor.getLong(IDCOLIDX));
1310 getContentResolver().update(uri, values, null, null);
1311 }
1312 } catch (SQLiteException ex) {
1313 }
1314 }
1315
1316 // Make sure there are at least 5 items after the currently playing item
1317 // and no more than 10 items before.
1318 private void doAutoShuffleUpdate() {
1319 boolean notify = false;
1320 // remove old entries
1321 if (mPlayPos > 10) {
1322 removeTracks(0, mPlayPos - 9);
1323 notify = true;
1324 }
1325 // add new entries if needed
1326 int to_add = 7 - (mPlayListLen - (mPlayPos < 0 ? -1 : mPlayPos));
1327 for (int i = 0; i < to_add; i++) {
1328 // pick something at random from the list
1329 int idx = mRand.nextInt(mAutoShuffleList.length);
Marco Nelissenbd447b62009-06-29 14:52:05 -07001330 long which = mAutoShuffleList[idx];
The Android Open Source Project792a2202009-03-03 19:32:30 -08001331 ensurePlayListCapacity(mPlayListLen + 1);
1332 mPlayList[mPlayListLen++] = which;
1333 notify = true;
1334 }
1335 if (notify) {
1336 notifyChange(QUEUE_CHANGED);
1337 }
1338 }
1339
1340 // A simple variation of Random that makes sure that the
1341 // value it returns is not equal to the value it returned
1342 // previously, unless the interval is 1.
Marco Nelissen756c3f52009-05-14 10:07:23 -07001343 private static class Shuffler {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001344 private int mPrevious;
1345 private Random mRandom = new Random();
1346 public int nextInt(int interval) {
1347 int ret;
1348 do {
1349 ret = mRandom.nextInt(interval);
1350 } while (ret == mPrevious && interval > 1);
1351 mPrevious = ret;
1352 return ret;
1353 }
1354 };
1355
1356 private boolean makeAutoShuffleList() {
1357 ContentResolver res = getContentResolver();
1358 Cursor c = null;
1359 try {
1360 c = res.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
1361 new String[] {MediaStore.Audio.Media._ID}, MediaStore.Audio.Media.IS_MUSIC + "=1",
1362 null, null);
1363 if (c == null || c.getCount() == 0) {
1364 return false;
1365 }
1366 int len = c.getCount();
Marco Nelissenbd447b62009-06-29 14:52:05 -07001367 long [] list = new long[len];
The Android Open Source Project792a2202009-03-03 19:32:30 -08001368 for (int i = 0; i < len; i++) {
1369 c.moveToNext();
Marco Nelissenbd447b62009-06-29 14:52:05 -07001370 list[i] = c.getLong(0);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001371 }
1372 mAutoShuffleList = list;
1373 return true;
1374 } catch (RuntimeException ex) {
1375 } finally {
1376 if (c != null) {
1377 c.close();
1378 }
1379 }
1380 return false;
1381 }
1382
1383 /**
1384 * Removes the range of tracks specified from the play list. If a file within the range is
1385 * the file currently being played, playback will move to the next file after the
1386 * range.
1387 * @param first The first file to be removed
1388 * @param last The last file to be removed
1389 * @return the number of tracks deleted
1390 */
1391 public int removeTracks(int first, int last) {
1392 int numremoved = removeTracksInternal(first, last);
1393 if (numremoved > 0) {
1394 notifyChange(QUEUE_CHANGED);
1395 }
1396 return numremoved;
1397 }
1398
1399 private int removeTracksInternal(int first, int last) {
1400 synchronized (this) {
1401 if (last < first) return 0;
1402 if (first < 0) first = 0;
1403 if (last >= mPlayListLen) last = mPlayListLen - 1;
1404
1405 boolean gotonext = false;
1406 if (first <= mPlayPos && mPlayPos <= last) {
1407 mPlayPos = first;
1408 gotonext = true;
1409 } else if (mPlayPos > last) {
1410 mPlayPos -= (last - first + 1);
1411 }
1412 int num = mPlayListLen - last - 1;
1413 for (int i = 0; i < num; i++) {
1414 mPlayList[first + i] = mPlayList[last + 1 + i];
1415 }
1416 mPlayListLen -= last - first + 1;
1417
1418 if (gotonext) {
1419 if (mPlayListLen == 0) {
1420 stop(true);
1421 mPlayPos = -1;
1422 } else {
1423 if (mPlayPos >= mPlayListLen) {
1424 mPlayPos = 0;
1425 }
1426 boolean wasPlaying = isPlaying();
1427 stop(false);
1428 openCurrent();
1429 if (wasPlaying) {
1430 play();
1431 }
1432 }
1433 }
1434 return last - first + 1;
1435 }
1436 }
1437
1438 /**
1439 * Removes all instances of the track with the given id
1440 * from the playlist.
1441 * @param id The id to be removed
1442 * @return how many instances of the track were removed
1443 */
Marco Nelissenbd447b62009-06-29 14:52:05 -07001444 public int removeTrack(long id) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001445 int numremoved = 0;
1446 synchronized (this) {
1447 for (int i = 0; i < mPlayListLen; i++) {
1448 if (mPlayList[i] == id) {
1449 numremoved += removeTracksInternal(i, i);
1450 i--;
1451 }
1452 }
1453 }
1454 if (numremoved > 0) {
1455 notifyChange(QUEUE_CHANGED);
1456 }
1457 return numremoved;
1458 }
1459
1460 public void setShuffleMode(int shufflemode) {
1461 synchronized(this) {
1462 if (mShuffleMode == shufflemode && mPlayListLen > 0) {
1463 return;
1464 }
1465 mShuffleMode = shufflemode;
1466 if (mShuffleMode == SHUFFLE_AUTO) {
1467 if (makeAutoShuffleList()) {
1468 mPlayListLen = 0;
1469 doAutoShuffleUpdate();
1470 mPlayPos = 0;
1471 openCurrent();
1472 play();
1473 notifyChange(META_CHANGED);
1474 return;
1475 } else {
1476 // failed to build a list of files to shuffle
1477 mShuffleMode = SHUFFLE_NONE;
1478 }
1479 }
1480 saveQueue(false);
1481 }
1482 }
1483 public int getShuffleMode() {
1484 return mShuffleMode;
1485 }
1486
1487 public void setRepeatMode(int repeatmode) {
1488 synchronized(this) {
1489 mRepeatMode = repeatmode;
1490 saveQueue(false);
1491 }
1492 }
1493 public int getRepeatMode() {
1494 return mRepeatMode;
1495 }
1496
1497 public int getMediaMountedCount() {
1498 return mMediaMountedCount;
1499 }
1500
1501 /**
1502 * Returns the path of the currently playing file, or null if
1503 * no file is currently playing.
1504 */
1505 public String getPath() {
1506 return mFileToPlay;
1507 }
1508
1509 /**
1510 * Returns the rowid of the currently playing file, or -1 if
1511 * no file is currently playing.
1512 */
Marco Nelissenbd447b62009-06-29 14:52:05 -07001513 public long getAudioId() {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001514 synchronized (this) {
1515 if (mPlayPos >= 0 && mPlayer.isInitialized()) {
1516 return mPlayList[mPlayPos];
1517 }
1518 }
1519 return -1;
1520 }
1521
1522 /**
1523 * Returns the position in the queue
1524 * @return the position in the queue
1525 */
1526 public int getQueuePosition() {
1527 synchronized(this) {
1528 return mPlayPos;
1529 }
1530 }
1531
1532 /**
1533 * Starts playing the track at the given position in the queue.
1534 * @param pos The position in the queue of the track that will be played.
1535 */
1536 public void setQueuePosition(int pos) {
1537 synchronized(this) {
1538 stop(false);
1539 mPlayPos = pos;
1540 openCurrent();
1541 play();
1542 notifyChange(META_CHANGED);
Marco Nelissenec0c57a2009-12-12 12:27:11 -08001543 if (mShuffleMode == SHUFFLE_AUTO) {
1544 doAutoShuffleUpdate();
1545 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08001546 }
1547 }
1548
1549 public String getArtistName() {
1550 synchronized(this) {
1551 if (mCursor == null) {
1552 return null;
1553 }
1554 return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST));
1555 }
1556 }
1557
Marco Nelissenbd447b62009-06-29 14:52:05 -07001558 public long getArtistId() {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001559 synchronized (this) {
1560 if (mCursor == null) {
1561 return -1;
1562 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07001563 return mCursor.getLong(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST_ID));
The Android Open Source Project792a2202009-03-03 19:32:30 -08001564 }
1565 }
1566
1567 public String getAlbumName() {
1568 synchronized (this) {
1569 if (mCursor == null) {
1570 return null;
1571 }
1572 return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM));
1573 }
1574 }
1575
Marco Nelissenbd447b62009-06-29 14:52:05 -07001576 public long getAlbumId() {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001577 synchronized (this) {
1578 if (mCursor == null) {
1579 return -1;
1580 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07001581 return mCursor.getLong(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM_ID));
The Android Open Source Project792a2202009-03-03 19:32:30 -08001582 }
1583 }
1584
1585 public String getTrackName() {
1586 synchronized (this) {
1587 if (mCursor == null) {
1588 return null;
1589 }
1590 return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE));
1591 }
1592 }
1593
1594 private boolean isPodcast() {
1595 synchronized (this) {
1596 if (mCursor == null) {
1597 return false;
1598 }
1599 return (mCursor.getInt(PODCASTCOLIDX) > 0);
1600 }
1601 }
1602
1603 private long getBookmark() {
1604 synchronized (this) {
1605 if (mCursor == null) {
1606 return 0;
1607 }
1608 return mCursor.getLong(BOOKMARKCOLIDX);
1609 }
1610 }
1611
1612 /**
1613 * Returns the duration of the file in milliseconds.
1614 * Currently this method returns -1 for the duration of MIDI files.
1615 */
1616 public long duration() {
1617 if (mPlayer.isInitialized()) {
1618 return mPlayer.duration();
1619 }
1620 return -1;
1621 }
1622
1623 /**
1624 * Returns the current playback position in milliseconds
1625 */
1626 public long position() {
1627 if (mPlayer.isInitialized()) {
1628 return mPlayer.position();
1629 }
1630 return -1;
1631 }
1632
1633 /**
1634 * Seeks to the position specified.
1635 *
1636 * @param pos The position to seek to, in milliseconds
1637 */
1638 public long seek(long pos) {
1639 if (mPlayer.isInitialized()) {
1640 if (pos < 0) pos = 0;
1641 if (pos > mPlayer.duration()) pos = mPlayer.duration();
1642 return mPlayer.seek(pos);
1643 }
1644 return -1;
1645 }
1646
1647 /**
1648 * Provides a unified interface for dealing with midi files and
1649 * other media files.
1650 */
1651 private class MultiPlayer {
1652 private MediaPlayer mMediaPlayer = new MediaPlayer();
1653 private Handler mHandler;
1654 private boolean mIsInitialized = false;
1655
1656 public MultiPlayer() {
1657 mMediaPlayer.setWakeMode(MediaPlaybackService.this, PowerManager.PARTIAL_WAKE_LOCK);
1658 }
1659
1660 public void setDataSourceAsync(String path) {
1661 try {
1662 mMediaPlayer.reset();
1663 mMediaPlayer.setDataSource(path);
1664 mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001665 mMediaPlayer.prepareAsync();
1666 } catch (IOException ex) {
1667 // TODO: notify the user why the file couldn't be opened
1668 mIsInitialized = false;
1669 return;
1670 } catch (IllegalArgumentException ex) {
1671 // TODO: notify the user why the file couldn't be opened
1672 mIsInitialized = false;
1673 return;
1674 }
1675 mMediaPlayer.setOnCompletionListener(listener);
1676 mMediaPlayer.setOnErrorListener(errorListener);
1677
1678 mIsInitialized = true;
1679 }
1680
1681 public void setDataSource(String path) {
1682 try {
1683 mMediaPlayer.reset();
1684 mMediaPlayer.setOnPreparedListener(null);
1685 if (path.startsWith("content://")) {
1686 mMediaPlayer.setDataSource(MediaPlaybackService.this, Uri.parse(path));
1687 } else {
1688 mMediaPlayer.setDataSource(path);
1689 }
1690 mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
1691 mMediaPlayer.prepare();
1692 } catch (IOException ex) {
1693 // TODO: notify the user why the file couldn't be opened
1694 mIsInitialized = false;
1695 return;
1696 } catch (IllegalArgumentException ex) {
1697 // TODO: notify the user why the file couldn't be opened
1698 mIsInitialized = false;
1699 return;
1700 }
1701 mMediaPlayer.setOnCompletionListener(listener);
1702 mMediaPlayer.setOnErrorListener(errorListener);
1703
1704 mIsInitialized = true;
1705 }
1706
1707 public boolean isInitialized() {
1708 return mIsInitialized;
1709 }
1710
1711 public void start() {
Marco Nelissen39888902010-03-02 10:27:05 -08001712 MusicUtils.debugLog(new Exception("MultiPlayer.start called"));
The Android Open Source Project792a2202009-03-03 19:32:30 -08001713 mMediaPlayer.start();
1714 }
1715
1716 public void stop() {
1717 mMediaPlayer.reset();
1718 mIsInitialized = false;
1719 }
1720
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001721 /**
1722 * You CANNOT use this player anymore after calling release()
1723 */
1724 public void release() {
1725 stop();
1726 mMediaPlayer.release();
1727 }
1728
The Android Open Source Project792a2202009-03-03 19:32:30 -08001729 public void pause() {
1730 mMediaPlayer.pause();
1731 }
1732
The Android Open Source Project792a2202009-03-03 19:32:30 -08001733 public void setHandler(Handler handler) {
1734 mHandler = handler;
1735 }
1736
1737 MediaPlayer.OnCompletionListener listener = new MediaPlayer.OnCompletionListener() {
1738 public void onCompletion(MediaPlayer mp) {
1739 // Acquire a temporary wakelock, since when we return from
1740 // this callback the MediaPlayer will release its wakelock
1741 // and allow the device to go to sleep.
1742 // This temporary wakelock is released when the RELEASE_WAKELOCK
1743 // message is processed, but just in case, put a timeout on it.
1744 mWakeLock.acquire(30000);
1745 mHandler.sendEmptyMessage(TRACK_ENDED);
1746 mHandler.sendEmptyMessage(RELEASE_WAKELOCK);
1747 }
1748 };
1749
The Android Open Source Project792a2202009-03-03 19:32:30 -08001750 MediaPlayer.OnErrorListener errorListener = new MediaPlayer.OnErrorListener() {
1751 public boolean onError(MediaPlayer mp, int what, int extra) {
1752 switch (what) {
1753 case MediaPlayer.MEDIA_ERROR_SERVER_DIED:
1754 mIsInitialized = false;
1755 mMediaPlayer.release();
1756 // Creating a new MediaPlayer and settings its wakemode does not
1757 // require the media service, so it's OK to do this now, while the
1758 // service is still being restarted
1759 mMediaPlayer = new MediaPlayer();
1760 mMediaPlayer.setWakeMode(MediaPlaybackService.this, PowerManager.PARTIAL_WAKE_LOCK);
1761 mHandler.sendMessageDelayed(mHandler.obtainMessage(SERVER_DIED), 2000);
1762 return true;
1763 default:
Marco Nelissene99341f2009-11-11 11:13:51 -08001764 Log.d("MultiPlayer", "Error: " + what + "," + extra);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001765 break;
1766 }
1767 return false;
1768 }
1769 };
1770
1771 public long duration() {
1772 return mMediaPlayer.getDuration();
1773 }
1774
1775 public long position() {
1776 return mMediaPlayer.getCurrentPosition();
1777 }
1778
1779 public long seek(long whereto) {
1780 mMediaPlayer.seekTo((int) whereto);
1781 return whereto;
1782 }
1783
1784 public void setVolume(float vol) {
1785 mMediaPlayer.setVolume(vol, vol);
1786 }
1787 }
1788
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001789 /*
1790 * By making this a static class with a WeakReference to the Service, we
1791 * ensure that the Service can be GCd even when the system process still
1792 * has a remote reference to the stub.
1793 */
1794 static class ServiceStub extends IMediaPlaybackService.Stub {
1795 WeakReference<MediaPlaybackService> mService;
1796
1797 ServiceStub(MediaPlaybackService service) {
1798 mService = new WeakReference<MediaPlaybackService>(service);
1799 }
1800
Marco Nelissen8d08ec22010-05-10 14:05:24 -07001801 public void openFile(String path)
The Android Open Source Project792a2202009-03-03 19:32:30 -08001802 {
Marco Nelissen8d08ec22010-05-10 14:05:24 -07001803 mService.get().open(path);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001804 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07001805 public void open(long [] list, int position) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001806 mService.get().open(list, position);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001807 }
1808 public int getQueuePosition() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001809 return mService.get().getQueuePosition();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001810 }
1811 public void setQueuePosition(int index) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001812 mService.get().setQueuePosition(index);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001813 }
1814 public boolean isPlaying() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001815 return mService.get().isPlaying();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001816 }
1817 public void stop() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001818 mService.get().stop();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001819 }
1820 public void pause() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001821 mService.get().pause();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001822 }
1823 public void play() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001824 mService.get().play();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001825 }
1826 public void prev() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001827 mService.get().prev();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001828 }
1829 public void next() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001830 mService.get().next(true);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001831 }
1832 public String getTrackName() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001833 return mService.get().getTrackName();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001834 }
1835 public String getAlbumName() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001836 return mService.get().getAlbumName();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001837 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07001838 public long getAlbumId() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001839 return mService.get().getAlbumId();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001840 }
1841 public String getArtistName() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001842 return mService.get().getArtistName();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001843 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07001844 public long getArtistId() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001845 return mService.get().getArtistId();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001846 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07001847 public void enqueue(long [] list , int action) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001848 mService.get().enqueue(list, action);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001849 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07001850 public long [] getQueue() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001851 return mService.get().getQueue();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001852 }
1853 public void moveQueueItem(int from, int to) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001854 mService.get().moveQueueItem(from, to);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001855 }
1856 public String getPath() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001857 return mService.get().getPath();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001858 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07001859 public long getAudioId() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001860 return mService.get().getAudioId();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001861 }
1862 public long position() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001863 return mService.get().position();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001864 }
1865 public long duration() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001866 return mService.get().duration();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001867 }
1868 public long seek(long pos) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001869 return mService.get().seek(pos);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001870 }
1871 public void setShuffleMode(int shufflemode) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001872 mService.get().setShuffleMode(shufflemode);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001873 }
1874 public int getShuffleMode() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001875 return mService.get().getShuffleMode();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001876 }
1877 public int removeTracks(int first, int last) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001878 return mService.get().removeTracks(first, last);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001879 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07001880 public int removeTrack(long id) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001881 return mService.get().removeTrack(id);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001882 }
1883 public void setRepeatMode(int repeatmode) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001884 mService.get().setRepeatMode(repeatmode);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001885 }
1886 public int getRepeatMode() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001887 return mService.get().getRepeatMode();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001888 }
1889 public int getMediaMountedCount() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001890 return mService.get().getMediaMountedCount();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001891 }
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001892
1893 }
Marco Nelissen39888902010-03-02 10:27:05 -08001894
1895 @Override
1896 protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
Marco Nelissenbf555ce2010-03-02 16:55:25 -08001897 writer.println("" + mPlayListLen + " items in queue, currently at index " + mPlayPos);
Marco Nelissen39888902010-03-02 10:27:05 -08001898 writer.println("Currently loaded:");
1899 writer.println(getArtistName());
1900 writer.println(getAlbumName());
1901 writer.println(getTrackName());
1902 writer.println(getPath());
1903 writer.println("playing: " + mIsSupposedToBePlaying);
1904 writer.println("actual: " + mPlayer.mMediaPlayer.isPlaying());
Marco Nelissenbf555ce2010-03-02 16:55:25 -08001905 writer.println("shuffle mode: " + mShuffleMode);
Marco Nelissen39888902010-03-02 10:27:05 -08001906 MusicUtils.debugDump(writer);
1907 }
1908
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001909 private final IBinder mBinder = new ServiceStub(this);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001910}