最简单的音乐播放器,实现基本功能(二)

时间:2022-02-01 01:50:41

上一篇写到点击列表中的一项,就能实现播放的功能,接下来就要继续写咯...

1.播放完一个音乐怎么让播放器继续播放,MediaPlayer这个类提供了音乐播放完毕监听事件:setOnCompletionListener。

我们要想在这音乐结束后继续播放下一个,就必须知道当前播放的音乐在列表中的位置,所以在启动PlayerService的时候,让intent对象多携带一个音乐的位置数据,在PlayerSerivce进行获取

musicPosition = intent.getExtras().getInt("musicPosition");
获取到这个position之后,我们可以用对其进行+1操作,然后启动+1这个位置的音乐url。

mediaPlayer.setOnCompletionListener(new OnCompletionListener()
{
@Override
public void onCompletion(MediaPlayer arg0) {
//musicList可以用工具类GetMusicListUtil里面的函数得到,是List<Mp3Info>类型
musicPosition = musicPosition + 1;
path = musicList.get(musicPosition).getUrl();
play(0);

}
});
这样就实现继续播放下一首音乐的目的了。

而在主界面中最容易实现的就是前一首歌曲和下一首歌曲的播放功能了。

首先,声明一个listPosition变量来记录当前播放音乐在listView中的位置。然后分为以下两种情况:

第一种情况,刚打开播放器,这时候用户用手机点击某一个音乐,会触发listView的OnItemClickListener事件,这个事件中的函数参数有一个就是position,直接将这个position赋值给listPosition变量就可以。此时用户再点击上一首歌曲(或夏一首歌曲)直接对listPosition-(+)1操作,然后得到这个位置的音乐url,然后启动服务播放就行。

第二种情况,用户点击列表中的一首音乐,这首歌播放完毕,而PlayerService监听自动播放下一首歌,但是listPosition这个变量的值并未改变,若是此时点击下一首歌曲,就会重新播放当前的这个歌曲。所以当音乐换了的时候,我们需要让listPosition也知道,它应该改变。这里我用到的是LocalBroadcastManager这个类。

LocalBroadcastManager是用来在应用程序内部进行广播消息的类。当音乐播放完毕事件触发时,发送一个消息给主界面,告诉它现在播放的音乐已经变了,让其改变listPosition的值。具体用法:

在MainActivity中写一个广播接收器:

private LocalBroadcastManager broadcastManager;


broadcastManager = LocalBroadcastManager.getInstance(MainActivity.this);
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("这是个String字符串,随意起个名字:music_update");
BroadcastReceiver receiver = new BroadcastReceiver(){
@Override
public void onReceive(Context context, Intent intent) {
if(intent.getAction().equals(Constants.MUSIC_UPDATE))
{
listPosition = intent.getExtras().getInt("musicPosition");
}

}
};
broadcastManager.registerReceiver(receiver, intentFilter);

在PlayerService中进行广播放送:

//也是先声明LocalBroadcastManager对象
broadcastManager = LocalBroadcastManager.getInstance(PlayerService.this);

//传递信息给主界面,让其改变显示信息
Intent intent = new Intent("这个String名字就是你在上面的那个:music_update");
intent.putExtra("musicPosition", musicPosition);
broadcastManager.sendBroadcast(intent);


把这个放送的代码放在
setOnCompletionListener{}这个类的函数中就可以,当音乐完毕事件被触发,播放下一首音乐,就传递消息给主界面MainActivity。

在这里放上播放下一首歌曲的函数:

private void next()
{
listPosition = listPosition + 1;
if(listPosition <= musicInfo.size()-1)
{
music = musicInfo.get(listPosition);
}
else
{
music = musicInfo.get(0);
}
Intent intent = new Intent();
intent.setAction("org.com.ViPlayer.MUSIC_SERVICE");
intent.putExtra("url", music.getUrl());
intent.putExtra("musicPosition", listPosition);
intent.putExtra("msg", 0);//0代表播放音乐哦
startService(intent);
Toast.makeText(MainActivity.this, "播放下一首歌曲", Toast.LENGTH_SHORT).show();
}

这样,到目前为止。。。播放上一首歌曲和下一首歌曲还有继续播放的功能就实现了哦。

接下来就是音乐模式的问题:

我设定的音乐模式只有三种类型,单曲循环播放,顺序循环播放以及随机播放。如果想要把顺序播放列表一次和顺序循环播放分开的同学,可以自行添加。

在这里稍微做一点小修改,改变一下代码结构,将用到的一些常量代表值放在一个Constants的类中:

