这篇是讲后台控制歌曲播放service的代码。我自己感觉解析没怎么写好,看的有问题就提,我会解答的。
嗯,最进弄到了个制作gif的软件,我在最后一篇,发送个软件运行的动态图和这个程序的整体代码逻辑。
哎!新人博客还是没什么人看,自己加油吧!!!!
package com.verzqli.service;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.MediaPlayer;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.support.annotation.Nullable;
import android.util.Log;
import android.widget.Toast;
import com.lidroid.xutils.HttpUtils;
import com.lidroid.xutils.exception.HttpException;
import com.lidroid.xutils.http.HttpHandler;
import com.lidroid.xutils.http.ResponseInfo;
import com.lidroid.xutils.http.callback.RequestCallBack;
import com.verzqli.fragment.SongLrcFragment;
import com.verzqli.model.LrcSong;
import com.verzqli.model.PlaySong;
import com.verzqli.model.Song;
import com.verzqli.music.MusicPlayActivity;
import com.verzqli.utils.AppControl;
import com.verzqli.utils.LogUtil;
import com.verzqli.utils.LrcGet;
import com.verzqli.utils.MusicLoader;
import org.litepal.crud.DataSupport;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class MusicPlayService extends Service {
private Intent intent; //获取actyivity传来的参数
private String url; //歌曲路径
private MediaPlayer mediaPlayer; //创建播放器
private List<Song> songListService; //歌曲列表
public int currentTime; //当前播放时间
public int duration; //歌曲总长度
private int currentSongPosition; //当前播放音乐位置
private int MSG; //播放按键处理事件
private Boolean isPause = false; //是否暂停
private int progress; //歌曲进度
private int state = 2; //控制歌曲的状态,随机还是循环
private MySongStateReceiver mySongStateReceiver; //自定义广播接收器
private ContentValues values; //存入数据库的歌曲状态
private LrcGet lrcGet;
private List<LrcSong> lrcSongList;
private int index = 0; //歌词检索值
//服务要发送的一些Action
public static final String SONG_STATE_UPDATE = "com.verzqli.SONG_STATE_UPDATE"; //更新动作
public static final String MUSIC_CURRENT = "com.verzqli.action.MUSIC_CURRENT"; //音乐当前时间改变动作
public static final String MUSIC_DURATION = "com.verzqli.action.MUSIC_DURATION";//音乐播放长度改变动作
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (msg.what == 1) {
if (mediaPlayer != null) {
/**
* 开始歌曲播放的重要部分了,歌曲的后台service部分
* 这个是一秒发送一次广播,发送的是当前歌曲的当前进度,
* 发送给播放界面,让其显示在进度条上。
*/
currentTime = mediaPlayer.getCurrentPosition(); // 获取当前音乐播放的位置
Intent intent = new Intent();
intent.setAction(MUSIC_CURRENT);
intent.putExtra("currentTime", currentTime);
sendBroadcast(intent);
handler.sendEmptyMessageDelayed(1, 1000);
}
}
}
};
@Override
public void onCreate() {
super.onCreate();
//这两个是关于歌词的部分
lrcGet = new LrcGet();
lrcSongList = new ArrayList<LrcSong>();
//这个数据库的工具是郭霖的litepal,不知道的可以去看郭霖大神的博客
values = new ContentValues();
//这个获取的是歌曲列表,好方便后面调用
songListService = MusicLoader.getInstance(getApplicationContext().getContentResolver()).queryData();
if (songListService == null) {
Intent intent = new Intent();
intent.setAction(SONG_STATE_UPDATE);
intent.putExtra("currentSongPosition", currentSongPosition);
sendBroadcast(intent);
}
//初始化播放器
mediaPlayer = new MediaPlayer();
//
/**
* 这个是判断歌曲结束后的时间,当歌曲播放完成后,他会根据歌曲的循环模式
* 来对歌曲播放完之后进行一个判断,是循环播放,还是随机播放等等。
* 这个state值默认为2,也就是全部循环,当你点击上一篇博客中讲到的播放界面时,
* 会把这个值传过来,然后歌曲播放完了进行判断下一首播放什么。
*/
mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
if (state == 1) {
mediaPlayer.start(); //单曲循环
} else if (state == 2) {
currentSongPosition++;
if (currentSongPosition > songListService.size() - 1) {
currentSongPosition = 0; //重复播放
}
Intent intent = new Intent();
intent.setAction(SONG_STATE_UPDATE);
intent.putExtra("currentSongPosition", currentSongPosition);
// 发送广播,将被Activity组件中的BroadcastReceiver接收到
sendBroadcast(intent);
url = songListService.get(currentSongPosition).getData();
//这个是播放的方法,参数是播放的开始位置。
playMusic(0);
} else if (state == 3) {
currentSongPosition = getRandomSongPosition(songListService.size() - 1);
Intent intent = new Intent();
intent.setAction(SONG_STATE_UPDATE);
intent.putExtra("currentSongPosition", currentSongPosition);
sendBroadcast(intent);
url = songListService.get(currentSongPosition).getData();
playMusic(0);
}
}
});
//注册广播
mySongStateReceiver = new MySongStateReceiver();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(MusicPlayActivity.SONG_STATE);
intentFilter.addAction(AppControl.LRC_SONG_CONTINUE);
intentFilter.addAction(AppControl.LRC_DOWNLOAD);
intentFilter.addAction(AppControl.NOTIFICATION_ACTION);
registerReceiver(mySongStateReceiver, intentFilter);
}
/**
* 获得随机播放的
*
* @param all 歌曲总数
* @return
*/
private int getRandomSongPosition(int all) {
return (int) (Math.random() * all);
}
@Override
public void onStart(Intent intent, int startId) {
super.onStart(intent, startId);
/**
* 下面这个intent为空的判断格外重要,当你退出界面后,歌曲还在后台播放,
* 此时你通过关闭进程的方式强行关掉程序,但是这个service没有被正确方式关掉,
* 它会产生一个service被杀死后重启的异常,重启之后,这个intent传过来的
* 值百分百就为空了,这个异常,当初找了很久,但是没有找到解决办法,
* 只好用这种粗糙的方式强行堵上。
* 后来问了下群里的小伙伴,他们说用AIDL的方式可以解决,你们如果写可以试试。
*/
if (intent == null) {
Intent service = new Intent();
service.setAction(AppControl.SERVICE_STOP);
sendBroadcast(service);
} else {
currentSongPosition = intent.getIntExtra("position", -1);
url = intent.getStringExtra("url");
currentTime = intent.getIntExtra("currentTime", 0);
MSG = intent.getIntExtra("MSG", 0);
switch (MSG) {
case 0:
initLrc(); //初始化歌词设置
playMusic(0);
break;
case 4:
initLrc();
//AppControl.MEDIA_PLAYING_NEXT_SONG下一首
playMusic(0);
break;
case 5:
initLrc();//AppControl.MEDIA_PLAYING_NEXT_SONG上一首
playMusic(0);
break;
case 6: //AppControl.MEDIA_PLAYING_SONG_PAUSE 暂停
songPause();
break;
case 7: //AppControl.MEDIA_PLAYING_SONG_CONTINUE继续播放
songContinue();
break;
case 8:
initLrc(); //初始化歌词设置,使拖动进度条之后也能让歌词跟上进度
progress = intent.getIntExtra("progress", -1);
playMusic(progress);
break;
case 9:
progress = intent.getIntExtra("progress", -1);
playMusic(progress);
break;
}
}
}
/**
* 初始化歌词配置
*/
public void initLrc() {
lrcGet = new LrcGet();
//读取歌词文件
lrcGet.readLRC(songListService.get(currentSongPosition).getData());
//传回处理后的歌词文件
lrcSongList = lrcGet.getLrcList();
if (SongLrcFragment.lrcView != null) {
SongLrcFragment.lrcView.setmLrcList(lrcSongList);
//切换带动画显示歌词
//MusicPlayActivity.lrcView.setAnimation(AnimationUtils.loadAnimation(MusicPlayService.this, R.anim.alpha_z));
handler.post(mRunnable);
}
}
/**
* 下载歌词
*/
private void downLoadLrc(String num) {
HttpUtils http = new HttpUtils();
String str = "/storage/sdcard0/" + songListService.get(currentSongPosition).getDisplayName();
String url = str.substring(0, str.length() - 4) + ".lrc";
LogUtil.itSelf.Log("downLoadLrc" + num);
HttpHandler handler = http.download("http://music.baidu.com/data2/lrc/" + num + "/" + num + ".lrc",
url,
true, // 如果目标文件存在,接着未完成的部分继续下载。服务器不支持RANGE时将从新下载。
false, // 如果从请求返回信息中获取到文件名,下载完成后自动重命名。
new RequestCallBack<File>() {
@Override
public void onStart() {
Log.i("TAG", "conn...");
}
@Override
public void onLoading(long total, long current, boolean isUploading) {
}
@Override
public void onSuccess(ResponseInfo<File> responseInfo) {
Toast.makeText(getApplicationContext(), "下载完成", Toast.LENGTH_SHORT).show();
initLrc();
}
@Override
public void onFailure(HttpException error, String msg) {
Toast.makeText(getApplicationContext(), "加载中失败", Toast.LENGTH_SHORT).show();
}
});
}
Runnable mRunnable = new Runnable() {
@Override
public void run() {
SongLrcFragment.lrcView.setIndex(lrcIndex());
SongLrcFragment.lrcView.invalidate();
handler.postDelayed(mRunnable, 1000);
}
};
/**
* 根据时间获取歌词显示的索引值
*
* @return
*/
public int lrcIndex() {
if (mediaPlayer.isPlaying()) {
currentTime = mediaPlayer.getCurrentPosition();
duration = mediaPlayer.getDuration();
}
if (currentTime < duration) {
for (int i = 0; i < lrcSongList.size(); i++) {
if (i < lrcSongList.size() - 1) {
if (currentTime < lrcSongList.get(i).getLrcTime() && i == 0) {
index = i;
}
if (currentTime > lrcSongList.get(i).getLrcTime()
&& currentTime < lrcSongList.get(i + 1).getLrcTime()) {
index = i;
}
}
if (i == lrcSongList.size() - 1
&& currentTime > lrcSongList.get(i).getLrcTime()) {
index = i;
}
}
}
return index;
}
/**
* 继续播放
*/
private void songContinue() {
if (isPause) {
mediaPlayer.start();
isPause = false;
}
}
/**
* 暂停播放
*/
private void songPause() {
if (mediaPlayer != null && mediaPlayer.isPlaying()) {
mediaPlayer.pause();
isPause = true;
}
}
/**
* 播放音乐
*
* @param currentTime
*/
private void playMusic(int currentTime) {
try {
Intent intent = new Intent();
intent.setAction(SONG_STATE_UPDATE);
intent.putExtra("currentSongPosition", currentSongPosition);
// 发送广播,将被Activity组件中的BroadcastReceiver接收到
sendBroadcast(intent);
mediaPlayer.reset();
mediaPlayer.setDataSource(url);
mediaPlayer.prepare();
mediaPlayer.setOnPreparedListener(new PrepareListener(currentTime));
handler.sendEmptyMessage(1);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 歌曲准备阶段接口
*/
public class PrepareListener implements MediaPlayer.OnPreparedListener {
private int pcurrentTime;
public PrepareListener(int currentTime) {
this.pcurrentTime = currentTime;
}
@Override
public void onPrepared(MediaPlayer mp) {
mediaPlayer.start(); // 开始播放
if (pcurrentTime > 0) { // 如果音乐不是从头播放
mediaPlayer.seekTo(pcurrentTime);
}
Song song = songListService.get(currentSongPosition);
Intent intent = new Intent();
intent.setAction(MUSIC_DURATION);
duration = mediaPlayer.getDuration();
intent.putExtra("duration", duration); //通过Intent来传递歌曲的总长度
intent.putExtra("text", song.getDisplayName());
//
/**
* 这个是存入当前播放的歌曲进入数据库,
* 前面我也说到了,我要的效果是退出了界面也能播放歌曲并能更新进度
* 就是在准备阶段将这个歌曲存入数据库,当进入程序界面时,能从数据库中准确取到当前播放的歌曲信息
*/
values.put("displayName", song.getDisplayName());
values.put("duration", song.getDuration());
values.put("songPosition", currentSongPosition);
DataSupport.update(PlaySong.class, values, 1);
sendBroadcast(intent);
}
}
public final static String INTENT_BUTTONID_TAG = "INTENT_BUTTONID_TAG";
private class MySongStateReceiver extends BroadcastReceiver {
//这里就是接受前两个界面点击事件时发送过来的广播
@Override
public void onReceive(Context context, Intent intent) {
int states = intent.getIntExtra("state", -1);
switch (states) {
case 1:
state = 1;
break;
case 2:
state = 2;
break;
case 3:
state = 3;
break;
case 4:
state = 4;
break;
}
String action = intent.getAction();
//AppControl.LRC_SONG_CONTINUE这些数据是一个专门存数据的类。下了代码你们就可以看到了
if (action.equals(AppControl.LRC_SONG_CONTINUE)) {
currentSongPosition = intent.getIntExtra("position", -1);
url = intent.getStringExtra("url");
currentTime = intent.getIntExtra("mainCurrentTime", 0);
initLrc();
}
if (action.equals(AppControl.LRC_DOWNLOAD)) {
String songLrcId = intent.getStringExtra("songLrdId");
downLoadLrc(songLrcId);
}
//这个是通知栏发送过来对歌曲播放控制的广播
if (action.equals(AppControl.NOTIFICATION_ACTION)) {
String buttonId = intent.getStringExtra(INTENT_BUTTONID_TAG);
switch (buttonId) {
case "prev":
if (currentSongPosition - 1 >= 0) {
currentSongPosition = currentSongPosition - 1;
url = songListService.get(currentSongPosition).getData();
playMusic(0);
Toast.makeText(getApplicationContext(), "上一首", Toast.LENGTH_SHORT).show();
} else {
currentSongPosition = songListService.size() - 1;
url = songListService.get(currentSongPosition).getData();
playMusic(0);
}
break;
case "play":
if (isPause) {
mediaPlayer.start();
isPause = false;
} else {
mediaPlayer.pause();
isPause = true;
}
Toast.makeText(getApplicationContext(), "play", Toast.LENGTH_SHORT).show();
break;
case "next":
if (currentSongPosition + 1 <= songListService.size() - 1) {
currentSongPosition = currentSongPosition + 1;
url = songListService.get(currentSongPosition).getData();
playMusic(0);
Toast.makeText(getApplicationContext(), "上一首", Toast.LENGTH_SHORT).show();
} else {
currentSongPosition = 0;
url = songListService.get(currentSongPosition).getData();
playMusic(0);
}
break;
default:
break;
}
}
}
}
@Override
public void onDestroy() {
super.onDestroy();
//ondestory中记得要反注册广播,关闭handler,关闭播放器,防止内存泄露
handler.removeCallbacks(mRunnable);
handler.removeMessages(1);
if (mediaPlayer != null) {
mediaPlayer.release();
}
unregisterReceiver(mySongStateReceiver);
AppControl.JUDGE_DESTORY_ONCREATE = -1;
values.put("playing", false);
//关闭程序时存下当前歌曲,再次进入程序时看到的是上次播放的那首
DataSupport.update(PlaySong.class, values, 1);
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
}