安卓简易音乐播放器实现

一、参考文章

参考文章:Android音乐播放器的实现-简书
原文使用了service,也稍微看了一下。service大概就是下面这样的:但是我发现即使只用Activity,切出去之后歌曲的播放也不会停止,于是就暂时没有使用它,只用到了Activity,需要用到service的朋友可以看一下上面那篇文章。

service是Android中实现程序后台的解决方案,不依赖任何用户界面,即使程序被切换到后台,或者用户打开了另外一个应用程序,service还能保持运行。

二、功能说明

实现了播放、暂停、停止、下一首歌、上一首歌和进度条的功能,可以拖动进度条来改变歌曲的进度。
界面就像下面这样:丑丑的
在这里插入图片描述

三、重要代码说明

1、存储权限读取

① 首先调用checkSelfPermission进行权限检查,如果无权限则使用requestPermissions进行权限申请,其中PackageManager.PERMISSION_GRANTED 表示有权限, PackageManager.PERMISSION_DENIED 表示无权限。

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);
            //这里会调用后面的onRequestPermissionResult
        }else{
            initMediaPlayer(0);
        }

② requestPermissions方法进行权限申请后会自动调用onRequestPermissionsResult(),覆写onRequestPermissionsResult()

@Override
    //拒绝权限获取则直接关闭程序
    public void onRequestPermissionsResult(int requestCode,String[] permissions,int[] grantResults){
        switch (requestCode){
            case 1:
            {
                if(grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){
                    initMediaPlayer(0);
                }else{
                    Toast.makeText(this,"拒绝权限将无法使用程序",Toast.LENGTH_SHORT).show();
                    finish();
                }
                break;
            }
            default:
                break;
        }
    }

③ 在AndroidManifest.xml文件中注明申请了什么权限
注意uses-permission要作为manifest的直接孩子

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
2、指定歌曲路径,初始化MediaPlayer组件并使其进入准备状态

下面这段代码你没看错,就是很粗暴地自己在内部存储根目录下建一个叫做MyTestMusic的文件夹,然后放四首歌进去,再把这几首歌都作为歌曲的路径。

 File pFile = Environment.getExternalStorageDirectory();//SD卡根目录
    //歌曲路径
    private String[] musicPath = new String[]{
            pFile + "/MyTestMusic/想自由_林宥嘉.flac",
            pFile + "/MyTestMusic/Adele-Someone Like You.ape",
            pFile + "/MyTestMusic/Taylor Swift - Love Story.mp3",
            pFile + "/MyTestMusic/感同身受_林宥嘉.flac"
    };
private void initMediaPlayer(int musicIndex){
       try {
           //File file = new File(pFile,"Adele-Someone Like You.ape");
           mediaPlayer.setDataSource(musicPath[musicIndex]);//指定音频文件路径
           mediaPlayer.prepare();//让MediaPlayer进入到准备状态
       }catch(Exception e){
           e.printStackTrace();
       }
}
3、进度条功能

① 在initMediaPlayer中加入对进度条最大长度的设置,并通过setOnSeekBarChangeListener来完成用户拖动进度条时改变歌曲进度的功能。为什么加在这里呢,因为每换一首歌seekBar的长度都要发生变化。

//这个要放在指定音频文件路径之后
        seekBar.setMax(mediaPlayer.getDuration());
        //拖动进度条时应该发生的事情
        seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                //如果不判断是否来自用户操作进度条,会不断执行下面语句块里面的逻辑
                if(fromUser){
                    //seekBar.getProgress()为进度条拖到的地方
                    mediaPlayer.seekTo(seekBar.getProgress());
                }
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {

            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {

            }
        });

② 更新UI,获取歌曲进度并在进度条上展现,并每隔一秒跳一个数字
主线程创建handler,在子线程中通过handler的post(Runnable)方法更新UI信息。

    private Handler myHandler = new Handler();
……
    //更新UI
    private Runnable updateUI = new Runnable() {
        @Override
        public void run() {
            //获取歌曲进度并在进度条上展现
            seekBar.setProgress(mediaPlayer.getCurrentPosition());
            //获取播放位置
            timeTextView.setText(time.format(mediaPlayer.getCurrentPosition()) + "s");
            myHandler.postDelayed(updateUI,1000);
        }

    };

③ 在onCreate函数的最后:

myHandler.post(updateUI);

四、用到的MediaPlayer的方法总结

重要的功能就前面那些了,其他的开始什么都是很容易地调用一下就可以了,下面总结一下MediaPlayer有哪些方法好了。

方法功能
setDataSource()指定音频文件路径
prepare()让MediaPlayer进入到准备状态
getDuration()获取歌曲长度
getCurrentPosition()获取现在播放到的位置
seekTo(int msec)播放到指定位置
isPlaying()是否正在播放
start()开始播放
pause()暂停播放
reset()应该是有些停止播放的意味,后面一般重新使MediaPlayer进入准备状态
release()释放资源

五、完整代码

1、activity_main.xml
<?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=".MainActivity">

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:orientation="vertical"
        android:layout_marginVertical="30dp">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            android:layout_marginHorizontal="20dp">
            <Button
                android:id="@+id/play"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="play" />

            <Button
                android:id="@+id/pause"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="pause"
                android:layout_marginLeft="40dp"/>

            <Button
                android:id="@+id/stop"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="stop"
                android:layout_marginLeft="20dp"/>

        </LinearLayout>


        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            android:layout_marginHorizontal="30dp"
            android:layout_marginVertical="30dp">

            <Button
                android:id="@+id/previous"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="previous" />

            <Button
                android:id="@+id/next"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="next"
                android:layout_marginLeft="120dp"/>

        </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>

