前言

是这样,前几天接触到一个可以随机获取网络音乐及其热评的 API(关于该API:github.com/isecret/yun… ),于是乎就想着要做一个小 demo 来练练手吧!

目前的效果就是上面那个样子。

我目前有打算把这个 demo 长期维护下去,后面会加入更多功能,例如收藏、下载等。

需求

需求很简单,就是通过 API 随机获取一首在线音乐及其某一条热评,实现音乐的后台播放、暂停、随机切换,显示热评及其点赞数。

API 返回的数据示例如下:

{

"song_id": 400162138,

"title": "海阔天空",

"images": "https://p1.music.126.net/a9oLdcFPhqQyuouJzG2mAQ==/3273246124149810.jpg",

"author": "Beyond",

"album": "华纳23周年纪念精选系列",

"description": "歌手:Beyond。所属专辑:华纳23周年纪念精选系列。",

"mp3_url": "https://api.comments.hk/music/400162138",

"pub_date": "2001-08-31 16:00:00",

"comment_id": 168923809,

"comment_user_id": 6942157,

"comment_nickname": "斑马斑斑",

"comment_avatar_url": "https://p1.music.126.net/O-z-71Ffl1VimPDElVDKcQ==/6057209557649645.jpg",

"comment_liked_count": 105599,

"comment_content": "如果家驹没走,现在是什么样的存在?",

"comment_pub_date": "2016-06-14 12:26:33"

}

复制代码

参数释义:

用到的库

背景虚化:Glide-transformations - github.com/wasabeef/gl…

OK,那接下来就看看是如何一点一点做出来的吧!

代码实现

音乐播放 - Service + MediaPlayer

public class MusicPlayService extends Service {

private static String TAG = "MusicPlayService";

MediaPlayer mediaPlayer;

boolean firstTimePlay;

public MusicPlayService(){}

@Override

public void onCreate() {

super.onCreate();

if (mediaPlayer == null){

mediaPlayer = new MediaPlayer();

firstTimePlay = true;

}

}

@Override

public int onStartCommand(Intent intent, int flags, int startId) {

return super.onStartCommand(intent, flags, startId);

}

@Override

public IBinder onBind(Intent intent) {

return new MyMusicPlayBinder();

}

public class MyMusicPlayBinder extends Binder {

public void playMusic(){

if (!mediaPlayer.isPlaying())

mediaPlayer.start();

}

public void pauseMusic(){

if (mediaPlayer.isPlaying())

mediaPlayer.pause();

}

public void playRandomMusic(String url, final OnNetworkMusicPreparedListener onNetworkMusicPreparedListener) {

try {

mediaPlayer.stop();

mediaPlayer.reset();

mediaPlayer.setDataSource(url);

mediaPlayer.prepareAsync();

mediaPlayer.setLooping(true); // 循环播放

mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {

@Override

public void onPrepared(MediaPlayer mp) {

mediaPlayer.start();

onNetworkMusicPreparedListener.onPrepared();

firstTimePlay = false;

}

});

} catch (IOException e) {

e.printStackTrace();

}

}

public boolean isPlaying(){

return mediaPlayer.isPlaying();

}

public int getMusicDuration(){

return mediaPlayer.getDuration();

}

// 获取当前播放进度

public int getCurPosition(){

return mediaPlayer.getCurrentPosition();

}

public boolean isFirstTimePlay(){

return firstTimePlay;

}

}

@Override

public void onDestroy() {

mediaPlayer.release();

super.onDestroy();

}

}

复制代码

分析一下主要代码。

首先声明一个了 MediaPlayer 对象(第 4 行),用于音乐的播放。随后在 Service 被创建的时候对其进行初始化(第 14 行)。

定义了一个内部类 MyMusicPlayBinder(29 - 76 行),用以和 Activity 通信。在该类中,定义了一系列方法(播放、暂停、获取进度等等)供 Activity 调用。并且在 onBind() 方法中返回了一个 MyMusicPlayBinder 实例(26 行)。

