文章目录

优化

优化用到的字符串
发送广播时,设置的 Action 的字符串至少用到了 3 次,所以建一个存放常量的类
【达内课程】音乐播放器3.0(下)_apache

public class Consts {
public static final String SET_PLAY_STATE = "set play state";
}

在用到的时候可以这样写

filter.addAction(Consts.SET_PLAY_STATE);

类似的,把其他字符串也放进 Consts

显示歌曲信息的代码

【4】实现播放信息的显示
  1)显示当前正在播放的歌曲的标题和时长
    a)在 Service 的 play() 方法中发出广播,并在 Intent 对象中封装正在播放的歌曲的索引和总时长
    b)在 Activity 中接收对应的广播,并根据 Intent 对象中封装的数据进行显示

PlayMusicService

//播放
private void play() {
try {
......
//发送广播要求界面显示为播放状态
Intent intent = new Intent(Consts.SET_PLAY_STATE);
intent.putExtra(Consts.EXTRA_CURRENT_MUSIC_INDEX, currentMusicIndex);
intent.putExtra(Consts.EXTRA_DURATION, player.getDuration());
sendBroadcast(intent);
} catch (IOException e) {
e.printStackTrace();
}
}

MainActivity

布局中增加显示时间的 TextView

private class InnerReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (Consts.SET_PLAY_STATE.equals(action)) {
ibPlay.setImageResource(android.R.drawable.ic_media_pause);
//获取正在播放的歌曲的索引
int currentMusicIndex = intent.getIntExtra(Consts.EXTRA_CURRENT_MUSIC_INDEX,0);
//设置显示当前正在播放的歌曲的标题和时长
tvMusicInfo.setText(musics.get(currentMusicIndex).getTitle());
tvMusicDuration.setText(CommonUtils.getFormattedTime(intent.getIntExtra(Consts.EXTRA_DURATION,0)));
} else if (Consts.SET_PAUSE_STATE.equals(action)) {
ibPlay.setImageResource(android.R.drawable.ic_media_play);
}
}
}

其中 CommonUtils 是工具类,里边的 ​​getFormattedTime​​是格式化时间显示的方法,我们在音乐播放器 2.0 中写过。

Consts 中还增加了其他一些常量。

到这里播放的歌曲信息就展示出来了
【达内课程】音乐播放器3.0(下)_apache_02

实现进度条的相关功能

  2)显示当前播放到的进度,即更新进度条与显示播放时间的 TextView
    a)声明并初始化 Seekbar 和显示播放时间的 TextView
    b)在 Service 中,使用内部类,声明更新进度的线程
        ------长时间的循环
        ------循环过程中,每隔 1s 休眠 1 次
        ------循环过程中,实时获取当前播放到的时间,及进度的百分比,然后使用广播进行发送
    c)在 Service 中,定义 ​​​startUpdateProgressThread()​​​ 方法和​​stopUpdateProgressThread()​​​,并在 play() 方法的最后开启线程,在 pause() 方法的最后停止线程
    d)在 Activity 中,接收更新进度的线程,并根据封装的数据更新 SeekBar 和显示播放时间的 TextView

PlayMusicService

/**
* 更新进度条的线程
*/
private class InnerThread extends Thread {
//线程的循环控制变量
private boolean isRunning;

public void setRunning(boolean isRunning) {
this.isRunning = isRunning;
}

//不要在循环中new对象
Intent intent = new Intent(Consts.UPDATE_PROGRESS);

@Override
public void run() {
while (isRunning) {
//计算相关数据
//子线程做的事越多越好,主线程做的事越少越好
int currentPosition = player.getCurrentPosition();
int percent = currentPosition * 100 / player.getDuration();

intent.putExtra(Consts.EXTRA_CURRENT_POSITION, currentPosition);
intent.putExtra(Consts.EXTRA_PERCENT, percent);
sendBroadcast(intent);
//休眠
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

//更新进度的线程
private InnerThread innerThread;

/**
* 开启更新进度的线程
*/
private void startUpdateProgressThread() {
if (innerThread == null) {
innerThread = new InnerThread();
innerThread.setRunning(true);
innerThread.start();
}
}

/**
* 关闭更新进度的线程
*/
private void stopUpdateProgressThread() {
if (innerThread != null) {
innerThread.setRunning(false);
innerThread = null;
}
}

//播放
private void play() {
try {
......
//开启更新进度的线程
startUpdateProgressThread();
} catch (IOException e) {
e.printStackTrace();
}
}

//暂停
private void pause() {
......
//停止更新进度的线程
stopUpdateProgressThread();
}

MainActivity

@Override
protected void onCreate(Bundle savedInstanceState) {
......
//注册广播接收者
receiver = new InnerReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction(Consts.SET_PLAY_STATE);
filter.addAction(Consts.SET_PAUSE_STATE);
filter.addAction(Consts.UPDATE_PROGRESS);
registerReceiver(receiver, filter);
}

private class InnerReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (Consts.SET_PLAY_STATE.equals(action)) {
......
} else if (Consts.SET_PAUSE_STATE.equals(action)) {
......
} else if (Consts.UPDATE_PROGRESS.equals(action)) {
int currentPosition = intent.getIntExtra(Consts.EXTRA_CURRENT_POSITION, 0);
int percent = intent.getIntExtra(Consts.EXTRA_PERCENT, 0);
//更新控件
tvMusicCurrentPosition.setText(CommonUtils.getFormattedTime(currentPosition));
skMusicProgress.setProgress(percent);
}
}
}

实现拖拽进度条

