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 + -
显示快捷键?