Quantcast
Channel: CSDN博客移动开发推荐文章
Viewing all articles
Browse latest Browse all 5930

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

$
0
0

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来代替,之后我们再慢慢完善。最终效果如下:
这里写图片描述

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();
        }

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

作者:hunterliy 发表于2016/10/16 9:48:21 原文链接
阅读:37 评论:0 查看评论

Viewing all articles
Browse latest Browse all 5930

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>