音频音乐播放 Service

界面效果:

       

界面就一个播放的Button和一个进度条SeekBar,也可以自己加上两个显示时间的TextView;

点击播放时,有音乐声音,进度条也会自动更新,Button文字变成暂停;

当音乐播放完毕时,Button文字变成播放,进度条重新回原始点;

1、添加权限

这里需要可以读取音频文件的权限:

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

2、添加依赖

本项目中使用EventBus在Service和Activity之间通讯,需要加入EventBus的库:

compile 'org.greenrobot:eventbus:3.0.0'

3、布局文件

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MusicActivity">

    <SeekBar
        android:id="@+id/seekbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.5" />

    <Button
        android:id="@+id/button_play"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="播放"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.6" />

</android.support.constraint.ConstraintLayout>

4、Service

创建Service:new->Service

在Service中,创建一个MediaPlayer对象和一个Binder类,Binder可以用来与Activity之间传输数据(进度条,点击事件),但是只能Activity主动通过Binder进行相互;播放结束后,音乐跳回最开始,并用EventBus主动通知Activity“播放完成”;

代码以及注释如下:

package com.example.m_evolution.Service;

import android.app.Service;
import android.content.Intent;
import android.media.MediaPlayer;
import android.os.Binder;
import android.os.Environment;
import android.os.IBinder;
import android.os.Parcel;
import android.os.RemoteException;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;

import org.greenrobot.eventbus.EventBus;

import java.io.IOException;

public class MusicService extends Service {
    private MediaPlayer mediaPlayer = new MediaPlayer();
    private String file_dir;

    private final int CODE_PLAY_MUSIC = 101;  //播放音乐
    private final int CODE_PAUSE_MUSIC = 102;  //暂停音乐
    private final int CODE_UPDATE_SEEKBAR = 103;  //更新进度条
    private final int CODE_CHANGE_SEEKBAR = 104;  //拖动进度条更新音乐

    public MusicService() {

    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        file_dir = intent.getExtras().getString("file_dir", Environment.getExternalStorageDirectory()+"/record/"+"a.mp3");
        try {
            mediaPlayer.setDataSource(file_dir);
            mediaPlayer.prepare();
            mediaPlayer.setLooping(false);
            mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
                @Override
                public void onCompletion(MediaPlayer mediaPlayer) {
                    try {
                        mediaPlayer.stop();
                        mediaPlayer.prepare();
                        mediaPlayer.seekTo(0);
                        //用EventBus通知Activity已完成播放
                        EventBus.getDefault().post("finish_music");
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            });
        } catch (IOException e) {
            Log.d("MusicService", "未找到文件");
            e.printStackTrace();
        }
        return new MyBinder();
    }

    //Service与Activity交互数据
    public class MyBinder extends Binder{
        @Override
        //code为操作代码
        //data为从Activity中传到Service的数据
        //reply为从Service传到Activity的数据
        protected boolean onTransact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags) throws RemoteException {
            switch (code)
            {
                case CODE_PLAY_MUSIC:  //播放
                    StartMusic();
                    break;
                case CODE_PAUSE_MUSIC:  //暂停
                    PauseMusic();
                    break;
                case CODE_UPDATE_SEEKBAR:  //更新Activity中的进度条
                    int out_data[]=new int[2];  //传出的数据
                    out_data[0] = 0;
                    out_data[1] = 0;
                    if(mediaPlayer != null){
                        out_data[0] = mediaPlayer.getCurrentPosition();  //获取现在播放时间
                        out_data[1] = mediaPlayer.getDuration();  //获取总时长
                    }
                    reply.writeIntArray(out_data);
                    break;
                case CODE_CHANGE_SEEKBAR:
                    int in_data = data.readInt(); //传入的数据:播放时间
                    if(mediaPlayer != null){
                        mediaPlayer.seekTo(in_data);
                    }
                    break;
            }
            return super.onTransact(code, data, reply, flags);
        }
    }

    public void StartMusic(){
        if (mediaPlayer!=null && !mediaPlayer.isPlaying()){
            mediaPlayer.start();
        }
    }

    public void PauseMusic(){
        if (mediaPlayer!=null && mediaPlayer.isPlaying()){
            mediaPlayer.pause();
        }
    }

    public void StopMusic(){
        if (mediaPlayer!=null){
            mediaPlayer.stop();
            mediaPlayer = null;
        }
    }

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

5、Activity中的代码及注释如下。

启动Service的方式有两种:
1、startService(intent);
2、bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
第一种是纯粹开启一个Service,跟Activity没有联系,就算Activity退出了,Service也还会运行,但是只能同一个Service只能开启一个,即多个Activity开启某个Service,该Service只会Create一次。=
第二种方式中,Activity会与Service建立联系,Activity销毁时,Service也会销毁,执行onDestroy函数,但这不代表音乐就停止了,你需要在onDestroy函数中手动停止音乐。

PS:这里面的IBinder对象,在开启Service后不是马上就有值的,所以不要在StartService之后立马对IBinder进行操作,因为它是null的;可以像我下面这样,建一个线程做个死循环,当IBinder对象不为空时再去进行操作。

package com.example.m_evolution;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Environment;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Parcel;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;
import android.widget.SeekBar;

import com.example.m_evolution.Service.MusicService;

import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;

public class MusicActivity extends AppCompatActivity {