public class Constants {

//播放,暂停,停止
public static final int PLAY_MSG = 1;
public static final int PAUSE_MSG = 2;
public static final int STOP_MSG = 3;

//单曲重复,全部重复,随机播放,顺序播放一次
public static final int REPEAT_ONE = 4;
public static final int REPEAT_ALL = 5;
public static final int REPEAT_RANDOM = 6;
public static final int REPEAT_NONE = 7;

//更新播放模式动作
public static final String MODE_UPDATE = "update_music_mode";
//更新歌曲信息,用来最下的显示
public static final String MUSIC_UPDATE = "update_music_info";
}

将启动播放服务写成一个函数叫做startService,提取公共部分的代码出来:
private void startService()
{
Intent intent = new Intent();
intent.setAction("org.com.ViPlayer.MUSIC_SERVICE");
intent.putExtra("url", music.getUrl());
intent.putExtra("musicMode", musicMode);
intent.putExtra("musicPosition", listPosition);
intent.putExtra("msg", Constants.PLAY_MSG);
startService(intent);
}
给里面的按钮统一设定点击事件(listView除外):

        repeat.setOnClickListener(this);//repeat代表的是音乐模式的按钮
previous.setOnClickListener(this);//前一首歌
play.setOnClickListener(this);//播放按钮
next.setOnClickListener(this);//后一首歌
listView.setOnItemClickListener(new MusicItemClickListener());

点击事件的处理:

首先,用户第一次打开播放器的时候,默认模式是顺序循环播放的,当用户点击模式按钮repeat的时候,模式由(顺序循环)转换为(单曲循环),再点击一次,模式由(单曲循环)转换为(随机模式),再点击一次,模式变回(顺序循环)。这期间每点击一次,图标按钮就改变一次,setBackgroundResource改变按钮图标,用Intent对象携带音乐模式数据,然后同理用LocalBroadcastManager播放给PlayerService知道

public void onClick(View v) {
switch(v.getId())
{
case R.id.repeat:
Intent intent = new Intent(Constants.MODE_UPDATE);
if(musicMode == Constants.REPEAT_NONE)
{
musicMode = Constants.REPEAT_ONE;
intent.putExtra("musicMode", musicMode);
repeat.setBackgroundResource(R.drawable.repeat_one);
Toast.makeText(MainActivity.this, "单曲循环模式", Toast.LENGTH_SHORT).show();
}
else if(musicMode == Constants.REPEAT_ONE)
{
musicMode = Constants.REPEAT_RANDOM;
intent.putExtra("musicMode", musicMode);
repeat.setBackgroundResource(R.drawable.random);
Toast.makeText(MainActivity.this, "随机模式", Toast.LENGTH_SHORT).show();
}
else if(musicMode == Constants.REPEAT_RANDOM)
{
musicMode = Constants.REPEAT_NONE;
intent.putExtra("musicMode", musicMode);
repeat.setBackgroundResource(R.drawable.repeat_none);
Toast.makeText(MainActivity.this, "顺序循环模式", Toast.LENGTH_SHORT).show();
}
broadcastManager.sendBroadcast(intent);
break;
//如果想要把顺序播放和顺序重复播放分开,再另外写
/*else if(musicMode == Constants.REPEAT_ALL)
{
musicMode = Constants.REPEAT_NONE;
repeat.setBackgroundResource(R.drawable.repeat_none);
}*/
case R.id.previous:
previous();
break;
case R.id.playMusic:
playMusic();
break;
case R.id.next:
next();
break;

}

}

在PlayerService中监听音乐模式:

private void initReceiver()
{
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Constants.MODE_UPDATE);
BroadcastReceiver receiver = new BroadcastReceiver(){
@Override
public void onReceive(Context context, Intent intent) {
if(intent.getAction().equals(Constants.MODE_UPDATE))
{
musicMode = intent.getExtras().getInt("musicMode");
}

}
};
broadcastManager.registerReceiver(receiver, intentFilter);
}


在PlayerService的音乐结束事件监听函数中判断当前音乐播放模式:

mediaPlayer.setOnCompletionListener(new OnCompletionListener()
{
@Override
public void onCompletion(MediaPlayer arg0) {
//顺序播放,设定本身就是顺序循环播放
if(musicMode == Constants.REPEAT_NONE)
{
if(musicPosition < musicList.size()-1)
{
musicPosition = musicPosition + 1;
}
else
{
musicPosition = 0;
}
}
//单曲循环
else if(musicMode == Constants.REPEAT_ONE)
{
//musicPosition = musicPosition;
}

/*else if(musicMode == Constants.REPEAT_ALL)
{
}*/

//随机播放
else if(musicMode == Constants.REPEAT_RANDOM)
{
musicPosition = randomPosition(musicList.size());
}
path = musicList.get(musicPosition).getUrl();
play(0);
//传递信息给主界面,让其改变显示信息
Intent intent = new Intent(Constants.MUSIC_UPDATE);
intent.putExtra("musicPosition", musicPosition);
broadcastManager.sendBroadcast(intent);
}
});
randomPosition是一个得到0-musicList.size()之间随机数的函数。


