blob: 6414eb4e51495672fc230b71bac4aeea9a6ae387 [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);
Marco Nelissenc37b2002012-10-16 12:59:54 -07001010 if (c != null) {
1011 c.moveToFirst();
1012 }
Marco Nelissene41bd182012-03-14 08:24:40 -07001013 return c;
1014 }
1015
1016 private void openCurrentAndNext() {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001017 synchronized (this) {
1018 if (mCursor != null) {
1019 mCursor.close();
1020 mCursor = null;
1021 }
Marco Nelissen8d08ec22010-05-10 14:05:24 -07001022
The Android Open Source Project792a2202009-03-03 19:32:30 -08001023 if (mPlayListLen == 0) {
1024 return;
1025 }
1026 stop(false);
1027
Marco Nelissen90d1f622012-04-05 12:27:56 -07001028 mCursor = getCursorForId(mPlayList[mPlayPos]);
Marco Nelissenc37b2002012-10-16 12:59:54 -07001029 while(true) {
1030 if (mCursor != null && mCursor.getCount() != 0 &&
1031 open(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + "/" +
1032 mCursor.getLong(IDCOLIDX))) {
1033 break;
Marco Nelissen6fb85512012-07-18 07:46:21 -07001034 }
Marco Nelissenc37b2002012-10-16 12:59:54 -07001035 // if we get here then opening the file failed. We can close the cursor now, because
1036 // we're either going to create a new one next, or stop trying
1037 if (mCursor != null) {
1038 mCursor.close();
1039 mCursor = null;
1040 }
Marco Nelissen90d1f622012-04-05 12:27:56 -07001041 if (mOpenFailedCounter++ < 10 && mPlayListLen > 1) {
1042 int pos = getNextPosition(false);
1043 if (pos < 0) {
1044 gotoIdleState();
1045 if (mIsSupposedToBePlaying) {
1046 mIsSupposedToBePlaying = false;
1047 notifyChange(PLAYSTATE_CHANGED);
1048 }
1049 return;
1050 }
1051 mPlayPos = pos;
1052 stop(false);
1053 mPlayPos = pos;
1054 mCursor = getCursorForId(mPlayList[mPlayPos]);
1055 } else {
1056 mOpenFailedCounter = 0;
1057 if (!mQuietMode) {
1058 Toast.makeText(this, R.string.playback_failed, Toast.LENGTH_SHORT).show();
1059 }
1060 Log.d(LOGTAG, "Failed to open file for playback");
Marco Nelissenc37b2002012-10-16 12:59:54 -07001061 gotoIdleState();
1062 if (mIsSupposedToBePlaying) {
1063 mIsSupposedToBePlaying = false;
1064 notifyChange(PLAYSTATE_CHANGED);
1065 }
Marco Nelissen90d1f622012-04-05 12:27:56 -07001066 return;
Marco Nelissene41bd182012-03-14 08:24:40 -07001067 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08001068 }
Marco Nelissen90d1f622012-04-05 12:27:56 -07001069
1070 // go to bookmark if needed
1071 if (isPodcast()) {
1072 long bookmark = getBookmark();
1073 // Start playing a little bit before the bookmark,
1074 // so it's easier to get back in to the narrative.
1075 seek(bookmark - 5000);
1076 }
1077 setNextTrack();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001078 }
1079 }
1080
Marco Nelissene41bd182012-03-14 08:24:40 -07001081 private void setNextTrack() {
1082 mNextPlayPos = getNextPosition(false);
1083 if (mNextPlayPos >= 0) {
1084 long id = mPlayList[mNextPlayPos];
1085 mPlayer.setNextDataSource(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + "/" + id);
1086 }
1087 }
1088
The Android Open Source Project792a2202009-03-03 19:32:30 -08001089 /**
1090 * Opens the specified file and readies it for playback.
1091 *
1092 * @param path The full path of the file to be opened.
The Android Open Source Project792a2202009-03-03 19:32:30 -08001093 */
Marco Nelissen90d1f622012-04-05 12:27:56 -07001094 public boolean open(String path) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001095 synchronized (this) {
1096 if (path == null) {
Marco Nelissen90d1f622012-04-05 12:27:56 -07001097 return false;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001098 }
1099
The Android Open Source Project792a2202009-03-03 19:32:30 -08001100 // if mCursor is null, try to associate path with a database cursor
1101 if (mCursor == null) {
1102
1103 ContentResolver resolver = getContentResolver();
1104 Uri uri;
1105 String where;
1106 String selectionArgs[];
1107 if (path.startsWith("content://media/")) {
1108 uri = Uri.parse(path);
1109 where = null;
1110 selectionArgs = null;
1111 } else {
1112 uri = MediaStore.Audio.Media.getContentUriForPath(path);
1113 where = MediaStore.Audio.Media.DATA + "=?";
1114 selectionArgs = new String[] { path };
1115 }
1116
1117 try {
1118 mCursor = resolver.query(uri, mCursorCols, where, selectionArgs, null);
1119 if (mCursor != null) {
1120 if (mCursor.getCount() == 0) {
1121 mCursor.close();
1122 mCursor = null;
1123 } else {
1124 mCursor.moveToNext();
1125 ensurePlayListCapacity(1);
1126 mPlayListLen = 1;
Marco Nelissenbd447b62009-06-29 14:52:05 -07001127 mPlayList[0] = mCursor.getLong(IDCOLIDX);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001128 mPlayPos = 0;
1129 }
1130 }
1131 } catch (UnsupportedOperationException ex) {
1132 }
1133 }
1134 mFileToPlay = path;
1135 mPlayer.setDataSource(mFileToPlay);
Marco Nelissen90d1f622012-04-05 12:27:56 -07001136 if (mPlayer.isInitialized()) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001137 mOpenFailedCounter = 0;
Marco Nelissen90d1f622012-04-05 12:27:56 -07001138 return true;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001139 }
Marco Nelissen90d1f622012-04-05 12:27:56 -07001140 stop(true);
1141 return false;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001142 }
1143 }
1144
1145 /**
1146 * Starts playback of a previously opened file.
1147 */
1148 public void play() {
Jean-Michel Trivi085cbfd2010-03-08 17:06:18 -08001149 mAudioManager.requestAudioFocus(mAudioFocusListener, AudioManager.STREAM_MUSIC,
1150 AudioManager.AUDIOFOCUS_GAIN);
Jean-Michel Trivi3d22fc22010-03-17 11:46:58 -07001151 mAudioManager.registerMediaButtonEventReceiver(new ComponentName(this.getPackageName(),
1152 MediaButtonIntentReceiver.class.getName()));
Jean-Michel Trivi085cbfd2010-03-08 17:06:18 -08001153
The Android Open Source Project792a2202009-03-03 19:32:30 -08001154 if (mPlayer.isInitialized()) {
Thomas Tuttle272eb782009-01-28 21:06:46 -05001155 // if we are at the end of the song, go to the next song first
Marco Nelissen2f9a1ce2009-06-26 14:23:31 -07001156 long duration = mPlayer.duration();
1157 if (mRepeatMode != REPEAT_CURRENT && duration > 2000 &&
1158 mPlayer.position() >= duration - 2000) {
Marco Nelissenbd0d2f12012-02-27 16:02:15 -08001159 gotoNext(true);
Thomas Tuttle272eb782009-01-28 21:06:46 -05001160 }
1161
The Android Open Source Project792a2202009-03-03 19:32:30 -08001162 mPlayer.start();
Marco Nelissen7181da82010-12-01 16:39:04 -08001163 // make sure we fade in, in case a previous fadein was stopped because
1164 // of another focus loss
Marco Nelissen7bae28f2010-12-02 10:04:54 -08001165 mMediaplayerHandler.removeMessages(FADEDOWN);
Marco Nelissen7181da82010-12-01 16:39:04 -08001166 mMediaplayerHandler.sendEmptyMessage(FADEUP);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001167
Marco Nelissene41bd182012-03-14 08:24:40 -07001168 updateNotification();
Marco Nelissenc1333372009-05-06 12:54:47 -07001169 if (!mIsSupposedToBePlaying) {
Mike Cleron347fe572009-10-09 12:26:28 -07001170 mIsSupposedToBePlaying = true;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001171 notifyChange(PLAYSTATE_CHANGED);
1172 }
Mike Cleron347fe572009-10-09 12:26:28 -07001173
The Android Open Source Project792a2202009-03-03 19:32:30 -08001174 } else if (mPlayListLen <= 0) {
1175 // This is mostly so that if you press 'play' on a bluetooth headset
1176 // without every having played anything before, it will still play
1177 // something.
1178 setShuffleMode(SHUFFLE_AUTO);
1179 }
1180 }
Marco Nelissene41bd182012-03-14 08:24:40 -07001181
1182 private void updateNotification() {
1183 RemoteViews views = new RemoteViews(getPackageName(), R.layout.statusbar);
1184 views.setImageViewResource(R.id.icon, R.drawable.stat_notify_musicplayer);
1185 if (getAudioId() < 0) {
1186 // streaming
1187 views.setTextViewText(R.id.trackname, getPath());
1188 views.setTextViewText(R.id.artistalbum, null);
1189 } else {
1190 String artist = getArtistName();
1191 views.setTextViewText(R.id.trackname, getTrackName());
1192 if (artist == null || artist.equals(MediaStore.UNKNOWN_STRING)) {
1193 artist = getString(R.string.unknown_artist_name);
1194 }
1195 String album = getAlbumName();
1196 if (album == null || album.equals(MediaStore.UNKNOWN_STRING)) {
1197 album = getString(R.string.unknown_album_name);
1198 }
1199
1200 views.setTextViewText(R.id.artistalbum,
1201 getString(R.string.notification_artist_album, artist, album)
1202 );
1203 }
1204 Notification status = new Notification();
1205 status.contentView = views;
1206 status.flags |= Notification.FLAG_ONGOING_EVENT;
1207 status.icon = R.drawable.stat_notify_musicplayer;
1208 status.contentIntent = PendingIntent.getActivity(this, 0,
1209 new Intent("com.android.music.PLAYBACK_VIEWER")
1210 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK), 0);
1211 startForeground(PLAYBACKSERVICE_STATUS, status);
1212 }
1213
The Android Open Source Project792a2202009-03-03 19:32:30 -08001214 private void stop(boolean remove_status_icon) {
1215 if (mPlayer.isInitialized()) {
1216 mPlayer.stop();
1217 }
1218 mFileToPlay = null;
1219 if (mCursor != null) {
1220 mCursor.close();
1221 mCursor = null;
1222 }
1223 if (remove_status_icon) {
1224 gotoIdleState();
Dianne Hackbornd5fc5b62009-08-18 11:35:30 -07001225 } else {
1226 stopForeground(false);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001227 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08001228 if (remove_status_icon) {
Marco Nelissenc1333372009-05-06 12:54:47 -07001229 mIsSupposedToBePlaying = false;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001230 }
1231 }
1232
1233 /**
1234 * Stops playback.
1235 */
1236 public void stop() {
1237 stop(true);
1238 }
1239
1240 /**
1241 * Pauses playback (call play() to resume)
1242 */
1243 public void pause() {
Marco Nelissen407cf912009-05-11 09:56:31 -07001244 synchronized(this) {
Marco Nelissen7181da82010-12-01 16:39:04 -08001245 mMediaplayerHandler.removeMessages(FADEUP);
Marco Nelissen407cf912009-05-11 09:56:31 -07001246 if (isPlaying()) {
1247 mPlayer.pause();
1248 gotoIdleState();
Marco Nelissen407cf912009-05-11 09:56:31 -07001249 mIsSupposedToBePlaying = false;
1250 notifyChange(PLAYSTATE_CHANGED);
1251 saveBookmarkIfNeeded();
1252 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08001253 }
1254 }
1255
Marco Nelissenb6e7bf72009-05-11 10:28:31 -07001256 /** Returns whether something is currently playing
The Android Open Source Project792a2202009-03-03 19:32:30 -08001257 *
Marco Nelissenb6e7bf72009-05-11 10:28:31 -07001258 * @return true if something is playing (or will be playing shortly, in case
1259 * we're currently transitioning between tracks), false if not.
The Android Open Source Project792a2202009-03-03 19:32:30 -08001260 */
1261 public boolean isPlaying() {
Marco Nelissenc1333372009-05-06 12:54:47 -07001262 return mIsSupposedToBePlaying;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001263 }
1264
1265 /*
1266 Desired behavior for prev/next/shuffle:
1267
1268 - NEXT will move to the next track in the list when not shuffling, and to
1269 a track randomly picked from the not-yet-played tracks when shuffling.
1270 If all tracks have already been played, pick from the full set, but
1271 avoid picking the previously played track if possible.
1272 - when shuffling, PREV will go to the previously played track. Hitting PREV
1273 again will go to the track played before that, etc. When the start of the
1274 history has been reached, PREV is a no-op.
1275 When not shuffling, PREV will go to the sequentially previous track (the
1276 difference with the shuffle-case is mainly that when not shuffling, the
1277 user can back up to tracks that are not in the history).
1278
1279 Example:
1280 When playing an album with 10 tracks from the start, and enabling shuffle
1281 while playing track 5, the remaining tracks (6-10) will be shuffled, e.g.
1282 the final play order might be 1-2-3-4-5-8-10-6-9-7.
1283 When hitting 'prev' 8 times while playing track 7 in this example, the
1284 user will go to tracks 9-6-10-8-5-4-3-2. If the user then hits 'next',
1285 a random track will be picked again. If at any time user disables shuffling
1286 the next/previous track will be picked in sequential order again.
1287 */
1288
1289 public void prev() {
1290 synchronized (this) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001291 if (mShuffleMode == SHUFFLE_NORMAL) {
1292 // go to previously-played track and remove it from the history
1293 int histsize = mHistory.size();
1294 if (histsize == 0) {
1295 // prev is a no-op
1296 return;
1297 }
1298 Integer pos = mHistory.remove(histsize - 1);
1299 mPlayPos = pos.intValue();
1300 } else {
1301 if (mPlayPos > 0) {
1302 mPlayPos--;
1303 } else {
1304 mPlayPos = mPlayListLen - 1;
1305 }
1306 }
1307 saveBookmarkIfNeeded();
1308 stop(false);
Marco Nelissene41bd182012-03-14 08:24:40 -07001309 openCurrentAndNext();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001310 play();
1311 notifyChange(META_CHANGED);
1312 }
1313 }
1314
Marco Nelissenbd0d2f12012-02-27 16:02:15 -08001315 /**
1316 * Get the next position to play. Note that this may actually modify mPlayPos
1317 * if playback is in SHUFFLE_AUTO mode and the shuffle list window needed to
1318 * be adjusted. Either way, the return value is the next value that should be
1319 * assigned to mPlayPos;
1320 */
1321 private int getNextPosition(boolean force) {
Marco Nelissene41bd182012-03-14 08:24:40 -07001322 if (mRepeatMode == REPEAT_CURRENT) {
1323 if (mPlayPos < 0) return 0;
1324 return mPlayPos;
1325 } else if (mShuffleMode == SHUFFLE_NORMAL) {
Marco Nelissenbd0d2f12012-02-27 16:02:15 -08001326 // Pick random next track from the not-yet-played ones
1327 // TODO: make it work right after adding/removing items in the queue.
1328
1329 // Store the current file in the history, but keep the history at a
1330 // reasonable size
1331 if (mPlayPos >= 0) {
1332 mHistory.add(mPlayPos);
1333 }
1334 if (mHistory.size() > MAX_HISTORY_SIZE) {
1335 mHistory.removeElementAt(0);
1336 }
1337
1338 int numTracks = mPlayListLen;
1339 int[] tracks = new int[numTracks];
1340 for (int i=0;i < numTracks; i++) {
1341 tracks[i] = i;
1342 }
1343
1344 int numHistory = mHistory.size();
1345 int numUnplayed = numTracks;
1346 for (int i=0;i < numHistory; i++) {
1347 int idx = mHistory.get(i).intValue();
1348 if (idx < numTracks && tracks[idx] >= 0) {
1349 numUnplayed--;
1350 tracks[idx] = -1;
1351 }
1352 }
1353
1354 // 'numUnplayed' now indicates how many tracks have not yet
1355 // been played, and 'tracks' contains the indices of those
1356 // tracks.
1357 if (numUnplayed <=0) {
1358 // everything's already been played
1359 if (mRepeatMode == REPEAT_ALL || force) {
1360 //pick from full set
1361 numUnplayed = numTracks;
1362 for (int i=0;i < numTracks; i++) {
1363 tracks[i] = i;
1364 }
1365 } else {
1366 // all done
1367 return -1;
1368 }
1369 }
1370 int skip = mRand.nextInt(numUnplayed);
1371 int cnt = -1;
1372 while (true) {
1373 while (tracks[++cnt] < 0)
1374 ;
1375 skip--;
1376 if (skip < 0) {
1377 break;
1378 }
1379 }
1380 return cnt;
1381 } else if (mShuffleMode == SHUFFLE_AUTO) {
1382 doAutoShuffleUpdate();
1383 return mPlayPos + 1;
1384 } else {
1385 if (mPlayPos >= mPlayListLen - 1) {
1386 // we're at the end of the list
1387 if (mRepeatMode == REPEAT_NONE && !force) {
1388 // all done
1389 return -1;
1390 } else if (mRepeatMode == REPEAT_ALL || force) {
1391 return 0;
1392 }
1393 return -1;
1394 } else {
1395 return mPlayPos + 1;
1396 }
1397 }
1398 }
1399
1400 public void gotoNext(boolean force) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001401 synchronized (this) {
Marco Nelissen663fea32009-06-12 13:39:27 -07001402 if (mPlayListLen <= 0) {
Marco Nelissene99341f2009-11-11 11:13:51 -08001403 Log.d(LOGTAG, "No play queue");
Marco Nelissen663fea32009-06-12 13:39:27 -07001404 return;
1405 }
1406
Marco Nelissenbd0d2f12012-02-27 16:02:15 -08001407 int pos = getNextPosition(force);
1408 if (pos < 0) {
1409 gotoIdleState();
1410 if (mIsSupposedToBePlaying) {
1411 mIsSupposedToBePlaying = false;
1412 notifyChange(PLAYSTATE_CHANGED);
Marco Nelissen3f502de2010-09-28 15:07:29 -07001413 }
Marco Nelissenbd0d2f12012-02-27 16:02:15 -08001414 return;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001415 }
Marco Nelissenbd0d2f12012-02-27 16:02:15 -08001416 mPlayPos = pos;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001417 saveBookmarkIfNeeded();
1418 stop(false);
Marco Nelissene41bd182012-03-14 08:24:40 -07001419 mPlayPos = pos;
1420 openCurrentAndNext();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001421 play();
1422 notifyChange(META_CHANGED);
1423 }
1424 }
1425
1426 private void gotoIdleState() {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001427 mDelayedStopHandler.removeCallbacksAndMessages(null);
1428 Message msg = mDelayedStopHandler.obtainMessage();
1429 mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
Dianne Hackbornd5fc5b62009-08-18 11:35:30 -07001430 stopForeground(true);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001431 }
1432
1433 private void saveBookmarkIfNeeded() {
1434 try {
1435 if (isPodcast()) {
1436 long pos = position();
1437 long bookmark = getBookmark();
1438 long duration = duration();
1439 if ((pos < bookmark && (pos + 10000) > bookmark) ||
1440 (pos > bookmark && (pos - 10000) < bookmark)) {
1441 // The existing bookmark is close to the current
1442 // position, so don't update it.
1443 return;
1444 }
1445 if (pos < 15000 || (pos + 10000) > duration) {
1446 // if we're near the start or end, clear the bookmark
1447 pos = 0;
1448 }
1449
1450 // write 'pos' to the bookmark field
1451 ContentValues values = new ContentValues();
1452 values.put(MediaStore.Audio.Media.BOOKMARK, pos);
1453 Uri uri = ContentUris.withAppendedId(
1454 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, mCursor.getLong(IDCOLIDX));
1455 getContentResolver().update(uri, values, null, null);
1456 }
1457 } catch (SQLiteException ex) {
1458 }
1459 }
1460
1461 // Make sure there are at least 5 items after the currently playing item
1462 // and no more than 10 items before.
1463 private void doAutoShuffleUpdate() {
1464 boolean notify = false;
Marco Nelissen3f502de2010-09-28 15:07:29 -07001465
The Android Open Source Project792a2202009-03-03 19:32:30 -08001466 // remove old entries
1467 if (mPlayPos > 10) {
1468 removeTracks(0, mPlayPos - 9);
1469 notify = true;
1470 }
1471 // add new entries if needed
1472 int to_add = 7 - (mPlayListLen - (mPlayPos < 0 ? -1 : mPlayPos));
1473 for (int i = 0; i < to_add; i++) {
1474 // pick something at random from the list
Marco Nelissen3f502de2010-09-28 15:07:29 -07001475
1476 int lookback = mHistory.size();
1477 int idx = -1;
1478 while(true) {
1479 idx = mRand.nextInt(mAutoShuffleList.length);
1480 if (!wasRecentlyUsed(idx, lookback)) {
1481 break;
1482 }
1483 lookback /= 2;
1484 }
1485 mHistory.add(idx);
1486 if (mHistory.size() > MAX_HISTORY_SIZE) {
1487 mHistory.remove(0);
1488 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08001489 ensurePlayListCapacity(mPlayListLen + 1);
Marco Nelissen3f502de2010-09-28 15:07:29 -07001490 mPlayList[mPlayListLen++] = mAutoShuffleList[idx];
The Android Open Source Project792a2202009-03-03 19:32:30 -08001491 notify = true;
1492 }
1493 if (notify) {
1494 notifyChange(QUEUE_CHANGED);
1495 }
1496 }
1497
Marco Nelissen3f502de2010-09-28 15:07:29 -07001498 // check that the specified idx is not in the history (but only look at at
1499 // most lookbacksize entries in the history)
1500 private boolean wasRecentlyUsed(int idx, int lookbacksize) {
1501
1502 // early exit to prevent infinite loops in case idx == mPlayPos
1503 if (lookbacksize == 0) {
1504 return false;
1505 }
1506
1507 int histsize = mHistory.size();
1508 if (histsize < lookbacksize) {
1509 Log.d(LOGTAG, "lookback too big");
1510 lookbacksize = histsize;
1511 }
1512 int maxidx = histsize - 1;
1513 for (int i = 0; i < lookbacksize; i++) {
1514 long entry = mHistory.get(maxidx - i);
1515 if (entry == idx) {
1516 return true;
1517 }
1518 }
1519 return false;
1520 }
1521
The Android Open Source Project792a2202009-03-03 19:32:30 -08001522 // A simple variation of Random that makes sure that the
1523 // value it returns is not equal to the value it returned
1524 // previously, unless the interval is 1.
Marco Nelissen756c3f52009-05-14 10:07:23 -07001525 private static class Shuffler {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001526 private int mPrevious;
1527 private Random mRandom = new Random();
1528 public int nextInt(int interval) {
1529 int ret;
1530 do {
1531 ret = mRandom.nextInt(interval);
1532 } while (ret == mPrevious && interval > 1);
1533 mPrevious = ret;
1534 return ret;
1535 }
1536 };
1537
1538 private boolean makeAutoShuffleList() {
1539 ContentResolver res = getContentResolver();
1540 Cursor c = null;
1541 try {
1542 c = res.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
1543 new String[] {MediaStore.Audio.Media._ID}, MediaStore.Audio.Media.IS_MUSIC + "=1",
1544 null, null);
1545 if (c == null || c.getCount() == 0) {
1546 return false;
1547 }
1548 int len = c.getCount();
Marco Nelissenbd447b62009-06-29 14:52:05 -07001549 long [] list = new long[len];
The Android Open Source Project792a2202009-03-03 19:32:30 -08001550 for (int i = 0; i < len; i++) {
1551 c.moveToNext();
Marco Nelissenbd447b62009-06-29 14:52:05 -07001552 list[i] = c.getLong(0);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001553 }
1554 mAutoShuffleList = list;
1555 return true;
1556 } catch (RuntimeException ex) {
1557 } finally {
1558 if (c != null) {
1559 c.close();
1560 }
1561 }
1562 return false;
1563 }
1564
1565 /**
1566 * Removes the range of tracks specified from the play list. If a file within the range is
1567 * the file currently being played, playback will move to the next file after the
1568 * range.
1569 * @param first The first file to be removed
1570 * @param last The last file to be removed
1571 * @return the number of tracks deleted
1572 */
1573 public int removeTracks(int first, int last) {
1574 int numremoved = removeTracksInternal(first, last);
1575 if (numremoved > 0) {
1576 notifyChange(QUEUE_CHANGED);
1577 }
1578 return numremoved;
1579 }
1580
1581 private int removeTracksInternal(int first, int last) {
1582 synchronized (this) {
1583 if (last < first) return 0;
1584 if (first < 0) first = 0;
1585 if (last >= mPlayListLen) last = mPlayListLen - 1;
1586
1587 boolean gotonext = false;
1588 if (first <= mPlayPos && mPlayPos <= last) {
1589 mPlayPos = first;
1590 gotonext = true;
1591 } else if (mPlayPos > last) {
1592 mPlayPos -= (last - first + 1);
1593 }
1594 int num = mPlayListLen - last - 1;
1595 for (int i = 0; i < num; i++) {
1596 mPlayList[first + i] = mPlayList[last + 1 + i];
1597 }
1598 mPlayListLen -= last - first + 1;
1599
1600 if (gotonext) {
1601 if (mPlayListLen == 0) {
1602 stop(true);
1603 mPlayPos = -1;
Marco Nelissen3aa9ad02010-09-16 13:23:11 -07001604 if (mCursor != null) {
1605 mCursor.close();
1606 mCursor = null;
1607 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08001608 } else {
1609 if (mPlayPos >= mPlayListLen) {
1610 mPlayPos = 0;
1611 }
1612 boolean wasPlaying = isPlaying();
1613 stop(false);
Marco Nelissene41bd182012-03-14 08:24:40 -07001614 openCurrentAndNext();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001615 if (wasPlaying) {
1616 play();
1617 }
1618 }
Marco Nelissen3aa9ad02010-09-16 13:23:11 -07001619 notifyChange(META_CHANGED);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001620 }
1621 return last - first + 1;
1622 }
1623 }
1624
1625 /**
1626 * Removes all instances of the track with the given id
1627 * from the playlist.
1628 * @param id The id to be removed
1629 * @return how many instances of the track were removed
1630 */
Marco Nelissenbd447b62009-06-29 14:52:05 -07001631 public int removeTrack(long id) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001632 int numremoved = 0;
1633 synchronized (this) {
1634 for (int i = 0; i < mPlayListLen; i++) {
1635 if (mPlayList[i] == id) {
1636 numremoved += removeTracksInternal(i, i);
1637 i--;
1638 }
1639 }
1640 }
1641 if (numremoved > 0) {
1642 notifyChange(QUEUE_CHANGED);
1643 }
1644 return numremoved;
1645 }
1646
1647 public void setShuffleMode(int shufflemode) {
1648 synchronized(this) {
1649 if (mShuffleMode == shufflemode && mPlayListLen > 0) {
1650 return;
1651 }
1652 mShuffleMode = shufflemode;
1653 if (mShuffleMode == SHUFFLE_AUTO) {
1654 if (makeAutoShuffleList()) {
1655 mPlayListLen = 0;
1656 doAutoShuffleUpdate();
1657 mPlayPos = 0;
Marco Nelissene41bd182012-03-14 08:24:40 -07001658 openCurrentAndNext();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001659 play();
1660 notifyChange(META_CHANGED);
1661 return;
1662 } else {
1663 // failed to build a list of files to shuffle
1664 mShuffleMode = SHUFFLE_NONE;
1665 }
1666 }
1667 saveQueue(false);
1668 }
1669 }
1670 public int getShuffleMode() {
1671 return mShuffleMode;
1672 }
1673
1674 public void setRepeatMode(int repeatmode) {
1675 synchronized(this) {
1676 mRepeatMode = repeatmode;
Marco Nelissene41bd182012-03-14 08:24:40 -07001677 setNextTrack();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001678 saveQueue(false);
1679 }
1680 }
1681 public int getRepeatMode() {
1682 return mRepeatMode;
1683 }
1684
1685 public int getMediaMountedCount() {
1686 return mMediaMountedCount;
1687 }
1688
1689 /**
1690 * Returns the path of the currently playing file, or null if
1691 * no file is currently playing.
1692 */
1693 public String getPath() {
1694 return mFileToPlay;
1695 }
1696
1697 /**
1698 * Returns the rowid of the currently playing file, or -1 if
1699 * no file is currently playing.
1700 */
Marco Nelissenbd447b62009-06-29 14:52:05 -07001701 public long getAudioId() {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001702 synchronized (this) {
1703 if (mPlayPos >= 0 && mPlayer.isInitialized()) {
1704 return mPlayList[mPlayPos];
1705 }
1706 }
1707 return -1;
1708 }
1709
1710 /**
1711 * Returns the position in the queue
1712 * @return the position in the queue
1713 */
1714 public int getQueuePosition() {
1715 synchronized(this) {
1716 return mPlayPos;
1717 }
1718 }
1719
1720 /**
1721 * Starts playing the track at the given position in the queue.
1722 * @param pos The position in the queue of the track that will be played.
1723 */
1724 public void setQueuePosition(int pos) {
1725 synchronized(this) {
1726 stop(false);
1727 mPlayPos = pos;
Marco Nelissene41bd182012-03-14 08:24:40 -07001728 openCurrentAndNext();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001729 play();
1730 notifyChange(META_CHANGED);
Marco Nelissenec0c57a2009-12-12 12:27:11 -08001731 if (mShuffleMode == SHUFFLE_AUTO) {
1732 doAutoShuffleUpdate();
1733 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08001734 }
1735 }
1736
1737 public String getArtistName() {
1738 synchronized(this) {
1739 if (mCursor == null) {
1740 return null;
1741 }
1742 return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST));
1743 }
1744 }
1745
Marco Nelissenbd447b62009-06-29 14:52:05 -07001746 public long getArtistId() {
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.ARTIST_ID));
The Android Open Source Project792a2202009-03-03 19:32:30 -08001752 }
1753 }
1754
1755 public String getAlbumName() {
1756 synchronized (this) {
1757 if (mCursor == null) {
1758 return null;
1759 }
1760 return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM));
1761 }
1762 }
1763
Marco Nelissenbd447b62009-06-29 14:52:05 -07001764 public long getAlbumId() {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001765 synchronized (this) {
1766 if (mCursor == null) {
1767 return -1;
1768 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07001769 return mCursor.getLong(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM_ID));
The Android Open Source Project792a2202009-03-03 19:32:30 -08001770 }
1771 }
1772
1773 public String getTrackName() {
1774 synchronized (this) {
1775 if (mCursor == null) {
1776 return null;
1777 }
1778 return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE));
1779 }
1780 }
1781
1782 private boolean isPodcast() {
1783 synchronized (this) {
1784 if (mCursor == null) {
1785 return false;
1786 }
1787 return (mCursor.getInt(PODCASTCOLIDX) > 0);
1788 }
1789 }
1790
1791 private long getBookmark() {
1792 synchronized (this) {
1793 if (mCursor == null) {
1794 return 0;
1795 }
1796 return mCursor.getLong(BOOKMARKCOLIDX);
1797 }
1798 }
1799
1800 /**
1801 * Returns the duration of the file in milliseconds.
1802 * Currently this method returns -1 for the duration of MIDI files.
1803 */
1804 public long duration() {
1805 if (mPlayer.isInitialized()) {
1806 return mPlayer.duration();
1807 }
1808 return -1;
1809 }
1810
1811 /**
1812 * Returns the current playback position in milliseconds
1813 */
1814 public long position() {
1815 if (mPlayer.isInitialized()) {
1816 return mPlayer.position();
1817 }
1818 return -1;
1819 }
1820
1821 /**
1822 * Seeks to the position specified.
1823 *
1824 * @param pos The position to seek to, in milliseconds
1825 */
1826 public long seek(long pos) {
1827 if (mPlayer.isInitialized()) {
1828 if (pos < 0) pos = 0;
1829 if (pos > mPlayer.duration()) pos = mPlayer.duration();
1830 return mPlayer.seek(pos);
1831 }
1832 return -1;
1833 }
1834
1835 /**
Eric Laurent1cc72a12010-06-28 11:27:01 -07001836 * Sets the audio session ID.
1837 *
1838 * @param sessionId: the audio session ID.
1839 */
1840 public void setAudioSessionId(int sessionId) {
1841 synchronized (this) {
1842 mPlayer.setAudioSessionId(sessionId);
1843 }
1844 }
1845
1846 /**
1847 * Returns the audio session ID.
1848 */
1849 public int getAudioSessionId() {
1850 synchronized (this) {
1851 return mPlayer.getAudioSessionId();
1852 }
1853 }
1854
1855 /**
The Android Open Source Project792a2202009-03-03 19:32:30 -08001856 * Provides a unified interface for dealing with midi files and
1857 * other media files.
1858 */
1859 private class MultiPlayer {
Marco Nelissen30867022012-03-14 16:04:34 -07001860 private CompatMediaPlayer mCurrentMediaPlayer = new CompatMediaPlayer();
1861 private CompatMediaPlayer mNextMediaPlayer;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001862 private Handler mHandler;
1863 private boolean mIsInitialized = false;
1864
1865 public MultiPlayer() {
Marco Nelissene41bd182012-03-14 08:24:40 -07001866 mCurrentMediaPlayer.setWakeMode(MediaPlaybackService.this, PowerManager.PARTIAL_WAKE_LOCK);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001867 }
1868
The Android Open Source Project792a2202009-03-03 19:32:30 -08001869 public void setDataSource(String path) {
Marco Nelissen90d1f622012-04-05 12:27:56 -07001870 mIsInitialized = setDataSourceImpl(mCurrentMediaPlayer, path);
1871 if (mIsInitialized) {
1872 setNextDataSource(null);
1873 }
Marco Nelissene41bd182012-03-14 08:24:40 -07001874 }
1875
Marco Nelissen90d1f622012-04-05 12:27:56 -07001876 private boolean setDataSourceImpl(MediaPlayer player, String path) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001877 try {
Marco Nelissene41bd182012-03-14 08:24:40 -07001878 player.reset();
1879 player.setOnPreparedListener(null);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001880 if (path.startsWith("content://")) {
Marco Nelissene41bd182012-03-14 08:24:40 -07001881 player.setDataSource(MediaPlaybackService.this, Uri.parse(path));
The Android Open Source Project792a2202009-03-03 19:32:30 -08001882 } else {
Marco Nelissene41bd182012-03-14 08:24:40 -07001883 player.setDataSource(path);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001884 }
Marco Nelissene41bd182012-03-14 08:24:40 -07001885 player.setAudioStreamType(AudioManager.STREAM_MUSIC);
1886 player.prepare();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001887 } catch (IOException ex) {
1888 // TODO: notify the user why the file couldn't be opened
Marco Nelissen90d1f622012-04-05 12:27:56 -07001889 return false;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001890 } catch (IllegalArgumentException ex) {
1891 // TODO: notify the user why the file couldn't be opened
Marco Nelissen90d1f622012-04-05 12:27:56 -07001892 return false;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001893 }
Marco Nelissene41bd182012-03-14 08:24:40 -07001894 player.setOnCompletionListener(listener);
1895 player.setOnErrorListener(errorListener);
Marco Nelissenf2ef3b52010-09-21 15:47:27 -07001896 Intent i = new Intent(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION);
1897 i.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, getAudioSessionId());
1898 i.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, getPackageName());
1899 sendBroadcast(i);
Marco Nelissen90d1f622012-04-05 12:27:56 -07001900 return true;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001901 }
Marco Nelissene41bd182012-03-14 08:24:40 -07001902
1903 public void setNextDataSource(String path) {
1904 mCurrentMediaPlayer.setNextMediaPlayer(null);
1905 if (mNextMediaPlayer != null) {
1906 mNextMediaPlayer.release();
1907 mNextMediaPlayer = null;
1908 }
1909 if (path == null) {
1910 return;
1911 }
Marco Nelissen30867022012-03-14 16:04:34 -07001912 mNextMediaPlayer = new CompatMediaPlayer();
Marco Nelissene41bd182012-03-14 08:24:40 -07001913 mNextMediaPlayer.setWakeMode(MediaPlaybackService.this, PowerManager.PARTIAL_WAKE_LOCK);
1914 mNextMediaPlayer.setAudioSessionId(getAudioSessionId());
Marco Nelissen90d1f622012-04-05 12:27:56 -07001915 if (setDataSourceImpl(mNextMediaPlayer, path)) {
1916 mCurrentMediaPlayer.setNextMediaPlayer(mNextMediaPlayer);
1917 } else {
1918 // failed to open next, we'll transition the old fashioned way,
1919 // which will skip over the faulty file
1920 mNextMediaPlayer.release();
1921 mNextMediaPlayer = null;
1922 }
Marco Nelissene41bd182012-03-14 08:24:40 -07001923 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08001924
1925 public boolean isInitialized() {
1926 return mIsInitialized;
1927 }
1928
1929 public void start() {
Marco Nelissen39888902010-03-02 10:27:05 -08001930 MusicUtils.debugLog(new Exception("MultiPlayer.start called"));
Marco Nelissene41bd182012-03-14 08:24:40 -07001931 mCurrentMediaPlayer.start();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001932 }
1933
1934 public void stop() {
Marco Nelissene41bd182012-03-14 08:24:40 -07001935 mCurrentMediaPlayer.reset();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001936 mIsInitialized = false;
1937 }
1938
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001939 /**
1940 * You CANNOT use this player anymore after calling release()
1941 */
1942 public void release() {
1943 stop();
Marco Nelissene41bd182012-03-14 08:24:40 -07001944 mCurrentMediaPlayer.release();
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001945 }
1946
The Android Open Source Project792a2202009-03-03 19:32:30 -08001947 public void pause() {
Marco Nelissene41bd182012-03-14 08:24:40 -07001948 mCurrentMediaPlayer.pause();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001949 }
1950
The Android Open Source Project792a2202009-03-03 19:32:30 -08001951 public void setHandler(Handler handler) {
1952 mHandler = handler;
1953 }
1954
1955 MediaPlayer.OnCompletionListener listener = new MediaPlayer.OnCompletionListener() {
1956 public void onCompletion(MediaPlayer mp) {
Marco Nelissene41bd182012-03-14 08:24:40 -07001957 if (mp == mCurrentMediaPlayer && mNextMediaPlayer != null) {
1958 mCurrentMediaPlayer.release();
1959 mCurrentMediaPlayer = mNextMediaPlayer;
1960 mNextMediaPlayer = null;
1961 mHandler.sendEmptyMessage(TRACK_WENT_TO_NEXT);
1962 } else {
1963 // Acquire a temporary wakelock, since when we return from
1964 // this callback the MediaPlayer will release its wakelock
1965 // and allow the device to go to sleep.
1966 // This temporary wakelock is released when the RELEASE_WAKELOCK
1967 // message is processed, but just in case, put a timeout on it.
1968 mWakeLock.acquire(30000);
1969 mHandler.sendEmptyMessage(TRACK_ENDED);
1970 mHandler.sendEmptyMessage(RELEASE_WAKELOCK);
1971 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08001972 }
1973 };
1974
The Android Open Source Project792a2202009-03-03 19:32:30 -08001975 MediaPlayer.OnErrorListener errorListener = new MediaPlayer.OnErrorListener() {
1976 public boolean onError(MediaPlayer mp, int what, int extra) {
1977 switch (what) {
1978 case MediaPlayer.MEDIA_ERROR_SERVER_DIED:
1979 mIsInitialized = false;
Marco Nelissene41bd182012-03-14 08:24:40 -07001980 mCurrentMediaPlayer.release();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001981 // Creating a new MediaPlayer and settings its wakemode does not
1982 // require the media service, so it's OK to do this now, while the
1983 // service is still being restarted
Marco Nelissen30867022012-03-14 16:04:34 -07001984 mCurrentMediaPlayer = new CompatMediaPlayer();
Marco Nelissene41bd182012-03-14 08:24:40 -07001985 mCurrentMediaPlayer.setWakeMode(MediaPlaybackService.this, PowerManager.PARTIAL_WAKE_LOCK);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001986 mHandler.sendMessageDelayed(mHandler.obtainMessage(SERVER_DIED), 2000);
1987 return true;
1988 default:
Marco Nelissene99341f2009-11-11 11:13:51 -08001989 Log.d("MultiPlayer", "Error: " + what + "," + extra);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001990 break;
1991 }
1992 return false;
1993 }
1994 };
1995
1996 public long duration() {
Marco Nelissene41bd182012-03-14 08:24:40 -07001997 return mCurrentMediaPlayer.getDuration();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001998 }
1999
2000 public long position() {
Marco Nelissene41bd182012-03-14 08:24:40 -07002001 return mCurrentMediaPlayer.getCurrentPosition();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002002 }
2003
2004 public long seek(long whereto) {
Marco Nelissene41bd182012-03-14 08:24:40 -07002005 mCurrentMediaPlayer.seekTo((int) whereto);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002006 return whereto;
2007 }
2008
2009 public void setVolume(float vol) {
Marco Nelissene41bd182012-03-14 08:24:40 -07002010 mCurrentMediaPlayer.setVolume(vol, vol);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002011 }
Eric Laurent1cc72a12010-06-28 11:27:01 -07002012
2013 public void setAudioSessionId(int sessionId) {
Marco Nelissene41bd182012-03-14 08:24:40 -07002014 mCurrentMediaPlayer.setAudioSessionId(sessionId);
Eric Laurent1cc72a12010-06-28 11:27:01 -07002015 }
2016
2017 public int getAudioSessionId() {
Marco Nelissene41bd182012-03-14 08:24:40 -07002018 return mCurrentMediaPlayer.getAudioSessionId();
Eric Laurent1cc72a12010-06-28 11:27:01 -07002019 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08002020 }
2021
Marco Nelissen30867022012-03-14 16:04:34 -07002022 static class CompatMediaPlayer extends MediaPlayer implements OnCompletionListener {
2023
2024 private boolean mCompatMode = true;
2025 private MediaPlayer mNextPlayer;
2026 private OnCompletionListener mCompletion;
2027
2028 public CompatMediaPlayer() {
2029 try {
2030 MediaPlayer.class.getMethod("setNextMediaPlayer", MediaPlayer.class);
2031 mCompatMode = false;
2032 } catch (NoSuchMethodException e) {
2033 mCompatMode = true;
2034 super.setOnCompletionListener(this);
2035 }
2036 }
2037
2038 public void setNextMediaPlayer(MediaPlayer next) {
2039 if (mCompatMode) {
2040 mNextPlayer = next;
2041 } else {
2042 super.setNextMediaPlayer(next);
2043 }
2044 }
2045
2046 @Override
2047 public void setOnCompletionListener(OnCompletionListener listener) {
2048 if (mCompatMode) {
2049 mCompletion = listener;
2050 } else {
2051 super.setOnCompletionListener(listener);
2052 }
2053 }
2054
2055 @Override
2056 public void onCompletion(MediaPlayer mp) {
2057 if (mNextPlayer != null) {
2058 // as it turns out, starting a new MediaPlayer on the completion
2059 // of a previous player ends up slightly overlapping the two
2060 // playbacks, so slightly delaying the start of the next player
2061 // gives a better user experience
2062 SystemClock.sleep(50);
2063 mNextPlayer.start();
2064 }
2065 mCompletion.onCompletion(this);
2066 }
2067 }
2068
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002069 /*
2070 * By making this a static class with a WeakReference to the Service, we
2071 * ensure that the Service can be GCd even when the system process still
2072 * has a remote reference to the stub.
2073 */
2074 static class ServiceStub extends IMediaPlaybackService.Stub {
2075 WeakReference<MediaPlaybackService> mService;
2076
2077 ServiceStub(MediaPlaybackService service) {
2078 mService = new WeakReference<MediaPlaybackService>(service);
2079 }
2080
Marco Nelissen8d08ec22010-05-10 14:05:24 -07002081 public void openFile(String path)
The Android Open Source Project792a2202009-03-03 19:32:30 -08002082 {
Marco Nelissen8d08ec22010-05-10 14:05:24 -07002083 mService.get().open(path);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002084 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07002085 public void open(long [] list, int position) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002086 mService.get().open(list, position);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002087 }
2088 public int getQueuePosition() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002089 return mService.get().getQueuePosition();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002090 }
2091 public void setQueuePosition(int index) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002092 mService.get().setQueuePosition(index);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002093 }
2094 public boolean isPlaying() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002095 return mService.get().isPlaying();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002096 }
2097 public void stop() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002098 mService.get().stop();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002099 }
2100 public void pause() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002101 mService.get().pause();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002102 }
2103 public void play() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002104 mService.get().play();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002105 }
2106 public void prev() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002107 mService.get().prev();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002108 }
2109 public void next() {
Marco Nelissenbd0d2f12012-02-27 16:02:15 -08002110 mService.get().gotoNext(true);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002111 }
2112 public String getTrackName() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002113 return mService.get().getTrackName();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002114 }
2115 public String getAlbumName() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002116 return mService.get().getAlbumName();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002117 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07002118 public long getAlbumId() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002119 return mService.get().getAlbumId();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002120 }
2121 public String getArtistName() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002122 return mService.get().getArtistName();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002123 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07002124 public long getArtistId() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002125 return mService.get().getArtistId();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002126 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07002127 public void enqueue(long [] list , int action) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002128 mService.get().enqueue(list, action);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002129 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07002130 public long [] getQueue() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002131 return mService.get().getQueue();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002132 }
2133 public void moveQueueItem(int from, int to) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002134 mService.get().moveQueueItem(from, to);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002135 }
2136 public String getPath() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002137 return mService.get().getPath();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002138 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07002139 public long getAudioId() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002140 return mService.get().getAudioId();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002141 }
2142 public long position() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002143 return mService.get().position();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002144 }
2145 public long duration() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002146 return mService.get().duration();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002147 }
2148 public long seek(long pos) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002149 return mService.get().seek(pos);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002150 }
2151 public void setShuffleMode(int shufflemode) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002152 mService.get().setShuffleMode(shufflemode);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002153 }
2154 public int getShuffleMode() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002155 return mService.get().getShuffleMode();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002156 }
2157 public int removeTracks(int first, int last) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002158 return mService.get().removeTracks(first, last);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002159 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07002160 public int removeTrack(long id) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002161 return mService.get().removeTrack(id);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002162 }
2163 public void setRepeatMode(int repeatmode) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002164 mService.get().setRepeatMode(repeatmode);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002165 }
2166 public int getRepeatMode() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002167 return mService.get().getRepeatMode();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002168 }
2169 public int getMediaMountedCount() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002170 return mService.get().getMediaMountedCount();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002171 }
Eric Laurent1cc72a12010-06-28 11:27:01 -07002172 public int getAudioSessionId() {
2173 return mService.get().getAudioSessionId();
2174 }
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002175 }
Marco Nelissen39888902010-03-02 10:27:05 -08002176
2177 @Override
2178 protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
Marco Nelissenbf555ce2010-03-02 16:55:25 -08002179 writer.println("" + mPlayListLen + " items in queue, currently at index " + mPlayPos);
Marco Nelissen39888902010-03-02 10:27:05 -08002180 writer.println("Currently loaded:");
2181 writer.println(getArtistName());
2182 writer.println(getAlbumName());
2183 writer.println(getTrackName());
2184 writer.println(getPath());
2185 writer.println("playing: " + mIsSupposedToBePlaying);
Marco Nelissene41bd182012-03-14 08:24:40 -07002186 writer.println("actual: " + mPlayer.mCurrentMediaPlayer.isPlaying());
Marco Nelissenbf555ce2010-03-02 16:55:25 -08002187 writer.println("shuffle mode: " + mShuffleMode);
Marco Nelissen39888902010-03-02 10:27:05 -08002188 MusicUtils.debugDump(writer);
2189 }
2190
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002191 private final IBinder mBinder = new ServiceStub(this);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002192}