    private SeekBar seekBar;
    private Button button_play;
    private String file_dir; //音频文件路径
    private ServiceConnection serviceConnection; //用来连接Service
    private IBinder mBinder;  //用来与Service交互数据
    private boolean isPlaying;  //是否正在进行播放

    private final int CODE_PLAY_MUSIC = 101;  //播放音乐
    private final int CODE_PAUSE_MUSIC = 102;  //暂停音乐
    private final int CODE_UPDATE_SEEKBAR = 103;  //更新进度条
    private final int CODE_CHANGE_SEEKBAR = 104;  //拖动进度条更新音乐

    private final int INIT_VIEW = 0;
    private final int UPDATE_SEEKBAR = 1;

    //更新进度条的线程
    private Thread thread_update_view;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_music);
        //注册EventBus,以便Service通知Activity意播放完毕
        EventBus.getDefault().register(this);

        FindView();
        InitData();
        StartService();  //开启Service
        //初始化进度条以及设置监听器
        new Thread(new Runnable() {
            @Override
            public void run() {
                while(mBinder == null) ;  //等待mBinder被赋值,表示Service成功开始
                Message msg = new Message();
                msg.what = 0;
                mHandler.sendMessage(msg);
            }
        }).start();
    }

    private void FindView(){
        seekBar = findViewById(R.id.seekbar);
        button_play = findViewById(R.id.button_play);
    }

    private void InitData(){
        file_dir = Environment.getExternalStorageDirectory()+"/record/"+"a.mp3";  //这是提前放在手机路径“record”文件夹中的音频文件a.mp3
        isPlaying = false;
    }

    //开启Service
    private void StartService(){
        serviceConnection = new ServiceConnection() {
            //与Service建立连接
            @Override
            public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
                mBinder = iBinder;  //绑定Binder
            }

            @Override
            public void onServiceDisconnected(ComponentName componentName) {
                serviceConnection = null;
            }
        };
        Intent intent = new Intent(this, MusicService.class);
        Bundle bundle = new Bundle();
        bundle.putString("file_dir", file_dir);
        intent.putExtras(bundle);
        //startService(intent);
        bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
    }

    private void SetListener(){
        button_play.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if(!isPlaying){ //如果现在是暂停状态
                    StartMusic();
                }
                else{ //如果现在是播放状态
                    PauseMusic();
                }
                isPlaying = !isPlaying;
            }
        });
        //进度条的事件监听器,手指抬起时根据进度条改变音乐播放时间
        seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int i, boolean b) {

            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {

            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {
                int code = CODE_CHANGE_SEEKBAR;
                Parcel data = Parcel.obtain();
                Parcel reply = Parcel.obtain();
                data.writeInt(seekBar.getProgress());  //写入进度条现在的数据
                try {
                    mBinder.transact(code, data, reply, 0);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        });
    }

    private void StartMusic(){
        button_play.setText("暂停");
        int code = CODE_PLAY_MUSIC;
        Parcel data = Parcel.obtain();
        Parcel reply = Parcel.obtain();
        try {
            mBinder.transact(code,data, reply, 0);  //告诉Service进行播放
        } catch (RemoteException e) {
            e.printStackTrace();
        }
        thread_update_view = new Thread(new Runnable() {
            @Override
            public void run() {
                while (isPlaying && mBinder!=null){
                    Message msg = new Message();
                    msg.what = UPDATE_SEEKBAR;
                    mHandler.sendMessage(msg);
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        thread_update_view.start();
    }

    private void PauseMusic(){
        button_play.setText("播放");
        int code = CODE_PAUSE_MUSIC;
        Parcel data = Parcel.obtain();
        Parcel reply = Parcel.obtain();
        try {
            mBinder.transact(code,data, reply, 0);  //告诉Service进行暂停
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    //更新Activity中的进度条或TextView等
    private void UpdateView(){
        try {
            int code = CODE_UPDATE_SEEKBAR;
            Parcel data = Parcel.obtain();
            Parcel reply = Parcel.obtain();
            mBinder.transact(code, data, reply, 0);
            int read_data[] = new int[2];  //读取到的音乐播放时间以及总长度
            reply.readIntArray(read_data);
            seekBar.setProgress(read_data[0]);   //音乐正在播放的时间
            seekBar.setMax(read_data[1]);  //音乐总长度
            //这里还可以设置显示时间的TextView
            //......
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    private Handler mHandler = new Handler(){
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {      //判断标志位
                case INIT_VIEW:   //初始化进度条以及监听器
                    UpdateView();
                    SetListener();
                    break;
                case UPDATE_SEEKBAR:
                    UpdateView();
                    break;
            }
        }
    };

    @Override
    protected void onDestroy() {
        if(serviceConnection != null){
unbindService(serviceConnection); serviceConnection
= null;
}
if(EventBus.getDefault().isRegistered(this)) { EventBus.getDefault().unregister(this); } super.onDestroy(); } //已完成播放 @Subscribe(threadMode = ThreadMode.MAIN) public void Event(String str) { if(str.equals("finish_music")){ if(thread_update_view!=null){ thread_update_view.interrupt(); thread_update_view = null; } button_play.setText("播放"); seekBar.setProgress(0); isPlaying = false; } } }

6、音频文件

自备一个音频文件,这里我放在了Environment.getExternalStorageDirectory()+"/record/"+"a.mp3"。

 

posted on 2019-04-17 15:28  赵子隆  阅读(269)  评论(0编辑  收藏  举报

导航