一口气直接写完,音乐模式已经写完了,接下来就是音乐的暂停和继续播放问题。

MediaPlayer这个类有一个seekTo(int position)函数,就是用于在当前的position(是个时间)继续播放。好了,最重要的已经有了,就差不重要的了。

在点击列表中的一个音乐开始播放后,播放按钮就应该改变图标,变成可暂停状态,我们在MainActivity中设定一个isPause的布尔变量,初始化为false,就是不暂停的意思。然后再播放歌曲的函数中设定暂停或者是开启音乐:

/**
* 播放歌曲
* 转换播放和暂停的图标
*/
private void playMusic()
{
if(isPause == true)
{//暂停中
play.setBackgroundResource(R.drawable.play);
startPauseService();
isPause = false;
}
else
{//播放中
play.setBackgroundResource(R.drawable.pause);
startService();
isPause = true;
}
}
注意,else启动的是开启播放服务,if启动的是开启暂停服务。暂停服务只有msg的数据和播放服务不一样,告诉PlayerService我是来启动你的暂停音乐播放功能的。

private void startPauseService()
{
Intent intent = new Intent();
intent.setAction("org.com.ViPlayer.MUSIC_SERVICE");
intent.putExtra("musicMode", musicMode);
intent.putExtra("url", music.getUrl());
intent.putExtra("musicPosition", listPosition);
intent.putExtra("msg", Constants.PAUSE_MSG);
startService(intent);
}

而在PlayerService中,接收来自主界面的信息进行服务:

public int onStartCommand(Intent intent, int flags, int startId) {
//从上级页面取到音乐源和播放标识,播放位置
path = intent.getStringExtra("url");
musicMode = intent.getIntExtra("musicMode", Constants.REPEAT_NONE);
int msg = intent.getIntExtra("msg", 0);
musicPosition = intent.getExtras().getInt("musicPosition");
if(msg == Constants.PLAY_MSG)
{
//如果当前状态是一首歌的暂停状态
if(isPause == true)
{//那么需要继续从currentPosition位置播放
play(timePosition);
}
else
{//如果当前未播放歌曲,则从头开始播放
play(0);
}
}
else if(msg == Constants.STOP_MSG)
{
stop();
}
else if(msg == Constants.PAUSE_MSG)
{
pause();
}
return super.onStartCommand(intent, flags, startId);
}
其中的timePosition很重要,是从哪里来呢,是在pause()函数中记录的。
/** * 播放音乐 *  * @param position */public void play(int position){try{mediaPlayer.reset();mediaPlayer.setDataSource(path);mediaPlayer.prepare();//注册监听器mediaPlayer.setOnPreparedListener(new PreparedListener(position));}catch(Exception e){e.printStackTrace();}//当前是播放状态,则暂停标识为falseisPause = false;}/** * 暂停音乐 * 每次暂停isPause标识设为false并得到当前的播放位置 */public void pause(){if(mediaPlayer != null){mediaPlayer.pause();isPause = true;timePosition = mediaPlayer.getCurrentPosition();}}/** * 停止音乐 * 这个stop和pause不一样,stop之后再想播放音乐要重新prepare一次 *  */public void stop(){if(mediaPlayer != null){mediaPlayer.stop();try{mediaPlayer.prepare();}catch(Exception e){e.printStackTrace();}}}

这样,最基本的功能就实现咯。 其中还有很多细节:比如说打开播放器,没有点击音乐列表进行播放,而是直接点击播放的按钮,这个问题可以在初始时给其一个默认播放的音乐,比如说listPosition是0的音乐(列表中的第一首歌)。 还有列表下方显示歌曲信息,项目中也写了,因为LocalBroadcastManager原理一样,就没说明。

同学们有需要可以自行在其中添加音乐播放进度条,或者是列表中显示专辑封面图片等需求。。。。


这个界面很一般,没什么设计感= =。 就是随意学个知识,大家不要吐槽。。。稍后就上传源码咯。。。

最后的版本就是酱紫。。。

最简单的音乐播放器,实现基本功能(二)