blob: 99b3b6897fb82701dd7e2307733cd9facd1b5555 [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;
Marco Nelissen8717d342011-09-08 09:32:51 -070035import android.graphics.Bitmap;
Marco Nelissenf2ef3b52010-09-21 15:47:27 -070036import android.media.audiofx.AudioEffect;
The Android Open Source Project792a2202009-03-03 19:32:30 -080037import android.media.AudioManager;
Jean-Michel Trivi085cbfd2010-03-08 17:06:18 -080038import android.media.AudioManager.OnAudioFocusChangeListener;
Marco Nelissen8717d342011-09-08 09:32:51 -070039import android.media.MediaMetadataRetriever;
The Android Open Source Project792a2202009-03-03 19:32:30 -080040import android.media.MediaPlayer;
Marco Nelissen30867022012-03-14 16:04:34 -070041import android.media.MediaPlayer.OnCompletionListener;
Marco Nelissen8717d342011-09-08 09:32:51 -070042import android.media.RemoteControlClient;
43import android.media.RemoteControlClient.MetadataEditor;
The Android Open Source Project792a2202009-03-03 19:32:30 -080044import android.net.Uri;
The Android Open Source Project792a2202009-03-03 19:32:30 -080045import android.os.Handler;
46import android.os.IBinder;
47import android.os.Message;
48import android.os.PowerManager;
49import android.os.SystemClock;
50import android.os.PowerManager.WakeLock;
51import android.provider.MediaStore;
52import android.util.Log;
53import android.widget.RemoteViews;
54import android.widget.Toast;
The Android Open Source Project792a2202009-03-03 19:32:30 -080055
Marco Nelissen39888902010-03-02 10:27:05 -080056import java.io.FileDescriptor;
The Android Open Source Project792a2202009-03-03 19:32:30 -080057import java.io.IOException;
Marco Nelissen39888902010-03-02 10:27:05 -080058import java.io.PrintWriter;
Marco Nelissen2b0b9132009-05-21 16:26:47 -070059import java.lang.ref.WeakReference;
The Android Open Source Project792a2202009-03-03 19:32:30 -080060import java.util.Random;
61import java.util.Vector;
62
63/**
64 * Provides "background" audio playback capabilities, allowing the
65 * user to switch between activities without stopping playback.
66 */
67public class MediaPlaybackService extends Service {
68 /** used to specify whether enqueue() should start playing
69 * the new list of files right away, next or once all the currently
70 * queued files have been played
71 */
72 public static final int NOW = 1;
73 public static final int NEXT = 2;
74 public static final int LAST = 3;
75 public static final int PLAYBACKSERVICE_STATUS = 1;
76
77 public static final int SHUFFLE_NONE = 0;
78 public static final int SHUFFLE_NORMAL = 1;
79 public static final int SHUFFLE_AUTO = 2;
80
81 public static final int REPEAT_NONE = 0;
82 public static final int REPEAT_CURRENT = 1;
83 public static final int REPEAT_ALL = 2;
84
85 public static final String PLAYSTATE_CHANGED = "com.android.music.playstatechanged";
86 public static final String META_CHANGED = "com.android.music.metachanged";
87 public static final String QUEUE_CHANGED = "com.android.music.queuechanged";
The Android Open Source Project792a2202009-03-03 19:32:30 -080088
89 public static final String SERVICECMD = "com.android.music.musicservicecommand";
90 public static final String CMDNAME = "command";
91 public static final String CMDTOGGLEPAUSE = "togglepause";
92 public static final String CMDSTOP = "stop";
93 public static final String CMDPAUSE = "pause";
Marco Nelissenfc1c0dc2010-10-25 11:08:36 -070094 public static final String CMDPLAY = "play";
The Android Open Source Project792a2202009-03-03 19:32:30 -080095 public static final String CMDPREVIOUS = "previous";
96 public static final String CMDNEXT = "next";
97
98 public static final String TOGGLEPAUSE_ACTION = "com.android.music.musicservicecommand.togglepause";
99 public static final String PAUSE_ACTION = "com.android.music.musicservicecommand.pause";
100 public static final String PREVIOUS_ACTION = "com.android.music.musicservicecommand.previous";
101 public static final String NEXT_ACTION = "com.android.music.musicservicecommand.next";
102
The Android Open Source Project792a2202009-03-03 19:32:30 -0800103 private static final int TRACK_ENDED = 1;
104 private static final int RELEASE_WAKELOCK = 2;
105 private static final int SERVER_DIED = 3;
Marco Nelissen7181da82010-12-01 16:39:04 -0800106 private static final int FOCUSCHANGE = 4;
Marco Nelissen2da473d2010-09-29 16:11:12 -0700107 private static final int FADEDOWN = 5;
108 private static final int FADEUP = 6;
Marco Nelissene41bd182012-03-14 08:24:40 -0700109 private static final int TRACK_WENT_TO_NEXT = 7;
Marco Nelissen3ec2ad92009-08-17 08:52:29 -0700110 private static final int MAX_HISTORY_SIZE = 100;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800111
112 private MultiPlayer mPlayer;
113 private String mFileToPlay;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800114 private int mShuffleMode = SHUFFLE_NONE;
115 private int mRepeatMode = REPEAT_NONE;
116 private int mMediaMountedCount = 0;
Marco Nelissenbd447b62009-06-29 14:52:05 -0700117 private long [] mAutoShuffleList = null;
Marco Nelissenbd447b62009-06-29 14:52:05 -0700118 private long [] mPlayList = null;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800119 private int mPlayListLen = 0;
120 private Vector<Integer> mHistory = new Vector<Integer>(MAX_HISTORY_SIZE);
121 private Cursor mCursor;
122 private int mPlayPos = -1;
Marco Nelissene41bd182012-03-14 08:24:40 -0700123 private int mNextPlayPos = -1;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800124 private static final String LOGTAG = "MediaPlaybackService";
125 private final Shuffler mRand = new Shuffler();
126 private int mOpenFailedCounter = 0;
127 String[] mCursorCols = new String[] {
128 "audio._id AS _id", // index must match IDCOLIDX below
129 MediaStore.Audio.Media.ARTIST,
130 MediaStore.Audio.Media.ALBUM,
131 MediaStore.Audio.Media.TITLE,
132 MediaStore.Audio.Media.DATA,
133 MediaStore.Audio.Media.MIME_TYPE,
134 MediaStore.Audio.Media.ALBUM_ID,
135 MediaStore.Audio.Media.ARTIST_ID,
136 MediaStore.Audio.Media.IS_PODCAST, // index must match PODCASTCOLIDX below
137 MediaStore.Audio.Media.BOOKMARK // index must match BOOKMARKCOLIDX below
138 };
139 private final static int IDCOLIDX = 0;
140 private final static int PODCASTCOLIDX = 8;
141 private final static int BOOKMARKCOLIDX = 9;
142 private BroadcastReceiver mUnmountReceiver = null;
143 private WakeLock mWakeLock;
144 private int mServiceStartId = -1;
145 private boolean mServiceInUse = false;
Marco Nelissenc1333372009-05-06 12:54:47 -0700146 private boolean mIsSupposedToBePlaying = false;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800147 private boolean mQuietMode = false;
Jean-Michel Trivi085cbfd2010-03-08 17:06:18 -0800148 private AudioManager mAudioManager;
Marco Nelissen8d08ec22010-05-10 14:05:24 -0700149 private boolean mQueueIsSaveable = true;
Jean-Michel Trivi085cbfd2010-03-08 17:06:18 -0800150 // used to track what type of audio focus loss caused the playback to pause
151 private boolean mPausedByTransientLossOfFocus = false;
152
The Android Open Source Project792a2202009-03-03 19:32:30 -0800153 private SharedPreferences mPreferences;
154 // We use this to distinguish between different cards when saving/restoring playlists.
155 // This will have to change if we want to support multiple simultaneous cards.
156 private int mCardId;
157
The Android Open Source Project490384b2009-03-11 12:11:59 -0700158 private MediaAppWidgetProvider mAppWidgetProvider = MediaAppWidgetProvider.getInstance();
The Android Open Source Project792a2202009-03-03 19:32:30 -0800159
160 // interval after which we stop the service when idle
Jean-Michel Triviaa331872010-04-20 17:00:26 -0700161 private static final int IDLE_DELAY = 60000;
Marco Nelissen8717d342011-09-08 09:32:51 -0700162
163 private RemoteControlClient mRemoteControlClient;
164
The Android Open Source Project792a2202009-03-03 19:32:30 -0800165 private Handler mMediaplayerHandler = new Handler() {
166 float mCurrentVolume = 1.0f;
167 @Override
168 public void handleMessage(Message msg) {
Marco Nelissen39888902010-03-02 10:27:05 -0800169 MusicUtils.debugLog("mMediaplayerHandler.handleMessage " + msg.what);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800170 switch (msg.what) {
Marco Nelissen2da473d2010-09-29 16:11:12 -0700171 case FADEDOWN:
172 mCurrentVolume -= .05f;
173 if (mCurrentVolume > .2f) {
174 mMediaplayerHandler.sendEmptyMessageDelayed(FADEDOWN, 10);
175 } else {
176 mCurrentVolume = .2f;
177 }
178 mPlayer.setVolume(mCurrentVolume);
179 break;
180 case FADEUP:
181 mCurrentVolume += .01f;
182 if (mCurrentVolume < 1.0f) {
183 mMediaplayerHandler.sendEmptyMessageDelayed(FADEUP, 10);
184 } else {
185 mCurrentVolume = 1.0f;
186 }
187 mPlayer.setVolume(mCurrentVolume);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800188 break;
189 case SERVER_DIED:
Marco Nelissenc1333372009-05-06 12:54:47 -0700190 if (mIsSupposedToBePlaying) {
Marco Nelissenbd0d2f12012-02-27 16:02:15 -0800191 gotoNext(true);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800192 } else {
193 // the server died when we were idle, so just
194 // reopen the same song (it will start again
195 // from the beginning though when the user
196 // restarts)
Marco Nelissene41bd182012-03-14 08:24:40 -0700197 openCurrentAndNext();
The Android Open Source Project792a2202009-03-03 19:32:30 -0800198 }
199 break;
Marco Nelissene41bd182012-03-14 08:24:40 -0700200 case TRACK_WENT_TO_NEXT:
201 mPlayPos = mNextPlayPos;
202 if (mCursor != null) {
203 mCursor.close();
204 mCursor = null;
205 }
206 mCursor = getCursorForId(mPlayList[mPlayPos]);
207 notifyChange(META_CHANGED);
208 updateNotification();
209 setNextTrack();
210 break;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800211 case TRACK_ENDED:
212 if (mRepeatMode == REPEAT_CURRENT) {
213 seek(0);
214 play();
The Android Open Source Project792a2202009-03-03 19:32:30 -0800215 } else {
Marco Nelissenbd0d2f12012-02-27 16:02:15 -0800216 gotoNext(false);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800217 }
218 break;
219 case RELEASE_WAKELOCK:
220 mWakeLock.release();
221 break;
Marco Nelissen7181da82010-12-01 16:39:04 -0800222
223 case FOCUSCHANGE:
224 // This code is here so we can better synchronize it with the code that
225 // handles fade-in
226 switch (msg.arg1) {
227 case AudioManager.AUDIOFOCUS_LOSS:
228 Log.v(LOGTAG, "AudioFocus: received AUDIOFOCUS_LOSS");
229 if(isPlaying()) {
230 mPausedByTransientLossOfFocus = false;
231 }
232 pause();
233 break;
Marco Nelissen7181da82010-12-01 16:39:04 -0800234 case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
Marco Nelissen7bae28f2010-12-02 10:04:54 -0800235 mMediaplayerHandler.removeMessages(FADEUP);
236 mMediaplayerHandler.sendEmptyMessage(FADEDOWN);
237 break;
238 case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
Marco Nelissen7181da82010-12-01 16:39:04 -0800239 Log.v(LOGTAG, "AudioFocus: received AUDIOFOCUS_LOSS_TRANSIENT");
240 if(isPlaying()) {
241 mPausedByTransientLossOfFocus = true;
242 }
243 pause();
244 break;
245 case AudioManager.AUDIOFOCUS_GAIN:
246 Log.v(LOGTAG, "AudioFocus: received AUDIOFOCUS_GAIN");
247 if(!isPlaying() && mPausedByTransientLossOfFocus) {
248 mPausedByTransientLossOfFocus = false;
249 mCurrentVolume = 0f;
250 mPlayer.setVolume(mCurrentVolume);
251 play(); // also queues a fade-in
252 } else {
Marco Nelissen7bae28f2010-12-02 10:04:54 -0800253 mMediaplayerHandler.removeMessages(FADEDOWN);
Marco Nelissen7181da82010-12-01 16:39:04 -0800254 mMediaplayerHandler.sendEmptyMessage(FADEUP);
255 }
256 break;
257 default:
258 Log.e(LOGTAG, "Unknown audio focus change code");
259 }
260 break;
261
The Android Open Source Project792a2202009-03-03 19:32:30 -0800262 default:
263 break;
264 }
265 }
266 };
267
268 private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
269 @Override
270 public void onReceive(Context context, Intent intent) {
271 String action = intent.getAction();
272 String cmd = intent.getStringExtra("command");
Marco Nelissen39888902010-03-02 10:27:05 -0800273 MusicUtils.debugLog("mIntentReceiver.onReceive " + action + " / " + cmd);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800274 if (CMDNEXT.equals(cmd) || NEXT_ACTION.equals(action)) {
Marco Nelissenbd0d2f12012-02-27 16:02:15 -0800275 gotoNext(true);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800276 } else if (CMDPREVIOUS.equals(cmd) || PREVIOUS_ACTION.equals(action)) {
277 prev();
278 } else if (CMDTOGGLEPAUSE.equals(cmd) || TOGGLEPAUSE_ACTION.equals(action)) {
279 if (isPlaying()) {
280 pause();
Jean-Michel Triviaa331872010-04-20 17:00:26 -0700281 mPausedByTransientLossOfFocus = false;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800282 } else {
283 play();
284 }
285 } else if (CMDPAUSE.equals(cmd) || PAUSE_ACTION.equals(action)) {
286 pause();
Jean-Michel Triviaa331872010-04-20 17:00:26 -0700287 mPausedByTransientLossOfFocus = false;
Marco Nelissenfc1c0dc2010-10-25 11:08:36 -0700288 } else if (CMDPLAY.equals(cmd)) {
289 play();
The Android Open Source Project792a2202009-03-03 19:32:30 -0800290 } else if (CMDSTOP.equals(cmd)) {
291 pause();
Jean-Michel Triviaa331872010-04-20 17:00:26 -0700292 mPausedByTransientLossOfFocus = false;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800293 seek(0);
The Android Open Source Project490384b2009-03-11 12:11:59 -0700294 } else if (MediaAppWidgetProvider.CMDAPPWIDGETUPDATE.equals(cmd)) {
295 // Someone asked us to refresh a set of specific widgets, probably
The Android Open Source Project792a2202009-03-03 19:32:30 -0800296 // because they were just added.
The Android Open Source Project490384b2009-03-11 12:11:59 -0700297 int[] appWidgetIds = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS);
298 mAppWidgetProvider.performUpdate(MediaPlaybackService.this, appWidgetIds);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800299 }
300 }
301 };
302
Jean-Michel Trivi085cbfd2010-03-08 17:06:18 -0800303 private OnAudioFocusChangeListener mAudioFocusListener = new OnAudioFocusChangeListener() {
Jean-Michel Trivif4cfdfd2010-03-31 12:12:23 -0700304 public void onAudioFocusChange(int focusChange) {
Marco Nelissen7181da82010-12-01 16:39:04 -0800305 mMediaplayerHandler.obtainMessage(FOCUSCHANGE, focusChange, 0).sendToTarget();
Jean-Michel Trivi085cbfd2010-03-08 17:06:18 -0800306 }
307 };
308
The Android Open Source Project792a2202009-03-03 19:32:30 -0800309 public MediaPlaybackService() {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800310 }
311
312 @Override
313 public void onCreate() {
314 super.onCreate();
Jean-Michel Trivi085cbfd2010-03-08 17:06:18 -0800315
316 mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
Marco Nelissen8717d342011-09-08 09:32:51 -0700317 ComponentName rec = new ComponentName(getPackageName(),
318 MediaButtonIntentReceiver.class.getName());
319 mAudioManager.registerMediaButtonEventReceiver(rec);
Mike Lockwood8ef36792013-08-01 10:03:03 -0700320
321 Intent i = new Intent(Intent.ACTION_MEDIA_BUTTON);
322 i.setComponent(rec);
323 PendingIntent pi = PendingIntent.getBroadcast(this /*context*/,
324 0 /*requestCode, ignored*/, i /*intent*/, 0 /*flags*/);
325 mRemoteControlClient = new RemoteControlClient(pi);
326 mAudioManager.registerRemoteControlClient(mRemoteControlClient);
327
328 int flags = RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS
329 | RemoteControlClient.FLAG_KEY_MEDIA_NEXT
330 | RemoteControlClient.FLAG_KEY_MEDIA_PLAY
331 | RemoteControlClient.FLAG_KEY_MEDIA_PAUSE
332 | RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE
333 | RemoteControlClient.FLAG_KEY_MEDIA_STOP;
334 mRemoteControlClient.setTransportControlFlags(flags);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800335
336 mPreferences = getSharedPreferences("Music", MODE_WORLD_READABLE | MODE_WORLD_WRITEABLE);
Marco Nelissen87bbf3c2009-12-23 16:18:45 -0800337 mCardId = MusicUtils.getCardId(this);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800338
339 registerExternalStorageListener();
340
341 // Needs to be done in this thread, since otherwise ApplicationContext.getPowerManager() crashes.
342 mPlayer = new MultiPlayer();
343 mPlayer.setHandler(mMediaplayerHandler);
344
The Android Open Source Project792a2202009-03-03 19:32:30 -0800345 reloadQueue();
Marco Nelissen8717d342011-09-08 09:32:51 -0700346 notifyChange(QUEUE_CHANGED);
347 notifyChange(META_CHANGED);
348
The Android Open Source Project792a2202009-03-03 19:32:30 -0800349 IntentFilter commandFilter = new IntentFilter();
350 commandFilter.addAction(SERVICECMD);
351 commandFilter.addAction(TOGGLEPAUSE_ACTION);
352 commandFilter.addAction(PAUSE_ACTION);
353 commandFilter.addAction(NEXT_ACTION);
354 commandFilter.addAction(PREVIOUS_ACTION);
355 registerReceiver(mIntentReceiver, commandFilter);
356
The Android Open Source Project792a2202009-03-03 19:32:30 -0800357 PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
358 mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, this.getClass().getName());
359 mWakeLock.setReferenceCounted(false);
360
361 // If the service was idle, but got killed before it stopped itself, the
362 // system will relaunch it. Make sure it gets stopped again in that case.
363 Message msg = mDelayedStopHandler.obtainMessage();
364 mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
365 }
366
367 @Override
368 public void onDestroy() {
369 // Check that we're not being destroyed while something is still playing.
370 if (isPlaying()) {
Marco Nelissene99341f2009-11-11 11:13:51 -0800371 Log.e(LOGTAG, "Service being destroyed while still playing.");
The Android Open Source Project792a2202009-03-03 19:32:30 -0800372 }
Marco Nelissen2b0b9132009-05-21 16:26:47 -0700373 // release all MediaPlayer resources, including the native player and wakelocks
Marco Nelissenf2ef3b52010-09-21 15:47:27 -0700374 Intent i = new Intent(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION);
375 i.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, getAudioSessionId());
376 i.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, getPackageName());
377 sendBroadcast(i);
Marco Nelissen2b0b9132009-05-21 16:26:47 -0700378 mPlayer.release();
379 mPlayer = null;
Jean-Michel Trivi085cbfd2010-03-08 17:06:18 -0800380
381 mAudioManager.abandonAudioFocus(mAudioFocusListener);
Mike Lockwood8ef36792013-08-01 10:03:03 -0700382 mAudioManager.unregisterRemoteControlClient(mRemoteControlClient);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800383
Marco Nelissen2b0b9132009-05-21 16:26:47 -0700384 // make sure there aren't any other messages coming
385 mDelayedStopHandler.removeCallbacksAndMessages(null);
Marco Nelissen49e36ea2009-05-28 10:20:02 -0700386 mMediaplayerHandler.removeCallbacksAndMessages(null);
Marco Nelissen2b0b9132009-05-21 16:26:47 -0700387
The Android Open Source Project792a2202009-03-03 19:32:30 -0800388 if (mCursor != null) {
389 mCursor.close();
390 mCursor = null;
391 }
392
393 unregisterReceiver(mIntentReceiver);
394 if (mUnmountReceiver != null) {
395 unregisterReceiver(mUnmountReceiver);
396 mUnmountReceiver = null;
397 }
The Android Open Source Project792a2202009-03-03 19:32:30 -0800398 mWakeLock.release();
399 super.onDestroy();
400 }
401
402 private final char hexdigits [] = new char [] {
403 '0', '1', '2', '3',
404 '4', '5', '6', '7',
405 '8', '9', 'a', 'b',
406 'c', 'd', 'e', 'f'
407 };
408
409 private void saveQueue(boolean full) {
Marco Nelissen8d08ec22010-05-10 14:05:24 -0700410 if (!mQueueIsSaveable) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800411 return;
412 }
Marco Nelissen8d08ec22010-05-10 14:05:24 -0700413
The Android Open Source Project792a2202009-03-03 19:32:30 -0800414 Editor ed = mPreferences.edit();
415 //long start = System.currentTimeMillis();
416 if (full) {
417 StringBuilder q = new StringBuilder();
418
419 // The current playlist is saved as a list of "reverse hexadecimal"
420 // numbers, which we can generate faster than normal decimal or
421 // hexadecimal numbers, which in turn allows us to save the playlist
422 // more often without worrying too much about performance.
423 // (saving the full state takes about 40 ms under no-load conditions
424 // on the phone)
425 int len = mPlayListLen;
426 for (int i = 0; i < len; i++) {
Marco Nelissenbd447b62009-06-29 14:52:05 -0700427 long n = mPlayList[i];
Marco Nelissen13355022010-08-31 15:13:27 -0700428 if (n < 0) {
429 continue;
430 } else if (n == 0) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800431 q.append("0;");
432 } else {
433 while (n != 0) {
Marco Nelissenbd447b62009-06-29 14:52:05 -0700434 int digit = (int)(n & 0xf);
Marco Nelissen13355022010-08-31 15:13:27 -0700435 n >>>= 4;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800436 q.append(hexdigits[digit]);
437 }
438 q.append(";");
439 }
440 }
441 //Log.i("@@@@ service", "created queue string in " + (System.currentTimeMillis() - start) + " ms");
442 ed.putString("queue", q.toString());
443 ed.putInt("cardid", mCardId);
Marco Nelissen63dbbbb2009-08-17 14:17:27 -0700444 if (mShuffleMode != SHUFFLE_NONE) {
445 // In shuffle mode we need to save the history too
446 len = mHistory.size();
447 q.setLength(0);
448 for (int i = 0; i < len; i++) {
449 int n = mHistory.get(i);
450 if (n == 0) {
451 q.append("0;");
452 } else {
453 while (n != 0) {
454 int digit = (n & 0xf);
Marco Nelissen13355022010-08-31 15:13:27 -0700455 n >>>= 4;
Marco Nelissen63dbbbb2009-08-17 14:17:27 -0700456 q.append(hexdigits[digit]);
457 }
458 q.append(";");
459 }
460 }
461 ed.putString("history", q.toString());
462 }
The Android Open Source Project792a2202009-03-03 19:32:30 -0800463 }
464 ed.putInt("curpos", mPlayPos);
465 if (mPlayer.isInitialized()) {
466 ed.putLong("seekpos", mPlayer.position());
467 }
468 ed.putInt("repeatmode", mRepeatMode);
469 ed.putInt("shufflemode", mShuffleMode);
Brad Fitzpatrick14c3cae2010-09-10 09:38:57 -0700470 SharedPreferencesCompat.apply(ed);
471
The Android Open Source Project792a2202009-03-03 19:32:30 -0800472 //Log.i("@@@@ service", "saved state in " + (System.currentTimeMillis() - start) + " ms");
473 }
474
475 private void reloadQueue() {
476 String q = null;
477
478 boolean newstyle = false;
479 int id = mCardId;
480 if (mPreferences.contains("cardid")) {
481 newstyle = true;
482 id = mPreferences.getInt("cardid", ~mCardId);
483 }
484 if (id == mCardId) {
485 // Only restore the saved playlist if the card is still
486 // the same one as when the playlist was saved
487 q = mPreferences.getString("queue", "");
488 }
Marco Nelissen6c615a22009-06-10 12:43:25 -0700489 int qlen = q != null ? q.length() : 0;
490 if (qlen > 1) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800491 //Log.i("@@@@ service", "loaded queue: " + q);
Marco Nelissenbf0ea142009-06-05 12:46:09 -0700492 int plen = 0;
493 int n = 0;
494 int shift = 0;
495 for (int i = 0; i < qlen; i++) {
496 char c = q.charAt(i);
497 if (c == ';') {
498 ensurePlayListCapacity(plen + 1);
499 mPlayList[plen] = n;
500 plen++;
501 n = 0;
502 shift = 0;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800503 } else {
Marco Nelissenbf0ea142009-06-05 12:46:09 -0700504 if (c >= '0' && c <= '9') {
505 n += ((c - '0') << shift);
506 } else if (c >= 'a' && c <= 'f') {
507 n += ((10 + c - 'a') << shift);
508 } else {
509 // bogus playlist data
510 plen = 0;
511 break;
512 }
513 shift += 4;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800514 }
515 }
Marco Nelissenbf0ea142009-06-05 12:46:09 -0700516 mPlayListLen = plen;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800517
518 int pos = mPreferences.getInt("curpos", 0);
Marco Nelissenbf0ea142009-06-05 12:46:09 -0700519 if (pos < 0 || pos >= mPlayListLen) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800520 // The saved playlist is bogus, discard it
521 mPlayListLen = 0;
522 return;
523 }
524 mPlayPos = pos;
525
526 // When reloadQueue is called in response to a card-insertion,
527 // we might not be able to query the media provider right away.
528 // To deal with this, try querying for the current file, and if
529 // that fails, wait a while and try again. If that too fails,
530 // assume there is a problem and don't restore the state.
Marco Nelissen63dbbbb2009-08-17 14:17:27 -0700531 Cursor crsr = MusicUtils.query(this,
The Android Open Source Project792a2202009-03-03 19:32:30 -0800532 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
533 new String [] {"_id"}, "_id=" + mPlayList[mPlayPos] , null, null);
Marco Nelissen63dbbbb2009-08-17 14:17:27 -0700534 if (crsr == null || crsr.getCount() == 0) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800535 // wait a bit and try again
536 SystemClock.sleep(3000);
Marco Nelissen63dbbbb2009-08-17 14:17:27 -0700537 crsr = getContentResolver().query(
The Android Open Source Project792a2202009-03-03 19:32:30 -0800538 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
539 mCursorCols, "_id=" + mPlayList[mPlayPos] , null, null);
540 }
Marco Nelissen63dbbbb2009-08-17 14:17:27 -0700541 if (crsr != null) {
542 crsr.close();
The Android Open Source Project792a2202009-03-03 19:32:30 -0800543 }
544
545 // Make sure we don't auto-skip to the next song, since that
546 // also starts playback. What could happen in that case is:
547 // - music is paused
548 // - go to UMS and delete some files, including the currently playing one
549 // - come back from UMS
550 // (time passes)
551 // - music app is killed for some reason (out of memory)
552 // - music service is restarted, service restores state, doesn't find
553 // the "current" file, goes to the next and: playback starts on its
554 // own, potentially at some random inconvenient time.
555 mOpenFailedCounter = 20;
556 mQuietMode = true;
Marco Nelissene41bd182012-03-14 08:24:40 -0700557 openCurrentAndNext();
The Android Open Source Project792a2202009-03-03 19:32:30 -0800558 mQuietMode = false;
559 if (!mPlayer.isInitialized()) {
560 // couldn't restore the saved state
561 mPlayListLen = 0;
562 return;
563 }
564
565 long seekpos = mPreferences.getLong("seekpos", 0);
566 seek(seekpos >= 0 && seekpos < duration() ? seekpos : 0);
Marco Nelissene99341f2009-11-11 11:13:51 -0800567 Log.d(LOGTAG, "restored queue, currently at position "
568 + position() + "/" + duration()
569 + " (requested " + seekpos + ")");
The Android Open Source Project792a2202009-03-03 19:32:30 -0800570
571 int repmode = mPreferences.getInt("repeatmode", REPEAT_NONE);
572 if (repmode != REPEAT_ALL && repmode != REPEAT_CURRENT) {
573 repmode = REPEAT_NONE;
574 }
575 mRepeatMode = repmode;
576
577 int shufmode = mPreferences.getInt("shufflemode", SHUFFLE_NONE);
578 if (shufmode != SHUFFLE_AUTO && shufmode != SHUFFLE_NORMAL) {
579 shufmode = SHUFFLE_NONE;
580 }
Marco Nelissen63dbbbb2009-08-17 14:17:27 -0700581 if (shufmode != SHUFFLE_NONE) {
582 // in shuffle mode we need to restore the history too
583 q = mPreferences.getString("history", "");
584 qlen = q != null ? q.length() : 0;
585 if (qlen > 1) {
586 plen = 0;
587 n = 0;
588 shift = 0;
589 mHistory.clear();
590 for (int i = 0; i < qlen; i++) {
591 char c = q.charAt(i);
592 if (c == ';') {
593 if (n >= mPlayListLen) {
594 // bogus history data
595 mHistory.clear();
596 break;
597 }
598 mHistory.add(n);
599 n = 0;
600 shift = 0;
601 } else {
602 if (c >= '0' && c <= '9') {
603 n += ((c - '0') << shift);
604 } else if (c >= 'a' && c <= 'f') {
605 n += ((10 + c - 'a') << shift);
606 } else {
607 // bogus history data
608 mHistory.clear();
609 break;
610 }
611 shift += 4;
612 }
613 }
614 }
615 }
The Android Open Source Project792a2202009-03-03 19:32:30 -0800616 if (shufmode == SHUFFLE_AUTO) {
617 if (! makeAutoShuffleList()) {
618 shufmode = SHUFFLE_NONE;
619 }
620 }
621 mShuffleMode = shufmode;
622 }
623 }
624
625 @Override
626 public IBinder onBind(Intent intent) {
627 mDelayedStopHandler.removeCallbacksAndMessages(null);
628 mServiceInUse = true;
629 return mBinder;
630 }
631
632 @Override
633 public void onRebind(Intent intent) {
634 mDelayedStopHandler.removeCallbacksAndMessages(null);
635 mServiceInUse = true;
636 }
637
638 @Override
Marco Nelissenc1017e52009-09-24 13:21:06 -0700639 public int onStartCommand(Intent intent, int flags, int startId) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800640 mServiceStartId = startId;
641 mDelayedStopHandler.removeCallbacksAndMessages(null);
Marco Nelissenc1017e52009-09-24 13:21:06 -0700642
643 if (intent != null) {
644 String action = intent.getAction();
645 String cmd = intent.getStringExtra("command");
Marco Nelissen39888902010-03-02 10:27:05 -0800646 MusicUtils.debugLog("onStartCommand " + action + " / " + cmd);
Marco Nelissenc1017e52009-09-24 13:21:06 -0700647
648 if (CMDNEXT.equals(cmd) || NEXT_ACTION.equals(action)) {
Marco Nelissenbd0d2f12012-02-27 16:02:15 -0800649 gotoNext(true);
Marco Nelissenc1017e52009-09-24 13:21:06 -0700650 } else if (CMDPREVIOUS.equals(cmd) || PREVIOUS_ACTION.equals(action)) {
Marco Nelissenb63b5d12009-11-18 16:04:19 -0800651 if (position() < 2000) {
652 prev();
653 } else {
654 seek(0);
655 play();
656 }
Marco Nelissenc1017e52009-09-24 13:21:06 -0700657 } else if (CMDTOGGLEPAUSE.equals(cmd) || TOGGLEPAUSE_ACTION.equals(action)) {
658 if (isPlaying()) {
659 pause();
Jean-Michel Triviaa331872010-04-20 17:00:26 -0700660 mPausedByTransientLossOfFocus = false;
Marco Nelissenc1017e52009-09-24 13:21:06 -0700661 } else {
662 play();
663 }
664 } else if (CMDPAUSE.equals(cmd) || PAUSE_ACTION.equals(action)) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800665 pause();
Jean-Michel Triviaa331872010-04-20 17:00:26 -0700666 mPausedByTransientLossOfFocus = false;
Jaikumar Ganesh47dd8472010-12-16 15:51:31 -0800667 } else if (CMDPLAY.equals(cmd)) {
668 play();
Marco Nelissenc1017e52009-09-24 13:21:06 -0700669 } else if (CMDSTOP.equals(cmd)) {
670 pause();
Jean-Michel Triviaa331872010-04-20 17:00:26 -0700671 mPausedByTransientLossOfFocus = false;
Marco Nelissenc1017e52009-09-24 13:21:06 -0700672 seek(0);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800673 }
The Android Open Source Project792a2202009-03-03 19:32:30 -0800674 }
675
676 // make sure the service will shut down on its own if it was
677 // just started but not bound to and nothing is playing
678 mDelayedStopHandler.removeCallbacksAndMessages(null);
679 Message msg = mDelayedStopHandler.obtainMessage();
680 mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
Marco Nelissenc1017e52009-09-24 13:21:06 -0700681 return START_STICKY;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800682 }
683
684 @Override
685 public boolean onUnbind(Intent intent) {
686 mServiceInUse = false;
687
688 // Take a snapshot of the current playlist
689 saveQueue(true);
690
Jean-Michel Triviaa331872010-04-20 17:00:26 -0700691 if (isPlaying() || mPausedByTransientLossOfFocus) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800692 // something is currently playing, or will be playing once
Jean-Michel Triviaa331872010-04-20 17:00:26 -0700693 // 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 -0800694 return true;
695 }
696
697 // If there is a playlist but playback is paused, then wait a while
698 // before stopping the service, so that pause/resume isn't slow.
699 // Also delay stopping the service if we're transitioning between tracks.
700 if (mPlayListLen > 0 || mMediaplayerHandler.hasMessages(TRACK_ENDED)) {
701 Message msg = mDelayedStopHandler.obtainMessage();
702 mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
703 return true;
704 }
705
706 // No active playlist, OK to stop the service right now
707 stopSelf(mServiceStartId);
708 return true;
709 }
710
711 private Handler mDelayedStopHandler = new Handler() {
712 @Override
713 public void handleMessage(Message msg) {
714 // Check again to make sure nothing is playing right now
Jean-Michel Triviaa331872010-04-20 17:00:26 -0700715 if (isPlaying() || mPausedByTransientLossOfFocus || mServiceInUse
The Android Open Source Project792a2202009-03-03 19:32:30 -0800716 || mMediaplayerHandler.hasMessages(TRACK_ENDED)) {
717 return;
718 }
719 // save the queue again, because it might have changed
720 // since the user exited the music app (because of
721 // party-shuffle or because the play-position changed)
722 saveQueue(true);
723 stopSelf(mServiceStartId);
724 }
725 };
Marco Nelissenf2ef3b52010-09-21 15:47:27 -0700726
The Android Open Source Project792a2202009-03-03 19:32:30 -0800727 /**
728 * Called when we receive a ACTION_MEDIA_EJECT notification.
729 *
730 * @param storagePath path to mount point for the removed media
731 */
732 public void closeExternalStorageFiles(String storagePath) {
733 // stop playback and clean up if the SD card is going to be unmounted.
734 stop(true);
735 notifyChange(QUEUE_CHANGED);
736 notifyChange(META_CHANGED);
737 }
738
739 /**
740 * Registers an intent to listen for ACTION_MEDIA_EJECT notifications.
741 * The intent will call closeExternalStorageFiles() if the external media
742 * is going to be ejected, so applications can clean up any files they have open.
743 */
744 public void registerExternalStorageListener() {
745 if (mUnmountReceiver == null) {
746 mUnmountReceiver = new BroadcastReceiver() {
747 @Override
748 public void onReceive(Context context, Intent intent) {
749 String action = intent.getAction();
750 if (action.equals(Intent.ACTION_MEDIA_EJECT)) {
751 saveQueue(true);
Marco Nelissen8d08ec22010-05-10 14:05:24 -0700752 mQueueIsSaveable = false;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800753 closeExternalStorageFiles(intent.getData().getPath());
754 } else if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) {
755 mMediaMountedCount++;
Marco Nelissen87bbf3c2009-12-23 16:18:45 -0800756 mCardId = MusicUtils.getCardId(MediaPlaybackService.this);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800757 reloadQueue();
Marco Nelissen8d08ec22010-05-10 14:05:24 -0700758 mQueueIsSaveable = true;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800759 notifyChange(QUEUE_CHANGED);
760 notifyChange(META_CHANGED);
761 }
762 }
763 };
764 IntentFilter iFilter = new IntentFilter();
765 iFilter.addAction(Intent.ACTION_MEDIA_EJECT);
766 iFilter.addAction(Intent.ACTION_MEDIA_MOUNTED);
767 iFilter.addDataScheme("file");
768 registerReceiver(mUnmountReceiver, iFilter);
769 }
770 }
771
772 /**
773 * Notify the change-receivers that something has changed.
774 * The intent that is sent contains the following data
775 * for the currently playing track:
776 * "id" - Integer: the database row ID
777 * "artist" - String: the name of the artist
778 * "album" - String: the name of the album
779 * "track" - String: the name of the track
780 * The intent has an action that is one of
781 * "com.android.music.metachanged"
782 * "com.android.music.queuechanged",
783 * "com.android.music.playbackcomplete"
784 * "com.android.music.playstatechanged"
785 * respectively indicating that a new track has
786 * started playing, that the playback queue has
787 * changed, that playback has stopped because
788 * the last file in the list has been played,
789 * or that the play-state changed (paused/resumed).
790 */
791 private void notifyChange(String what) {
Marco Nelissen8717d342011-09-08 09:32:51 -0700792
The Android Open Source Project792a2202009-03-03 19:32:30 -0800793 Intent i = new Intent(what);
Marco Nelissenbd447b62009-06-29 14:52:05 -0700794 i.putExtra("id", Long.valueOf(getAudioId()));
The Android Open Source Project792a2202009-03-03 19:32:30 -0800795 i.putExtra("artist", getArtistName());
796 i.putExtra("album",getAlbumName());
797 i.putExtra("track", getTrackName());
Marco Nelissen6b507de2010-10-20 14:08:10 -0700798 i.putExtra("playing", isPlaying());
799 sendStickyBroadcast(i);
Marco Nelissen8717d342011-09-08 09:32:51 -0700800
801 if (what.equals(PLAYSTATE_CHANGED)) {
Mike Lockwood8ef36792013-08-01 10:03:03 -0700802 mRemoteControlClient.setPlaybackState(isPlaying() ?
803 RemoteControlClient.PLAYSTATE_PLAYING : RemoteControlClient.PLAYSTATE_PAUSED);
Marco Nelissen8717d342011-09-08 09:32:51 -0700804 } else if (what.equals(META_CHANGED)) {
Mike Lockwood8ef36792013-08-01 10:03:03 -0700805 RemoteControlClient.MetadataEditor ed = mRemoteControlClient.editMetadata(true);
806 ed.putString(MediaMetadataRetriever.METADATA_KEY_TITLE, getTrackName());
807 ed.putString(MediaMetadataRetriever.METADATA_KEY_ALBUM, getAlbumName());
808 ed.putString(MediaMetadataRetriever.METADATA_KEY_ARTIST, getArtistName());
809 ed.putLong(MediaMetadataRetriever.METADATA_KEY_DURATION, duration());
810 Bitmap b = MusicUtils.getArtwork(this, getAudioId(), getAlbumId(), false);
811 if (b != null) {
812 ed.putBitmap(MetadataEditor.BITMAP_KEY_ARTWORK, b);
813 }
814 ed.apply();
Marco Nelissen8717d342011-09-08 09:32:51 -0700815 }
816
The Android Open Source Project792a2202009-03-03 19:32:30 -0800817 if (what.equals(QUEUE_CHANGED)) {
818 saveQueue(true);
819 } else {
820 saveQueue(false);
821 }
822
The Android Open Source Project490384b2009-03-11 12:11:59 -0700823 // Share this notification directly with our widgets
824 mAppWidgetProvider.notifyChange(this, what);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800825 }
826
827 private void ensurePlayListCapacity(int size) {
828 if (mPlayList == null || size > mPlayList.length) {
829 // reallocate at 2x requested size so we don't
830 // need to grow and copy the array for every
831 // insert
Marco Nelissenbd447b62009-06-29 14:52:05 -0700832 long [] newlist = new long[size * 2];
Marco Nelissenbf0ea142009-06-05 12:46:09 -0700833 int len = mPlayList != null ? mPlayList.length : mPlayListLen;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800834 for (int i = 0; i < len; i++) {
835 newlist[i] = mPlayList[i];
836 }
837 mPlayList = newlist;
838 }
839 // FIXME: shrink the array when the needed size is much smaller
840 // than the allocated size
841 }
842
843 // insert the list of songs at the specified position in the playlist
Marco Nelissenbd447b62009-06-29 14:52:05 -0700844 private void addToPlayList(long [] list, int position) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800845 int addlen = list.length;
846 if (position < 0) { // overwrite
847 mPlayListLen = 0;
848 position = 0;
849 }
850 ensurePlayListCapacity(mPlayListLen + addlen);
851 if (position > mPlayListLen) {
852 position = mPlayListLen;
853 }
854
855 // move part of list after insertion point
856 int tailsize = mPlayListLen - position;
857 for (int i = tailsize ; i > 0 ; i--) {
858 mPlayList[position + i] = mPlayList[position + i - addlen];
859 }
860
861 // copy list into playlist
862 for (int i = 0; i < addlen; i++) {
863 mPlayList[position + i] = list[i];
864 }
865 mPlayListLen += addlen;
Marco Nelissen3aa9ad02010-09-16 13:23:11 -0700866 if (mPlayListLen == 0) {
867 mCursor.close();
868 mCursor = null;
869 notifyChange(META_CHANGED);
870 }
The Android Open Source Project792a2202009-03-03 19:32:30 -0800871 }
872
873 /**
874 * Appends a list of tracks to the current playlist.
875 * If nothing is playing currently, playback will be started at
876 * the first track.
877 * If the action is NOW, playback will switch to the first of
878 * the new tracks immediately.
879 * @param list The list of tracks to append.
880 * @param action NOW, NEXT or LAST
881 */
Marco Nelissenbd447b62009-06-29 14:52:05 -0700882 public void enqueue(long [] list, int action) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800883 synchronized(this) {
884 if (action == NEXT && mPlayPos + 1 < mPlayListLen) {
885 addToPlayList(list, mPlayPos + 1);
886 notifyChange(QUEUE_CHANGED);
887 } else {
888 // action == LAST || action == NOW || mPlayPos + 1 == mPlayListLen
889 addToPlayList(list, Integer.MAX_VALUE);
890 notifyChange(QUEUE_CHANGED);
891 if (action == NOW) {
892 mPlayPos = mPlayListLen - list.length;
Marco Nelissene41bd182012-03-14 08:24:40 -0700893 openCurrentAndNext();
The Android Open Source Project792a2202009-03-03 19:32:30 -0800894 play();
895 notifyChange(META_CHANGED);
896 return;
897 }
898 }
899 if (mPlayPos < 0) {
900 mPlayPos = 0;
Marco Nelissene41bd182012-03-14 08:24:40 -0700901 openCurrentAndNext();
The Android Open Source Project792a2202009-03-03 19:32:30 -0800902 play();
903 notifyChange(META_CHANGED);
904 }
905 }
906 }
907
908 /**
909 * Replaces the current playlist with a new list,
910 * and prepares for starting playback at the specified
911 * position in the list, or a random position if the
912 * specified position is 0.
913 * @param list The new list of tracks.
914 */
Marco Nelissenbd447b62009-06-29 14:52:05 -0700915 public void open(long [] list, int position) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800916 synchronized (this) {
917 if (mShuffleMode == SHUFFLE_AUTO) {
918 mShuffleMode = SHUFFLE_NORMAL;
919 }
Marco Nelissenbd447b62009-06-29 14:52:05 -0700920 long oldId = getAudioId();
The Android Open Source Project792a2202009-03-03 19:32:30 -0800921 int listlength = list.length;
922 boolean newlist = true;
923 if (mPlayListLen == listlength) {
924 // possible fast path: list might be the same
925 newlist = false;
926 for (int i = 0; i < listlength; i++) {
927 if (list[i] != mPlayList[i]) {
928 newlist = true;
929 break;
930 }
931 }
932 }
933 if (newlist) {
934 addToPlayList(list, -1);
935 notifyChange(QUEUE_CHANGED);
936 }
937 int oldpos = mPlayPos;
938 if (position >= 0) {
939 mPlayPos = position;
940 } else {
941 mPlayPos = mRand.nextInt(mPlayListLen);
942 }
943 mHistory.clear();
944
945 saveBookmarkIfNeeded();
Marco Nelissene41bd182012-03-14 08:24:40 -0700946 openCurrentAndNext();
Jeffrey Sharkeyd8c69672009-03-24 17:59:05 -0700947 if (oldId != getAudioId()) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800948 notifyChange(META_CHANGED);
949 }
950 }
951 }
952
953 /**
954 * Moves the item at index1 to index2.
955 * @param index1
956 * @param index2
957 */
958 public void moveQueueItem(int index1, int index2) {
959 synchronized (this) {
960 if (index1 >= mPlayListLen) {
961 index1 = mPlayListLen - 1;
962 }
963 if (index2 >= mPlayListLen) {
964 index2 = mPlayListLen - 1;
965 }
966 if (index1 < index2) {
Marco Nelissenbd447b62009-06-29 14:52:05 -0700967 long tmp = mPlayList[index1];
The Android Open Source Project792a2202009-03-03 19:32:30 -0800968 for (int i = index1; i < index2; i++) {
969 mPlayList[i] = mPlayList[i+1];
970 }
971 mPlayList[index2] = tmp;
972 if (mPlayPos == index1) {
973 mPlayPos = index2;
974 } else if (mPlayPos >= index1 && mPlayPos <= index2) {
975 mPlayPos--;
976 }
977 } else if (index2 < index1) {
Marco Nelissenbd447b62009-06-29 14:52:05 -0700978 long tmp = mPlayList[index1];
The Android Open Source Project792a2202009-03-03 19:32:30 -0800979 for (int i = index1; i > index2; i--) {
980 mPlayList[i] = mPlayList[i-1];
981 }
982 mPlayList[index2] = tmp;
983 if (mPlayPos == index1) {
984 mPlayPos = index2;
985 } else if (mPlayPos >= index2 && mPlayPos <= index1) {
986 mPlayPos++;
987 }
988 }
989 notifyChange(QUEUE_CHANGED);
990 }
991 }
992
993 /**
994 * Returns the current play list
995 * @return An array of integers containing the IDs of the tracks in the play list
996 */
Marco Nelissenbd447b62009-06-29 14:52:05 -0700997 public long [] getQueue() {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800998 synchronized (this) {
999 int len = mPlayListLen;
Marco Nelissenbd447b62009-06-29 14:52:05 -07001000 long [] list = new long[len];
The Android Open Source Project792a2202009-03-03 19:32:30 -08001001 for (int i = 0; i < len; i++) {
1002 list[i] = mPlayList[i];
1003 }
1004 return list;
1005 }
1006 }
1007
Marco Nelissene41bd182012-03-14 08:24:40 -07001008 private Cursor getCursorForId(long lid) {
1009 String id = String.valueOf(lid);
1010
1011 Cursor c = getContentResolver().query(
1012 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
1013 mCursorCols, "_id=" + id , null, null);
Marco Nelissenc37b2002012-10-16 12:59:54 -07001014 if (c != null) {
1015 c.moveToFirst();
1016 }
Marco Nelissene41bd182012-03-14 08:24:40 -07001017 return c;
1018 }
1019
1020 private void openCurrentAndNext() {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001021 synchronized (this) {
1022 if (mCursor != null) {
1023 mCursor.close();
1024 mCursor = null;
1025 }
Marco Nelissen8d08ec22010-05-10 14:05:24 -07001026
The Android Open Source Project792a2202009-03-03 19:32:30 -08001027 if (mPlayListLen == 0) {
1028 return;
1029 }
1030 stop(false);
1031
Marco Nelissen90d1f622012-04-05 12:27:56 -07001032 mCursor = getCursorForId(mPlayList[mPlayPos]);
Marco Nelissenc37b2002012-10-16 12:59:54 -07001033 while(true) {
1034 if (mCursor != null && mCursor.getCount() != 0 &&
1035 open(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + "/" +
1036 mCursor.getLong(IDCOLIDX))) {
1037 break;
Marco Nelissen6fb85512012-07-18 07:46:21 -07001038 }
Marco Nelissenc37b2002012-10-16 12:59:54 -07001039 // if we get here then opening the file failed. We can close the cursor now, because
1040 // we're either going to create a new one next, or stop trying
1041 if (mCursor != null) {
1042 mCursor.close();
1043 mCursor = null;
1044 }
Marco Nelissen90d1f622012-04-05 12:27:56 -07001045 if (mOpenFailedCounter++ < 10 && mPlayListLen > 1) {
1046 int pos = getNextPosition(false);
1047 if (pos < 0) {
1048 gotoIdleState();
1049 if (mIsSupposedToBePlaying) {
1050 mIsSupposedToBePlaying = false;
1051 notifyChange(PLAYSTATE_CHANGED);
1052 }
1053 return;
1054 }
1055 mPlayPos = pos;
1056 stop(false);
1057 mPlayPos = pos;
1058 mCursor = getCursorForId(mPlayList[mPlayPos]);
1059 } else {
1060 mOpenFailedCounter = 0;
1061 if (!mQuietMode) {
1062 Toast.makeText(this, R.string.playback_failed, Toast.LENGTH_SHORT).show();
1063 }
1064 Log.d(LOGTAG, "Failed to open file for playback");
Marco Nelissenc37b2002012-10-16 12:59:54 -07001065 gotoIdleState();
1066 if (mIsSupposedToBePlaying) {
1067 mIsSupposedToBePlaying = false;
1068 notifyChange(PLAYSTATE_CHANGED);
1069 }
Marco Nelissen90d1f622012-04-05 12:27:56 -07001070 return;
Marco Nelissene41bd182012-03-14 08:24:40 -07001071 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08001072 }
Marco Nelissen90d1f622012-04-05 12:27:56 -07001073
1074 // go to bookmark if needed
1075 if (isPodcast()) {
1076 long bookmark = getBookmark();
1077 // Start playing a little bit before the bookmark,
1078 // so it's easier to get back in to the narrative.
1079 seek(bookmark - 5000);
1080 }
1081 setNextTrack();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001082 }
1083 }
1084
Marco Nelissene41bd182012-03-14 08:24:40 -07001085 private void setNextTrack() {
1086 mNextPlayPos = getNextPosition(false);
1087 if (mNextPlayPos >= 0) {
1088 long id = mPlayList[mNextPlayPos];
1089 mPlayer.setNextDataSource(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + "/" + id);
1090 }
1091 }
1092
The Android Open Source Project792a2202009-03-03 19:32:30 -08001093 /**
1094 * Opens the specified file and readies it for playback.
1095 *
1096 * @param path The full path of the file to be opened.
The Android Open Source Project792a2202009-03-03 19:32:30 -08001097 */
Marco Nelissen90d1f622012-04-05 12:27:56 -07001098 public boolean open(String path) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001099 synchronized (this) {
1100 if (path == null) {
Marco Nelissen90d1f622012-04-05 12:27:56 -07001101 return false;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001102 }
1103
The Android Open Source Project792a2202009-03-03 19:32:30 -08001104 // if mCursor is null, try to associate path with a database cursor
1105 if (mCursor == null) {
1106
1107 ContentResolver resolver = getContentResolver();
1108 Uri uri;
1109 String where;
1110 String selectionArgs[];
1111 if (path.startsWith("content://media/")) {
1112 uri = Uri.parse(path);
1113 where = null;
1114 selectionArgs = null;
1115 } else {
1116 uri = MediaStore.Audio.Media.getContentUriForPath(path);
1117 where = MediaStore.Audio.Media.DATA + "=?";
1118 selectionArgs = new String[] { path };
1119 }
1120
1121 try {
1122 mCursor = resolver.query(uri, mCursorCols, where, selectionArgs, null);
1123 if (mCursor != null) {
1124 if (mCursor.getCount() == 0) {
1125 mCursor.close();
1126 mCursor = null;
1127 } else {
1128 mCursor.moveToNext();
1129 ensurePlayListCapacity(1);
1130 mPlayListLen = 1;
Marco Nelissenbd447b62009-06-29 14:52:05 -07001131 mPlayList[0] = mCursor.getLong(IDCOLIDX);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001132 mPlayPos = 0;
1133 }
1134 }
1135 } catch (UnsupportedOperationException ex) {
1136 }
1137 }
1138 mFileToPlay = path;
1139 mPlayer.setDataSource(mFileToPlay);
Marco Nelissen90d1f622012-04-05 12:27:56 -07001140 if (mPlayer.isInitialized()) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001141 mOpenFailedCounter = 0;
Marco Nelissen90d1f622012-04-05 12:27:56 -07001142 return true;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001143 }
Marco Nelissen90d1f622012-04-05 12:27:56 -07001144 stop(true);
1145 return false;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001146 }
1147 }
1148
1149 /**
1150 * Starts playback of a previously opened file.
1151 */
1152 public void play() {
Jean-Michel Trivi085cbfd2010-03-08 17:06:18 -08001153 mAudioManager.requestAudioFocus(mAudioFocusListener, AudioManager.STREAM_MUSIC,
1154 AudioManager.AUDIOFOCUS_GAIN);
Jean-Michel Trivi3d22fc22010-03-17 11:46:58 -07001155 mAudioManager.registerMediaButtonEventReceiver(new ComponentName(this.getPackageName(),
1156 MediaButtonIntentReceiver.class.getName()));
Jean-Michel Trivi085cbfd2010-03-08 17:06:18 -08001157
The Android Open Source Project792a2202009-03-03 19:32:30 -08001158 if (mPlayer.isInitialized()) {
Thomas Tuttle272eb782009-01-28 21:06:46 -05001159 // if we are at the end of the song, go to the next song first
Marco Nelissen2f9a1ce2009-06-26 14:23:31 -07001160 long duration = mPlayer.duration();
1161 if (mRepeatMode != REPEAT_CURRENT && duration > 2000 &&
1162 mPlayer.position() >= duration - 2000) {
Marco Nelissenbd0d2f12012-02-27 16:02:15 -08001163 gotoNext(true);
Thomas Tuttle272eb782009-01-28 21:06:46 -05001164 }
1165
The Android Open Source Project792a2202009-03-03 19:32:30 -08001166 mPlayer.start();
Marco Nelissen7181da82010-12-01 16:39:04 -08001167 // make sure we fade in, in case a previous fadein was stopped because
1168 // of another focus loss
Marco Nelissen7bae28f2010-12-02 10:04:54 -08001169 mMediaplayerHandler.removeMessages(FADEDOWN);
Marco Nelissen7181da82010-12-01 16:39:04 -08001170 mMediaplayerHandler.sendEmptyMessage(FADEUP);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001171
Marco Nelissene41bd182012-03-14 08:24:40 -07001172 updateNotification();
Marco Nelissenc1333372009-05-06 12:54:47 -07001173 if (!mIsSupposedToBePlaying) {
Mike Cleron347fe572009-10-09 12:26:28 -07001174 mIsSupposedToBePlaying = true;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001175 notifyChange(PLAYSTATE_CHANGED);
1176 }
Mike Cleron347fe572009-10-09 12:26:28 -07001177
The Android Open Source Project792a2202009-03-03 19:32:30 -08001178 } else if (mPlayListLen <= 0) {
1179 // This is mostly so that if you press 'play' on a bluetooth headset
1180 // without every having played anything before, it will still play
1181 // something.
1182 setShuffleMode(SHUFFLE_AUTO);
1183 }
1184 }
Marco Nelissene41bd182012-03-14 08:24:40 -07001185
1186 private void updateNotification() {
1187 RemoteViews views = new RemoteViews(getPackageName(), R.layout.statusbar);
1188 views.setImageViewResource(R.id.icon, R.drawable.stat_notify_musicplayer);
1189 if (getAudioId() < 0) {
1190 // streaming
1191 views.setTextViewText(R.id.trackname, getPath());
1192 views.setTextViewText(R.id.artistalbum, null);
1193 } else {
1194 String artist = getArtistName();
1195 views.setTextViewText(R.id.trackname, getTrackName());
1196 if (artist == null || artist.equals(MediaStore.UNKNOWN_STRING)) {
1197 artist = getString(R.string.unknown_artist_name);
1198 }
1199 String album = getAlbumName();
1200 if (album == null || album.equals(MediaStore.UNKNOWN_STRING)) {
1201 album = getString(R.string.unknown_album_name);
1202 }
1203
1204 views.setTextViewText(R.id.artistalbum,
1205 getString(R.string.notification_artist_album, artist, album)
1206 );
1207 }
1208 Notification status = new Notification();
1209 status.contentView = views;
1210 status.flags |= Notification.FLAG_ONGOING_EVENT;
1211 status.icon = R.drawable.stat_notify_musicplayer;
1212 status.contentIntent = PendingIntent.getActivity(this, 0,
1213 new Intent("com.android.music.PLAYBACK_VIEWER")
1214 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK), 0);
1215 startForeground(PLAYBACKSERVICE_STATUS, status);
1216 }
1217
The Android Open Source Project792a2202009-03-03 19:32:30 -08001218 private void stop(boolean remove_status_icon) {
Glenn Kastenb1c285c2012-11-07 15:44:01 -08001219 if (mPlayer != null && mPlayer.isInitialized()) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001220 mPlayer.stop();
1221 }
1222 mFileToPlay = null;
1223 if (mCursor != null) {
1224 mCursor.close();
1225 mCursor = null;
1226 }
1227 if (remove_status_icon) {
1228 gotoIdleState();
Dianne Hackbornd5fc5b62009-08-18 11:35:30 -07001229 } else {
1230 stopForeground(false);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001231 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08001232 if (remove_status_icon) {
Marco Nelissenc1333372009-05-06 12:54:47 -07001233 mIsSupposedToBePlaying = false;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001234 }
1235 }
1236
1237 /**
1238 * Stops playback.
1239 */
1240 public void stop() {
1241 stop(true);
1242 }
1243
1244 /**
1245 * Pauses playback (call play() to resume)
1246 */
1247 public void pause() {
Marco Nelissen407cf912009-05-11 09:56:31 -07001248 synchronized(this) {
Marco Nelissen7181da82010-12-01 16:39:04 -08001249 mMediaplayerHandler.removeMessages(FADEUP);
Marco Nelissen407cf912009-05-11 09:56:31 -07001250 if (isPlaying()) {
1251 mPlayer.pause();
1252 gotoIdleState();
Marco Nelissen407cf912009-05-11 09:56:31 -07001253 mIsSupposedToBePlaying = false;
1254 notifyChange(PLAYSTATE_CHANGED);
1255 saveBookmarkIfNeeded();
1256 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08001257 }
1258 }
1259
Marco Nelissenb6e7bf72009-05-11 10:28:31 -07001260 /** Returns whether something is currently playing
The Android Open Source Project792a2202009-03-03 19:32:30 -08001261 *
Marco Nelissenb6e7bf72009-05-11 10:28:31 -07001262 * @return true if something is playing (or will be playing shortly, in case
1263 * we're currently transitioning between tracks), false if not.
The Android Open Source Project792a2202009-03-03 19:32:30 -08001264 */
1265 public boolean isPlaying() {
Marco Nelissenc1333372009-05-06 12:54:47 -07001266 return mIsSupposedToBePlaying;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001267 }
1268
1269 /*
1270 Desired behavior for prev/next/shuffle:
1271
1272 - NEXT will move to the next track in the list when not shuffling, and to
1273 a track randomly picked from the not-yet-played tracks when shuffling.
1274 If all tracks have already been played, pick from the full set, but
1275 avoid picking the previously played track if possible.
1276 - when shuffling, PREV will go to the previously played track. Hitting PREV
1277 again will go to the track played before that, etc. When the start of the
1278 history has been reached, PREV is a no-op.
1279 When not shuffling, PREV will go to the sequentially previous track (the
1280 difference with the shuffle-case is mainly that when not shuffling, the
1281 user can back up to tracks that are not in the history).
1282
1283 Example:
1284 When playing an album with 10 tracks from the start, and enabling shuffle
1285 while playing track 5, the remaining tracks (6-10) will be shuffled, e.g.
1286 the final play order might be 1-2-3-4-5-8-10-6-9-7.
1287 When hitting 'prev' 8 times while playing track 7 in this example, the
1288 user will go to tracks 9-6-10-8-5-4-3-2. If the user then hits 'next',
1289 a random track will be picked again. If at any time user disables shuffling
1290 the next/previous track will be picked in sequential order again.
1291 */
1292
1293 public void prev() {
1294 synchronized (this) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001295 if (mShuffleMode == SHUFFLE_NORMAL) {
1296 // go to previously-played track and remove it from the history
1297 int histsize = mHistory.size();
1298 if (histsize == 0) {
1299 // prev is a no-op
1300 return;
1301 }
1302 Integer pos = mHistory.remove(histsize - 1);
1303 mPlayPos = pos.intValue();
1304 } else {
1305 if (mPlayPos > 0) {
1306 mPlayPos--;
1307 } else {
1308 mPlayPos = mPlayListLen - 1;
1309 }
1310 }
1311 saveBookmarkIfNeeded();
1312 stop(false);
Marco Nelissene41bd182012-03-14 08:24:40 -07001313 openCurrentAndNext();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001314 play();
1315 notifyChange(META_CHANGED);
1316 }
1317 }
1318
Marco Nelissenbd0d2f12012-02-27 16:02:15 -08001319 /**
1320 * Get the next position to play. Note that this may actually modify mPlayPos
1321 * if playback is in SHUFFLE_AUTO mode and the shuffle list window needed to
1322 * be adjusted. Either way, the return value is the next value that should be
1323 * assigned to mPlayPos;
1324 */
1325 private int getNextPosition(boolean force) {
Marco Nelissene41bd182012-03-14 08:24:40 -07001326 if (mRepeatMode == REPEAT_CURRENT) {
1327 if (mPlayPos < 0) return 0;
1328 return mPlayPos;
1329 } else if (mShuffleMode == SHUFFLE_NORMAL) {
Marco Nelissenbd0d2f12012-02-27 16:02:15 -08001330 // Pick random next track from the not-yet-played ones
1331 // TODO: make it work right after adding/removing items in the queue.
1332
1333 // Store the current file in the history, but keep the history at a
1334 // reasonable size
1335 if (mPlayPos >= 0) {
1336 mHistory.add(mPlayPos);
1337 }
1338 if (mHistory.size() > MAX_HISTORY_SIZE) {
1339 mHistory.removeElementAt(0);
1340 }
1341
1342 int numTracks = mPlayListLen;
1343 int[] tracks = new int[numTracks];
1344 for (int i=0;i < numTracks; i++) {
1345 tracks[i] = i;
1346 }
1347
1348 int numHistory = mHistory.size();
1349 int numUnplayed = numTracks;
1350 for (int i=0;i < numHistory; i++) {
1351 int idx = mHistory.get(i).intValue();
1352 if (idx < numTracks && tracks[idx] >= 0) {
1353 numUnplayed--;
1354 tracks[idx] = -1;
1355 }
1356 }
1357
1358 // 'numUnplayed' now indicates how many tracks have not yet
1359 // been played, and 'tracks' contains the indices of those
1360 // tracks.
1361 if (numUnplayed <=0) {
1362 // everything's already been played
1363 if (mRepeatMode == REPEAT_ALL || force) {
1364 //pick from full set
1365 numUnplayed = numTracks;
1366 for (int i=0;i < numTracks; i++) {
1367 tracks[i] = i;
1368 }
1369 } else {
1370 // all done
1371 return -1;
1372 }
1373 }
1374 int skip = mRand.nextInt(numUnplayed);
1375 int cnt = -1;
1376 while (true) {
1377 while (tracks[++cnt] < 0)
1378 ;
1379 skip--;
1380 if (skip < 0) {
1381 break;
1382 }
1383 }
1384 return cnt;
1385 } else if (mShuffleMode == SHUFFLE_AUTO) {
1386 doAutoShuffleUpdate();
1387 return mPlayPos + 1;
1388 } else {
1389 if (mPlayPos >= mPlayListLen - 1) {
1390 // we're at the end of the list
1391 if (mRepeatMode == REPEAT_NONE && !force) {
1392 // all done
1393 return -1;
1394 } else if (mRepeatMode == REPEAT_ALL || force) {
1395 return 0;
1396 }
1397 return -1;
1398 } else {
1399 return mPlayPos + 1;
1400 }
1401 }
1402 }
1403
1404 public void gotoNext(boolean force) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001405 synchronized (this) {
Marco Nelissen663fea32009-06-12 13:39:27 -07001406 if (mPlayListLen <= 0) {
Marco Nelissene99341f2009-11-11 11:13:51 -08001407 Log.d(LOGTAG, "No play queue");
Marco Nelissen663fea32009-06-12 13:39:27 -07001408 return;
1409 }
1410
Marco Nelissenbd0d2f12012-02-27 16:02:15 -08001411 int pos = getNextPosition(force);
1412 if (pos < 0) {
1413 gotoIdleState();
1414 if (mIsSupposedToBePlaying) {
1415 mIsSupposedToBePlaying = false;
1416 notifyChange(PLAYSTATE_CHANGED);
Marco Nelissen3f502de2010-09-28 15:07:29 -07001417 }
Marco Nelissenbd0d2f12012-02-27 16:02:15 -08001418 return;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001419 }
Marco Nelissenbd0d2f12012-02-27 16:02:15 -08001420 mPlayPos = pos;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001421 saveBookmarkIfNeeded();
1422 stop(false);
Marco Nelissene41bd182012-03-14 08:24:40 -07001423 mPlayPos = pos;
1424 openCurrentAndNext();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001425 play();
1426 notifyChange(META_CHANGED);
1427 }
1428 }
1429
1430 private void gotoIdleState() {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001431 mDelayedStopHandler.removeCallbacksAndMessages(null);
1432 Message msg = mDelayedStopHandler.obtainMessage();
1433 mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
Dianne Hackbornd5fc5b62009-08-18 11:35:30 -07001434 stopForeground(true);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001435 }
1436
1437 private void saveBookmarkIfNeeded() {
1438 try {
1439 if (isPodcast()) {
1440 long pos = position();
1441 long bookmark = getBookmark();
1442 long duration = duration();
1443 if ((pos < bookmark && (pos + 10000) > bookmark) ||
1444 (pos > bookmark && (pos - 10000) < bookmark)) {
1445 // The existing bookmark is close to the current
1446 // position, so don't update it.
1447 return;
1448 }
1449 if (pos < 15000 || (pos + 10000) > duration) {
1450 // if we're near the start or end, clear the bookmark
1451 pos = 0;
1452 }
1453
1454 // write 'pos' to the bookmark field
1455 ContentValues values = new ContentValues();
1456 values.put(MediaStore.Audio.Media.BOOKMARK, pos);
1457 Uri uri = ContentUris.withAppendedId(
1458 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, mCursor.getLong(IDCOLIDX));
1459 getContentResolver().update(uri, values, null, null);
1460 }
1461 } catch (SQLiteException ex) {
1462 }
1463 }
1464
1465 // Make sure there are at least 5 items after the currently playing item
1466 // and no more than 10 items before.
1467 private void doAutoShuffleUpdate() {
1468 boolean notify = false;
Marco Nelissen3f502de2010-09-28 15:07:29 -07001469
The Android Open Source Project792a2202009-03-03 19:32:30 -08001470 // remove old entries
1471 if (mPlayPos > 10) {
1472 removeTracks(0, mPlayPos - 9);
1473 notify = true;
1474 }
1475 // add new entries if needed
1476 int to_add = 7 - (mPlayListLen - (mPlayPos < 0 ? -1 : mPlayPos));
1477 for (int i = 0; i < to_add; i++) {
1478 // pick something at random from the list
Marco Nelissen3f502de2010-09-28 15:07:29 -07001479
1480 int lookback = mHistory.size();
1481 int idx = -1;
1482 while(true) {
1483 idx = mRand.nextInt(mAutoShuffleList.length);
1484 if (!wasRecentlyUsed(idx, lookback)) {
1485 break;
1486 }
1487 lookback /= 2;
1488 }
1489 mHistory.add(idx);
1490 if (mHistory.size() > MAX_HISTORY_SIZE) {
1491 mHistory.remove(0);
1492 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08001493 ensurePlayListCapacity(mPlayListLen + 1);
Marco Nelissen3f502de2010-09-28 15:07:29 -07001494 mPlayList[mPlayListLen++] = mAutoShuffleList[idx];
The Android Open Source Project792a2202009-03-03 19:32:30 -08001495 notify = true;
1496 }
1497 if (notify) {
1498 notifyChange(QUEUE_CHANGED);
1499 }
1500 }
1501
Marco Nelissen3f502de2010-09-28 15:07:29 -07001502 // check that the specified idx is not in the history (but only look at at
1503 // most lookbacksize entries in the history)
1504 private boolean wasRecentlyUsed(int idx, int lookbacksize) {
1505
1506 // early exit to prevent infinite loops in case idx == mPlayPos
1507 if (lookbacksize == 0) {
1508 return false;
1509 }
1510
1511 int histsize = mHistory.size();
1512 if (histsize < lookbacksize) {
1513 Log.d(LOGTAG, "lookback too big");
1514 lookbacksize = histsize;
1515 }
1516 int maxidx = histsize - 1;
1517 for (int i = 0; i < lookbacksize; i++) {
1518 long entry = mHistory.get(maxidx - i);
1519 if (entry == idx) {
1520 return true;
1521 }
1522 }
1523 return false;
1524 }
1525
The Android Open Source Project792a2202009-03-03 19:32:30 -08001526 // A simple variation of Random that makes sure that the
1527 // value it returns is not equal to the value it returned
1528 // previously, unless the interval is 1.
Marco Nelissen756c3f52009-05-14 10:07:23 -07001529 private static class Shuffler {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001530 private int mPrevious;
1531 private Random mRandom = new Random();
1532 public int nextInt(int interval) {
1533 int ret;
1534 do {
1535 ret = mRandom.nextInt(interval);
1536 } while (ret == mPrevious && interval > 1);
1537 mPrevious = ret;
1538 return ret;
1539 }
1540 };
1541
1542 private boolean makeAutoShuffleList() {
1543 ContentResolver res = getContentResolver();
1544 Cursor c = null;
1545 try {
1546 c = res.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
1547 new String[] {MediaStore.Audio.Media._ID}, MediaStore.Audio.Media.IS_MUSIC + "=1",
1548 null, null);
1549 if (c == null || c.getCount() == 0) {
1550 return false;
1551 }
1552 int len = c.getCount();
Marco Nelissenbd447b62009-06-29 14:52:05 -07001553 long [] list = new long[len];
The Android Open Source Project792a2202009-03-03 19:32:30 -08001554 for (int i = 0; i < len; i++) {
1555 c.moveToNext();
Marco Nelissenbd447b62009-06-29 14:52:05 -07001556 list[i] = c.getLong(0);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001557 }
1558 mAutoShuffleList = list;
1559 return true;
1560 } catch (RuntimeException ex) {
1561 } finally {
1562 if (c != null) {
1563 c.close();
1564 }
1565 }
1566 return false;
1567 }
1568
1569 /**
1570 * Removes the range of tracks specified from the play list. If a file within the range is
1571 * the file currently being played, playback will move to the next file after the
1572 * range.
1573 * @param first The first file to be removed
1574 * @param last The last file to be removed
1575 * @return the number of tracks deleted
1576 */
1577 public int removeTracks(int first, int last) {
1578 int numremoved = removeTracksInternal(first, last);
1579 if (numremoved > 0) {
1580 notifyChange(QUEUE_CHANGED);
1581 }
1582 return numremoved;
1583 }
1584
1585 private int removeTracksInternal(int first, int last) {
1586 synchronized (this) {
1587 if (last < first) return 0;
1588 if (first < 0) first = 0;
1589 if (last >= mPlayListLen) last = mPlayListLen - 1;
1590
1591 boolean gotonext = false;
1592 if (first <= mPlayPos && mPlayPos <= last) {
1593 mPlayPos = first;
1594 gotonext = true;
1595 } else if (mPlayPos > last) {
1596 mPlayPos -= (last - first + 1);
1597 }
1598 int num = mPlayListLen - last - 1;
1599 for (int i = 0; i < num; i++) {
1600 mPlayList[first + i] = mPlayList[last + 1 + i];
1601 }
1602 mPlayListLen -= last - first + 1;
1603
1604 if (gotonext) {
1605 if (mPlayListLen == 0) {
1606 stop(true);
1607 mPlayPos = -1;
Marco Nelissen3aa9ad02010-09-16 13:23:11 -07001608 if (mCursor != null) {
1609 mCursor.close();
1610 mCursor = null;
1611 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08001612 } else {
1613 if (mPlayPos >= mPlayListLen) {
1614 mPlayPos = 0;
1615 }
1616 boolean wasPlaying = isPlaying();
1617 stop(false);
Marco Nelissene41bd182012-03-14 08:24:40 -07001618 openCurrentAndNext();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001619 if (wasPlaying) {
1620 play();
1621 }
1622 }
Marco Nelissen3aa9ad02010-09-16 13:23:11 -07001623 notifyChange(META_CHANGED);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001624 }
1625 return last - first + 1;
1626 }
1627 }
1628
1629 /**
1630 * Removes all instances of the track with the given id
1631 * from the playlist.
1632 * @param id The id to be removed
1633 * @return how many instances of the track were removed
1634 */
Marco Nelissenbd447b62009-06-29 14:52:05 -07001635 public int removeTrack(long id) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001636 int numremoved = 0;
1637 synchronized (this) {
1638 for (int i = 0; i < mPlayListLen; i++) {
1639 if (mPlayList[i] == id) {
1640 numremoved += removeTracksInternal(i, i);
1641 i--;
1642 }
1643 }
1644 }
1645 if (numremoved > 0) {
1646 notifyChange(QUEUE_CHANGED);
1647 }
1648 return numremoved;
1649 }
1650
1651 public void setShuffleMode(int shufflemode) {
1652 synchronized(this) {
1653 if (mShuffleMode == shufflemode && mPlayListLen > 0) {
1654 return;
1655 }
1656 mShuffleMode = shufflemode;
1657 if (mShuffleMode == SHUFFLE_AUTO) {
1658 if (makeAutoShuffleList()) {
1659 mPlayListLen = 0;
1660 doAutoShuffleUpdate();
1661 mPlayPos = 0;
Marco Nelissene41bd182012-03-14 08:24:40 -07001662 openCurrentAndNext();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001663 play();
1664 notifyChange(META_CHANGED);
1665 return;
1666 } else {
1667 // failed to build a list of files to shuffle
1668 mShuffleMode = SHUFFLE_NONE;
1669 }
1670 }
1671 saveQueue(false);
1672 }
1673 }
1674 public int getShuffleMode() {
1675 return mShuffleMode;
1676 }
1677
1678 public void setRepeatMode(int repeatmode) {
1679 synchronized(this) {
1680 mRepeatMode = repeatmode;
Marco Nelissene41bd182012-03-14 08:24:40 -07001681 setNextTrack();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001682 saveQueue(false);
1683 }
1684 }
1685 public int getRepeatMode() {
1686 return mRepeatMode;
1687 }
1688
1689 public int getMediaMountedCount() {
1690 return mMediaMountedCount;
1691 }
1692
1693 /**
1694 * Returns the path of the currently playing file, or null if
1695 * no file is currently playing.
1696 */
1697 public String getPath() {
1698 return mFileToPlay;
1699 }
1700
1701 /**
1702 * Returns the rowid of the currently playing file, or -1 if
1703 * no file is currently playing.
1704 */
Marco Nelissenbd447b62009-06-29 14:52:05 -07001705 public long getAudioId() {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001706 synchronized (this) {
1707 if (mPlayPos >= 0 && mPlayer.isInitialized()) {
1708 return mPlayList[mPlayPos];
1709 }
1710 }
1711 return -1;
1712 }
1713
1714 /**
1715 * Returns the position in the queue
1716 * @return the position in the queue
1717 */
1718 public int getQueuePosition() {
1719 synchronized(this) {
1720 return mPlayPos;
1721 }
1722 }
1723
1724 /**
1725 * Starts playing the track at the given position in the queue.
1726 * @param pos The position in the queue of the track that will be played.
1727 */
1728 public void setQueuePosition(int pos) {
1729 synchronized(this) {
1730 stop(false);
1731 mPlayPos = pos;
Marco Nelissene41bd182012-03-14 08:24:40 -07001732 openCurrentAndNext();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001733 play();
1734 notifyChange(META_CHANGED);
Marco Nelissenec0c57a2009-12-12 12:27:11 -08001735 if (mShuffleMode == SHUFFLE_AUTO) {
1736 doAutoShuffleUpdate();
1737 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08001738 }
1739 }
1740
1741 public String getArtistName() {
1742 synchronized(this) {
1743 if (mCursor == null) {
1744 return null;
1745 }
1746 return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST));
1747 }
1748 }
1749
Marco Nelissenbd447b62009-06-29 14:52:05 -07001750 public long getArtistId() {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001751 synchronized (this) {
1752 if (mCursor == null) {
1753 return -1;
1754 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07001755 return mCursor.getLong(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST_ID));
The Android Open Source Project792a2202009-03-03 19:32:30 -08001756 }
1757 }
1758
1759 public String getAlbumName() {
1760 synchronized (this) {
1761 if (mCursor == null) {
1762 return null;
1763 }
1764 return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM));
1765 }
1766 }
1767
Marco Nelissenbd447b62009-06-29 14:52:05 -07001768 public long getAlbumId() {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001769 synchronized (this) {
1770 if (mCursor == null) {
1771 return -1;
1772 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07001773 return mCursor.getLong(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM_ID));
The Android Open Source Project792a2202009-03-03 19:32:30 -08001774 }
1775 }
1776
1777 public String getTrackName() {
1778 synchronized (this) {
1779 if (mCursor == null) {
1780 return null;
1781 }
1782 return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE));
1783 }
1784 }
1785
1786 private boolean isPodcast() {
1787 synchronized (this) {
1788 if (mCursor == null) {
1789 return false;
1790 }
1791 return (mCursor.getInt(PODCASTCOLIDX) > 0);
1792 }
1793 }
1794
1795 private long getBookmark() {
1796 synchronized (this) {
1797 if (mCursor == null) {
1798 return 0;
1799 }
1800 return mCursor.getLong(BOOKMARKCOLIDX);
1801 }
1802 }
1803
1804 /**
1805 * Returns the duration of the file in milliseconds.
1806 * Currently this method returns -1 for the duration of MIDI files.
1807 */
1808 public long duration() {
1809 if (mPlayer.isInitialized()) {
1810 return mPlayer.duration();
1811 }
1812 return -1;
1813 }
1814
1815 /**
1816 * Returns the current playback position in milliseconds
1817 */
1818 public long position() {
1819 if (mPlayer.isInitialized()) {
1820 return mPlayer.position();
1821 }
1822 return -1;
1823 }
1824
1825 /**
1826 * Seeks to the position specified.
1827 *
1828 * @param pos The position to seek to, in milliseconds
1829 */
1830 public long seek(long pos) {
1831 if (mPlayer.isInitialized()) {
1832 if (pos < 0) pos = 0;
1833 if (pos > mPlayer.duration()) pos = mPlayer.duration();
1834 return mPlayer.seek(pos);
1835 }
1836 return -1;
1837 }
1838
1839 /**
Eric Laurent1cc72a12010-06-28 11:27:01 -07001840 * Sets the audio session ID.
1841 *
1842 * @param sessionId: the audio session ID.
1843 */
1844 public void setAudioSessionId(int sessionId) {
1845 synchronized (this) {
1846 mPlayer.setAudioSessionId(sessionId);
1847 }
1848 }
1849
1850 /**
1851 * Returns the audio session ID.
1852 */
1853 public int getAudioSessionId() {
1854 synchronized (this) {
1855 return mPlayer.getAudioSessionId();
1856 }
1857 }
1858
1859 /**
The Android Open Source Project792a2202009-03-03 19:32:30 -08001860 * Provides a unified interface for dealing with midi files and
1861 * other media files.
1862 */
1863 private class MultiPlayer {
Marco Nelissen30867022012-03-14 16:04:34 -07001864 private CompatMediaPlayer mCurrentMediaPlayer = new CompatMediaPlayer();
1865 private CompatMediaPlayer mNextMediaPlayer;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001866 private Handler mHandler;
1867 private boolean mIsInitialized = false;
1868
1869 public MultiPlayer() {
Marco Nelissene41bd182012-03-14 08:24:40 -07001870 mCurrentMediaPlayer.setWakeMode(MediaPlaybackService.this, PowerManager.PARTIAL_WAKE_LOCK);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001871 }
1872
The Android Open Source Project792a2202009-03-03 19:32:30 -08001873 public void setDataSource(String path) {
Marco Nelissen90d1f622012-04-05 12:27:56 -07001874 mIsInitialized = setDataSourceImpl(mCurrentMediaPlayer, path);
1875 if (mIsInitialized) {
1876 setNextDataSource(null);
1877 }
Marco Nelissene41bd182012-03-14 08:24:40 -07001878 }
1879
Marco Nelissen90d1f622012-04-05 12:27:56 -07001880 private boolean setDataSourceImpl(MediaPlayer player, String path) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001881 try {
Marco Nelissene41bd182012-03-14 08:24:40 -07001882 player.reset();
1883 player.setOnPreparedListener(null);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001884 if (path.startsWith("content://")) {
Marco Nelissene41bd182012-03-14 08:24:40 -07001885 player.setDataSource(MediaPlaybackService.this, Uri.parse(path));
The Android Open Source Project792a2202009-03-03 19:32:30 -08001886 } else {
Marco Nelissene41bd182012-03-14 08:24:40 -07001887 player.setDataSource(path);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001888 }
Marco Nelissene41bd182012-03-14 08:24:40 -07001889 player.setAudioStreamType(AudioManager.STREAM_MUSIC);
1890 player.prepare();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001891 } catch (IOException ex) {
1892 // TODO: notify the user why the file couldn't be opened
Marco Nelissen90d1f622012-04-05 12:27:56 -07001893 return false;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001894 } catch (IllegalArgumentException ex) {
1895 // TODO: notify the user why the file couldn't be opened
Marco Nelissen90d1f622012-04-05 12:27:56 -07001896 return false;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001897 }
Marco Nelissene41bd182012-03-14 08:24:40 -07001898 player.setOnCompletionListener(listener);
1899 player.setOnErrorListener(errorListener);
Marco Nelissenf2ef3b52010-09-21 15:47:27 -07001900 Intent i = new Intent(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION);
1901 i.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, getAudioSessionId());
1902 i.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, getPackageName());
1903 sendBroadcast(i);
Marco Nelissen90d1f622012-04-05 12:27:56 -07001904 return true;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001905 }
Marco Nelissene41bd182012-03-14 08:24:40 -07001906
1907 public void setNextDataSource(String path) {
1908 mCurrentMediaPlayer.setNextMediaPlayer(null);
1909 if (mNextMediaPlayer != null) {
1910 mNextMediaPlayer.release();
1911 mNextMediaPlayer = null;
1912 }
1913 if (path == null) {
1914 return;
1915 }
Marco Nelissen30867022012-03-14 16:04:34 -07001916 mNextMediaPlayer = new CompatMediaPlayer();
Marco Nelissene41bd182012-03-14 08:24:40 -07001917 mNextMediaPlayer.setWakeMode(MediaPlaybackService.this, PowerManager.PARTIAL_WAKE_LOCK);
1918 mNextMediaPlayer.setAudioSessionId(getAudioSessionId());
Marco Nelissen90d1f622012-04-05 12:27:56 -07001919 if (setDataSourceImpl(mNextMediaPlayer, path)) {
1920 mCurrentMediaPlayer.setNextMediaPlayer(mNextMediaPlayer);
1921 } else {
1922 // failed to open next, we'll transition the old fashioned way,
1923 // which will skip over the faulty file
1924 mNextMediaPlayer.release();
1925 mNextMediaPlayer = null;
1926 }
Marco Nelissene41bd182012-03-14 08:24:40 -07001927 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08001928
1929 public boolean isInitialized() {
1930 return mIsInitialized;
1931 }
1932
1933 public void start() {
Marco Nelissen39888902010-03-02 10:27:05 -08001934 MusicUtils.debugLog(new Exception("MultiPlayer.start called"));
Marco Nelissene41bd182012-03-14 08:24:40 -07001935 mCurrentMediaPlayer.start();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001936 }
1937
1938 public void stop() {
Marco Nelissene41bd182012-03-14 08:24:40 -07001939 mCurrentMediaPlayer.reset();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001940 mIsInitialized = false;
1941 }
1942
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001943 /**
1944 * You CANNOT use this player anymore after calling release()
1945 */
1946 public void release() {
1947 stop();
Marco Nelissene41bd182012-03-14 08:24:40 -07001948 mCurrentMediaPlayer.release();
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001949 }
1950
The Android Open Source Project792a2202009-03-03 19:32:30 -08001951 public void pause() {
Marco Nelissene41bd182012-03-14 08:24:40 -07001952 mCurrentMediaPlayer.pause();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001953 }
1954
The Android Open Source Project792a2202009-03-03 19:32:30 -08001955 public void setHandler(Handler handler) {
1956 mHandler = handler;
1957 }
1958
1959 MediaPlayer.OnCompletionListener listener = new MediaPlayer.OnCompletionListener() {
1960 public void onCompletion(MediaPlayer mp) {
Marco Nelissene41bd182012-03-14 08:24:40 -07001961 if (mp == mCurrentMediaPlayer && mNextMediaPlayer != null) {
1962 mCurrentMediaPlayer.release();
1963 mCurrentMediaPlayer = mNextMediaPlayer;
1964 mNextMediaPlayer = null;
1965 mHandler.sendEmptyMessage(TRACK_WENT_TO_NEXT);
1966 } else {
1967 // Acquire a temporary wakelock, since when we return from
1968 // this callback the MediaPlayer will release its wakelock
1969 // and allow the device to go to sleep.
1970 // This temporary wakelock is released when the RELEASE_WAKELOCK
1971 // message is processed, but just in case, put a timeout on it.
1972 mWakeLock.acquire(30000);
1973 mHandler.sendEmptyMessage(TRACK_ENDED);
1974 mHandler.sendEmptyMessage(RELEASE_WAKELOCK);
1975 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08001976 }
1977 };
1978
The Android Open Source Project792a2202009-03-03 19:32:30 -08001979 MediaPlayer.OnErrorListener errorListener = new MediaPlayer.OnErrorListener() {
1980 public boolean onError(MediaPlayer mp, int what, int extra) {
1981 switch (what) {
1982 case MediaPlayer.MEDIA_ERROR_SERVER_DIED:
1983 mIsInitialized = false;
Marco Nelissene41bd182012-03-14 08:24:40 -07001984 mCurrentMediaPlayer.release();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001985 // Creating a new MediaPlayer and settings its wakemode does not
1986 // require the media service, so it's OK to do this now, while the
1987 // service is still being restarted
Marco Nelissen30867022012-03-14 16:04:34 -07001988 mCurrentMediaPlayer = new CompatMediaPlayer();
Marco Nelissene41bd182012-03-14 08:24:40 -07001989 mCurrentMediaPlayer.setWakeMode(MediaPlaybackService.this, PowerManager.PARTIAL_WAKE_LOCK);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001990 mHandler.sendMessageDelayed(mHandler.obtainMessage(SERVER_DIED), 2000);
1991 return true;
1992 default:
Marco Nelissene99341f2009-11-11 11:13:51 -08001993 Log.d("MultiPlayer", "Error: " + what + "," + extra);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001994 break;
1995 }
1996 return false;
1997 }
1998 };
1999
2000 public long duration() {
Marco Nelissene41bd182012-03-14 08:24:40 -07002001 return mCurrentMediaPlayer.getDuration();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002002 }
2003
2004 public long position() {
Marco Nelissene41bd182012-03-14 08:24:40 -07002005 return mCurrentMediaPlayer.getCurrentPosition();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002006 }
2007
2008 public long seek(long whereto) {
Marco Nelissene41bd182012-03-14 08:24:40 -07002009 mCurrentMediaPlayer.seekTo((int) whereto);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002010 return whereto;
2011 }
2012
2013 public void setVolume(float vol) {
Marco Nelissene41bd182012-03-14 08:24:40 -07002014 mCurrentMediaPlayer.setVolume(vol, vol);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002015 }
Eric Laurent1cc72a12010-06-28 11:27:01 -07002016
2017 public void setAudioSessionId(int sessionId) {
Marco Nelissene41bd182012-03-14 08:24:40 -07002018 mCurrentMediaPlayer.setAudioSessionId(sessionId);
Eric Laurent1cc72a12010-06-28 11:27:01 -07002019 }
2020
2021 public int getAudioSessionId() {
Marco Nelissene41bd182012-03-14 08:24:40 -07002022 return mCurrentMediaPlayer.getAudioSessionId();
Eric Laurent1cc72a12010-06-28 11:27:01 -07002023 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08002024 }
2025
Marco Nelissen30867022012-03-14 16:04:34 -07002026 static class CompatMediaPlayer extends MediaPlayer implements OnCompletionListener {
2027
2028 private boolean mCompatMode = true;
2029 private MediaPlayer mNextPlayer;
2030 private OnCompletionListener mCompletion;
2031
2032 public CompatMediaPlayer() {
2033 try {
2034 MediaPlayer.class.getMethod("setNextMediaPlayer", MediaPlayer.class);
2035 mCompatMode = false;
2036 } catch (NoSuchMethodException e) {
2037 mCompatMode = true;
2038 super.setOnCompletionListener(this);
2039 }
2040 }
2041
2042 public void setNextMediaPlayer(MediaPlayer next) {
2043 if (mCompatMode) {
2044 mNextPlayer = next;
2045 } else {
2046 super.setNextMediaPlayer(next);
2047 }
2048 }
2049
2050 @Override
2051 public void setOnCompletionListener(OnCompletionListener listener) {
2052 if (mCompatMode) {
2053 mCompletion = listener;
2054 } else {
2055 super.setOnCompletionListener(listener);
2056 }
2057 }
2058
2059 @Override
2060 public void onCompletion(MediaPlayer mp) {
2061 if (mNextPlayer != null) {
2062 // as it turns out, starting a new MediaPlayer on the completion
2063 // of a previous player ends up slightly overlapping the two
2064 // playbacks, so slightly delaying the start of the next player
2065 // gives a better user experience
2066 SystemClock.sleep(50);
2067 mNextPlayer.start();
2068 }
2069 mCompletion.onCompletion(this);
2070 }
2071 }
2072
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002073 /*
2074 * By making this a static class with a WeakReference to the Service, we
2075 * ensure that the Service can be GCd even when the system process still
2076 * has a remote reference to the stub.
2077 */
2078 static class ServiceStub extends IMediaPlaybackService.Stub {
2079 WeakReference<MediaPlaybackService> mService;
2080
2081 ServiceStub(MediaPlaybackService service) {
2082 mService = new WeakReference<MediaPlaybackService>(service);
2083 }
2084
Marco Nelissen8d08ec22010-05-10 14:05:24 -07002085 public void openFile(String path)
The Android Open Source Project792a2202009-03-03 19:32:30 -08002086 {
Marco Nelissen8d08ec22010-05-10 14:05:24 -07002087 mService.get().open(path);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002088 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07002089 public void open(long [] list, int position) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002090 mService.get().open(list, position);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002091 }
2092 public int getQueuePosition() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002093 return mService.get().getQueuePosition();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002094 }
2095 public void setQueuePosition(int index) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002096 mService.get().setQueuePosition(index);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002097 }
2098 public boolean isPlaying() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002099 return mService.get().isPlaying();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002100 }
2101 public void stop() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002102 mService.get().stop();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002103 }
2104 public void pause() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002105 mService.get().pause();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002106 }
2107 public void play() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002108 mService.get().play();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002109 }
2110 public void prev() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002111 mService.get().prev();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002112 }
2113 public void next() {
Marco Nelissenbd0d2f12012-02-27 16:02:15 -08002114 mService.get().gotoNext(true);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002115 }
2116 public String getTrackName() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002117 return mService.get().getTrackName();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002118 }
2119 public String getAlbumName() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002120 return mService.get().getAlbumName();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002121 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07002122 public long getAlbumId() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002123 return mService.get().getAlbumId();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002124 }
2125 public String getArtistName() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002126 return mService.get().getArtistName();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002127 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07002128 public long getArtistId() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002129 return mService.get().getArtistId();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002130 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07002131 public void enqueue(long [] list , int action) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002132 mService.get().enqueue(list, action);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002133 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07002134 public long [] getQueue() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002135 return mService.get().getQueue();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002136 }
2137 public void moveQueueItem(int from, int to) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002138 mService.get().moveQueueItem(from, to);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002139 }
2140 public String getPath() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002141 return mService.get().getPath();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002142 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07002143 public long getAudioId() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002144 return mService.get().getAudioId();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002145 }
2146 public long position() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002147 return mService.get().position();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002148 }
2149 public long duration() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002150 return mService.get().duration();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002151 }
2152 public long seek(long pos) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002153 return mService.get().seek(pos);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002154 }
2155 public void setShuffleMode(int shufflemode) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002156 mService.get().setShuffleMode(shufflemode);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002157 }
2158 public int getShuffleMode() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002159 return mService.get().getShuffleMode();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002160 }
2161 public int removeTracks(int first, int last) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002162 return mService.get().removeTracks(first, last);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002163 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07002164 public int removeTrack(long id) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002165 return mService.get().removeTrack(id);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002166 }
2167 public void setRepeatMode(int repeatmode) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002168 mService.get().setRepeatMode(repeatmode);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002169 }
2170 public int getRepeatMode() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002171 return mService.get().getRepeatMode();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002172 }
2173 public int getMediaMountedCount() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002174 return mService.get().getMediaMountedCount();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002175 }
Eric Laurent1cc72a12010-06-28 11:27:01 -07002176 public int getAudioSessionId() {
2177 return mService.get().getAudioSessionId();
2178 }
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002179 }
Marco Nelissen39888902010-03-02 10:27:05 -08002180
2181 @Override
2182 protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
Marco Nelissenbf555ce2010-03-02 16:55:25 -08002183 writer.println("" + mPlayListLen + " items in queue, currently at index " + mPlayPos);
Marco Nelissen39888902010-03-02 10:27:05 -08002184 writer.println("Currently loaded:");
2185 writer.println(getArtistName());
2186 writer.println(getAlbumName());
2187 writer.println(getTrackName());
2188 writer.println(getPath());
2189 writer.println("playing: " + mIsSupposedToBePlaying);
Marco Nelissene41bd182012-03-14 08:24:40 -07002190 writer.println("actual: " + mPlayer.mCurrentMediaPlayer.isPlaying());
Marco Nelissenbf555ce2010-03-02 16:55:25 -08002191 writer.println("shuffle mode: " + mShuffleMode);
Marco Nelissen39888902010-03-02 10:27:05 -08002192 MusicUtils.debugDump(writer);
2193 }
2194
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002195 private final IBinder mBinder = new ServiceStub(this);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002196}