blob: 07cd81da70da95b61a3f3d0708e028b4e1d25144 [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;
Jack Heb0fba8b2017-01-26 15:54:38 -080076
The Android Open Source Project792a2202009-03-03 19:32:30 -080077 public static final int SHUFFLE_NONE = 0;
78 public static final int SHUFFLE_NORMAL = 1;
79 public static final int SHUFFLE_AUTO = 2;
Jack Heb0fba8b2017-01-26 15:54:38 -080080
The Android Open Source Project792a2202009-03-03 19:32:30 -080081 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
Jack Heb0fba8b2017-01-26 15:54:38 -080098 public static final String TOGGLEPAUSE_ACTION =
99 "com.android.music.musicservicecommand.togglepause";
The Android Open Source Project792a2202009-03-03 19:32:30 -0800100 public static final String PAUSE_ACTION = "com.android.music.musicservicecommand.pause";
101 public static final String PREVIOUS_ACTION = "com.android.music.musicservicecommand.previous";
102 public static final String NEXT_ACTION = "com.android.music.musicservicecommand.next";
103
The Android Open Source Project792a2202009-03-03 19:32:30 -0800104 private static final int TRACK_ENDED = 1;
105 private static final int RELEASE_WAKELOCK = 2;
106 private static final int SERVER_DIED = 3;
Marco Nelissen7181da82010-12-01 16:39:04 -0800107 private static final int FOCUSCHANGE = 4;
Marco Nelissen2da473d2010-09-29 16:11:12 -0700108 private static final int FADEDOWN = 5;
109 private static final int FADEUP = 6;
Marco Nelissene41bd182012-03-14 08:24:40 -0700110 private static final int TRACK_WENT_TO_NEXT = 7;
Marco Nelissen3ec2ad92009-08-17 08:52:29 -0700111 private static final int MAX_HISTORY_SIZE = 100;
Jack Heb0fba8b2017-01-26 15:54:38 -0800112
The Android Open Source Project792a2202009-03-03 19:32:30 -0800113 private MultiPlayer mPlayer;
114 private String mFileToPlay;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800115 private int mShuffleMode = SHUFFLE_NONE;
116 private int mRepeatMode = REPEAT_NONE;
117 private int mMediaMountedCount = 0;
Jack Heb0fba8b2017-01-26 15:54:38 -0800118 private long[] mAutoShuffleList = null;
119 private long[] mPlayList = null;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800120 private int mPlayListLen = 0;
121 private Vector<Integer> mHistory = new Vector<Integer>(MAX_HISTORY_SIZE);
122 private Cursor mCursor;
123 private int mPlayPos = -1;
Marco Nelissene41bd182012-03-14 08:24:40 -0700124 private int mNextPlayPos = -1;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800125 private static final String LOGTAG = "MediaPlaybackService";
126 private final Shuffler mRand = new Shuffler();
127 private int mOpenFailedCounter = 0;
128 String[] mCursorCols = new String[] {
Jack Heb0fba8b2017-01-26 15:54:38 -0800129 "audio._id AS _id", // index must match IDCOLIDX below
130 MediaStore.Audio.Media.ARTIST, MediaStore.Audio.Media.ALBUM,
131 MediaStore.Audio.Media.TITLE, MediaStore.Audio.Media.DATA,
132 MediaStore.Audio.Media.MIME_TYPE, MediaStore.Audio.Media.ALBUM_ID,
The Android Open Source Project792a2202009-03-03 19:32:30 -0800133 MediaStore.Audio.Media.ARTIST_ID,
134 MediaStore.Audio.Media.IS_PODCAST, // index must match PODCASTCOLIDX below
Jack Heb0fba8b2017-01-26 15:54:38 -0800135 MediaStore.Audio.Media.BOOKMARK // index must match BOOKMARKCOLIDX below
The Android Open Source Project792a2202009-03-03 19:32:30 -0800136 };
137 private final static int IDCOLIDX = 0;
138 private final static int PODCASTCOLIDX = 8;
139 private final static int BOOKMARKCOLIDX = 9;
140 private BroadcastReceiver mUnmountReceiver = null;
141 private WakeLock mWakeLock;
142 private int mServiceStartId = -1;
143 private boolean mServiceInUse = false;
Marco Nelissenc1333372009-05-06 12:54:47 -0700144 private boolean mIsSupposedToBePlaying = false;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800145 private boolean mQuietMode = false;
Jean-Michel Trivi085cbfd2010-03-08 17:06:18 -0800146 private AudioManager mAudioManager;
Marco Nelissen8d08ec22010-05-10 14:05:24 -0700147 private boolean mQueueIsSaveable = true;
Jean-Michel Trivi085cbfd2010-03-08 17:06:18 -0800148 // used to track what type of audio focus loss caused the playback to pause
149 private boolean mPausedByTransientLossOfFocus = false;
150
The Android Open Source Project792a2202009-03-03 19:32:30 -0800151 private SharedPreferences mPreferences;
152 // We use this to distinguish between different cards when saving/restoring playlists.
153 // This will have to change if we want to support multiple simultaneous cards.
154 private int mCardId;
Jack Heb0fba8b2017-01-26 15:54:38 -0800155
The Android Open Source Project490384b2009-03-11 12:11:59 -0700156 private MediaAppWidgetProvider mAppWidgetProvider = MediaAppWidgetProvider.getInstance();
Jack Heb0fba8b2017-01-26 15:54:38 -0800157
The Android Open Source Project792a2202009-03-03 19:32:30 -0800158 // interval after which we stop the service when idle
Jean-Michel Triviaa331872010-04-20 17:00:26 -0700159 private static final int IDLE_DELAY = 60000;
Marco Nelissen8717d342011-09-08 09:32:51 -0700160
161 private RemoteControlClient mRemoteControlClient;
162
The Android Open Source Project792a2202009-03-03 19:32:30 -0800163 private Handler mMediaplayerHandler = new Handler() {
164 float mCurrentVolume = 1.0f;
165 @Override
166 public void handleMessage(Message msg) {
Marco Nelissen39888902010-03-02 10:27:05 -0800167 MusicUtils.debugLog("mMediaplayerHandler.handleMessage " + msg.what);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800168 switch (msg.what) {
Marco Nelissen2da473d2010-09-29 16:11:12 -0700169 case FADEDOWN:
170 mCurrentVolume -= .05f;
171 if (mCurrentVolume > .2f) {
172 mMediaplayerHandler.sendEmptyMessageDelayed(FADEDOWN, 10);
173 } else {
174 mCurrentVolume = .2f;
175 }
176 mPlayer.setVolume(mCurrentVolume);
177 break;
178 case FADEUP:
179 mCurrentVolume += .01f;
180 if (mCurrentVolume < 1.0f) {
181 mMediaplayerHandler.sendEmptyMessageDelayed(FADEUP, 10);
182 } else {
183 mCurrentVolume = 1.0f;
184 }
185 mPlayer.setVolume(mCurrentVolume);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800186 break;
187 case SERVER_DIED:
Marco Nelissenc1333372009-05-06 12:54:47 -0700188 if (mIsSupposedToBePlaying) {
Marco Nelissenbd0d2f12012-02-27 16:02:15 -0800189 gotoNext(true);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800190 } else {
191 // the server died when we were idle, so just
192 // reopen the same song (it will start again
193 // from the beginning though when the user
194 // restarts)
Marco Nelissene41bd182012-03-14 08:24:40 -0700195 openCurrentAndNext();
The Android Open Source Project792a2202009-03-03 19:32:30 -0800196 }
197 break;
Marco Nelissene41bd182012-03-14 08:24:40 -0700198 case TRACK_WENT_TO_NEXT:
199 mPlayPos = mNextPlayPos;
200 if (mCursor != null) {
201 mCursor.close();
202 mCursor = null;
203 }
kenthinea507512012-10-16 03:34:11 +0800204 if (mPlayPos >= 0 && mPlayPos < mPlayList.length) {
205 mCursor = getCursorForId(mPlayList[mPlayPos]);
206 }
Marco Nelissene41bd182012-03-14 08:24:40 -0700207 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");
Jack Heb0fba8b2017-01-26 15:54:38 -0800229 if (isPlaying()) {
Marco Nelissen7181da82010-12-01 16:39:04 -0800230 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");
Jack Heb0fba8b2017-01-26 15:54:38 -0800240 if (isPlaying()) {
Marco Nelissen7181da82010-12-01 16:39:04 -0800241 mPausedByTransientLossOfFocus = true;
242 }
243 pause();
244 break;
245 case AudioManager.AUDIOFOCUS_GAIN:
246 Log.v(LOGTAG, "AudioFocus: received AUDIOFOCUS_GAIN");
Jack Heb0fba8b2017-01-26 15:54:38 -0800247 if (!isPlaying() && mPausedByTransientLossOfFocus) {
Marco Nelissen7181da82010-12-01 16:39:04 -0800248 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
Jack Heb0fba8b2017-01-26 15:54:38 -0800309 public MediaPlaybackService() {}
The Android Open Source Project792a2202009-03-03 19:32:30 -0800310
311 @Override
312 public void onCreate() {
313 super.onCreate();
Jean-Michel Trivi085cbfd2010-03-08 17:06:18 -0800314
315 mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
Jack Heb0fba8b2017-01-26 15:54:38 -0800316 ComponentName rec =
317 new ComponentName(getPackageName(), MediaButtonIntentReceiver.class.getName());
Marco Nelissen8717d342011-09-08 09:32:51 -0700318 mAudioManager.registerMediaButtonEventReceiver(rec);
Mike Lockwood8ef36792013-08-01 10:03:03 -0700319
320 Intent i = new Intent(Intent.ACTION_MEDIA_BUTTON);
321 i.setComponent(rec);
Jack Heb0fba8b2017-01-26 15:54:38 -0800322 PendingIntent pi = PendingIntent.getBroadcast(
323 this /*context*/, 0 /*requestCode, ignored*/, i /*intent*/, 0 /*flags*/);
Mike Lockwood8ef36792013-08-01 10:03:03 -0700324 mRemoteControlClient = new RemoteControlClient(pi);
325 mAudioManager.registerRemoteControlClient(mRemoteControlClient);
326
327 int flags = RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS
Jack Heb0fba8b2017-01-26 15:54:38 -0800328 | RemoteControlClient.FLAG_KEY_MEDIA_NEXT | RemoteControlClient.FLAG_KEY_MEDIA_PLAY
Mike Lockwood8ef36792013-08-01 10:03:03 -0700329 | RemoteControlClient.FLAG_KEY_MEDIA_PAUSE
330 | RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE
331 | RemoteControlClient.FLAG_KEY_MEDIA_STOP;
332 mRemoteControlClient.setTransportControlFlags(flags);
Jack Heb0fba8b2017-01-26 15:54:38 -0800333
Dmitry Shmidt6036ce62016-06-06 15:44:11 -0700334 mPreferences = getSharedPreferences("Music", MODE_PRIVATE);
Marco Nelissen87bbf3c2009-12-23 16:18:45 -0800335 mCardId = MusicUtils.getCardId(this);
Jack Heb0fba8b2017-01-26 15:54:38 -0800336
The Android Open Source Project792a2202009-03-03 19:32:30 -0800337 registerExternalStorageListener();
338
Jack Heb0fba8b2017-01-26 15:54:38 -0800339 // Needs to be done in this thread, since otherwise ApplicationContext.getPowerManager()
340 // crashes.
The Android Open Source Project792a2202009-03-03 19:32:30 -0800341 mPlayer = new MultiPlayer();
342 mPlayer.setHandler(mMediaplayerHandler);
343
The Android Open Source Project792a2202009-03-03 19:32:30 -0800344 reloadQueue();
Marco Nelissen8717d342011-09-08 09:32:51 -0700345 notifyChange(QUEUE_CHANGED);
346 notifyChange(META_CHANGED);
347
The Android Open Source Project792a2202009-03-03 19:32:30 -0800348 IntentFilter commandFilter = new IntentFilter();
349 commandFilter.addAction(SERVICECMD);
350 commandFilter.addAction(TOGGLEPAUSE_ACTION);
351 commandFilter.addAction(PAUSE_ACTION);
352 commandFilter.addAction(NEXT_ACTION);
353 commandFilter.addAction(PREVIOUS_ACTION);
354 registerReceiver(mIntentReceiver, commandFilter);
Jack Heb0fba8b2017-01-26 15:54:38 -0800355
356 PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800357 mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, this.getClass().getName());
358 mWakeLock.setReferenceCounted(false);
359
360 // If the service was idle, but got killed before it stopped itself, the
361 // system will relaunch it. Make sure it gets stopped again in that case.
362 Message msg = mDelayedStopHandler.obtainMessage();
363 mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
364 }
365
366 @Override
367 public void onDestroy() {
368 // Check that we're not being destroyed while something is still playing.
369 if (isPlaying()) {
Marco Nelissene99341f2009-11-11 11:13:51 -0800370 Log.e(LOGTAG, "Service being destroyed while still playing.");
The Android Open Source Project792a2202009-03-03 19:32:30 -0800371 }
Marco Nelissen2b0b9132009-05-21 16:26:47 -0700372 // release all MediaPlayer resources, including the native player and wakelocks
Marco Nelissenf2ef3b52010-09-21 15:47:27 -0700373 Intent i = new Intent(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION);
374 i.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, getAudioSessionId());
375 i.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, getPackageName());
376 sendBroadcast(i);
Marco Nelissen2b0b9132009-05-21 16:26:47 -0700377 mPlayer.release();
378 mPlayer = null;
Jean-Michel Trivi085cbfd2010-03-08 17:06:18 -0800379
380 mAudioManager.abandonAudioFocus(mAudioFocusListener);
Mike Lockwood8ef36792013-08-01 10:03:03 -0700381 mAudioManager.unregisterRemoteControlClient(mRemoteControlClient);
Jack Heb0fba8b2017-01-26 15:54:38 -0800382
Marco Nelissen2b0b9132009-05-21 16:26:47 -0700383 // make sure there aren't any other messages coming
384 mDelayedStopHandler.removeCallbacksAndMessages(null);
Marco Nelissen49e36ea2009-05-28 10:20:02 -0700385 mMediaplayerHandler.removeCallbacksAndMessages(null);
Marco Nelissen2b0b9132009-05-21 16:26:47 -0700386
The Android Open Source Project792a2202009-03-03 19:32:30 -0800387 if (mCursor != null) {
388 mCursor.close();
389 mCursor = null;
390 }
391
392 unregisterReceiver(mIntentReceiver);
393 if (mUnmountReceiver != null) {
394 unregisterReceiver(mUnmountReceiver);
395 mUnmountReceiver = null;
396 }
The Android Open Source Project792a2202009-03-03 19:32:30 -0800397 mWakeLock.release();
398 super.onDestroy();
399 }
Jack Heb0fba8b2017-01-26 15:54:38 -0800400
401 private final char hexdigits[] = new char[] {
402 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
The Android Open Source Project792a2202009-03-03 19:32:30 -0800403
404 private void saveQueue(boolean full) {
Marco Nelissen8d08ec22010-05-10 14:05:24 -0700405 if (!mQueueIsSaveable) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800406 return;
407 }
Marco Nelissen8d08ec22010-05-10 14:05:24 -0700408
The Android Open Source Project792a2202009-03-03 19:32:30 -0800409 Editor ed = mPreferences.edit();
Jack Heb0fba8b2017-01-26 15:54:38 -0800410 // long start = System.currentTimeMillis();
The Android Open Source Project792a2202009-03-03 19:32:30 -0800411 if (full) {
412 StringBuilder q = new StringBuilder();
Jack Heb0fba8b2017-01-26 15:54:38 -0800413
The Android Open Source Project792a2202009-03-03 19:32:30 -0800414 // The current playlist is saved as a list of "reverse hexadecimal"
415 // numbers, which we can generate faster than normal decimal or
416 // hexadecimal numbers, which in turn allows us to save the playlist
417 // more often without worrying too much about performance.
418 // (saving the full state takes about 40 ms under no-load conditions
419 // on the phone)
420 int len = mPlayListLen;
421 for (int i = 0; i < len; i++) {
Marco Nelissenbd447b62009-06-29 14:52:05 -0700422 long n = mPlayList[i];
Marco Nelissen13355022010-08-31 15:13:27 -0700423 if (n < 0) {
424 continue;
425 } else if (n == 0) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800426 q.append("0;");
427 } else {
428 while (n != 0) {
Jack Heb0fba8b2017-01-26 15:54:38 -0800429 int digit = (int) (n & 0xf);
Marco Nelissen13355022010-08-31 15:13:27 -0700430 n >>>= 4;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800431 q.append(hexdigits[digit]);
432 }
433 q.append(";");
434 }
435 }
Jack Heb0fba8b2017-01-26 15:54:38 -0800436 // Log.i("@@@@ service", "created queue string in " + (System.currentTimeMillis() -
437 // start) + " ms");
The Android Open Source Project792a2202009-03-03 19:32:30 -0800438 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
Jack Heb0fba8b2017-01-26 15:54:38 -0800468 // Log.i("@@@@ service", "saved state in " + (System.currentTimeMillis() - start) + " ms");
The Android Open Source Project792a2202009-03-03 19:32:30 -0800469 }
470
471 private void reloadQueue() {
472 String q = null;
Jack Heb0fba8b2017-01-26 15:54:38 -0800473
The Android Open Source Project792a2202009-03-03 19:32:30 -0800474 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) {
Jack Heb0fba8b2017-01-26 15:54:38 -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;
Jack Heb0fba8b2017-01-26 15:54:38 -0800521
The Android Open Source Project792a2202009-03-03 19:32:30 -0800522 // 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.
Jack Heb0fba8b2017-01-26 15:54:38 -0800527 Cursor crsr = MusicUtils.query(this, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
528 new String[] {"_id"}, "_id=" + mPlayList[mPlayPos], null, null);
Marco Nelissen63dbbbb2009-08-17 14:17:27 -0700529 if (crsr == null || crsr.getCount() == 0) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800530 // wait a bit and try again
531 SystemClock.sleep(3000);
Jack Heb0fba8b2017-01-26 15:54:38 -0800532 crsr = getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
533 mCursorCols, "_id=" + mPlayList[mPlayPos], null, null);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800534 }
Marco Nelissen63dbbbb2009-08-17 14:17:27 -0700535 if (crsr != null) {
536 crsr.close();
The Android Open Source Project792a2202009-03-03 19:32:30 -0800537 }
538
539 // Make sure we don't auto-skip to the next song, since that
540 // also starts playback. What could happen in that case is:
541 // - music is paused
542 // - go to UMS and delete some files, including the currently playing one
543 // - come back from UMS
544 // (time passes)
545 // - music app is killed for some reason (out of memory)
546 // - music service is restarted, service restores state, doesn't find
547 // the "current" file, goes to the next and: playback starts on its
548 // own, potentially at some random inconvenient time.
549 mOpenFailedCounter = 20;
550 mQuietMode = true;
Marco Nelissene41bd182012-03-14 08:24:40 -0700551 openCurrentAndNext();
The Android Open Source Project792a2202009-03-03 19:32:30 -0800552 mQuietMode = false;
553 if (!mPlayer.isInitialized()) {
554 // couldn't restore the saved state
555 mPlayListLen = 0;
556 return;
557 }
Jack Heb0fba8b2017-01-26 15:54:38 -0800558
The Android Open Source Project792a2202009-03-03 19:32:30 -0800559 long seekpos = mPreferences.getLong("seekpos", 0);
560 seek(seekpos >= 0 && seekpos < duration() ? seekpos : 0);
Jack Heb0fba8b2017-01-26 15:54:38 -0800561 Log.d(LOGTAG, "restored queue, currently at position " + position() + "/" + duration()
562 + " (requested " + seekpos + ")");
563
The Android Open Source Project792a2202009-03-03 19:32:30 -0800564 int repmode = mPreferences.getInt("repeatmode", REPEAT_NONE);
565 if (repmode != REPEAT_ALL && repmode != REPEAT_CURRENT) {
566 repmode = REPEAT_NONE;
567 }
568 mRepeatMode = repmode;
569
570 int shufmode = mPreferences.getInt("shufflemode", SHUFFLE_NONE);
571 if (shufmode != SHUFFLE_AUTO && shufmode != SHUFFLE_NORMAL) {
572 shufmode = SHUFFLE_NONE;
573 }
Marco Nelissen63dbbbb2009-08-17 14:17:27 -0700574 if (shufmode != SHUFFLE_NONE) {
575 // in shuffle mode we need to restore the history too
576 q = mPreferences.getString("history", "");
577 qlen = q != null ? q.length() : 0;
578 if (qlen > 1) {
579 plen = 0;
580 n = 0;
581 shift = 0;
582 mHistory.clear();
583 for (int i = 0; i < qlen; i++) {
584 char c = q.charAt(i);
585 if (c == ';') {
586 if (n >= mPlayListLen) {
587 // bogus history data
588 mHistory.clear();
589 break;
590 }
591 mHistory.add(n);
592 n = 0;
593 shift = 0;
594 } else {
595 if (c >= '0' && c <= '9') {
596 n += ((c - '0') << shift);
597 } else if (c >= 'a' && c <= 'f') {
598 n += ((10 + c - 'a') << shift);
599 } else {
600 // bogus history data
601 mHistory.clear();
602 break;
603 }
604 shift += 4;
605 }
606 }
607 }
608 }
The Android Open Source Project792a2202009-03-03 19:32:30 -0800609 if (shufmode == SHUFFLE_AUTO) {
Jack Heb0fba8b2017-01-26 15:54:38 -0800610 if (!makeAutoShuffleList()) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800611 shufmode = SHUFFLE_NONE;
612 }
613 }
614 mShuffleMode = shufmode;
615 }
616 }
Jack Heb0fba8b2017-01-26 15:54:38 -0800617
The Android Open Source Project792a2202009-03-03 19:32:30 -0800618 @Override
619 public IBinder onBind(Intent intent) {
620 mDelayedStopHandler.removeCallbacksAndMessages(null);
621 mServiceInUse = true;
622 return mBinder;
623 }
624
625 @Override
626 public void onRebind(Intent intent) {
627 mDelayedStopHandler.removeCallbacksAndMessages(null);
628 mServiceInUse = true;
629 }
630
631 @Override
Marco Nelissenc1017e52009-09-24 13:21:06 -0700632 public int onStartCommand(Intent intent, int flags, int startId) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800633 mServiceStartId = startId;
634 mDelayedStopHandler.removeCallbacksAndMessages(null);
Marco Nelissenc1017e52009-09-24 13:21:06 -0700635
636 if (intent != null) {
637 String action = intent.getAction();
638 String cmd = intent.getStringExtra("command");
Marco Nelissen39888902010-03-02 10:27:05 -0800639 MusicUtils.debugLog("onStartCommand " + action + " / " + cmd);
Marco Nelissenc1017e52009-09-24 13:21:06 -0700640
641 if (CMDNEXT.equals(cmd) || NEXT_ACTION.equals(action)) {
Marco Nelissenbd0d2f12012-02-27 16:02:15 -0800642 gotoNext(true);
Marco Nelissenc1017e52009-09-24 13:21:06 -0700643 } else if (CMDPREVIOUS.equals(cmd) || PREVIOUS_ACTION.equals(action)) {
Marco Nelissenb63b5d12009-11-18 16:04:19 -0800644 if (position() < 2000) {
645 prev();
646 } else {
647 seek(0);
648 play();
649 }
Marco Nelissenc1017e52009-09-24 13:21:06 -0700650 } else if (CMDTOGGLEPAUSE.equals(cmd) || TOGGLEPAUSE_ACTION.equals(action)) {
651 if (isPlaying()) {
652 pause();
Jean-Michel Triviaa331872010-04-20 17:00:26 -0700653 mPausedByTransientLossOfFocus = false;
Marco Nelissenc1017e52009-09-24 13:21:06 -0700654 } else {
655 play();
656 }
657 } else if (CMDPAUSE.equals(cmd) || PAUSE_ACTION.equals(action)) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800658 pause();
Jean-Michel Triviaa331872010-04-20 17:00:26 -0700659 mPausedByTransientLossOfFocus = false;
Jaikumar Ganesh47dd8472010-12-16 15:51:31 -0800660 } else if (CMDPLAY.equals(cmd)) {
661 play();
Marco Nelissenc1017e52009-09-24 13:21:06 -0700662 } else if (CMDSTOP.equals(cmd)) {
663 pause();
Jean-Michel Triviaa331872010-04-20 17:00:26 -0700664 mPausedByTransientLossOfFocus = false;
Marco Nelissenc1017e52009-09-24 13:21:06 -0700665 seek(0);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800666 }
The Android Open Source Project792a2202009-03-03 19:32:30 -0800667 }
Jack Heb0fba8b2017-01-26 15:54:38 -0800668
The Android Open Source Project792a2202009-03-03 19:32:30 -0800669 // make sure the service will shut down on its own if it was
670 // just started but not bound to and nothing is playing
671 mDelayedStopHandler.removeCallbacksAndMessages(null);
672 Message msg = mDelayedStopHandler.obtainMessage();
673 mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
Marco Nelissenc1017e52009-09-24 13:21:06 -0700674 return START_STICKY;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800675 }
Jack Heb0fba8b2017-01-26 15:54:38 -0800676
The Android Open Source Project792a2202009-03-03 19:32:30 -0800677 @Override
678 public boolean onUnbind(Intent intent) {
679 mServiceInUse = false;
680
681 // Take a snapshot of the current playlist
682 saveQueue(true);
683
Jean-Michel Triviaa331872010-04-20 17:00:26 -0700684 if (isPlaying() || mPausedByTransientLossOfFocus) {
Jack Heb0fba8b2017-01-26 15:54:38 -0800685 // something is currently playing, or will be playing once
Jean-Michel Triviaa331872010-04-20 17:00:26 -0700686 // 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 -0800687 return true;
688 }
Jack Heb0fba8b2017-01-26 15:54:38 -0800689
The Android Open Source Project792a2202009-03-03 19:32:30 -0800690 // If there is a playlist but playback is paused, then wait a while
691 // before stopping the service, so that pause/resume isn't slow.
692 // Also delay stopping the service if we're transitioning between tracks.
Jack Heb0fba8b2017-01-26 15:54:38 -0800693 if (mPlayListLen > 0 || mMediaplayerHandler.hasMessages(TRACK_ENDED)) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800694 Message msg = mDelayedStopHandler.obtainMessage();
695 mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
696 return true;
697 }
Jack Heb0fba8b2017-01-26 15:54:38 -0800698
The Android Open Source Project792a2202009-03-03 19:32:30 -0800699 // No active playlist, OK to stop the service right now
700 stopSelf(mServiceStartId);
701 return true;
702 }
Jack Heb0fba8b2017-01-26 15:54:38 -0800703
The Android Open Source Project792a2202009-03-03 19:32:30 -0800704 private Handler mDelayedStopHandler = new Handler() {
705 @Override
706 public void handleMessage(Message msg) {
707 // Check again to make sure nothing is playing right now
Jean-Michel Triviaa331872010-04-20 17:00:26 -0700708 if (isPlaying() || mPausedByTransientLossOfFocus || mServiceInUse
The Android Open Source Project792a2202009-03-03 19:32:30 -0800709 || mMediaplayerHandler.hasMessages(TRACK_ENDED)) {
710 return;
711 }
712 // save the queue again, because it might have changed
713 // since the user exited the music app (because of
714 // party-shuffle or because the play-position changed)
715 saveQueue(true);
716 stopSelf(mServiceStartId);
717 }
718 };
Marco Nelissenf2ef3b52010-09-21 15:47:27 -0700719
The Android Open Source Project792a2202009-03-03 19:32:30 -0800720 /**
721 * Called when we receive a ACTION_MEDIA_EJECT notification.
722 *
723 * @param storagePath path to mount point for the removed media
724 */
725 public void closeExternalStorageFiles(String storagePath) {
726 // stop playback and clean up if the SD card is going to be unmounted.
727 stop(true);
728 notifyChange(QUEUE_CHANGED);
729 notifyChange(META_CHANGED);
730 }
731
732 /**
733 * Registers an intent to listen for ACTION_MEDIA_EJECT notifications.
734 * The intent will call closeExternalStorageFiles() if the external media
735 * is going to be ejected, so applications can clean up any files they have open.
736 */
737 public void registerExternalStorageListener() {
738 if (mUnmountReceiver == null) {
739 mUnmountReceiver = new BroadcastReceiver() {
740 @Override
741 public void onReceive(Context context, Intent intent) {
742 String action = intent.getAction();
743 if (action.equals(Intent.ACTION_MEDIA_EJECT)) {
744 saveQueue(true);
Marco Nelissen8d08ec22010-05-10 14:05:24 -0700745 mQueueIsSaveable = false;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800746 closeExternalStorageFiles(intent.getData().getPath());
747 } else if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) {
748 mMediaMountedCount++;
Marco Nelissen87bbf3c2009-12-23 16:18:45 -0800749 mCardId = MusicUtils.getCardId(MediaPlaybackService.this);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800750 reloadQueue();
Marco Nelissen8d08ec22010-05-10 14:05:24 -0700751 mQueueIsSaveable = true;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800752 notifyChange(QUEUE_CHANGED);
753 notifyChange(META_CHANGED);
754 }
755 }
756 };
757 IntentFilter iFilter = new IntentFilter();
758 iFilter.addAction(Intent.ACTION_MEDIA_EJECT);
759 iFilter.addAction(Intent.ACTION_MEDIA_MOUNTED);
760 iFilter.addDataScheme("file");
761 registerReceiver(mUnmountReceiver, iFilter);
762 }
763 }
764
765 /**
766 * Notify the change-receivers that something has changed.
767 * The intent that is sent contains the following data
768 * for the currently playing track:
769 * "id" - Integer: the database row ID
770 * "artist" - String: the name of the artist
771 * "album" - String: the name of the album
772 * "track" - String: the name of the track
773 * The intent has an action that is one of
774 * "com.android.music.metachanged"
775 * "com.android.music.queuechanged",
776 * "com.android.music.playbackcomplete"
777 * "com.android.music.playstatechanged"
778 * respectively indicating that a new track has
779 * started playing, that the playback queue has
780 * changed, that playback has stopped because
781 * the last file in the list has been played,
782 * or that the play-state changed (paused/resumed).
783 */
784 private void notifyChange(String what) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800785 Intent i = new Intent(what);
Marco Nelissenbd447b62009-06-29 14:52:05 -0700786 i.putExtra("id", Long.valueOf(getAudioId()));
The Android Open Source Project792a2202009-03-03 19:32:30 -0800787 i.putExtra("artist", getArtistName());
Jack Heb0fba8b2017-01-26 15:54:38 -0800788 i.putExtra("album", getAlbumName());
The Android Open Source Project792a2202009-03-03 19:32:30 -0800789 i.putExtra("track", getTrackName());
Marco Nelissen6b507de2010-10-20 14:08:10 -0700790 i.putExtra("playing", isPlaying());
791 sendStickyBroadcast(i);
Marco Nelissen8717d342011-09-08 09:32:51 -0700792
793 if (what.equals(PLAYSTATE_CHANGED)) {
Jack Heb0fba8b2017-01-26 15:54:38 -0800794 mRemoteControlClient.setPlaybackState(isPlaying()
795 ? RemoteControlClient.PLAYSTATE_PLAYING
796 : RemoteControlClient.PLAYSTATE_PAUSED);
Marco Nelissen8717d342011-09-08 09:32:51 -0700797 } else if (what.equals(META_CHANGED)) {
Mike Lockwood8ef36792013-08-01 10:03:03 -0700798 RemoteControlClient.MetadataEditor ed = mRemoteControlClient.editMetadata(true);
799 ed.putString(MediaMetadataRetriever.METADATA_KEY_TITLE, getTrackName());
800 ed.putString(MediaMetadataRetriever.METADATA_KEY_ALBUM, getAlbumName());
801 ed.putString(MediaMetadataRetriever.METADATA_KEY_ARTIST, getArtistName());
802 ed.putLong(MediaMetadataRetriever.METADATA_KEY_DURATION, duration());
803 Bitmap b = MusicUtils.getArtwork(this, getAudioId(), getAlbumId(), false);
804 if (b != null) {
805 ed.putBitmap(MetadataEditor.BITMAP_KEY_ARTWORK, b);
806 }
807 ed.apply();
Marco Nelissen8717d342011-09-08 09:32:51 -0700808 }
809
The Android Open Source Project792a2202009-03-03 19:32:30 -0800810 if (what.equals(QUEUE_CHANGED)) {
811 saveQueue(true);
812 } else {
813 saveQueue(false);
814 }
Jack Heb0fba8b2017-01-26 15:54:38 -0800815
The Android Open Source Project490384b2009-03-11 12:11:59 -0700816 // Share this notification directly with our widgets
817 mAppWidgetProvider.notifyChange(this, what);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800818 }
819
820 private void ensurePlayListCapacity(int size) {
821 if (mPlayList == null || size > mPlayList.length) {
822 // reallocate at 2x requested size so we don't
823 // need to grow and copy the array for every
824 // insert
Jack Heb0fba8b2017-01-26 15:54:38 -0800825 long[] newlist = new long[size * 2];
Marco Nelissenbf0ea142009-06-05 12:46:09 -0700826 int len = mPlayList != null ? mPlayList.length : mPlayListLen;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800827 for (int i = 0; i < len; i++) {
828 newlist[i] = mPlayList[i];
829 }
830 mPlayList = newlist;
831 }
832 // FIXME: shrink the array when the needed size is much smaller
833 // than the allocated size
834 }
Jack Heb0fba8b2017-01-26 15:54:38 -0800835
The Android Open Source Project792a2202009-03-03 19:32:30 -0800836 // insert the list of songs at the specified position in the playlist
Jack Heb0fba8b2017-01-26 15:54:38 -0800837 private void addToPlayList(long[] list, int position) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800838 int addlen = list.length;
839 if (position < 0) { // overwrite
840 mPlayListLen = 0;
841 position = 0;
842 }
843 ensurePlayListCapacity(mPlayListLen + addlen);
844 if (position > mPlayListLen) {
845 position = mPlayListLen;
846 }
Jack Heb0fba8b2017-01-26 15:54:38 -0800847
The Android Open Source Project792a2202009-03-03 19:32:30 -0800848 // move part of list after insertion point
849 int tailsize = mPlayListLen - position;
Jack Heb0fba8b2017-01-26 15:54:38 -0800850 for (int i = tailsize; i > 0; i--) {
851 mPlayList[position + i] = mPlayList[position + i - addlen];
The Android Open Source Project792a2202009-03-03 19:32:30 -0800852 }
Jack Heb0fba8b2017-01-26 15:54:38 -0800853
The Android Open Source Project792a2202009-03-03 19:32:30 -0800854 // copy list into playlist
855 for (int i = 0; i < addlen; i++) {
856 mPlayList[position + i] = list[i];
857 }
858 mPlayListLen += addlen;
Marco Nelissen3aa9ad02010-09-16 13:23:11 -0700859 if (mPlayListLen == 0) {
860 mCursor.close();
861 mCursor = null;
862 notifyChange(META_CHANGED);
863 }
The Android Open Source Project792a2202009-03-03 19:32:30 -0800864 }
Jack Heb0fba8b2017-01-26 15:54:38 -0800865
The Android Open Source Project792a2202009-03-03 19:32:30 -0800866 /**
867 * Appends a list of tracks to the current playlist.
868 * If nothing is playing currently, playback will be started at
869 * the first track.
870 * If the action is NOW, playback will switch to the first of
871 * the new tracks immediately.
872 * @param list The list of tracks to append.
873 * @param action NOW, NEXT or LAST
874 */
Jack Heb0fba8b2017-01-26 15:54:38 -0800875 public void enqueue(long[] list, int action) {
876 synchronized (this) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800877 if (action == NEXT && mPlayPos + 1 < mPlayListLen) {
878 addToPlayList(list, mPlayPos + 1);
879 notifyChange(QUEUE_CHANGED);
880 } else {
881 // action == LAST || action == NOW || mPlayPos + 1 == mPlayListLen
882 addToPlayList(list, Integer.MAX_VALUE);
883 notifyChange(QUEUE_CHANGED);
884 if (action == NOW) {
885 mPlayPos = mPlayListLen - list.length;
Marco Nelissene41bd182012-03-14 08:24:40 -0700886 openCurrentAndNext();
The Android Open Source Project792a2202009-03-03 19:32:30 -0800887 play();
888 notifyChange(META_CHANGED);
889 return;
890 }
891 }
892 if (mPlayPos < 0) {
893 mPlayPos = 0;
Marco Nelissene41bd182012-03-14 08:24:40 -0700894 openCurrentAndNext();
The Android Open Source Project792a2202009-03-03 19:32:30 -0800895 play();
896 notifyChange(META_CHANGED);
897 }
898 }
899 }
900
901 /**
902 * Replaces the current playlist with a new list,
903 * and prepares for starting playback at the specified
904 * position in the list, or a random position if the
905 * specified position is 0.
906 * @param list The new list of tracks.
907 */
Jack Heb0fba8b2017-01-26 15:54:38 -0800908 public void open(long[] list, int position) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800909 synchronized (this) {
910 if (mShuffleMode == SHUFFLE_AUTO) {
911 mShuffleMode = SHUFFLE_NORMAL;
912 }
Marco Nelissenbd447b62009-06-29 14:52:05 -0700913 long oldId = getAudioId();
The Android Open Source Project792a2202009-03-03 19:32:30 -0800914 int listlength = list.length;
915 boolean newlist = true;
916 if (mPlayListLen == listlength) {
917 // possible fast path: list might be the same
918 newlist = false;
919 for (int i = 0; i < listlength; i++) {
920 if (list[i] != mPlayList[i]) {
921 newlist = true;
922 break;
923 }
924 }
925 }
926 if (newlist) {
927 addToPlayList(list, -1);
928 notifyChange(QUEUE_CHANGED);
929 }
930 int oldpos = mPlayPos;
931 if (position >= 0) {
932 mPlayPos = position;
933 } else {
934 mPlayPos = mRand.nextInt(mPlayListLen);
935 }
936 mHistory.clear();
937
938 saveBookmarkIfNeeded();
Marco Nelissene41bd182012-03-14 08:24:40 -0700939 openCurrentAndNext();
Jeffrey Sharkeyd8c69672009-03-24 17:59:05 -0700940 if (oldId != getAudioId()) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800941 notifyChange(META_CHANGED);
942 }
943 }
944 }
Jack Heb0fba8b2017-01-26 15:54:38 -0800945
The Android Open Source Project792a2202009-03-03 19:32:30 -0800946 /**
947 * Moves the item at index1 to index2.
948 * @param index1
949 * @param index2
950 */
951 public void moveQueueItem(int index1, int index2) {
952 synchronized (this) {
953 if (index1 >= mPlayListLen) {
954 index1 = mPlayListLen - 1;
955 }
956 if (index2 >= mPlayListLen) {
957 index2 = mPlayListLen - 1;
958 }
959 if (index1 < index2) {
Marco Nelissenbd447b62009-06-29 14:52:05 -0700960 long tmp = mPlayList[index1];
The Android Open Source Project792a2202009-03-03 19:32:30 -0800961 for (int i = index1; i < index2; i++) {
Jack Heb0fba8b2017-01-26 15:54:38 -0800962 mPlayList[i] = mPlayList[i + 1];
The Android Open Source Project792a2202009-03-03 19:32:30 -0800963 }
964 mPlayList[index2] = tmp;
965 if (mPlayPos == index1) {
966 mPlayPos = index2;
967 } else if (mPlayPos >= index1 && mPlayPos <= index2) {
Jack Heb0fba8b2017-01-26 15:54:38 -0800968 mPlayPos--;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800969 }
970 } else if (index2 < index1) {
Marco Nelissenbd447b62009-06-29 14:52:05 -0700971 long tmp = mPlayList[index1];
The Android Open Source Project792a2202009-03-03 19:32:30 -0800972 for (int i = index1; i > index2; i--) {
Jack Heb0fba8b2017-01-26 15:54:38 -0800973 mPlayList[i] = mPlayList[i - 1];
The Android Open Source Project792a2202009-03-03 19:32:30 -0800974 }
975 mPlayList[index2] = tmp;
976 if (mPlayPos == index1) {
977 mPlayPos = index2;
978 } else if (mPlayPos >= index2 && mPlayPos <= index1) {
Jack Heb0fba8b2017-01-26 15:54:38 -0800979 mPlayPos++;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800980 }
981 }
982 notifyChange(QUEUE_CHANGED);
983 }
984 }
985
986 /**
987 * Returns the current play list
988 * @return An array of integers containing the IDs of the tracks in the play list
989 */
Jack Heb0fba8b2017-01-26 15:54:38 -0800990 public long[] getQueue() {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800991 synchronized (this) {
992 int len = mPlayListLen;
Jack Heb0fba8b2017-01-26 15:54:38 -0800993 long[] list = new long[len];
The Android Open Source Project792a2202009-03-03 19:32:30 -0800994 for (int i = 0; i < len; i++) {
995 list[i] = mPlayList[i];
996 }
997 return list;
998 }
999 }
1000
Marco Nelissene41bd182012-03-14 08:24:40 -07001001 private Cursor getCursorForId(long lid) {
1002 String id = String.valueOf(lid);
1003
1004 Cursor c = getContentResolver().query(
Jack Heb0fba8b2017-01-26 15:54:38 -08001005 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, mCursorCols, "_id=" + id, null, null);
Marco Nelissenc37b2002012-10-16 12:59:54 -07001006 if (c != null) {
1007 c.moveToFirst();
1008 }
Marco Nelissene41bd182012-03-14 08:24:40 -07001009 return c;
1010 }
1011
1012 private void openCurrentAndNext() {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001013 synchronized (this) {
1014 if (mCursor != null) {
1015 mCursor.close();
1016 mCursor = null;
1017 }
Marco Nelissen8d08ec22010-05-10 14:05:24 -07001018
The Android Open Source Project792a2202009-03-03 19:32:30 -08001019 if (mPlayListLen == 0) {
1020 return;
1021 }
1022 stop(false);
1023
Marco Nelissen90d1f622012-04-05 12:27:56 -07001024 mCursor = getCursorForId(mPlayList[mPlayPos]);
Jack Heb0fba8b2017-01-26 15:54:38 -08001025 while (true) {
1026 if (mCursor != null && mCursor.getCount() != 0
1027 && open(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + "/"
1028 + mCursor.getLong(IDCOLIDX))) {
Marco Nelissenc37b2002012-10-16 12:59:54 -07001029 break;
Marco Nelissen6fb85512012-07-18 07:46:21 -07001030 }
Marco Nelissenc37b2002012-10-16 12:59:54 -07001031 // if we get here then opening the file failed. We can close the cursor now, because
1032 // we're either going to create a new one next, or stop trying
1033 if (mCursor != null) {
1034 mCursor.close();
1035 mCursor = null;
1036 }
Jack Heb0fba8b2017-01-26 15:54:38 -08001037 if (mOpenFailedCounter++ < 10 && mPlayListLen > 1) {
Marco Nelissen90d1f622012-04-05 12:27:56 -07001038 int pos = getNextPosition(false);
1039 if (pos < 0) {
1040 gotoIdleState();
1041 if (mIsSupposedToBePlaying) {
1042 mIsSupposedToBePlaying = false;
1043 notifyChange(PLAYSTATE_CHANGED);
1044 }
1045 return;
1046 }
1047 mPlayPos = pos;
1048 stop(false);
1049 mPlayPos = pos;
1050 mCursor = getCursorForId(mPlayList[mPlayPos]);
1051 } else {
1052 mOpenFailedCounter = 0;
1053 if (!mQuietMode) {
1054 Toast.makeText(this, R.string.playback_failed, Toast.LENGTH_SHORT).show();
1055 }
1056 Log.d(LOGTAG, "Failed to open file for playback");
Marco Nelissenc37b2002012-10-16 12:59:54 -07001057 gotoIdleState();
1058 if (mIsSupposedToBePlaying) {
1059 mIsSupposedToBePlaying = false;
1060 notifyChange(PLAYSTATE_CHANGED);
1061 }
Marco Nelissen90d1f622012-04-05 12:27:56 -07001062 return;
Marco Nelissene41bd182012-03-14 08:24:40 -07001063 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08001064 }
Marco Nelissen90d1f622012-04-05 12:27:56 -07001065
1066 // go to bookmark if needed
1067 if (isPodcast()) {
1068 long bookmark = getBookmark();
1069 // Start playing a little bit before the bookmark,
1070 // so it's easier to get back in to the narrative.
1071 seek(bookmark - 5000);
1072 }
1073 setNextTrack();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001074 }
1075 }
1076
Marco Nelissene41bd182012-03-14 08:24:40 -07001077 private void setNextTrack() {
1078 mNextPlayPos = getNextPosition(false);
1079 if (mNextPlayPos >= 0) {
1080 long id = mPlayList[mNextPlayPos];
1081 mPlayer.setNextDataSource(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + "/" + id);
Jun Tian0a7f03f2013-07-22 16:21:20 +08001082 } else {
1083 mPlayer.setNextDataSource(null);
Marco Nelissene41bd182012-03-14 08:24:40 -07001084 }
1085 }
1086
The Android Open Source Project792a2202009-03-03 19:32:30 -08001087 /**
1088 * Opens the specified file and readies it for playback.
1089 *
1090 * @param path The full path of the file to be opened.
The Android Open Source Project792a2202009-03-03 19:32:30 -08001091 */
Marco Nelissen90d1f622012-04-05 12:27:56 -07001092 public boolean open(String path) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001093 synchronized (this) {
1094 if (path == null) {
Marco Nelissen90d1f622012-04-05 12:27:56 -07001095 return false;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001096 }
Jack Heb0fba8b2017-01-26 15:54:38 -08001097
The Android Open Source Project792a2202009-03-03 19:32:30 -08001098 // if mCursor is null, try to associate path with a database cursor
1099 if (mCursor == null) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001100 ContentResolver resolver = getContentResolver();
1101 Uri uri;
1102 String where;
1103 String selectionArgs[];
1104 if (path.startsWith("content://media/")) {
1105 uri = Uri.parse(path);
1106 where = null;
1107 selectionArgs = null;
1108 } else {
Jack Heb0fba8b2017-01-26 15:54:38 -08001109 uri = MediaStore.Audio.Media.getContentUriForPath(path);
1110 where = MediaStore.Audio.Media.DATA + "=?";
1111 selectionArgs = new String[] {path};
The Android Open Source Project792a2202009-03-03 19:32:30 -08001112 }
Jack Heb0fba8b2017-01-26 15:54:38 -08001113
The Android Open Source Project792a2202009-03-03 19:32:30 -08001114 try {
1115 mCursor = resolver.query(uri, mCursorCols, where, selectionArgs, null);
Jack Heb0fba8b2017-01-26 15:54:38 -08001116 if (mCursor != null) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001117 if (mCursor.getCount() == 0) {
1118 mCursor.close();
1119 mCursor = null;
1120 } else {
1121 mCursor.moveToNext();
1122 ensurePlayListCapacity(1);
1123 mPlayListLen = 1;
Marco Nelissenbd447b62009-06-29 14:52:05 -07001124 mPlayList[0] = mCursor.getLong(IDCOLIDX);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001125 mPlayPos = 0;
1126 }
1127 }
1128 } catch (UnsupportedOperationException ex) {
1129 }
1130 }
1131 mFileToPlay = path;
1132 mPlayer.setDataSource(mFileToPlay);
Marco Nelissen90d1f622012-04-05 12:27:56 -07001133 if (mPlayer.isInitialized()) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001134 mOpenFailedCounter = 0;
Marco Nelissen90d1f622012-04-05 12:27:56 -07001135 return true;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001136 }
Marco Nelissen90d1f622012-04-05 12:27:56 -07001137 stop(true);
1138 return false;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001139 }
1140 }
1141
1142 /**
1143 * Starts playback of a previously opened file.
1144 */
1145 public void play() {
Jack Heb0fba8b2017-01-26 15:54:38 -08001146 mAudioManager.requestAudioFocus(
1147 mAudioFocusListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
1148 mAudioManager.registerMediaButtonEventReceiver(new ComponentName(
1149 this.getPackageName(), MediaButtonIntentReceiver.class.getName()));
Jean-Michel Trivi085cbfd2010-03-08 17:06:18 -08001150
The Android Open Source Project792a2202009-03-03 19:32:30 -08001151 if (mPlayer.isInitialized()) {
Thomas Tuttle272eb782009-01-28 21:06:46 -05001152 // if we are at the end of the song, go to the next song first
Marco Nelissen2f9a1ce2009-06-26 14:23:31 -07001153 long duration = mPlayer.duration();
Jack Heb0fba8b2017-01-26 15:54:38 -08001154 if (mRepeatMode != REPEAT_CURRENT && duration > 2000
1155 && mPlayer.position() >= duration - 2000) {
Marco Nelissenbd0d2f12012-02-27 16:02:15 -08001156 gotoNext(true);
Thomas Tuttle272eb782009-01-28 21:06:46 -05001157 }
1158
The Android Open Source Project792a2202009-03-03 19:32:30 -08001159 mPlayer.start();
Marco Nelissen7181da82010-12-01 16:39:04 -08001160 // make sure we fade in, in case a previous fadein was stopped because
1161 // of another focus loss
Marco Nelissen7bae28f2010-12-02 10:04:54 -08001162 mMediaplayerHandler.removeMessages(FADEDOWN);
Marco Nelissen7181da82010-12-01 16:39:04 -08001163 mMediaplayerHandler.sendEmptyMessage(FADEUP);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001164
Marco Nelissene41bd182012-03-14 08:24:40 -07001165 updateNotification();
Marco Nelissenc1333372009-05-06 12:54:47 -07001166 if (!mIsSupposedToBePlaying) {
Mike Cleron347fe572009-10-09 12:26:28 -07001167 mIsSupposedToBePlaying = true;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001168 notifyChange(PLAYSTATE_CHANGED);
1169 }
Mike Cleron347fe572009-10-09 12:26:28 -07001170
The Android Open Source Project792a2202009-03-03 19:32:30 -08001171 } else if (mPlayListLen <= 0) {
1172 // This is mostly so that if you press 'play' on a bluetooth headset
1173 // without every having played anything before, it will still play
1174 // something.
1175 setShuffleMode(SHUFFLE_AUTO);
1176 }
1177 }
Marco Nelissene41bd182012-03-14 08:24:40 -07001178
1179 private void updateNotification() {
1180 RemoteViews views = new RemoteViews(getPackageName(), R.layout.statusbar);
1181 views.setImageViewResource(R.id.icon, R.drawable.stat_notify_musicplayer);
1182 if (getAudioId() < 0) {
1183 // streaming
1184 views.setTextViewText(R.id.trackname, getPath());
1185 views.setTextViewText(R.id.artistalbum, null);
1186 } else {
1187 String artist = getArtistName();
1188 views.setTextViewText(R.id.trackname, getTrackName());
1189 if (artist == null || artist.equals(MediaStore.UNKNOWN_STRING)) {
1190 artist = getString(R.string.unknown_artist_name);
1191 }
1192 String album = getAlbumName();
1193 if (album == null || album.equals(MediaStore.UNKNOWN_STRING)) {
1194 album = getString(R.string.unknown_album_name);
1195 }
1196
Jack Heb0fba8b2017-01-26 15:54:38 -08001197 views.setTextViewText(
1198 R.id.artistalbum, getString(R.string.notification_artist_album, artist, album));
Marco Nelissene41bd182012-03-14 08:24:40 -07001199 }
1200 Notification status = new Notification();
1201 status.contentView = views;
1202 status.flags |= Notification.FLAG_ONGOING_EVENT;
1203 status.icon = R.drawable.stat_notify_musicplayer;
Jack Heb0fba8b2017-01-26 15:54:38 -08001204 status.contentIntent =
1205 PendingIntent.getActivity(this, 0, new Intent("com.android.music.PLAYBACK_VIEWER")
1206 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
1207 0);
Marco Nelissene41bd182012-03-14 08:24:40 -07001208 startForeground(PLAYBACKSERVICE_STATUS, status);
1209 }
1210
The Android Open Source Project792a2202009-03-03 19:32:30 -08001211 private void stop(boolean remove_status_icon) {
Glenn Kastenb1c285c2012-11-07 15:44:01 -08001212 if (mPlayer != null && mPlayer.isInitialized()) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001213 mPlayer.stop();
1214 }
1215 mFileToPlay = null;
1216 if (mCursor != null) {
1217 mCursor.close();
1218 mCursor = null;
1219 }
1220 if (remove_status_icon) {
1221 gotoIdleState();
Dianne Hackbornd5fc5b62009-08-18 11:35:30 -07001222 } else {
1223 stopForeground(false);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001224 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08001225 if (remove_status_icon) {
Marco Nelissenc1333372009-05-06 12:54:47 -07001226 mIsSupposedToBePlaying = false;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001227 }
1228 }
1229
1230 /**
1231 * Stops playback.
1232 */
1233 public void stop() {
1234 stop(true);
1235 }
1236
1237 /**
1238 * Pauses playback (call play() to resume)
1239 */
1240 public void pause() {
Jack Heb0fba8b2017-01-26 15:54:38 -08001241 synchronized (this) {
Marco Nelissen7181da82010-12-01 16:39:04 -08001242 mMediaplayerHandler.removeMessages(FADEUP);
Marco Nelissen407cf912009-05-11 09:56:31 -07001243 if (isPlaying()) {
1244 mPlayer.pause();
1245 gotoIdleState();
Marco Nelissen407cf912009-05-11 09:56:31 -07001246 mIsSupposedToBePlaying = false;
1247 notifyChange(PLAYSTATE_CHANGED);
1248 saveBookmarkIfNeeded();
1249 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08001250 }
1251 }
1252
Marco Nelissenb6e7bf72009-05-11 10:28:31 -07001253 /** Returns whether something is currently playing
The Android Open Source Project792a2202009-03-03 19:32:30 -08001254 *
Marco Nelissenb6e7bf72009-05-11 10:28:31 -07001255 * @return true if something is playing (or will be playing shortly, in case
1256 * we're currently transitioning between tracks), false if not.
The Android Open Source Project792a2202009-03-03 19:32:30 -08001257 */
1258 public boolean isPlaying() {
Marco Nelissenc1333372009-05-06 12:54:47 -07001259 return mIsSupposedToBePlaying;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001260 }
1261
1262 /*
1263 Desired behavior for prev/next/shuffle:
1264
1265 - NEXT will move to the next track in the list when not shuffling, and to
1266 a track randomly picked from the not-yet-played tracks when shuffling.
1267 If all tracks have already been played, pick from the full set, but
1268 avoid picking the previously played track if possible.
1269 - when shuffling, PREV will go to the previously played track. Hitting PREV
1270 again will go to the track played before that, etc. When the start of the
1271 history has been reached, PREV is a no-op.
1272 When not shuffling, PREV will go to the sequentially previous track (the
1273 difference with the shuffle-case is mainly that when not shuffling, the
1274 user can back up to tracks that are not in the history).
1275
1276 Example:
1277 When playing an album with 10 tracks from the start, and enabling shuffle
1278 while playing track 5, the remaining tracks (6-10) will be shuffled, e.g.
1279 the final play order might be 1-2-3-4-5-8-10-6-9-7.
1280 When hitting 'prev' 8 times while playing track 7 in this example, the
1281 user will go to tracks 9-6-10-8-5-4-3-2. If the user then hits 'next',
1282 a random track will be picked again. If at any time user disables shuffling
1283 the next/previous track will be picked in sequential order again.
1284 */
1285
1286 public void prev() {
1287 synchronized (this) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001288 if (mShuffleMode == SHUFFLE_NORMAL) {
1289 // go to previously-played track and remove it from the history
1290 int histsize = mHistory.size();
1291 if (histsize == 0) {
1292 // prev is a no-op
1293 return;
1294 }
1295 Integer pos = mHistory.remove(histsize - 1);
1296 mPlayPos = pos.intValue();
1297 } else {
1298 if (mPlayPos > 0) {
1299 mPlayPos--;
1300 } else {
1301 mPlayPos = mPlayListLen - 1;
1302 }
1303 }
1304 saveBookmarkIfNeeded();
1305 stop(false);
Marco Nelissene41bd182012-03-14 08:24:40 -07001306 openCurrentAndNext();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001307 play();
1308 notifyChange(META_CHANGED);
1309 }
1310 }
1311
Marco Nelissenbd0d2f12012-02-27 16:02:15 -08001312 /**
1313 * Get the next position to play. Note that this may actually modify mPlayPos
1314 * if playback is in SHUFFLE_AUTO mode and the shuffle list window needed to
1315 * be adjusted. Either way, the return value is the next value that should be
1316 * assigned to mPlayPos;
1317 */
1318 private int getNextPosition(boolean force) {
Marco Nelissene41bd182012-03-14 08:24:40 -07001319 if (mRepeatMode == REPEAT_CURRENT) {
1320 if (mPlayPos < 0) return 0;
1321 return mPlayPos;
1322 } else if (mShuffleMode == SHUFFLE_NORMAL) {
Marco Nelissenbd0d2f12012-02-27 16:02:15 -08001323 // Pick random next track from the not-yet-played ones
1324 // TODO: make it work right after adding/removing items in the queue.
1325
1326 // Store the current file in the history, but keep the history at a
1327 // reasonable size
1328 if (mPlayPos >= 0) {
1329 mHistory.add(mPlayPos);
1330 }
1331 if (mHistory.size() > MAX_HISTORY_SIZE) {
1332 mHistory.removeElementAt(0);
1333 }
1334
1335 int numTracks = mPlayListLen;
1336 int[] tracks = new int[numTracks];
Jack Heb0fba8b2017-01-26 15:54:38 -08001337 for (int i = 0; i < numTracks; i++) {
Marco Nelissenbd0d2f12012-02-27 16:02:15 -08001338 tracks[i] = i;
1339 }
1340
1341 int numHistory = mHistory.size();
1342 int numUnplayed = numTracks;
Jack Heb0fba8b2017-01-26 15:54:38 -08001343 for (int i = 0; i < numHistory; i++) {
Marco Nelissenbd0d2f12012-02-27 16:02:15 -08001344 int idx = mHistory.get(i).intValue();
1345 if (idx < numTracks && tracks[idx] >= 0) {
1346 numUnplayed--;
1347 tracks[idx] = -1;
1348 }
1349 }
1350
1351 // 'numUnplayed' now indicates how many tracks have not yet
1352 // been played, and 'tracks' contains the indices of those
1353 // tracks.
Jack Heb0fba8b2017-01-26 15:54:38 -08001354 if (numUnplayed <= 0) {
Marco Nelissenbd0d2f12012-02-27 16:02:15 -08001355 // everything's already been played
1356 if (mRepeatMode == REPEAT_ALL || force) {
Jack Heb0fba8b2017-01-26 15:54:38 -08001357 // pick from full set
Marco Nelissenbd0d2f12012-02-27 16:02:15 -08001358 numUnplayed = numTracks;
Jack Heb0fba8b2017-01-26 15:54:38 -08001359 for (int i = 0; i < numTracks; i++) {
Marco Nelissenbd0d2f12012-02-27 16:02:15 -08001360 tracks[i] = i;
1361 }
1362 } else {
1363 // all done
1364 return -1;
1365 }
1366 }
1367 int skip = mRand.nextInt(numUnplayed);
1368 int cnt = -1;
1369 while (true) {
1370 while (tracks[++cnt] < 0)
1371 ;
1372 skip--;
1373 if (skip < 0) {
1374 break;
1375 }
1376 }
1377 return cnt;
1378 } else if (mShuffleMode == SHUFFLE_AUTO) {
1379 doAutoShuffleUpdate();
1380 return mPlayPos + 1;
1381 } else {
1382 if (mPlayPos >= mPlayListLen - 1) {
1383 // we're at the end of the list
1384 if (mRepeatMode == REPEAT_NONE && !force) {
1385 // all done
1386 return -1;
1387 } else if (mRepeatMode == REPEAT_ALL || force) {
1388 return 0;
1389 }
1390 return -1;
1391 } else {
1392 return mPlayPos + 1;
1393 }
1394 }
1395 }
1396
1397 public void gotoNext(boolean force) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001398 synchronized (this) {
Marco Nelissen663fea32009-06-12 13:39:27 -07001399 if (mPlayListLen <= 0) {
Marco Nelissene99341f2009-11-11 11:13:51 -08001400 Log.d(LOGTAG, "No play queue");
Marco Nelissen663fea32009-06-12 13:39:27 -07001401 return;
1402 }
1403
Marco Nelissenbd0d2f12012-02-27 16:02:15 -08001404 int pos = getNextPosition(force);
1405 if (pos < 0) {
1406 gotoIdleState();
1407 if (mIsSupposedToBePlaying) {
1408 mIsSupposedToBePlaying = false;
1409 notifyChange(PLAYSTATE_CHANGED);
Marco Nelissen3f502de2010-09-28 15:07:29 -07001410 }
Marco Nelissenbd0d2f12012-02-27 16:02:15 -08001411 return;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001412 }
Marco Nelissenbd0d2f12012-02-27 16:02:15 -08001413 mPlayPos = pos;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001414 saveBookmarkIfNeeded();
1415 stop(false);
Marco Nelissene41bd182012-03-14 08:24:40 -07001416 mPlayPos = pos;
1417 openCurrentAndNext();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001418 play();
1419 notifyChange(META_CHANGED);
1420 }
1421 }
Jack Heb0fba8b2017-01-26 15:54:38 -08001422
The Android Open Source Project792a2202009-03-03 19:32:30 -08001423 private void gotoIdleState() {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001424 mDelayedStopHandler.removeCallbacksAndMessages(null);
1425 Message msg = mDelayedStopHandler.obtainMessage();
1426 mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
Dianne Hackbornd5fc5b62009-08-18 11:35:30 -07001427 stopForeground(true);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001428 }
Jack Heb0fba8b2017-01-26 15:54:38 -08001429
The Android Open Source Project792a2202009-03-03 19:32:30 -08001430 private void saveBookmarkIfNeeded() {
1431 try {
1432 if (isPodcast()) {
1433 long pos = position();
1434 long bookmark = getBookmark();
1435 long duration = duration();
Jack Heb0fba8b2017-01-26 15:54:38 -08001436 if ((pos < bookmark && (pos + 10000) > bookmark)
1437 || (pos > bookmark && (pos - 10000) < bookmark)) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001438 // The existing bookmark is close to the current
1439 // position, so don't update it.
1440 return;
1441 }
1442 if (pos < 15000 || (pos + 10000) > duration) {
1443 // if we're near the start or end, clear the bookmark
1444 pos = 0;
1445 }
Jack Heb0fba8b2017-01-26 15:54:38 -08001446
The Android Open Source Project792a2202009-03-03 19:32:30 -08001447 // write 'pos' to the bookmark field
1448 ContentValues values = new ContentValues();
1449 values.put(MediaStore.Audio.Media.BOOKMARK, pos);
1450 Uri uri = ContentUris.withAppendedId(
1451 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, mCursor.getLong(IDCOLIDX));
1452 getContentResolver().update(uri, values, null, null);
1453 }
1454 } catch (SQLiteException ex) {
1455 }
1456 }
1457
1458 // Make sure there are at least 5 items after the currently playing item
1459 // and no more than 10 items before.
1460 private void doAutoShuffleUpdate() {
1461 boolean notify = false;
Marco Nelissen3f502de2010-09-28 15:07:29 -07001462
The Android Open Source Project792a2202009-03-03 19:32:30 -08001463 // remove old entries
1464 if (mPlayPos > 10) {
1465 removeTracks(0, mPlayPos - 9);
1466 notify = true;
1467 }
1468 // add new entries if needed
1469 int to_add = 7 - (mPlayListLen - (mPlayPos < 0 ? -1 : mPlayPos));
1470 for (int i = 0; i < to_add; i++) {
1471 // pick something at random from the list
Marco Nelissen3f502de2010-09-28 15:07:29 -07001472
1473 int lookback = mHistory.size();
1474 int idx = -1;
Jack Heb0fba8b2017-01-26 15:54:38 -08001475 while (true) {
Marco Nelissen3f502de2010-09-28 15:07:29 -07001476 idx = mRand.nextInt(mAutoShuffleList.length);
1477 if (!wasRecentlyUsed(idx, lookback)) {
1478 break;
1479 }
1480 lookback /= 2;
1481 }
1482 mHistory.add(idx);
1483 if (mHistory.size() > MAX_HISTORY_SIZE) {
1484 mHistory.remove(0);
1485 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08001486 ensurePlayListCapacity(mPlayListLen + 1);
Marco Nelissen3f502de2010-09-28 15:07:29 -07001487 mPlayList[mPlayListLen++] = mAutoShuffleList[idx];
The Android Open Source Project792a2202009-03-03 19:32:30 -08001488 notify = true;
1489 }
1490 if (notify) {
1491 notifyChange(QUEUE_CHANGED);
1492 }
1493 }
1494
Marco Nelissen3f502de2010-09-28 15:07:29 -07001495 // check that the specified idx is not in the history (but only look at at
1496 // most lookbacksize entries in the history)
1497 private boolean wasRecentlyUsed(int idx, int lookbacksize) {
Marco Nelissen3f502de2010-09-28 15:07:29 -07001498 // early exit to prevent infinite loops in case idx == mPlayPos
1499 if (lookbacksize == 0) {
1500 return false;
1501 }
1502
1503 int histsize = mHistory.size();
1504 if (histsize < lookbacksize) {
1505 Log.d(LOGTAG, "lookback too big");
1506 lookbacksize = histsize;
1507 }
1508 int maxidx = histsize - 1;
1509 for (int i = 0; i < lookbacksize; i++) {
1510 long entry = mHistory.get(maxidx - i);
1511 if (entry == idx) {
1512 return true;
1513 }
1514 }
1515 return false;
1516 }
1517
The Android Open Source Project792a2202009-03-03 19:32:30 -08001518 // A simple variation of Random that makes sure that the
1519 // value it returns is not equal to the value it returned
1520 // previously, unless the interval is 1.
Marco Nelissen756c3f52009-05-14 10:07:23 -07001521 private static class Shuffler {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001522 private int mPrevious;
1523 private Random mRandom = new Random();
1524 public int nextInt(int interval) {
1525 int ret;
1526 do {
1527 ret = mRandom.nextInt(interval);
1528 } while (ret == mPrevious && interval > 1);
1529 mPrevious = ret;
1530 return ret;
1531 }
1532 };
1533
1534 private boolean makeAutoShuffleList() {
1535 ContentResolver res = getContentResolver();
1536 Cursor c = null;
1537 try {
1538 c = res.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
Jack Heb0fba8b2017-01-26 15:54:38 -08001539 new String[] {MediaStore.Audio.Media._ID},
1540 MediaStore.Audio.Media.IS_MUSIC + "=1", null, null);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001541 if (c == null || c.getCount() == 0) {
1542 return false;
1543 }
1544 int len = c.getCount();
Jack Heb0fba8b2017-01-26 15:54:38 -08001545 long[] list = new long[len];
The Android Open Source Project792a2202009-03-03 19:32:30 -08001546 for (int i = 0; i < len; i++) {
1547 c.moveToNext();
Marco Nelissenbd447b62009-06-29 14:52:05 -07001548 list[i] = c.getLong(0);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001549 }
1550 mAutoShuffleList = list;
1551 return true;
1552 } catch (RuntimeException ex) {
1553 } finally {
1554 if (c != null) {
1555 c.close();
1556 }
1557 }
1558 return false;
1559 }
Jack Heb0fba8b2017-01-26 15:54:38 -08001560
The Android Open Source Project792a2202009-03-03 19:32:30 -08001561 /**
1562 * Removes the range of tracks specified from the play list. If a file within the range is
1563 * the file currently being played, playback will move to the next file after the
Jack Heb0fba8b2017-01-26 15:54:38 -08001564 * range.
The Android Open Source Project792a2202009-03-03 19:32:30 -08001565 * @param first The first file to be removed
1566 * @param last The last file to be removed
1567 * @return the number of tracks deleted
1568 */
1569 public int removeTracks(int first, int last) {
1570 int numremoved = removeTracksInternal(first, last);
1571 if (numremoved > 0) {
1572 notifyChange(QUEUE_CHANGED);
1573 }
1574 return numremoved;
1575 }
Jack Heb0fba8b2017-01-26 15:54:38 -08001576
The Android Open Source Project792a2202009-03-03 19:32:30 -08001577 private int removeTracksInternal(int first, int last) {
1578 synchronized (this) {
1579 if (last < first) return 0;
1580 if (first < 0) first = 0;
1581 if (last >= mPlayListLen) last = mPlayListLen - 1;
1582
1583 boolean gotonext = false;
1584 if (first <= mPlayPos && mPlayPos <= last) {
1585 mPlayPos = first;
1586 gotonext = true;
1587 } else if (mPlayPos > last) {
1588 mPlayPos -= (last - first + 1);
1589 }
1590 int num = mPlayListLen - last - 1;
1591 for (int i = 0; i < num; i++) {
1592 mPlayList[first + i] = mPlayList[last + 1 + i];
1593 }
1594 mPlayListLen -= last - first + 1;
Jack Heb0fba8b2017-01-26 15:54:38 -08001595
The Android Open Source Project792a2202009-03-03 19:32:30 -08001596 if (gotonext) {
1597 if (mPlayListLen == 0) {
1598 stop(true);
1599 mPlayPos = -1;
Marco Nelissen3aa9ad02010-09-16 13:23:11 -07001600 if (mCursor != null) {
1601 mCursor.close();
1602 mCursor = null;
1603 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08001604 } else {
1605 if (mPlayPos >= mPlayListLen) {
1606 mPlayPos = 0;
1607 }
1608 boolean wasPlaying = isPlaying();
1609 stop(false);
Marco Nelissene41bd182012-03-14 08:24:40 -07001610 openCurrentAndNext();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001611 if (wasPlaying) {
1612 play();
1613 }
1614 }
Marco Nelissen3aa9ad02010-09-16 13:23:11 -07001615 notifyChange(META_CHANGED);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001616 }
1617 return last - first + 1;
1618 }
1619 }
Jack Heb0fba8b2017-01-26 15:54:38 -08001620
The Android Open Source Project792a2202009-03-03 19:32:30 -08001621 /**
1622 * Removes all instances of the track with the given id
1623 * from the playlist.
1624 * @param id The id to be removed
1625 * @return how many instances of the track were removed
1626 */
Marco Nelissenbd447b62009-06-29 14:52:05 -07001627 public int removeTrack(long id) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001628 int numremoved = 0;
1629 synchronized (this) {
1630 for (int i = 0; i < mPlayListLen; i++) {
1631 if (mPlayList[i] == id) {
1632 numremoved += removeTracksInternal(i, i);
1633 i--;
1634 }
1635 }
1636 }
1637 if (numremoved > 0) {
1638 notifyChange(QUEUE_CHANGED);
1639 }
1640 return numremoved;
1641 }
Jack Heb0fba8b2017-01-26 15:54:38 -08001642
The Android Open Source Project792a2202009-03-03 19:32:30 -08001643 public void setShuffleMode(int shufflemode) {
Jack Heb0fba8b2017-01-26 15:54:38 -08001644 synchronized (this) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001645 if (mShuffleMode == shufflemode && mPlayListLen > 0) {
1646 return;
1647 }
1648 mShuffleMode = shufflemode;
1649 if (mShuffleMode == SHUFFLE_AUTO) {
1650 if (makeAutoShuffleList()) {
1651 mPlayListLen = 0;
1652 doAutoShuffleUpdate();
1653 mPlayPos = 0;
Marco Nelissene41bd182012-03-14 08:24:40 -07001654 openCurrentAndNext();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001655 play();
1656 notifyChange(META_CHANGED);
1657 return;
1658 } else {
1659 // failed to build a list of files to shuffle
1660 mShuffleMode = SHUFFLE_NONE;
1661 }
1662 }
1663 saveQueue(false);
1664 }
1665 }
1666 public int getShuffleMode() {
1667 return mShuffleMode;
1668 }
Jack Heb0fba8b2017-01-26 15:54:38 -08001669
The Android Open Source Project792a2202009-03-03 19:32:30 -08001670 public void setRepeatMode(int repeatmode) {
Jack Heb0fba8b2017-01-26 15:54:38 -08001671 synchronized (this) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001672 mRepeatMode = repeatmode;
Marco Nelissene41bd182012-03-14 08:24:40 -07001673 setNextTrack();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001674 saveQueue(false);
1675 }
1676 }
1677 public int getRepeatMode() {
1678 return mRepeatMode;
1679 }
1680
1681 public int getMediaMountedCount() {
1682 return mMediaMountedCount;
1683 }
1684
1685 /**
1686 * Returns the path of the currently playing file, or null if
1687 * no file is currently playing.
1688 */
1689 public String getPath() {
1690 return mFileToPlay;
1691 }
Jack Heb0fba8b2017-01-26 15:54:38 -08001692
The Android Open Source Project792a2202009-03-03 19:32:30 -08001693 /**
1694 * Returns the rowid of the currently playing file, or -1 if
1695 * no file is currently playing.
1696 */
Marco Nelissenbd447b62009-06-29 14:52:05 -07001697 public long getAudioId() {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001698 synchronized (this) {
1699 if (mPlayPos >= 0 && mPlayer.isInitialized()) {
1700 return mPlayList[mPlayPos];
1701 }
1702 }
1703 return -1;
1704 }
Jack Heb0fba8b2017-01-26 15:54:38 -08001705
The Android Open Source Project792a2202009-03-03 19:32:30 -08001706 /**
Jack Heb0fba8b2017-01-26 15:54:38 -08001707 * Returns the position in the queue
The Android Open Source Project792a2202009-03-03 19:32:30 -08001708 * @return the position in the queue
1709 */
1710 public int getQueuePosition() {
Jack Heb0fba8b2017-01-26 15:54:38 -08001711 synchronized (this) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001712 return mPlayPos;
1713 }
1714 }
Jack Heb0fba8b2017-01-26 15:54:38 -08001715
The Android Open Source Project792a2202009-03-03 19:32:30 -08001716 /**
1717 * Starts playing the track at the given position in the queue.
1718 * @param pos The position in the queue of the track that will be played.
1719 */
1720 public void setQueuePosition(int pos) {
Jack Heb0fba8b2017-01-26 15:54:38 -08001721 synchronized (this) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001722 stop(false);
1723 mPlayPos = pos;
Marco Nelissene41bd182012-03-14 08:24:40 -07001724 openCurrentAndNext();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001725 play();
1726 notifyChange(META_CHANGED);
Marco Nelissenec0c57a2009-12-12 12:27:11 -08001727 if (mShuffleMode == SHUFFLE_AUTO) {
1728 doAutoShuffleUpdate();
1729 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08001730 }
1731 }
1732
1733 public String getArtistName() {
Jack Heb0fba8b2017-01-26 15:54:38 -08001734 synchronized (this) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001735 if (mCursor == null) {
1736 return null;
1737 }
1738 return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST));
1739 }
1740 }
Jack Heb0fba8b2017-01-26 15:54:38 -08001741
Marco Nelissenbd447b62009-06-29 14:52:05 -07001742 public long getArtistId() {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001743 synchronized (this) {
1744 if (mCursor == null) {
1745 return -1;
1746 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07001747 return mCursor.getLong(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST_ID));
The Android Open Source Project792a2202009-03-03 19:32:30 -08001748 }
1749 }
1750
1751 public String getAlbumName() {
1752 synchronized (this) {
1753 if (mCursor == null) {
1754 return null;
1755 }
1756 return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM));
1757 }
1758 }
1759
Marco Nelissenbd447b62009-06-29 14:52:05 -07001760 public long getAlbumId() {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001761 synchronized (this) {
1762 if (mCursor == null) {
1763 return -1;
1764 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07001765 return mCursor.getLong(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM_ID));
The Android Open Source Project792a2202009-03-03 19:32:30 -08001766 }
1767 }
1768
1769 public String getTrackName() {
1770 synchronized (this) {
1771 if (mCursor == null) {
1772 return null;
1773 }
1774 return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE));
1775 }
1776 }
1777
1778 private boolean isPodcast() {
1779 synchronized (this) {
1780 if (mCursor == null) {
1781 return false;
1782 }
1783 return (mCursor.getInt(PODCASTCOLIDX) > 0);
1784 }
1785 }
Jack Heb0fba8b2017-01-26 15:54:38 -08001786
The Android Open Source Project792a2202009-03-03 19:32:30 -08001787 private long getBookmark() {
1788 synchronized (this) {
1789 if (mCursor == null) {
1790 return 0;
1791 }
1792 return mCursor.getLong(BOOKMARKCOLIDX);
1793 }
1794 }
Jack Heb0fba8b2017-01-26 15:54:38 -08001795
The Android Open Source Project792a2202009-03-03 19:32:30 -08001796 /**
1797 * Returns the duration of the file in milliseconds.
1798 * Currently this method returns -1 for the duration of MIDI files.
1799 */
1800 public long duration() {
1801 if (mPlayer.isInitialized()) {
1802 return mPlayer.duration();
1803 }
1804 return -1;
1805 }
1806
1807 /**
1808 * Returns the current playback position in milliseconds
1809 */
1810 public long position() {
1811 if (mPlayer.isInitialized()) {
1812 return mPlayer.position();
1813 }
1814 return -1;
1815 }
1816
1817 /**
1818 * Seeks to the position specified.
1819 *
1820 * @param pos The position to seek to, in milliseconds
1821 */
1822 public long seek(long pos) {
1823 if (mPlayer.isInitialized()) {
1824 if (pos < 0) pos = 0;
1825 if (pos > mPlayer.duration()) pos = mPlayer.duration();
1826 return mPlayer.seek(pos);
1827 }
1828 return -1;
1829 }
1830
1831 /**
Eric Laurent1cc72a12010-06-28 11:27:01 -07001832 * Sets the audio session ID.
1833 *
1834 * @param sessionId: the audio session ID.
1835 */
1836 public void setAudioSessionId(int sessionId) {
1837 synchronized (this) {
1838 mPlayer.setAudioSessionId(sessionId);
1839 }
1840 }
1841
1842 /**
1843 * Returns the audio session ID.
1844 */
1845 public int getAudioSessionId() {
1846 synchronized (this) {
1847 return mPlayer.getAudioSessionId();
1848 }
1849 }
1850
1851 /**
The Android Open Source Project792a2202009-03-03 19:32:30 -08001852 * Provides a unified interface for dealing with midi files and
1853 * other media files.
1854 */
1855 private class MultiPlayer {
Marco Nelissen30867022012-03-14 16:04:34 -07001856 private CompatMediaPlayer mCurrentMediaPlayer = new CompatMediaPlayer();
1857 private CompatMediaPlayer mNextMediaPlayer;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001858 private Handler mHandler;
1859 private boolean mIsInitialized = false;
1860
1861 public MultiPlayer() {
Jack Heb0fba8b2017-01-26 15:54:38 -08001862 mCurrentMediaPlayer.setWakeMode(
1863 MediaPlaybackService.this, PowerManager.PARTIAL_WAKE_LOCK);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001864 }
1865
The Android Open Source Project792a2202009-03-03 19:32:30 -08001866 public void setDataSource(String path) {
Marco Nelissen90d1f622012-04-05 12:27:56 -07001867 mIsInitialized = setDataSourceImpl(mCurrentMediaPlayer, path);
1868 if (mIsInitialized) {
1869 setNextDataSource(null);
1870 }
Marco Nelissene41bd182012-03-14 08:24:40 -07001871 }
1872
Marco Nelissen90d1f622012-04-05 12:27:56 -07001873 private boolean setDataSourceImpl(MediaPlayer player, String path) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001874 try {
Marco Nelissene41bd182012-03-14 08:24:40 -07001875 player.reset();
1876 player.setOnPreparedListener(null);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001877 if (path.startsWith("content://")) {
Marco Nelissene41bd182012-03-14 08:24:40 -07001878 player.setDataSource(MediaPlaybackService.this, Uri.parse(path));
The Android Open Source Project792a2202009-03-03 19:32:30 -08001879 } else {
Marco Nelissene41bd182012-03-14 08:24:40 -07001880 player.setDataSource(path);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001881 }
Marco Nelissene41bd182012-03-14 08:24:40 -07001882 player.setAudioStreamType(AudioManager.STREAM_MUSIC);
1883 player.prepare();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001884 } catch (IOException ex) {
1885 // TODO: notify the user why the file couldn't be opened
Marco Nelissen90d1f622012-04-05 12:27:56 -07001886 return false;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001887 } catch (IllegalArgumentException 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 }
Marco Nelissene41bd182012-03-14 08:24:40 -07001891 player.setOnCompletionListener(listener);
1892 player.setOnErrorListener(errorListener);
Marco Nelissenf2ef3b52010-09-21 15:47:27 -07001893 Intent i = new Intent(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION);
1894 i.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, getAudioSessionId());
1895 i.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, getPackageName());
1896 sendBroadcast(i);
Marco Nelissen90d1f622012-04-05 12:27:56 -07001897 return true;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001898 }
Marco Nelissene41bd182012-03-14 08:24:40 -07001899
1900 public void setNextDataSource(String path) {
1901 mCurrentMediaPlayer.setNextMediaPlayer(null);
1902 if (mNextMediaPlayer != null) {
1903 mNextMediaPlayer.release();
1904 mNextMediaPlayer = null;
1905 }
1906 if (path == null) {
1907 return;
1908 }
Marco Nelissen30867022012-03-14 16:04:34 -07001909 mNextMediaPlayer = new CompatMediaPlayer();
Marco Nelissene41bd182012-03-14 08:24:40 -07001910 mNextMediaPlayer.setWakeMode(MediaPlaybackService.this, PowerManager.PARTIAL_WAKE_LOCK);
1911 mNextMediaPlayer.setAudioSessionId(getAudioSessionId());
Marco Nelissen90d1f622012-04-05 12:27:56 -07001912 if (setDataSourceImpl(mNextMediaPlayer, path)) {
1913 mCurrentMediaPlayer.setNextMediaPlayer(mNextMediaPlayer);
1914 } else {
1915 // failed to open next, we'll transition the old fashioned way,
1916 // which will skip over the faulty file
1917 mNextMediaPlayer.release();
1918 mNextMediaPlayer = null;
1919 }
Marco Nelissene41bd182012-03-14 08:24:40 -07001920 }
Jack Heb0fba8b2017-01-26 15:54:38 -08001921
The Android Open Source Project792a2202009-03-03 19:32:30 -08001922 public boolean isInitialized() {
1923 return mIsInitialized;
1924 }
1925
1926 public void start() {
Marco Nelissen39888902010-03-02 10:27:05 -08001927 MusicUtils.debugLog(new Exception("MultiPlayer.start called"));
Marco Nelissene41bd182012-03-14 08:24:40 -07001928 mCurrentMediaPlayer.start();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001929 }
1930
1931 public void stop() {
Marco Nelissene41bd182012-03-14 08:24:40 -07001932 mCurrentMediaPlayer.reset();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001933 mIsInitialized = false;
1934 }
1935
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001936 /**
1937 * You CANNOT use this player anymore after calling release()
1938 */
1939 public void release() {
1940 stop();
Marco Nelissene41bd182012-03-14 08:24:40 -07001941 mCurrentMediaPlayer.release();
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001942 }
Jack Heb0fba8b2017-01-26 15:54:38 -08001943
The Android Open Source Project792a2202009-03-03 19:32:30 -08001944 public void pause() {
Marco Nelissene41bd182012-03-14 08:24:40 -07001945 mCurrentMediaPlayer.pause();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001946 }
Jack Heb0fba8b2017-01-26 15:54:38 -08001947
The Android Open Source Project792a2202009-03-03 19:32:30 -08001948 public void setHandler(Handler handler) {
1949 mHandler = handler;
1950 }
1951
1952 MediaPlayer.OnCompletionListener listener = new MediaPlayer.OnCompletionListener() {
1953 public void onCompletion(MediaPlayer mp) {
Marco Nelissene41bd182012-03-14 08:24:40 -07001954 if (mp == mCurrentMediaPlayer && mNextMediaPlayer != null) {
1955 mCurrentMediaPlayer.release();
1956 mCurrentMediaPlayer = mNextMediaPlayer;
1957 mNextMediaPlayer = null;
1958 mHandler.sendEmptyMessage(TRACK_WENT_TO_NEXT);
1959 } else {
1960 // Acquire a temporary wakelock, since when we return from
1961 // this callback the MediaPlayer will release its wakelock
1962 // and allow the device to go to sleep.
1963 // This temporary wakelock is released when the RELEASE_WAKELOCK
1964 // message is processed, but just in case, put a timeout on it.
1965 mWakeLock.acquire(30000);
1966 mHandler.sendEmptyMessage(TRACK_ENDED);
1967 mHandler.sendEmptyMessage(RELEASE_WAKELOCK);
1968 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08001969 }
1970 };
1971
The Android Open Source Project792a2202009-03-03 19:32:30 -08001972 MediaPlayer.OnErrorListener errorListener = new MediaPlayer.OnErrorListener() {
1973 public boolean onError(MediaPlayer mp, int what, int extra) {
1974 switch (what) {
Jack Heb0fba8b2017-01-26 15:54:38 -08001975 case MediaPlayer.MEDIA_ERROR_SERVER_DIED:
1976 mIsInitialized = false;
1977 mCurrentMediaPlayer.release();
1978 // Creating a new MediaPlayer and settings its wakemode does not
1979 // require the media service, so it's OK to do this now, while the
1980 // service is still being restarted
1981 mCurrentMediaPlayer = new CompatMediaPlayer();
1982 mCurrentMediaPlayer.setWakeMode(
1983 MediaPlaybackService.this, PowerManager.PARTIAL_WAKE_LOCK);
1984 mHandler.sendMessageDelayed(mHandler.obtainMessage(SERVER_DIED), 2000);
1985 return true;
1986 default:
1987 Log.d("MultiPlayer", "Error: " + what + "," + extra);
1988 break;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001989 }
1990 return false;
Jack Heb0fba8b2017-01-26 15:54:38 -08001991 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08001992 };
1993
1994 public long duration() {
Marco Nelissene41bd182012-03-14 08:24:40 -07001995 return mCurrentMediaPlayer.getDuration();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001996 }
1997
1998 public long position() {
Marco Nelissene41bd182012-03-14 08:24:40 -07001999 return mCurrentMediaPlayer.getCurrentPosition();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002000 }
2001
2002 public long seek(long whereto) {
Marco Nelissene41bd182012-03-14 08:24:40 -07002003 mCurrentMediaPlayer.seekTo((int) whereto);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002004 return whereto;
2005 }
2006
2007 public void setVolume(float vol) {
Marco Nelissene41bd182012-03-14 08:24:40 -07002008 mCurrentMediaPlayer.setVolume(vol, vol);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002009 }
Eric Laurent1cc72a12010-06-28 11:27:01 -07002010
2011 public void setAudioSessionId(int sessionId) {
Marco Nelissene41bd182012-03-14 08:24:40 -07002012 mCurrentMediaPlayer.setAudioSessionId(sessionId);
Eric Laurent1cc72a12010-06-28 11:27:01 -07002013 }
2014
2015 public int getAudioSessionId() {
Marco Nelissene41bd182012-03-14 08:24:40 -07002016 return mCurrentMediaPlayer.getAudioSessionId();
Eric Laurent1cc72a12010-06-28 11:27:01 -07002017 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08002018 }
2019
Marco Nelissen30867022012-03-14 16:04:34 -07002020 static class CompatMediaPlayer extends MediaPlayer implements OnCompletionListener {
Marco Nelissen30867022012-03-14 16:04:34 -07002021 private boolean mCompatMode = true;
2022 private MediaPlayer mNextPlayer;
2023 private OnCompletionListener mCompletion;
2024
2025 public CompatMediaPlayer() {
2026 try {
2027 MediaPlayer.class.getMethod("setNextMediaPlayer", MediaPlayer.class);
2028 mCompatMode = false;
2029 } catch (NoSuchMethodException e) {
2030 mCompatMode = true;
2031 super.setOnCompletionListener(this);
2032 }
2033 }
2034
2035 public void setNextMediaPlayer(MediaPlayer next) {
2036 if (mCompatMode) {
2037 mNextPlayer = next;
2038 } else {
2039 super.setNextMediaPlayer(next);
2040 }
2041 }
2042
2043 @Override
2044 public void setOnCompletionListener(OnCompletionListener listener) {
2045 if (mCompatMode) {
2046 mCompletion = listener;
2047 } else {
2048 super.setOnCompletionListener(listener);
2049 }
2050 }
2051
2052 @Override
2053 public void onCompletion(MediaPlayer mp) {
2054 if (mNextPlayer != null) {
2055 // as it turns out, starting a new MediaPlayer on the completion
2056 // of a previous player ends up slightly overlapping the two
2057 // playbacks, so slightly delaying the start of the next player
2058 // gives a better user experience
2059 SystemClock.sleep(50);
2060 mNextPlayer.start();
2061 }
2062 mCompletion.onCompletion(this);
2063 }
2064 }
2065
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002066 /*
2067 * By making this a static class with a WeakReference to the Service, we
2068 * ensure that the Service can be GCd even when the system process still
2069 * has a remote reference to the stub.
2070 */
2071 static class ServiceStub extends IMediaPlaybackService.Stub {
2072 WeakReference<MediaPlaybackService> mService;
Jack Heb0fba8b2017-01-26 15:54:38 -08002073
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002074 ServiceStub(MediaPlaybackService service) {
2075 mService = new WeakReference<MediaPlaybackService>(service);
2076 }
2077
Jack Heb0fba8b2017-01-26 15:54:38 -08002078 public void openFile(String path) {
Marco Nelissen8d08ec22010-05-10 14:05:24 -07002079 mService.get().open(path);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002080 }
Jack Heb0fba8b2017-01-26 15:54:38 -08002081 public void open(long[] list, int position) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002082 mService.get().open(list, position);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002083 }
2084 public int getQueuePosition() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002085 return mService.get().getQueuePosition();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002086 }
2087 public void setQueuePosition(int index) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002088 mService.get().setQueuePosition(index);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002089 }
2090 public boolean isPlaying() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002091 return mService.get().isPlaying();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002092 }
2093 public void stop() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002094 mService.get().stop();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002095 }
2096 public void pause() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002097 mService.get().pause();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002098 }
2099 public void play() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002100 mService.get().play();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002101 }
2102 public void prev() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002103 mService.get().prev();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002104 }
2105 public void next() {
Marco Nelissenbd0d2f12012-02-27 16:02:15 -08002106 mService.get().gotoNext(true);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002107 }
2108 public String getTrackName() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002109 return mService.get().getTrackName();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002110 }
2111 public String getAlbumName() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002112 return mService.get().getAlbumName();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002113 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07002114 public long getAlbumId() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002115 return mService.get().getAlbumId();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002116 }
2117 public String getArtistName() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002118 return mService.get().getArtistName();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002119 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07002120 public long getArtistId() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002121 return mService.get().getArtistId();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002122 }
Jack Heb0fba8b2017-01-26 15:54:38 -08002123 public void enqueue(long[] list, int action) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002124 mService.get().enqueue(list, action);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002125 }
Jack Heb0fba8b2017-01-26 15:54:38 -08002126 public long[] getQueue() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002127 return mService.get().getQueue();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002128 }
2129 public void moveQueueItem(int from, int to) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002130 mService.get().moveQueueItem(from, to);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002131 }
2132 public String getPath() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002133 return mService.get().getPath();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002134 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07002135 public long getAudioId() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002136 return mService.get().getAudioId();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002137 }
2138 public long position() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002139 return mService.get().position();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002140 }
2141 public long duration() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002142 return mService.get().duration();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002143 }
2144 public long seek(long pos) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002145 return mService.get().seek(pos);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002146 }
2147 public void setShuffleMode(int shufflemode) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002148 mService.get().setShuffleMode(shufflemode);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002149 }
2150 public int getShuffleMode() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002151 return mService.get().getShuffleMode();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002152 }
2153 public int removeTracks(int first, int last) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002154 return mService.get().removeTracks(first, last);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002155 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07002156 public int removeTrack(long id) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002157 return mService.get().removeTrack(id);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002158 }
2159 public void setRepeatMode(int repeatmode) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002160 mService.get().setRepeatMode(repeatmode);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002161 }
2162 public int getRepeatMode() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002163 return mService.get().getRepeatMode();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002164 }
2165 public int getMediaMountedCount() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002166 return mService.get().getMediaMountedCount();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002167 }
Eric Laurent1cc72a12010-06-28 11:27:01 -07002168 public int getAudioSessionId() {
2169 return mService.get().getAudioSessionId();
2170 }
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002171 }
Marco Nelissen39888902010-03-02 10:27:05 -08002172
2173 @Override
2174 protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
Marco Nelissenbf555ce2010-03-02 16:55:25 -08002175 writer.println("" + mPlayListLen + " items in queue, currently at index " + mPlayPos);
Marco Nelissen39888902010-03-02 10:27:05 -08002176 writer.println("Currently loaded:");
2177 writer.println(getArtistName());
2178 writer.println(getAlbumName());
2179 writer.println(getTrackName());
2180 writer.println(getPath());
2181 writer.println("playing: " + mIsSupposedToBePlaying);
Marco Nelissene41bd182012-03-14 08:24:40 -07002182 writer.println("actual: " + mPlayer.mCurrentMediaPlayer.isPlaying());
Marco Nelissenbf555ce2010-03-02 16:55:25 -08002183 writer.println("shuffle mode: " + mShuffleMode);
Marco Nelissen39888902010-03-02 10:27:05 -08002184 MusicUtils.debugDump(writer);
2185 }
2186
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002187 private final IBinder mBinder = new ServiceStub(this);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002188}