安卓媒体播放器如何在应用关闭后继续播放歌曲?

7

想知道在应用程序关闭后如何播放下一首歌曲,就像播放整张CD或播放列表一样...


“应用程序关闭”是指完全不在任务管理器中显示,还是只是在后台运行? - Jason Robinson
我想我的意思是当它在后台运行时。 - Metallicraft
5个回答

15
媒体播放器只能播放一个音轨。媒体播放器会监听onCompletion事件并播放下一首曲目。MediaPlayer绑定到进程而非活动,因此只要进程在运行,它就会一直播放。活动可能会暂停或销毁,但这不会影响MediaPlayer使用的内部线程。我正在构建一个音频播放器来学习Android,你可以在这里看到播放音频文件的服务。
编辑:关于第一个评论:服务在后台保持运行,并在您“退出”应用程序后继续运行,因为服务和活动的生命周期不同。为了播放下一首曲目,服务在MediaPlayer上注册回调,以便在音频流完成时通知服务。当音频完成时,服务通过调用MediaPlayer.release()清理MediaPlayer使用的资源,然后创建一个新的媒体播放器来播放下一个音轨,并再次注册自己以在该音轨完成时再次接收通知,无限循环 :)。MediaPlayer类不理解播放列表,因此服务负责在前一首曲目完成后播放曲目。在我创建的AudioPlayer服务中,活动将曲目排队到AudioPlayer中,而AudioPlayer负责按顺序播放它们。

我希望这很清楚,如果您有时间,请检查我上面放置的AudioPlayer服务的代码。它不是完美的,但它能够胜任其工作。


每次播放新歌时,服务会被销毁并重新创建吗?还是整个歌曲列表发送到一个服务中进行处理? - Metallicraft
我创建了一个带有按钮的项目,而不是像你那样的列表,来尝试你正在做的事情。这非常有帮助。我以前从未进行过服务绑定。但是当我运行它并按下“返回”按钮,或打开首选项,甚至是主页键时,音频就会停止。是否有什么特殊的方法使其持久化? - Metallicraft
嗯,我有弹出显示手机状态的提示...尽管音乐停止了,但它们仍在触发。这似乎意味着服务仍在运行... - Metallicraft

3

您可以创建一个服务,使得MediaPlayer在您的应用程序退出或暂停后继续播放。为了让MediaPlayer播放连续的音轨,您可以注册一个onCompletionListener来决定下一首要播放的音轨。这里是一个简单的示例服务:

package edu.gvsu.cis.muzak;

import android.app.Service;
import android.content.Intent;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnCompletionListener;
import android.net.Uri;
import android.os.IBinder;
import android.util.Log;

public class MuzakService extends Service {

    private static final String DEBUG_TAG = "MuzakService";
    private MediaPlayer mp;
    private String[] tracks = {
            "http://freedownloads.last.fm/download/288181172/Nocturne.mp3",
            "http://freedownloads.last.fm/download/367924875/Behemoths%2BSternentanz.mp3",
            "http://freedownloads.last.fm/download/185193341/Snowflake%2BImpromptu.mp3",
            "http://freedownloads.last.fm/download/305596593/Prel%25C3%25BAdio.mp3",
            "http://freedownloads.last.fm/download/142005075/Piano%2BSonata%2B22%2B-%2Bmovement%2B2%2B%2528Beethoven%2529.mp3",
            "http://freedownloads.last.fm/download/106179902/Piano%2BSonata%2B%25231%2B-%2Bmovement%2B%25234%2B%2528Brahms%2529.mp3",

    };
    private int currentTrack = 0;

    @Override
    public void onCreate() {
        super.onCreate();
        Log.d(DEBUG_TAG, "In onCreate.");

        try {
            Uri file = Uri.parse(tracks[this.currentTrack]);
            mp = new MediaPlayer();
            mp.setDataSource(this, file);
            mp.prepare();
            mp.setOnCompletionListener(new OnCompletionListener() {

                @Override
                public void onCompletion(MediaPlayer mp) {
                    currentTrack = (currentTrack + 1) % tracks.length;
                    Uri nextTrack = Uri.parse(tracks[currentTrack]);
                    try {
                        mp.setDataSource(MuzakService.this,nextTrack);
                        mp.prepare();
                        mp.start();
                    } catch (Exception e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    } 

                }

            });

        } catch (Exception e) { 
            Log.e(DEBUG_TAG, "Player failed", e);
        }
    }


