如何使用锁定屏幕媒体控件正确构建一个在应用程序退出时永不停止的音乐服务?

时间:2017-11-18 08:25:30

标签: java android android-service android-mediaplayer

我试了好几次都没有成功。

  1. 在平息应用程序时,甚至在按下后退按钮时,服务都会停止。
  2. 即使应用程序被刷了,我也希望音乐能够继续播放,就像Google音乐一样。

    1. 我设置了会话和媒体控件,但是当设备被锁定时,无法控制音乐播放。
    2. 我可能会缺少什么?以下是此类。

      MusicService

      public class MediaPlayerService extends Service implements 
      MediaPlayer.OnCompletionListener, MediaPlayer.OnPreparedListener,
      
      MediaPlayer.OnErrorListener, MediaPlayer.OnSeekCompleteListener,
      
      MediaPlayer.OnInfoListener, MediaPlayer.OnBufferingUpdateListener,
      
      AudioManager.OnAudioFocusChangeListener {
          private MediaPlayer mediaPlayer;
          private final IBinder iBinder = new LocalBinder();
      
          public void onCreate() {
              super.onCreate();
          initMediaPlayer();
          callStateListener();
          registerBecomingNoisyReceiver();
          register_playNewAudio();
      }
      
      public static final String MusicServiceTag = "MusicTest";
      
      @Override
      public int onStartCommand(Intent intent, int flags, int startId) {
          try {
              StorageUtil storage = new StorageUtil(getApplicationContext());
              audioList = storage.loadAudio();
              audioIndex = storage.loadAudioIndex();
              if (audioIndex != -1 && audioIndex < audioList.size()) {
                  activeAudio = audioList.get(audioIndex);
              } else {
                  stopSelf();
              }
          } catch (NullPointerException e) {
              stopSelf();
          }
      
          //Request audio focus
          if (!requestAudioFocus()) {
              //Could not gain focus
              stopSelf();
          }
      
          if (mediaSessionManager == null) {
              try {
                  initMediaSession();
                  initMediaPlayer();
              } catch (RemoteException e) {
                  e.printStackTrace();
                  stopSelf();
              }
              buildNotification(PlaybackStatus.PLAYING);
          }
      
          //Handle Intent action from MediaSession.TransportControls
          handleIncomingActions(intent);
          return super.onStartCommand(intent, flags, startId);
      }
      
      @Override
      public void onDestroy() {
          super.onDestroy();
          if (mediaPlayer != null) {
              stopMedia();
              mediaPlayer.release();
          }
          removeAudioFocus();
          //Disable the PhoneStateListener
          if (phoneStateListener != null) {
              telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_NONE);
          }
      
          removeNotification();
      
          //unregister BroadcastReceivers
          unregisterReceiver(becomingNoisyReceiver);
          unregisterReceiver(playNewAudio);
      
          //clear cached playlist
          new StorageUtil(getApplicationContext()).clearCachedAudioPlaylist();
      }
      
      private int resumePosition;
      
      private void initMediaPlayer() {
          mediaPlayer = new MediaPlayer();
          mediaPlayer.setOnCompletionListener(this);
          mediaPlayer.setOnErrorListener(this);
          mediaPlayer.setOnPreparedListener(this);
          mediaPlayer.setOnBufferingUpdateListener(this);
          mediaPlayer.setOnSeekCompleteListener(this);
          mediaPlayer.setOnInfoListener(this);
          mediaPlayer.reset();
          mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
          try {
              System.out.println(MusicServiceTag + "File :: " + activeAudio);
              String x = "https://upload.wikimedia.org/wikipedia/commons/6/6c/Grieg_Lyric_Pieces_Kobold.ogg";
              mediaPlayer.setDataSource(activeAudio == null ? x : activeAudio.getData());
              mediaPlayer.prepareAsync();
          } catch (Exception e) {
              e.printStackTrace();
              stopSelf();
          }
      }
      
      private void playMedia() {
          if (!mediaPlayer.isPlaying()) {
              mediaPlayer.start();
          }
      }
      
      private void stopMedia() {
          if (mediaPlayer == null) return;
          if (mediaPlayer.isPlaying()) {
              mediaPlayer.stop();
          }
      }
      
      private void pauseMedia() {
          if (mediaPlayer.isPlaying()) {
              mediaPlayer.pause();
              resumePosition = mediaPlayer.getCurrentPosition();
          }
      }
      
      private void resumeMedia() {
          if (!mediaPlayer.isPlaying()) {
              mediaPlayer.seekTo(resumePosition);
              mediaPlayer.start();
          }
      }
      
      @Override
      public void onBufferingUpdate(MediaPlayer mediaPlayer, int i) {
      
      }
      
      @Override
      public boolean onInfo(MediaPlayer mediaPlayer, int i, int i1) {
          return false;
      }
      
      @Override
      public void onSeekComplete(MediaPlayer mediaPlayer) {
      
      }
      
      public class LocalBinder extends Binder {
          public MediaPlayerService getService() {
              return MediaPlayerService.this;
          }
      }
      
      @Nullable
      @Override
      public IBinder onBind(Intent intent) {
          return iBinder;
      }
      
      @Override
      public void onCompletion(MediaPlayer mp) {
          stopMedia();
          stopSelf();
      }
      
      private static final String TAG_ERROR = "MediaPlayer Error";
      
      
      @Override
      public boolean onError(MediaPlayer mp, int what, int extra) {
          switch (what) {
              case MediaPlayer.MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK:
                  Log.d(TAG_ERROR, "MEDIA ERROR NOT VALID FOR PROGRESSIVE PLAYBACK " + extra);
                  break;
              case MediaPlayer.MEDIA_ERROR_SERVER_DIED:
                  Log.d(TAG_ERROR, "MEDIA ERROR SERVER DIED " + extra);
                  break;
              case MediaPlayer.MEDIA_ERROR_UNKNOWN:
                  Log.d(TAG_ERROR, "MEDIA ERROR UNKNOWN " + extra);
                  break;
          }
          return false;
      }
      
      @Override
      public void onPrepared(MediaPlayer mp) {
          playMedia();
      }
      
      //Audio Focus
      
      private AudioManager audioManager;
      
      //List of available Audio files
      private ArrayList<Sound> audioList;
      private int audioIndex = -1;
      private Sound activeAudio; //an object of the currently playing audio
      
      @Override
      public void onAudioFocusChange(int focusState) {
          //Invoked when the audio focus of the system is updated.
          switch (focusState) {
              case AudioManager.AUDIOFOCUS_GAIN:
                  // resume playback
                  if (mediaPlayer == null) initMediaPlayer();
                  else if (!mediaPlayer.isPlaying()) mediaPlayer.start();
                  mediaPlayer.setVolume(1.0f, 1.0f);
                  break;
              case AudioManager.AUDIOFOCUS_LOSS:
                  // Lost focus for an unbounded amount of time: stop playback and release media player
                  if (mediaPlayer.isPlaying()) mediaPlayer.stop();
                  mediaPlayer.release();
                  mediaPlayer = null;
                  break;
              case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
                  // Lost focus for a short time, but we have to stop
                  // playback. We don't release the media player because playback
                  // is likely to resume
                  if (mediaPlayer.isPlaying()) mediaPlayer.pause();
                  break;
              case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
                  // Lost focus for a short time, but it's ok to keep playing
                  // at an attenuated level
                  if (mediaPlayer.isPlaying()) mediaPlayer.setVolume(0.1f, 0.1f);
                  break;
          }
      }
      
      private boolean requestAudioFocus() {
          audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
          int result = audioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
          return result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
      }
      
      private boolean removeAudioFocus() {
          return AudioManager.AUDIOFOCUS_REQUEST_GRANTED ==
                 audioManager.abandonAudioFocus(this);
      }
      
      //Becoming noisy
      private BroadcastReceiver becomingNoisyReceiver = new BroadcastReceiver() {
          @Override
          public void onReceive(Context context, Intent intent) {
              //pause audio on ACTION_AUDIO_BECOMING_NOISY
              pauseMedia();
              buildNotification(PlaybackStatus.PAUSED);
          }
      };
      
      private void registerBecomingNoisyReceiver() {
          //register after getting audio focus
          IntentFilter intentFilter = new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
          registerReceiver(becomingNoisyReceiver, intentFilter);
      }
      
      //Handle incoming phone calls
      private boolean ongoingCall = false;
      private PhoneStateListener phoneStateListener;
      private TelephonyManager   telephonyManager;
      
      //Handle incoming phone calls
      private void callStateListener() {
          // Get the telephony manager
          telephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
          //Starting listening for PhoneState changes
          phoneStateListener = new PhoneStateListener() {
              @Override
              public void onCallStateChanged(int state, String incomingNumber) {
                  switch (state) {
                      //if at least one call exists or the phone is ringing
                      //pause the MediaPlayer
                      case TelephonyManager.CALL_STATE_OFFHOOK:
                      case TelephonyManager.CALL_STATE_RINGING:
                          if (mediaPlayer != null) {
                              pauseMedia();
                              ongoingCall = true;
                          }
                          break;
                      case TelephonyManager.CALL_STATE_IDLE:
                          // Phone idle. Start playing.
                          if (mediaPlayer != null) {
                              if (ongoingCall) {
                                  ongoingCall = false;
                                  resumeMedia();
                              }
                          }
                          break;
                  }
              }
          };
          // Register the listener with the telephony manager
          // Listen for changes to the device call state.
          telephonyManager.listen(phoneStateListener,
                  PhoneStateListener.LISTEN_CALL_STATE);
      }
      
      private BroadcastReceiver playNewAudio = new BroadcastReceiver() {
          @Override
          public void onReceive(Context context, Intent intent) {
      
              //Get the new media index form SharedPreferences
              audioIndex = new StorageUtil(getApplicationContext()).loadAudioIndex();
              if (audioIndex != -1 && audioIndex < audioList.size()) {
                  //index is in a valid range
                  activeAudio = audioList.get(audioIndex);
              } else {
                  stopSelf();
              }
      
              //A PLAY_NEW_AUDIO action received
              //reset mediaPlayer to play the new Audio
              stopMedia();
              mediaPlayer.reset();
              initMediaPlayer();
              updateMetaData();
              buildNotification(PlaybackStatus.PLAYING);
          }
      };
      
      private void register_playNewAudio() {
          //Register playNewMedia receiver
          IntentFilter filter = new IntentFilter(MainActivity.Broadcast_PLAY_NEW_AUDIO);
          registerReceiver(playNewAudio, filter);
      }
      
      //Notifications
      
      public static final String ACTION_PLAY     = "com.valdioveliu.valdio.audioplayer.ACTION_PLAY";
      public static final String ACTION_PAUSE    = "com.valdioveliu.valdio.audioplayer.ACTION_PAUSE";
      public static final String ACTION_PREVIOUS = "com.valdioveliu.valdio.audioplayer.ACTION_PREVIOUS";
      public static final String ACTION_NEXT     = "com.valdioveliu.valdio.audioplayer.ACTION_NEXT";
      public static final String ACTION_STOP     = "com.valdioveliu.valdio.audioplayer.ACTION_STOP";
      
      //MediaSession
      private MediaSessionManager                     mediaSessionManager;
      private MediaSessionCompat                      mediaSession;
      private MediaControllerCompat.TransportControls transportControls;
      
      //AudioPlayer notification ID
      private static final int NOTIFICATION_ID = 101;
      
      private void initMediaSession() throws RemoteException {
          if (mediaSessionManager != null) {
              return; //mediaSessionManager exists
          }
      
          mediaSessionManager = (MediaSessionManager) getSystemService(Context.MEDIA_SESSION_SERVICE);
          // Create a new MediaSession
          mediaSession = new MediaSessionCompat(getApplicationContext(), "AudioPlayer");
          //Get MediaSessions transport controls
          transportControls = mediaSession.getController().getTransportControls();
          //set MediaSession -> ready to receive media commands
          mediaSession.setActive(true);
          //indicate that the MediaSession handles transport control commands
          // through its MediaSessionCompat.Callback.
          mediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
      
          //Set mediaSession's MetaData
          updateMetaData();
      
          // Attach Callback to receive MediaSession updates
          mediaSession.setCallback(new MediaSessionCompat.Callback() {
              // Implement callbacks
              @Override
              public void onPlay() {
                  super.onPlay();
                  resumeMedia();
                  buildNotification(PlaybackStatus.PLAYING);
              }
      
              @Override
              public void onPause() {
                  super.onPause();
                  pauseMedia();
                  buildNotification(PlaybackStatus.PAUSED);
              }
      
              @Override
              public void onSkipToNext() {
                  super.onSkipToNext();
                  skipToNext();
                  updateMetaData();
                  buildNotification(PlaybackStatus.PLAYING);
              }
      
              @Override
              public void onSkipToPrevious() {
                  super.onSkipToPrevious();
                  skipToPrevious();
                  updateMetaData();
                  buildNotification(PlaybackStatus.PLAYING);
              }
      
              @Override
              public void onStop() {
                  super.onStop();
                  removeNotification();
                  //Stop the service
                  stopSelf();
              }
      
              @Override
              public void onSeekTo(long position) {
                  super.onSeekTo(position);
              }
          });
      }
      
      private void updateMetaData() {
          Bitmap                      albumArt = BitmapFactory.decodeResource(getResources(), R.drawable.music_placeholder);
          MediaMetadataCompat.Builder builder  = new MediaMetadataCompat.Builder();
          builder.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, albumArt);
          builder.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, activeAudio.getArtist());
          builder.putString(MediaMetadataCompat.METADATA_KEY_ALBUM, activeAudio.getAlbum());
          builder.putString(MediaMetadataCompat.METADATA_KEY_TITLE, activeAudio.getTitle());
          mediaSession.setMetadata(builder.build());
      }
      
      private void skipToNext() {
          if (audioIndex == audioList.size() - 1) {
              //if last in playlist
              audioIndex = 0;
              activeAudio = audioList.get(audioIndex);
          } else {
              //get next in playlist
              activeAudio = audioList.get(++audioIndex);
          }
      
          //Update stored index
          new StorageUtil(getApplicationContext()).storeAudioIndex(audioIndex);
      
          stopMedia();
          //reset mediaPlayer
          mediaPlayer.reset();
          initMediaPlayer();
      }
      
      private void skipToPrevious() {
      
          if (audioIndex == 0) {
              //if first in playlist
              //set index to the last of audioList
              audioIndex = audioList.size() - 1;
              activeAudio = audioList.get(audioIndex);
          } else {
              //get previous in playlist
              activeAudio = audioList.get(--audioIndex);
          }
      
          //Update stored index
          new StorageUtil(getApplicationContext()).storeAudioIndex(audioIndex);
      
          stopMedia();
          //reset mediaPlayer
          mediaPlayer.reset();
          initMediaPlayer();
      }
      
      private void buildNotification(PlaybackStatus playbackStatus) {
          int           pauseIcon          = R.drawable.exo_controls_pause;
          int           notificationAction = pauseIcon;
          PendingIntent play_pauseAction   = null;
          if (playbackStatus == PlaybackStatus.PLAYING) {
              notificationAction = pauseIcon;
              play_pauseAction = playbackAction(1);
          } else if (playbackStatus == PlaybackStatus.PAUSED) {
              notificationAction = R.drawable.exo_controls_play;
              play_pauseAction = playbackAction(0);
          }
      
          Bitmap largeIcon = BitmapFactory.decodeResource(getResources(), R.drawable.music_placeholder);
          // Create a new Notification
      
          MediaControllerCompat                             mediaControllerCompat  = mediaSession.getController();
          MediaMetadataCompat                               mediaMetadataCompat    = mediaControllerCompat.getMetadata();
          MediaDescriptionCompat                            mediaDescriptionCompat = mediaMetadataCompat.getDescription();
          android.support.v4.app.NotificationCompat.Builder builder                = MediaStyleHelper.from(getBaseContext(), mediaSession);
          builder.setShowWhen(false);
          builder.setStyle(new NotificationCompat.MediaStyle());
          //builder.setMediaSession(mediaSession.getSessionToken());
          //builder.setShowActionsInCompactView(0, 1, 2);
          builder.setContentTitle(mediaDescriptionCompat.getTitle());
          builder.setContentText(mediaDescriptionCompat.getSubtitle());
          //builder.setContentIntent(contentIntent());
          builder.setVisibility(android.support.v4.app.NotificationCompat.VISIBILITY_PUBLIC);
          builder.setLargeIcon(mediaDescriptionCompat.getIconBitmap());
          builder.setSmallIcon(R.drawable.icon);
          builder.setColor(ContextCompat.getColor(this, R.color.colorPrimaryDark));
          builder.setPriority(Notification.PRIORITY_MAX);
          builder.addAction(R.drawable.exo_controls_previous, "previous", playbackAction(3));
          builder.addAction(notificationAction, "pause", play_pauseAction);
          builder.addAction(R.drawable.exo_controls_next, "next", playbackAction(2));
          startForeground(453, builder.build());
      }
      
      private void removeNotification() {
          NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
          notificationManager.cancel(NOTIFICATION_ID);
      }
      
      private PendingIntent playbackAction(int actionNumber) {
          Intent playbackAction = new Intent(this, MediaPlayerService.class);
          switch (actionNumber) {
              case 0:
                  // Play
                  playbackAction.setAction(ACTION_PLAY);
                  return PendingIntent.getService(this, actionNumber, playbackAction, 0);
              case 1:
                  // Pause
                  playbackAction.setAction(ACTION_PAUSE);
                  return PendingIntent.getService(this, actionNumber, playbackAction, 0);
              case 2:
                  // Next track
                  playbackAction.setAction(ACTION_NEXT);
                  return PendingIntent.getService(this, actionNumber, playbackAction, 0);
              case 3:
                  // Previous track
                  playbackAction.setAction(ACTION_PREVIOUS);
                  return PendingIntent.getService(this, actionNumber, playbackAction, 0);
              default:
                  break;
          }
          return null;
      }
      
      private void handleIncomingActions(Intent playbackAction) {
          if (playbackAction == null || playbackAction.getAction() == null) return;
      
          String actionString = playbackAction.getAction();
          if (actionString.equalsIgnoreCase(ACTION_PLAY)) {
              transportControls.play();
          } else if (actionString.equalsIgnoreCase(ACTION_PAUSE)) {
              transportControls.pause();
          } else if (actionString.equalsIgnoreCase(ACTION_NEXT)) {
              transportControls.skipToNext();
          } else if (actionString.equalsIgnoreCase(ACTION_PREVIOUS)) {
              transportControls.skipToPrevious();
          } else if (actionString.equalsIgnoreCase(ACTION_STOP)) {
              transportControls.stop();
          }
      }
      }
      

      MediaStyleHelper

      public class MediaStyleHelper {
          public static NotificationCompat.Builder from(Context context, MediaSessionCompat mediaSession) {
          MediaControllerCompat  controller    = mediaSession.getController();
          MediaMetadataCompat    mediaMetadata = controller.getMetadata();
          MediaDescriptionCompat description   = mediaMetadata.getDescription();
      
          NotificationCompat.Builder builder = new NotificationCompat.Builder(context, "channel_1");
          builder.setContentTitle(description.getTitle());
          builder.setContentText(description.getSubtitle());
          builder.setSubText(description.getDescription());
          builder.setContentIntent(controller.getSessionActivity());
          builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC);
          builder.setWhen(0);
          builder.setShowWhen(false);
      
          if (description.getIconBitmap() == null || description.getIconBitmap().isRecycled()) {
              builder.setLargeIcon(BitmapFactory.decodeResource(context.getResources(), R.drawable.music_placeholder));
          } else {
              builder.setLargeIcon(description.getIconBitmap());
          }
      
          return builder;
      }
      
      
      public static PendingIntent getActionIntent(Context context, int mediaKeyEvent) {
          Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON);
          intent.setPackage(context.getPackageName());
          intent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_DOWN, mediaKeyEvent));
          return PendingIntent.getBroadcast(context, mediaKeyEvent, intent, 0);
      }
      }
      

      StorageUtil

      public class StorageUtil {
      
      private final String STORAGE = " com.domain.pack.core.media.STORAGE";
      private SharedPreferences preferences;
      private Context           context;
      
      public StorageUtil(Context context) {
          this.context = context;
      }
      
      public void storeAudio(ArrayList<Sound> arrayList) {
          preferences = context.getSharedPreferences(STORAGE, Context.MODE_PRIVATE);
      
          SharedPreferences.Editor editor = preferences.edit();
          Gson                     gson   = new Gson();
          String                   json   = gson.toJson(arrayList);
          editor.putString("audioArrayList", json);
          editor.apply();
      }
      
      public ArrayList<Sound> loadAudio() {
          preferences = context.getSharedPreferences(STORAGE, Context.MODE_PRIVATE);
          Gson   gson = new Gson();
          String json = preferences.getString("audioArrayList", null);
          Type type = new TypeToken<ArrayList<Sound>>() {
          }.getType();
          return gson.fromJson(json, type);
      }
      
      public void storeAudioIndex(int index) {
          preferences = context.getSharedPreferences(STORAGE, Context.MODE_PRIVATE);
          SharedPreferences.Editor editor = preferences.edit();
          editor.putInt("audioIndex", index);
          editor.apply();
      }
      
      public int loadAudioIndex() {
          preferences = context.getSharedPreferences(STORAGE, Context.MODE_PRIVATE);
          return preferences.getInt("audioIndex", -1);//return -1 if no data found
      }
      
      public void clearCachedAudioPlaylist() {
          preferences = context.getSharedPreferences(STORAGE, Context.MODE_PRIVATE);
          SharedPreferences.Editor editor = preferences.edit();
          editor.clear();
          editor.commit();
      }
      }
      

      // 编辑在MainActivity中启动服务

      private void playAudio(int audioIndex) {
          //Check is service is active
          if (!serviceBound) {
              //Store Serializable audioList to SharedPreferences
              StorageUtil storage = new StorageUtil(getApplicationContext());
              storage.storeAudio(audioList);
              storage.storeAudioIndex(audioIndex);
      
              Intent playerIntent = new Intent(this, MediaPlayerService.class);
              startService(playerIntent);
              bindService(playerIntent, serviceConnection, Context.BIND_AUTO_CREATE);
          } else {
              //Store the new audioIndex to SharedPreferences
              StorageUtil storage = new StorageUtil(getApplicationContext());
              storage.storeAudioIndex(audioIndex);
      
              //Service is active
              //Send a broadcast to the service -> PLAY_NEW_AUDIO
              Intent broadcastIntent = new Intent(Broadcast_PLAY_NEW_AUDIO);
              sendBroadcast(broadcastIntent);
          }
      }
      

      服务连接

      //Binding this Client to the AudioPlayer Service
      
      private ServiceConnection serviceConnection = new ServiceConnection() {
          @Override
          public void onServiceConnected(ComponentName name, IBinder service) {
              // We've bound to LocalService, cast the IBinder and get LocalService instance
              MediaPlayerService.LocalBinder binder = (MediaPlayerService.LocalBinder) service;
              player = binder.getService();
              serviceBound = true;
              Toast.makeText(MainActivity.this, "Service Bound", Toast.LENGTH_SHORT).show();
          }
      
          @Override
          public void onServiceDisconnected(ComponentName name) {
              serviceBound = false;
          }
      };
      

0 个答案:

没有答案