Android MediaPlayer MP3播放器(倍速和音量)的封装和所见的问题
技术实现 咱要感谢谷歌大哥的mediaplayer,虽然有些问题挺难搞定,但是咱还是充满信心
功能包括
- MP3格式音乐的播放(MediaPlayer也支持其他格式 so:不解释了)
- MediaPlayer倍速
- MediaPlayer音量设置
- 进度条设置
- 结束 播放完成监听
- 背景和封面 背景高斯模糊(图片都是在线图片)
- 封面旋转动画(已解决从结束位置开始)
等等…
运行效果
代码可能比较多,仔细阅读,转发请标明出处
一.技术选型
- MP3播放选择了谷歌大哥的MediaPlayer(本也考虑ijk,写着发现个弊端,倍速功能不支持6.0以下手机)
- 背景图和显示图片使用了Glide加载图片(包括高斯模糊效果)
- 旋转动画(重点是停止后继续播放当时确实头疼不好找)
简单介绍下开发背景(原因):
最近项目的第一个版本完成,第二个版本的迭代已经开始,产品经理剑走偏锋要求项目增加音频播放功能,包括音频的音量和倍速功能,优于之前并未做过该项功能,对于音频播放咱也是一知半解,于是乎我就开始对一些成功的开源项目进行了简单的研究,绝大多数都是基于mediaplayer进行了封装,源码咱的水平有些地方还是没有看懂,并且绝大多数是都是使用service还有混合广播等方式实现状态栏播放等,由于项目中并不需要这些需求.所以就自己动手写了个,咱写这个博客的时候功能基本完善. 还有啊我用的时间不长可能有些地方考虑不到位 请在评论区指出,thank you
添加的依赖
/glide 相关依赖/
implementation ‘com.github.bumptech.glide:glide:3.8.0’
//高斯模糊 Glide工具类性质的依赖 对性能消耗相对较小
implementation ‘jp.wasabeef:glide-transformations:2.0.1’
二.为了方便调试
为了方便自己调试(得亏是这么做的不然后边bug调试这块咱能头疼死,毕竟直接从项目中进行修改需要担点不大不小的风险)和修改减少耦合度就使用了依赖的方式进行开发
那咱创建Library的方式咱就先不说了
三.实现步骤
接下来呢咱就步入正题
1.创建Library(也可以不创建,直接从demo中写也行)
点击File(左上角)->选择new->选择new Modile->选择Android Library->然后起个中听的名字和顺眼的包名->finish就OK了(emmm)
2.创建一个布局文件…(有点啰嗦了别嫌弃)
咱没有起名字的天赋就先起个my_music_player.xml吧
关于这个布局嵌套的问题是因为刚开始就是用约束去写的但是发现无法撑开整个view但是时间紧迫所以就这么写了下, 那个 大家用的时候自己改一下…别嫌费事…
// my_music_player布局 一顿CV 起的名字是听难听
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#000">
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/my_music_showpopup"
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:background="#0000"/>
<ImageView
android:id="@+id/my_music_background"
android:layout_width="0dp"
android:layout_height="0dp"
android:scaleType="centerCrop"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/my_music_image"
android:layout_width="124dp"
android:layout_height="124dp"
android:padding="4dp"
android:background="@drawable/musicicon"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<ImageView
android:id="@+id/my_music_start"
android:layout_width="44dp"
android:layout_height="44dp"
android:src="@drawable/music_play_normal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<SeekBar
android:id="@+id/my_music_seek"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="88dp"
android:layout_marginRight="88dp"
android:layout_marginBottom="20dp"
android:maxHeight="2dp"
android:progressDrawable="@drawable/playerbg_music_seek_bar"
android:thumb="@drawable/music_player_seek_btn"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<LinearLayout
android:id="@+id/my_music_loaderror"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/music_play_errorbg"
android:gravity="center"
android:orientation="vertical"
android:padding="10dp"
android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="音频加载失败"
android:textColor="#fff"
android:textSize="14sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:background="@drawable/music_play_errorbg"
android:paddingLeft="10dp"
android:paddingTop="5dp"
android:paddingRight="10dp"
android:paddingBottom="5dp"
android:text="点击重试"
android:textColor="#fff"
android:textSize="12sp" />
</LinearLayout>
<ImageView
android:layout_width="24dp"
android:layout_height="wrap_content"
android:layout_marginTop="15dp"
android:layout_marginRight="18dp"
android:src="@drawable/music_icon_more"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/my_music_more"
android:layout_width="44dp"
android:layout_height="44dp"
android:layout_marginRight="10dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<TextView
android:id="@+id/my_music_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="00:00"
android:textColor="#FFF"
android:textSize="13sp"
app:layout_constraintBottom_toBottomOf="@+id/my_music_seek"
app:layout_constraintEnd_toStartOf="@+id/my_music_seek"
app:layout_constraintTop_toTopOf="@+id/my_music_seek" />
<TextView
android:id="@+id/my_music_endtime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="00:00"
android:textColor="#FFF"
android:textSize="13sp"
app:layout_constraintBottom_toBottomOf="@+id/my_music_seek"
app:layout_constraintStart_toEndOf="@+id/my_music_seek"
app:layout_constraintTop_toTopOf="@+id/my_music_seek" />
</android.support.constraint.ConstraintLayout>
</RelativeLayout>
写出来就是这个样子了:
设置SeekBar的属性和封面图的边界
3.Java类继承RelativeLayout(组合式自定义控件)
本想写service去实现播放,由于时间问题,就直接写类里边去了
/**
* 音乐播放器View
*/
public class MyMusicPlayerView extends RelativeLayout {
//控件
public ImageView my_music_background;
public ImageView my_music_image;
public ImageView my_music_start;
public SeekBar my_music_seek;
public LinearLayout my_music_loaderror;
public ImageView my_music_more;
public TextView my_music_time;
public TextView my_music_endtime;
public TextView my_music_showpopup;
//变量
private boolean playNow;//切换后是否立即播放
private int duration;//音频总长度
private int handlerSeekTo = 0;
private int seconds;//记录handle执行的次数和SeekBar最大长度
private int secondsTemp;//用于保存变速后 一秒所代表的长度
private HeadsetReceiver headsetReceiver; // 耳机连接广播
private AudioManager audioManager;//播放管理类
private int status = 0;
private MediaPlayer mediaPlayer;
private Context context;
private String path;
private ObjectAnimator myAnimator;
private PopupWindow popupWindow;
private int thisViewHeight;
private int soundSize = 10;
private int speedText = 10001;
private MusicPlayerComplete musicPlayerComplete;
private boolean isChanged = false;
private String imageUrl;
//常量
private final int SEEK_MAX = 1000;
private final int SECONDS_NOMAL_SPPED = 1000;
private final int MEDIA_STATUS_ISNOTSTART = 0;//未开始播放
private final int MEDIA_STATUS_ISSTART = 1;//已经开始播放
private final int MEDIA_STATUS_ISEND = 2;//播放结束
private final int MEDIA_STATUS_ISERROR = 3;//播放出错
private final int MEDIA_URL_NULL = 4;//url为空
private final float MEDIA_SPEED_1_0 = 1.0f;//一倍速
private final float MEDIA_SPEED_1_25 = 1.25f;//一点二倍速
private final float MEDIA_SPEED_1_5 = 1.5f;//一点五倍速
private final float MEDIA_SPEED_2_0 = 2.0f;//二倍速
private final int MEDIA_CHECKEDSPEED_1_0 = 10001;//一倍速 选中标识
private final int MEDIA_CHECKEDSPEED_1_25 = 10002;//一点二倍速 选中标识
private final int MEDIA_CHECKEDSPEED_1_5 = 10003;//一点五倍速 选中标识
private final int MEDIA_CHECKEDSPEED_2_0 = 10004;//二倍速 选中标识
/**
* 进度条
*/
Handler handler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
my_music_seek.setProgress(handlerSeekTo);//修改进度到---
handlerSeekTo++;
//进度调 进度
if (handlerSeekTo >= seconds) {
handler.removeCallbacksAndMessages(null);
handlerSeekTo = seconds;
status = MEDIA_STATUS_ISEND;
my_music_start.setImageResource(R.drawable.music_restart_normal);
stopAnimation();
//播放完成监听 外部方法
if (musicPlayerComplete != null) {
musicPlayerComplete.musicComplete();
}
} else {
handler.sendEmptyMessageDelayed(1, secondsTemp);
}
my_music_time.setText(getTime(handlerSeekTo * SECONDS_NOMAL_SPPED));
return false;
}
});
public MyMusicPlayerView(Context context) {
this(context, null);
}
public MyMusicPlayerView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MyMusicPlayerView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.context = context;
findView(context);
initAudioManager();
initReceiver(context);
}
private void initAudioManager() {
audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
}
/**
* 初始化耳机连接监听
*
* @param context
*/
private void initReceiver(Context context) {
headsetReceiver = new HeadsetReceiver();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_HEADSET_PLUG);
context.registerReceiver(headsetReceiver, intentFilter);
headsetReceiver.setOnHeadsetDetectLintener(new HeadsetReceiver.OnHeadsetDetectLintener() {
@Override
public void isHeadseDetectConnected(boolean connected) {
if (connected) {
//耳机已连接 do something...
changeToHeadset();
Log.e("外音播放", "" + connected);
} else {
//耳机已断开 do something...
changeToSpeaker();
Log.e("外音播放", "" + connected);
}
}
});
}
/**
* 寻找布局 并初始化需要的东西
*
* @param context 上下文
*/
private void findView(Context context) {
View view = View.inflate(context, R.layout.my_music_player, this);
initView(view);
initMediaPlayer();
initListener(view);
initAnimation();
}
/**
* 初始化View
*
* @param view 布局
*/
private void initView(View view) {
my_music_background = (ImageView) view.findViewById(R.id.my_music_background);
my_music_image = (ImageView) view.findViewById(R.id.my_music_image);
my_music_start = (ImageView) view.findViewById(R.id.my_music_start);
my_music_seek = (SeekBar) view.findViewById(R.id.my_music_seek);
my_music_loaderror = (LinearLayout) view.findViewById(R.id.my_music_loaderror);
my_music_more = (ImageView) view.findViewById(R.id.my_music_more);
my_music_time = (TextView) view.findViewById(R.id.my_music_time);
my_music_endtime = (TextView) view.findViewById(R.id.my_music_endtime);
my_music_showpopup = (TextView) view.findViewById(R.id.my_music_showpopup);
my_music_seek.setMax(SEEK_MAX);
}
/**
* 初始化监听
*
* @param view 布局
*/
private void initListener(View view) {
//播放进度
my_music_seek.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
//手动改变进度
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
int progress = my_music_seek.getProgress();
mediaPlayer.seekTo(progress * 1000);
handlerSeekTo = progress;
}
});
//播放 暂停 重播
my_music_start.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
SelectStatus();
}
});
//播放出错
mediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
status = MEDIA_STATUS_ISERROR;
my_music_start.setImageResource(R.drawable.music_restart_normal);
handler.removeCallbacksAndMessages(null);
stopAnimation();
return false;
}
});
/*//获取当前控件高度
my_music_background.getViewTreeObserver().addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
if (Build.VERSION.SDK_INT >= 16) {
my_music_background.getViewTreeObserver()
.removeOnGlobalLayoutListener(this);
} else {
my_music_background.getViewTreeObserver()
.removeGlobalOnLayoutListener(this);
}
}
});*/
//点击更多功能
my_music_more.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
thisViewHeight = my_music_background.getHeight();
showPoupWind();
}
});
}
/**
* 初始化 MediaPlayer
*/
private void initMediaPlayer() {
mediaPlayer = new MediaPlayer();
if (TextUtils.isEmpty(path)) {
status = MEDIA_URL_NULL;
} else {
status = MEDIA_STATUS_ISNOTSTART;
}
}
/**
* 播放结束 接口回调
*
* @param musicPlayerComplete 接口
*/
public void setMusicPlayerComplete(MusicPlayerComplete musicPlayerComplete) {
this.musicPlayerComplete = musicPlayerComplete;
}
/**
* 展示更多功能弹框
*/
private void showPoupWind() {
popupWindow = new PopupWindow(this);
popupWindow.setWidth(ViewGroup.LayoutParams.MATCH_PARENT);
popupWindow.setHeight(thisViewHeight);
View popupVeiw = LayoutInflater.from(context).inflate(R.layout.my_music_popup, null);
popupWindow.setContentView(popupVeiw);
popupWindow.setBackgroundDrawable(new ColorDrawable(0x00000000));
popupWindow.setOutsideTouchable(false);
popupWindow.setFocusable(true);
popupWindow.showAsDropDown(my_music_showpopup);
final RadioGroup my_music_bsgroup = (RadioGroup) popupVeiw.findViewById(R.id.my_music_bsgroup);
SeekBar my_music_sound = (SeekBar) popupVeiw.findViewById(R.id.my_music_sound);
//上一次popupwindow打开时的音量和倍速
my_music_bsgroup.check(getCheckedId());
my_music_sound.setProgress(soundSize);
changedCheckedColor(my_music_bsgroup, getCheckedId());
//radiobutton 选中
my_music_bsgroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
if (checkedId == R.id.my_music_bsbtn1) {
speedText = MEDIA_CHECKEDSPEED_1_0;
changedSpeed(MEDIA_SPEED_1_0);
} else if (checkedId == R.id.my_music_bsbtn1_25) {
speedText = MEDIA_CHECKEDSPEED_1_25;
changedSpeed(MEDIA_SPEED_1_25);
} else if (checkedId == R.id.my_music_bsbtn1_5) {
speedText = MEDIA_CHECKEDSPEED_1_5;
changedSpeed(MEDIA_SPEED_1_5);
} else if (checkedId == R.id.my_music_bsbtn2) {
speedText = MEDIA_CHECKEDSPEED_2_0;
changedSpeed(MEDIA_SPEED_2_0);
}
changedCheckedColor(my_music_bsgroup, checkedId);
}
});
//播放音量
my_music_sound.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
soundSize = seekBar.getProgress();
float soundSize = progress * 0.1f;
mediaPlayer.setVolume(soundSize, soundSize);
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
//点击背景关闭弹框
popupVeiw.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
popupWindow.dismiss();
}
});
}
/**
* 改变选中btn颜色
*
* @param group 单选按钮的组
* @param checkedId 组内选中的id
*/
private void changedCheckedColor(RadioGroup group, int checkedId) {
for (int i = 0; i < group.getChildCount(); i++) {
if (i != 0) {
int id = group.getChildAt(i).getId();
RadioButton childAt = (RadioButton) group.getChildAt(i);
if (id == checkedId) {
childAt.setTextColor(Color.rgb(216, 176, 118));
} else {
childAt.setTextColor(Color.rgb(255, 255, 255));
}
}
}
}
/**
* 倍速选中
*
* @return view的id
*/
private int getCheckedId() {
int checkedId = R.id.my_music_bsbtn1;
switch (speedText) {
case MEDIA_CHECKEDSPEED_1_0:
return checkedId;
case MEDIA_CHECKEDSPEED_1_25:
checkedId = R.id.my_music_bsbtn1_25;
return checkedId;
case MEDIA_CHECKEDSPEED_1_5:
checkedId = R.id.my_music_bsbtn1_5;
return checkedId;
case MEDIA_CHECKEDSPEED_2_0:
checkedId = R.id.my_music_bsbtn2;
return checkedId;
}
return checkedId;
}
/**
* 设置 MP3地址 和背景图地址
*
* @param path Mp3的路径
* @param imageUrl 封面图路径
*/
public void setUp(String path, String imageUrl) {
this.path = path;//播放地址
this.imageUrl = imageUrl;
try {
if (TextUtils.isEmpty(path)) {
//播放地址无效
status = MEDIA_URL_NULL;
} else {
//有效播放地址
Uri parse = Uri.parse(path);
/*mediaPlayer.setDataSource(context, parse);*/
mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setDataSource(context, parse);
mediaPlayer.prepareAsync();
Log.e("总时长", "" + mediaPlayer.getDuration());
mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
try {
/* mediaPlayer.prepare();*/
status = MEDIA_STATUS_ISNOTSTART;
//音频总长度
duration = mediaPlayer.getDuration();
//seekbar最大长度
seconds = duration / SEEK_MAX;
my_music_seek.setMax(seconds);
//seekbar每一次更新的时间
String time = getTime(duration);
my_music_endtime.setText(time);
if (isChanged && playNow) {
MediaStart();
} else {
secondsTemp = SECONDS_NOMAL_SPPED;
}
isChanged = false;
playNow = false;
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
} catch (IOException e) {
e.printStackTrace();
}
//高斯模糊背景
Glide.with(context)
.load(TextUtils.isEmpty(imageUrl) ? R.drawable.music_play_icon : imageUrl)
.dontAnimate()
.bitmapTransform(new BlurTransformation(context, 40, 3))
.into(my_music_background);
//音乐封面
Glide.with(context)
.load(TextUtils.isEmpty(imageUrl) ? R.drawable.music_play_icon : imageUrl)
.dontAnimate()
.transform(new GlideCircleUtil(context))
.into(my_music_image);
}
/**
* 将int值转换成时长
*
* @return 时长
*/
private String getTime(int durations) {
int time = durations / 1000;
int temp;
StringBuffer sb = new StringBuffer();
temp = time / 3600;
if (temp != 0) {//时长不超过一小时则不添加
sb.append((temp < 10) ? "0" + temp + ":" : "" + temp + ":");
}
temp = time % 3600 / 60;
sb.append((temp < 10) ? "0" + temp + ":" : "" + temp + ":");
temp = time % 3600 % 60;
sb.append((temp < 10) ? "0" + temp : "" + temp);
return sb.toString();
}
/**
* 播放状态 选中状态
*/
private void SelectStatus() {
startMyAnimation();
switch (status) {
case MEDIA_URL_NULL:
Toast.makeText(context, "无效播放地址", Toast.LENGTH_SHORT).show();
my_music_start.setImageResource(R.drawable.music_play_normal);
stopAnimation();
break;
case MEDIA_STATUS_ISNOTSTART:
MediaStart();
break;
case MEDIA_STATUS_ISSTART:
MediaPause();
break;
case MEDIA_STATUS_ISEND:
MediaStart();
break;
case MEDIA_STATUS_ISERROR:
MediaStart();
my_music_start.setVisibility(INVISIBLE);
my_music_loaderror.setVisibility(VISIBLE);
break;
}
}
/**
* 暂停播放
*/
public void MediaPause() {
mediaPlayer.pause();
status = MEDIA_STATUS_ISNOTSTART;
my_music_start.setVisibility(VISIBLE);
my_music_loaderror.setVisibility(INVISIBLE);
my_music_start.setImageResource(R.drawable.music_play_normal);
handler.removeCallbacksAndMessages(null);
stopAnimation();
}
/**
* 音频 开始播放 继续播放 重新播放
*/
public void MediaStart() {
if (handlerSeekTo >= seconds) {
handlerSeekTo = 0;
}
status = MEDIA_STATUS_ISSTART;
my_music_start.setImageResource(R.drawable.music_pause_normal);
my_music_start.setVisibility(VISIBLE);
my_music_loaderror.setVisibility(INVISIBLE);
mediaPlayer.start();
handler.removeCallbacksAndMessages(null);
handler.sendEmptyMessageDelayed(1, 0);
startMyAnimation();
//播放完成
mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
}
});
}
/**
* 播放的地址发生改变
*
* @param musicUrl 播放地址
* @param playNow 是否立即播放
*/
public void MediaChanged(String musicUrl, boolean playNow) {
my_music_time.setText("00:00");
my_music_endtime.setText("00:00");
speedText = MEDIA_CHECKEDSPEED_1_0;
isChanged = true;
this.playNow = playNow;
this.setUp(musicUrl, imageUrl);
}
/**
* 释放内存
*/
public void MediaClear() {
try {
mediaPlayer.release();
handler.removeCallbacksAndMessages(null);
my_music_time.setText("00:00");
mediaPlayer = null;
handlerSeekTo = 0;
my_music_seek.setProgress(0);
this.getContext().unregisterReceiver(headsetReceiver);
} catch (Exception e) {
Log.e("清理MediaPlayer", "" + e);
}
}
/**
* 播放速度
*
* @param speed
*/
private void changedSpeed(float speed) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (!TextUtils.isEmpty(path)) {
mediaPlayer.setPlaybackParams(new PlaybackParams().setSpeed(speed));
String speedString = "已切换为<font color='#D8B076'>" + speed + "</font>倍速度播放";
new CenterToast(this.getContext(), speedString);
secondsTemp = (int) (SECONDS_NOMAL_SPPED / speed);
mediaPlayer.pause();//因为华为等机型的问题 在改变倍速后必须要这么写 先暂停再开始才能倍速播放
MediaStart();
} else {
speedText = MEDIA_CHECKEDSPEED_1_0;
Toast.makeText(context, "无效播放地址", Toast.LENGTH_SHORT).show();
}
}
}
/**
* 初始化 动画
*/
private void initAnimation() {
myAnimator = ObjectAnimator.ofFloat(my_music_image, "rotation", 0f, 360f);
myAnimator.setDuration(25000);
myAnimator.setRepeatCount(-1);
myAnimator.setInterpolator(new LinearInterpolator());
myAnimator.start();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
myAnimator.pause();
} else {
myAnimator.cancel();
}
}
/**
* 开始动画
*/
private void startMyAnimation() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
myAnimator.resume();
} else {
myAnimator.start();
}
}
/**
* 停止动画
*/
private void stopAnimation() {
if (myAnimator != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
myAnimator.pause();
} else {
myAnimator.cancel();
}
}
}
/**
* 切换到外放
*/
public void changeToSpeaker() {
audioManager.setMode(AudioManager.MODE_NORMAL);
audioManager.setSpeakerphoneOn(true);
}
/**
* 切换到耳机模式
*/
public void changeToHeadset() {
audioManager.setSpeakerphoneOn(false);
}
}
4.设置结束监听回调的接口
public interface MusicPlayerComplete {
void musicComplete();//仅仅用于结束监听
}
5.封面图使用的Glide圆形工具
本来是不想着放的 想了想还是放吧 其实也没啥意义 主要是Glide比较新的版本都可以直接整圆了 我因为项目的问题暂时还用着较老版本
public class GlideCircleUtil extends BitmapTransformation {
public GlideCircleUtil(Context context) {
super(context);
}
@Override protected Bitmap transform(BitmapPool pool, Bitmap toTransform, int outWidth, int outHeight) {
return circleCrop(pool, toTransform);
}
private static Bitmap circleCrop(BitmapPool pool, Bitmap source) {
if (source == null) return null;
int size = Math.min(source.getWidth(), source.getHeight());
int x = (source.getWidth() - size) / 2;
int y = (source.getHeight() - size) / 2;
// TODO this could be acquired from the pool too
Bitmap squared = Bitmap.createBitmap(source, x, y, size, size);
Bitmap result = pool.get(size, size, Bitmap.Config.ARGB_8888);
if (result == null) {
result = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
}
Canvas canvas = new Canvas(result);
Paint paint = new Paint();
paint.setShader(new BitmapShader(squared, BitmapShader.TileMode.CLAMP, BitmapShader.TileMode.CLAMP));
paint.setAntiAlias(true);
float r = size / 2f;
canvas.drawCircle(r, r, r, paint);
return result;
}
@Override public String getId() {
return getClass().getName();
}
}
我刚开始写到这里就感觉已经完成了
但是提交后产品给提了个 插上耳机耳机无声音
其实上边代码可以看到一个角耳机连接广播的类是红色的 这个是因为Android没有自己处理 咱只能通过广播去监听耳机连接状态 SO :
6.添加耳机连接的广播接收者
public class HeadsetReceiver extends BroadcastReceiver {
private OnHeadsetDetectLintener onHeadsetDetectLintener;
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (Intent.ACTION_HEADSET_PLUG.equals(action)) {
if (intent.hasExtra("state")) {
int state = intent.getIntExtra("state", 0);
if (state == 1) {
//插入耳机
onHeadsetDetectLintener.isHeadseDetectConnected(true);
} else if (state == 0) {
//拔出耳机
onHeadsetDetectLintener.isHeadseDetectConnected(false);
}
}
}
}
public interface OnHeadsetDetectLintener {
void isHeadseDetectConnected(boolean connected);
}
public void setOnHeadsetDetectLintener(OnHeadsetDetectLintener onHeadsetDetectLintener) {
this.onHeadsetDetectLintener = onHeadsetDetectLintener;
}
}
到这里封装基本完成了就 代码量不多 可是当时也是耗费了一天的时间
接下来就是标准的流程了–总结 — 发现个问题好像每一篇博客的长度都是有限制的
四.出现的问题
别看代码不多,一些值得注意值得记下来的问题倒是不少
1.属性动画点暂停后再次点击开始将会从头开始播放
属性动画,确实也好用但是会出现一个比较普遍的问题就是结束后停留在指定位置,再次开始将会从头开始,如果是我这中微强迫症还能捏着鼻子接收,就是微微的不爽,但是对于一些强迫症比较强的人来说就能难受死,所以啊全网找方法,最终! 功夫不负有心人找到了.但是也是有弊端的,就是对Android版本有一定的要求,要是有大佬有更好的解决方案,请告知
/**
* 初始化 动画
*/
private void initAnimation() {
myAnimator = ObjectAnimator.ofFloat(my_music_image, "rotation", 0f, 360f);
myAnimator.setDuration(25000);
myAnimator.setRepeatCount(-1);
myAnimator.setInterpolator(new LinearInterpolator());
myAnimator.start();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
myAnimator.pause();
} else {
myAnimator.cancel();
}
}
/**
* 开始动画
*/
private void startMyAnimation() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
myAnimator.resume();
} else {
myAnimator.start();
}
}
/**
* 停止动画
*/
private void stopAnimation() {
if (myAnimator != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
myAnimator.pause();
} else {
myAnimator.cancel();
}
}
}
2.进度条的进度问题
在刚开始的时候我吧进度条设置为了1000的长度,在播放时间相对较短的音频的时候还没有什么问题,但是我加载了一个我们公司时长为1:32:00的音频的时候发现进度条每好几秒(也是给忘了几秒了)移动一个单位…这种情况肯定是不行的,于是就讲handler的发送时间调整为了1000(毫秒),以整秒发送,音频总长度比上1000就是handler执行的次数和进度条的最大长度,
3.华为手机设置倍速问题
mediaplayer在播放的时候需要添加倍速功能,于是就开始找倍速方法和代码,发现好简单啊,写完后提交功能后经测试,发现在三款华为手机上皆有问题,mediaplayer正常播放没有问题,但是!一旦切换倍速功能就会出现问题,只要点击倍速功能 哪怕是正常播放(就是一倍速)点击了一倍速播放那也会进入无声状态…
倍速接口说明:
(1) 使用这个接口可以进行播放速率的设置。
(2) 播放器prepared状态之前调用这个方法不会更改播放器的状态。
(3) prepared状态之后设置速率0等同于调用pause(),当调用start恢复播放以后,将以原来的速率进行播放。
(4) prepared状态之后设置非0的速率等同于调用start()。
(5) 当播放器还未初始化或者已经被释放的时候设置会抛IllegalStateException的异常。
(6) 当参数不支持的时候会抛IllegalArgumentException的异常。
设置时机要求:
合法的时机:Initialized, Prepared, Started, Paused, PlaybackCompleted, Error
非法的时机:Idle, Stopped
4.界面绘制出现过慢或者ANR
主要是因为音频mediaPlayer.prepare();方法
原因:一般在线音频播放是不使用mediaPlayer.prepare()而是使用mediaPlayer.prepareAsync()方法
prepare方法是将资源同步缓存到内存中,一般加载本地较小的资源可以用这个,如果是较大的资源或者网络资源建议使用prepareAsync方法,异步加载.但如果想让资源启动,即start()起来,因为在异步中,如果不设置监听直接start的话,是拿不到这个资源,如果让线程睡眠一段时间,则可以取得资源,因为这个时候,异步线程已经取得资源,但不可能使用线程睡眠的方式来获取资源啊.所以就需要设置监听事件setOnPreparedListener();来通知MediaPlayer资源已经获取到了,然后实现onPrepared(MediaPlayer mp)方法.在里面启动MediaPlayer
这句摘抄自(https://blog.csdn.net/qq_24223073/article/details/69315856)
不管咋,确实人家说的对 ,咱认同 按照所说改了后也就好使了
5.解决完界面绘制出现过慢或者ANR发现获取音频长度不准确…
刚开始写的加载音频方法是:mediaPlayer.prepare();
使用这个方法获取音频时长是正确的.
因为使用prepare方法会导致界面加载过慢等问题就给替换成了prepareAsync,运行时发现某些机型上获取音频的总时长要小于正常音频时长,也有些会多一些(获取的时长短了咱能理解,边长那就不明白了)
我之前的写法是
mediaPlayer.prepareAsync();
//音频总长度
duration = mediaPlayer.getDuration();
找了一系列方法发现此方法是最有效的一个方法:
//此方法要放到mediaPlayer.prepareAsync()之后不然无效
mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
//获取时长
}
});
至此呢基本已经解决了问题,有些问题解决了 没想起来就没有往里边写
6.免积分下载地址 (记得点赞)
下载链接这个音频播放器(虽然称不上播放器)并没有创建单独的一个工作空间区备份,就先和视频播放器在一个工作空间了,视频播放器不是咱自己写的,是基于饺子视频播放器改的
来源:CSDN
作者:要买菜 先挣钱
链接:https://blog.csdn.net/L_1145418863/article/details/94381342