hunterliy小作品之 HunterMusic音乐播放器(Day2-后台播放服务实现)

时间:2021-08-15 10:15:53

1.1完成音乐播放布局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/play_layout"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#e0e0e0"
tools:context="com.example.administrator.huntermusic.activity.AudioPlayActivity">

<TextView
android:id="@+id/music_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_gravity="center"
android:text="歌曲名"
android:textSize="20sp"
android:layout_marginTop="30dp"/>

<TextView
android:id="@+id/singer_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="歌手"
android:layout_margin="5dp"
android:layout_gravity="center"
android:gravity="center"/>

<com.example.administrator.huntermusic.utils.CDView
android:id="@+id/CD_view"
android:layout_marginBottom="80dp"
android:layout_marginTop="50dp"
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_gravity="center">

</com.example.administrator.huntermusic.utils.CDView>
<TextView
android:id="@+id/tv_position"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:text="00:00/00:00"
android:textSize="15sp"
android:layout_margin="10dp"
/>

<SeekBar
android:id="@+id/seekbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:layout_marginLeft="4dp"
android:layout_marginRight="4dp"
android:layout_margin="20dp"
android:progress="0"
android:thumbOffset="0dp"
/>

<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
>

<LinearLayout
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:gravity="center">

<ImageView
android:id="@+id/play_mode"
android:layout_width="32dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="10dp"
/>

<ImageView
android:id="@+id/pre"
android:layout_width="32dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="10dp"
android:background="@drawable/pre"/>

<ImageView
android:id="@+id/play"
android:layout_width="64dp"
android:layout_height="wrap_content"
android:layout_margin="10dp"
/>

<ImageView
android:id="@+id/next"
android:layout_width="32dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="10dp"
android:background="@drawable/next"/>

<ImageView
android:id="@+id/playlist"
android:layout_width="32dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="10dp"
android:background="@drawable/playlist"/>

</LinearLayout>
</LinearLayout>
</LinearLayout>

其中的CDView可以先使用一个ImageView来代替,之后我们再慢慢完善。最终效果如下:
hunterliy小作品之 HunterMusic音乐播放器(Day2-后台播放服务实现)

1.2播放音乐

播放音乐需要在服务中播放,因为在退出界面的时候,音乐还在播放,这导致音乐的不可控制。所以为了便于控制我们将播放的逻辑放在服务中。那么就需要将数据也传递到服务中。

AudioPlayActivity .java

package com.example.administrator.huntermusic.activity;

import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.res.Resources;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.view.Window;


public class AudioPlayActivity extends AppCompatActivity implements View.OnClickListener{

private AudioServiceConnection audioServiceConnection;
private AudioPlayService.AudioBinder binder;


@Override
protected void onCreate(Bundle savedInstanceState) {
requestWindowFeature(Window.FEATURE_NO_TITLE);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_audio_play);
initData();
initView();

}

@Override
protected void onDestroy(){
super.onDestroy();
unbindService(audioServiceConnection);

}

public void initData(){
Intent service = new Intent(getIntent());
service.setClass(this, AudioPlayService.class);
audioServiceConnection = new AudioServiceConnection();
bindService(service, audioServiceConnection, BIND_AUTO_CREATE);
startService(service);
}





public class AudioServiceConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder iBinder) {
binder = (AudioPlayService.AudioBinder) iBinder;
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
}
}

在服务中播放音乐

AudioPlayService .java

package com.example.administrator.huntermusic.service;

import android.app.Service;
import android.content.Intent;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Binder;
import android.os.IBinder;
import com.example.administrator.huntermusic.bean.AudioItem;
import java.io.IOException;


public class AudioPlayService extends Service {
private AudioBinder binder;
private List<AudioItem> audioItemList;
private int position;

@Override
public IBinder onBind(Intent intent) {
binder = new AudioBinder();
return binder;
}

@Override
public void onCreate() {
super.onCreate();

}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
audioItemList = (List<AudioItem>) intent.getSerializableExtra("audioItems");
position = intent.getIntExtra("position", -1);
binder.play();
return super.onStartCommand(intent, flags, startId);
}

@Override
public boolean onUnbind(Intent intent) {
return super.onUnbind(intent);
}

@Override
public void onDestroy() {
super.onDestroy();
}



public class AudioBinder extends Binder{
private MediaPlayer media_player;
public void play(){
if (media_player!=null){
media_player.reset();
media_player.release();
media_player = null;

}
AudioItem audioItem = audioItemList.get(position);
media_player = new MediaPlayer();
backIsPlay = true;
try {
media_player.setDataSource(AudioPlayService.this, Uri.parse(audioItem.getPath()));
media_player.prepare();
media_player.setOnPreparedListener(new OnAudioPreparedListener(media_player.getCurrentPosition()));
media_player.start();
} catch (IOException e) {
e.printStackTrace();
}
}
}