这其中注意一下 playRandomMusic(40 - 58 行) 方法,由于我们播放的是网络音乐,所以要调用的是 mediaPlayer.prepareAsync(),即异步准备,然后必须要设置回调,即 mediaPlayer.setOnPreparedListener(),该回调会在 mediaPlayer 准备好之后被调用,我们应该在该回调中开始播放(调用 mediaPlayer.start())。

界面实现

接下来看看主界面的实现。

还是先看代码吧:

(为了避免代码看起来过长,省略了一些不重要的代码)

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

private static String TAG = "MainActivity";

private int IMAGE_SOURCE_PLAY = R.drawable.play_white;

private int IMAGE_SOURCE_PAUSE = R.drawable.pause_white;

/*

控件声明 省略

*/

private MusicPlayService.MyMusicPlayBinder musicController;

ObjectAnimator objectAnimator; // 图片旋转动画

SeekBar seekBar; // 进度条

private ServiceConnection serviceConnection = new ServiceConnection(){

@Override

public void onServiceConnected(ComponentName name, IBinder service) {

musicController = (MusicPlayService.MyMusicPlayBinder) service;

if (musicController.isPlaying())

btn_play_or_pause.setImageResource(IMAGE_SOURCE_PAUSE);

else

btn_play_or_pause.setImageResource(IMAGE_SOURCE_PLAY);

}

@Override

public void onServiceDisconnected(ComponentName name) {}

};

Handler mHandler = new Handler();

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

initView();

initService();

if (savedInstanceState != null)

tv_music_title.setText(savedInstanceState.getString("musicTitle"));

}

private void initView(){

/*

各个控件的初始化 省略

*/

seekBar = (SeekBar) findViewById(R.id.music_progress_seek_bar);

// 禁止拖动 点击

seekBar.setOnTouchListener(new View.OnTouchListener() {

@Override

public boolean onTouch(View v, MotionEvent event) {

return true;

}

});

objectAnimator = ObjectAnimator.ofFloat(image_view_music, "rotation", 0, 360);

objectAnimator.setInterpolator(new LinearInterpolator());

objectAnimator.setDuration(20 * 1000);

objectAnimator.setRepeatCount(ValueAnimator.INFINITE);//Animation.INFINITE 表示重复多次

objectAnimator.setRepeatMode(ValueAnimator.RESTART);//RESTART表示从头开始,REVERSE表示从末尾倒播

btn_play_or_pause.setOnClickListener(this);

btn_next_random.setOnClickListener(this);

}

private void initService(){

Intent intent = new Intent(this, MusicPlayService.class);

startService(intent);

bindService(intent, serviceConnection, BIND_AUTO_CREATE);

}

@RequiresApi(api = Build.VERSION_CODES.KITKAT)

@Override

public void onClick(View v) {

switch (v.getId()){

case R.id.btn_play_or_pause:

playOrPauseMusic();

break;

case R.id.next_random:

nextRandomMusic();

default:

break;

}

}

@RequiresApi(api = Build.VERSION_CODES.KITKAT)

private void playMusic(){

musicController.playMusic();

objectAnimator.resume();

}

@RequiresApi(api = Build.VERSION_CODES.KITKAT)

private void pauseMusic(){

musicController.pauseMusic();

objectAnimator.pause();

}

@RequiresApi(api = Build.VERSION_CODES.KITKAT)

private void playOrPauseMusic(){

if (musicController.isFirstTimePlay()){

Toast.makeText(MainActivity.this, "没有正在播放的音乐!", Toast.LENGTH_SHORT).show();

return;

}

if (musicController.isPlaying()){

pauseMusic();

btn_play_or_pause.setImageResource(IMAGE_SOURCE_PLAY);

}else {

playMusic();

btn_play_or_pause.setImageResource(IMAGE_SOURCE_PAUSE);

}

}