</android.support.constraint.ConstraintLayout>
2、MainActivity.java
package com.example.yogi.mymusicplayer;

import android.Manifest;
import android.content.pm.PackageManager;
import android.media.MediaPlayer;
import android.os.Environment;
import android.os.Handler;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.Toast;

import java.io.File;
import java.text.SimpleDateFormat;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    //主线程创建handler,在子线程中通过handler的post(Runnable)方法更新UI信息。
    private Handler myHandler = new Handler();

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

    private int i = 0;//当前歌曲序号

    File pFile = Environment.getExternalStorageDirectory();//SD卡根目录
    //歌曲路径
    private String[] musicPath = new String[]{
            pFile + "/MyTestMusic/想自由_林宥嘉.flac",
            pFile + "/MyTestMusic/Adele-Someone Like You.ape",
            pFile + "/MyTestMusic/Taylor Swift - Love Story.mp3",
            pFile + "/MyTestMusic/感同身受_林宥嘉.flac"
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button play = findViewById(R.id.play);
        Button stop = findViewById(R.id.stop);
        Button pause = findViewById(R.id.pause);
        Button nextMusic = findViewById(R.id.next);
        Button preMusic = findViewById(R.id.previous);

        play.setOnClickListener(this);
        stop.setOnClickListener(this);
        pause.setOnClickListener(this);
        nextMusic.setOnClickListener(this);
        preMusic.setOnClickListener(this);

        seekBar = findViewById(R.id.seekbar);
        timeTextView = findViewById(R.id.text1);

        //运行时权限处理,动态申请WRITE_EXTERNAL_STORAGE权限
        //PackageManager.PERMISSION_GRANTED 表示有权限, PackageManager.PERMISSION_DENIED 表示无权限
        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);
            //这里会调用后面的onRequestPermissionResult
        }else{
            initMediaPlayer(0);
        }

        myHandler.post(updateUI);
    }

    private void initMediaPlayer(int musicIndex){
        try {
            //File file = new File(pFile,"Adele-Someone Like You.ape");
            mediaPlayer.setDataSource(musicPath[musicIndex]);//指定音频文件路径
            mediaPlayer.prepare();//让MediaPlayer进入到准备状态
        }catch(Exception e){
            e.printStackTrace();
        }

        //这个要放在指定音频文件路径之后
        seekBar.setMax(mediaPlayer.getDuration());
        //拖动进度条时应该发生的事情
        seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                //如果不判断是否来自用户操作进度条,会不断执行下面语句块里面的逻辑
                if(fromUser){
                    mediaPlayer.seekTo(seekBar.getProgress());
                }
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {

            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {

            }
        });
    }

    @Override
    //拒绝权限获取则直接关闭程序
    public void onRequestPermissionsResult(int requestCode,String[] permissions,int[] grantResults){
        switch (requestCode){
            case 1:
            {
                if(grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){
                    initMediaPlayer(0);
                }else{
                    Toast.makeText(this,"拒绝权限将无法使用程序",Toast.LENGTH_SHORT).show();
                    finish();
                }
                break;
            }
            default:
                break;
        }
    }

    @Override
    public void onClick(View v){
        switch (v.getId()){
            case R.id.play:
                if(!mediaPlayer.isPlaying()){
                    mediaPlayer.start();
                }
                break;
            case R.id.pause:
                if(mediaPlayer.isPlaying()){
                    mediaPlayer.pause();
                }
                break;
            case R.id.stop:
                if(mediaPlayer.isPlaying()){
                    mediaPlayer.reset();
                    initMediaPlayer(i);
                }
                break;
            case R.id.next:
                playNextMusic();
                break;
            case R.id.previous:
                playPreMusic();
                break;
            default:
                break;
        }
    }

    private void playNextMusic(){
        if(mediaPlayer != null && i < 4 && i >=0){
            mediaPlayer.reset();//没有reset会报IllegalStateException
            switch (i){
                case 0: case 1: case 2:
                    initMediaPlayer(i+1);
                    i = i + 1;
                case 3:
                    initMediaPlayer(0);
            }
            if(!mediaPlayer.isPlaying()){
                mediaPlayer.start();
            }
        }
    }

    private void playPreMusic(){
        if(mediaPlayer != null && i < 4 && i >=0){
            mediaPlayer.reset();//没有reset会报IllegalStateException
            switch (i){
                case 1: case 2: case 3:
                    initMediaPlayer(i-1);
                    i = i - 1;
                case 0:
                    initMediaPlayer(3);
            }
            if(!mediaPlayer.isPlaying()){
                mediaPlayer.start();
            }
        }
    }

    //更新UI
    private Runnable updateUI = new Runnable() {
        @Override
        public void run() {
            //获取歌曲进度并在进度条上展现
            seekBar.setProgress(mediaPlayer.getCurrentPosition());
            //获取播放位置
            timeTextView.setText(time.format(mediaPlayer.getCurrentPosition()) + "s");
            myHandler.postDelayed(updateUI,1000);
        }

    };


    //释放资源
    protected void onDestroy(){
        super.onDestroy();
        //handler发送是定时1000s发送的,如果不关闭,MediaPlayer release了还在getCurrentPosition就会报IllegalStateException错误
        myHandler.removeCallbacks(updateUI);
        if(mediaPlayer != null){
            mediaPlayer.stop();
            mediaPlayer.release();
        }
    }
}

3、不要忘记AndroidManifect也需要写明申请什么权限
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.yogi.mymusicplayertest">

    ……

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

</manifest>
  • 32
    点赞
  • 164
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值