blob: f872da2f29440845c44b57f629b4f067180da1cc [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);
Jean-Michel Trivi94ed2422011-09-13 18:27:19 -0700320 // TODO update to new constructor
321// mRemoteControlClient = new RemoteControlClient(rec);
322// mAudioManager.registerRemoteControlClient(mRemoteControlClient);
323//
324// int flags = RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS
325// | RemoteControlClient.FLAG_KEY_MEDIA_NEXT
326// | RemoteControlClient.FLAG_KEY_MEDIA_PLAY
327// | RemoteControlClient.FLAG_KEY_MEDIA_PAUSE
328// | RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE
329// | RemoteControlClient.FLAG_KEY_MEDIA_STOP;
330// mRemoteControlClient.setTransportControlFlags(flags);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800331
332 mPreferences = getSharedPreferences("Music", MODE_WORLD_READABLE | MODE_WORLD_WRITEABLE);
Marco Nelissen87bbf3c2009-12-23 16:18:45 -0800333 mCardId = MusicUtils.getCardId(this);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800334
335 registerExternalStorageListener();
336
337 // Needs to be done in this thread, since otherwise ApplicationContext.getPowerManager() crashes.
338 mPlayer = new MultiPlayer();
339 mPlayer.setHandler(mMediaplayerHandler);
340
The Android Open Source Project792a2202009-03-03 19:32:30 -0800341 reloadQueue();
Marco Nelissen8717d342011-09-08 09:32:51 -0700342 notifyChange(QUEUE_CHANGED);
343 notifyChange(META_CHANGED);
344
The Android Open Source Project792a2202009-03-03 19:32:30 -0800345 IntentFilter commandFilter = new IntentFilter();
346 commandFilter.addAction(SERVICECMD);
347 commandFilter.addAction(TOGGLEPAUSE_ACTION);
348 commandFilter.addAction(PAUSE_ACTION);
349 commandFilter.addAction(NEXT_ACTION);
350 commandFilter.addAction(PREVIOUS_ACTION);
351 registerReceiver(mIntentReceiver, commandFilter);
352
The Android Open Source Project792a2202009-03-03 19:32:30 -0800353 PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
354 mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, this.getClass().getName());
355 mWakeLock.setReferenceCounted(false);
356
357 // If the service was idle, but got killed before it stopped itself, the
358 // system will relaunch it. Make sure it gets stopped again in that case.
359 Message msg = mDelayedStopHandler.obtainMessage();
360 mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
361 }
362
363 @Override
364 public void onDestroy() {
365 // Check that we're not being destroyed while something is still playing.
366 if (isPlaying()) {
Marco Nelissene99341f2009-11-11 11:13:51 -0800367 Log.e(LOGTAG, "Service being destroyed while still playing.");
The Android Open Source Project792a2202009-03-03 19:32:30 -0800368 }
Marco Nelissen2b0b9132009-05-21 16:26:47 -0700369 // release all MediaPlayer resources, including the native player and wakelocks
Marco Nelissenf2ef3b52010-09-21 15:47:27 -0700370 Intent i = new Intent(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION);
371 i.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, getAudioSessionId());
372 i.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, getPackageName());
373 sendBroadcast(i);
Marco Nelissen2b0b9132009-05-21 16:26:47 -0700374 mPlayer.release();
375 mPlayer = null;
Jean-Michel Trivi085cbfd2010-03-08 17:06:18 -0800376
377 mAudioManager.abandonAudioFocus(mAudioFocusListener);
Jean-Michel Trivi94ed2422011-09-13 18:27:19 -0700378 //mAudioManager.unregisterRemoteControlClient(mRemoteControlClient);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800379
Marco Nelissen2b0b9132009-05-21 16:26:47 -0700380 // make sure there aren't any other messages coming
381 mDelayedStopHandler.removeCallbacksAndMessages(null);
Marco Nelissen49e36ea2009-05-28 10:20:02 -0700382 mMediaplayerHandler.removeCallbacksAndMessages(null);
Marco Nelissen2b0b9132009-05-21 16:26:47 -0700383
The Android Open Source Project792a2202009-03-03 19:32:30 -0800384 if (mCursor != null) {
385 mCursor.close();
386 mCursor = null;
387 }
388
389 unregisterReceiver(mIntentReceiver);
390 if (mUnmountReceiver != null) {
391 unregisterReceiver(mUnmountReceiver);
392 mUnmountReceiver = null;
393 }
The Android Open Source Project792a2202009-03-03 19:32:30 -0800394 mWakeLock.release();
395 super.onDestroy();
396 }
397
398 private final char hexdigits [] = new char [] {
399 '0', '1', '2', '3',
400 '4', '5', '6', '7',
401 '8', '9', 'a', 'b',
402 'c', 'd', 'e', 'f'
403 };
404
405 private void saveQueue(boolean full) {
Marco Nelissen8d08ec22010-05-10 14:05:24 -0700406 if (!mQueueIsSaveable) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800407 return;
408 }
Marco Nelissen8d08ec22010-05-10 14:05:24 -0700409
The Android Open Source Project792a2202009-03-03 19:32:30 -0800410 Editor ed = mPreferences.edit();
411 //long start = System.currentTimeMillis();
412 if (full) {
413 StringBuilder q = new StringBuilder();
414
415 // The current playlist is saved as a list of "reverse hexadecimal"
416 // numbers, which we can generate faster than normal decimal or
417 // hexadecimal numbers, which in turn allows us to save the playlist
418 // more often without worrying too much about performance.
419 // (saving the full state takes about 40 ms under no-load conditions
420 // on the phone)
421 int len = mPlayListLen;
422 for (int i = 0; i < len; i++) {
Marco Nelissenbd447b62009-06-29 14:52:05 -0700423 long n = mPlayList[i];
Marco Nelissen13355022010-08-31 15:13:27 -0700424 if (n < 0) {
425 continue;
426 } else if (n == 0) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800427 q.append("0;");
428 } else {
429 while (n != 0) {
Marco Nelissenbd447b62009-06-29 14:52:05 -0700430 int digit = (int)(n & 0xf);
Marco Nelissen13355022010-08-31 15:13:27 -0700431 n >>>= 4;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800432 q.append(hexdigits[digit]);
433 }
434 q.append(";");
435 }
436 }
437 //Log.i("@@@@ service", "created queue string in " + (System.currentTimeMillis() - start) + " ms");
438 ed.putString("queue", q.toString());
439 ed.putInt("cardid", mCardId);
Marco Nelissen63dbbbb2009-08-17 14:17:27 -0700440 if (mShuffleMode != SHUFFLE_NONE) {
441 // In shuffle mode we need to save the history too
442 len = mHistory.size();
443 q.setLength(0);
444 for (int i = 0; i < len; i++) {
445 int n = mHistory.get(i);
446 if (n == 0) {
447 q.append("0;");
448 } else {
449 while (n != 0) {
450 int digit = (n & 0xf);
Marco Nelissen13355022010-08-31 15:13:27 -0700451 n >>>= 4;
Marco Nelissen63dbbbb2009-08-17 14:17:27 -0700452 q.append(hexdigits[digit]);
453 }
454 q.append(";");
455 }
456 }
457 ed.putString("history", q.toString());
458 }
The Android Open Source Project792a2202009-03-03 19:32:30 -0800459 }
460 ed.putInt("curpos", mPlayPos);
461 if (mPlayer.isInitialized()) {
462 ed.putLong("seekpos", mPlayer.position());
463 }
464 ed.putInt("repeatmode", mRepeatMode);
465 ed.putInt("shufflemode", mShuffleMode);
Brad Fitzpatrick14c3cae2010-09-10 09:38:57 -0700466 SharedPreferencesCompat.apply(ed);
467
The Android Open Source Project792a2202009-03-03 19:32:30 -0800468 //Log.i("@@@@ service", "saved state in " + (System.currentTimeMillis() - start) + " ms");
469 }
470
471 private void reloadQueue() {
472 String q = null;
473
474 boolean newstyle = false;
475 int id = mCardId;
476 if (mPreferences.contains("cardid")) {
477 newstyle = true;
478 id = mPreferences.getInt("cardid", ~mCardId);
479 }
480 if (id == mCardId) {
481 // Only restore the saved playlist if the card is still
482 // the same one as when the playlist was saved
483 q = mPreferences.getString("queue", "");
484 }
Marco Nelissen6c615a22009-06-10 12:43:25 -0700485 int qlen = q != null ? q.length() : 0;
486 if (qlen > 1) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800487 //Log.i("@@@@ service", "loaded queue: " + q);
Marco Nelissenbf0ea142009-06-05 12:46:09 -0700488 int plen = 0;
489 int n = 0;
490 int shift = 0;
491 for (int i = 0; i < qlen; i++) {
492 char c = q.charAt(i);
493 if (c == ';') {
494 ensurePlayListCapacity(plen + 1);
495 mPlayList[plen] = n;
496 plen++;
497 n = 0;
498 shift = 0;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800499 } else {
Marco Nelissenbf0ea142009-06-05 12:46:09 -0700500 if (c >= '0' && c <= '9') {
501 n += ((c - '0') << shift);
502 } else if (c >= 'a' && c <= 'f') {
503 n += ((10 + c - 'a') << shift);
504 } else {
505 // bogus playlist data
506 plen = 0;
507 break;
508 }
509 shift += 4;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800510 }
511 }
Marco Nelissenbf0ea142009-06-05 12:46:09 -0700512 mPlayListLen = plen;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800513
514 int pos = mPreferences.getInt("curpos", 0);
Marco Nelissenbf0ea142009-06-05 12:46:09 -0700515 if (pos < 0 || pos >= mPlayListLen) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800516 // The saved playlist is bogus, discard it
517 mPlayListLen = 0;
518 return;
519 }
520 mPlayPos = pos;
521
522 // When reloadQueue is called in response to a card-insertion,
523 // we might not be able to query the media provider right away.
524 // To deal with this, try querying for the current file, and if
525 // that fails, wait a while and try again. If that too fails,
526 // assume there is a problem and don't restore the state.
Marco Nelissen63dbbbb2009-08-17 14:17:27 -0700527 Cursor crsr = MusicUtils.query(this,
The Android Open Source Project792a2202009-03-03 19:32:30 -0800528 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
529 new String [] {"_id"}, "_id=" + mPlayList[mPlayPos] , null, null);
Marco Nelissen63dbbbb2009-08-17 14:17:27 -0700530 if (crsr == null || crsr.getCount() == 0) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800531 // wait a bit and try again
532 SystemClock.sleep(3000);
Marco Nelissen63dbbbb2009-08-17 14:17:27 -0700533 crsr = getContentResolver().query(
The Android Open Source Project792a2202009-03-03 19:32:30 -0800534 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
535 mCursorCols, "_id=" + mPlayList[mPlayPos] , null, null);
536 }
Marco Nelissen63dbbbb2009-08-17 14:17:27 -0700537 if (crsr != null) {
538 crsr.close();
The Android Open Source Project792a2202009-03-03 19:32:30 -0800539 }
540
541 // Make sure we don't auto-skip to the next song, since that
542 // also starts playback. What could happen in that case is:
543 // - music is paused
544 // - go to UMS and delete some files, including the currently playing one
545 // - come back from UMS
546 // (time passes)
547 // - music app is killed for some reason (out of memory)
548 // - music service is restarted, service restores state, doesn't find
549 // the "current" file, goes to the next and: playback starts on its
550 // own, potentially at some random inconvenient time.
551 mOpenFailedCounter = 20;
552 mQuietMode = true;
Marco Nelissene41bd182012-03-14 08:24:40 -0700553 openCurrentAndNext();
The Android Open Source Project792a2202009-03-03 19:32:30 -0800554 mQuietMode = false;
555 if (!mPlayer.isInitialized()) {
556 // couldn't restore the saved state
557 mPlayListLen = 0;
558 return;
559 }
560
561 long seekpos = mPreferences.getLong("seekpos", 0);
562 seek(seekpos >= 0 && seekpos < duration() ? seekpos : 0);
Marco Nelissene99341f2009-11-11 11:13:51 -0800563 Log.d(LOGTAG, "restored queue, currently at position "
564 + position() + "/" + duration()
565 + " (requested " + seekpos + ")");
The Android Open Source Project792a2202009-03-03 19:32:30 -0800566
567 int repmode = mPreferences.getInt("repeatmode", REPEAT_NONE);
568 if (repmode != REPEAT_ALL && repmode != REPEAT_CURRENT) {
569 repmode = REPEAT_NONE;
570 }
571 mRepeatMode = repmode;
572
573 int shufmode = mPreferences.getInt("shufflemode", SHUFFLE_NONE);
574 if (shufmode != SHUFFLE_AUTO && shufmode != SHUFFLE_NORMAL) {
575 shufmode = SHUFFLE_NONE;
576 }
Marco Nelissen63dbbbb2009-08-17 14:17:27 -0700577 if (shufmode != SHUFFLE_NONE) {
578 // in shuffle mode we need to restore the history too
579 q = mPreferences.getString("history", "");
580 qlen = q != null ? q.length() : 0;
581 if (qlen > 1) {
582 plen = 0;
583 n = 0;
584 shift = 0;
585 mHistory.clear();
586 for (int i = 0; i < qlen; i++) {
587 char c = q.charAt(i);
588 if (c == ';') {
589 if (n >= mPlayListLen) {
590 // bogus history data
591 mHistory.clear();
592 break;
593 }
594 mHistory.add(n);
595 n = 0;
596 shift = 0;
597 } else {
598 if (c >= '0' && c <= '9') {
599 n += ((c - '0') << shift);
600 } else if (c >= 'a' && c <= 'f') {
601 n += ((10 + c - 'a') << shift);
602 } else {
603 // bogus history data
604 mHistory.clear();
605 break;
606 }
607 shift += 4;
608 }
609 }
610 }
611 }
The Android Open Source Project792a2202009-03-03 19:32:30 -0800612 if (shufmode == SHUFFLE_AUTO) {
613 if (! makeAutoShuffleList()) {
614 shufmode = SHUFFLE_NONE;
615 }
616 }
617 mShuffleMode = shufmode;
618 }
619 }
620
621 @Override
622 public IBinder onBind(Intent intent) {
623 mDelayedStopHandler.removeCallbacksAndMessages(null);
624 mServiceInUse = true;
625 return mBinder;
626 }
627
628 @Override
629 public void onRebind(Intent intent) {
630 mDelayedStopHandler.removeCallbacksAndMessages(null);
631 mServiceInUse = true;
632 }
633
634 @Override
Marco Nelissenc1017e52009-09-24 13:21:06 -0700635 public int onStartCommand(Intent intent, int flags, int startId) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800636 mServiceStartId = startId;
637 mDelayedStopHandler.removeCallbacksAndMessages(null);
Marco Nelissenc1017e52009-09-24 13:21:06 -0700638
639 if (intent != null) {
640 String action = intent.getAction();
641 String cmd = intent.getStringExtra("command");
Marco Nelissen39888902010-03-02 10:27:05 -0800642 MusicUtils.debugLog("onStartCommand " + action + " / " + cmd);
Marco Nelissenc1017e52009-09-24 13:21:06 -0700643
644 if (CMDNEXT.equals(cmd) || NEXT_ACTION.equals(action)) {
Marco Nelissenbd0d2f12012-02-27 16:02:15 -0800645 gotoNext(true);
Marco Nelissenc1017e52009-09-24 13:21:06 -0700646 } else if (CMDPREVIOUS.equals(cmd) || PREVIOUS_ACTION.equals(action)) {
Marco Nelissenb63b5d12009-11-18 16:04:19 -0800647 if (position() < 2000) {
648 prev();
649 } else {
650 seek(0);
651 play();
652 }
Marco Nelissenc1017e52009-09-24 13:21:06 -0700653 } else if (CMDTOGGLEPAUSE.equals(cmd) || TOGGLEPAUSE_ACTION.equals(action)) {
654 if (isPlaying()) {
655 pause();
Jean-Michel Triviaa331872010-04-20 17:00:26 -0700656 mPausedByTransientLossOfFocus = false;
Marco Nelissenc1017e52009-09-24 13:21:06 -0700657 } else {
658 play();
659 }
660 } else if (CMDPAUSE.equals(cmd) || PAUSE_ACTION.equals(action)) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800661 pause();
Jean-Michel Triviaa331872010-04-20 17:00:26 -0700662 mPausedByTransientLossOfFocus = false;
Jaikumar Ganesh47dd8472010-12-16 15:51:31 -0800663 } else if (CMDPLAY.equals(cmd)) {
664 play();
Marco Nelissenc1017e52009-09-24 13:21:06 -0700665 } else if (CMDSTOP.equals(cmd)) {
666 pause();
Jean-Michel Triviaa331872010-04-20 17:00:26 -0700667 mPausedByTransientLossOfFocus = false;
Marco Nelissenc1017e52009-09-24 13:21:06 -0700668 seek(0);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800669 }
The Android Open Source Project792a2202009-03-03 19:32:30 -0800670 }
671
672 // make sure the service will shut down on its own if it was
673 // just started but not bound to and nothing is playing
674 mDelayedStopHandler.removeCallbacksAndMessages(null);
675 Message msg = mDelayedStopHandler.obtainMessage();
676 mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
Marco Nelissenc1017e52009-09-24 13:21:06 -0700677 return START_STICKY;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800678 }
679
680 @Override
681 public boolean onUnbind(Intent intent) {
682 mServiceInUse = false;
683
684 // Take a snapshot of the current playlist
685 saveQueue(true);
686
Jean-Michel Triviaa331872010-04-20 17:00:26 -0700687 if (isPlaying() || mPausedByTransientLossOfFocus) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800688 // something is currently playing, or will be playing once
Jean-Michel Triviaa331872010-04-20 17:00:26 -0700689 // 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 -0800690 return true;
691 }
692
693 // If there is a playlist but playback is paused, then wait a while
694 // before stopping the service, so that pause/resume isn't slow.
695 // Also delay stopping the service if we're transitioning between tracks.
696 if (mPlayListLen > 0 || mMediaplayerHandler.hasMessages(TRACK_ENDED)) {
697 Message msg = mDelayedStopHandler.obtainMessage();
698 mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
699 return true;
700 }
701
702 // No active playlist, OK to stop the service right now
703 stopSelf(mServiceStartId);
704 return true;
705 }
706
707 private Handler mDelayedStopHandler = new Handler() {
708 @Override
709 public void handleMessage(Message msg) {
710 // Check again to make sure nothing is playing right now
Jean-Michel Triviaa331872010-04-20 17:00:26 -0700711 if (isPlaying() || mPausedByTransientLossOfFocus || mServiceInUse
The Android Open Source Project792a2202009-03-03 19:32:30 -0800712 || mMediaplayerHandler.hasMessages(TRACK_ENDED)) {
713 return;
714 }
715 // save the queue again, because it might have changed
716 // since the user exited the music app (because of
717 // party-shuffle or because the play-position changed)
718 saveQueue(true);
719 stopSelf(mServiceStartId);
720 }
721 };
Marco Nelissenf2ef3b52010-09-21 15:47:27 -0700722
The Android Open Source Project792a2202009-03-03 19:32:30 -0800723 /**
724 * Called when we receive a ACTION_MEDIA_EJECT notification.
725 *
726 * @param storagePath path to mount point for the removed media
727 */
728 public void closeExternalStorageFiles(String storagePath) {
729 // stop playback and clean up if the SD card is going to be unmounted.
730 stop(true);
731 notifyChange(QUEUE_CHANGED);
732 notifyChange(META_CHANGED);
733 }
734
735 /**
736 * Registers an intent to listen for ACTION_MEDIA_EJECT notifications.
737 * The intent will call closeExternalStorageFiles() if the external media
738 * is going to be ejected, so applications can clean up any files they have open.
739 */
740 public void registerExternalStorageListener() {
741 if (mUnmountReceiver == null) {
742 mUnmountReceiver = new BroadcastReceiver() {
743 @Override
744 public void onReceive(Context context, Intent intent) {
745 String action = intent.getAction();
746 if (action.equals(Intent.ACTION_MEDIA_EJECT)) {
747 saveQueue(true);
Marco Nelissen8d08ec22010-05-10 14:05:24 -0700748 mQueueIsSaveable = false;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800749 closeExternalStorageFiles(intent.getData().getPath());
750 } else if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) {
751 mMediaMountedCount++;
Marco Nelissen87bbf3c2009-12-23 16:18:45 -0800752 mCardId = MusicUtils.getCardId(MediaPlaybackService.this);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800753 reloadQueue();
Marco Nelissen8d08ec22010-05-10 14:05:24 -0700754 mQueueIsSaveable = true;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800755 notifyChange(QUEUE_CHANGED);
756 notifyChange(META_CHANGED);
757 }
758 }
759 };
760 IntentFilter iFilter = new IntentFilter();
761 iFilter.addAction(Intent.ACTION_MEDIA_EJECT);
762 iFilter.addAction(Intent.ACTION_MEDIA_MOUNTED);
763 iFilter.addDataScheme("file");
764 registerReceiver(mUnmountReceiver, iFilter);
765 }
766 }
767
768 /**
769 * Notify the change-receivers that something has changed.
770 * The intent that is sent contains the following data
771 * for the currently playing track:
772 * "id" - Integer: the database row ID
773 * "artist" - String: the name of the artist
774 * "album" - String: the name of the album
775 * "track" - String: the name of the track
776 * The intent has an action that is one of
777 * "com.android.music.metachanged"
778 * "com.android.music.queuechanged",
779 * "com.android.music.playbackcomplete"
780 * "com.android.music.playstatechanged"
781 * respectively indicating that a new track has
782 * started playing, that the playback queue has
783 * changed, that playback has stopped because
784 * the last file in the list has been played,
785 * or that the play-state changed (paused/resumed).
786 */
787 private void notifyChange(String what) {
Marco Nelissen8717d342011-09-08 09:32:51 -0700788
The Android Open Source Project792a2202009-03-03 19:32:30 -0800789 Intent i = new Intent(what);
Marco Nelissenbd447b62009-06-29 14:52:05 -0700790 i.putExtra("id", Long.valueOf(getAudioId()));
The Android Open Source Project792a2202009-03-03 19:32:30 -0800791 i.putExtra("artist", getArtistName());
792 i.putExtra("album",getAlbumName());
793 i.putExtra("track", getTrackName());
Marco Nelissen6b507de2010-10-20 14:08:10 -0700794 i.putExtra("playing", isPlaying());
795 sendStickyBroadcast(i);
Marco Nelissen8717d342011-09-08 09:32:51 -0700796
797 if (what.equals(PLAYSTATE_CHANGED)) {
Jean-Michel Trivi94ed2422011-09-13 18:27:19 -0700798// mRemoteControlClient.setPlaybackState(isPlaying() ?
799// RemoteControlClient.PLAYSTATE_PLAYING : RemoteControlClient.PLAYSTATE_PAUSED);
Marco Nelissen8717d342011-09-08 09:32:51 -0700800 } else if (what.equals(META_CHANGED)) {
Jean-Michel Trivi94ed2422011-09-13 18:27:19 -0700801// RemoteControlClient.MetadataEditor ed = mRemoteControlClient.editMetadata(true);
802// ed.putString(MediaMetadataRetriever.METADATA_KEY_TITLE, getTrackName());
803// ed.putString(MediaMetadataRetriever.METADATA_KEY_ALBUM, getAlbumName());
804// ed.putString(MediaMetadataRetriever.METADATA_KEY_ARTIST, getArtistName());
805// ed.putLong(MediaMetadataRetriever.METADATA_KEY_DURATION, duration());
806// Bitmap b = MusicUtils.getArtwork(this, getAudioId(), getAlbumId(), false);
807// if (b != null) {
808// ed.putBitmap(MetadataEditor.BITMAP_KEY_ARTWORK, b);
809// }
810// ed.apply();
Marco Nelissen8717d342011-09-08 09:32:51 -0700811 }
812
The Android Open Source Project792a2202009-03-03 19:32:30 -0800813 if (what.equals(QUEUE_CHANGED)) {
814 saveQueue(true);
815 } else {
816 saveQueue(false);
817 }
818
The Android Open Source Project490384b2009-03-11 12:11:59 -0700819 // Share this notification directly with our widgets
820 mAppWidgetProvider.notifyChange(this, what);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800821 }
822
823 private void ensurePlayListCapacity(int size) {
824 if (mPlayList == null || size > mPlayList.length) {
825 // reallocate at 2x requested size so we don't
826 // need to grow and copy the array for every
827 // insert
Marco Nelissenbd447b62009-06-29 14:52:05 -0700828 long [] newlist = new long[size * 2];
Marco Nelissenbf0ea142009-06-05 12:46:09 -0700829 int len = mPlayList != null ? mPlayList.length : mPlayListLen;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800830 for (int i = 0; i < len; i++) {
831 newlist[i] = mPlayList[i];
832 }
833 mPlayList = newlist;
834 }
835 // FIXME: shrink the array when the needed size is much smaller
836 // than the allocated size
837 }
838
839 // insert the list of songs at the specified position in the playlist
Marco Nelissenbd447b62009-06-29 14:52:05 -0700840 private void addToPlayList(long [] list, int position) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800841 int addlen = list.length;
842 if (position < 0) { // overwrite
843 mPlayListLen = 0;
844 position = 0;
845 }
846 ensurePlayListCapacity(mPlayListLen + addlen);
847 if (position > mPlayListLen) {
848 position = mPlayListLen;
849 }
850
851 // move part of list after insertion point
852 int tailsize = mPlayListLen - position;
853 for (int i = tailsize ; i > 0 ; i--) {
854 mPlayList[position + i] = mPlayList[position + i - addlen];
855 }
856
857 // copy list into playlist
858 for (int i = 0; i < addlen; i++) {
859 mPlayList[position + i] = list[i];
860 }
861 mPlayListLen += addlen;
Marco Nelissen3aa9ad02010-09-16 13:23:11 -0700862 if (mPlayListLen == 0) {
863 mCursor.close();
864 mCursor = null;
865 notifyChange(META_CHANGED);
866 }
The Android Open Source Project792a2202009-03-03 19:32:30 -0800867 }
868
869 /**
870 * Appends a list of tracks to the current playlist.
871 * If nothing is playing currently, playback will be started at
872 * the first track.
873 * If the action is NOW, playback will switch to the first of
874 * the new tracks immediately.
875 * @param list The list of tracks to append.
876 * @param action NOW, NEXT or LAST
877 */
Marco Nelissenbd447b62009-06-29 14:52:05 -0700878 public void enqueue(long [] list, int action) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800879 synchronized(this) {
880 if (action == NEXT && mPlayPos + 1 < mPlayListLen) {
881 addToPlayList(list, mPlayPos + 1);
882 notifyChange(QUEUE_CHANGED);
883 } else {
884 // action == LAST || action == NOW || mPlayPos + 1 == mPlayListLen
885 addToPlayList(list, Integer.MAX_VALUE);
886 notifyChange(QUEUE_CHANGED);
887 if (action == NOW) {
888 mPlayPos = mPlayListLen - list.length;
Marco Nelissene41bd182012-03-14 08:24:40 -0700889 openCurrentAndNext();
The Android Open Source Project792a2202009-03-03 19:32:30 -0800890 play();
891 notifyChange(META_CHANGED);
892 return;
893 }
894 }
895 if (mPlayPos < 0) {
896 mPlayPos = 0;
Marco Nelissene41bd182012-03-14 08:24:40 -0700897 openCurrentAndNext();
The Android Open Source Project792a2202009-03-03 19:32:30 -0800898 play();
899 notifyChange(META_CHANGED);
900 }
901 }
902 }
903
904 /**
905 * Replaces the current playlist with a new list,
906 * and prepares for starting playback at the specified
907 * position in the list, or a random position if the
908 * specified position is 0.
909 * @param list The new list of tracks.
910 */
Marco Nelissenbd447b62009-06-29 14:52:05 -0700911 public void open(long [] list, int position) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800912 synchronized (this) {
913 if (mShuffleMode == SHUFFLE_AUTO) {
914 mShuffleMode = SHUFFLE_NORMAL;
915 }
Marco Nelissenbd447b62009-06-29 14:52:05 -0700916 long oldId = getAudioId();
The Android Open Source Project792a2202009-03-03 19:32:30 -0800917 int listlength = list.length;
918 boolean newlist = true;
919 if (mPlayListLen == listlength) {
920 // possible fast path: list might be the same
921 newlist = false;
922 for (int i = 0; i < listlength; i++) {
923 if (list[i] != mPlayList[i]) {
924 newlist = true;
925 break;
926 }
927 }
928 }
929 if (newlist) {
930 addToPlayList(list, -1);
931 notifyChange(QUEUE_CHANGED);
932 }
933 int oldpos = mPlayPos;
934 if (position >= 0) {
935 mPlayPos = position;
936 } else {
937 mPlayPos = mRand.nextInt(mPlayListLen);
938 }
939 mHistory.clear();
940
941 saveBookmarkIfNeeded();
Marco Nelissene41bd182012-03-14 08:24:40 -0700942 openCurrentAndNext();
Jeffrey Sharkeyd8c69672009-03-24 17:59:05 -0700943 if (oldId != getAudioId()) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800944 notifyChange(META_CHANGED);
945 }
946 }
947 }
948
949 /**
950 * Moves the item at index1 to index2.
951 * @param index1
952 * @param index2
953 */
954 public void moveQueueItem(int index1, int index2) {
955 synchronized (this) {
956 if (index1 >= mPlayListLen) {
957 index1 = mPlayListLen - 1;
958 }
959 if (index2 >= mPlayListLen) {
960 index2 = mPlayListLen - 1;
961 }
962 if (index1 < index2) {
Marco Nelissenbd447b62009-06-29 14:52:05 -0700963 long tmp = mPlayList[index1];
The Android Open Source Project792a2202009-03-03 19:32:30 -0800964 for (int i = index1; i < index2; i++) {
965 mPlayList[i] = mPlayList[i+1];
966 }
967 mPlayList[index2] = tmp;
968 if (mPlayPos == index1) {
969 mPlayPos = index2;
970 } else if (mPlayPos >= index1 && mPlayPos <= index2) {
971 mPlayPos--;
972 }
973 } else if (index2 < index1) {
Marco Nelissenbd447b62009-06-29 14:52:05 -0700974 long tmp = mPlayList[index1];
The Android Open Source Project792a2202009-03-03 19:32:30 -0800975 for (int i = index1; i > index2; i--) {
976 mPlayList[i] = mPlayList[i-1];
977 }
978 mPlayList[index2] = tmp;
979 if (mPlayPos == index1) {
980 mPlayPos = index2;
981 } else if (mPlayPos >= index2 && mPlayPos <= index1) {
982 mPlayPos++;
983 }
984 }
985 notifyChange(QUEUE_CHANGED);
986 }
987 }
988
989 /**
990 * Returns the current play list
991 * @return An array of integers containing the IDs of the tracks in the play list
992 */
Marco Nelissenbd447b62009-06-29 14:52:05 -0700993 public long [] getQueue() {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800994 synchronized (this) {
995 int len = mPlayListLen;
Marco Nelissenbd447b62009-06-29 14:52:05 -0700996 long [] list = new long[len];
The Android Open Source Project792a2202009-03-03 19:32:30 -0800997 for (int i = 0; i < len; i++) {
998 list[i] = mPlayList[i];
999 }
1000 return list;
1001 }
1002 }
1003
Marco Nelissene41bd182012-03-14 08:24:40 -07001004 private Cursor getCursorForId(long lid) {
1005 String id = String.valueOf(lid);
1006
1007 Cursor c = getContentResolver().query(
1008 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
1009 mCursorCols, "_id=" + id , null, null);
1010 c.moveToFirst();
1011 return c;
1012 }
1013
1014 private void openCurrentAndNext() {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001015 synchronized (this) {
1016 if (mCursor != null) {
1017 mCursor.close();
1018 mCursor = null;
1019 }
Marco Nelissen8d08ec22010-05-10 14:05:24 -07001020
The Android Open Source Project792a2202009-03-03 19:32:30 -08001021 if (mPlayListLen == 0) {
1022 return;
1023 }
1024 stop(false);
1025
Marco Nelissen90d1f622012-04-05 12:27:56 -07001026 mCursor = getCursorForId(mPlayList[mPlayPos]);
1027 while(!open(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + "/" + mCursor.getLong(IDCOLIDX))) {
1028 if (mOpenFailedCounter++ < 10 && mPlayListLen > 1) {
1029 int pos = getNextPosition(false);
1030 if (pos < 0) {
1031 gotoIdleState();
1032 if (mIsSupposedToBePlaying) {
1033 mIsSupposedToBePlaying = false;
1034 notifyChange(PLAYSTATE_CHANGED);
1035 }
1036 return;
1037 }
1038 mPlayPos = pos;
1039 stop(false);
1040 mPlayPos = pos;
1041 mCursor = getCursorForId(mPlayList[mPlayPos]);
1042 } else {
1043 mOpenFailedCounter = 0;
1044 if (!mQuietMode) {
1045 Toast.makeText(this, R.string.playback_failed, Toast.LENGTH_SHORT).show();
1046 }
1047 Log.d(LOGTAG, "Failed to open file for playback");
1048 return;
Marco Nelissene41bd182012-03-14 08:24:40 -07001049 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08001050 }
Marco Nelissen90d1f622012-04-05 12:27:56 -07001051
1052 // go to bookmark if needed
1053 if (isPodcast()) {
1054 long bookmark = getBookmark();
1055 // Start playing a little bit before the bookmark,
1056 // so it's easier to get back in to the narrative.
1057 seek(bookmark - 5000);
1058 }
1059 setNextTrack();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001060 }
1061 }
1062
Marco Nelissene41bd182012-03-14 08:24:40 -07001063 private void setNextTrack() {
1064 mNextPlayPos = getNextPosition(false);
1065 if (mNextPlayPos >= 0) {
1066 long id = mPlayList[mNextPlayPos];
1067 mPlayer.setNextDataSource(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + "/" + id);
1068 }
1069 }
1070
The Android Open Source Project792a2202009-03-03 19:32:30 -08001071 /**
1072 * Opens the specified file and readies it for playback.
1073 *
1074 * @param path The full path of the file to be opened.
The Android Open Source Project792a2202009-03-03 19:32:30 -08001075 */
Marco Nelissen90d1f622012-04-05 12:27:56 -07001076 public boolean open(String path) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001077 synchronized (this) {
1078 if (path == null) {
Marco Nelissen90d1f622012-04-05 12:27:56 -07001079 return false;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001080 }
1081
The Android Open Source Project792a2202009-03-03 19:32:30 -08001082 // if mCursor is null, try to associate path with a database cursor
1083 if (mCursor == null) {
1084
1085 ContentResolver resolver = getContentResolver();
1086 Uri uri;
1087 String where;
1088 String selectionArgs[];
1089 if (path.startsWith("content://media/")) {
1090 uri = Uri.parse(path);
1091 where = null;
1092 selectionArgs = null;
1093 } else {
1094 uri = MediaStore.Audio.Media.getContentUriForPath(path);
1095 where = MediaStore.Audio.Media.DATA + "=?";
1096 selectionArgs = new String[] { path };
1097 }
1098
1099 try {
1100 mCursor = resolver.query(uri, mCursorCols, where, selectionArgs, null);
1101 if (mCursor != null) {
1102 if (mCursor.getCount() == 0) {
1103 mCursor.close();
1104 mCursor = null;
1105 } else {
1106 mCursor.moveToNext();
1107 ensurePlayListCapacity(1);
1108 mPlayListLen = 1;
Marco Nelissenbd447b62009-06-29 14:52:05 -07001109 mPlayList[0] = mCursor.getLong(IDCOLIDX);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001110 mPlayPos = 0;
1111 }
1112 }
1113 } catch (UnsupportedOperationException ex) {
1114 }
1115 }
1116 mFileToPlay = path;
1117 mPlayer.setDataSource(mFileToPlay);
Marco Nelissen90d1f622012-04-05 12:27:56 -07001118 if (mPlayer.isInitialized()) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001119 mOpenFailedCounter = 0;
Marco Nelissen90d1f622012-04-05 12:27:56 -07001120 return true;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001121 }
Marco Nelissen90d1f622012-04-05 12:27:56 -07001122 stop(true);
1123 return false;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001124 }
1125 }
1126
1127 /**
1128 * Starts playback of a previously opened file.
1129 */
1130 public void play() {
Jean-Michel Trivi085cbfd2010-03-08 17:06:18 -08001131 mAudioManager.requestAudioFocus(mAudioFocusListener, AudioManager.STREAM_MUSIC,
1132 AudioManager.AUDIOFOCUS_GAIN);
Jean-Michel Trivi3d22fc22010-03-17 11:46:58 -07001133 mAudioManager.registerMediaButtonEventReceiver(new ComponentName(this.getPackageName(),
1134 MediaButtonIntentReceiver.class.getName()));
Jean-Michel Trivi085cbfd2010-03-08 17:06:18 -08001135
The Android Open Source Project792a2202009-03-03 19:32:30 -08001136 if (mPlayer.isInitialized()) {
Thomas Tuttle272eb782009-01-28 21:06:46 -05001137 // if we are at the end of the song, go to the next song first
Marco Nelissen2f9a1ce2009-06-26 14:23:31 -07001138 long duration = mPlayer.duration();
1139 if (mRepeatMode != REPEAT_CURRENT && duration > 2000 &&
1140 mPlayer.position() >= duration - 2000) {
Marco Nelissenbd0d2f12012-02-27 16:02:15 -08001141 gotoNext(true);
Thomas Tuttle272eb782009-01-28 21:06:46 -05001142 }
1143
The Android Open Source Project792a2202009-03-03 19:32:30 -08001144 mPlayer.start();
Marco Nelissen7181da82010-12-01 16:39:04 -08001145 // make sure we fade in, in case a previous fadein was stopped because
1146 // of another focus loss
Marco Nelissen7bae28f2010-12-02 10:04:54 -08001147 mMediaplayerHandler.removeMessages(FADEDOWN);
Marco Nelissen7181da82010-12-01 16:39:04 -08001148 mMediaplayerHandler.sendEmptyMessage(FADEUP);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001149
Marco Nelissene41bd182012-03-14 08:24:40 -07001150 updateNotification();
Marco Nelissenc1333372009-05-06 12:54:47 -07001151 if (!mIsSupposedToBePlaying) {
Mike Cleron347fe572009-10-09 12:26:28 -07001152 mIsSupposedToBePlaying = true;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001153 notifyChange(PLAYSTATE_CHANGED);
1154 }
Mike Cleron347fe572009-10-09 12:26:28 -07001155
The Android Open Source Project792a2202009-03-03 19:32:30 -08001156 } else if (mPlayListLen <= 0) {
1157 // This is mostly so that if you press 'play' on a bluetooth headset
1158 // without every having played anything before, it will still play
1159 // something.
1160 setShuffleMode(SHUFFLE_AUTO);
1161 }
1162 }
Marco Nelissene41bd182012-03-14 08:24:40 -07001163
1164 private void updateNotification() {
1165 RemoteViews views = new RemoteViews(getPackageName(), R.layout.statusbar);
1166 views.setImageViewResource(R.id.icon, R.drawable.stat_notify_musicplayer);
1167 if (getAudioId() < 0) {
1168 // streaming
1169 views.setTextViewText(R.id.trackname, getPath());
1170 views.setTextViewText(R.id.artistalbum, null);
1171 } else {
1172 String artist = getArtistName();
1173 views.setTextViewText(R.id.trackname, getTrackName());
1174 if (artist == null || artist.equals(MediaStore.UNKNOWN_STRING)) {
1175 artist = getString(R.string.unknown_artist_name);
1176 }
1177 String album = getAlbumName();
1178 if (album == null || album.equals(MediaStore.UNKNOWN_STRING)) {
1179 album = getString(R.string.unknown_album_name);
1180 }
1181
1182 views.setTextViewText(R.id.artistalbum,
1183 getString(R.string.notification_artist_album, artist, album)
1184 );
1185 }
1186 Notification status = new Notification();
1187 status.contentView = views;
1188 status.flags |= Notification.FLAG_ONGOING_EVENT;
1189 status.icon = R.drawable.stat_notify_musicplayer;
1190 status.contentIntent = PendingIntent.getActivity(this, 0,
1191 new Intent("com.android.music.PLAYBACK_VIEWER")
1192 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK), 0);
1193 startForeground(PLAYBACKSERVICE_STATUS, status);
1194 }
1195
The Android Open Source Project792a2202009-03-03 19:32:30 -08001196 private void stop(boolean remove_status_icon) {
1197 if (mPlayer.isInitialized()) {
1198 mPlayer.stop();
1199 }
1200 mFileToPlay = null;
1201 if (mCursor != null) {
1202 mCursor.close();
1203 mCursor = null;
1204 }
1205 if (remove_status_icon) {
1206 gotoIdleState();
Dianne Hackbornd5fc5b62009-08-18 11:35:30 -07001207 } else {
1208 stopForeground(false);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001209 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08001210 if (remove_status_icon) {
Marco Nelissenc1333372009-05-06 12:54:47 -07001211 mIsSupposedToBePlaying = false;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001212 }
1213 }
1214
1215 /**
1216 * Stops playback.
1217 */
1218 public void stop() {
1219 stop(true);
1220 }
1221
1222 /**
1223 * Pauses playback (call play() to resume)
1224 */
1225 public void pause() {
Marco Nelissen407cf912009-05-11 09:56:31 -07001226 synchronized(this) {
Marco Nelissen7181da82010-12-01 16:39:04 -08001227 mMediaplayerHandler.removeMessages(FADEUP);
Marco Nelissen407cf912009-05-11 09:56:31 -07001228 if (isPlaying()) {
1229 mPlayer.pause();
1230 gotoIdleState();
Marco Nelissen407cf912009-05-11 09:56:31 -07001231 mIsSupposedToBePlaying = false;
1232 notifyChange(PLAYSTATE_CHANGED);
1233 saveBookmarkIfNeeded();
1234 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08001235 }
1236 }
1237
Marco Nelissenb6e7bf72009-05-11 10:28:31 -07001238 /** Returns whether something is currently playing
The Android Open Source Project792a2202009-03-03 19:32:30 -08001239 *
Marco Nelissenb6e7bf72009-05-11 10:28:31 -07001240 * @return true if something is playing (or will be playing shortly, in case
1241 * we're currently transitioning between tracks), false if not.
The Android Open Source Project792a2202009-03-03 19:32:30 -08001242 */
1243 public boolean isPlaying() {
Marco Nelissenc1333372009-05-06 12:54:47 -07001244 return mIsSupposedToBePlaying;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001245 }
1246
1247 /*
1248 Desired behavior for prev/next/shuffle:
1249
1250 - NEXT will move to the next track in the list when not shuffling, and to
1251 a track randomly picked from the not-yet-played tracks when shuffling.
1252 If all tracks have already been played, pick from the full set, but
1253 avoid picking the previously played track if possible.
1254 - when shuffling, PREV will go to the previously played track. Hitting PREV
1255 again will go to the track played before that, etc. When the start of the
1256 history has been reached, PREV is a no-op.
1257 When not shuffling, PREV will go to the sequentially previous track (the
1258 difference with the shuffle-case is mainly that when not shuffling, the
1259 user can back up to tracks that are not in the history).
1260
1261 Example:
1262 When playing an album with 10 tracks from the start, and enabling shuffle
1263 while playing track 5, the remaining tracks (6-10) will be shuffled, e.g.
1264 the final play order might be 1-2-3-4-5-8-10-6-9-7.
1265 When hitting 'prev' 8 times while playing track 7 in this example, the
1266 user will go to tracks 9-6-10-8-5-4-3-2. If the user then hits 'next',
1267 a random track will be picked again. If at any time user disables shuffling
1268 the next/previous track will be picked in sequential order again.
1269 */
1270
1271 public void prev() {
1272 synchronized (this) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001273 if (mShuffleMode == SHUFFLE_NORMAL) {
1274 // go to previously-played track and remove it from the history
1275 int histsize = mHistory.size();
1276 if (histsize == 0) {
1277 // prev is a no-op
1278 return;
1279 }
1280 Integer pos = mHistory.remove(histsize - 1);
1281 mPlayPos = pos.intValue();
1282 } else {
1283 if (mPlayPos > 0) {
1284 mPlayPos--;
1285 } else {
1286 mPlayPos = mPlayListLen - 1;
1287 }
1288 }
1289 saveBookmarkIfNeeded();
1290 stop(false);
Marco Nelissene41bd182012-03-14 08:24:40 -07001291 openCurrentAndNext();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001292 play();
1293 notifyChange(META_CHANGED);
1294 }
1295 }
1296
Marco Nelissenbd0d2f12012-02-27 16:02:15 -08001297 /**
1298 * Get the next position to play. Note that this may actually modify mPlayPos
1299 * if playback is in SHUFFLE_AUTO mode and the shuffle list window needed to
1300 * be adjusted. Either way, the return value is the next value that should be
1301 * assigned to mPlayPos;
1302 */
1303 private int getNextPosition(boolean force) {
Marco Nelissene41bd182012-03-14 08:24:40 -07001304 if (mRepeatMode == REPEAT_CURRENT) {
1305 if (mPlayPos < 0) return 0;
1306 return mPlayPos;
1307 } else if (mShuffleMode == SHUFFLE_NORMAL) {
Marco Nelissenbd0d2f12012-02-27 16:02:15 -08001308 // Pick random next track from the not-yet-played ones
1309 // TODO: make it work right after adding/removing items in the queue.
1310
1311 // Store the current file in the history, but keep the history at a
1312 // reasonable size
1313 if (mPlayPos >= 0) {
1314 mHistory.add(mPlayPos);
1315 }
1316 if (mHistory.size() > MAX_HISTORY_SIZE) {
1317 mHistory.removeElementAt(0);
1318 }
1319
1320 int numTracks = mPlayListLen;
1321 int[] tracks = new int[numTracks];
1322 for (int i=0;i < numTracks; i++) {
1323 tracks[i] = i;
1324 }
1325
1326 int numHistory = mHistory.size();
1327 int numUnplayed = numTracks;
1328 for (int i=0;i < numHistory; i++) {
1329 int idx = mHistory.get(i).intValue();
1330 if (idx < numTracks && tracks[idx] >= 0) {
1331 numUnplayed--;
1332 tracks[idx] = -1;
1333 }
1334 }
1335
1336 // 'numUnplayed' now indicates how many tracks have not yet
1337 // been played, and 'tracks' contains the indices of those
1338 // tracks.
1339 if (numUnplayed <=0) {
1340 // everything's already been played
1341 if (mRepeatMode == REPEAT_ALL || force) {
1342 //pick from full set
1343 numUnplayed = numTracks;
1344 for (int i=0;i < numTracks; i++) {
1345 tracks[i] = i;
1346 }
1347 } else {
1348 // all done
1349 return -1;
1350 }
1351 }
1352 int skip = mRand.nextInt(numUnplayed);
1353 int cnt = -1;
1354 while (true) {
1355 while (tracks[++cnt] < 0)
1356 ;
1357 skip--;
1358 if (skip < 0) {
1359 break;
1360 }
1361 }
1362 return cnt;
1363 } else if (mShuffleMode == SHUFFLE_AUTO) {
1364 doAutoShuffleUpdate();
1365 return mPlayPos + 1;
1366 } else {
1367 if (mPlayPos >= mPlayListLen - 1) {
1368 // we're at the end of the list
1369 if (mRepeatMode == REPEAT_NONE && !force) {
1370 // all done
1371 return -1;
1372 } else if (mRepeatMode == REPEAT_ALL || force) {
1373 return 0;
1374 }
1375 return -1;
1376 } else {
1377 return mPlayPos + 1;
1378 }
1379 }
1380 }
1381
1382 public void gotoNext(boolean force) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001383 synchronized (this) {
Marco Nelissen663fea32009-06-12 13:39:27 -07001384 if (mPlayListLen <= 0) {
Marco Nelissene99341f2009-11-11 11:13:51 -08001385 Log.d(LOGTAG, "No play queue");
Marco Nelissen663fea32009-06-12 13:39:27 -07001386 return;
1387 }
1388
Marco Nelissenbd0d2f12012-02-27 16:02:15 -08001389 int pos = getNextPosition(force);
1390 if (pos < 0) {
1391 gotoIdleState();
1392 if (mIsSupposedToBePlaying) {
1393 mIsSupposedToBePlaying = false;
1394 notifyChange(PLAYSTATE_CHANGED);
Marco Nelissen3f502de2010-09-28 15:07:29 -07001395 }
Marco Nelissenbd0d2f12012-02-27 16:02:15 -08001396 return;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001397 }
Marco Nelissenbd0d2f12012-02-27 16:02:15 -08001398 mPlayPos = pos;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001399 saveBookmarkIfNeeded();
1400 stop(false);
Marco Nelissene41bd182012-03-14 08:24:40 -07001401 mPlayPos = pos;
1402 openCurrentAndNext();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001403 play();
1404 notifyChange(META_CHANGED);
1405 }
1406 }
1407
1408 private void gotoIdleState() {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001409 mDelayedStopHandler.removeCallbacksAndMessages(null);
1410 Message msg = mDelayedStopHandler.obtainMessage();
1411 mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
Dianne Hackbornd5fc5b62009-08-18 11:35:30 -07001412 stopForeground(true);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001413 }
1414
1415 private void saveBookmarkIfNeeded() {
1416 try {
1417 if (isPodcast()) {
1418 long pos = position();
1419 long bookmark = getBookmark();
1420 long duration = duration();
1421 if ((pos < bookmark && (pos + 10000) > bookmark) ||
1422 (pos > bookmark && (pos - 10000) < bookmark)) {
1423 // The existing bookmark is close to the current
1424 // position, so don't update it.
1425 return;
1426 }
1427 if (pos < 15000 || (pos + 10000) > duration) {
1428 // if we're near the start or end, clear the bookmark
1429 pos = 0;
1430 }
1431
1432 // write 'pos' to the bookmark field
1433 ContentValues values = new ContentValues();
1434 values.put(MediaStore.Audio.Media.BOOKMARK, pos);
1435 Uri uri = ContentUris.withAppendedId(
1436 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, mCursor.getLong(IDCOLIDX));
1437 getContentResolver().update(uri, values, null, null);
1438 }
1439 } catch (SQLiteException ex) {
1440 }
1441 }
1442
1443 // Make sure there are at least 5 items after the currently playing item
1444 // and no more than 10 items before.
1445 private void doAutoShuffleUpdate() {
1446 boolean notify = false;
Marco Nelissen3f502de2010-09-28 15:07:29 -07001447
The Android Open Source Project792a2202009-03-03 19:32:30 -08001448 // remove old entries
1449 if (mPlayPos > 10) {
1450 removeTracks(0, mPlayPos - 9);
1451 notify = true;
1452 }
1453 // add new entries if needed
1454 int to_add = 7 - (mPlayListLen - (mPlayPos < 0 ? -1 : mPlayPos));
1455 for (int i = 0; i < to_add; i++) {
1456 // pick something at random from the list
Marco Nelissen3f502de2010-09-28 15:07:29 -07001457
1458 int lookback = mHistory.size();
1459 int idx = -1;
1460 while(true) {
1461 idx = mRand.nextInt(mAutoShuffleList.length);
1462 if (!wasRecentlyUsed(idx, lookback)) {
1463 break;
1464 }
1465 lookback /= 2;
1466 }
1467 mHistory.add(idx);
1468 if (mHistory.size() > MAX_HISTORY_SIZE) {
1469 mHistory.remove(0);
1470 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08001471 ensurePlayListCapacity(mPlayListLen + 1);
Marco Nelissen3f502de2010-09-28 15:07:29 -07001472 mPlayList[mPlayListLen++] = mAutoShuffleList[idx];
The Android Open Source Project792a2202009-03-03 19:32:30 -08001473 notify = true;
1474 }
1475 if (notify) {
1476 notifyChange(QUEUE_CHANGED);
1477 }
1478 }
1479
Marco Nelissen3f502de2010-09-28 15:07:29 -07001480 // check that the specified idx is not in the history (but only look at at
1481 // most lookbacksize entries in the history)
1482 private boolean wasRecentlyUsed(int idx, int lookbacksize) {
1483
1484 // early exit to prevent infinite loops in case idx == mPlayPos
1485 if (lookbacksize == 0) {
1486 return false;
1487 }
1488
1489 int histsize = mHistory.size();
1490 if (histsize < lookbacksize) {
1491 Log.d(LOGTAG, "lookback too big");
1492 lookbacksize = histsize;
1493 }
1494 int maxidx = histsize - 1;
1495 for (int i = 0; i < lookbacksize; i++) {
1496 long entry = mHistory.get(maxidx - i);
1497 if (entry == idx) {
1498 return true;
1499 }
1500 }
1501 return false;
1502 }
1503
The Android Open Source Project792a2202009-03-03 19:32:30 -08001504 // A simple variation of Random that makes sure that the
1505 // value it returns is not equal to the value it returned
1506 // previously, unless the interval is 1.
Marco Nelissen756c3f52009-05-14 10:07:23 -07001507 private static class Shuffler {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001508 private int mPrevious;
1509 private Random mRandom = new Random();
1510 public int nextInt(int interval) {
1511 int ret;
1512 do {
1513 ret = mRandom.nextInt(interval);
1514 } while (ret == mPrevious && interval > 1);
1515 mPrevious = ret;
1516 return ret;
1517 }
1518 };
1519
1520 private boolean makeAutoShuffleList() {
1521 ContentResolver res = getContentResolver();
1522 Cursor c = null;
1523 try {
1524 c = res.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
1525 new String[] {MediaStore.Audio.Media._ID}, MediaStore.Audio.Media.IS_MUSIC + "=1",
1526 null, null);
1527 if (c == null || c.getCount() == 0) {
1528 return false;
1529 }
1530 int len = c.getCount();
Marco Nelissenbd447b62009-06-29 14:52:05 -07001531 long [] list = new long[len];
The Android Open Source Project792a2202009-03-03 19:32:30 -08001532 for (int i = 0; i < len; i++) {
1533 c.moveToNext();
Marco Nelissenbd447b62009-06-29 14:52:05 -07001534 list[i] = c.getLong(0);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001535 }
1536 mAutoShuffleList = list;
1537 return true;
1538 } catch (RuntimeException ex) {
1539 } finally {
1540 if (c != null) {
1541 c.close();
1542 }
1543 }
1544 return false;
1545 }
1546
1547 /**
1548 * Removes the range of tracks specified from the play list. If a file within the range is
1549 * the file currently being played, playback will move to the next file after the
1550 * range.
1551 * @param first The first file to be removed
1552 * @param last The last file to be removed
1553 * @return the number of tracks deleted
1554 */
1555 public int removeTracks(int first, int last) {
1556 int numremoved = removeTracksInternal(first, last);
1557 if (numremoved > 0) {
1558 notifyChange(QUEUE_CHANGED);
1559 }
1560 return numremoved;
1561 }
1562
1563 private int removeTracksInternal(int first, int last) {
1564 synchronized (this) {
1565 if (last < first) return 0;
1566 if (first < 0) first = 0;
1567 if (last >= mPlayListLen) last = mPlayListLen - 1;
1568
1569 boolean gotonext = false;
1570 if (first <= mPlayPos && mPlayPos <= last) {
1571 mPlayPos = first;
1572 gotonext = true;
1573 } else if (mPlayPos > last) {
1574 mPlayPos -= (last - first + 1);
1575 }
1576 int num = mPlayListLen - last - 1;
1577 for (int i = 0; i < num; i++) {
1578 mPlayList[first + i] = mPlayList[last + 1 + i];
1579 }
1580 mPlayListLen -= last - first + 1;
1581
1582 if (gotonext) {
1583 if (mPlayListLen == 0) {
1584 stop(true);
1585 mPlayPos = -1;
Marco Nelissen3aa9ad02010-09-16 13:23:11 -07001586 if (mCursor != null) {
1587 mCursor.close();
1588 mCursor = null;
1589 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08001590 } else {
1591 if (mPlayPos >= mPlayListLen) {
1592 mPlayPos = 0;
1593 }
1594 boolean wasPlaying = isPlaying();
1595 stop(false);
Marco Nelissene41bd182012-03-14 08:24:40 -07001596 openCurrentAndNext();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001597 if (wasPlaying) {
1598 play();
1599 }
1600 }
Marco Nelissen3aa9ad02010-09-16 13:23:11 -07001601 notifyChange(META_CHANGED);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001602 }
1603 return last - first + 1;
1604 }
1605 }
1606
1607 /**
1608 * Removes all instances of the track with the given id
1609 * from the playlist.
1610 * @param id The id to be removed
1611 * @return how many instances of the track were removed
1612 */
Marco Nelissenbd447b62009-06-29 14:52:05 -07001613 public int removeTrack(long id) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001614 int numremoved = 0;
1615 synchronized (this) {
1616 for (int i = 0; i < mPlayListLen; i++) {
1617 if (mPlayList[i] == id) {
1618 numremoved += removeTracksInternal(i, i);
1619 i--;
1620 }
1621 }
1622 }
1623 if (numremoved > 0) {
1624 notifyChange(QUEUE_CHANGED);
1625 }
1626 return numremoved;
1627 }
1628
1629 public void setShuffleMode(int shufflemode) {
1630 synchronized(this) {
1631 if (mShuffleMode == shufflemode && mPlayListLen > 0) {
1632 return;
1633 }
1634 mShuffleMode = shufflemode;
1635 if (mShuffleMode == SHUFFLE_AUTO) {
1636 if (makeAutoShuffleList()) {
1637 mPlayListLen = 0;
1638 doAutoShuffleUpdate();
1639 mPlayPos = 0;
Marco Nelissene41bd182012-03-14 08:24:40 -07001640 openCurrentAndNext();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001641 play();
1642 notifyChange(META_CHANGED);
1643 return;
1644 } else {
1645 // failed to build a list of files to shuffle
1646 mShuffleMode = SHUFFLE_NONE;
1647 }
1648 }
1649 saveQueue(false);
1650 }
1651 }
1652 public int getShuffleMode() {
1653 return mShuffleMode;
1654 }
1655
1656 public void setRepeatMode(int repeatmode) {
1657 synchronized(this) {
1658 mRepeatMode = repeatmode;
Marco Nelissene41bd182012-03-14 08:24:40 -07001659 setNextTrack();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001660 saveQueue(false);
1661 }
1662 }
1663 public int getRepeatMode() {
1664 return mRepeatMode;
1665 }
1666
1667 public int getMediaMountedCount() {
1668 return mMediaMountedCount;
1669 }
1670
1671 /**
1672 * Returns the path of the currently playing file, or null if
1673 * no file is currently playing.
1674 */
1675 public String getPath() {
1676 return mFileToPlay;
1677 }
1678
1679 /**
1680 * Returns the rowid of the currently playing file, or -1 if
1681 * no file is currently playing.
1682 */
Marco Nelissenbd447b62009-06-29 14:52:05 -07001683 public long getAudioId() {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001684 synchronized (this) {
1685 if (mPlayPos >= 0 && mPlayer.isInitialized()) {
1686 return mPlayList[mPlayPos];
1687 }
1688 }
1689 return -1;
1690 }
1691
1692 /**
1693 * Returns the position in the queue
1694 * @return the position in the queue
1695 */
1696 public int getQueuePosition() {
1697 synchronized(this) {
1698 return mPlayPos;
1699 }
1700 }
1701
1702 /**
1703 * Starts playing the track at the given position in the queue.
1704 * @param pos The position in the queue of the track that will be played.
1705 */
1706 public void setQueuePosition(int pos) {
1707 synchronized(this) {
1708 stop(false);
1709 mPlayPos = pos;
Marco Nelissene41bd182012-03-14 08:24:40 -07001710 openCurrentAndNext();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001711 play();
1712 notifyChange(META_CHANGED);
Marco Nelissenec0c57a2009-12-12 12:27:11 -08001713 if (mShuffleMode == SHUFFLE_AUTO) {
1714 doAutoShuffleUpdate();
1715 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08001716 }
1717 }
1718
1719 public String getArtistName() {
1720 synchronized(this) {
1721 if (mCursor == null) {
1722 return null;
1723 }
1724 return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST));
1725 }
1726 }
1727
Marco Nelissenbd447b62009-06-29 14:52:05 -07001728 public long getArtistId() {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001729 synchronized (this) {
1730 if (mCursor == null) {
1731 return -1;
1732 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07001733 return mCursor.getLong(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST_ID));
The Android Open Source Project792a2202009-03-03 19:32:30 -08001734 }
1735 }
1736
1737 public String getAlbumName() {
1738 synchronized (this) {
1739 if (mCursor == null) {
1740 return null;
1741 }
1742 return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM));
1743 }
1744 }
1745
Marco Nelissenbd447b62009-06-29 14:52:05 -07001746 public long getAlbumId() {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001747 synchronized (this) {
1748 if (mCursor == null) {
1749 return -1;
1750 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07001751 return mCursor.getLong(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM_ID));
The Android Open Source Project792a2202009-03-03 19:32:30 -08001752 }
1753 }
1754
1755 public String getTrackName() {
1756 synchronized (this) {
1757 if (mCursor == null) {
1758 return null;
1759 }
1760 return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE));
1761 }
1762 }
1763
1764 private boolean isPodcast() {
1765 synchronized (this) {
1766 if (mCursor == null) {
1767 return false;
1768 }
1769 return (mCursor.getInt(PODCASTCOLIDX) > 0);
1770 }
1771 }
1772
1773 private long getBookmark() {
1774 synchronized (this) {
1775 if (mCursor == null) {
1776 return 0;
1777 }
1778 return mCursor.getLong(BOOKMARKCOLIDX);
1779 }
1780 }
1781
1782 /**
1783 * Returns the duration of the file in milliseconds.
1784 * Currently this method returns -1 for the duration of MIDI files.
1785 */
1786 public long duration() {
1787 if (mPlayer.isInitialized()) {
1788 return mPlayer.duration();
1789 }
1790 return -1;
1791 }
1792
1793 /**
1794 * Returns the current playback position in milliseconds
1795 */
1796 public long position() {
1797 if (mPlayer.isInitialized()) {
1798 return mPlayer.position();
1799 }
1800 return -1;
1801 }
1802
1803 /**
1804 * Seeks to the position specified.
1805 *
1806 * @param pos The position to seek to, in milliseconds
1807 */
1808 public long seek(long pos) {
1809 if (mPlayer.isInitialized()) {
1810 if (pos < 0) pos = 0;
1811 if (pos > mPlayer.duration()) pos = mPlayer.duration();
1812 return mPlayer.seek(pos);
1813 }
1814 return -1;
1815 }
1816
1817 /**
Eric Laurent1cc72a12010-06-28 11:27:01 -07001818 * Sets the audio session ID.
1819 *
1820 * @param sessionId: the audio session ID.
1821 */
1822 public void setAudioSessionId(int sessionId) {
1823 synchronized (this) {
1824 mPlayer.setAudioSessionId(sessionId);
1825 }
1826 }
1827
1828 /**
1829 * Returns the audio session ID.
1830 */
1831 public int getAudioSessionId() {
1832 synchronized (this) {
1833 return mPlayer.getAudioSessionId();
1834 }
1835 }
1836
1837 /**
The Android Open Source Project792a2202009-03-03 19:32:30 -08001838 * Provides a unified interface for dealing with midi files and
1839 * other media files.
1840 */
1841 private class MultiPlayer {
Marco Nelissen30867022012-03-14 16:04:34 -07001842 private CompatMediaPlayer mCurrentMediaPlayer = new CompatMediaPlayer();
1843 private CompatMediaPlayer mNextMediaPlayer;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001844 private Handler mHandler;
1845 private boolean mIsInitialized = false;
1846
1847 public MultiPlayer() {
Marco Nelissene41bd182012-03-14 08:24:40 -07001848 mCurrentMediaPlayer.setWakeMode(MediaPlaybackService.this, PowerManager.PARTIAL_WAKE_LOCK);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001849 }
1850
The Android Open Source Project792a2202009-03-03 19:32:30 -08001851 public void setDataSource(String path) {
Marco Nelissen90d1f622012-04-05 12:27:56 -07001852 mIsInitialized = setDataSourceImpl(mCurrentMediaPlayer, path);
1853 if (mIsInitialized) {
1854 setNextDataSource(null);
1855 }
Marco Nelissene41bd182012-03-14 08:24:40 -07001856 }
1857
Marco Nelissen90d1f622012-04-05 12:27:56 -07001858 private boolean setDataSourceImpl(MediaPlayer player, String path) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001859 try {
Marco Nelissene41bd182012-03-14 08:24:40 -07001860 player.reset();
1861 player.setOnPreparedListener(null);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001862 if (path.startsWith("content://")) {
Marco Nelissene41bd182012-03-14 08:24:40 -07001863 player.setDataSource(MediaPlaybackService.this, Uri.parse(path));
The Android Open Source Project792a2202009-03-03 19:32:30 -08001864 } else {
Marco Nelissene41bd182012-03-14 08:24:40 -07001865 player.setDataSource(path);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001866 }
Marco Nelissene41bd182012-03-14 08:24:40 -07001867 player.setAudioStreamType(AudioManager.STREAM_MUSIC);
1868 player.prepare();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001869 } catch (IOException ex) {
1870 // TODO: notify the user why the file couldn't be opened
Marco Nelissen90d1f622012-04-05 12:27:56 -07001871 return false;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001872 } catch (IllegalArgumentException ex) {
1873 // TODO: notify the user why the file couldn't be opened
Marco Nelissen90d1f622012-04-05 12:27:56 -07001874 return false;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001875 }
Marco Nelissene41bd182012-03-14 08:24:40 -07001876 player.setOnCompletionListener(listener);
1877 player.setOnErrorListener(errorListener);
Marco Nelissenf2ef3b52010-09-21 15:47:27 -07001878 Intent i = new Intent(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION);
1879 i.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, getAudioSessionId());
1880 i.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, getPackageName());
1881 sendBroadcast(i);
Marco Nelissen90d1f622012-04-05 12:27:56 -07001882 return true;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001883 }
Marco Nelissene41bd182012-03-14 08:24:40 -07001884
1885 public void setNextDataSource(String path) {
1886 mCurrentMediaPlayer.setNextMediaPlayer(null);
1887 if (mNextMediaPlayer != null) {
1888 mNextMediaPlayer.release();
1889 mNextMediaPlayer = null;
1890 }
1891 if (path == null) {
1892 return;
1893 }
Marco Nelissen30867022012-03-14 16:04:34 -07001894 mNextMediaPlayer = new CompatMediaPlayer();
Marco Nelissene41bd182012-03-14 08:24:40 -07001895 mNextMediaPlayer.setWakeMode(MediaPlaybackService.this, PowerManager.PARTIAL_WAKE_LOCK);
1896 mNextMediaPlayer.setAudioSessionId(getAudioSessionId());
Marco Nelissen90d1f622012-04-05 12:27:56 -07001897 if (setDataSourceImpl(mNextMediaPlayer, path)) {
1898 mCurrentMediaPlayer.setNextMediaPlayer(mNextMediaPlayer);
1899 } else {
1900 // failed to open next, we'll transition the old fashioned way,
1901 // which will skip over the faulty file
1902 mNextMediaPlayer.release();
1903 mNextMediaPlayer = null;
1904 }
Marco Nelissene41bd182012-03-14 08:24:40 -07001905 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08001906
1907 public boolean isInitialized() {
1908 return mIsInitialized;
1909 }
1910
1911 public void start() {
Marco Nelissen39888902010-03-02 10:27:05 -08001912 MusicUtils.debugLog(new Exception("MultiPlayer.start called"));
Marco Nelissene41bd182012-03-14 08:24:40 -07001913 mCurrentMediaPlayer.start();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001914 }
1915
1916 public void stop() {
Marco Nelissene41bd182012-03-14 08:24:40 -07001917 mCurrentMediaPlayer.reset();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001918 mIsInitialized = false;
1919 }
1920
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001921 /**
1922 * You CANNOT use this player anymore after calling release()
1923 */
1924 public void release() {
1925 stop();
Marco Nelissene41bd182012-03-14 08:24:40 -07001926 mCurrentMediaPlayer.release();
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001927 }
1928
The Android Open Source Project792a2202009-03-03 19:32:30 -08001929 public void pause() {
Marco Nelissene41bd182012-03-14 08:24:40 -07001930 mCurrentMediaPlayer.pause();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001931 }
1932
The Android Open Source Project792a2202009-03-03 19:32:30 -08001933 public void setHandler(Handler handler) {
1934 mHandler = handler;
1935 }
1936
1937 MediaPlayer.OnCompletionListener listener = new MediaPlayer.OnCompletionListener() {
1938 public void onCompletion(MediaPlayer mp) {
Marco Nelissene41bd182012-03-14 08:24:40 -07001939 if (mp == mCurrentMediaPlayer && mNextMediaPlayer != null) {
1940 mCurrentMediaPlayer.release();
1941 mCurrentMediaPlayer = mNextMediaPlayer;
1942 mNextMediaPlayer = null;
1943 mHandler.sendEmptyMessage(TRACK_WENT_TO_NEXT);
1944 } else {
1945 // Acquire a temporary wakelock, since when we return from
1946 // this callback the MediaPlayer will release its wakelock
1947 // and allow the device to go to sleep.
1948 // This temporary wakelock is released when the RELEASE_WAKELOCK
1949 // message is processed, but just in case, put a timeout on it.
1950 mWakeLock.acquire(30000);
1951 mHandler.sendEmptyMessage(TRACK_ENDED);
1952 mHandler.sendEmptyMessage(RELEASE_WAKELOCK);
1953 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08001954 }
1955 };
1956
The Android Open Source Project792a2202009-03-03 19:32:30 -08001957 MediaPlayer.OnErrorListener errorListener = new MediaPlayer.OnErrorListener() {
1958 public boolean onError(MediaPlayer mp, int what, int extra) {
1959 switch (what) {
1960 case MediaPlayer.MEDIA_ERROR_SERVER_DIED:
1961 mIsInitialized = false;
Marco Nelissene41bd182012-03-14 08:24:40 -07001962 mCurrentMediaPlayer.release();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001963 // Creating a new MediaPlayer and settings its wakemode does not
1964 // require the media service, so it's OK to do this now, while the
1965 // service is still being restarted
Marco Nelissen30867022012-03-14 16:04:34 -07001966 mCurrentMediaPlayer = new CompatMediaPlayer();
Marco Nelissene41bd182012-03-14 08:24:40 -07001967 mCurrentMediaPlayer.setWakeMode(MediaPlaybackService.this, PowerManager.PARTIAL_WAKE_LOCK);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001968 mHandler.sendMessageDelayed(mHandler.obtainMessage(SERVER_DIED), 2000);
1969 return true;
1970 default:
Marco Nelissene99341f2009-11-11 11:13:51 -08001971 Log.d("MultiPlayer", "Error: " + what + "," + extra);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001972 break;
1973 }
1974 return false;
1975 }
1976 };
1977
1978 public long duration() {
Marco Nelissene41bd182012-03-14 08:24:40 -07001979 return mCurrentMediaPlayer.getDuration();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001980 }
1981
1982 public long position() {
Marco Nelissene41bd182012-03-14 08:24:40 -07001983 return mCurrentMediaPlayer.getCurrentPosition();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001984 }
1985
1986 public long seek(long whereto) {
Marco Nelissene41bd182012-03-14 08:24:40 -07001987 mCurrentMediaPlayer.seekTo((int) whereto);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001988 return whereto;
1989 }
1990
1991 public void setVolume(float vol) {
Marco Nelissene41bd182012-03-14 08:24:40 -07001992 mCurrentMediaPlayer.setVolume(vol, vol);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001993 }
Eric Laurent1cc72a12010-06-28 11:27:01 -07001994
1995 public void setAudioSessionId(int sessionId) {
Marco Nelissene41bd182012-03-14 08:24:40 -07001996 mCurrentMediaPlayer.setAudioSessionId(sessionId);
Eric Laurent1cc72a12010-06-28 11:27:01 -07001997 }
1998
1999 public int getAudioSessionId() {
Marco Nelissene41bd182012-03-14 08:24:40 -07002000 return mCurrentMediaPlayer.getAudioSessionId();
Eric Laurent1cc72a12010-06-28 11:27:01 -07002001 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08002002 }
2003
Marco Nelissen30867022012-03-14 16:04:34 -07002004 static class CompatMediaPlayer extends MediaPlayer implements OnCompletionListener {
2005
2006 private boolean mCompatMode = true;
2007 private MediaPlayer mNextPlayer;
2008 private OnCompletionListener mCompletion;
2009
2010 public CompatMediaPlayer() {
2011 try {
2012 MediaPlayer.class.getMethod("setNextMediaPlayer", MediaPlayer.class);
2013 mCompatMode = false;
2014 } catch (NoSuchMethodException e) {
2015 mCompatMode = true;
2016 super.setOnCompletionListener(this);
2017 }
2018 }
2019
2020 public void setNextMediaPlayer(MediaPlayer next) {
2021 if (mCompatMode) {
2022 mNextPlayer = next;
2023 } else {
2024 super.setNextMediaPlayer(next);
2025 }
2026 }
2027
2028 @Override
2029 public void setOnCompletionListener(OnCompletionListener listener) {
2030 if (mCompatMode) {
2031 mCompletion = listener;
2032 } else {
2033 super.setOnCompletionListener(listener);
2034 }
2035 }
2036
2037 @Override
2038 public void onCompletion(MediaPlayer mp) {
2039 if (mNextPlayer != null) {
2040 // as it turns out, starting a new MediaPlayer on the completion
2041 // of a previous player ends up slightly overlapping the two
2042 // playbacks, so slightly delaying the start of the next player
2043 // gives a better user experience
2044 SystemClock.sleep(50);
2045 mNextPlayer.start();
2046 }
2047 mCompletion.onCompletion(this);
2048 }
2049 }
2050
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002051 /*
2052 * By making this a static class with a WeakReference to the Service, we
2053 * ensure that the Service can be GCd even when the system process still
2054 * has a remote reference to the stub.
2055 */
2056 static class ServiceStub extends IMediaPlaybackService.Stub {
2057 WeakReference<MediaPlaybackService> mService;
2058
2059 ServiceStub(MediaPlaybackService service) {
2060 mService = new WeakReference<MediaPlaybackService>(service);
2061 }
2062
Marco Nelissen8d08ec22010-05-10 14:05:24 -07002063 public void openFile(String path)
The Android Open Source Project792a2202009-03-03 19:32:30 -08002064 {
Marco Nelissen8d08ec22010-05-10 14:05:24 -07002065 mService.get().open(path);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002066 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07002067 public void open(long [] list, int position) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002068 mService.get().open(list, position);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002069 }
2070 public int getQueuePosition() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002071 return mService.get().getQueuePosition();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002072 }
2073 public void setQueuePosition(int index) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002074 mService.get().setQueuePosition(index);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002075 }
2076 public boolean isPlaying() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002077 return mService.get().isPlaying();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002078 }
2079 public void stop() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002080 mService.get().stop();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002081 }
2082 public void pause() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002083 mService.get().pause();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002084 }
2085 public void play() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002086 mService.get().play();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002087 }
2088 public void prev() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002089 mService.get().prev();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002090 }
2091 public void next() {
Marco Nelissenbd0d2f12012-02-27 16:02:15 -08002092 mService.get().gotoNext(true);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002093 }
2094 public String getTrackName() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002095 return mService.get().getTrackName();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002096 }
2097 public String getAlbumName() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002098 return mService.get().getAlbumName();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002099 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07002100 public long getAlbumId() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002101 return mService.get().getAlbumId();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002102 }
2103 public String getArtistName() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002104 return mService.get().getArtistName();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002105 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07002106 public long getArtistId() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002107 return mService.get().getArtistId();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002108 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07002109 public void enqueue(long [] list , int action) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002110 mService.get().enqueue(list, action);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002111 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07002112 public long [] getQueue() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002113 return mService.get().getQueue();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002114 }
2115 public void moveQueueItem(int from, int to) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002116 mService.get().moveQueueItem(from, to);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002117 }
2118 public String getPath() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002119 return mService.get().getPath();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002120 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07002121 public long getAudioId() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002122 return mService.get().getAudioId();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002123 }
2124 public long position() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002125 return mService.get().position();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002126 }
2127 public long duration() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002128 return mService.get().duration();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002129 }
2130 public long seek(long pos) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002131 return mService.get().seek(pos);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002132 }
2133 public void setShuffleMode(int shufflemode) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002134 mService.get().setShuffleMode(shufflemode);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002135 }
2136 public int getShuffleMode() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002137 return mService.get().getShuffleMode();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002138 }
2139 public int removeTracks(int first, int last) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002140 return mService.get().removeTracks(first, last);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002141 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07002142 public int removeTrack(long id) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002143 return mService.get().removeTrack(id);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002144 }
2145 public void setRepeatMode(int repeatmode) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002146 mService.get().setRepeatMode(repeatmode);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002147 }
2148 public int getRepeatMode() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002149 return mService.get().getRepeatMode();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002150 }
2151 public int getMediaMountedCount() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002152 return mService.get().getMediaMountedCount();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002153 }
Eric Laurent1cc72a12010-06-28 11:27:01 -07002154 public int getAudioSessionId() {
2155 return mService.get().getAudioSessionId();
2156 }
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002157 }
Marco Nelissen39888902010-03-02 10:27:05 -08002158
2159 @Override
2160 protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
Marco Nelissenbf555ce2010-03-02 16:55:25 -08002161 writer.println("" + mPlayListLen + " items in queue, currently at index " + mPlayPos);
Marco Nelissen39888902010-03-02 10:27:05 -08002162 writer.println("Currently loaded:");
2163 writer.println(getArtistName());
2164 writer.println(getAlbumName());
2165 writer.println(getTrackName());
2166 writer.println(getPath());
2167 writer.println("playing: " + mIsSupposedToBePlaying);
Marco Nelissene41bd182012-03-14 08:24:40 -07002168 writer.println("actual: " + mPlayer.mCurrentMediaPlayer.isPlaying());
Marco Nelissenbf555ce2010-03-02 16:55:25 -08002169 writer.println("shuffle mode: " + mShuffleMode);
Marco Nelissen39888902010-03-02 10:27:05 -08002170 MusicUtils.debugDump(writer);
2171 }
2172
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002173 private final IBinder mBinder = new ServiceStub(this);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002174}