private void nextRandomMusic(){

btn_play_or_pause.setImageResource(IMAGE_SOURCE_PLAY);

MusicModel.getRandomMusic(new GetRandomMusicListener() {

@Override

public void onSuccess(final Music music) {

LogUtil.e(TAG, music.getDescription());

musicController.playRandomMusic(music.getMp3_url(), new OnNetworkMusicPreparedListener() {

@Override

public void onPrepared() {

btn_play_or_pause.setImageResource(IMAGE_SOURCE_PAUSE);

mHandler.post(new Runnable() {

@Override

public void run() {

tv_music_title.setText(music.getTitle());

tv_music_desc.setText(music.getDescription().replaceAll("。", " "));

tv_comment.setText(music.getComment_content());

tv_comment_time.setText(music.getComment_pub_date());

tv_comment_username.setText(music.getComment_nickname());

tv_liked_count.setText(music.getComment_liked_count() + "");

seekBar.setMax(musicController.getMusicDuration());

new UpdateProgressThread().start();

tv_music_total_time.setText(TimeTool.format(musicController.getMusicDuration()));

Glide.with(MainActivity.this).load(music.getImages()).into(image_view_music);

Glide.with(MainActivity.this).load(music.getImages()).

apply(RequestOptions.bitmapTransform(new BlurTransformation(50,10))).into(image_view_bg);

Glide.with(MainActivity.this).load(music.getComment_avatar_url()).placeholder(R.drawable.placeholder).into(image_view_user_avatar);

objectAnimator.start();

}

});

}

});

}

@Override

public void onFailed() {

LogUtil.e(TAG, "获取音乐失败!");

}

});

}

@Override

protected void onDestroy() {

super.onDestroy();

unbindService(serviceConnection);

}

@Override

public void onSaveInstanceState(Bundle outState, PersistableBundle outPersistentState) {

super.onSaveInstanceState(outState, outPersistentState);

outState.putString("musicTitle", tv_music_title.getText().toString());

}

class UpdateProgressThread extends Thread {

@Override

public void run() {

super.run();

while (seekBar.getProgress() < seekBar.getMax()){

final int curPosition = musicController.getCurPosition();

seekBar.setProgress(curPosition);

SystemClock.sleep(1000);

mHandler.post(new Runnable() {

@Override

public void run() {

tv_music_cur_time.setText(TimeTool.format(curPosition));

}

});

}

}

}

}

复制代码

简单分析下代码。首先第 14 - 25 行,定义了一个 serviceConnection,用于 Activity 和 Service 建立连接。27 行定义一个 Handler 用于界面的更新(切换线程更新界面)。第 56 - 60 行初始化 objectAnimator,并将其与 image_view_music 绑定,该动画用于歌曲图片的旋转。169 - 185 行,定义了一个内部类 UpdateProgressThread,这个线程类是用来 SeekBar 的更新的,也就是实时更新播放进度条,由于不能在子线程更新 UI,所以必须要是用 mHandler.post() 切换到主线程去更新。

主要的代码差不多就这些了。

琐碎

在使用 Okhttp 的时候,连续多次调用 response.body().string() 会导致 java.lang.IllegalStateException: closed 错误。

Service 的绑定(bindService())实际上是一个异步的过程。

使用 bindService() 绑定服务就一定要调用 unbindService() 解绑,要记住了啊!

