背景:我们正常的文章列表是句式连播,ui上进度条两侧是显示的当前播放是第几句以及总句数,以句为单位,在进行进度条滑动时也是设置到某一句播放。 这2点不同于传统音乐播放器,传统音乐播放器播放的是整首单个mp3文件,ui上显示的当前播放到多少秒,以及总时长共多少秒,进度条拖动也是精确到秒的。
有的句音频长有的短,所以进度条在动态上会有些突兀。
需要是让我改成与传统音乐播放器一样,播放以秒为单位。逐秒控制播放、当前和总共都以秒来显示。 以下是设计思路和关键代码 记录一下。
实现思路:
1、先把总秒数算出来。 如果数据能支持就让数据支持,在数据模型中加一个字段duration ,给出每一句的时长,然后累加计算,得出的总时长用来设置进度的长度和ui显示。
csharp
//在获取数据中 进行累加时长
if (list.get(i) != null && list.get(i).getDuration() > 0) {
map.put("duration", list.get(i).getDuration() + "");
// Log.i("TAG", "duration: "+ list.get(i).getDuration());
long duration = list.get(i).getDuration();
ConstantFd.durations.add(duration);
ConstantFd.totalDuration = ConstantFd.totalDuration + duration;
// Log.i("TAG", "totalDuration: "+totalDuration);
}
//3个全局变量
public static List<Long> durations = new ArrayList<>(); //每句的时长列表
public static long totalDuration = 0;//总时长
public static int currentPoi = 0; //当前播放句
如果数据上不支持,只能让播放器去加载音频列表再得出总时长了。
2、在播放服务中建一个Handler 每秒向UI发送进度,UI收到消息进行数据转换和展示
arduino
private static long process = 0; //播放进度
private static long max = 0; //最大进度
private final Handler mHandler_tb = new Handler(new Handler.Callback() { //播放进度
@Override
public boolean handleMessage(Message msg) {
switch (msg.what) {
case 0:
max = ConstantFd.totalDuration;
if(max<0){
max=0;
}else if (max > 10000){
max -= 1000;
}
long cur = apm.getMediaPlayer().getCurrentPosition(); //当前句子进度
process = cur + getPlayProcess(); //前几句的总时间
if(process<0){
process=0;
}
// Log.i("TAG", "process: "+process);
// Log.i("TAG", "max: "+max);
PoiToUIMsg.sendMsg(process, max); //发送进度到UI
if(ConstantFd.isplay){
process = process + 1000;
mHandler_tb.sendEmptyMessageDelayed(0, 1000);
}
fixedThreadPool.execute(command4); //保存播放进度
break;
}
return true;
}
});
private static long getPlayProcess(){ //获取播放进度
if(ConstantFd.durations.size()==0 || mPosSomeTime == 0){
return 0;
}
if(mPosSomeTime >= ConstantFd.durations.size()){
mPosSomeTime = ConstantFd.durations.size()-1;
ConstantFd.currentPoi = mPosSomeTime;
}
long pro = 0;
for (int i = 0; i < mPosSomeTime; i++) {
pro = pro + ConstantFd.durations.get(i);
}
return pro;
}
//进度消息
public class PoiToUIMsg { //服务->UI
long poi; //当前指针
long max; //最大指针
public long getPoi() {
return poi;
}
public void setPoi(long poi) {
this.poi = poi;
}
public long getMax() {
return max;
}
public void setMax(long max) {
this.max = max;
}
public static void sendMsg(long poi, long max){
PoiToUIMsg msg = new PoiToUIMsg();
msg.setPoi(poi);
msg.setMax(max);
EventBus.getDefault().post(msg); //发送指针
}
}
ini
@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
public void receiveProcess(PoiToUIMsg msg) { //接收进度
// Log.i("TAG", "process: "+msg.getPoi());
// Log.i("TAG", "getMax: "+msg.getMax());
mBinding.seekbar1.setMax((int)msg.getMax());
mBinding.seekbar1.setProgress((int)msg.getPoi());
if(msg.getPoi()<=msg.getMax()){
mBinding.tvNum1.setText(ConstantFd.FormatMiss(msg.getPoi()));
}else {
mBinding.tvNum1.setText(ConstantFd.FormatMiss(msg.getMax()));
}
mBinding.tvNum2.setText(ConstantFd.FormatMiss(msg.getMax()));
}
// 格式化 录音时长为 时:分:秒
public static String FormatMiss(long miss) {
String mm,ss;
if(((miss / 1000) % 3600) / 60 < 10){
mm = "0"+((miss / 1000) % 3600) / 60;
}else {
mm = ""+((miss / 1000) % 3600) / 60;
}
if((miss / 1000) % 60 < 10){
ss = "0"+(miss / 1000) % 60;
}else {
ss = ""+(miss / 1000) % 60;
}
return mm + ":" + ss;
}
3、播放动态中,需要把秒和句进行转换 ,下面是Fragment中的代码,滑动进度条时发送进度消息,ui页面和服务都会接收到。
java
mBinding.seekbar1.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) {//进度条滑动
Log.e("TAG", "进度条,当前值:" + seekBar.getProgress());
mBinding.ivReadBf.setImageResource(R.drawable.icon_pause);//图标改为播放
PoiToPlayMsg.sendMsg(seekBar.getProgress()); //发送指针
mBinding.tvNum1.setText(ConstantFd.FormatMiss(seekBar.getProgress()));
}
});
@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
public void receiveProcess(PoiToPlayMsg msg) { //接收滑动的进度
seekToSpecificPoi(msg.getPoi());
}
private void seekToSpecificPoi(long totalPositionMs) { //播放第几秒
Log.i("TAG", " totalPositionMs: "+totalPositionMs);
Log.i("TAG", " totalDuration: "+ConstantFd.totalDuration);
int mPosSomeTime = 0;
if (totalPositionMs >= ConstantFd.totalDuration) {
mPosSomeTime = ConstantFd.durations.size() - 1;
}else {
long accumulatedDuration = 0l;
for (int i = 0; i < ConstantFd.durations.size(); i++) {
long duration = ConstantFd.durations.get(i);
if (totalPositionMs < accumulatedDuration + duration) {
long relativePositionMs = totalPositionMs - accumulatedDuration;
mPosSomeTime = i;
//Log.i("TAG", " mPosSomeTime: "+mPosSomeTime);
//Log.i("TAG", " relativePositionMs: "+relativePositionMs);
break;
}
accumulatedDuration += duration;
}
}
readAdapter.mPosSomeTime = mPosSomeTime;//播放指针 高亮显示
if(isResumed()){
readAdapter.notifyDataSetChanged(); //列表UI更新
mBinding.recyclerView.scrollToPosition(readAdapter.mPosSomeTime); //列表滚动
}
}
public class PoiToPlayMsg {
long poi; //当前指针
public long getPoi() {
return poi;
}
public void setPoi(long poi) {
this.poi = poi;
}
public static void sendMsg(long poi){
PoiToPlayMsg msg = new PoiToPlayMsg();
msg.setPoi(poi);
EventBus.getDefault().post(msg); //发送指针
}
}
4、服务中的代码 根据秒计算播放指针,以及连续播放时根据指针计算秒
ini
@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
public void receiveProcess(PoiToPlayMsg msg) { //接收滑动的进度
if (readType.equals(ConstantFd.tbtl)) {
setPlayPoi(msg.getPoi());
}
}
private void setPlayPoi(long poi){ //播放指定位置
Log.i("TAG", "滑动的 poi: "+poi);
seekToSpecificPosition(poi);
ConstantFd.isplay = true;
apm.playOrPause(true);
}
private void seekToSpecificPosition(long totalPositionMs) { //需要播放第几秒
Log.i("TAG", " totalPositionMs: "+totalPositionMs);
Log.i("TAG", " totalDuration: "+ConstantFd.totalDuration);
mHandler_tb.removeMessages(0);
apm.playOrPause(false);
if (totalPositionMs >= ConstantFd.totalDuration) {
mPosSomeTime = ConstantFd.durations.size() - 1;
ConstantFd.currentPoi = mPosSomeTime;
playStart();
return;
}
process = totalPositionMs;
long accumulatedDuration = 0l;
for (int i = 0; i < ConstantFd.durations.size(); i++) {
long duration = ConstantFd.durations.get(i);
if (totalPositionMs < accumulatedDuration + duration) {
long relativePositionMs = totalPositionMs - accumulatedDuration;
mPosSomeTime = i;
ConstantFd.currentPoi = mPosSomeTime;
Log.i("TAG", " mPosSomeTime: "+mPosSomeTime);
Log.i("TAG", " relativePositionMs: "+relativePositionMs);
playStart();//先加载播放到当前句
apm.getMediaPlayer().seekTo(relativePositionMs);//然后设置到秒
break;
}
accumulatedDuration += duration;
}
mHandler_tb.sendEmptyMessage(0);
}
private void playStart() {
apm.setAudioFile2(ConstantFd.ycList.get(mPosSomeTime).get("read")); //在线
apm.start();
}
@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
public void receiveEventBus_Poi(PlayPoiMsg msg) { //接收指针 播这一句
mHandler_tb.removeMessages(0);
mPosSomeTime = msg.getPoi();
ConstantFd.currentPoi = mPosSomeTime;
process = 0;
for (int i = 0; i < mPosSomeTime; i++) {
long duration = ConstantFd.durations.get(i);
process = process+duration; //计算已播放的时长
}
ConstantFd.isplay = true;
apm.playOrPause(true);
playStart();
mHandler_tb.sendEmptyMessage(0);
}
总结:
秒-句转换算法(核心)
有两个类似的遍历逻辑,但作用不同:
- 服务层
seekToSpecificPosition:把秒拆成句索引 + 句内偏移,然后seekTo(offset) - UI 层
seekToSpecificPoi:只算句索引,用于列表高亮和滚动
ini
总时长 = [句0: 3000ms] + [句1: 2500ms] + [句2: 4000ms] = 9500ms
用户拖动到 5500ms:
- 句0累计 3000ms,5500 > 3000,继续
- 句1累计 5500ms,5500 < 5500?不,是 <= 还是 < 要注意边界
- 实际应落在句1内偏移 2500ms(如果 5500-3000=2500,正好等于句1时长)
边界:如果拖动到 9500ms(末尾),应直接定位到最后一句。
3 个全局静态是老项目遗留写法,不推荐新项目使用!!!