美文网首页新的开始
Android音乐播放器的实现-Service 与 MediaP

Android音乐播放器的实现-Service 与 MediaP

作者: minminaya | 来源:发表于2017-03-17 20:50 被阅读11659次

    Android学习整理 - 系列

    Android学习整理 - 6 -Service

    Android学习整理- 8 -MediaPlayer 放歌


    为了实现后台放歌,可以转移到Service中,具体的逻辑和在Activity中是差不多的


    功能需求:

    • 播放,暂停,停止
    • 歌曲进度条(SeekBar)
    • 后台

    实现过程

    下载4首歌曲改名a1,a2,a3,a4放进sd卡目录下Sounds文件夹

    新建服务MediaService

        private static final String TAG = "MediaService";
        private MyBinder mBinder = new MyBinder();
        //标记当前歌曲的序号
        private int i = 0;
        //歌曲路径
        private String[] musicPath = new String[]{
                Environment.getExternalStorageDirectory() + "/Sounds/a1.mp3",
                Environment.getExternalStorageDirectory() + "/Sounds/a2.mp3",
                Environment.getExternalStorageDirectory() + "/Sounds/a3.mp3",
                Environment.getExternalStorageDirectory() + "/Sounds/a4.mp3"
        };
      //这里要是路径有问题,就加上getAbsolutePath(),像下面这样
      //Environment.getExternalStorageDirectory().getAbsolutePath() + "/Sounds/a1.mp3",
    
        //初始化MediaPlayer
        public MediaPlayer mMediaPlayer = new MediaPlayer();
    
    
    
        @Nullable
        @Override
        public IBinder onBind(Intent intent) {
            return mBinder;
        }
    
        public class MyBinder extends Binder {
         
        }
    

    在MyBinder类里面添加逻辑功能暂停,播放,下一首,上一首,等

     /**
             * 播放音乐
             */
            public void playMusic() {
                if (!mMediaPlayer.isPlaying()) {
                    //如果还没开始播放,就开始
                    mMediaPlayer.start();
                }
            }
    
            /**
             * 暂停播放
             */
            public void pauseMusic() {
                if (mMediaPlayer.isPlaying()) {
                    //如果还没开始播放,就开始
                    mMediaPlayer.pause();
                }
            }
    
             /**
             * 下一首
             */
            public void nextMusic() {
                if (mMediaPlayer != null && i < 4 && i >= 0) {
                    //切换歌曲reset()很重要很重要很重要,没有会报IllegalStateException
                    mMediaPlayer.reset();
                    iniMediaPlayerFile(i + 1);
                    //这里的if只要是为了不让歌曲的序号越界,因为只有4首歌
                    if (i == 2) {
    
                    } else {
                        i = i + 1;
                    }
                    playMusic();
                }
            }
    
            /**
             * 上一首
             */
            public void preciousMusic() {
                if (mMediaPlayer != null && i < 4 && i > 0) {
                    mMediaPlayer.reset();
                    iniMediaPlayerFile(i - 1);
                    if (i == 1) {
    
                    } else {
    
                        i = i - 1;
                    }
                    playMusic();
                }
            }
      
    
            /**
             * 关闭播放器
             */
            public void closeMedia() {
                if (mMediaPlayer != null) {
                    mMediaPlayer.stop();
                    mMediaPlayer.release();
                }
            }
    

    注意

    在下一首和上一首的功能里重新调用 setDataSource时,要先reset再重新加载资源,不然会爆java.lang.IllegalStateException错误,

    下面这段有点多余,也是MyBinder里面的,因为可以在Activity中直接获取到MediaPlayer的对象而不用像下面这样拐弯,嗯,暂时先这样写了

     /**
             * 获取歌曲长度
             **/
            public int getProgress() {
    
                return mMediaPlayer.getDuration();
            }
    
            /**
             * 获取播放位置
             */
            public int getPlayPosition() {
    
                return mMediaPlayer.getCurrentPosition();
            }
            /**
             * 播放指定位置
             */
            public void seekToPositon(int msec) {
                mMediaPlayer.seekTo(msec);
            }
    

    接着载服务中MediaService中

    /**
         * 添加file文件到MediaPlayer对象并且准备播放音频
         */
        private void iniMediaPlayerFile(int dex) {
            //获取文件路径
            try {
                //此处的两个方法需要捕获IO异常
                //设置音频文件到MediaPlayer对象中
                mMediaPlayer.setDataSource(musicPath[dex]);
                //让MediaPlayer对象准备
                mMediaPlayer.prepare();
            } catch (IOException e) {
                Log.d(TAG, "设置资源,准备阶段出错");
                e.printStackTrace();
            }
        }
    

    加入权限

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

    注册服务

    <service android:name=".service.MediaService"/>
    

    布局文件如下

    <?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/activity_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:paddingBottom="@dimen/activity_vertical_margin"
        android:paddingLeft="@dimen/activity_horizontal_margin"
        android:paddingRight="@dimen/activity_horizontal_margin"
        android:paddingTop="@dimen/activity_vertical_margin"
        tools:context="com.minminaya.mediaservice.MainActivity">
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">
    
            <Button
                android:id="@+id/play"
                android:layout_width="0dp"
                android:layout_height="50dp"
                android:layout_weight="1"
                android:text="paly"/>
    
            <Button
                android:id="@+id/pause"
                android:layout_width="0dp"
                android:layout_height="50dp"
                android:layout_weight="1"
                android:text="pause"/>
        </LinearLayout>
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">
    
    
            <Button
                android:id="@+id/precious"
                android:layout_width="0dp"
                android:layout_height="50dp"
                android:layout_weight="1"
                android:text="precious"/>
    
            <Button
                android:id="@+id/next"
                android:layout_width="0dp"
                android:layout_height="50dp"
                android:layout_weight="1"
                android:text="next"/>
    
    
        </LinearLayout>
    
        <SeekBar
            android:layout_marginTop="20dp"
            android:id="@+id/seekbar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>
        <TextView
            android:text="当前进度:"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
        <TextView
            android:id="@+id/text1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
    </LinearLayout>
    
    
    预览

    Activity中,首先先实现播放的4大功能,声明

        private MediaService.MyBinder mMyBinder;
        private Button playButton;
        private Button pauseButton;
        private Button nextButton;
        private Button preciousButton;
        //“绑定”服务的intent
        Intent MediaServiceIntent;
    

    服务与活动的纽带ServiceConnection

     private ServiceConnection mServiceConnection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                mMyBinder = (MediaService.MyBinder) service;
                 Log.d(TAG, "Service与Activity已连接");
            }
    
            @Override
            public void onServiceDisconnected(ComponentName name) {
      
            }
        };
    

    绑定id,这里所有view都绑了,button事件

    private void iniView() {
            playButton = (Button) findViewById(R.id.play);
            pauseButton = (Button) findViewById(R.id.pause);
            nextButton = (Button) findViewById(R.id.next);
            preciousButton = (Button) findViewById(R.id.precious);
            mSeekBar = (SeekBar) findViewById(R.id.seekbar);
            mTextView = (TextView) findViewById(R.id.text1);
            playButton.setOnClickListener(this);
            pauseButton.setOnClickListener(this);
            nextButton.setOnClickListener(this);
            preciousButton.setOnClickListener(this);
        }
    

    button事件详细

     @Override
        public void onClick(View v) {
            switch (v.getId()) {
                case R.id.play:
                    mMyBinder.playMusic();
                    break;
                case R.id.pause:
                    mMyBinder.pauseMusic();
                    break;
                case R.id.next:
                    mMyBinder.nextMusic();
                    break;
                case R.id.precious:
                    mMyBinder.preciousMusic();
                    break;
            }
        }
    

    接下来是onCreate方法,针对Android6.0以上的运行时权限,动态申请权限

          iniView();
            MediaServiceIntent = new Intent(this, MediaService.class);
    
    
            //判断权限够不够,不够就给
            if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
                ActivityCompat.requestPermissions(MainActivity.this, new String[]{
                        Manifest.permission.WRITE_EXTERNAL_STORAGE
                }, 1);
            } else {
                //够了绑定播放音乐的服务
                bindService(MediaServiceIntent, mServiceConnection, BIND_AUTO_CREATE);
            }
    

    运行时权限的回调,在Activity中的

    //获取到权限回调方法
        @Override
        public void onRequestPermissionsResult(int requestCode, @NonNull  String[]permissions, @NonNull int[] grantResults) {
            switch (requestCode) {
                case 1:
                    if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                        bindService(MediaServiceIntent, mServiceConnection, BIND_AUTO_CREATE);
                    } else {
                        Toast.makeText(this, "权限不够获取不到音乐,程序将退出", Toast.LENGTH_SHORT).show();
                        finish();
                    }
                    break;
                default:
                    break;
            }
        }
    

    onDestroy方法种添加

            mMyBinder.closeMedia();
            unbindService(mServiceConnection);
    

    这个时候可以播放了,进度条当然没有动

    效果图

    接下来实现进度条的功能

    在Activity中实例化handler

        private Handler mHandler = new Handler();
    
        private SeekBar mSeekBar;
        private TextView mTextView;
        //进度条下面的当前进度文字,将毫秒化为m:ss格式
        private SimpleDateFormat time = new SimpleDateFormat("m:ss");
    

    在ServiceConnection中添加

     mSeekBar.setMax(mMyBinder.getProgress());
    
                mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
                    @Override
                    public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                        //这里很重要,如果不判断是否来自用户操作进度条,会不断执行下面语句块里面的逻辑,然后就会卡顿卡顿
                        if(fromUser){
                            mMyBinder.seekToPositon(seekBar.getProgress());
    //                    mMediaService.mMediaPlayer.seekTo(seekBar.getProgress());
                        }
                    }
    
                    @Override
                    public void onStartTrackingTouch(SeekBar seekBar) {
    
                    }
    
                    @Override
                    public void onStopTrackingTouch(SeekBar seekBar) {
    
                    }
                });
    
                mHandler.post(mRunnable);
    

    注意

    • 这里的seekbar回调里,如果不判断fromUser,播放会一直卡顿

    还有一个runnable

     /**
         * 更新ui的runnable
         */
        private Runnable mRunnable = new Runnable() {
            @Override
            public void run() {
                mSeekBar.setProgress(mMyBinder.getPlayPosition());
                mTextView.setText(time.format(mMyBinder.getPlayPosition()) + "s");
                mHandler.postDelayed(mRunnable, 1000);
            }
        };
    

    onDestroy中 mMyBinder.closeMedia();前添加

    //我们的handler发送是定时1000s发送的,如果不关闭,MediaPlayer release掉了还在获取getCurrentPosition就会爆IllegalStateException错误
            mHandler.removeCallbacks(mRunnable);
    

    注意关闭handle的队列,不然,转入后台播放时程序崩溃


    1.gif

    贴全部代码环节

    全部Activity代码如下

    package com.minminaya.mediaservice;
    
    import android.Manifest;
    import android.content.ComponentName;
    import android.content.Intent;
    import android.content.ServiceConnection;
    import android.content.pm.PackageManager;
    import android.os.Handler;
    import android.os.IBinder;
    import android.os.Message;
    import android.support.annotation.NonNull;
    import android.support.v4.app.ActivityCompat;
    import android.support.v4.content.ContextCompat;
    import android.support.v7.app.AppCompatActivity;
    import android.os.Bundle;
    import android.text.format.Time;
    import android.util.Log;
    import android.view.View;
    import android.widget.Button;
    import android.widget.SeekBar;
    import android.widget.TextView;
    import android.widget.Toast;
    
    import com.minminaya.mediaservice.service.MediaService;
    
    import java.text.SimpleDateFormat;
    
    public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    
        private Handler mHandler = new Handler();
    
        private static final String TAG = "MainActivity";
        private MediaService.MyBinder mMyBinder;
    //    private MediaService mMediaService;
    
        private Button playButton;
        private Button pauseButton;
        private Button nextButton;
        private Button preciousButton;
        private SeekBar mSeekBar;
        private TextView mTextView;
        //进度条下面的当前进度文字,将毫秒化为m:ss格式
        private SimpleDateFormat time = new SimpleDateFormat("m:ss");
        //“绑定”服务的intent
        Intent MediaServiceIntent;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            iniView();
            MediaServiceIntent = new Intent(this, MediaService.class);
    
    
            //判断权限够不够,不够就给
            if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
                ActivityCompat.requestPermissions(MainActivity.this, new String[]{
                        Manifest.permission.WRITE_EXTERNAL_STORAGE
                }, 1);
            } else {
                //够了就设置路径等,准备播放
                bindService(MediaServiceIntent, mServiceConnection, BIND_AUTO_CREATE);
            }
        }
    
        //获取到权限回调方法
        @Override
        public void onRequestPermissionsResult(int requestCode, @NonNull  String[]permissions, @NonNull int[] grantResults) {
            switch (requestCode) {
                case 1:
                    if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                        bindService(MediaServiceIntent, mServiceConnection, BIND_AUTO_CREATE);
                    } else {
                        Toast.makeText(this, "权限不够获取不到音乐,程序将退出", Toast.LENGTH_SHORT).show();
                        finish();
                    }
                    break;
                default:
                    break;
            }
        }
    
    
        private ServiceConnection mServiceConnection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                mMyBinder = (MediaService.MyBinder) service;
    //            mMediaService = ((MediaService.MyBinder) service).getInstance();
                mSeekBar.setMax(mMyBinder.getProgress());
    
                mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
                    @Override
                    public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                        //这里很重要,如果不判断是否来自用户操作进度条,会不断执行下面语句块里面的逻辑,然后就会卡顿卡顿
                        if(fromUser){
                            mMyBinder.seekToPositon(seekBar.getProgress());
    //                    mMediaService.mMediaPlayer.seekTo(seekBar.getProgress());
                        }
                    }
    
                    @Override
                    public void onStartTrackingTouch(SeekBar seekBar) {
    
                    }
    
                    @Override
                    public void onStopTrackingTouch(SeekBar seekBar) {
    
                    }
                });
    
                mHandler.post(mRunnable);
    
                Log.d(TAG, "Service与Activity已连接");
            }
    
            @Override
            public void onServiceDisconnected(ComponentName name) {
    
            }
        };
    
    
        private void iniView() {
            playButton = (Button) findViewById(R.id.play);
            pauseButton = (Button) findViewById(R.id.pause);
            nextButton = (Button) findViewById(R.id.next);
            preciousButton = (Button) findViewById(R.id.precious);
            mSeekBar = (SeekBar) findViewById(R.id.seekbar);
            mTextView = (TextView) findViewById(R.id.text1);
            playButton.setOnClickListener(this);
            pauseButton.setOnClickListener(this);
            nextButton.setOnClickListener(this);
            preciousButton.setOnClickListener(this);
        }
    
    
        @Override
        public void onClick(View v) {
            switch (v.getId()) {
                case R.id.play:
                    mMyBinder.playMusic();
                    break;
                case R.id.pause:
                    mMyBinder.pauseMusic();
                    break;
                case R.id.next:
                    mMyBinder.nextMusic();
                    break;
                case R.id.precious:
                    mMyBinder.preciousMusic();
                    break;
            }
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            //我们的handler发送是定时1000s发送的,如果不关闭,MediaPlayer release掉了还在获取getCurrentPosition就会爆IllegalStateException错误
            mHandler.removeCallbacks(mRunnable);
    
            mMyBinder.closeMedia();
            unbindService(mServiceConnection);
        }
    
        /**
         * 更新ui的runnable
         */
        private Runnable mRunnable = new Runnable() {
            @Override
            public void run() {
                mSeekBar.setProgress(mMyBinder.getPlayPosition());
                mTextView.setText(time.format(mMyBinder.getPlayPosition()) + "s");
                mHandler.postDelayed(mRunnable, 1000);
            }
        };
    
    }
    
    

    服务的

    package com.minminaya.mediaservice.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.support.annotation.Nullable;
    import android.util.Log;
    
    import java.io.IOException;
    
    /**
     * Created by NIWA on 2017/3/17.
     */
    
    public class MediaService extends Service {
    
        private static final String TAG = "MediaService";
        private MyBinder mBinder = new MyBinder();
        //标记当前歌曲的序号
        private int i = 0;
        //歌曲路径
        private String[] musicPath = new String[]{
                Environment.getExternalStorageDirectory() + "/Sounds/a1.mp3",
                Environment.getExternalStorageDirectory() + "/Sounds/a2.mp3",
                Environment.getExternalStorageDirectory() + "/Sounds/a3.mp3",
                Environment.getExternalStorageDirectory() + "/Sounds/a4.mp3"
        };
        //初始化MediaPlayer
        public MediaPlayer mMediaPlayer = new MediaPlayer();
    
    
        public MediaService() {
            iniMediaPlayerFile(i);
        }
    
    
        @Nullable
        @Override
        public IBinder onBind(Intent intent) {
            return mBinder;
        }
    
        public class MyBinder extends Binder {
    
    //        /**
    //         *  获取MediaService.this(方便在ServiceConnection中)
    //         *
    //         * *//*
    //        public MediaService getInstance() {
    //            return MediaService.this;
    //        }*/
            /**
             * 播放音乐
             */
            public void playMusic() {
                if (!mMediaPlayer.isPlaying()) {
                    //如果还没开始播放,就开始
                    mMediaPlayer.start();
                }
            }
    
            /**
             * 暂停播放
             */
            public void pauseMusic() {
                if (mMediaPlayer.isPlaying()) {
                    //如果还没开始播放,就开始
                    mMediaPlayer.pause();
                }
            }
    
            /**
             * reset
             */
            public void resetMusic() {
                if (!mMediaPlayer.isPlaying()) {
                    //如果还没开始播放,就开始
                    mMediaPlayer.reset();
                    iniMediaPlayerFile(i);
                }
            }
    
            /**
             * 关闭播放器
             */
            public void closeMedia() {
                if (mMediaPlayer != null) {
                    mMediaPlayer.stop();
                    mMediaPlayer.release();
                }
            }
    
            /**
             * 下一首
             */
            public void nextMusic() {
                if (mMediaPlayer != null && i < 4 && i >= 0) {
                    //切换歌曲reset()很重要很重要很重要,没有会报IllegalStateException
                    mMediaPlayer.reset();
                    iniMediaPlayerFile(i + 1);
                    //这里的if只要是为了不让歌曲的序号越界,因为只有4首歌
                    if (i == 2) {
    
                    } else {
                        i = i + 1;
                    }
                    playMusic();
                }
            }
    
            /**
             * 上一首
             */
            public void preciousMusic() {
                if (mMediaPlayer != null && i < 4 && i > 0) {
                    mMediaPlayer.reset();
                    iniMediaPlayerFile(i - 1);
                    if (i == 1) {
    
                    } else {
    
                        i = i - 1;
                    }
                    playMusic();
                }
            }
    
            /**
             * 获取歌曲长度
             **/
            public int getProgress() {
    
                return mMediaPlayer.getDuration();
            }
    
            /**
             * 获取播放位置
             */
            public int getPlayPosition() {
    
                return mMediaPlayer.getCurrentPosition();
            }
            /**
             * 播放指定位置
             */
            public void seekToPositon(int msec) {
                mMediaPlayer.seekTo(msec);
            }
    
    
    
    
        }
    
    
        /**
         * 添加file文件到MediaPlayer对象并且准备播放音频
         */
        private void iniMediaPlayerFile(int dex) {
            //获取文件路径
            try {
                //此处的两个方法需要捕获IO异常
                //设置音频文件到MediaPlayer对象中
                mMediaPlayer.setDataSource(musicPath[dex]);
                //让MediaPlayer对象准备
                mMediaPlayer.prepare();
            } catch (IOException e) {
                Log.d(TAG, "设置资源,准备阶段出错");
                e.printStackTrace();
            }
        }
    }
    
    

    Github源代码:MediaService

    高级播放器。。。人类用的

    上面的播放是固有的几个文件(连音频文件名都订好了,真坑)

    有个博主,做的毕业项目,是一系列文章,感觉不错,
    Android开源音乐播放器之播放器基本功能

    注意事项,

    几个注意点在上面了

    相关文章

      网友评论

        本文标题:Android音乐播放器的实现-Service 与 MediaP

        本文链接:https://www.haomeiwen.com/subject/opdenttx.html