    @Override
    public void onDestroy() {
        // TODO Auto-generated method stub
        super.onDestroy();
        Log.d(DEBUG_TAG, "In onDestroy.");
        if(mp != null) {
            mp.stop();
        }
    }

    @Override
    public int onStartCommand(Intent intent,int flags, int startId) {
        super.onStart(intent, startId);
        Log.d(DEBUG_TAG, "In onStart.");
        mp.start();
        return Service.START_STICKY_COMPATIBILITY;
    }


    @Override
    public IBinder onBind(Intent intent) {
        Log.d(DEBUG_TAG, "In onBind with intent=" + intent.getAction());
        return null;
    }

}

你可以在Activity中按以下方式启动此服务:
Intent serv = new Intent(this,MuzakService.class);
startService(serv);

如何停止它:

Intent serv = new Intent(this,MuzakService.class);
stopService(serv);

如果音频在服务中播放,那么MediaController如何适应这一切? - Metallicraft
你的代码中嵌入了曲目,这与播放列表有何不同?或者您能否将歌曲列表作为数组或其他形式发送到服务中? - Metallicraft
为了保持示例的简单性,我只是在Service中嵌入了一组曲目列表。 您可以通过Bundle将播放列表/曲目传递给Service,如此处所述。 如果您想要更复杂的控制,则可以创建绑定服务。 要了解此类服务与上面的代码不同,请阅读这里 - jengelsma
如果您想要一个交互式用户界面,您可以使用MediaController,或者您可以自己编写用户界面。这里有一些示例代码(https://dev59.com/92865IYBdhLWcg3wkfcW#5265629),展示如何在MediaPlayer中使用MediaController。 - jengelsma
我已经看到你提到的MediaController链接。但那并没有使用MediaPlayer作为服务。如果MediaPlayer是一个服务,那么MediaController(假设在创建MediaPlayer服务的活动中)是否有一个钩子或其他方式来访问MediaPlayer呢?如果活动进入后台,并在稍后再次回到前台,MediaController是否仍然与MediaPlayer保持“连接”? - Metallicraft

1
媒体播放器应该从服务中运行,在这里我已经将歌曲的ArrayList从活动传递到服务,并通过读取ArrayList来运行所有歌曲。

public class MyService extends Service implements OnCompletionListener,
  MediaPlayer.OnPreparedListener, MediaPlayer.OnErrorListener{

 Context context; 
 private static final String ACTION_PLAY = "PLAY";
 private static final String TAG = "SONG SERVICE";
 MediaPlayer mediaPlayer; 
 private int currentTrack = 0;
 ArrayList<String> list;
 public MyService() {
  context=getBaseContext();  
 }

 @Override
 public IBinder onBind(Intent intent) {
  throw new UnsupportedOperationException("Not yet implemented");
 }

 @Override
 public int onStartCommand(Intent intent, int flags, int startId) {
  list = (ArrayList<String>)intent.getSerializableExtra("arraylist");
  int count=0;
  Log.d(TAG, "total count:"+list.size());
  //playing song one by one
  for (String string : list) {
   //play(string);
   count++;
   Log.d(TAG, "count:"+list);
  }
  play(currentTrack);
  Log.d(TAG, "count:"+count);
  if(count==list.size()){
   //stopSelf();
   Log.d(TAG, "stoping service");
   //mediaPlayer.setOnCompletionListener(this);
  }else{
   Log.d(TAG, "not stoping service");
  }
  
  if (!mediaPlayer.isPlaying()) {
   mediaPlayer.start();
   Log.d(TAG, "oncommat");
  }
  return START_STICKY;
 }
 
 @Override
 public void onCreate() {  
  
  Toast.makeText(this, "Service was Created", Toast.LENGTH_LONG).show();
 }


 
 @Override
 public void onStart(Intent intent, int startId) {
  // Perform your long running operations here.
  Toast.makeText(this, "Service Started", Toast.LENGTH_LONG).show();
 }

 @Override
 public void onDestroy() {
  Toast.makeText(this, "Service Destroyed", Toast.LENGTH_LONG).show();
  Log.d("service", "destroyed");
  if (mediaPlayer.isPlaying()) {
        mediaPlayer.stop();
      }
      mediaPlayer.release();
 }



 @Override
 public boolean onError(MediaPlayer mp, int what, int extra) {
  // TODO Auto-generated method stub
  return false;
 }

 @Override
 public void onPrepared(MediaPlayer mp) {
  // TODO Auto-generated method stub

 }
 
 private void play(int id) {
  
  
  if(mediaPlayer!=null && mediaPlayer.isPlaying()){
   Log.d("*****begin*****", "playing");
   stopPlaying();
    Log.d("*****begin*****", "stoping");
       } else{
       Log.d("*****begin*****", "nothing");
       }
  
  Log.d("*****play count*****", "="+currentTrack);
  Log.i("******playing", list.get(currentTrack));
  
  Uri myUri1 = Uri.parse(list.get(id));
  mediaPlayer = new MediaPlayer();
  mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
  //mediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);
  mediaPlayer.setOnPreparedListener(this); 
  //mediaPlayer.setOnCompletionListener(this); 
  mediaPlayer.setOnErrorListener(this); 
  try {
   mediaPlayer.setDataSource(context, myUri1);   
   Log.i("******playing", myUri1.getPath());
  } catch (IllegalArgumentException e) {
            Toast.makeText(context, "You might not set the URI correctly!", Toast.LENGTH_LONG).show();
        } catch (SecurityException e) {
            Toast.makeText(context, "You might not set the URI correctly!", Toast.LENGTH_LONG).show();
        } catch (IllegalStateException e) {
            Toast.makeText(context, "You might not set the URI correctly!", Toast.LENGTH_LONG).show();
        } catch (IOException e) {
            e.printStackTrace();
        }
  
  try {
   mediaPlayer.prepare();
        } catch (IllegalStateException e) {
            Toast.makeText(context, "You might not set the URI correctly!", Toast.LENGTH_LONG).show();
        } catch (IOException e) {
            Toast.makeText(context, "You might not set the URI correctly!", Toast.LENGTH_LONG).show();
        }
  
  mediaPlayer.setOnCompletionListener(new OnCompletionListener() {
   
   @Override
   public void onCompletion(MediaPlayer mp) {
    currentTrack=currentTrack+1;
    play(currentTrack);
    /* currentTrack = (currentTrack + 1) % list.size();
     Uri nextTrack=Uri.parse(list.get(currentTrack));
     
     try {
      mediaPlayer.setDataSource(context,nextTrack);
      mediaPlayer.prepare();
     // mediaPlayer.start();
    } catch (Exception e) {
     e.printStackTrace();
    }*/
    
   }
  });
  
        mediaPlayer.start();
 }
 
 private void stopPlaying() { 
        if (mediaPlayer != null) {
         mediaPlayer.stop();
         mediaPlayer.release();
         mediaPlayer = null;
       } 
    }

 @Override
 public void onCompletion(MediaPlayer mp) {
  // TODO Auto-generated method stub
  
 }


1
请注意,服务也可以在前台运行。
请查看官方文档。它用示例代码进行了解释。 使用 MediaPlayer 的 Service: http://developer.android.com/guide/topics/media/mediaplayer.html#mpandservices 作为前台服务运行
服务通常用于执行后台任务。
但是考虑播放音乐的服务情况。显然,这是用户积极意识到的服务,任何中断都会严重影响体验。此外,这是用户在执行期间可能希望与之交互的服务。在这种情况下,服务应该作为“前台服务”运行。前台服务在系统中具有更高的重要性——系统几乎永远不会杀死服务,因为它对用户来说非常重要。当在前台运行时,服务还必须提供状态栏通知,以确保用户知道正在运行的服务,并允许他们打开一个可以与服务交互的活动。
为了将您的服务转换为前台服务,您必须为状态栏创建通知,并从服务调用 startForeground()。

0

每次歌曲结束时,服务都会被销毁并重新创建吗? - Metallicraft
不,服务并不会消耗太多的系统资源,除非它们在积极地工作。即使您的应用程序关闭,它也可以始终运行。例如服务:短信接收器、闹钟。 - ahmet alp balkan
我想我应该用不同的措辞。我的意思是,为了连续播放歌曲,您是否创建一个服务,然后当歌曲播放完毕时,销毁该服务并为播放列表中的下一首歌曲创建一个新服务?还是您可以传递要播放的歌曲列表,服务会一直保持活动状态,直到播放完所有歌曲? - Metallicraft
你可以传递一个列表,或者向服务发送消息“播放当前正在播放的音乐并开始播放另一首歌曲”。这完全取决于你如何编写服务。你不应该杀死服务。如果你杀死了服务,它会自动重启。因为服务应该一直运行,只要应用程序说它应该运行。 - ahmet alp balkan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接