Android音乐播放器开发实战

本文介绍音乐播放器的音乐播放功能实现。

音乐播放功能最初的形态是根据B站大佬的视频学着做的,现在应该还能搜索到他的视频。当然他只做了一个雏形,印象中他只做了一个播放按钮和进度条。然后我在他的基础上逐渐完善各个功能。

感谢这位老哥

一、布局

在这里插入图片描述

播放界面的背景自行挑选,(个人感觉我选的背景还是蛮好看的)。图标都来自 阿里巴巴矢量图标库,内容丰富。

由上至下的控件分别为:

  • Button控件,用于关闭服务和关闭界面。(这个控件是最后加上的,虽然摆在这里不好看,但是当时没有别的好办法,就暂时放在这里了。)
  • 相对布局,在布局内添加SmartImageView控件,用于加载服务端的歌曲图片(非常好用);
  • 两个TextView控件,第1个用于显示在播歌曲名称,第2个用于显示歌手姓名;
  • 线性布局,在布局内添加SeekBar控件,用于显示歌曲播放进度、用户拖动可以改变进度;
  • 线性布局,在布局内添加5个Button控件,从左到右依次为播放模式、上一首、播放/暂停、下一首、歌曲播放列表。播放模式包括顺序播放、随机播放和单曲循环。

二、功能实现

首先设置两个接口,分别命名为PlayerControl.java和PlayerViewControl.java,用于分离逻辑层和表现层。前者包含的方法有播放上一首play_last()、播放/暂停playOrPause()、播放下一首play_next()、停止播放stopPlay()、设置播放进度seekTo(),并由类PlayerPresenter继承并改写,用于处理程序的内在逻辑;后者包含播放状态的通知onPlayerStateChange()和播放进度的改变onSeekChange(),用于更新UI。

在这里插入图片描述

2.1 获取用户信息

在音乐播放器初始化之前,首先应该获取用户信息数据,根据用户上次关闭播放器之前保留的记录重新展示。在用户登录时,登录程序(暂时还没写,后续更新)将用户信息使用Intent方法进行传递。在本程序中,获取登录账户对应的全部用户信息,并从中解析出用户的账号account、所听歌曲music_id和播放模式pattern。

每个歌曲都有对应的歌曲id,播放时也使用此id。播放模式是int类型,0代表循环播放,1代表随机播放,2代表单曲循环。

/*
获取用户整个信息
*/
Intent intent=getIntent();//获取到Intent对象,包含了用户的整个信息
String user = intent.getStringExtra("result");
JSONObject user_info = (JSONObject) RequestServlet.getJSON(user);

music_id=user_info.optInt("music_id"); //歌曲
account=user_info.optString("account"); //账户
pattern=user_info.optInt("pattern"); //播放模式

上述程序使用的getJSON函数用于将String转换为json,方便提取数据。用于经常用到此方法,因此将其写为了函数。

