blob: 603d1a37f6699e4aeb5fc14013a4ea6b00f14a7d [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;
Ayan Ghoshe6f136d2014-07-23 21:03:40 +053050import android.os.SystemProperties;
The Android Open Source Project792a2202009-03-03 19:32:30 -080051import android.os.PowerManager.WakeLock;
52import android.provider.MediaStore;
53import android.util.Log;
54import android.widget.RemoteViews;
55import android.widget.Toast;
The Android Open Source Project792a2202009-03-03 19:32:30 -080056
Marco Nelissen39888902010-03-02 10:27:05 -080057import java.io.FileDescriptor;
The Android Open Source Project792a2202009-03-03 19:32:30 -080058import java.io.IOException;
Marco Nelissen39888902010-03-02 10:27:05 -080059import java.io.PrintWriter;
Marco Nelissen2b0b9132009-05-21 16:26:47 -070060import java.lang.ref.WeakReference;
The Android Open Source Project792a2202009-03-03 19:32:30 -080061import java.util.Random;
62import java.util.Vector;
Ayan Ghoshe6f136d2014-07-23 21:03:40 +053063import java.util.HashMap;
The Android Open Source Project792a2202009-03-03 19:32:30 -080064
65/**
66 * Provides "background" audio playback capabilities, allowing the
67 * user to switch between activities without stopping playback.
68 */
69public class MediaPlaybackService extends Service {
70 /** used to specify whether enqueue() should start playing
71 * the new list of files right away, next or once all the currently
72 * queued files have been played
73 */
74 public static final int NOW = 1;
75 public static final int NEXT = 2;
76 public static final int LAST = 3;
77 public static final int PLAYBACKSERVICE_STATUS = 1;
78
79 public static final int SHUFFLE_NONE = 0;
80 public static final int SHUFFLE_NORMAL = 1;
81 public static final int SHUFFLE_AUTO = 2;
82
83 public static final int REPEAT_NONE = 0;
84 public static final int REPEAT_CURRENT = 1;
85 public static final int REPEAT_ALL = 2;
Ayan Ghoshe6f136d2014-07-23 21:03:40 +053086 private HashMap<Byte, Boolean> mAttributePairs = new HashMap<Byte, Boolean>();
The Android Open Source Project792a2202009-03-03 19:32:30 -080087
88 public static final String PLAYSTATE_CHANGED = "com.android.music.playstatechanged";
89 public static final String META_CHANGED = "com.android.music.metachanged";
Ayan Ghoshe6f136d2014-07-23 21:03:40 +053090 public static final String SHUFFLE_CHANGED = "org.codeaurora.music.shuffle";
91 public static final String REPEAT_CHANGED = "org.codeaurora.music.repeat";
The Android Open Source Project792a2202009-03-03 19:32:30 -080092 public static final String QUEUE_CHANGED = "com.android.music.queuechanged";
The Android Open Source Project792a2202009-03-03 19:32:30 -080093
94 public static final String SERVICECMD = "com.android.music.musicservicecommand";
95 public static final String CMDNAME = "command";
96 public static final String CMDTOGGLEPAUSE = "togglepause";
97 public static final String CMDSTOP = "stop";
98 public static final String CMDPAUSE = "pause";
Marco Nelissenfc1c0dc2010-10-25 11:08:36 -070099 public static final String CMDPLAY = "play";
The Android Open Source Project792a2202009-03-03 19:32:30 -0800100 public static final String CMDPREVIOUS = "previous";
101 public static final String CMDNEXT = "next";
Ayan Ghoshe6f136d2014-07-23 21:03:40 +0530102 public static final String CMDGET = "get";
103 public static final String CMDSET = "set";
The Android Open Source Project792a2202009-03-03 19:32:30 -0800104
105 public static final String TOGGLEPAUSE_ACTION = "com.android.music.musicservicecommand.togglepause";
106 public static final String PAUSE_ACTION = "com.android.music.musicservicecommand.pause";
107 public static final String PREVIOUS_ACTION = "com.android.music.musicservicecommand.previous";
108 public static final String NEXT_ACTION = "com.android.music.musicservicecommand.next";
Ayan Ghoshe6f136d2014-07-23 21:03:40 +0530109 private static final String PLAYSTATUS_REQUEST = "org.codeaurora.android.music.playstatusrequest";
110 private static final String PLAYSTATUS_RESPONSE = "org.codeaurora.music.playstatusresponse";
111 private static final String PLAYERSETTINGS_REQUEST = "org.codeaurora.music.playersettingsrequest";
112 private static final String PLAYERSETTINGS_RESPONSE = "org.codeaurora.music.playersettingsresponse";
113 private static final String SET_ADDRESSED_PLAYER = "org.codeaurora.music.setaddressedplayer";
114 private static final String EXTRA_SHUFFLE_VAL = "shuffle";
115 private static final String EXTRA_REPEAT_VAL = "repeat";
The Android Open Source Project792a2202009-03-03 19:32:30 -0800116
The Android Open Source Project792a2202009-03-03 19:32:30 -0800117 private static final int TRACK_ENDED = 1;
118 private static final int RELEASE_WAKELOCK = 2;
119 private static final int SERVER_DIED = 3;
Marco Nelissen7181da82010-12-01 16:39:04 -0800120 private static final int FOCUSCHANGE = 4;
Marco Nelissen2da473d2010-09-29 16:11:12 -0700121 private static final int FADEDOWN = 5;
122 private static final int FADEUP = 6;
Marco Nelissene41bd182012-03-14 08:24:40 -0700123 private static final int TRACK_WENT_TO_NEXT = 7;
Ayan Ghoshe6f136d2014-07-23 21:03:40 +0530124 private static final int ERROR = 8 ;
Marco Nelissen3ec2ad92009-08-17 08:52:29 -0700125 private static final int MAX_HISTORY_SIZE = 100;
Ayan Ghoshe6f136d2014-07-23 21:03:40 +0530126 private static final int DEFAULT_REPEAT_VAL = 0;
127 private static final int DEFAULT_SHUFFLE_VAL = 0;
128 private static final int SET_BROWSED_PLAYER = 1001;
129 private static final int SET_PLAY_ITEM = 1002;
130 private static final int GET_NOW_PLAYING_ENTRIES = 1003;
131
132 private static final int SCOPE_FILE_SYSTEM = 0x01;
133 private static final int SCOPE_NOW_PLAYING = 0x03;
134 private static final int INVALID_SONG_UID = 0xffffffff;
135
The Android Open Source Project792a2202009-03-03 19:32:30 -0800136 private MultiPlayer mPlayer;
137 private String mFileToPlay;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800138 private int mShuffleMode = SHUFFLE_NONE;
139 private int mRepeatMode = REPEAT_NONE;
140 private int mMediaMountedCount = 0;
Marco Nelissenbd447b62009-06-29 14:52:05 -0700141 private long [] mAutoShuffleList = null;
Marco Nelissenbd447b62009-06-29 14:52:05 -0700142 private long [] mPlayList = null;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800143 private int mPlayListLen = 0;
144 private Vector<Integer> mHistory = new Vector<Integer>(MAX_HISTORY_SIZE);
145 private Cursor mCursor;
146 private int mPlayPos = -1;
Marco Nelissene41bd182012-03-14 08:24:40 -0700147 private int mNextPlayPos = -1;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800148 private static final String LOGTAG = "MediaPlaybackService";
149 private final Shuffler mRand = new Shuffler();
150 private int mOpenFailedCounter = 0;
151 String[] mCursorCols = new String[] {
152 "audio._id AS _id", // index must match IDCOLIDX below
153 MediaStore.Audio.Media.ARTIST,
154 MediaStore.Audio.Media.ALBUM,
155 MediaStore.Audio.Media.TITLE,
156 MediaStore.Audio.Media.DATA,
157 MediaStore.Audio.Media.MIME_TYPE,
158 MediaStore.Audio.Media.ALBUM_ID,
159 MediaStore.Audio.Media.ARTIST_ID,
160 MediaStore.Audio.Media.IS_PODCAST, // index must match PODCASTCOLIDX below
161 MediaStore.Audio.Media.BOOKMARK // index must match BOOKMARKCOLIDX below
162 };
163 private final static int IDCOLIDX = 0;
164 private final static int PODCASTCOLIDX = 8;
165 private final static int BOOKMARKCOLIDX = 9;
166 private BroadcastReceiver mUnmountReceiver = null;
Ayan Ghoshe6f136d2014-07-23 21:03:40 +0530167 private BroadcastReceiver mA2dpReceiver = null;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800168 private WakeLock mWakeLock;
169 private int mServiceStartId = -1;
170 private boolean mServiceInUse = false;
Marco Nelissenc1333372009-05-06 12:54:47 -0700171 private boolean mIsSupposedToBePlaying = false;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800172 private boolean mQuietMode = false;
Jean-Michel Trivi085cbfd2010-03-08 17:06:18 -0800173 private AudioManager mAudioManager;
Marco Nelissen8d08ec22010-05-10 14:05:24 -0700174 private boolean mQueueIsSaveable = true;
Jean-Michel Trivi085cbfd2010-03-08 17:06:18 -0800175 // used to track what type of audio focus loss caused the playback to pause
176 private boolean mPausedByTransientLossOfFocus = false;
Ayan Ghoshe6f136d2014-07-23 21:03:40 +0530177 private SetBrowsedPlayerMonitor mSetBrowsedPlayerMonitor;
178 private SetPlayItemMonitor mSetPlayItemMonitor;
179 private GetNowPlayingEntriesMonitor mGetNowPlayingEntriesMonitor;
180 public static final byte ATTRIBUTE_ALL = -1;
181 public static final byte ERROR_NOTSUPPORTED = -1;
182 public static final byte ATTRIBUTE_EQUALIZER = 1;
183 public static final byte ATTRIBUTE_REPEATMODE = 2;
184 public static final byte ATTRIBUTE_SHUFFLEMODE = 3;
185 public static final byte ATTRIBUTE_SCANMODE = 4;
186
187 private byte [] supportedAttributes = new byte[] {
188 ATTRIBUTE_REPEATMODE,
189 ATTRIBUTE_SHUFFLEMODE
190 };
191
192 public static final byte VALUE_REPEATMODE_OFF = 1;
193 public static final byte VALUE_REPEATMODE_SINGLE = 2;
194 public static final byte VALUE_REPEATMODE_ALL = 3;
195 public static final byte VALUE_REPEATMODE_GROUP = 4;
196
197 private byte [] supportedRepeatValues = new byte [] {
198 VALUE_REPEATMODE_OFF,
199 VALUE_REPEATMODE_SINGLE,
200 VALUE_REPEATMODE_ALL
201 };
202
203 public static final byte VALUE_SHUFFLEMODE_OFF = 1;
204 public static final byte VALUE_SHUFFLEMODE_ALL = 2;
205 public static final byte VALUE_SHUFFLEMODE_GROUP = 3;
206
207 private byte [] supportedShuffleValues = new byte [] {
208 VALUE_SHUFFLEMODE_OFF,
209 VALUE_SHUFFLEMODE_ALL
210 };
211
212 String [] AttrStr = new String[] {
213 "",
214 "Equalizer",
215 "Repeat Mode",
216 "Shuffle Mode",
217 "Scan Mode"
218 };
219
220 private byte [] unsupportedList = new byte [] {
221 0
222 };
223 private static final String EXTRA_GET_COMMAND = "commandExtra";
224 private static final String EXTRA_GET_RESPONSE = "Response";
225 private static final int GET_ATTRIBUTE_IDS = 0;
226 private static final int GET_VALUE_IDS = 1;
227 private static final int GET_ATTRIBUTE_TEXT = 2;
228 private static final int GET_VALUE_TEXT = 3;
229 private static final int GET_ATTRIBUTE_VALUES = 4;
230 private static final int NOTIFY_ATTRIBUTE_VALUES = 5;
231 private static final int SET_ATTRIBUTE_VALUES = 6;
232 private static final int GET_INVALID = 0xff;
233 private static final byte GET_ATTR_INVALID = 0x7f;
234
235 private static final String EXTRA_ATTRIBUTE_ID = "Attribute";
236 private static final String EXTRA_VALUE_STRING_ARRAY = "ValueStrings";
237 private static final String EXTRA_ATTRIB_VALUE_PAIRS = "AttribValuePairs";
238 private static final String EXTRA_ATTRIBUTE_STRING_ARRAY = "AttributeStrings";
239 private static final String EXTRA_VALUE_ID_ARRAY = "Values";
240 private static final String EXTRA_ATTIBUTE_ID_ARRAY = "Attributes";
241
Jean-Michel Trivi085cbfd2010-03-08 17:06:18 -0800242
The Android Open Source Project792a2202009-03-03 19:32:30 -0800243 private SharedPreferences mPreferences;
244 // We use this to distinguish between different cards when saving/restoring playlists.
245 // This will have to change if we want to support multiple simultaneous cards.
246 private int mCardId;
247
The Android Open Source Project490384b2009-03-11 12:11:59 -0700248 private MediaAppWidgetProvider mAppWidgetProvider = MediaAppWidgetProvider.getInstance();
The Android Open Source Project792a2202009-03-03 19:32:30 -0800249
250 // interval after which we stop the service when idle
Jean-Michel Triviaa331872010-04-20 17:00:26 -0700251 private static final int IDLE_DELAY = 60000;
Marco Nelissen8717d342011-09-08 09:32:51 -0700252
253 private RemoteControlClient mRemoteControlClient;
254
The Android Open Source Project792a2202009-03-03 19:32:30 -0800255 private Handler mMediaplayerHandler = new Handler() {
256 float mCurrentVolume = 1.0f;
257 @Override
258 public void handleMessage(Message msg) {
Marco Nelissen39888902010-03-02 10:27:05 -0800259 MusicUtils.debugLog("mMediaplayerHandler.handleMessage " + msg.what);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800260 switch (msg.what) {
Marco Nelissen2da473d2010-09-29 16:11:12 -0700261 case FADEDOWN:
262 mCurrentVolume -= .05f;
263 if (mCurrentVolume > .2f) {
264 mMediaplayerHandler.sendEmptyMessageDelayed(FADEDOWN, 10);
265 } else {
266 mCurrentVolume = .2f;
267 }
268 mPlayer.setVolume(mCurrentVolume);
269 break;
270 case FADEUP:
271 mCurrentVolume += .01f;
272 if (mCurrentVolume < 1.0f) {
273 mMediaplayerHandler.sendEmptyMessageDelayed(FADEUP, 10);
274 } else {
275 mCurrentVolume = 1.0f;
276 }
277 mPlayer.setVolume(mCurrentVolume);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800278 break;
279 case SERVER_DIED:
Marco Nelissenc1333372009-05-06 12:54:47 -0700280 if (mIsSupposedToBePlaying) {
Marco Nelissenbd0d2f12012-02-27 16:02:15 -0800281 gotoNext(true);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800282 } else {
283 // the server died when we were idle, so just
284 // reopen the same song (it will start again
285 // from the beginning though when the user
286 // restarts)
Marco Nelissene41bd182012-03-14 08:24:40 -0700287 openCurrentAndNext();
The Android Open Source Project792a2202009-03-03 19:32:30 -0800288 }
289 break;
Marco Nelissene41bd182012-03-14 08:24:40 -0700290 case TRACK_WENT_TO_NEXT:
291 mPlayPos = mNextPlayPos;
292 if (mCursor != null) {
293 mCursor.close();
294 mCursor = null;
295 }
kenthinea507512012-10-16 03:34:11 +0800296 if (mPlayPos >= 0 && mPlayPos < mPlayList.length) {
297 mCursor = getCursorForId(mPlayList[mPlayPos]);
298 }
Marco Nelissene41bd182012-03-14 08:24:40 -0700299 notifyChange(META_CHANGED);
300 updateNotification();
301 setNextTrack();
302 break;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800303 case TRACK_ENDED:
304 if (mRepeatMode == REPEAT_CURRENT) {
305 seek(0);
306 play();
The Android Open Source Project792a2202009-03-03 19:32:30 -0800307 } else {
Marco Nelissenbd0d2f12012-02-27 16:02:15 -0800308 gotoNext(false);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800309 }
310 break;
311 case RELEASE_WAKELOCK:
312 mWakeLock.release();
313 break;
Marco Nelissen7181da82010-12-01 16:39:04 -0800314
315 case FOCUSCHANGE:
316 // This code is here so we can better synchronize it with the code that
317 // handles fade-in
318 switch (msg.arg1) {
319 case AudioManager.AUDIOFOCUS_LOSS:
320 Log.v(LOGTAG, "AudioFocus: received AUDIOFOCUS_LOSS");
321 if(isPlaying()) {
322 mPausedByTransientLossOfFocus = false;
323 }
324 pause();
325 break;
Marco Nelissen7181da82010-12-01 16:39:04 -0800326 case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
Marco Nelissen7bae28f2010-12-02 10:04:54 -0800327 mMediaplayerHandler.removeMessages(FADEUP);
328 mMediaplayerHandler.sendEmptyMessage(FADEDOWN);
329 break;
330 case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
Marco Nelissen7181da82010-12-01 16:39:04 -0800331 Log.v(LOGTAG, "AudioFocus: received AUDIOFOCUS_LOSS_TRANSIENT");
332 if(isPlaying()) {
333 mPausedByTransientLossOfFocus = true;
334 }
335 pause();
336 break;
337 case AudioManager.AUDIOFOCUS_GAIN:
338 Log.v(LOGTAG, "AudioFocus: received AUDIOFOCUS_GAIN");
339 if(!isPlaying() && mPausedByTransientLossOfFocus) {
340 mPausedByTransientLossOfFocus = false;
341 mCurrentVolume = 0f;
342 mPlayer.setVolume(mCurrentVolume);
343 play(); // also queues a fade-in
344 } else {
Marco Nelissen7bae28f2010-12-02 10:04:54 -0800345 mMediaplayerHandler.removeMessages(FADEDOWN);
Marco Nelissen7181da82010-12-01 16:39:04 -0800346 mMediaplayerHandler.sendEmptyMessage(FADEUP);
347 }
348 break;
349 default:
350 Log.e(LOGTAG, "Unknown audio focus change code");
351 }
352 break;
353
The Android Open Source Project792a2202009-03-03 19:32:30 -0800354 default:
355 break;
356 }
357 }
358 };
359
360 private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
361 @Override
362 public void onReceive(Context context, Intent intent) {
363 String action = intent.getAction();
364 String cmd = intent.getStringExtra("command");
Marco Nelissen39888902010-03-02 10:27:05 -0800365 MusicUtils.debugLog("mIntentReceiver.onReceive " + action + " / " + cmd);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800366 if (CMDNEXT.equals(cmd) || NEXT_ACTION.equals(action)) {
Marco Nelissenbd0d2f12012-02-27 16:02:15 -0800367 gotoNext(true);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800368 } else if (CMDPREVIOUS.equals(cmd) || PREVIOUS_ACTION.equals(action)) {
369 prev();
370 } else if (CMDTOGGLEPAUSE.equals(cmd) || TOGGLEPAUSE_ACTION.equals(action)) {
371 if (isPlaying()) {
372 pause();
Jean-Michel Triviaa331872010-04-20 17:00:26 -0700373 mPausedByTransientLossOfFocus = false;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800374 } else {
375 play();
376 }
377 } else if (CMDPAUSE.equals(cmd) || PAUSE_ACTION.equals(action)) {
378 pause();
Jean-Michel Triviaa331872010-04-20 17:00:26 -0700379 mPausedByTransientLossOfFocus = false;
Marco Nelissenfc1c0dc2010-10-25 11:08:36 -0700380 } else if (CMDPLAY.equals(cmd)) {
381 play();
The Android Open Source Project792a2202009-03-03 19:32:30 -0800382 } else if (CMDSTOP.equals(cmd)) {
383 pause();
Jean-Michel Triviaa331872010-04-20 17:00:26 -0700384 mPausedByTransientLossOfFocus = false;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800385 seek(0);
The Android Open Source Project490384b2009-03-11 12:11:59 -0700386 } else if (MediaAppWidgetProvider.CMDAPPWIDGETUPDATE.equals(cmd)) {
387 // Someone asked us to refresh a set of specific widgets, probably
The Android Open Source Project792a2202009-03-03 19:32:30 -0800388 // because they were just added.
The Android Open Source Project490384b2009-03-11 12:11:59 -0700389 int[] appWidgetIds = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS);
390 mAppWidgetProvider.performUpdate(MediaPlaybackService.this, appWidgetIds);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800391 }
392 }
393 };
394
Jean-Michel Trivi085cbfd2010-03-08 17:06:18 -0800395 private OnAudioFocusChangeListener mAudioFocusListener = new OnAudioFocusChangeListener() {
Jean-Michel Trivif4cfdfd2010-03-31 12:12:23 -0700396 public void onAudioFocusChange(int focusChange) {
Marco Nelissen7181da82010-12-01 16:39:04 -0800397 mMediaplayerHandler.obtainMessage(FOCUSCHANGE, focusChange, 0).sendToTarget();
Jean-Michel Trivi085cbfd2010-03-08 17:06:18 -0800398 }
399 };
400
The Android Open Source Project792a2202009-03-03 19:32:30 -0800401 public MediaPlaybackService() {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800402 }
403
404 @Override
405 public void onCreate() {
406 super.onCreate();
Jean-Michel Trivi085cbfd2010-03-08 17:06:18 -0800407
408 mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
Marco Nelissen8717d342011-09-08 09:32:51 -0700409 ComponentName rec = new ComponentName(getPackageName(),
410 MediaButtonIntentReceiver.class.getName());
411 mAudioManager.registerMediaButtonEventReceiver(rec);
Mike Lockwood8ef36792013-08-01 10:03:03 -0700412
413 Intent i = new Intent(Intent.ACTION_MEDIA_BUTTON);
414 i.setComponent(rec);
415 PendingIntent pi = PendingIntent.getBroadcast(this /*context*/,
416 0 /*requestCode, ignored*/, i /*intent*/, 0 /*flags*/);
417 mRemoteControlClient = new RemoteControlClient(pi);
418 mAudioManager.registerRemoteControlClient(mRemoteControlClient);
419
420 int flags = RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS
421 | RemoteControlClient.FLAG_KEY_MEDIA_NEXT
422 | RemoteControlClient.FLAG_KEY_MEDIA_PLAY
423 | RemoteControlClient.FLAG_KEY_MEDIA_PAUSE
424 | RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE
425 | RemoteControlClient.FLAG_KEY_MEDIA_STOP;
426 mRemoteControlClient.setTransportControlFlags(flags);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800427
428 mPreferences = getSharedPreferences("Music", MODE_WORLD_READABLE | MODE_WORLD_WRITEABLE);
Marco Nelissen87bbf3c2009-12-23 16:18:45 -0800429 mCardId = MusicUtils.getCardId(this);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800430
431 registerExternalStorageListener();
Ayan Ghoshe6f136d2014-07-23 21:03:40 +0530432 registerA2dpServiceListener();
The Android Open Source Project792a2202009-03-03 19:32:30 -0800433
434 // Needs to be done in this thread, since otherwise ApplicationContext.getPowerManager() crashes.
435 mPlayer = new MultiPlayer();
436 mPlayer.setHandler(mMediaplayerHandler);
437
Ayan Ghoshe6f136d2014-07-23 21:03:40 +0530438 mSetBrowsedPlayerMonitor = new SetBrowsedPlayerMonitor();
439 mRemoteControlClient.setBrowsedPlayerUpdateListener(mSetBrowsedPlayerMonitor);
440
441 mSetPlayItemMonitor = new SetPlayItemMonitor();
442 mRemoteControlClient.setPlayItemListener(mSetPlayItemMonitor);
443
444 mGetNowPlayingEntriesMonitor = new GetNowPlayingEntriesMonitor();
445 mRemoteControlClient.setNowPlayingEntriesUpdateListener(mGetNowPlayingEntriesMonitor);
446
The Android Open Source Project792a2202009-03-03 19:32:30 -0800447 reloadQueue();
Marco Nelissen8717d342011-09-08 09:32:51 -0700448 notifyChange(QUEUE_CHANGED);
449 notifyChange(META_CHANGED);
450
Ayan Ghoshe6f136d2014-07-23 21:03:40 +0530451 mAttributePairs.put(ATTRIBUTE_EQUALIZER, false);
452 mAttributePairs.put(ATTRIBUTE_REPEATMODE, true);
453 mAttributePairs.put(ATTRIBUTE_SHUFFLEMODE, true);
454
The Android Open Source Project792a2202009-03-03 19:32:30 -0800455 IntentFilter commandFilter = new IntentFilter();
456 commandFilter.addAction(SERVICECMD);
457 commandFilter.addAction(TOGGLEPAUSE_ACTION);
458 commandFilter.addAction(PAUSE_ACTION);
459 commandFilter.addAction(NEXT_ACTION);
460 commandFilter.addAction(PREVIOUS_ACTION);
461 registerReceiver(mIntentReceiver, commandFilter);
462
The Android Open Source Project792a2202009-03-03 19:32:30 -0800463 PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
464 mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, this.getClass().getName());
465 mWakeLock.setReferenceCounted(false);
466
467 // If the service was idle, but got killed before it stopped itself, the
468 // system will relaunch it. Make sure it gets stopped again in that case.
469 Message msg = mDelayedStopHandler.obtainMessage();
470 mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
471 }
472
473 @Override
474 public void onDestroy() {
475 // Check that we're not being destroyed while something is still playing.
476 if (isPlaying()) {
Marco Nelissene99341f2009-11-11 11:13:51 -0800477 Log.e(LOGTAG, "Service being destroyed while still playing.");
The Android Open Source Project792a2202009-03-03 19:32:30 -0800478 }
Marco Nelissen2b0b9132009-05-21 16:26:47 -0700479 // release all MediaPlayer resources, including the native player and wakelocks
Marco Nelissenf2ef3b52010-09-21 15:47:27 -0700480 Intent i = new Intent(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION);
481 i.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, getAudioSessionId());
482 i.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, getPackageName());
483 sendBroadcast(i);
Marco Nelissen2b0b9132009-05-21 16:26:47 -0700484 mPlayer.release();
485 mPlayer = null;
Jean-Michel Trivi085cbfd2010-03-08 17:06:18 -0800486
487 mAudioManager.abandonAudioFocus(mAudioFocusListener);
Mike Lockwood8ef36792013-08-01 10:03:03 -0700488 mAudioManager.unregisterRemoteControlClient(mRemoteControlClient);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800489
Marco Nelissen2b0b9132009-05-21 16:26:47 -0700490 // make sure there aren't any other messages coming
491 mDelayedStopHandler.removeCallbacksAndMessages(null);
Marco Nelissen49e36ea2009-05-28 10:20:02 -0700492 mMediaplayerHandler.removeCallbacksAndMessages(null);
Marco Nelissen2b0b9132009-05-21 16:26:47 -0700493
The Android Open Source Project792a2202009-03-03 19:32:30 -0800494 if (mCursor != null) {
495 mCursor.close();
496 mCursor = null;
497 }
498
499 unregisterReceiver(mIntentReceiver);
500 if (mUnmountReceiver != null) {
501 unregisterReceiver(mUnmountReceiver);
502 mUnmountReceiver = null;
503 }
Ayan Ghoshe6f136d2014-07-23 21:03:40 +0530504
505 if (mA2dpReceiver != null) {
506 unregisterReceiver(mA2dpReceiver);
507 mA2dpReceiver = null;
508 }
509
510 if (mAttributePairs != null) {
511 mAttributePairs.clear();
512 }
513
The Android Open Source Project792a2202009-03-03 19:32:30 -0800514 mWakeLock.release();
515 super.onDestroy();
516 }
517
518 private final char hexdigits [] = new char [] {
519 '0', '1', '2', '3',
520 '4', '5', '6', '7',
521 '8', '9', 'a', 'b',
522 'c', 'd', 'e', 'f'
523 };
524
525 private void saveQueue(boolean full) {
Marco Nelissen8d08ec22010-05-10 14:05:24 -0700526 if (!mQueueIsSaveable) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800527 return;
528 }
Marco Nelissen8d08ec22010-05-10 14:05:24 -0700529
The Android Open Source Project792a2202009-03-03 19:32:30 -0800530 Editor ed = mPreferences.edit();
531 //long start = System.currentTimeMillis();
532 if (full) {
533 StringBuilder q = new StringBuilder();
534
535 // The current playlist is saved as a list of "reverse hexadecimal"
536 // numbers, which we can generate faster than normal decimal or
537 // hexadecimal numbers, which in turn allows us to save the playlist
538 // more often without worrying too much about performance.
539 // (saving the full state takes about 40 ms under no-load conditions
540 // on the phone)
541 int len = mPlayListLen;
542 for (int i = 0; i < len; i++) {
Marco Nelissenbd447b62009-06-29 14:52:05 -0700543 long n = mPlayList[i];
Marco Nelissen13355022010-08-31 15:13:27 -0700544 if (n < 0) {
545 continue;
546 } else if (n == 0) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800547 q.append("0;");
548 } else {
549 while (n != 0) {
Marco Nelissenbd447b62009-06-29 14:52:05 -0700550 int digit = (int)(n & 0xf);
Marco Nelissen13355022010-08-31 15:13:27 -0700551 n >>>= 4;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800552 q.append(hexdigits[digit]);
553 }
554 q.append(";");
555 }
556 }
557 //Log.i("@@@@ service", "created queue string in " + (System.currentTimeMillis() - start) + " ms");
558 ed.putString("queue", q.toString());
559 ed.putInt("cardid", mCardId);
Marco Nelissen63dbbbb2009-08-17 14:17:27 -0700560 if (mShuffleMode != SHUFFLE_NONE) {
561 // In shuffle mode we need to save the history too
562 len = mHistory.size();
563 q.setLength(0);
564 for (int i = 0; i < len; i++) {
565 int n = mHistory.get(i);
566 if (n == 0) {
567 q.append("0;");
568 } else {
569 while (n != 0) {
570 int digit = (n & 0xf);
Marco Nelissen13355022010-08-31 15:13:27 -0700571 n >>>= 4;
Marco Nelissen63dbbbb2009-08-17 14:17:27 -0700572 q.append(hexdigits[digit]);
573 }
574 q.append(";");
575 }
576 }
577 ed.putString("history", q.toString());
578 }
The Android Open Source Project792a2202009-03-03 19:32:30 -0800579 }
580 ed.putInt("curpos", mPlayPos);
581 if (mPlayer.isInitialized()) {
582 ed.putLong("seekpos", mPlayer.position());
583 }
584 ed.putInt("repeatmode", mRepeatMode);
585 ed.putInt("shufflemode", mShuffleMode);
Brad Fitzpatrick14c3cae2010-09-10 09:38:57 -0700586 SharedPreferencesCompat.apply(ed);
587
The Android Open Source Project792a2202009-03-03 19:32:30 -0800588 //Log.i("@@@@ service", "saved state in " + (System.currentTimeMillis() - start) + " ms");
589 }
590
591 private void reloadQueue() {
592 String q = null;
593
594 boolean newstyle = false;
595 int id = mCardId;
596 if (mPreferences.contains("cardid")) {
597 newstyle = true;
598 id = mPreferences.getInt("cardid", ~mCardId);
599 }
600 if (id == mCardId) {
601 // Only restore the saved playlist if the card is still
602 // the same one as when the playlist was saved
603 q = mPreferences.getString("queue", "");
604 }
Marco Nelissen6c615a22009-06-10 12:43:25 -0700605 int qlen = q != null ? q.length() : 0;
606 if (qlen > 1) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800607 //Log.i("@@@@ service", "loaded queue: " + q);
Marco Nelissenbf0ea142009-06-05 12:46:09 -0700608 int plen = 0;
609 int n = 0;
610 int shift = 0;
611 for (int i = 0; i < qlen; i++) {
612 char c = q.charAt(i);
613 if (c == ';') {
614 ensurePlayListCapacity(plen + 1);
615 mPlayList[plen] = n;
616 plen++;
617 n = 0;
618 shift = 0;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800619 } else {
Marco Nelissenbf0ea142009-06-05 12:46:09 -0700620 if (c >= '0' && c <= '9') {
621 n += ((c - '0') << shift);
622 } else if (c >= 'a' && c <= 'f') {
623 n += ((10 + c - 'a') << shift);
624 } else {
625 // bogus playlist data
626 plen = 0;
627 break;
628 }
629 shift += 4;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800630 }
631 }
Marco Nelissenbf0ea142009-06-05 12:46:09 -0700632 mPlayListLen = plen;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800633
634 int pos = mPreferences.getInt("curpos", 0);
Marco Nelissenbf0ea142009-06-05 12:46:09 -0700635 if (pos < 0 || pos >= mPlayListLen) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800636 // The saved playlist is bogus, discard it
637 mPlayListLen = 0;
638 return;
639 }
640 mPlayPos = pos;
641
642 // When reloadQueue is called in response to a card-insertion,
643 // we might not be able to query the media provider right away.
644 // To deal with this, try querying for the current file, and if
645 // that fails, wait a while and try again. If that too fails,
646 // assume there is a problem and don't restore the state.
Marco Nelissen63dbbbb2009-08-17 14:17:27 -0700647 Cursor crsr = MusicUtils.query(this,
The Android Open Source Project792a2202009-03-03 19:32:30 -0800648 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
649 new String [] {"_id"}, "_id=" + mPlayList[mPlayPos] , null, null);
Marco Nelissen63dbbbb2009-08-17 14:17:27 -0700650 if (crsr == null || crsr.getCount() == 0) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800651 // wait a bit and try again
652 SystemClock.sleep(3000);
Marco Nelissen63dbbbb2009-08-17 14:17:27 -0700653 crsr = getContentResolver().query(
The Android Open Source Project792a2202009-03-03 19:32:30 -0800654 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
655 mCursorCols, "_id=" + mPlayList[mPlayPos] , null, null);
656 }
Marco Nelissen63dbbbb2009-08-17 14:17:27 -0700657 if (crsr != null) {
658 crsr.close();
The Android Open Source Project792a2202009-03-03 19:32:30 -0800659 }
660
661 // Make sure we don't auto-skip to the next song, since that
662 // also starts playback. What could happen in that case is:
663 // - music is paused
664 // - go to UMS and delete some files, including the currently playing one
665 // - come back from UMS
666 // (time passes)
667 // - music app is killed for some reason (out of memory)
668 // - music service is restarted, service restores state, doesn't find
669 // the "current" file, goes to the next and: playback starts on its
670 // own, potentially at some random inconvenient time.
671 mOpenFailedCounter = 20;
672 mQuietMode = true;
Marco Nelissene41bd182012-03-14 08:24:40 -0700673 openCurrentAndNext();
The Android Open Source Project792a2202009-03-03 19:32:30 -0800674 mQuietMode = false;
675 if (!mPlayer.isInitialized()) {
676 // couldn't restore the saved state
677 mPlayListLen = 0;
678 return;
679 }
680
681 long seekpos = mPreferences.getLong("seekpos", 0);
682 seek(seekpos >= 0 && seekpos < duration() ? seekpos : 0);
Marco Nelissene99341f2009-11-11 11:13:51 -0800683 Log.d(LOGTAG, "restored queue, currently at position "
684 + position() + "/" + duration()
685 + " (requested " + seekpos + ")");
The Android Open Source Project792a2202009-03-03 19:32:30 -0800686
687 int repmode = mPreferences.getInt("repeatmode", REPEAT_NONE);
688 if (repmode != REPEAT_ALL && repmode != REPEAT_CURRENT) {
689 repmode = REPEAT_NONE;
690 }
691 mRepeatMode = repmode;
692
693 int shufmode = mPreferences.getInt("shufflemode", SHUFFLE_NONE);
694 if (shufmode != SHUFFLE_AUTO && shufmode != SHUFFLE_NORMAL) {
695 shufmode = SHUFFLE_NONE;
696 }
Marco Nelissen63dbbbb2009-08-17 14:17:27 -0700697 if (shufmode != SHUFFLE_NONE) {
698 // in shuffle mode we need to restore the history too
699 q = mPreferences.getString("history", "");
700 qlen = q != null ? q.length() : 0;
701 if (qlen > 1) {
702 plen = 0;
703 n = 0;
704 shift = 0;
705 mHistory.clear();
706 for (int i = 0; i < qlen; i++) {
707 char c = q.charAt(i);
708 if (c == ';') {
709 if (n >= mPlayListLen) {
710 // bogus history data
711 mHistory.clear();
712 break;
713 }
714 mHistory.add(n);
715 n = 0;
716 shift = 0;
717 } else {
718 if (c >= '0' && c <= '9') {
719 n += ((c - '0') << shift);
720 } else if (c >= 'a' && c <= 'f') {
721 n += ((10 + c - 'a') << shift);
722 } else {
723 // bogus history data
724 mHistory.clear();
725 break;
726 }
727 shift += 4;
728 }
729 }
730 }
731 }
The Android Open Source Project792a2202009-03-03 19:32:30 -0800732 if (shufmode == SHUFFLE_AUTO) {
733 if (! makeAutoShuffleList()) {
734 shufmode = SHUFFLE_NONE;
735 }
736 }
737 mShuffleMode = shufmode;
738 }
739 }
740
741 @Override
742 public IBinder onBind(Intent intent) {
743 mDelayedStopHandler.removeCallbacksAndMessages(null);
744 mServiceInUse = true;
745 return mBinder;
746 }
747
748 @Override
749 public void onRebind(Intent intent) {
750 mDelayedStopHandler.removeCallbacksAndMessages(null);
751 mServiceInUse = true;
752 }
753
754 @Override
Marco Nelissenc1017e52009-09-24 13:21:06 -0700755 public int onStartCommand(Intent intent, int flags, int startId) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800756 mServiceStartId = startId;
757 mDelayedStopHandler.removeCallbacksAndMessages(null);
Marco Nelissenc1017e52009-09-24 13:21:06 -0700758
759 if (intent != null) {
760 String action = intent.getAction();
761 String cmd = intent.getStringExtra("command");
Marco Nelissen39888902010-03-02 10:27:05 -0800762 MusicUtils.debugLog("onStartCommand " + action + " / " + cmd);
Marco Nelissenc1017e52009-09-24 13:21:06 -0700763
764 if (CMDNEXT.equals(cmd) || NEXT_ACTION.equals(action)) {
Marco Nelissenbd0d2f12012-02-27 16:02:15 -0800765 gotoNext(true);
Marco Nelissenc1017e52009-09-24 13:21:06 -0700766 } else if (CMDPREVIOUS.equals(cmd) || PREVIOUS_ACTION.equals(action)) {
Marco Nelissenb63b5d12009-11-18 16:04:19 -0800767 if (position() < 2000) {
768 prev();
769 } else {
770 seek(0);
771 play();
772 }
Marco Nelissenc1017e52009-09-24 13:21:06 -0700773 } else if (CMDTOGGLEPAUSE.equals(cmd) || TOGGLEPAUSE_ACTION.equals(action)) {
774 if (isPlaying()) {
775 pause();
Jean-Michel Triviaa331872010-04-20 17:00:26 -0700776 mPausedByTransientLossOfFocus = false;
Marco Nelissenc1017e52009-09-24 13:21:06 -0700777 } else {
778 play();
779 }
780 } else if (CMDPAUSE.equals(cmd) || PAUSE_ACTION.equals(action)) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800781 pause();
Jean-Michel Triviaa331872010-04-20 17:00:26 -0700782 mPausedByTransientLossOfFocus = false;
Jaikumar Ganesh47dd8472010-12-16 15:51:31 -0800783 } else if (CMDPLAY.equals(cmd)) {
784 play();
Marco Nelissenc1017e52009-09-24 13:21:06 -0700785 } else if (CMDSTOP.equals(cmd)) {
786 pause();
Jean-Michel Triviaa331872010-04-20 17:00:26 -0700787 mPausedByTransientLossOfFocus = false;
Marco Nelissenc1017e52009-09-24 13:21:06 -0700788 seek(0);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800789 }
The Android Open Source Project792a2202009-03-03 19:32:30 -0800790 }
791
792 // make sure the service will shut down on its own if it was
793 // just started but not bound to and nothing is playing
794 mDelayedStopHandler.removeCallbacksAndMessages(null);
795 Message msg = mDelayedStopHandler.obtainMessage();
796 mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
Marco Nelissenc1017e52009-09-24 13:21:06 -0700797 return START_STICKY;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800798 }
799
800 @Override
801 public boolean onUnbind(Intent intent) {
802 mServiceInUse = false;
803
804 // Take a snapshot of the current playlist
805 saveQueue(true);
806
Jean-Michel Triviaa331872010-04-20 17:00:26 -0700807 if (isPlaying() || mPausedByTransientLossOfFocus) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800808 // something is currently playing, or will be playing once
Jean-Michel Triviaa331872010-04-20 17:00:26 -0700809 // 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 -0800810 return true;
811 }
812
813 // If there is a playlist but playback is paused, then wait a while
814 // before stopping the service, so that pause/resume isn't slow.
815 // Also delay stopping the service if we're transitioning between tracks.
816 if (mPlayListLen > 0 || mMediaplayerHandler.hasMessages(TRACK_ENDED)) {
817 Message msg = mDelayedStopHandler.obtainMessage();
818 mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
819 return true;
820 }
821
822 // No active playlist, OK to stop the service right now
823 stopSelf(mServiceStartId);
824 return true;
825 }
Ayan Ghoshe6f136d2014-07-23 21:03:40 +0530826
827 private void getNowPlayingEntries() {
828 Log.i(LOGTAG, "getNowPlayingEntries: num of items: " + mPlayListLen);
829 synchronized (mPlayList) {
830 long [] nowPlayingList = new long[mPlayListLen];
831 for (int count = 0; count < mPlayListLen; count++) {
832 nowPlayingList[count] = mPlayList[count];
833 }
834 mRemoteControlClient.updateNowPlayingEntries(nowPlayingList);
835 }
836 }
837
838 private void setBrowsedPlayer() {
839 Log.i(LOGTAG, "setBrowsedPlayer");
840 Uri uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
841 Log.i(LOGTAG, "URI: " + uri);
842 mRemoteControlClient.updateFolderInfoBrowsedPlayer(uri.toString());
843 }
844
845 private void playItem(int scope, long playItemUid) {
846 boolean success = false;
847 Log.i(LOGTAG, "playItem uid: " + playItemUid + " scope: " + scope);
848 if (playItemUid < 0) {
849 mRemoteControlClient.playItemResponse(success);
850 return;
851 } else if (scope == SCOPE_FILE_SYSTEM) {
852 success = openItem(playItemUid);
853 } else if (scope == SCOPE_NOW_PLAYING) {
854 for (int index = 0; index < mPlayListLen; index++) {
855 if (mPlayList[index] == playItemUid) {
856 Log.i(LOGTAG, "Now Playing list contains UID at " + index);
857 success = true;
858 break;
859 }
860 }
861 if (success) {
862 success = openItem(playItemUid);
863 }
864 }
865 mRemoteControlClient.playItemResponse(success);
866 }
867
868 private boolean openItem (long playItemUid) {
869 boolean isSuccess = false;
870 if (mCursor != null) {
871 mCursor.close();
872 mCursor = null;
873 }
874 if (mPlayListLen == 0) {
875 Log.e(LOGTAG, "Playlist Length = 0");
876 return isSuccess;
877 }
878 stop(false);
879 mCursor = getCursorForId(playItemUid);
880 if (mCursor != null) {
881 long [] list = new long[] { playItemUid };
882 enqueue(list, NOW);
883 Log.i(LOGTAG, "Opened UID: " + playItemUid);
884 isSuccess = true;
885 } else {
886 Log.e(LOGTAG, "Cursor could not be fetched");
887 }
888 return isSuccess;
889 }
890
The Android Open Source Project792a2202009-03-03 19:32:30 -0800891 private Handler mDelayedStopHandler = new Handler() {
892 @Override
893 public void handleMessage(Message msg) {
894 // Check again to make sure nothing is playing right now
Jean-Michel Triviaa331872010-04-20 17:00:26 -0700895 if (isPlaying() || mPausedByTransientLossOfFocus || mServiceInUse
The Android Open Source Project792a2202009-03-03 19:32:30 -0800896 || mMediaplayerHandler.hasMessages(TRACK_ENDED)) {
897 return;
898 }
899 // save the queue again, because it might have changed
900 // since the user exited the music app (because of
901 // party-shuffle or because the play-position changed)
902 saveQueue(true);
903 stopSelf(mServiceStartId);
904 }
905 };
Marco Nelissenf2ef3b52010-09-21 15:47:27 -0700906
Ayan Ghoshe6f136d2014-07-23 21:03:40 +0530907 private Handler mAvrcpHandler = new Handler() {
908 @Override
909 public void handleMessage(Message msg) {
910 switch (msg.what) {
911 case SET_BROWSED_PLAYER:
912 setBrowsedPlayer();
913 break;
914 case SET_PLAY_ITEM:
915 playItem(msg.arg1, ((Long)msg.obj).longValue());
916 break;
917 case GET_NOW_PLAYING_ENTRIES:
918 getNowPlayingEntries();
919 break;
920 default:
921 }
922 }
923 };
924
The Android Open Source Project792a2202009-03-03 19:32:30 -0800925 /**
926 * Called when we receive a ACTION_MEDIA_EJECT notification.
927 *
928 * @param storagePath path to mount point for the removed media
929 */
930 public void closeExternalStorageFiles(String storagePath) {
931 // stop playback and clean up if the SD card is going to be unmounted.
932 stop(true);
933 notifyChange(QUEUE_CHANGED);
934 notifyChange(META_CHANGED);
935 }
936
937 /**
938 * Registers an intent to listen for ACTION_MEDIA_EJECT notifications.
939 * The intent will call closeExternalStorageFiles() if the external media
940 * is going to be ejected, so applications can clean up any files they have open.
941 */
942 public void registerExternalStorageListener() {
943 if (mUnmountReceiver == null) {
944 mUnmountReceiver = new BroadcastReceiver() {
945 @Override
946 public void onReceive(Context context, Intent intent) {
947 String action = intent.getAction();
948 if (action.equals(Intent.ACTION_MEDIA_EJECT)) {
949 saveQueue(true);
Marco Nelissen8d08ec22010-05-10 14:05:24 -0700950 mQueueIsSaveable = false;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800951 closeExternalStorageFiles(intent.getData().getPath());
952 } else if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) {
953 mMediaMountedCount++;
Marco Nelissen87bbf3c2009-12-23 16:18:45 -0800954 mCardId = MusicUtils.getCardId(MediaPlaybackService.this);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800955 reloadQueue();
Marco Nelissen8d08ec22010-05-10 14:05:24 -0700956 mQueueIsSaveable = true;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800957 notifyChange(QUEUE_CHANGED);
958 notifyChange(META_CHANGED);
959 }
960 }
961 };
962 IntentFilter iFilter = new IntentFilter();
963 iFilter.addAction(Intent.ACTION_MEDIA_EJECT);
964 iFilter.addAction(Intent.ACTION_MEDIA_MOUNTED);
965 iFilter.addDataScheme("file");
966 registerReceiver(mUnmountReceiver, iFilter);
967 }
968 }
969
Ayan Ghoshe6f136d2014-07-23 21:03:40 +0530970 public void registerA2dpServiceListener() {
971 mA2dpReceiver = new BroadcastReceiver() {
972 @Override
973 public void onReceive(Context context, Intent intent) {
974 String action = intent.getAction();
975 String cmd = intent.getStringExtra("command");
976 if (action.equals(SET_ADDRESSED_PLAYER)) {
977 play(); // this ensures audio focus change is called and play the media
978 } else if (action.equals(PLAYSTATUS_REQUEST)) {
979 notifyChange(PLAYSTATUS_RESPONSE);
980 } else if (PLAYERSETTINGS_REQUEST.equals(action)) {
981 if (CMDGET.equals(cmd)) {
982 int getCommand = intent.getIntExtra(EXTRA_GET_COMMAND,
983 GET_INVALID);
984 byte attribute;
985 byte [] attrIds; byte [] valIds;
986 switch (getCommand) {
987 case GET_ATTRIBUTE_IDS:
988 notifyAttributeIDs(PLAYERSETTINGS_RESPONSE);
989 break;
990 case GET_VALUE_IDS:
991 attribute =
992 intent.getByteExtra(EXTRA_ATTRIBUTE_ID,
993 GET_ATTR_INVALID);
994 notifyValueIDs(PLAYERSETTINGS_RESPONSE, attribute);
995 break;
996 case GET_ATTRIBUTE_TEXT:
997 attrIds = intent.getByteArrayExtra(
998 EXTRA_ATTIBUTE_ID_ARRAY);
999 notifyAttributesText(PLAYERSETTINGS_RESPONSE, attrIds);
1000 break;
1001 case GET_VALUE_TEXT:
1002 attribute =
1003 intent.getByteExtra(EXTRA_ATTRIBUTE_ID,
1004 GET_ATTR_INVALID);
1005 valIds = intent.getByteArrayExtra(
1006 EXTRA_VALUE_ID_ARRAY);
1007 notifyAttributeValuesText(
1008 PLAYERSETTINGS_RESPONSE, attribute, valIds);
1009 break;
1010 case GET_ATTRIBUTE_VALUES:
1011 notifyAttributeValues(PLAYERSETTINGS_RESPONSE,
1012 mAttributePairs, GET_ATTRIBUTE_VALUES);
1013 break;
1014 default:
1015 Log.e(LOGTAG, "invalid getCommand"+getCommand);
1016 break;
1017 }
1018 } else if (CMDSET.equals(cmd)){
1019 byte[] attribValuePairs = intent.getByteArrayExtra(
1020 EXTRA_ATTRIB_VALUE_PAIRS);
1021 setValidAttributes(attribValuePairs);
1022 }
1023 }
1024 }
1025 };
1026 IntentFilter iFilter = new IntentFilter();
1027 iFilter.addAction(SET_ADDRESSED_PLAYER);
1028 iFilter.addAction(PLAYSTATUS_REQUEST);
1029 iFilter.addAction(PLAYERSETTINGS_REQUEST);
1030 registerReceiver(mA2dpReceiver, iFilter);
1031 }
1032
The Android Open Source Project792a2202009-03-03 19:32:30 -08001033 /**
1034 * Notify the change-receivers that something has changed.
1035 * The intent that is sent contains the following data
1036 * for the currently playing track:
1037 * "id" - Integer: the database row ID
1038 * "artist" - String: the name of the artist
1039 * "album" - String: the name of the album
1040 * "track" - String: the name of the track
1041 * The intent has an action that is one of
1042 * "com.android.music.metachanged"
1043 * "com.android.music.queuechanged",
1044 * "com.android.music.playbackcomplete"
1045 * "com.android.music.playstatechanged"
1046 * respectively indicating that a new track has
1047 * started playing, that the playback queue has
1048 * changed, that playback has stopped because
1049 * the last file in the list has been played,
1050 * or that the play-state changed (paused/resumed).
1051 */
1052 private void notifyChange(String what) {
Marco Nelissen8717d342011-09-08 09:32:51 -07001053
The Android Open Source Project792a2202009-03-03 19:32:30 -08001054 Intent i = new Intent(what);
Marco Nelissenbd447b62009-06-29 14:52:05 -07001055 i.putExtra("id", Long.valueOf(getAudioId()));
The Android Open Source Project792a2202009-03-03 19:32:30 -08001056 i.putExtra("artist", getArtistName());
1057 i.putExtra("album",getAlbumName());
1058 i.putExtra("track", getTrackName());
Marco Nelissen6b507de2010-10-20 14:08:10 -07001059 i.putExtra("playing", isPlaying());
1060 sendStickyBroadcast(i);
Marco Nelissen8717d342011-09-08 09:32:51 -07001061
1062 if (what.equals(PLAYSTATE_CHANGED)) {
Ayan Ghoshe6f136d2014-07-23 21:03:40 +05301063 mRemoteControlClient.setPlaybackState((isPlaying() ?
1064 RemoteControlClient.PLAYSTATE_PLAYING : RemoteControlClient.PLAYSTATE_PAUSED),
1065 position() , RemoteControlClient.PLAYBACK_SPEED_1X);
Marco Nelissen8717d342011-09-08 09:32:51 -07001066 } else if (what.equals(META_CHANGED)) {
Mike Lockwood8ef36792013-08-01 10:03:03 -07001067 RemoteControlClient.MetadataEditor ed = mRemoteControlClient.editMetadata(true);
1068 ed.putString(MediaMetadataRetriever.METADATA_KEY_TITLE, getTrackName());
1069 ed.putString(MediaMetadataRetriever.METADATA_KEY_ALBUM, getAlbumName());
Suresh Kamasala01f4aef2015-10-27 13:34:29 +05301070 ed.putString(MediaMetadataRetriever.METADATA_KEY_ARTIST, getArtistName());
Mike Lockwood8ef36792013-08-01 10:03:03 -07001071 ed.putLong(MediaMetadataRetriever.METADATA_KEY_DURATION, duration());
Ayan Ghoshe6f136d2014-07-23 21:03:40 +05301072 if ((mPlayList != null) && (mPlayPos >= 0) && (mPlayPos < mPlayList.length)) {
1073 ed.putLong(MediaMetadataRetriever.METADATA_KEY_CD_TRACK_NUMBER,
1074 mPlayList[mPlayPos]);
1075 } else {
1076 ed.putLong(MediaMetadataRetriever.METADATA_KEY_CD_TRACK_NUMBER,
1077 INVALID_SONG_UID);
1078 }
1079 ed.putLong(MediaMetadataRetriever.METADATA_KEY_DISC_NUMBER, mPlayPos);
1080 try {
1081 ed.putLong(MediaMetadataRetriever.METADATA_KEY_NUM_TRACKS, mPlayListLen);
1082 } catch (IllegalArgumentException e) {
1083 Log.e(LOGTAG, "METADATA_KEY_NUM_TRACKS: failed: " + e);
1084 }
Mike Lockwood8ef36792013-08-01 10:03:03 -07001085 Bitmap b = MusicUtils.getArtwork(this, getAudioId(), getAlbumId(), false);
1086 if (b != null) {
1087 ed.putBitmap(MetadataEditor.BITMAP_KEY_ARTWORK, b);
1088 }
1089 ed.apply();
Ayan Ghoshe6f136d2014-07-23 21:03:40 +05301090 } else if (what.equals(QUEUE_CHANGED)) {
1091 mRemoteControlClient.updateNowPlayingContentChange();
Marco Nelissen8717d342011-09-08 09:32:51 -07001092 }
1093
The Android Open Source Project792a2202009-03-03 19:32:30 -08001094 if (what.equals(QUEUE_CHANGED)) {
1095 saveQueue(true);
1096 } else {
1097 saveQueue(false);
1098 }
1099
The Android Open Source Project490384b2009-03-11 12:11:59 -07001100 // Share this notification directly with our widgets
1101 mAppWidgetProvider.notifyChange(this, what);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001102 }
1103
1104 private void ensurePlayListCapacity(int size) {
1105 if (mPlayList == null || size > mPlayList.length) {
1106 // reallocate at 2x requested size so we don't
1107 // need to grow and copy the array for every
1108 // insert
Marco Nelissenbd447b62009-06-29 14:52:05 -07001109 long [] newlist = new long[size * 2];
Marco Nelissenbf0ea142009-06-05 12:46:09 -07001110 int len = mPlayList != null ? mPlayList.length : mPlayListLen;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001111 for (int i = 0; i < len; i++) {
1112 newlist[i] = mPlayList[i];
1113 }
1114 mPlayList = newlist;
1115 }
1116 // FIXME: shrink the array when the needed size is much smaller
1117 // than the allocated size
1118 }
1119
1120 // insert the list of songs at the specified position in the playlist
Marco Nelissenbd447b62009-06-29 14:52:05 -07001121 private void addToPlayList(long [] list, int position) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001122 int addlen = list.length;
1123 if (position < 0) { // overwrite
1124 mPlayListLen = 0;
1125 position = 0;
1126 }
1127 ensurePlayListCapacity(mPlayListLen + addlen);
1128 if (position > mPlayListLen) {
1129 position = mPlayListLen;
1130 }
1131
1132 // move part of list after insertion point
1133 int tailsize = mPlayListLen - position;
1134 for (int i = tailsize ; i > 0 ; i--) {
1135 mPlayList[position + i] = mPlayList[position + i - addlen];
1136 }
1137
1138 // copy list into playlist
1139 for (int i = 0; i < addlen; i++) {
1140 mPlayList[position + i] = list[i];
1141 }
1142 mPlayListLen += addlen;
Marco Nelissen3aa9ad02010-09-16 13:23:11 -07001143 if (mPlayListLen == 0) {
1144 mCursor.close();
1145 mCursor = null;
1146 notifyChange(META_CHANGED);
1147 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08001148 }
1149
1150 /**
1151 * Appends a list of tracks to the current playlist.
1152 * If nothing is playing currently, playback will be started at
1153 * the first track.
1154 * If the action is NOW, playback will switch to the first of
1155 * the new tracks immediately.
1156 * @param list The list of tracks to append.
1157 * @param action NOW, NEXT or LAST
1158 */
Marco Nelissenbd447b62009-06-29 14:52:05 -07001159 public void enqueue(long [] list, int action) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001160 synchronized(this) {
1161 if (action == NEXT && mPlayPos + 1 < mPlayListLen) {
1162 addToPlayList(list, mPlayPos + 1);
1163 notifyChange(QUEUE_CHANGED);
1164 } else {
1165 // action == LAST || action == NOW || mPlayPos + 1 == mPlayListLen
1166 addToPlayList(list, Integer.MAX_VALUE);
1167 notifyChange(QUEUE_CHANGED);
1168 if (action == NOW) {
1169 mPlayPos = mPlayListLen - list.length;
Marco Nelissene41bd182012-03-14 08:24:40 -07001170 openCurrentAndNext();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001171 play();
1172 notifyChange(META_CHANGED);
1173 return;
1174 }
1175 }
1176 if (mPlayPos < 0) {
1177 mPlayPos = 0;
Marco Nelissene41bd182012-03-14 08:24:40 -07001178 openCurrentAndNext();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001179 play();
1180 notifyChange(META_CHANGED);
1181 }
1182 }
1183 }
1184
1185 /**
1186 * Replaces the current playlist with a new list,
1187 * and prepares for starting playback at the specified
1188 * position in the list, or a random position if the
1189 * specified position is 0.
1190 * @param list The new list of tracks.
1191 */
Marco Nelissenbd447b62009-06-29 14:52:05 -07001192 public void open(long [] list, int position) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001193 synchronized (this) {
1194 if (mShuffleMode == SHUFFLE_AUTO) {
1195 mShuffleMode = SHUFFLE_NORMAL;
1196 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07001197 long oldId = getAudioId();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001198 int listlength = list.length;
1199 boolean newlist = true;
1200 if (mPlayListLen == listlength) {
1201 // possible fast path: list might be the same
1202 newlist = false;
1203 for (int i = 0; i < listlength; i++) {
1204 if (list[i] != mPlayList[i]) {
1205 newlist = true;
1206 break;
1207 }
1208 }
1209 }
1210 if (newlist) {
1211 addToPlayList(list, -1);
1212 notifyChange(QUEUE_CHANGED);
1213 }
1214 int oldpos = mPlayPos;
1215 if (position >= 0) {
1216 mPlayPos = position;
1217 } else {
1218 mPlayPos = mRand.nextInt(mPlayListLen);
1219 }
1220 mHistory.clear();
1221
1222 saveBookmarkIfNeeded();
Marco Nelissene41bd182012-03-14 08:24:40 -07001223 openCurrentAndNext();
Jeffrey Sharkeyd8c69672009-03-24 17:59:05 -07001224 if (oldId != getAudioId()) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001225 notifyChange(META_CHANGED);
1226 }
1227 }
1228 }
1229
1230 /**
1231 * Moves the item at index1 to index2.
1232 * @param index1
1233 * @param index2
1234 */
1235 public void moveQueueItem(int index1, int index2) {
1236 synchronized (this) {
1237 if (index1 >= mPlayListLen) {
1238 index1 = mPlayListLen - 1;
1239 }
1240 if (index2 >= mPlayListLen) {
1241 index2 = mPlayListLen - 1;
1242 }
1243 if (index1 < index2) {
Marco Nelissenbd447b62009-06-29 14:52:05 -07001244 long tmp = mPlayList[index1];
The Android Open Source Project792a2202009-03-03 19:32:30 -08001245 for (int i = index1; i < index2; i++) {
1246 mPlayList[i] = mPlayList[i+1];
1247 }
1248 mPlayList[index2] = tmp;
1249 if (mPlayPos == index1) {
1250 mPlayPos = index2;
1251 } else if (mPlayPos >= index1 && mPlayPos <= index2) {
1252 mPlayPos--;
1253 }
1254 } else if (index2 < index1) {
Marco Nelissenbd447b62009-06-29 14:52:05 -07001255 long tmp = mPlayList[index1];
The Android Open Source Project792a2202009-03-03 19:32:30 -08001256 for (int i = index1; i > index2; i--) {
1257 mPlayList[i] = mPlayList[i-1];
1258 }
1259 mPlayList[index2] = tmp;
1260 if (mPlayPos == index1) {
1261 mPlayPos = index2;
1262 } else if (mPlayPos >= index2 && mPlayPos <= index1) {
1263 mPlayPos++;
1264 }
1265 }
1266 notifyChange(QUEUE_CHANGED);
1267 }
1268 }
1269
1270 /**
1271 * Returns the current play list
1272 * @return An array of integers containing the IDs of the tracks in the play list
1273 */
Marco Nelissenbd447b62009-06-29 14:52:05 -07001274 public long [] getQueue() {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001275 synchronized (this) {
1276 int len = mPlayListLen;
Marco Nelissenbd447b62009-06-29 14:52:05 -07001277 long [] list = new long[len];
The Android Open Source Project792a2202009-03-03 19:32:30 -08001278 for (int i = 0; i < len; i++) {
1279 list[i] = mPlayList[i];
1280 }
1281 return list;
1282 }
1283 }
1284
Marco Nelissene41bd182012-03-14 08:24:40 -07001285 private Cursor getCursorForId(long lid) {
1286 String id = String.valueOf(lid);
1287
1288 Cursor c = getContentResolver().query(
1289 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
1290 mCursorCols, "_id=" + id , null, null);
Marco Nelissenc37b2002012-10-16 12:59:54 -07001291 if (c != null) {
1292 c.moveToFirst();
1293 }
Marco Nelissene41bd182012-03-14 08:24:40 -07001294 return c;
1295 }
1296
1297 private void openCurrentAndNext() {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001298 synchronized (this) {
1299 if (mCursor != null) {
1300 mCursor.close();
1301 mCursor = null;
1302 }
Marco Nelissen8d08ec22010-05-10 14:05:24 -07001303
The Android Open Source Project792a2202009-03-03 19:32:30 -08001304 if (mPlayListLen == 0) {
1305 return;
1306 }
1307 stop(false);
1308
Marco Nelissen90d1f622012-04-05 12:27:56 -07001309 mCursor = getCursorForId(mPlayList[mPlayPos]);
Marco Nelissenc37b2002012-10-16 12:59:54 -07001310 while(true) {
1311 if (mCursor != null && mCursor.getCount() != 0 &&
1312 open(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + "/" +
1313 mCursor.getLong(IDCOLIDX))) {
1314 break;
Marco Nelissen6fb85512012-07-18 07:46:21 -07001315 }
Marco Nelissenc37b2002012-10-16 12:59:54 -07001316 // if we get here then opening the file failed. We can close the cursor now, because
1317 // we're either going to create a new one next, or stop trying
1318 if (mCursor != null) {
1319 mCursor.close();
1320 mCursor = null;
1321 }
Marco Nelissen90d1f622012-04-05 12:27:56 -07001322 if (mOpenFailedCounter++ < 10 && mPlayListLen > 1) {
1323 int pos = getNextPosition(false);
1324 if (pos < 0) {
1325 gotoIdleState();
1326 if (mIsSupposedToBePlaying) {
1327 mIsSupposedToBePlaying = false;
1328 notifyChange(PLAYSTATE_CHANGED);
1329 }
1330 return;
1331 }
1332 mPlayPos = pos;
1333 stop(false);
1334 mPlayPos = pos;
1335 mCursor = getCursorForId(mPlayList[mPlayPos]);
1336 } else {
1337 mOpenFailedCounter = 0;
1338 if (!mQuietMode) {
1339 Toast.makeText(this, R.string.playback_failed, Toast.LENGTH_SHORT).show();
1340 }
1341 Log.d(LOGTAG, "Failed to open file for playback");
Marco Nelissenc37b2002012-10-16 12:59:54 -07001342 gotoIdleState();
1343 if (mIsSupposedToBePlaying) {
1344 mIsSupposedToBePlaying = false;
1345 notifyChange(PLAYSTATE_CHANGED);
1346 }
Marco Nelissen90d1f622012-04-05 12:27:56 -07001347 return;
Marco Nelissene41bd182012-03-14 08:24:40 -07001348 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08001349 }
Marco Nelissen90d1f622012-04-05 12:27:56 -07001350
1351 // go to bookmark if needed
1352 if (isPodcast()) {
1353 long bookmark = getBookmark();
1354 // Start playing a little bit before the bookmark,
1355 // so it's easier to get back in to the narrative.
1356 seek(bookmark - 5000);
1357 }
1358 setNextTrack();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001359 }
1360 }
1361
Marco Nelissene41bd182012-03-14 08:24:40 -07001362 private void setNextTrack() {
Ayan Ghoshe6f136d2014-07-23 21:03:40 +05301363 if(SystemProperties.getBoolean("gapless.audio.decode", false)) {
1364 mNextPlayPos = getNextPosition(false);
1365 if (mNextPlayPos >= 0) {
1366 long id = mPlayList[mNextPlayPos];
1367 mPlayer.setNextDataSource(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + "/" + id);
1368 } else {
1369 mPlayer.setNextDataSource(null);
1370 }
Marco Nelissene41bd182012-03-14 08:24:40 -07001371 }
1372 }
1373
The Android Open Source Project792a2202009-03-03 19:32:30 -08001374 /**
1375 * Opens the specified file and readies it for playback.
1376 *
1377 * @param path The full path of the file to be opened.
The Android Open Source Project792a2202009-03-03 19:32:30 -08001378 */
Marco Nelissen90d1f622012-04-05 12:27:56 -07001379 public boolean open(String path) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001380 synchronized (this) {
1381 if (path == null) {
Marco Nelissen90d1f622012-04-05 12:27:56 -07001382 return false;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001383 }
1384
The Android Open Source Project792a2202009-03-03 19:32:30 -08001385 // if mCursor is null, try to associate path with a database cursor
1386 if (mCursor == null) {
1387
1388 ContentResolver resolver = getContentResolver();
1389 Uri uri;
1390 String where;
1391 String selectionArgs[];
1392 if (path.startsWith("content://media/")) {
1393 uri = Uri.parse(path);
1394 where = null;
1395 selectionArgs = null;
1396 } else {
1397 uri = MediaStore.Audio.Media.getContentUriForPath(path);
1398 where = MediaStore.Audio.Media.DATA + "=?";
1399 selectionArgs = new String[] { path };
1400 }
1401
1402 try {
1403 mCursor = resolver.query(uri, mCursorCols, where, selectionArgs, null);
1404 if (mCursor != null) {
1405 if (mCursor.getCount() == 0) {
1406 mCursor.close();
1407 mCursor = null;
1408 } else {
1409 mCursor.moveToNext();
1410 ensurePlayListCapacity(1);
1411 mPlayListLen = 1;
Marco Nelissenbd447b62009-06-29 14:52:05 -07001412 mPlayList[0] = mCursor.getLong(IDCOLIDX);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001413 mPlayPos = 0;
1414 }
1415 }
1416 } catch (UnsupportedOperationException ex) {
1417 }
1418 }
1419 mFileToPlay = path;
1420 mPlayer.setDataSource(mFileToPlay);
Marco Nelissen90d1f622012-04-05 12:27:56 -07001421 if (mPlayer.isInitialized()) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001422 mOpenFailedCounter = 0;
Marco Nelissen90d1f622012-04-05 12:27:56 -07001423 return true;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001424 }
Marco Nelissen90d1f622012-04-05 12:27:56 -07001425 stop(true);
1426 return false;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001427 }
1428 }
1429
1430 /**
1431 * Starts playback of a previously opened file.
1432 */
1433 public void play() {
Jean-Michel Trivi085cbfd2010-03-08 17:06:18 -08001434 mAudioManager.requestAudioFocus(mAudioFocusListener, AudioManager.STREAM_MUSIC,
1435 AudioManager.AUDIOFOCUS_GAIN);
Jean-Michel Trivi3d22fc22010-03-17 11:46:58 -07001436 mAudioManager.registerMediaButtonEventReceiver(new ComponentName(this.getPackageName(),
1437 MediaButtonIntentReceiver.class.getName()));
Ayan Ghoshe6f136d2014-07-23 21:03:40 +05301438 mAudioManager.registerRemoteControlClient(mRemoteControlClient);
Jean-Michel Trivi085cbfd2010-03-08 17:06:18 -08001439
The Android Open Source Project792a2202009-03-03 19:32:30 -08001440 if (mPlayer.isInitialized()) {
Thomas Tuttle272eb782009-01-28 21:06:46 -05001441 // if we are at the end of the song, go to the next song first
Marco Nelissen2f9a1ce2009-06-26 14:23:31 -07001442 long duration = mPlayer.duration();
1443 if (mRepeatMode != REPEAT_CURRENT && duration > 2000 &&
1444 mPlayer.position() >= duration - 2000) {
Marco Nelissenbd0d2f12012-02-27 16:02:15 -08001445 gotoNext(true);
Thomas Tuttle272eb782009-01-28 21:06:46 -05001446 }
1447
The Android Open Source Project792a2202009-03-03 19:32:30 -08001448 mPlayer.start();
Marco Nelissen7181da82010-12-01 16:39:04 -08001449 // make sure we fade in, in case a previous fadein was stopped because
1450 // of another focus loss
Marco Nelissen7bae28f2010-12-02 10:04:54 -08001451 mMediaplayerHandler.removeMessages(FADEDOWN);
Marco Nelissen7181da82010-12-01 16:39:04 -08001452 mMediaplayerHandler.sendEmptyMessage(FADEUP);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001453
Marco Nelissene41bd182012-03-14 08:24:40 -07001454 updateNotification();
Marco Nelissenc1333372009-05-06 12:54:47 -07001455 if (!mIsSupposedToBePlaying) {
Mike Cleron347fe572009-10-09 12:26:28 -07001456 mIsSupposedToBePlaying = true;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001457 notifyChange(PLAYSTATE_CHANGED);
1458 }
Mike Cleron347fe572009-10-09 12:26:28 -07001459
The Android Open Source Project792a2202009-03-03 19:32:30 -08001460 } else if (mPlayListLen <= 0) {
1461 // This is mostly so that if you press 'play' on a bluetooth headset
1462 // without every having played anything before, it will still play
1463 // something.
1464 setShuffleMode(SHUFFLE_AUTO);
1465 }
1466 }
Marco Nelissene41bd182012-03-14 08:24:40 -07001467
1468 private void updateNotification() {
1469 RemoteViews views = new RemoteViews(getPackageName(), R.layout.statusbar);
1470 views.setImageViewResource(R.id.icon, R.drawable.stat_notify_musicplayer);
1471 if (getAudioId() < 0) {
1472 // streaming
1473 views.setTextViewText(R.id.trackname, getPath());
1474 views.setTextViewText(R.id.artistalbum, null);
1475 } else {
1476 String artist = getArtistName();
1477 views.setTextViewText(R.id.trackname, getTrackName());
1478 if (artist == null || artist.equals(MediaStore.UNKNOWN_STRING)) {
1479 artist = getString(R.string.unknown_artist_name);
1480 }
1481 String album = getAlbumName();
1482 if (album == null || album.equals(MediaStore.UNKNOWN_STRING)) {
1483 album = getString(R.string.unknown_album_name);
1484 }
1485
1486 views.setTextViewText(R.id.artistalbum,
1487 getString(R.string.notification_artist_album, artist, album)
1488 );
1489 }
1490 Notification status = new Notification();
1491 status.contentView = views;
1492 status.flags |= Notification.FLAG_ONGOING_EVENT;
1493 status.icon = R.drawable.stat_notify_musicplayer;
1494 status.contentIntent = PendingIntent.getActivity(this, 0,
1495 new Intent("com.android.music.PLAYBACK_VIEWER")
1496 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK), 0);
1497 startForeground(PLAYBACKSERVICE_STATUS, status);
1498 }
1499
The Android Open Source Project792a2202009-03-03 19:32:30 -08001500 private void stop(boolean remove_status_icon) {
Glenn Kastenb1c285c2012-11-07 15:44:01 -08001501 if (mPlayer != null && mPlayer.isInitialized()) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001502 mPlayer.stop();
1503 }
1504 mFileToPlay = null;
1505 if (mCursor != null) {
1506 mCursor.close();
1507 mCursor = null;
1508 }
1509 if (remove_status_icon) {
1510 gotoIdleState();
Dianne Hackbornd5fc5b62009-08-18 11:35:30 -07001511 } else {
1512 stopForeground(false);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001513 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08001514 if (remove_status_icon) {
Marco Nelissenc1333372009-05-06 12:54:47 -07001515 mIsSupposedToBePlaying = false;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001516 }
1517 }
1518
1519 /**
1520 * Stops playback.
1521 */
1522 public void stop() {
1523 stop(true);
1524 }
1525
1526 /**
1527 * Pauses playback (call play() to resume)
1528 */
1529 public void pause() {
Marco Nelissen407cf912009-05-11 09:56:31 -07001530 synchronized(this) {
Marco Nelissen7181da82010-12-01 16:39:04 -08001531 mMediaplayerHandler.removeMessages(FADEUP);
Marco Nelissen407cf912009-05-11 09:56:31 -07001532 if (isPlaying()) {
1533 mPlayer.pause();
1534 gotoIdleState();
Marco Nelissen407cf912009-05-11 09:56:31 -07001535 mIsSupposedToBePlaying = false;
1536 notifyChange(PLAYSTATE_CHANGED);
1537 saveBookmarkIfNeeded();
1538 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08001539 }
1540 }
1541
Marco Nelissenb6e7bf72009-05-11 10:28:31 -07001542 /** Returns whether something is currently playing
The Android Open Source Project792a2202009-03-03 19:32:30 -08001543 *
Marco Nelissenb6e7bf72009-05-11 10:28:31 -07001544 * @return true if something is playing (or will be playing shortly, in case
1545 * we're currently transitioning between tracks), false if not.
The Android Open Source Project792a2202009-03-03 19:32:30 -08001546 */
1547 public boolean isPlaying() {
Marco Nelissenc1333372009-05-06 12:54:47 -07001548 return mIsSupposedToBePlaying;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001549 }
1550
1551 /*
1552 Desired behavior for prev/next/shuffle:
1553
1554 - NEXT will move to the next track in the list when not shuffling, and to
1555 a track randomly picked from the not-yet-played tracks when shuffling.
1556 If all tracks have already been played, pick from the full set, but
1557 avoid picking the previously played track if possible.
1558 - when shuffling, PREV will go to the previously played track. Hitting PREV
1559 again will go to the track played before that, etc. When the start of the
1560 history has been reached, PREV is a no-op.
1561 When not shuffling, PREV will go to the sequentially previous track (the
1562 difference with the shuffle-case is mainly that when not shuffling, the
1563 user can back up to tracks that are not in the history).
1564
1565 Example:
1566 When playing an album with 10 tracks from the start, and enabling shuffle
1567 while playing track 5, the remaining tracks (6-10) will be shuffled, e.g.
1568 the final play order might be 1-2-3-4-5-8-10-6-9-7.
1569 When hitting 'prev' 8 times while playing track 7 in this example, the
1570 user will go to tracks 9-6-10-8-5-4-3-2. If the user then hits 'next',
1571 a random track will be picked again. If at any time user disables shuffling
1572 the next/previous track will be picked in sequential order again.
1573 */
1574
1575 public void prev() {
1576 synchronized (this) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001577 if (mShuffleMode == SHUFFLE_NORMAL) {
1578 // go to previously-played track and remove it from the history
1579 int histsize = mHistory.size();
1580 if (histsize == 0) {
1581 // prev is a no-op
1582 return;
1583 }
1584 Integer pos = mHistory.remove(histsize - 1);
1585 mPlayPos = pos.intValue();
1586 } else {
1587 if (mPlayPos > 0) {
1588 mPlayPos--;
1589 } else {
1590 mPlayPos = mPlayListLen - 1;
1591 }
1592 }
1593 saveBookmarkIfNeeded();
1594 stop(false);
Marco Nelissene41bd182012-03-14 08:24:40 -07001595 openCurrentAndNext();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001596 play();
1597 notifyChange(META_CHANGED);
1598 }
1599 }
1600
Marco Nelissenbd0d2f12012-02-27 16:02:15 -08001601 /**
1602 * Get the next position to play. Note that this may actually modify mPlayPos
1603 * if playback is in SHUFFLE_AUTO mode and the shuffle list window needed to
1604 * be adjusted. Either way, the return value is the next value that should be
1605 * assigned to mPlayPos;
1606 */
1607 private int getNextPosition(boolean force) {
Marco Nelissene41bd182012-03-14 08:24:40 -07001608 if (mRepeatMode == REPEAT_CURRENT) {
1609 if (mPlayPos < 0) return 0;
1610 return mPlayPos;
1611 } else if (mShuffleMode == SHUFFLE_NORMAL) {
Marco Nelissenbd0d2f12012-02-27 16:02:15 -08001612 // Pick random next track from the not-yet-played ones
1613 // TODO: make it work right after adding/removing items in the queue.
1614
1615 // Store the current file in the history, but keep the history at a
1616 // reasonable size
1617 if (mPlayPos >= 0) {
1618 mHistory.add(mPlayPos);
1619 }
1620 if (mHistory.size() > MAX_HISTORY_SIZE) {
1621 mHistory.removeElementAt(0);
1622 }
1623
1624 int numTracks = mPlayListLen;
1625 int[] tracks = new int[numTracks];
1626 for (int i=0;i < numTracks; i++) {
1627 tracks[i] = i;
1628 }
1629
1630 int numHistory = mHistory.size();
1631 int numUnplayed = numTracks;
1632 for (int i=0;i < numHistory; i++) {
1633 int idx = mHistory.get(i).intValue();
1634 if (idx < numTracks && tracks[idx] >= 0) {
1635 numUnplayed--;
1636 tracks[idx] = -1;
1637 }
1638 }
1639
1640 // 'numUnplayed' now indicates how many tracks have not yet
1641 // been played, and 'tracks' contains the indices of those
1642 // tracks.
1643 if (numUnplayed <=0) {
1644 // everything's already been played
1645 if (mRepeatMode == REPEAT_ALL || force) {
1646 //pick from full set
1647 numUnplayed = numTracks;
1648 for (int i=0;i < numTracks; i++) {
1649 tracks[i] = i;
1650 }
1651 } else {
1652 // all done
1653 return -1;
1654 }
1655 }
1656 int skip = mRand.nextInt(numUnplayed);
1657 int cnt = -1;
1658 while (true) {
1659 while (tracks[++cnt] < 0)
1660 ;
1661 skip--;
1662 if (skip < 0) {
1663 break;
1664 }
1665 }
1666 return cnt;
1667 } else if (mShuffleMode == SHUFFLE_AUTO) {
1668 doAutoShuffleUpdate();
1669 return mPlayPos + 1;
1670 } else {
1671 if (mPlayPos >= mPlayListLen - 1) {
1672 // we're at the end of the list
1673 if (mRepeatMode == REPEAT_NONE && !force) {
1674 // all done
1675 return -1;
1676 } else if (mRepeatMode == REPEAT_ALL || force) {
1677 return 0;
1678 }
1679 return -1;
1680 } else {
1681 return mPlayPos + 1;
1682 }
1683 }
1684 }
1685
1686 public void gotoNext(boolean force) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001687 synchronized (this) {
Marco Nelissen663fea32009-06-12 13:39:27 -07001688 if (mPlayListLen <= 0) {
Marco Nelissene99341f2009-11-11 11:13:51 -08001689 Log.d(LOGTAG, "No play queue");
Marco Nelissen663fea32009-06-12 13:39:27 -07001690 return;
1691 }
1692
Marco Nelissenbd0d2f12012-02-27 16:02:15 -08001693 int pos = getNextPosition(force);
1694 if (pos < 0) {
1695 gotoIdleState();
1696 if (mIsSupposedToBePlaying) {
1697 mIsSupposedToBePlaying = false;
1698 notifyChange(PLAYSTATE_CHANGED);
Marco Nelissen3f502de2010-09-28 15:07:29 -07001699 }
Marco Nelissenbd0d2f12012-02-27 16:02:15 -08001700 return;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001701 }
Marco Nelissenbd0d2f12012-02-27 16:02:15 -08001702 mPlayPos = pos;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001703 saveBookmarkIfNeeded();
1704 stop(false);
Marco Nelissene41bd182012-03-14 08:24:40 -07001705 mPlayPos = pos;
1706 openCurrentAndNext();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001707 play();
1708 notifyChange(META_CHANGED);
1709 }
1710 }
1711
1712 private void gotoIdleState() {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001713 mDelayedStopHandler.removeCallbacksAndMessages(null);
1714 Message msg = mDelayedStopHandler.obtainMessage();
1715 mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
Dianne Hackbornd5fc5b62009-08-18 11:35:30 -07001716 stopForeground(true);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001717 }
1718
1719 private void saveBookmarkIfNeeded() {
1720 try {
1721 if (isPodcast()) {
1722 long pos = position();
1723 long bookmark = getBookmark();
1724 long duration = duration();
1725 if ((pos < bookmark && (pos + 10000) > bookmark) ||
1726 (pos > bookmark && (pos - 10000) < bookmark)) {
1727 // The existing bookmark is close to the current
1728 // position, so don't update it.
1729 return;
1730 }
1731 if (pos < 15000 || (pos + 10000) > duration) {
1732 // if we're near the start or end, clear the bookmark
1733 pos = 0;
1734 }
1735
1736 // write 'pos' to the bookmark field
1737 ContentValues values = new ContentValues();
1738 values.put(MediaStore.Audio.Media.BOOKMARK, pos);
1739 Uri uri = ContentUris.withAppendedId(
1740 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, mCursor.getLong(IDCOLIDX));
1741 getContentResolver().update(uri, values, null, null);
1742 }
1743 } catch (SQLiteException ex) {
1744 }
1745 }
1746
1747 // Make sure there are at least 5 items after the currently playing item
1748 // and no more than 10 items before.
1749 private void doAutoShuffleUpdate() {
1750 boolean notify = false;
Marco Nelissen3f502de2010-09-28 15:07:29 -07001751
The Android Open Source Project792a2202009-03-03 19:32:30 -08001752 // remove old entries
1753 if (mPlayPos > 10) {
1754 removeTracks(0, mPlayPos - 9);
1755 notify = true;
1756 }
1757 // add new entries if needed
1758 int to_add = 7 - (mPlayListLen - (mPlayPos < 0 ? -1 : mPlayPos));
1759 for (int i = 0; i < to_add; i++) {
1760 // pick something at random from the list
Marco Nelissen3f502de2010-09-28 15:07:29 -07001761
1762 int lookback = mHistory.size();
1763 int idx = -1;
1764 while(true) {
1765 idx = mRand.nextInt(mAutoShuffleList.length);
1766 if (!wasRecentlyUsed(idx, lookback)) {
1767 break;
1768 }
1769 lookback /= 2;
1770 }
1771 mHistory.add(idx);
1772 if (mHistory.size() > MAX_HISTORY_SIZE) {
1773 mHistory.remove(0);
1774 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08001775 ensurePlayListCapacity(mPlayListLen + 1);
Marco Nelissen3f502de2010-09-28 15:07:29 -07001776 mPlayList[mPlayListLen++] = mAutoShuffleList[idx];
The Android Open Source Project792a2202009-03-03 19:32:30 -08001777 notify = true;
1778 }
1779 if (notify) {
1780 notifyChange(QUEUE_CHANGED);
1781 }
1782 }
1783
Marco Nelissen3f502de2010-09-28 15:07:29 -07001784 // check that the specified idx is not in the history (but only look at at
1785 // most lookbacksize entries in the history)
1786 private boolean wasRecentlyUsed(int idx, int lookbacksize) {
1787
1788 // early exit to prevent infinite loops in case idx == mPlayPos
1789 if (lookbacksize == 0) {
1790 return false;
1791 }
1792
1793 int histsize = mHistory.size();
1794 if (histsize < lookbacksize) {
1795 Log.d(LOGTAG, "lookback too big");
1796 lookbacksize = histsize;
1797 }
1798 int maxidx = histsize - 1;
1799 for (int i = 0; i < lookbacksize; i++) {
1800 long entry = mHistory.get(maxidx - i);
1801 if (entry == idx) {
1802 return true;
1803 }
1804 }
1805 return false;
1806 }
1807
Ayan Ghoshe6f136d2014-07-23 21:03:40 +05301808 private class SetBrowsedPlayerMonitor implements
1809 RemoteControlClient.OnSetBrowsedPlayerListener{
1810 @Override
1811 public void onSetBrowsedPlayer() {
1812 Log.d(LOGTAG, "onSetBrowsedPlayer");
1813 mAvrcpHandler.obtainMessage(SET_BROWSED_PLAYER).sendToTarget();
1814 }
1815 };
1816
1817 private class SetPlayItemMonitor implements
1818 RemoteControlClient.OnSetPlayItemListener{
1819 @Override
1820 public void onSetPlayItem(int scope, long uid) {
1821 Log.d(LOGTAG, "onSetPlayItem");
1822 mAvrcpHandler.obtainMessage(SET_PLAY_ITEM, scope, 0, new Long(uid)).sendToTarget();
1823 }
1824 };
1825
1826 private class GetNowPlayingEntriesMonitor implements
1827 RemoteControlClient.OnGetNowPlayingEntriesListener{
1828 @Override
1829 public void onGetNowPlayingEntries() {
1830 Log.d(LOGTAG, "onGetNowPlayingEntries");
1831 mAvrcpHandler.obtainMessage(GET_NOW_PLAYING_ENTRIES).sendToTarget();
1832 }
1833 };
1834
The Android Open Source Project792a2202009-03-03 19:32:30 -08001835 // A simple variation of Random that makes sure that the
1836 // value it returns is not equal to the value it returned
1837 // previously, unless the interval is 1.
Marco Nelissen756c3f52009-05-14 10:07:23 -07001838 private static class Shuffler {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001839 private int mPrevious;
1840 private Random mRandom = new Random();
1841 public int nextInt(int interval) {
1842 int ret;
1843 do {
1844 ret = mRandom.nextInt(interval);
1845 } while (ret == mPrevious && interval > 1);
1846 mPrevious = ret;
1847 return ret;
1848 }
1849 };
1850
1851 private boolean makeAutoShuffleList() {
1852 ContentResolver res = getContentResolver();
1853 Cursor c = null;
1854 try {
1855 c = res.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
1856 new String[] {MediaStore.Audio.Media._ID}, MediaStore.Audio.Media.IS_MUSIC + "=1",
1857 null, null);
1858 if (c == null || c.getCount() == 0) {
1859 return false;
1860 }
1861 int len = c.getCount();
Marco Nelissenbd447b62009-06-29 14:52:05 -07001862 long [] list = new long[len];
The Android Open Source Project792a2202009-03-03 19:32:30 -08001863 for (int i = 0; i < len; i++) {
1864 c.moveToNext();
Marco Nelissenbd447b62009-06-29 14:52:05 -07001865 list[i] = c.getLong(0);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001866 }
1867 mAutoShuffleList = list;
1868 return true;
1869 } catch (RuntimeException ex) {
1870 } finally {
1871 if (c != null) {
1872 c.close();
1873 }
1874 }
1875 return false;
1876 }
1877
1878 /**
1879 * Removes the range of tracks specified from the play list. If a file within the range is
1880 * the file currently being played, playback will move to the next file after the
1881 * range.
1882 * @param first The first file to be removed
1883 * @param last The last file to be removed
1884 * @return the number of tracks deleted
1885 */
1886 public int removeTracks(int first, int last) {
1887 int numremoved = removeTracksInternal(first, last);
1888 if (numremoved > 0) {
1889 notifyChange(QUEUE_CHANGED);
1890 }
1891 return numremoved;
1892 }
1893
1894 private int removeTracksInternal(int first, int last) {
1895 synchronized (this) {
1896 if (last < first) return 0;
1897 if (first < 0) first = 0;
1898 if (last >= mPlayListLen) last = mPlayListLen - 1;
1899
1900 boolean gotonext = false;
1901 if (first <= mPlayPos && mPlayPos <= last) {
1902 mPlayPos = first;
1903 gotonext = true;
1904 } else if (mPlayPos > last) {
1905 mPlayPos -= (last - first + 1);
1906 }
1907 int num = mPlayListLen - last - 1;
1908 for (int i = 0; i < num; i++) {
1909 mPlayList[first + i] = mPlayList[last + 1 + i];
1910 }
1911 mPlayListLen -= last - first + 1;
1912
1913 if (gotonext) {
1914 if (mPlayListLen == 0) {
1915 stop(true);
1916 mPlayPos = -1;
Marco Nelissen3aa9ad02010-09-16 13:23:11 -07001917 if (mCursor != null) {
1918 mCursor.close();
1919 mCursor = null;
1920 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08001921 } else {
1922 if (mPlayPos >= mPlayListLen) {
1923 mPlayPos = 0;
1924 }
1925 boolean wasPlaying = isPlaying();
1926 stop(false);
Marco Nelissene41bd182012-03-14 08:24:40 -07001927 openCurrentAndNext();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001928 if (wasPlaying) {
1929 play();
1930 }
1931 }
Marco Nelissen3aa9ad02010-09-16 13:23:11 -07001932 notifyChange(META_CHANGED);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001933 }
1934 return last - first + 1;
1935 }
1936 }
1937
1938 /**
1939 * Removes all instances of the track with the given id
1940 * from the playlist.
1941 * @param id The id to be removed
1942 * @return how many instances of the track were removed
1943 */
Marco Nelissenbd447b62009-06-29 14:52:05 -07001944 public int removeTrack(long id) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001945 int numremoved = 0;
1946 synchronized (this) {
1947 for (int i = 0; i < mPlayListLen; i++) {
1948 if (mPlayList[i] == id) {
1949 numremoved += removeTracksInternal(i, i);
1950 i--;
1951 }
1952 }
1953 }
1954 if (numremoved > 0) {
1955 notifyChange(QUEUE_CHANGED);
1956 }
1957 return numremoved;
1958 }
1959
1960 public void setShuffleMode(int shufflemode) {
1961 synchronized(this) {
1962 if (mShuffleMode == shufflemode && mPlayListLen > 0) {
AnubhavGuptaef25f7b2014-12-15 20:05:23 +05301963 /**
1964 * Some carkits send Shuffle Values same as our current
1965 * values. In such cases we need to respond back to ck
1966 */
1967 notifyAttributeValues(PLAYERSETTINGS_RESPONSE,
1968 mAttributePairs, SET_ATTRIBUTE_VALUES);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001969 return;
1970 }
1971 mShuffleMode = shufflemode;
1972 if (mShuffleMode == SHUFFLE_AUTO) {
1973 if (makeAutoShuffleList()) {
1974 mPlayListLen = 0;
1975 doAutoShuffleUpdate();
1976 mPlayPos = 0;
Marco Nelissene41bd182012-03-14 08:24:40 -07001977 openCurrentAndNext();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001978 play();
1979 notifyChange(META_CHANGED);
AnubhavGuptaef25f7b2014-12-15 20:05:23 +05301980 notifyAttributeValues(PLAYERSETTINGS_RESPONSE,
1981 mAttributePairs, SET_ATTRIBUTE_VALUES);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001982 return;
1983 } else {
1984 // failed to build a list of files to shuffle
1985 mShuffleMode = SHUFFLE_NONE;
1986 }
1987 }
AnubhavGuptaef25f7b2014-12-15 20:05:23 +05301988 notifyAttributeValues(PLAYERSETTINGS_RESPONSE,
1989 mAttributePairs, SET_ATTRIBUTE_VALUES);
Ayan Ghoshe6f136d2014-07-23 21:03:40 +05301990 notifyChange(SHUFFLE_CHANGED);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001991 saveQueue(false);
1992 }
1993 }
1994 public int getShuffleMode() {
1995 return mShuffleMode;
1996 }
1997
1998 public void setRepeatMode(int repeatmode) {
1999 synchronized(this) {
2000 mRepeatMode = repeatmode;
Marco Nelissene41bd182012-03-14 08:24:40 -07002001 setNextTrack();
Ayan Ghoshe6f136d2014-07-23 21:03:40 +05302002 notifyAttributeValues(PLAYERSETTINGS_RESPONSE,
2003 mAttributePairs, SET_ATTRIBUTE_VALUES);
2004 notifyChange(REPEAT_CHANGED);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002005 saveQueue(false);
2006 }
2007 }
2008 public int getRepeatMode() {
2009 return mRepeatMode;
2010 }
2011
2012 public int getMediaMountedCount() {
2013 return mMediaMountedCount;
2014 }
2015
2016 /**
2017 * Returns the path of the currently playing file, or null if
2018 * no file is currently playing.
2019 */
2020 public String getPath() {
2021 return mFileToPlay;
2022 }
2023
2024 /**
2025 * Returns the rowid of the currently playing file, or -1 if
2026 * no file is currently playing.
2027 */
Marco Nelissenbd447b62009-06-29 14:52:05 -07002028 public long getAudioId() {
The Android Open Source Project792a2202009-03-03 19:32:30 -08002029 synchronized (this) {
2030 if (mPlayPos >= 0 && mPlayer.isInitialized()) {
2031 return mPlayList[mPlayPos];
2032 }
2033 }
2034 return -1;
2035 }
2036
2037 /**
2038 * Returns the position in the queue
2039 * @return the position in the queue
2040 */
2041 public int getQueuePosition() {
2042 synchronized(this) {
2043 return mPlayPos;
2044 }
2045 }
2046
2047 /**
2048 * Starts playing the track at the given position in the queue.
2049 * @param pos The position in the queue of the track that will be played.
2050 */
2051 public void setQueuePosition(int pos) {
2052 synchronized(this) {
2053 stop(false);
2054 mPlayPos = pos;
Marco Nelissene41bd182012-03-14 08:24:40 -07002055 openCurrentAndNext();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002056 play();
2057 notifyChange(META_CHANGED);
Marco Nelissenec0c57a2009-12-12 12:27:11 -08002058 if (mShuffleMode == SHUFFLE_AUTO) {
2059 doAutoShuffleUpdate();
2060 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08002061 }
2062 }
2063
2064 public String getArtistName() {
2065 synchronized(this) {
2066 if (mCursor == null) {
2067 return null;
2068 }
2069 return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST));
2070 }
2071 }
2072
Marco Nelissenbd447b62009-06-29 14:52:05 -07002073 public long getArtistId() {
The Android Open Source Project792a2202009-03-03 19:32:30 -08002074 synchronized (this) {
2075 if (mCursor == null) {
2076 return -1;
2077 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07002078 return mCursor.getLong(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST_ID));
The Android Open Source Project792a2202009-03-03 19:32:30 -08002079 }
2080 }
2081
2082 public String getAlbumName() {
2083 synchronized (this) {
2084 if (mCursor == null) {
2085 return null;
2086 }
2087 return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM));
2088 }
2089 }
2090
Marco Nelissenbd447b62009-06-29 14:52:05 -07002091 public long getAlbumId() {
The Android Open Source Project792a2202009-03-03 19:32:30 -08002092 synchronized (this) {
2093 if (mCursor == null) {
2094 return -1;
2095 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07002096 return mCursor.getLong(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM_ID));
The Android Open Source Project792a2202009-03-03 19:32:30 -08002097 }
2098 }
2099
2100 public String getTrackName() {
2101 synchronized (this) {
2102 if (mCursor == null) {
2103 return null;
2104 }
2105 return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE));
2106 }
2107 }
2108
2109 private boolean isPodcast() {
2110 synchronized (this) {
2111 if (mCursor == null) {
2112 return false;
2113 }
2114 return (mCursor.getInt(PODCASTCOLIDX) > 0);
2115 }
2116 }
2117
2118 private long getBookmark() {
2119 synchronized (this) {
2120 if (mCursor == null) {
2121 return 0;
2122 }
2123 return mCursor.getLong(BOOKMARKCOLIDX);
2124 }
2125 }
2126
2127 /**
2128 * Returns the duration of the file in milliseconds.
2129 * Currently this method returns -1 for the duration of MIDI files.
2130 */
2131 public long duration() {
2132 if (mPlayer.isInitialized()) {
2133 return mPlayer.duration();
2134 }
2135 return -1;
2136 }
2137
2138 /**
2139 * Returns the current playback position in milliseconds
2140 */
2141 public long position() {
2142 if (mPlayer.isInitialized()) {
2143 return mPlayer.position();
2144 }
2145 return -1;
2146 }
2147
2148 /**
2149 * Seeks to the position specified.
2150 *
2151 * @param pos The position to seek to, in milliseconds
2152 */
2153 public long seek(long pos) {
2154 if (mPlayer.isInitialized()) {
2155 if (pos < 0) pos = 0;
2156 if (pos > mPlayer.duration()) pos = mPlayer.duration();
2157 return mPlayer.seek(pos);
2158 }
2159 return -1;
2160 }
2161
2162 /**
Eric Laurent1cc72a12010-06-28 11:27:01 -07002163 * Sets the audio session ID.
2164 *
2165 * @param sessionId: the audio session ID.
2166 */
2167 public void setAudioSessionId(int sessionId) {
2168 synchronized (this) {
2169 mPlayer.setAudioSessionId(sessionId);
2170 }
2171 }
2172
2173 /**
2174 * Returns the audio session ID.
2175 */
2176 public int getAudioSessionId() {
2177 synchronized (this) {
2178 return mPlayer.getAudioSessionId();
2179 }
2180 }
2181
2182 /**
Ayan Ghoshe6f136d2014-07-23 21:03:40 +05302183 * Returns the player supported attribute IDs.
2184 */
2185 private void notifyAttributeIDs(String what) {
2186 Intent i = new Intent(what);
2187 i.putExtra(EXTRA_GET_RESPONSE, GET_ATTRIBUTE_IDS);
2188 i.putExtra(EXTRA_ATTIBUTE_ID_ARRAY, supportedAttributes);
2189 Log.e(LOGTAG, "notifying attributes");
2190 sendBroadcast(i);
2191 }
2192
2193 /**
2194 * Returns the player supported value IDs for given attrib.
2195 */
2196 private void notifyValueIDs(String what, byte attribute) {
2197 Intent intent = new Intent(what);
2198 intent.putExtra(EXTRA_GET_RESPONSE, GET_VALUE_IDS);
2199 intent.putExtra(EXTRA_ATTRIBUTE_ID, attribute);
2200 switch (attribute) {
2201 case ATTRIBUTE_REPEATMODE:
2202 intent.putExtra(EXTRA_VALUE_ID_ARRAY, supportedRepeatValues);
2203 break;
2204 case ATTRIBUTE_SHUFFLEMODE:
2205 intent.putExtra(EXTRA_VALUE_ID_ARRAY, supportedShuffleValues);
2206 break;
2207 default:
2208 Log.e(LOGTAG,"unsupported attribute"+attribute);
2209 intent.putExtra(EXTRA_VALUE_ID_ARRAY, unsupportedList);
2210 break;
2211 }
2212 sendBroadcast(intent);
2213 }
2214
2215 /**
2216 * Returns the player supported attrib text for given IDs.
2217 */
2218 private void notifyAttributesText(String what, byte [] attrIds) {
2219 String [] AttribStrings = new String [attrIds.length];
2220 Intent intent = new Intent(what);
2221 intent.putExtra(EXTRA_GET_RESPONSE, GET_ATTRIBUTE_TEXT);
2222 for (int i = 0; i < attrIds.length; i++) {
2223 if (attrIds[i] >= AttrStr.length) {
2224 Log.e(LOGTAG, "attrib id is"+attrIds[i]+"which is not supported");
2225 AttribStrings[i] = "";
2226 } else {
2227 AttribStrings[i] = AttrStr[attrIds[i]];
2228 }
2229 }
2230 intent.putExtra(EXTRA_ATTRIBUTE_STRING_ARRAY, AttribStrings);
2231 sendBroadcast(intent);
2232 }
2233
2234 /**
2235 * Returns the player supported value text for given IDs.
2236 */
2237 private void notifyAttributeValuesText(String what, int attribute,
2238 byte [] valIds) {
2239 Intent intent = new Intent(what);
2240 String [] ValueStrings = new String [valIds.length];
2241 intent.putExtra(EXTRA_GET_RESPONSE,GET_VALUE_TEXT);
2242 intent.putExtra(EXTRA_ATTRIBUTE_ID, attribute);
2243 Log.e(LOGTAG, "attrib is "+ attribute);
2244 String [] valueStrs = null;
2245 switch (attribute) {
2246 case ATTRIBUTE_REPEATMODE:
2247 valueStrs = new String[] {
2248 "",
2249 getString(R.string.repeat_off_notif),
2250 getString(R.string.repeat_current_notif),
2251 getString(R.string.repeat_all_notif),
2252 };
2253 break;
2254 case ATTRIBUTE_SHUFFLEMODE:
2255 valueStrs = new String[] {
2256 "",
2257 getString(R.string.shuffle_off_notif),
2258 getString(R.string.shuffle_on_notif),
2259 };
2260 break;
2261 }
2262 for (int i = 0; i < valIds.length; i++) {
2263 if ((valueStrs == null) ||
2264 (valIds[i] >= valueStrs.length)) {
2265 Log.e(LOGTAG, "value id is" + valIds[i] + "which is not supported");
2266 ValueStrings[i] = "";
2267 } else {
2268 ValueStrings[i] = valueStrs[valIds[i]];
2269 }
2270 }
2271 intent.putExtra(EXTRA_VALUE_STRING_ARRAY, ValueStrings);
2272 sendBroadcast(intent);
2273 }
2274
2275 /**
2276 * Returns the player current values for given attrib IDs.
2277 */
2278 private void notifyAttributeValues(String what, HashMap<Byte, Boolean> attrIds, int extra) {
2279 Intent intent = new Intent(what);
2280 intent.putExtra(EXTRA_GET_RESPONSE, extra);
2281 int j = 0;
2282 byte [] retValarray = new byte [attrIds.size()*2];
2283 for (int i = 0; i < attrIds.size()*2; i++) {
2284 retValarray[i] = 0x0;
2285 }
2286
2287 for (Byte attribute : attrIds.keySet()) {
2288 if(attrIds.get(attribute)) {
2289 retValarray[j] = attribute;
2290 if (attribute == ATTRIBUTE_REPEATMODE) {
2291 retValarray[j+1] = getMappingRepeatVal(mRepeatMode);
2292 } else if (attribute == ATTRIBUTE_SHUFFLEMODE) {
2293 retValarray[j+1] = getMappingShuffleVal(mShuffleMode);
2294 }
2295 j += 2;
2296 } else {
2297 retValarray[j] = attribute;
2298 retValarray[j+1] = ERROR_NOTSUPPORTED;
2299 j += 2;
2300 }
2301 }
2302 intent.putExtra(EXTRA_ATTRIB_VALUE_PAIRS, retValarray);
2303 sendBroadcast(intent);
2304 }
2305
2306 /**
2307 * Sets the values to current player for given attrib IDs.
2308 */
2309 private void setValidAttributes(byte [] attribValuePairs) {
2310 byte attrib, value;
2311
2312 for (int i = 0; i < (attribValuePairs.length-1); i += 2) {
2313 attrib = attribValuePairs[i];
2314 value = attribValuePairs[i+1];
2315 switch(attrib) {
2316 case ATTRIBUTE_REPEATMODE:
2317 if (isValidRepeatMode(value)) {
2318 setRepeatMode(getMappingRepeatMode(value));
2319 }
2320 break;
2321 case ATTRIBUTE_SHUFFLEMODE:
2322 if (isValidShuffleMode(value)) {
2323 setShuffleMode(getMappingShuffleMode(value));
2324 }
2325 break;
2326 default:
2327 Log.e(LOGTAG,"Unknown attribute"+attrib);
2328 notifyAttributeValues(PLAYERSETTINGS_RESPONSE,
2329 mAttributePairs, SET_ATTRIBUTE_VALUES);
2330 break;
2331 }
2332 }
2333 }
2334
2335 byte getMappingRepeatVal (int repeatMode) {
2336 switch (repeatMode) {
2337 case REPEAT_NONE:
2338 return VALUE_REPEATMODE_OFF;
2339 case REPEAT_CURRENT:
2340 return VALUE_REPEATMODE_SINGLE;
2341 case REPEAT_ALL:
2342 return VALUE_REPEATMODE_ALL;
2343 default:
2344 return VALUE_REPEATMODE_OFF;
2345 }
2346 }
2347
2348 byte getMappingShuffleVal (int shuffleMode) {
2349 switch (shuffleMode) {
2350 case SHUFFLE_NONE:
2351 return VALUE_SHUFFLEMODE_OFF;
2352 case SHUFFLE_NORMAL:
2353 return VALUE_SHUFFLEMODE_ALL;
2354 case SHUFFLE_AUTO:
2355 return VALUE_SHUFFLEMODE_ALL;
2356 default:
2357 return VALUE_SHUFFLEMODE_OFF;
2358 }
2359 }
2360
2361 int getMappingRepeatMode (byte repeatVal) {
2362 switch (repeatVal) {
2363 case VALUE_REPEATMODE_OFF:
2364 return REPEAT_NONE;
2365 case VALUE_REPEATMODE_SINGLE:
2366 return REPEAT_CURRENT;
2367 case VALUE_REPEATMODE_ALL:
2368 case VALUE_REPEATMODE_GROUP:
2369 return REPEAT_ALL;
2370 default:
2371 return REPEAT_NONE;
2372 }
2373 }
2374
2375 int getMappingShuffleMode (byte shuffleVal) {
2376 switch (shuffleVal) {
2377 case VALUE_SHUFFLEMODE_OFF:
2378 return SHUFFLE_NONE;
2379 case VALUE_SHUFFLEMODE_ALL:
2380 case VALUE_SHUFFLEMODE_GROUP:
2381 return SHUFFLE_NORMAL;
2382 default:
2383 return SHUFFLE_NONE;
2384 }
2385 }
2386
2387 /**
2388 * Validates the value with CMDSET for Repeat mode.
2389 */
2390 private boolean isValidRepeatMode(byte value) {
2391 if (value == 0) {
2392 return false;
2393 }
2394 value--;
2395 if ((value >= REPEAT_NONE) && ( value <= REPEAT_ALL)) {
2396 return true;
2397 }
2398 return false;
2399 }
2400
2401 /**
2402 * Validates the value with CMDSET for Shuffle mode.
2403 */
2404 private boolean isValidShuffleMode(byte value) {
2405 if (value == 0) {
2406 return false;
2407 }
2408 value--;
2409 // check the mapping for local suffle and argument
2410 if ((value >= SHUFFLE_NONE) && ( value <= SHUFFLE_AUTO)) {
2411 return true;
2412 }
2413 return false;
2414 }
2415
2416 /**
The Android Open Source Project792a2202009-03-03 19:32:30 -08002417 * Provides a unified interface for dealing with midi files and
2418 * other media files.
2419 */
2420 private class MultiPlayer {
Marco Nelissen30867022012-03-14 16:04:34 -07002421 private CompatMediaPlayer mCurrentMediaPlayer = new CompatMediaPlayer();
2422 private CompatMediaPlayer mNextMediaPlayer;
The Android Open Source Project792a2202009-03-03 19:32:30 -08002423 private Handler mHandler;
2424 private boolean mIsInitialized = false;
2425
2426 public MultiPlayer() {
Marco Nelissene41bd182012-03-14 08:24:40 -07002427 mCurrentMediaPlayer.setWakeMode(MediaPlaybackService.this, PowerManager.PARTIAL_WAKE_LOCK);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002428 }
2429
The Android Open Source Project792a2202009-03-03 19:32:30 -08002430 public void setDataSource(String path) {
Marco Nelissen90d1f622012-04-05 12:27:56 -07002431 mIsInitialized = setDataSourceImpl(mCurrentMediaPlayer, path);
2432 if (mIsInitialized) {
2433 setNextDataSource(null);
2434 }
Marco Nelissene41bd182012-03-14 08:24:40 -07002435 }
2436
Marco Nelissen90d1f622012-04-05 12:27:56 -07002437 private boolean setDataSourceImpl(MediaPlayer player, String path) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08002438 try {
Marco Nelissene41bd182012-03-14 08:24:40 -07002439 player.reset();
2440 player.setOnPreparedListener(null);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002441 if (path.startsWith("content://")) {
Marco Nelissene41bd182012-03-14 08:24:40 -07002442 player.setDataSource(MediaPlaybackService.this, Uri.parse(path));
The Android Open Source Project792a2202009-03-03 19:32:30 -08002443 } else {
Marco Nelissene41bd182012-03-14 08:24:40 -07002444 player.setDataSource(path);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002445 }
Marco Nelissene41bd182012-03-14 08:24:40 -07002446 player.setAudioStreamType(AudioManager.STREAM_MUSIC);
2447 player.prepare();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002448 } catch (IOException ex) {
2449 // TODO: notify the user why the file couldn't be opened
Marco Nelissen90d1f622012-04-05 12:27:56 -07002450 return false;
The Android Open Source Project792a2202009-03-03 19:32:30 -08002451 } catch (IllegalArgumentException ex) {
2452 // TODO: notify the user why the file couldn't be opened
Marco Nelissen90d1f622012-04-05 12:27:56 -07002453 return false;
The Android Open Source Project792a2202009-03-03 19:32:30 -08002454 }
Marco Nelissene41bd182012-03-14 08:24:40 -07002455 player.setOnCompletionListener(listener);
2456 player.setOnErrorListener(errorListener);
Marco Nelissenf2ef3b52010-09-21 15:47:27 -07002457 Intent i = new Intent(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION);
2458 i.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, getAudioSessionId());
2459 i.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, getPackageName());
2460 sendBroadcast(i);
Marco Nelissen90d1f622012-04-05 12:27:56 -07002461 return true;
The Android Open Source Project792a2202009-03-03 19:32:30 -08002462 }
Marco Nelissene41bd182012-03-14 08:24:40 -07002463
2464 public void setNextDataSource(String path) {
2465 mCurrentMediaPlayer.setNextMediaPlayer(null);
2466 if (mNextMediaPlayer != null) {
2467 mNextMediaPlayer.release();
2468 mNextMediaPlayer = null;
2469 }
2470 if (path == null) {
2471 return;
2472 }
Marco Nelissen30867022012-03-14 16:04:34 -07002473 mNextMediaPlayer = new CompatMediaPlayer();
Marco Nelissene41bd182012-03-14 08:24:40 -07002474 mNextMediaPlayer.setWakeMode(MediaPlaybackService.this, PowerManager.PARTIAL_WAKE_LOCK);
2475 mNextMediaPlayer.setAudioSessionId(getAudioSessionId());
Marco Nelissen90d1f622012-04-05 12:27:56 -07002476 if (setDataSourceImpl(mNextMediaPlayer, path)) {
2477 mCurrentMediaPlayer.setNextMediaPlayer(mNextMediaPlayer);
2478 } else {
2479 // failed to open next, we'll transition the old fashioned way,
2480 // which will skip over the faulty file
2481 mNextMediaPlayer.release();
2482 mNextMediaPlayer = null;
2483 }
Marco Nelissene41bd182012-03-14 08:24:40 -07002484 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08002485
2486 public boolean isInitialized() {
2487 return mIsInitialized;
2488 }
2489
2490 public void start() {
Marco Nelissen39888902010-03-02 10:27:05 -08002491 MusicUtils.debugLog(new Exception("MultiPlayer.start called"));
Marco Nelissene41bd182012-03-14 08:24:40 -07002492 mCurrentMediaPlayer.start();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002493 }
2494
2495 public void stop() {
Marco Nelissene41bd182012-03-14 08:24:40 -07002496 mCurrentMediaPlayer.reset();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002497 mIsInitialized = false;
2498 }
2499
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002500 /**
2501 * You CANNOT use this player anymore after calling release()
2502 */
2503 public void release() {
2504 stop();
Marco Nelissene41bd182012-03-14 08:24:40 -07002505 mCurrentMediaPlayer.release();
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002506 }
2507
The Android Open Source Project792a2202009-03-03 19:32:30 -08002508 public void pause() {
Marco Nelissene41bd182012-03-14 08:24:40 -07002509 mCurrentMediaPlayer.pause();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002510 }
2511
The Android Open Source Project792a2202009-03-03 19:32:30 -08002512 public void setHandler(Handler handler) {
2513 mHandler = handler;
2514 }
2515
2516 MediaPlayer.OnCompletionListener listener = new MediaPlayer.OnCompletionListener() {
2517 public void onCompletion(MediaPlayer mp) {
Marco Nelissene41bd182012-03-14 08:24:40 -07002518 if (mp == mCurrentMediaPlayer && mNextMediaPlayer != null) {
2519 mCurrentMediaPlayer.release();
2520 mCurrentMediaPlayer = mNextMediaPlayer;
2521 mNextMediaPlayer = null;
2522 mHandler.sendEmptyMessage(TRACK_WENT_TO_NEXT);
2523 } else {
2524 // Acquire a temporary wakelock, since when we return from
2525 // this callback the MediaPlayer will release its wakelock
2526 // and allow the device to go to sleep.
2527 // This temporary wakelock is released when the RELEASE_WAKELOCK
2528 // message is processed, but just in case, put a timeout on it.
2529 mWakeLock.acquire(30000);
2530 mHandler.sendEmptyMessage(TRACK_ENDED);
2531 mHandler.sendEmptyMessage(RELEASE_WAKELOCK);
2532 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08002533 }
2534 };
2535
The Android Open Source Project792a2202009-03-03 19:32:30 -08002536 MediaPlayer.OnErrorListener errorListener = new MediaPlayer.OnErrorListener() {
2537 public boolean onError(MediaPlayer mp, int what, int extra) {
2538 switch (what) {
2539 case MediaPlayer.MEDIA_ERROR_SERVER_DIED:
2540 mIsInitialized = false;
Marco Nelissene41bd182012-03-14 08:24:40 -07002541 mCurrentMediaPlayer.release();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002542 // Creating a new MediaPlayer and settings its wakemode does not
2543 // require the media service, so it's OK to do this now, while the
2544 // service is still being restarted
Marco Nelissen30867022012-03-14 16:04:34 -07002545 mCurrentMediaPlayer = new CompatMediaPlayer();
Marco Nelissene41bd182012-03-14 08:24:40 -07002546 mCurrentMediaPlayer.setWakeMode(MediaPlaybackService.this, PowerManager.PARTIAL_WAKE_LOCK);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002547 mHandler.sendMessageDelayed(mHandler.obtainMessage(SERVER_DIED), 2000);
2548 return true;
2549 default:
Marco Nelissene99341f2009-11-11 11:13:51 -08002550 Log.d("MultiPlayer", "Error: " + what + "," + extra);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002551 break;
2552 }
2553 return false;
2554 }
2555 };
2556
2557 public long duration() {
Marco Nelissene41bd182012-03-14 08:24:40 -07002558 return mCurrentMediaPlayer.getDuration();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002559 }
2560
2561 public long position() {
Marco Nelissene41bd182012-03-14 08:24:40 -07002562 return mCurrentMediaPlayer.getCurrentPosition();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002563 }
2564
2565 public long seek(long whereto) {
Marco Nelissene41bd182012-03-14 08:24:40 -07002566 mCurrentMediaPlayer.seekTo((int) whereto);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002567 return whereto;
2568 }
2569
2570 public void setVolume(float vol) {
Marco Nelissene41bd182012-03-14 08:24:40 -07002571 mCurrentMediaPlayer.setVolume(vol, vol);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002572 }
Eric Laurent1cc72a12010-06-28 11:27:01 -07002573
2574 public void setAudioSessionId(int sessionId) {
Marco Nelissene41bd182012-03-14 08:24:40 -07002575 mCurrentMediaPlayer.setAudioSessionId(sessionId);
Eric Laurent1cc72a12010-06-28 11:27:01 -07002576 }
2577
2578 public int getAudioSessionId() {
Marco Nelissene41bd182012-03-14 08:24:40 -07002579 return mCurrentMediaPlayer.getAudioSessionId();
Eric Laurent1cc72a12010-06-28 11:27:01 -07002580 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08002581 }
2582
Marco Nelissen30867022012-03-14 16:04:34 -07002583 static class CompatMediaPlayer extends MediaPlayer implements OnCompletionListener {
2584
2585 private boolean mCompatMode = true;
2586 private MediaPlayer mNextPlayer;
2587 private OnCompletionListener mCompletion;
2588
2589 public CompatMediaPlayer() {
2590 try {
2591 MediaPlayer.class.getMethod("setNextMediaPlayer", MediaPlayer.class);
2592 mCompatMode = false;
2593 } catch (NoSuchMethodException e) {
2594 mCompatMode = true;
2595 super.setOnCompletionListener(this);
2596 }
2597 }
2598
2599 public void setNextMediaPlayer(MediaPlayer next) {
2600 if (mCompatMode) {
2601 mNextPlayer = next;
2602 } else {
2603 super.setNextMediaPlayer(next);
2604 }
2605 }
2606
2607 @Override
2608 public void setOnCompletionListener(OnCompletionListener listener) {
2609 if (mCompatMode) {
2610 mCompletion = listener;
2611 } else {
2612 super.setOnCompletionListener(listener);
2613 }
2614 }
2615
2616 @Override
2617 public void onCompletion(MediaPlayer mp) {
2618 if (mNextPlayer != null) {
2619 // as it turns out, starting a new MediaPlayer on the completion
2620 // of a previous player ends up slightly overlapping the two
2621 // playbacks, so slightly delaying the start of the next player
2622 // gives a better user experience
2623 SystemClock.sleep(50);
2624 mNextPlayer.start();
2625 }
2626 mCompletion.onCompletion(this);
2627 }
2628 }
2629
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002630 /*
2631 * By making this a static class with a WeakReference to the Service, we
2632 * ensure that the Service can be GCd even when the system process still
2633 * has a remote reference to the stub.
2634 */
2635 static class ServiceStub extends IMediaPlaybackService.Stub {
2636 WeakReference<MediaPlaybackService> mService;
2637
2638 ServiceStub(MediaPlaybackService service) {
2639 mService = new WeakReference<MediaPlaybackService>(service);
2640 }
2641
Marco Nelissen8d08ec22010-05-10 14:05:24 -07002642 public void openFile(String path)
The Android Open Source Project792a2202009-03-03 19:32:30 -08002643 {
Marco Nelissen8d08ec22010-05-10 14:05:24 -07002644 mService.get().open(path);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002645 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07002646 public void open(long [] list, int position) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002647 mService.get().open(list, position);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002648 }
2649 public int getQueuePosition() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002650 return mService.get().getQueuePosition();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002651 }
2652 public void setQueuePosition(int index) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002653 mService.get().setQueuePosition(index);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002654 }
2655 public boolean isPlaying() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002656 return mService.get().isPlaying();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002657 }
2658 public void stop() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002659 mService.get().stop();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002660 }
2661 public void pause() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002662 mService.get().pause();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002663 }
2664 public void play() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002665 mService.get().play();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002666 }
2667 public void prev() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002668 mService.get().prev();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002669 }
2670 public void next() {
Marco Nelissenbd0d2f12012-02-27 16:02:15 -08002671 mService.get().gotoNext(true);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002672 }
2673 public String getTrackName() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002674 return mService.get().getTrackName();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002675 }
2676 public String getAlbumName() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002677 return mService.get().getAlbumName();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002678 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07002679 public long getAlbumId() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002680 return mService.get().getAlbumId();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002681 }
2682 public String getArtistName() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002683 return mService.get().getArtistName();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002684 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07002685 public long getArtistId() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002686 return mService.get().getArtistId();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002687 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07002688 public void enqueue(long [] list , int action) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002689 mService.get().enqueue(list, action);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002690 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07002691 public long [] getQueue() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002692 return mService.get().getQueue();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002693 }
2694 public void moveQueueItem(int from, int to) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002695 mService.get().moveQueueItem(from, to);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002696 }
2697 public String getPath() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002698 return mService.get().getPath();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002699 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07002700 public long getAudioId() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002701 return mService.get().getAudioId();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002702 }
2703 public long position() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002704 return mService.get().position();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002705 }
2706 public long duration() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002707 return mService.get().duration();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002708 }
2709 public long seek(long pos) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002710 return mService.get().seek(pos);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002711 }
2712 public void setShuffleMode(int shufflemode) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002713 mService.get().setShuffleMode(shufflemode);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002714 }
2715 public int getShuffleMode() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002716 return mService.get().getShuffleMode();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002717 }
2718 public int removeTracks(int first, int last) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002719 return mService.get().removeTracks(first, last);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002720 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07002721 public int removeTrack(long id) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002722 return mService.get().removeTrack(id);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002723 }
2724 public void setRepeatMode(int repeatmode) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002725 mService.get().setRepeatMode(repeatmode);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002726 }
2727 public int getRepeatMode() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002728 return mService.get().getRepeatMode();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002729 }
2730 public int getMediaMountedCount() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002731 return mService.get().getMediaMountedCount();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002732 }
Eric Laurent1cc72a12010-06-28 11:27:01 -07002733 public int getAudioSessionId() {
2734 return mService.get().getAudioSessionId();
2735 }
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002736 }
Marco Nelissen39888902010-03-02 10:27:05 -08002737
2738 @Override
2739 protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
Marco Nelissenbf555ce2010-03-02 16:55:25 -08002740 writer.println("" + mPlayListLen + " items in queue, currently at index " + mPlayPos);
Marco Nelissen39888902010-03-02 10:27:05 -08002741 writer.println("Currently loaded:");
2742 writer.println(getArtistName());
2743 writer.println(getAlbumName());
2744 writer.println(getTrackName());
2745 writer.println(getPath());
2746 writer.println("playing: " + mIsSupposedToBePlaying);
Marco Nelissene41bd182012-03-14 08:24:40 -07002747 writer.println("actual: " + mPlayer.mCurrentMediaPlayer.isPlaying());
Marco Nelissenbf555ce2010-03-02 16:55:25 -08002748 writer.println("shuffle mode: " + mShuffleMode);
Marco Nelissen39888902010-03-02 10:27:05 -08002749 MusicUtils.debugDump(writer);
2750 }
2751
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002752 private final IBinder mBinder = new ServiceStub(this);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002753}