1.3 完成界面功能

1.3.1在 AudioBinder 中提供音乐暂停开始的方法。在AudioPlayActivity 中点击开始按钮调用暂停开始的方法。

public void pause(){
media_player.pause();
backIsPlay = false;
}
public void start(){
media_player.start();
backIsPlay = true;
}
public boolean isPlaying(){
return media_player.isPlaying();
}

AudioPlayActivity 继承View .OnClickListener接口,在 AudioPlayActivity 中调用开始暂停播放的方法

@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.play :
switchPlayState();
break;
}
private void switchPlayState() {
if (binder.isPlaying()){
binder.pause();
}else{
binder.start();
}
updataPlayButton();
}

更新播放按钮

private void updataPlayButton(){
if (binder.isPlaying()) {
play_button.setImageResource(R.drawable.pause_shadow);
} else {
play_button.setImageResource(R.drawable.play_shadow);
}
}

当音乐准备完成后需要改变界面按钮的状态为暂停按钮,并且初始化接收到的歌曲名和歌手名。在服务中发送广播,在界面中注册广播之后更新界面。

public class AudioBinder extends Binder{
private MediaPlayer media_player;
public void play(){
if (media_player!=null){
media_player.reset();
media_player.release();
media_player = null;

}
AudioItem audioItem = audioItemList.get(position);
media_player = new MediaPlayer();
backIsPlay = true;
try {
media_player.setDataSource(AudioPlayService.this, Uri.parse(audioItem.getPath()));
media_player.prepare();
media_player.setOnPreparedListener(new OnAudioPreparedListener(media_player.getCurrentPosition()));
media_player.start();
} catch (IOException e) {
e.printStackTrace();
}
public void pause(){
media_player.pause();
backIsPlay = false;
}
public void start(){
media_player.start();
backIsPlay = true;
}
public boolean isPlaying(){
return media_player.isPlaying();
}

private class OnAudioPreparedListener implements MediaPlayer.OnPreparedListener{
@Override
public void onPrepared(MediaPlayer mp) {
mp.start();
Intent intent = new Intent("com.work.huntermusic.updateplaybtn");
//获取当前正在播放的音乐
AudioItem audioItem = audioItemList.get(position);
intent.putExtra("audioItem",audioItem);
sendBroadcast(intent);
}
}
}

在 AudioPlayActivity 中注册广播,更新界面

IntentFilter intentFilter = new IntentFilter("com.work.huntermusic.updateplaybtn");
registerReceiver(new AudioBroadCastReceiver(), intentFilter);

private class AudioBroadCastReceiver extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
//更新界面的按钮
updataPlayButton();
AudioItem audioItem = (AudioItem) intent.getSerializableExtra("audioItem");
music_name.setText(audioItem.getName());
singer_name.setText(audioItem.getSinger());
}
}

1.3.2时间进度更新
在服务中的 AudioBinder中提供获取播放总时长和当前播放进度的方法, 当 MediaPlayer准备完成后播放界面收到广播发消息开始更新

在 AudioBinder 中提供获取总时长和当前播放进度的方法

/** 获取音乐的播放总时长*/
public int getDuration(){
return media_player.getDuration();
}
/** 获取音乐当前播放进度*/
public int getCurrentPosition(){
return media_player.getCurrentPosition();
}

在播放界面收到广播之后开始发消息更新

private class AudioBroadCastReceiver extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
//更新界面的按钮
updataPlayButton();
AudioItem audioItem = (AudioItem) intent.getSerializableExtra("audioItem");
music_name.setText(audioItem.getName());
singer_name.setText(audioItem.getSinger());
//更新播放进度
updateCurrentPosition();
}
}
public void updataCurrentPosition(){
int duration=binder.getDuration();
int position=binder.getCurrentPosition();
seekbar.setMax(binder.getDuration());
String durationStr= StringUtil.formatDuration(duration);
String positionStr=StringUtil.formatDuration(position);
tv_position.setText(positionStr + " / " + durationStr);
handler.sendEmptyMessageDelayed(UPDATA_POSITION,500);
}

private static final int UPDATA_POSITION = 0;
private Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case UPDATA_POSITION:
updataCurrentPosition();
break;

}
}
};

这里给出StringUtil中时间转换的方法formatDuration()