android 随机播放代码,Android | 一个随机播放网络音乐的小 Demo相关推荐

  1. Java黑皮书课后题第3章:**3.4(随机月份)编写一个随机产生1和12之间整数的程序,并根据数组1,2,3...显示对应的月份

    **3.4(随机月份)编写一个随机产生1和12之间整数的程序,并根据数组1,2,3...显示对应的月份 题目 题目描述 破题 代码 如何理解产生随机数 题目 题目描述 **3.4(随机月份)编写一个随 ...

  2. android小球移动代码,Android中如何绘制一个跟随手指移动的小球

    Android中如何绘制一个跟随手指移动的小球 发布时间:2020-11-07 16:22:43 来源:亿速云 阅读:82 作者:Leah 本篇文章为大家展示了Android中如何绘制一个跟随手指移动 ...

  3. Android钢琴滑动代码,Android实现简易版弹钢琴效果

    本文实例为大家分享了Android实现弹钢琴效果展示的具体代码,供大家参考,具体内容如下 目标效果: 1.drawable下新建button_selector.xml页面: 2.drawable下新建 ...

  4. android小球移动代码,Android自定义圆形View实现小球跟随手指移动效果

    本文实例为大家分享了Android实现小球跟随手指移动效果的具体代码,供大家参考,具体内容如下 一. 需求功能 手指在屏幕上滑动,红色的小球始终跟随手指移动. 实现的思路: 1)自定义View,在on ...

  5. Android钢琴滑动代码,android 钢琴界面实现

    近在做一个钢琴的东西,关于这个界面如何设计画了很长时间,主要是考虑到针对不同的分辨率,如果只针对一种分辨率的话用绝对布局可以实现,实现的基本思想是每个白色的键的位置是可以计算出来的,屏幕的宽度可以获得 ...

  6. android确认密码代码,Android自定义View实现验证码or密码输入框

    前言 最近项目中有支付功能,用户输入密码时要类似微信支付密码输入框的样式,本想直接copy网上的,但设计姐姐总是对样式挑三拣四,抽空自己自定义了一个,无奈之下抽空自定义了个,并把它贴到GitHub上供 ...

  7. android相对布局代码,Android基础_3 Activity相对布局(示例代码)

    相对布局要比前面讲的线性布局和表格布局要灵活一些,所以平常用得也是比较多的.相对布局控件的位置是与其周围控件的位置相关的,从名字可以看出来,这些位置都是相对的,确定出了其中一个控件的位置就可以确定另一 ...

  8. android确认密码代码,Android手机卫士之确认密码对话框

    本文接着实现"确认密码"功能,也即是用户以前设置过密码,现在只需要输入确认密码 布局文件和<Android 手机卫士--设置密码对话框>中的布局基本类似,所有copy一 ...

  9. android 进度条 代码,Android 进度条使用详解及示例代码

    在这里,总结一下loading进度条的使用简单总结一下. 一.说起进度条,必须说说条形进度条,经常都会使用到嘛,特别是下载文件进度等等,还有像腾讯QQ安装进度条一样,有个进度总给人良好的用户体验. 先 ...

最新文章

  1. 微软亚研院20周年独家撰文:数据智能的现在与未来
  2. 开启Thread线程只执行一次
  3. 两个表点击分页的时候怎么判断点的是哪一个表_百亿级数据分表后怎么分页查询?...
  4. 基于Linux+Nagios+Centreon+Nagvis等构建海量运维监控系统
  5. JdbcTmplate中的update方法(代码)基础操作
  6. python运行的原理_Python运行机制(转)
  7. 让开!!!谁也别拦着我封装React组件!
  8. cornerstone 库删除 后 重新添加 ,引用找不到,
  9. node.js + express 初体验【hello world】
  10. MFC 获取其他窗口的Edit文本和单击Button
  11. OpenShift 4 之Knative(1) - 创建Knative无服务器架构环境
  12. java fragment_初步认识Fragment 之一 编写简单的fragment代码
  13. MySQL新建数据库时utf8_general_ci编码解释
  14. 斐讯K2刷华硕固件教程(最新)
  15. 大数据私房菜--Hadoop完全分布式安装
  16. 计算机科学美国大学专业,最新!2019年USNews美国大学计算机专业排名
  17. 用telnet逛bbs
  18. 一篇文章搞懂 Hadoop RPC 到底是什么
  19. 从软件外包到阿里技术专家再到CTO,他究竟是如何一路晋升?
  20. 20155327《Java程序设计》第八周学习总结

热门文章

  1. ISP自学1:传感器的动态范围 VS 成像系统的动态范围
  2. 一个奇鸽「船新体验」for —— 这次不上车速度上船 / 更新后的最新版神器!
  3. 【C语言练习】四个整数中找出最大的一个
  4. js有一个棋盘,有64个方格,在第一个方格里面放1粒芝麻重量是0.00001kg,第二个里面放2粒,第三个里面放4,求出棋盘上放的所有芝麻的重量
  5. burpsuite 黑名单绕过爆破
  6. 3-Tensorflow-demo_1-Graph和Session
  7. 使用线性分类器探针理解中间层—Understanding intermediate layers using linear classifier probes
  8. linux基础是什么,Linux基础(字节序是什么鬼)
  9. 基于JavaWeb的企业采购管理系统(源码+论文)
  10. 计算机科学与应用期刊级别,《计算机应用与软件》是什么级别的刊物