Java tutorial
/* * Copyright (C) 2011 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.nd.android.u.square.service; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; import android.content.*; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.media.AudioManager; import android.media.MediaMetadataRetriever; import android.media.MediaPlayer; import android.media.MediaPlayer.OnCompletionListener; import android.media.MediaPlayer.OnErrorListener; import android.media.MediaPlayer.OnPreparedListener; import android.media.RemoteControlClient; import android.net.Uri; import android.net.wifi.WifiManager; import android.net.wifi.WifiManager.WifiLock; import android.os.IBinder; import android.os.PowerManager; import android.support.v4.app.NotificationCompat; import android.telephony.PhoneStateListener; import android.telephony.TelephonyManager; import android.widget.RemoteViews; import com.nd.android.u.filestoragesystem.externalInterface.FileUtilFactory; import com.nd.android.u.square.R; import com.nd.android.u.square.business.backgroundRable.MusicExecutor; import com.nd.android.u.square.dataStructure.MusicList; import com.nd.android.u.square.dataStructure.UserInfo; import com.nd.android.u.square.ui.activity.SquareActivity; import android.util.Log; import java.io.IOException; import java.io.Serializable; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; /** * Service that handles media playback. This is the Service through which we perform all the media * handling in our application. Upon initialization, it waits for Intents (which come from our main activity, * {@link com.nd.android.u.square.ui.activity.SquareActivity}, which signal the service to perform specific operations: * Play, Pause, Rewind, Skip, etc. */ public class MusicPlaybackService extends Service implements OnCompletionListener, OnPreparedListener, OnErrorListener, MusicFocusable, MediaPlayer.OnBufferingUpdateListener { // The tag we put on debug messages final static String TAG = "MusicPlaybackService"; // These are the Intent actions that we are prepared to handle. Notice that the fact these // constants exist in our class is a mere convenience: what really defines the actions our // service can handle are the <action> tags in the <intent-filters> tag for our service in // AndroidManifest.xml. public static final String ACTION_TOGGLE_PLAYBACK = MusicPlaybackService.class.getName() + ".TOGGLE_PLAYBACK"; public static final String ACTION_PLAY = MusicPlaybackService.class.getName() + ".PLAY"; public static final String ACTION_PAUSE = MusicPlaybackService.class.getName() + ".PAUSE"; public static final String ACTION_STOP = MusicPlaybackService.class.getName() + ".STOP"; public static final String ACTION_SKIP = MusicPlaybackService.class.getName() + ".SKIP"; // public static final String ACTION_REWIND = MusicPlaybackService.class.getName() + ".REWIND"; public static final String ACTION_URL = MusicPlaybackService.class.getName() + ".URL"; public static final String INTENT_EXTRA_PLAY_ITEM = MusicPlaybackService.class.getName() + ".INTENT_EXTRA_PLAY_ITEM"; static OnStateChangedListener sOnStateChangedListener; static MusicPlaybackService sInstance; /** * ???????????null * * @param listener */ public static void setOnStateChangedListener(OnStateChangedListener listener) { sOnStateChangedListener = listener; } /** * ??onCreate * * @return ??null */ public static MusicPlaybackService getInstance() { return sInstance; } // The volume we set the media player to when we lose audio focus, but are allowed to reduce // the volume instead of stopping playback. public static final float DUCK_VOLUME = 0.1f; // our media player MediaPlayer mPlayer = null; // our AudioFocusHelper object, if it's available (it's available on SDK level >= 8) // If not available, this will be null. Always check for null before using! AudioFocusHelper mAudioFocusHelper = null; // indicates the state our service: public enum State { Stopped, // media player is stopped and not prepared to play Preparing, // media player is preparing... Playing, // playback active (media player ready!). (but the media player may actually be // paused in this state if we don't have audio focus. But we stay in this state // so that we know we have to resume playback once we get focus back) Paused // playback paused (media player ready!) } State mState = State.Stopped; // if in Retrieving mode, this flag indicates whether we should start playing immediately // when we are ready or not. boolean mStartPlayingAfterRetrieve = false; // if mStartPlayingAfterRetrieve is true, this variable indicates the URL that we should // start playing when we are ready. If null, we should play a random song from the device Uri mWhatToPlayAfterRetrieve = null; enum PauseReason { UserRequest, // paused by user request FocusLoss, // paused because of audio focus loss PhoneCall, // paused because of phone state is not idle } // why did we pause? (only relevant if mState == State.Paused) PauseReason mPauseReason = PauseReason.UserRequest; // do we have audio focus? enum AudioFocus { NoFocusNoDuck, // we don't have audio focus, and can't duck NoFocusCanDuck, // we don't have focus, but can play at a low volume ("ducking") Focused // we have full audio focus } AudioFocus mAudioFocus = AudioFocus.NoFocusNoDuck; // title of the song we are currently playing String mSongTitle = ""; // whether the song we are playing is streaming from the network boolean mIsStreaming = false; // Wifi lock that we hold when streaming files from the internet, in order to prevent the // device from shutting off the Wifi radio WifiLock mWifiLock; // The ID we use for the notification (the onscreen alert that appears at the notification // area at the top of the screen as an icon -- and as text as well if the user expands the // notification area). final int NOTIFICATION_ID = 1; // our RemoteControlClient object, which will use remote control APIs available in // SDK level >= 14, if they're available. RemoteControlClientCompat mRemoteControlClientCompat; // Dummy album art we will pass to the remote control (if the APIs are available). Bitmap mDummyAlbumArt; // The component name of MusicIntentReceiver, for use with media button and remote control // APIs ComponentName mMediaButtonReceiverComponent; AudioManager mAudioManager; NotificationManager mNotificationManager; Notification mNotification = null; Item mPlayingItem; NotificationIntentBroadcastReceiver mNotificationIntentActionReceiver = new NotificationIntentBroadcastReceiver(); /** * Makes sure the media player exists and has been reset. This will create the media player * if needed, or reset the existing media player if one already exists. */ void createMediaPlayerIfNeeded() { if (mPlayer == null) { mPlayer = new MediaPlayer(); // Make sure the media player will acquire a wake-lock while playing. If we don't do // that, the CPU might go to sleep while the song is playing, causing playback to stop. // // Remember that to use this, we have to declare the android.permission.WAKE_LOCK // permission in AndroidManifest.xml. mPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK); // we want the media player to notify us when it's ready preparing, and when it's done // playing: mPlayer.setOnPreparedListener(this); mPlayer.setOnCompletionListener(this); mPlayer.setOnErrorListener(this); mPlayer.setOnBufferingUpdateListener(this); } else mPlayer.reset(); } @Override public void onCreate() { Log.i(TAG, "debug: Creating service"); sInstance = this; // Create the Wifi lock (this does not acquire the lock, this just creates it) mWifiLock = ((WifiManager) getSystemService(Context.WIFI_SERVICE)) .createWifiLock(WifiManager.WIFI_MODE_FULL, "mylock"); mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); mAudioManager = (AudioManager) getSystemService(AUDIO_SERVICE); // create the Audio Focus Helper, if the Audio Focus feature is available (SDK 8 or above) if (android.os.Build.VERSION.SDK_INT >= 8) mAudioFocusHelper = new AudioFocusHelper(getApplicationContext(), this); else mAudioFocus = AudioFocus.Focused; // no focus feature, so we always "have" audio focus mDummyAlbumArt = BitmapFactory.decodeResource(getResources(), R.drawable.ic_square_notification_music_play); mMediaButtonReceiverComponent = new ComponentName(this, MusicIntentReceiver.class); MusicExecutor.getInstance().getEventBus().register(this); // ? MusicExecutor.getInstance().schedulePlayListUpdate(0L, 10L, TimeUnit.SECONDS); IntentFilter notificationIntentFilter = new IntentFilter(); notificationIntentFilter.addAction(ACTION_STOP); registerReceiver(mNotificationIntentActionReceiver, notificationIntentFilter); } /** * Called when we receive an Intent. When we receive an intent sent to us via startService(), * this is the method that gets called. So here we react appropriately depending on the * Intent's action, which specifies what is being requested of us. */ @Override public int onStartCommand(Intent intent, int flags, int startId) { if (null != intent) { String action = intent.getAction(); if (null != action && action.length() > 0) { Log.d(TAG, "action = " + action); if (action.equals(ACTION_TOGGLE_PLAYBACK)) processTogglePlaybackRequest(); else if (action.equals(ACTION_PLAY)) processPlayRequest(); else if (action.equals(ACTION_PAUSE)) processPauseRequest(); else if (action.equals(ACTION_SKIP)) processSkipRequest(); else if (action.equals(ACTION_STOP)) processStopRequest(); // else if (action.equals(ACTION_REWIND)) processRewindRequest(); // else if (action.equals(ACTION_URL)) processAddRequest(intent); } } // MusicPlaybackService?SquareActivity?START_STICKY return START_STICKY; } public void onEventMainThread(MusicList.MusicListeningInfo info) { Log.d(TAG, "receive play list updated event, first song is: " + info.mMusicInfo.mSongName); if (null != sOnStateChangedListener) sOnStateChangedListener.onPlayListAvailable(createPlayingItem(info)); } /**???*/ public State getPlayerState() { return mState; } /**??*/ public Item getPlayingItem() { return mPlayingItem; } void processTogglePlaybackRequest() { if (mState == State.Paused || mState == State.Stopped) { processPlayRequest(); } else { processPauseRequest(); } } void processPlayRequest() { if (MusicExecutor.getInstance().isPlayListEmpty()) { if (null != sOnStateChangedListener) sOnStateChangedListener.onPlayListIsEmpty(); return; } tryToGetAudioFocus(); // actually play the song if (mState == State.Stopped) { // If we're stopped, just go ahead to the next song and start playing playNextSong(mPlayingItem); } else if (mState == State.Paused) { // If we're paused, just continue playback and restore the 'foreground service' state. mState = State.Playing; setUpAsForeground(mSongTitle + ""); configAndStartMediaPlayer(); } // Tell any remote controls that our playback state is 'playing'. if (mRemoteControlClientCompat != null) { mRemoteControlClientCompat.setPlaybackState(RemoteControlClient.PLAYSTATE_PLAYING); } } void processPauseRequest() { if (mState == State.Playing) { // Pause media player and cancel the 'foreground service' state. mState = State.Paused; mPlayer.pause(); relaxResources(false); // while paused, we always retain the MediaPlayer // do not give up audio focus if (null != sOnStateChangedListener) sOnStateChangedListener.onPlayerPaused(); } else if (mState == State.Preparing) { // ??MediaPlayer???MediaPlayer // Error?? processStopRequest(true); } // Tell any remote controls that our playback state is 'paused'. if (mRemoteControlClientCompat != null) { mRemoteControlClientCompat.setPlaybackState(RemoteControlClient.PLAYSTATE_PAUSED); } } // void processRewindRequest() { // if (mState == State.Playing || mState == State.Paused) // mPlayer.seekTo(0); // } void processSkipRequest() { // ??? if (null != mPlayingItem) mPlayingItem.lastPosition = 0; if (mState == State.Playing || mState == State.Preparing) { tryToGetAudioFocus(); playNextSong(null); } else { if (mState == State.Paused) { mState = State.Stopped; relaxResources(false); } mPlayingItem = pollNextSong(mPlayingItem); if (null == mPlayingItem) { if (sOnStateChangedListener != null) sOnStateChangedListener.onPlayListIsEmpty(); } else { if (null != sOnStateChangedListener) sOnStateChangedListener.onCurrentPlayingItemChanged(mPlayingItem); } } } void processStopRequest() { processStopRequest(false); } void processStopRequest(boolean force) { if (mState == State.Playing || mState == State.Paused || force) { mState = State.Stopped; // let go of all resources... relaxResources(true); giveUpAudioFocus(); if (null != sOnStateChangedListener) sOnStateChangedListener.onPlayerStopped(this); // Tell any remote controls that our playback state is 'paused'. if (mRemoteControlClientCompat != null) { mRemoteControlClientCompat.setPlaybackState(RemoteControlClient.PLAYSTATE_STOPPED); } // MusicPlaybackService?SquareActivity?????? // stopSelf(); } } /** * Releases resources used by the service for playback. This includes the "foreground service" * status and notification, the wake locks and possibly the MediaPlayer. * * @param releaseMediaPlayer Indicates whether the Media Player should also be released or not */ void relaxResources(boolean releaseMediaPlayer) { // stop being a foreground service stopForeground(true); // stop and release the Media Player, if it's available if (releaseMediaPlayer && mPlayer != null) { mPlayer.reset(); mPlayer.release(); mPlayer = null; } // we can also release the Wifi lock, if we're holding it if (mWifiLock.isHeld()) mWifiLock.release(); } void giveUpAudioFocus() { if (mAudioFocus == AudioFocus.Focused && mAudioFocusHelper != null && mAudioFocusHelper.abandonFocus()) mAudioFocus = AudioFocus.NoFocusNoDuck; } /** * Reconfigures MediaPlayer according to audio focus settings and starts/restarts it. This * method starts/restarts the MediaPlayer respecting the current audio focus state. So if * we have focus, it will play normally; if we don't have focus, it will either leave the * MediaPlayer paused or set it to a low volume, depending on what is allowed by the * current focus settings. This method assumes mPlayer != null, so if you are calling it, * you have to do so from a context where you are sure this is the case. */ void configAndStartMediaPlayer() { if (mAudioFocus == AudioFocus.NoFocusNoDuck) { // If we don't have audio focus and can't duck, we have to pause, even if mState // is State.Playing. But we stay in the Playing state so that we know we have to resume // playback once we get the focus back. if (mPlayer.isPlaying()) { mPlayer.pause(); if (null != sOnStateChangedListener) sOnStateChangedListener.onPlayerPaused(); } return; } else if (mAudioFocus == AudioFocus.NoFocusCanDuck) mPlayer.setVolume(DUCK_VOLUME, DUCK_VOLUME); // we'll be relatively quiet else mPlayer.setVolume(1.0f, 1.0f); // we can be loud if (!mPlayer.isPlaying()) { mPlayer.start(); if (null != sOnStateChangedListener) sOnStateChangedListener.onPlayerPlaying(); } } // void processAddRequest(Intent intent) { // if (mState == State.Playing || mState == State.Paused || mState == State.Stopped) { // tryToGetAudioFocus(); // playNextSong(intent.getData().toString()); // } // } void tryToGetAudioFocus() { if (mAudioFocus != AudioFocus.Focused && mAudioFocusHelper != null && mAudioFocusHelper.requestFocus()) mAudioFocus = AudioFocus.Focused; } Item pollNextSong(Item current) { if (MusicExecutor.getInstance().isPlayListEmpty()) { return null; } MusicList.MusicListeningInfo info = MusicExecutor.getInstance().pollMusic(); if (null != current && info.mMusicInfo.mSongFid == current.id) { info = MusicExecutor.getInstance().pollMusic(); } return createPlayingItem(info); } /** * Starts playing the next song. If manualUrl is null, the next song will be randomly selected * from our Media Retriever (that is, it will be a random song in the user's device). If * manualUrl is non-null, then it specifies the URL or path to the song that will be played * next. */ void playNextSong(Item playingItem) { mState = State.Stopped; relaxResources(false); // release everything except MediaPlayer try { if (null == playingItem) { playingItem = pollNextSong(mPlayingItem); if (null == playingItem) { if (sOnStateChangedListener != null) { sOnStateChangedListener.onPlayListIsEmpty(); } return; } mPlayingItem = playingItem; if (null != sOnStateChangedListener) sOnStateChangedListener.onCurrentPlayingItemChanged(mPlayingItem); } // set the source of the media player a a content URI createMediaPlayerIfNeeded(); mPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); mPlayer.setDataSource(playingItem.getUrl()); mIsStreaming = true; mSongTitle = playingItem.title; mState = State.Preparing; setUpAsForeground(mSongTitle + ""); // Use the media button APIs (if available) to register ourselves for media button // events MediaButtonHelper.registerMediaButtonEventReceiverCompat(mAudioManager, mMediaButtonReceiverComponent); // Use the remote control APIs (if available) to set the playback state if (mRemoteControlClientCompat == null) { Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON); intent.setComponent(mMediaButtonReceiverComponent); mRemoteControlClientCompat = new RemoteControlClientCompat(PendingIntent .getBroadcast(this /*context*/, 0 /*requestCode, ignored*/, intent /*intent*/, 0 /*flags*/ )); RemoteControlHelper.registerRemoteControlClient(mAudioManager, mRemoteControlClientCompat); } mRemoteControlClientCompat.setPlaybackState(RemoteControlClient.PLAYSTATE_PLAYING); mRemoteControlClientCompat.setTransportControlFlags( RemoteControlClient.FLAG_KEY_MEDIA_PLAY | RemoteControlClient.FLAG_KEY_MEDIA_PAUSE | RemoteControlClient.FLAG_KEY_MEDIA_NEXT | RemoteControlClient.FLAG_KEY_MEDIA_STOP); // Update the remote controls mRemoteControlClientCompat.editMetadata(true) .putString(MediaMetadataRetriever.METADATA_KEY_TITLE, playingItem.title) .putLong(MediaMetadataRetriever.METADATA_KEY_DURATION, playingItem.duration) // TODO: fetch real item artwork .putBitmap(RemoteControlClientCompat.MetadataEditorCompat.METADATA_KEY_ARTWORK, mDummyAlbumArt) .apply(); // starts preparing the media player in the background. When it's done, it will call // our OnPreparedListener (that is, the onPrepared() method on this class, since we set // the listener to 'this'). // // Until the media player is prepared, we *cannot* call start() on it! mPlayingItem.bufferedPercent = 0; mPlayer.prepareAsync(); if (null != sOnStateChangedListener) sOnStateChangedListener.onPlayerPreparing(mPlayingItem); // If we are streaming from the internet, we want to hold a Wifi lock, which prevents // the Wifi radio from going to sleep while the song is playing. If, on the other hand, // we are *not* streaming, we want to release the lock if we were holding it before. if (mIsStreaming) mWifiLock.acquire(); else if (mWifiLock.isHeld()) mWifiLock.release(); } catch (IOException ex) { Log.e(TAG, "IOException playing next song: " + ex.getMessage(), ex); } } public static Item createPlayingItem(MusicList.MusicListeningInfo info) { if (null == info) return null; Item playingItem; playingItem = new Item(); final long fid = info.mMusicInfo.mSongFid; final String url = FileUtilFactory.getInstance().getDownUrlByFid(fid, null, 0); playingItem.setUrl(url); playingItem.setId(fid); playingItem.setTitle(info.mMusicInfo.mSongName); playingItem.setUserCount(info.mUserCount); playingItem.getUserList().addAll(info.mUserInfoList); return playingItem; } /** * Called when media player is done playing current song. */ public void onCompletion(MediaPlayer player) { final int pos = player.getCurrentPosition(); final int duration = player.getDuration(); Log.d(TAG, "current position: " + pos + "/" + duration); Log.d(TAG, "Song " + mSongTitle + " buffered percent: " + mPlayingItem.bufferedPercent); if ((duration - pos) > 1000) { Log.d(TAG, "Song: " + mSongTitle + " uncompleted, stop it."); // onCompletion???????? // ???durationpos??1000ms // 1000ms?? mPlayingItem.lastPosition = pos; processStopRequest(); } else { mPlayingItem.lastPosition = 0; Log.d(TAG, "Song: " + mSongTitle + " completed, try play next song."); // The media player finished playing the current song, so we go ahead and start the next. playNextSong(null); } } /** * Called when media player is done preparing. */ public void onPrepared(MediaPlayer player) { Log.d(TAG, "prepared: " + mSongTitle); // The media player is done preparing. That means we can start playing! mState = State.Playing; updateNotification(mSongTitle + ""); configAndStartMediaPlayer(); if (mPlayingItem.lastPosition > 0 && mPlayingItem.lastPosition <= player.getDuration()) player.seekTo(mPlayingItem.lastPosition); } @Override public void onBufferingUpdate(MediaPlayer mp, int percent) { Log.d("yulin", "buffered percent: " + percent); if (null != mPlayingItem) { mPlayingItem.bufferedPercent = percent; } } /** * Updates the notification. */ void updateNotification(String text) { // Log.d("yulin", "updateNotification, musicName = " + musicName); mNotification = buildNotification(text); //Update the current notification. mNotificationManager.notify(NOTIFICATION_ID, mNotification); } /** * Configures service as a foreground service. A foreground service is a service that's doing * something the user is actively aware of (such as playing music), and must appear to the * user as a notification. That's why we create the notification here. */ void setUpAsForeground(String text) { PendingIntent pi = PendingIntent.getActivity(getApplicationContext(), 0, new Intent(getApplicationContext(), SquareActivity.class), PendingIntent.FLAG_UPDATE_CURRENT); // mNotification = new Notification(); // mNotification.tickerText = text; // mNotification.icon = R.drawable.ic_square_notification_music_play; // mNotification.flags |= Notification.FLAG_ONGOING_EVENT; // mNotification.setLatestEventInfo(getApplicationContext(), "Jay&Me", // text, pi); mNotification = buildNotification(text); startForeground(NOTIFICATION_ID, mNotification); } Notification buildNotification(String text) { // Log.d("yulin", "buildNotification, musicName = " + musicName + ", mIsPause = " + mIsPause); NotificationCompat.Builder mNotificationBuilder = new NotificationCompat.Builder(this); mNotificationBuilder.setOngoing(true); mNotificationBuilder.setAutoCancel(false); mNotificationBuilder.setSmallIcon(R.drawable.ic_square_notification_music_play); mNotificationBuilder.setTicker(text); //Grab the notification layout. RemoteViews notificationView = new RemoteViews(getPackageName(), R.layout.square_notification_music_play); Intent stopServiceIntent = new Intent(ACTION_STOP); PendingIntent stopServicePendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 0, stopServiceIntent, 0); //Set the notification content. notificationView.setTextViewText(R.id.notification_base_line_one, text); //Set the "Stop Service" pending intent. notificationView.setOnClickPendingIntent(R.id.notification_base_collapse, stopServicePendingIntent); //Set the album art. notificationView.setImageViewResource(R.id.notification_base_image, R.drawable.ic_square_music_default); //Attach the shrunken layout to the notification. mNotificationBuilder.setContent(notificationView); //Build the notification object and set its flags. Notification notification = mNotificationBuilder.build(); notification.flags = Notification.FLAG_FOREGROUND_SERVICE | Notification.FLAG_NO_CLEAR | Notification.FLAG_ONGOING_EVENT; return notification; } /** * Called when there's an error playing media. When this happens, the media player goes to * the Error state. We warn the user about the error and reset the media player. */ public boolean onError(MediaPlayer mp, int what, int extra) { Log.e(TAG, "Song: " + mSongTitle + ", Error: what=" + String.valueOf(what) + ", extra=" + String.valueOf(extra)); mState = State.Stopped; relaxResources(true); giveUpAudioFocus(); if (null != sOnStateChangedListener) sOnStateChangedListener.onPlayerError(mPlayingItem, what, extra); return true; // true indicates we handled the error } public void onGainedAudioFocus() { Log.i(TAG, "gained audio focus."); mAudioFocus = AudioFocus.Focused; // restart media player with new focus settings if (mState == State.Playing) configAndStartMediaPlayer(); } public void onLostAudioFocus(boolean canDuck) { Log.i(TAG, "lost audio focus." + (canDuck ? "can duck" : "no duck")); mAudioFocus = canDuck ? AudioFocus.NoFocusCanDuck : AudioFocus.NoFocusNoDuck; // start/restart/pause media player with new focus settings if (mPlayer != null && mPlayer.isPlaying()) configAndStartMediaPlayer(); } @Override public void onDestroy() { Log.i(TAG, "service destroy"); // Service is being killed, so make sure we release our resources if (null != mNotificationIntentActionReceiver) unregisterReceiver(mNotificationIntentActionReceiver); MusicExecutor.getInstance().getEventBus().unregister(this); mState = State.Stopped; relaxResources(true); giveUpAudioFocus(); MusicExecutor.getInstance().shutdown(); if (null != sOnStateChangedListener) sOnStateChangedListener.onServiceDestroy(); sInstance = null; } @Override public IBinder onBind(Intent arg0) { return null; } public static class Item implements Serializable { long id; String title; long duration; String url; int userCount; List<UserInfo> userList = new ArrayList<UserInfo>(); int bufferedPercent; int lastPosition; public void setId(long id) { this.id = id; } public long getId() { return id; } public void setTitle(String title) { this.title = title; } public String getTitle() { return title; } public void setDuration(long duration) { this.duration = duration; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public int getUserCount() { return userCount; } public void setUserCount(int userCount) { this.userCount = userCount; } public List<UserInfo> getUserList() { return userList; } } /** * ???? */ public static interface OnStateChangedListener { void onPlayerPlaying(); void onPlayerPaused(); void onPlayerStopped(MusicPlaybackService service); void onPlayerError(Item playingItem, int what, int extra); void onPlayerPreparing(Item playingItem); void onServiceDestroy(); void onPlayListIsEmpty(); void onCurrentPlayingItemChanged(Item playingItem); void onPlayListAvailable(Item playingItem); } private class NotificationIntentBroadcastReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { if (ACTION_STOP.equals(intent.getAction())) { processStopRequest(); } } } private class MyPhoneStateListener extends PhoneStateListener { @Override public void onCallStateChanged(int state, String incomingNumber) { switch (state) { case TelephonyManager.CALL_STATE_IDLE: processTogglePlaybackRequest(); break; case TelephonyManager.CALL_STATE_OFFHOOK: case TelephonyManager.CALL_STATE_RINGING: processPauseRequest(); break; default: break; } } } }