  3)实现拖拽进度条
    a)在 Activity 中,为 SeekBar 配置监听器
    b)在 Activity 中,在开始拖拽时,标记为正在拖拽
    c)在 Activity 中,在结束拖拽时,标记为停止拖拽,发出广播,使 Service 实现快进功能
    d)在 Activity 中,在更新进度时,判断是否正在拖拽,如果是,则不要更新进度条
    e)在 Service 中,声明 seekTo(int) 方法,实现快进功能
    f)在 Service 中,接收“快进”的广播,并在接收到时,调用 seekTo(int) 方法

MainActivity实现​​OnSeekBarChangeListener​​接口

public class MainActivity extends AppCompatActivity implements View.OnClickListener, AdapterView.OnItemClickListener, SeekBar.OnSeekBarChangeListener {
......
private void setListeners() {
......
skMusicProgress.setOnSeekBarChangeListener(this);
}
......
//进度条拖拽
//标记是否正在标记进度条
private boolean isTrackingTouch;

@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {

}

@Override
public void onStartTrackingTouch(SeekBar seekBar) {
isTrackingTouch = true;
}

@Override
public void onStopTrackingTouch(SeekBar seekBar) {
isTrackingTouch = false;
//当停止拖拽时发出广播,使service实现快进
Intent intent = new Intent(Consts.SEEK_T0);
intent.putExtra(Consts.EXTRA_SEEK_TO_PERCENT, skMusicProgress.getProgress());
sendBroadcast(intent);
}
......
}

PlayMusicService

public class PlayMusicService extends Service {
......
@Override
public void onCreate() {
......
//注册广播接收者
receiver = new InnerReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction(Consts.PLAY_OR_PAUSE);
filter.addAction(Consts.PLAY_APPOINT);
filter.addAction(Consts.SEEK_T0);
registerReceiver(receiver, filter);
}
......
/**
* 快进到指定位置再开始播放
*
* @param percent 要播放到的百分比,取值例如50
*/
private void seekTo(int percent) {
//停止更新进度的线程
stopUpdateProgressThread();
//计算要快进到的位置,并作为暂停位置
pausePosition = percent * player.getDuration() / 100;
play();
}

/**
* 内部广播接收者
*/
private class InnerReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
//获取广播中的Action
String action = intent.getAction();
//判断Action
if (Consts.PLAY_OR_PAUSE.equals(action)) {
......
} else if (Consts.SEEK_T0.equals(action)) {
//获取快进到的位置
int percent = intent.getIntExtra(Consts.EXTRA_PERCENT, 0);
seekTo(percent);
}
}
}
......
}

处理细节,修复bug

【5】处理细节,修复 Bug
  1)在没有开始播放任何一首歌之前,不应该拖拽进度条
  2)当界面被置于后台,则不应该再更新播放进度
  3)当界面被置于后台,如果自动切换到下一首歌曲,也不应该再重新开启更新进度的线程
  4)拖拽进度条时,如果没有获取到当前歌曲的总时长,或者有效的当前播放的位置,则不应该更新进度,表现为在播放过程中拖拽进度条可能会出现闪退
  5)退出应用程序时,应该释放资源

剩余的这些bug都是 2.0 处理过的,大家可以自行练习

源码下载

​源码下载​