public static String formatDuration(int duration){
int hour = 60*60*1000;
int minute = 60*1000;
int second = 1000;

int ghour = duration/hour;
int temp = duration%hour;

int gminute = temp/minute;
temp = temp%minute;

int gsecond = temp/second;

if (ghour==0){
return String.format("%02d:%02d",gminute,gsecond);
}else{
return String.format("%02d:%02d:%2d",ghour,gminute,gsecond);
}
}

为了防止内存泄露,在界面销毁的时候也需要将消息都移除掉。

1.3.3时间进度条的实现

在播放页面收到广播之后给 SeekBar 进行设置最大进度,在更新播放进度的时候同时去更新当前进度并且滑动 SeekBar 的时候改变进度

设置当前播放进度

 /** 更新播放进度*/
public void updateCurrentPosition() {
int position=binder.getCurrentPosition();
//及时更新进度时间
updatePosition(position);
handler.sendEmptyMessageDelayed(UPDATE_POSITION,500);
}

滑动 SeekBar 的时候改变进度。在服务中提供改变进度的方法并且给 SeekBar 设置进度改变的监听

 seekbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
if (fromUser) {
binder.seekTo(progress);
updatePosition(progress);
}
}

@Override
public void onStartTrackingTouch(SeekBar seekBar) {

}

@Override
public void onStopTrackingTouch(SeekBar seekBar) {
binder.seekTo(seekBar.getProgress());
}
});

服务 AudioBinder 中的改变进度的方法

/** 进度跳转*/
public void seekTo(int progress){
mediaPlayer.seekTo(progress);
}

1.3.4播放上一首和下一首

在 AudioBinder 中提供播放上一首和下一首的方法,当点击按钮的时候调用播放上一首和下一首的方法。注意:在播放的时候需要将之前的 mediaplayer 进行重置

        public void previous(){
if (position!=0){
position--;
media_player.reset();
play();
}else {
position = audioItemList.size()-1;
}
}
public void next(){
if (position!=audioItemList.size()-1){
position++;
media_player.reset();
play();
}else {
position = 0;
}
}

然后在播放页面调用binder的方法前一首和后一首的方法

@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.play :
switchPlayState();
break;
case R.id.pre :
binder.previous();
break;
case R.id.next :
binder.next();
break;

}
}

1.3.5切换播放模式

在 AudioBinder 中提供切换播放模式的方法和获取当前播放模式

    public static final int PLAY_ALL_REPEAT = 0;
public static final int PLAY_RANDOM = 1;
public static final int PLAY_SINGLE_REPEAT = 2;

private int playMode = PLAY_ALL_REPEAT;
public void changePlayMode(){
if (playMode == PLAY_ALL_REPEAT){
playMode = PLAY_RANDOM;
}else if (playMode ==PLAY_RANDOM) {
playMode = PLAY_SINGLE_REPEAT;
}else if (playMode ==PLAY_SINGLE_REPEAT){
playMode = PLAY_ALL_REPEAT;
}
}

在播放页面点击切换播放模式的图片

@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.play :
switchPlayState();
break;
case R.id.pre :
binder.previous();
break;
case R.id.next :
binder.next();
break;
case R.id.play_mode:
binder.changePlayMode();
//及时更新播放模式
updataPlayMode();
break;
}
}

private void updataPlayMode() {
switch (binder.getPlayMode()){
case AudioPlayService.PLAY_ALL_REPEAT:
play_mode.setImageResource(R.drawable.list_repeat);
break;
case AudioPlayService.PLAY_RANDOM:
play_mode.setImageResource(R.drawable.random);
break;
case AudioPlayService.PLAY_SINGLE_REPEAT:
play_mode.setImageResource(R.drawable.single_repeat);
break;
}
}

1.3.6根据播放模式自动播放下一首音乐

在服务的 AudioBinder 的 play 方法中给 mediaplay 设置结束的监听,当音乐播放结束之后自动播放下一首

 media_player.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
//歌曲播放结束
//自动播放下一首
autoPlayNext();
}
});

private void autoPlayNext() {
switch (playMode){
case PLAY_ALL_REPEAT:
if (position == audioItemList.size()-1) {
position = 0;
}else {
position++;
}
break;
case PLAY_RANDOM:
position = new Random().nextInt(audioItemList.size());
break;
case PLAY_SINGLE_REPEAT:
break;
}
play();
}

hunterliy小作品之 HunterMusic音乐播放器(Day2-后台播放服务实现)
至此音乐播放服务和界面已经初步完成了,但是还有一些工作要作补充比如专辑图片和歌词等等,这个我会在之后进行补充,但是一个简略的MP3播放器已经成型,大家也可以根据我的代码自己加工构造出更好的作品,我只是作为抛砖引玉,同时希望也能得到大家的分享。