mediaplaybackservice.java
来自「Android平台上的media player, iPhone风格」· Java 代码 · 共 1,619 行 · 第 1/4 页
JAVA
1,619 行
/* * Copyright (C) 2007 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */package com.android.imusic;import android.app.Notification;import android.app.NotificationManager;import android.app.PendingIntent;import android.app.Service;import android.content.ContentResolver;import android.content.Context;import android.content.Intent;import android.content.IntentFilter;import android.content.BroadcastReceiver;import android.content.SharedPreferences;import android.content.SharedPreferences.Editor;import android.database.Cursor;import android.media.AudioManager;import android.media.MediaFile;import android.media.MediaPlayer;import android.net.Uri;import android.os.Environment;import android.os.FileUtils;import android.os.Handler;import android.os.IBinder;import android.os.Message;import android.os.PowerManager;import android.os.SystemClock;import android.os.PowerManager.WakeLock;import android.provider.MediaStore;import android.provider.Settings;//import com.android.internal.telephony.Phone;//import com.android.internal.telephony.PhoneStateIntentReceiver;import android.util.Log;import android.widget.RemoteViews;import android.widget.Toast;import java.io.IOException;import java.util.Random;import java.util.Vector;/** * Provides "background" audio playback capabilities, allowing the * user to switch between activities without stopping playback. */public class MediaPlaybackService extends Service { /** used to specify whether enqueue() should start playing * the new list of files right away, next or once all the currently * queued files have been played */ public static final int NOW = 1; public static final int NEXT = 2; public static final int LAST = 3; public static final int PLAYBACKSERVICE_STATUS = 1; public static final int SHUFFLE_NONE = 0; public static final int SHUFFLE_NORMAL = 1; public static final int SHUFFLE_AUTO = 2; public static final int REPEAT_NONE = 0; public static final int REPEAT_CURRENT = 1; public static final int REPEAT_ALL = 2; public static final String PLAYSTATE_CHANGED = "com.android.imusic.playstatechanged"; public static final String META_CHANGED = "com.android.imusic.metachanged"; public static final String QUEUE_CHANGED = "com.android.imusic.queuechanged"; public static final String PLAYBACK_COMPLETE = "com.android.imusic.playbackcomplete"; public static final String ASYNC_OPEN_COMPLETE = "com.android.imusic.asyncopencomplete"; public static final String SERVICECMD = "com.android.imusic.musicservicecommand"; public static final String CMDNAME = "command"; public static final String CMDTOGGLEPAUSE = "togglepause"; public static final String CMDPAUSE = "pause"; public static final String CMDNEXT = "next"; private static final int PHONE_CHANGED = 1; private static final int TRACK_ENDED = 1; private static final int RELEASE_WAKELOCK = 2; private static final int SERVER_DIED = 3; private static final int MAX_HISTORY_SIZE = 10; private MultiPlayer mPlayer; private String mFileToPlay;// private PhoneStateIntentReceiver mPsir; private int mShuffleMode = SHUFFLE_NONE; private int mRepeatMode = REPEAT_NONE; private int mMediaMountedCount = 0; private int [] mAutoShuffleList = null; private boolean mOneShot; private int [] mPlayList = null; private int mPlayListLen = 0; private Vector<Integer> mHistory = new Vector<Integer>(MAX_HISTORY_SIZE); private Cursor mCursor; private int mPlayPos = -1; private static final String LOGTAG = "MediaPlaybackService"; private final Shuffler mRand = new Shuffler(); private int mOpenFailedCounter = 0; String[] mCursorCols = new String[] { "audio._id AS _id", MediaStore.Audio.Media.ARTIST, MediaStore.Audio.Media.ALBUM, MediaStore.Audio.Media.TITLE, MediaStore.Audio.Media.DATA, MediaStore.Audio.Media.MIME_TYPE, MediaStore.Audio.Media.ALBUM_ID, MediaStore.Audio.Media.ARTIST_ID }; private BroadcastReceiver mUnmountReceiver = null; private WakeLock mWakeLock; private int mServiceStartId = -1; private boolean mServiceInUse = false; private boolean mResumeAfterCall = false; private boolean mWasPlaying = false; private SharedPreferences mPreferences; // We use this to distinguish between different cards when saving/restoring playlists. // This will have to change if we want to support multiple simultaneous cards. private int mCardId; // interval after which we stop the service when idle private static final int IDLE_DELAY = 60000; private Handler mPhoneHandler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { case PHONE_CHANGED://// Phone.State state = mPsir.getPhoneState();//// if (state == Phone.State.RINGING) {// AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);// int ringvolume = audioManager.getStreamVolume(AudioManager.STREAM_RING);// if (ringvolume > 0) {// mResumeAfterCall = (isPlaying() || mResumeAfterCall) && (getAudioId() >= 0);// pause();// }// } else if (state == Phone.State.OFFHOOK) {// // pause the music while a conversation is in progress// mResumeAfterCall = (isPlaying() || mResumeAfterCall) && (getAudioId() >= 0);// pause();// } else if (state == Phone.State.IDLE) {// // start playing again// if (mResumeAfterCall) {// // resume playback only if music was playing// // when the call was answered// play();// mResumeAfterCall = false;// }// } break; default: break; } } }; private Handler mMediaplayerHandler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { case SERVER_DIED: if (mWasPlaying) { next(true); } else { // the server died when we were idle, so just // reopen the same song (it will start again // from the beginning though when the user // restarts) openCurrent(); } break; case TRACK_ENDED: if (mRepeatMode == REPEAT_CURRENT) { seek(0); play(); } else if (!mOneShot) { next(false); } else { notifyChange(PLAYBACK_COMPLETE); } break; case RELEASE_WAKELOCK: mWakeLock.release(); break; default: break; } } }; private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String cmd = intent.getStringExtra("command"); if (CMDNEXT.equals(cmd)) { next(true); } else if (CMDTOGGLEPAUSE.equals(cmd)) { if (isPlaying()) { pause(); } else { play(); } } else if (CMDPAUSE.equals(cmd)) { pause(); } } }; public MediaPlaybackService() {// mPsir = new PhoneStateIntentReceiver(this, mPhoneHandler);// mPsir.notifyPhoneCallState(PHONE_CHANGED); } @Override public void onCreate() { super.onCreate(); mPreferences = getSharedPreferences("Music", MODE_WORLD_READABLE | MODE_WORLD_WRITEABLE); mCardId = FileUtils.getFatVolumeId(Environment.getExternalStorageDirectory().getPath()); registerExternalStorageListener(); // Needs to be done in this thread, since otherwise ApplicationContext.getPowerManager() crashes. mPlayer = new MultiPlayer(); mPlayer.setHandler(mMediaplayerHandler); // Clear leftover notification in case this service previously got killed while playing NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); nm.cancel(PLAYBACKSERVICE_STATUS); reloadQueue(); registerReceiver(mIntentReceiver, new IntentFilter(SERVICECMD));// mPsir.registerIntent(); PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE); mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, this.getClass().getName()); mWakeLock.setReferenceCounted(false); } @Override public void onDestroy() { unregisterReceiver(mIntentReceiver); if (mUnmountReceiver != null) { unregisterReceiver(mUnmountReceiver); mUnmountReceiver = null; }// mPsir.unregisterIntent(); mWakeLock.release(); super.onDestroy(); } private final char hexdigits [] = new char [] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; private void saveQueue(boolean full) { if (mOneShot) { return; } Editor ed = mPreferences.edit(); //long start = System.currentTimeMillis(); if (full) { StringBuilder q = new StringBuilder(); // The current playlist is saved as a list of "reverse hexadecimal" // numbers, which we can generate faster than normal decimal or // hexadecimal numbers, which in turn allows us to save the playlist // more often without worrying too much about performance. // (saving the full state takes about 40 ms under no-load conditions // on the phone) int len = mPlayListLen; for (int i = 0; i < len; i++) { int n = mPlayList[i]; if (n == 0) { q.append("0;"); } else { while (n != 0) { int digit = n & 0xf; n >>= 4; q.append(hexdigits[digit]); } q.append(";"); } } //Log.i("@@@@ service", "created queue string in " + (System.currentTimeMillis() - start) + " ms"); ed.putString("queue", q.toString()); ed.putInt("cardid", mCardId); } ed.putInt("curpos", mPlayPos); if (mPlayer.isInitialized()) { ed.putLong("seekpos", mPlayer.position()); } ed.putInt("repeatmode", mRepeatMode); ed.putInt("shufflemode", mShuffleMode); ed.commit(); //Log.i("@@@@ service", "saved state in " + (System.currentTimeMillis() - start) + " ms"); } private void reloadQueue() { String q = null; boolean newstyle = false; int id = mCardId; if (mPreferences.contains("cardid")) { newstyle = true; id = mPreferences.getInt("cardid", ~mCardId); } if (id == mCardId) { // Only restore the saved playlist if the card is still // the same one as when the playlist was saved q = mPreferences.getString("queue", ""); } if (q != null && q.length() > 1) { //Log.i("@@@@ service", "loaded queue: " + q); String [] entries = q.split(";"); int len = entries.length; ensurePlayListCapacity(len); for (int i = 0; i < len; i++) { if (newstyle) { String revhex = entries[i]; int n = 0; for (int j = revhex.length() - 1; j >= 0 ; j--) { n <<= 4; char c = revhex.charAt(j); if (c >= '0' && c <= '9') { n += (c - '0'); } else if (c >= 'a' && c <= 'f') { n += (10 + c - 'a'); } else { // bogus playlist data len = 0; break; } } mPlayList[i] = n; } else { mPlayList[i] = Integer.parseInt(entries[i]); } } mPlayListLen = len; int pos = mPreferences.getInt("curpos", 0); if (pos < 0 || pos >= len) { // The saved playlist is bogus, discard it mPlayListLen = 0; return; } mPlayPos = pos; // When reloadQueue is called in response to a card-insertion, // we might not be able to query the media provider right away. // To deal with this, try querying for the current file, and if // that fails, wait a while and try again. If that too fails, // assume there is a problem and don't restore the state. Cursor c = MusicUtils.query(this, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, new String [] {"_id"}, "_id=" + mPlayList[mPlayPos] , null, null); if (c == null || c.getCount() == 0) { // wait a bit and try again SystemClock.sleep(3000); c = getContentResolver().query( MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, mCursorCols, "_id=" + mPlayList[mPlayPos] , null, null); } if (c != null) { c.close(); } // Make sure we don't auto-skip to the next song, since that // also starts playback. What could happen in that case is: // - music is paused // - go to UMS and delete some files, including the currently playing one // - come back from UMS // (time passes) // - music app is killed for some reason (out of memory) // - music service is restarted, service restores state, doesn't find // the "current" file, goes to the next and: playback starts on its // own, potentially at some random inconvenient time. mOpenFailedCounter = 20; openCurrent(); if (!mPlayer.isInitialized()) { // couldn't restore the saved state mPlayListLen = 0; return; } long seekpos = mPreferences.getLong("seekpos", 0); seek(seekpos >= 0 && seekpos < duration() ? seekpos : 0);
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?