public static JSONObject getJSON(String json){
    try {
        JSONobj = new JSONObject(json);
    }catch (Exception e){
        e.printStackTrace();
    }
    return JSONobj;

2.2 初始化播放界面

使用findViewById方法绑定布局中的各个控件。

mSeekBar = (SeekBar) this.findViewById(R.id.seek_bar);//进度条
mPlayOrPause = (Button) this.findViewById(R.id.play_or_pause_btn);//播放
mPlay_way = (Button) this.findViewById(R.id.play_way_btn);//播放模式
mPlay_last= (Button) this.findViewById(R.id.play_last_btn);//上一首
mPlay_next = (Button) this.findViewById(R.id.play_next_btn);//下一首
mPlay_menu = (Button) this.findViewById(R.id.play_menu_btn);//歌曲列表
mQuit=(Button) this.findViewById(R.id.quit_btn);//退出
text_view_name = (TextView) this.findViewById(R.id.text_view_name);//歌名
text_view_artist = (TextView) this.findViewById(R.id.text_view_artist);//歌手
siv = (SmartImageView) this.findViewById(R.id.siv_icon);//图片

根据播放模式pattern更改播放界面的播放模式UI

if (pattern==0) {
    mPlay_way.setBackgroundResource(R.drawable.xunhuanbofang);
}else if(pattern==1){
    mPlay_way.setBackgroundResource(R.drawable.suijibofang);
} else if (pattern==2) {
    mPlay_way.setBackgroundResource(R.drawable.danquxunhuan);
}

开启子线程,在服务端获取整个音乐播放列表,并获取歌曲总数song_num,使用Message方法将获取到的音乐列表传递到主线程。

new Thread(){
    public void run(){
        try {

            JSONArray result = RequestServlet.getMusicList();
            song_num=result.length();
            Message msg = new Message();
            msg.what=1;
            msg.obj = result;
            handler1.sendMessage(msg);
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}.start();

获取歌单使用的getMusicList函数如下(实际上这属于客户端与服务端的交互部分的程序,若有不解,待后续给出解释)

//获取歌单
public static JSONArray getMusicList(){
    String path = SELECT_SERVLET+"?table=music&all=Y";
    HttpURLConnection conn;
    JSONArray result;

    try {
        conn = getConn(path);
        int code = conn.getResponseCode();
        if (code == 200){
            InputStream jsonArray = conn.getInputStream();
            String sjsonArray = streamToString(jsonArray);
            result = getJsonArray(sjsonArray);
            conn.disconnect();
            return result;
        }else {
            return null;
        }
    }catch (Exception e){
        e.printStackTrace();
    }
    return null;
}

在主线程中使用Handler方法获取子线程中的音乐列表

private Handler handler1 = new Handler() {
    public void handleMessage(android.os.Message msg) {
        try {
            if (msg.what == 1) {
                musicList = (JSONArray) msg.obj;
                add_view(0);
            }
        }catch (Exception e) {
            e.printStackTrace();
        }
    }

其中add_view函数用于初始化界面信息,由于很多地方应用到了此功能,故将其写为函数,方便调用。

add_view()方法,首先根据music_id在音乐列表中获取用户所听歌曲的全部信息,并从中解析出歌曲的名称、演唱者、歌曲封面存储路径和歌曲所在路径,使用set方法将以上信息全部加载到播放页面,至此,界面初始化完成。然后在该方法中添加一个判断语句,在调用该方法是需要传入一个int型参数,如果该参数等于特定值时(如:1),则执行音乐播放。而初始化时并不需要播放。

public static void add_view(int a) {
    try {
        JSONObject A_music = (JSONObject) musicList.get(music_id); //获取歌曲信息
        String name = A_music.optString("name");
        String author = A_music.optString("author");
        String img = A_music.optString("img");
        address=A_music.optString("address");
     																													siv.setImageUrl(MusiclistActivity.IMG+img,R.mipmap.ic_launcher,R.mipmap.ic_launcher);	//此处在博客中缩进有问题,自行更正																				               
        text_view_name.setText(name);
        text_view_artist.setText(author);
        //如果a=1,则播放
        if (a == 1) {
            if ( mplayerControl != null) {
                mplayerControl.stopPlay();
            }
            mplayerControl.playOrPause();
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

2.3 播放/暂停

系统默认播放状态为STOP

对播放按钮设置监听,按钮被点击后,调用逻辑层接口中的playOrPause()方法,创建一个MediaPlayer播放器,指定播放文件为音频。(MediaPlayer类在Android系统中是用于播放音频和视频的,它支持多种格式音频文件,并提供了非常全面的控制方法,从而使播放音频的工作变得十分简单。)

/*
初始化播放器
*/
private void initPlayer() {
    if (mMediaPlayer == null) {
        mMediaPlayer=new MediaPlayer();
        //指定参数为音频文件
        mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
    }
}

MediaPlayer播放器按照初始化中解析出的歌曲所在路径获取歌曲文件,使用start方法开始播放音乐,同时修改播放状态为PLAY,开始计时。

if (mCurrentState == PLAY_STATE_STOP) {
    //创建播放器
    initPlayer();
    try {
        //指定播放路径
        mMediaPlayer.setDataSource(MainActivity.ADDRESS + MainActivity.address);
        //准备播放
        mMediaPlayer.prepareAsync();
        //播放
        mMediaPlayer.setOnPreparedListener(new OnPreparedListener() {
            @Override
            public void onPrepared(MediaPlayer mp) {
                mMediaPlayer.start();
            }
        });
        mCurrentState = PLAY_STATE_PLAY;
        startTimer();

播放状态更新后,通知表现层接口的onPlayerStateChange()方法,进行UI更新。

private PlayerViewControl mPlayerViewControl=new PlayerViewControl() {
    @Override
    public void onPlayerStateChange(int state) {
        //我们要根据播放状态来修改UI
        switch (state) {
            case PLAY_STATE_PLAY:
                //播放中的话,我们要修改按钮显示为暂停
                mPlayOrPause.setBackgroundResource(R.drawable.bofangb);
                break;
            case PLAY_STATE_PAUSE:
            case PLAY_STATE_STOP:
                //反之,修改为播放
                mPlayOrPause.setBackgroundResource(R.drawable.bofang);
                break;
        }
    }

再次点击播放按钮,音乐暂停播放,更改状态为PAUSE,停止计时,并更新UI。

else if (mCurrentState == PLAY_STATE_PLAY) {
    //如果当前的状态为播放,那么就暂停
    if (mMediaPlayer != null) {
        mMediaPlayer.pause();
        mCurrentState = PLAY_STATE_PAUSE;
        stopTimer();
    }
} else if (mCurrentState == PLAY_STATE_PAUSE) {
    //如果当前的状态为暂停,那么继续播放
    if (mMediaPlayer != null) {
        mMediaPlayer.start();
        mCurrentState = PLAY_STATE_PLAY;
        startTimer();
    }
}

2.4 进度条更新

歌曲播放时,系统创建一个Timer()方法,设置该方法每隔500ms访问一次run()方法。run()方法获取歌曲播放进度,将其转化为百分比,当歌曲进度小于100%时,通知表现层进行进度条更新。

public void run() {
    //获取当前的播放进度
    if (mMediaPlayer != null && mViewController!=null) {
        int currentPosition = mMediaPlayer.getCurrentPosition();
        //记录百分比
        int curPosition=(int)(currentPosition*1.0f/mMediaPlayer.getDuration()*100);
        if(curPosition<=100) {
            mViewController.onSeekChange(curPosition);
        }
    }
}

表现层获取歌曲播放百分比,使用setProgress进行进度条更新,当歌曲播放进度为100%时,执行播放下一首操作。

public void onSeekChange(final int seek) {
    //改变播放进度,有一个条件:当用户的手触摸到进度条的时候,就不更新。
    runOnUiThread(new Runnable() {
        @Override
        public void run() {
            if (!isUserTouchProgressBar) {
                mSeekBar.setProgress(seek);
                if(seek==100) {
                    mplayerControl.play_next();
                }
            }
        }
    });
}

2.5 拖拽进度条改变播放进度

  • 对进度条改变设置监听。
  • 用户在歌曲播放时拖动进度条,停止拖动时,获取进度条当前值所占百分比,传入逻辑层的seekTo()方法;
  • 逻辑层将传入的值转化为歌曲播放时间,使用MediaPlayer的seekTo()方法改变歌曲播放进度。
@Override
//设置播放进度
public void seekTo(int seek) {
    //0~100之间
    //需要做一个转换,得到的seek其实是一个百分比
    if (mMediaPlayer != null) {
        //getDuration()获取音频时长
        int tarSeek=(int)(seek*1f/100*mMediaPlayer.getDuration());
        mMediaPlayer.seekTo(tarSeek);
    }
}

2.6 更改播放模式

  • 将播放模式pattern声明为int型公共变量,供所有类调用。
  • 对播放模式按钮设置监听。
  • 对播放模式进行求余操作,确保值不会“溢出”。
  • 用户点击一次按钮,更改一次pattern值,并根据pattern值更改UI。
mPlay_way.setOnClickListener(new View.OnClickListener(){

    @Override
    public void onClick(View v) {
        if( mplayerControl != null) {
            pattern=(pattern+1) % 3;
            if (pattern==0) {
                mPlay_way.setBackgroundResource(R.drawable.xunhuanbofang);
            }else if(pattern==1){
                mPlay_way.setBackgroundResource(R.drawable.suijibofang);
            } else if (pattern==2) {
                mPlay_way.setBackgroundResource(R.drawable.danquxunhuan);
            }
        }
    }
}

2.7 播放上一首

  • 对播放上一首按钮设置监听。
  • 用户点击按钮后,调用逻辑层的play_last()方法。
  • 根据播放模式的不同,在不同条件语句下对music_id进行减法操作。
  • 调用add_view()方法,传入参数1,播放新歌曲。
public void play_last() {
    // 顺序播放
    if (MainActivity.pattern == 0) {
        if (MainActivity.music_id == 0) {
            MainActivity.music_id = MainActivity.song_num;
            MainActivity.add_view(1);
        } else {
            MainActivity.music_id = MainActivity.music_id - 1;
            MainActivity.add_view(1);
        }
    }
    //随机播放
    else if (MainActivity.pattern == 1) {
        MainActivity.music_id=(MainActivity.music_id+(int)(1+Math.random()*(20-1+1))) % MainActivity.song_num ;
        MainActivity.add_view(1);
    }
    //单曲循环
    else if(MainActivity.pattern==2){
        MainActivity.add_view(1);
    }
}

2.8 播放下一首

  • 与播放上一首功能类似,对music_id进行加法操作。

2.9 停止播放

  • 用于切换歌曲,在切换歌曲前,应该停止播放当前歌曲。
  • 更改播放状态。
  • 停止计时。
  • 释放MediaPlayer的资源。(因为播放新的歌曲会创建新的MediaPlayer)
@Override
public void stopPlay() {
    if (mMediaPlayer != null ) {
        mMediaPlayer.stop();
        mCurrentState= PLAY_STATE_STOP;
        stopTimer();
        //更新播放状态
        if (mViewController != null) {
            mViewController.onPlayerStateChange(mCurrentState);
        }
        mMediaPlayer.release();//释放资源
        mMediaPlayer=null;
    }
}

2.10 打开音乐列表

  • 对按钮设置监听。
  • 按钮被点击,打开音乐列表。
mPlay_menu.setOnClickListener(new View.OnClickListener() {

    @Override
    public void onClick(View v) {
        if ( mplayerControl != null) {
            Intent intent = new Intent(MainActivity.this,MusiclistActivity.class);
            startActivity(intent);
        }
    }
}

2.11 Service(服务)

  • 服务可以长期运行在后台,没有用户界面,可以用来播放歌曲;
  • 关闭按钮被点击时,服务才被销毁。

2.12 onKeyDown()方法

  • 用于监听用户是否点击手机的返回键
  • 该方法将home界面当做一个Activity,再次打开软件,不会重启软件
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
    if (keyCode == KeyEvent.KEYCODE_BACK) {
        Intent home = new Intent(Intent.ACTION_MAIN);
        home.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
        home.addCategory(Intent.CATEGORY_HOME);
        startActivity(home);
        return true;
    }
    return super.onKeyDown(keyCode, event);
}

2.13 关闭按钮

  • 设置监听。
  • 按钮被点击时,调用stopPlay()方法,停止播放歌曲,释放掉MediaPlayer资源。
  • 开启子线程,保存用户当前所听歌曲ID和播放模式到服务端对应账户中。
private void write() {
    new Thread() {
        public void run () {
            try {
                JSONObject result = RequestServlet.SavePlayerInformation(account, music_id, pattern, 0);
                Message msg = new Message();
                msg.what = 1;
                msg.obj = result;
                handler2.sendMessage(msg);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }.start();

}
  • 关闭服务。
  • 关闭界面。
private Handler handler2 = new Handler() {
    public void handleMessage(android.os.Message msg) {
        try {
            if (msg.what == 1) {

                JSONObject result = (JSONObject) msg.obj;
                stop();
                MainActivity.this.finish();
                Toast.makeText(MainActivity.this, "已退出", Toast.LENGTH_SHORT).show();
            }
        }catch (Exception e) {
            e.printStackTrace();
        }
    }
};

至此,音乐播放界面的功能已基本实现。

发布了5 篇原创文章 · 获赞 1 · 访问量 344

猜你喜欢

转载自blog.csdn.net/cun_king/article/details/105180958