当前位置: 首页 > news >正文

Android -- 简易音乐播放器

Android – 简易音乐播放器

 播放器功能:* 1. 播放模式:单曲、列表循环、列表随机;* 2. 后台播放(单例模式);* 3. 多位置同步状态回调;处理模块:* 1. 提取文件信息:音频文件(.mp3) -> 对象类(AudioBean);* 2. 后台播放管理:VMPlayer(实现对音频的播放相关处理);* 3. UI显示及控制:歌曲列表 + 播放控制器;

效果:

在这里插入图片描述

模块一:处理音频文件(后台服务内)
/**
* 同步指定文件夹下音频文件
* * @param autoPlay 是否自动播放
*/
private void flashAudioRes(boolean autoPlay) {Log.d(TAG, "同步音频中...");new Thread(() -> {try {List<AudioBean> audioItems = synLocalMusic2(FileUtils.getAudioDir());if (audioItems != null && !audioItems.isEmpty()) {//排序Collections.sort(audioItems, (o1, o2) -> o1.getDisplayName().compareTo(o2.getDisplayName()));VMPlayer.getInstance().setPlayList(audioItems);if(autoPlay){Thread.sleep(1000);VMPlayer.getInstance().resetIndex();VMPlayer.getInstance().play();VMPlayer.getInstance().notifyListChanged();}} else {//closeDialogSyn("本地无有效音频文件!", 3000);}} catch (Exception e) {e.printStackTrace();}}).start();}/*** 同步指定文件夹下音频文件:仅一层* @param dir 文件夹*/private List<AudioBean> synLocalMusic2(String dir) {File root = new File(dir);if (root.exists()) {File[] files = root.listFiles();if (files == null || files.length < 1) {return null;}List<AudioBean> list = new ArrayList<>();MediaPlayer mediaPlayer = new MediaPlayer();int duration = 0;for (File f : files) {//筛选目标文件if (f.isFile() && f.getName().endsWith(".mp3")) {try {mediaPlayer.reset();mediaPlayer.setDataSource(f.getPath());mediaPlayer.prepare();duration = mediaPlayer.getDuration();} catch (IOException var5) {var5.printStackTrace();}Log.v(TAG, "synLocalMusic: " + f.getName() + " - " + duration);AudioBean bean = getAudioFileInfo(f.getPath(), f.length(), duration);list.add(bean);}}if (mediaPlayer != null) {mediaPlayer.reset();mediaPlayer.release();}return list;}return null;}/*** 文件绝对路径,校验放在外面* 文件名格式:歌手 - 歌名.mp3*/private AudioBean getAudioFileInfo(String path, long size, int duration) {AudioBean songsInfo = new AudioBean();//xxx/Music/歌手 - 歌名.mp3//filenameString displayName = path.substring(path.lastIndexOf("/") + 1);//歌手 - 歌名.mp3String album = displayName.substring(0, displayName.lastIndexOf("."));//歌手 - 歌名String name;String artist;if (album.contains("-")) {artist = album.substring(0, album.lastIndexOf("-")).trim();//歌手name = album.substring(album.lastIndexOf("-") + 1).trim();//歌名} else {artist = name = album;}songsInfo.setName(name);songsInfo.setDisplayName(displayName);songsInfo.setArtist(artist);songsInfo.setDuration(duration);songsInfo.setSize(size);songsInfo.setPath(path);return songsInfo;}
/*** Created by Administrator on 2024/11/24.* Usage: 简单自定义音频文件bean类*/public class AudioBean implements Serializable {private String name;//歌名private String displayName;//显示名(文件名去后缀)private String artist;//歌手名private String path;//文件路径private int duration;//时长private long size;//文件大小public AudioBean() {}public AudioBean(String path) {//this.path = path;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getDisplayName() {return displayName;}public void setDisplayName(String displayName) {this.displayName = displayName;}public String getArtist() {return artist;}public void setArtist(String artist) {this.artist = artist;}public String getPath() {return path;}public void setPath(String path) {this.path = path;}public int getDuration() {return duration;}public void setDuration(int duration) {this.duration = duration;}public long getSize() {return size;}public void setSize(long size) {this.size = size;}@Overridepublic String toString() {return "AudioBean{" +"name='" + name + '\'' +", displayName='" + displayName + '\'' +", artist='" + artist + '\'' +", path='" + path + '\'' +", duration=" + duration +", size=" + size +'}';}
}
模块二:播放管理器
VMPlayer.java (主要类)
import android.annotation.SuppressLint;
import android.content.Context;
import android.media.MediaPlayer;
import android.os.Handler;
import android.text.TextUtils;
import android.util.Log;import com.nepalese.harinetest.config.ShareDao;
import com.nepalese.harinetest.utils.MathUtil;import java.io.IOException;
import java.util.ArrayList;
import java.util.List;/*** Created by Administrator on 2024/11/25.* Usage: virgo music player* 1. 播放模式:单曲、列表循环、列表随机;* 2. 后台播放(单例模式);* 3. 多位置同步状态回调;*/
public class VMPlayer implements MediaPlayer.OnCompletionListener, VirgoPlayerCallback {private static final String TAG = "VMPlayer";private static final long INTERVAL_GET_PROGRESS = 500;//后台获取进度频率//播放器状态public static final int STATE_ERROR = -1; //错误状态:需要重置列表才能继续使用public static final int STATE_INITIAL = 0;//初始化状态public static final int STATE_PREPARED = 1;//播放列表/资源已设置public static final int STATE_PLAYING = 2;public static final int STATE_PAUSE = 3;//播放模式public static final int MODE_SINGLE = 0;//单曲循环public static final int MODE_LOOP = 1;//列表循环public static final int MODE_RANDOM = 2;//列表随机@SuppressLint("StaticFieldLeak")private static volatile VMPlayer instance;//单例private Context context;private MediaPlayer mediaPlayer;private List<AudioBean> beanList;//当前播放列表private List<iPlayBack> iPlayBacks;//已注册回调列表private AudioBean curBean;//当前在播放的音频private int curState;//当前播放状态private int curIndex;//当前播放索引private int curMode;//当前播放模式private int errTime;//播放器连续出错次数private int aimSeek;//播放前设置的进度public static VMPlayer getInstance() {if (instance == null) {synchronized (VMPlayer.class) {if (instance == null) {instance = new VMPlayer();}}}return instance;}private VMPlayer() {beanList = new ArrayList<>();iPlayBacks = new ArrayList<>(5);//最多同时存在回调个数mediaPlayer = new MediaPlayer();mediaPlayer.setLooping(false);mediaPlayer.setOnCompletionListener(this);}public void init(Context context) {this.context = context;curState = STATE_INITIAL;curMode = ShareDao.getAudioMode(context);//记忆播放模式 默认列表循环 1curIndex = ShareDao.getAudioIndex(context);//记忆播放位置 0errTime = 0;aimSeek = 0;Log.d(TAG, "init: " + curIndex);}/*** 播放器是否可播放*/private boolean isValid() {return curState >= STATE_PREPARED && !beanList.isEmpty();}public List<AudioBean> getBeanList() {return beanList;}//仅手动导入时调用public void resetIndex() {this.curIndex = 0;}@Overridepublic void onCompletion(MediaPlayer mp) {if (curMode == MODE_SINGLE) {//单曲循环时自动重复播放mediaPlayer.seekTo(0);mediaPlayer.start();} else {notifyComplete();}}public void playOrPause() {if (curState == STATE_PLAYING) {pause();} else {play();}}/*** 播放|继续播放*/@Overridepublic void play() {if (isValid()) {if (curState == STATE_PAUSE) {//继续播放curState = STATE_PLAYING;mediaPlayer.start();notifyStateChanged(true);} else if (curState == STATE_PREPARED) {prepareAndPlay();}//正在播放...} else {Log.d(TAG, "play: " + curState + " - size: " + beanList.size());notifyError("未设置播放列表!");}}private void prepareAndPlay() {curState = STATE_PREPARED;if (curIndex < 0 || curIndex >= beanList.size()) {curIndex = 0;}ShareDao.setAudioIndex(context, curIndex);Log.d(TAG, "播放: " + curIndex);playResource(beanList.get(curIndex));}//播放资源private void playResource(AudioBean bean) {if (bean == null || TextUtils.isEmpty(bean.getPath())) {++errTime;notifyStateChanged(false);if (errTime >= beanList.size()) {//需要重置列表才能继续使用curState = STATE_ERROR;notifyError("播放列表异常!");} else {//播放下一首curState = STATE_PREPARED;playNext();}return;}try {mediaPlayer.reset();mediaPlayer.setDataSource(bean.getPath());//本地文件、在线链接mediaPlayer.setOnPreparedListener(mp -> {notifySongChanged(bean);notifyStateChanged(true);curState = STATE_PLAYING;mediaPlayer.seekTo(aimSeek);mediaPlayer.start();errTime = 0;aimSeek = 0;curBean = bean;});mediaPlayer.prepareAsync();startTask();} catch (IOException e) {++errTime;if (errTime >= beanList.size()) {//需要重置列表才能继续使用curState = STATE_ERROR;} else {//重置状态if (beanList.size() > 0) {curState = STATE_PREPARED;} else {curState = STATE_INITIAL;}}notifyStateChanged(false);notifyError("播放器出错!" + e.getMessage());}}/*** 播放当前列表指定位置** @param index index*/@Overridepublic void play(int index) {if (isValid()) {curIndex = index;prepareAndPlay();} else {notifyError("未设置播放列表!");}}/*** 临时播放某个音频文件** @param bean AudioBean*/@Overridepublic void play(AudioBean bean) {if (bean == null) {notifyError("指定歌曲为空!");return;}curState = STATE_PREPARED;playResource(bean);}/*** 更换播放列表** @param list  新列表* @param index 开始位置,默认:0*/@Overridepublic void play(List<AudioBean> list, int index) {if (list == null || list.isEmpty()) {notifyError("新列表为空!");return;}curIndex = index;setPlayList(list);prepareAndPlay();}/*** 上一首*/@Overridepublic void playLast() {if (isValid()) {switch (curMode) {case MODE_SINGLE:break;case MODE_LOOP:if (curIndex > 0) {--curIndex;} else {curIndex = beanList.size() - 1;}prepareAndPlay();break;case MODE_RANDOM:curIndex = MathUtil.getRandom(0, beanList.size(), curIndex);prepareAndPlay();break;}} else {notifyError("未设置播放列表!");}}/*** 下一首*/@Overridepublic void playNext() {if (isValid()) {switch (curMode) {case MODE_SINGLE:break;case MODE_LOOP:++curIndex;prepareAndPlay();break;case MODE_RANDOM:curIndex = MathUtil.getRandom(0, beanList.size(), curIndex);prepareAndPlay();break;}} else {notifyError("未设置播放列表!");}}/*** 暂停播放*/@Overridepublic void pause() {if (isPlaying()) {curState = STATE_PAUSE;mediaPlayer.pause();notifyStateChanged(false);}}/*** 跳转播放进度** @param progress p*/@Overridepublic void seekTo(int progress) {if (isValid()) {if (curState > STATE_PREPARED) {aimSeek = 0;mediaPlayer.seekTo(progress);} else {aimSeek = progress;}}}/*** 设置播放列表** @param beans b*/@Overridepublic void setPlayList(List<AudioBean> beans) {if (beans == null || beans.isEmpty()) {notifyError("新列表为空!");return;}Log.d(TAG, "setPlayList: " + beans.size());curState = STATE_PREPARED;beanList.clear();beanList.addAll(beans);curBean = beanList.get(curIndex);}/*** 设置播放模式,外部校验** @param mode m*/@Overridepublic void setPlayMode(int mode) {if (mode == this.curMode) {return;}this.curMode = mode;ShareDao.setAudioMode(context, mode);Log.d(TAG, "setPlayMode: " + curMode);}/*** 是否正在播放*/@Overridepublic boolean isPlaying() {return isValid() && mediaPlayer.isPlaying();}/*** 当前播放进度*/@Overridepublic int getCurProgress() {return mediaPlayer.getCurrentPosition();}/*** 当前播放器状态*/@Overridepublic int getCurState() {return curState;}@Overridepublic int getCurMode() {return curMode;}/*** 获取当前播放音频信息* 可空*/@Overridepublic AudioBean getCurMusic() {if (isValid()) {return curBean;}return null;}/*** 注销播放器*/@Overridepublic void releasePlayer() {stopTask();if (iPlayBacks != null) {iPlayBacks.clear();iPlayBacks = null;}if (beanList != null) {beanList.clear();beanList = null;}try {if (mediaPlayer != null) {//stop 可能会有异常if (mediaPlayer.isPlaying()) {mediaPlayer.stop();}mediaPlayer.reset();mediaPlayer.release();mediaPlayer = null;}} catch (Exception e) {//} finally {if (mediaPlayer != null) {mediaPlayer.reset();mediaPlayer.release();mediaPlayer = null;}}instance = null;curState = STATE_INITIAL;}/*** 注册播放器回调*/@Overridepublic void registerCallback(iPlayBack callback) {iPlayBacks.add(callback);}/*** 注销播放器回调*/@Overridepublic void unregisterCallback(iPlayBack callback) {iPlayBacks.remove(callback);}@Overridepublic void removeCallbacks() {iPlayBacks.clear();}public void notifyListChanged() {if (iPlayBacks != null) {for (iPlayBack callBack : iPlayBacks) {callBack.onListChange();}}}private void notifySongChanged(AudioBean bean) {if (iPlayBacks != null) {for (iPlayBack callBack : iPlayBacks) {callBack.onChangeSong(bean);}}}private void notifyStateChanged(boolean isPlaying) {if (iPlayBacks != null) {for (iPlayBack callback : iPlayBacks) {callback.onPlayStateChanged(isPlaying);}}}private void notifyComplete() {if (iPlayBacks != null) {for (iPlayBack callback : iPlayBacks) {callback.onPlayCompleted();}}}private void notifyProcessChanged(int process) {if (iPlayBacks != null) {for (iPlayBack callback : iPlayBacks) {callback.onProcessChanged(process);}}}private void notifyError(String msg) {if (iPlayBacks != null) {for (iPlayBack callback : iPlayBacks) {callback.onPlayError(curState, msg);}}}private final Handler handler = new Handler(msg -> false);private final Runnable getProcessTask = new Runnable() {@Overridepublic void run() {handler.postDelayed(getProcessTask, INTERVAL_GET_PROGRESS);try {if (isPlaying()) {notifyProcessChanged(getCurProgress());}} catch (Throwable ignored) {}}};private void startTask() {stopTask();handler.post(getProcessTask);}private void stopTask() {handler.removeCallbacks(getProcessTask);}}
VirgoPlayerCallback.java (功能接口)
/*** Created by Administrator on 2024/11/24.* Usage: 音乐播放器公开接口*/
public interface VirgoPlayerCallback {//播放|继续播放void play();//播放当前列表指定位置void play(int index);//临时播放某个音频文件void play(AudioBean bean);//更换播放列表void play(List<AudioBean> beanList, int index);//上一首void playLast();//下一首void playNext();//暂停播放void pause();//跳转播放进度void seekTo(int progress);//设置播放列表void setPlayList(List<AudioBean> beans);//设置播放模式void setPlayMode(int mode);//是否正在播放boolean isPlaying();//当前播放进度int getCurProgress();//当前播放器状态int getCurState();//当前播放模式int getCurMode();//获取当前播放音频信息AudioBean getCurMusic();//注销播放器void releasePlayer();void registerCallback(iPlayBack callback);void unregisterCallback(iPlayBack callback);void removeCallbacks();
}
iPlayBack.java(播放状态回调接口)
public interface iPlayBack {//歌单变化void onListChange();void onChangeSong(@NonNull AudioBean bean);//播放结束时调用void onPlayCompleted();//播放状态变化时调用:播放|暂停void onPlayStateChanged(boolean isPlaying);//播放进度变化时调用void onProcessChanged(int process);//播放出错时调用void onPlayError(int state, String error);
}
模块三:播放控件+歌曲列表
VirgoSimplePlayer.java (简单音乐播放器控件)

在这里插入图片描述

import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.widget.ImageButton;
import android.widget.RelativeLayout;
import android.widget.SeekBar;
import android.widget.TextView;import com.nepalese.harinetest.R;
import com.nepalese.harinetest.utils.ConvertUtil;/*** Created by Administrator on 2024/11/24.* Usage: 简单音乐播放器控件*/public class VirgoSimplePlayer extends RelativeLayout {private static final String TAG = "VirgoSimplePlayer";private SeekBar musicSeekbar;private TextView musicName, musicCur, musicAll;private ImageButton musicLast, musicPlay, musicNext, musicMode;private VMPlayer vmPlayer;public VirgoSimplePlayer(Context context) {this(context, null);}public VirgoSimplePlayer(Context context, AttributeSet attrs) {this(context, attrs, 0);}public VirgoSimplePlayer(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);LayoutInflater.from(context).inflate(R.layout.layout_simple_virgo_player, this, true);init();}private void init() {initUI();initData();setListener();}private void initUI() {musicSeekbar = findViewById(R.id.music_seekbar);musicName = findViewById(R.id.music_tv_name);musicCur = findViewById(R.id.music_cur);musicAll = findViewById(R.id.music_all);musicLast = findViewById(R.id.music_btn_last);musicPlay = findViewById(R.id.music_btn_paly);musicNext = findViewById(R.id.music_btn_next);musicMode = findViewById(R.id.music_btn_mode);musicName.setSelected(true);}private void initData() {vmPlayer = VMPlayer.getInstance();}private void setListener() {musicLast.setOnClickListener(v -> vmPlayer.playLast());musicNext.setOnClickListener(v -> vmPlayer.playNext());musicPlay.setOnClickListener(v -> vmPlayer.playOrPause());musicMode.setOnClickListener(v -> changPlayMode());musicSeekbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {@Overridepublic void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {}@Overridepublic void onStartTrackingTouch(SeekBar seekBar) {}@Overridepublic void onStopTrackingTouch(SeekBar seekBar) {//拖动进度条控制播放进度vmPlayer.seekTo(seekBar.getProgress());}});}private void changPlayMode() {int curMode = vmPlayer.getCurMode();curMode++;if (curMode > VMPlayer.MODE_RANDOM) {curMode = VMPlayer.MODE_SINGLE;}vmPlayer.setPlayMode(curMode);updateModeImg(curMode);}private void updateModeImg(int curMode) {switch (curMode) {case VMPlayer.MODE_SINGLE:musicMode.setImageResource(R.mipmap.icon_single);break;case VMPlayer.MODE_LOOP:musicMode.setImageResource(R.mipmap.icon_order);break;case VMPlayer.MODE_RANDOM:musicMode.setImageResource(R.mipmap.icon_random);break;}}public void notifyStateChanged(boolean isPlaying) {if (isPlaying) {musicPlay.setImageResource(R.mipmap.icon_playing);} else {musicPlay.setImageResource(R.mipmap.icon_pause);}}public void notifyProcessChanged(int process) {musicSeekbar.setProgress(process);musicCur.setText(ConvertUtil.formatTime(process));}public void notifySongChanged(String name, int duration) {musicName.setText(name);musicSeekbar.setMax(duration);musicAll.setText(ConvertUtil.formatTime(duration));}public void synInfo() {//重新进入时,如果在播放,则需同步一下歌曲信息if (vmPlayer.isPlaying()) {AudioBean bean = vmPlayer.getCurMusic();notifySongChanged(bean.getName() + " - " + bean.getArtist(), bean.getDuration());notifyStateChanged(true);}else{//自动播放vmPlayer.play();}//同步播放模式updateModeImg(vmPlayer.getCurMode());}
}
layout_simple_virgo_player.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="wrap_content"android:gravity="center_vertical"android:padding="15dp"android:orientation="horizontal"android:background="@drawable/bg_card_red"><ImageViewandroid:layout_width="@dimen/player_img_size"android:layout_height="@dimen/player_img_size"android:src="@mipmap/img_cover_default"android:scaleType="centerCrop"/><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="vertical"android:layout_marginStart="10dp"><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:layout_gravity="center_vertical"android:orientation="vertical"><TextViewandroid:id="@+id/music_tv_name"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_gravity="left"android:singleLine="true"android:ellipsize="marquee"android:marqueeRepeatLimit="marquee_forever"android:text="歌名"android:textSize="@dimen/text_size_14"android:textColor="@color/black"android:paddingStart="15dp"/><SeekBarandroid:id="@+id/music_seekbar"android:layout_width="match_parent"android:layout_height="wrap_content"android:progressTint="@color/black"android:thumbTint="@color/color_QYH"android:layout_marginTop="3dp"android:progress="0"/></LinearLayout><RelativeLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"><LinearLayoutandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerVertical="true"android:layout_marginLeft="5dp"android:orientation="horizontal"><TextViewandroid:id="@+id/music_cur"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="00"android:textColor="@color/white"android:textSize="@dimen/text_size_12"/><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="/"android:textColor="@color/white"android:textSize="@dimen/text_size_12"/><TextViewandroid:id="@+id/music_all"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="00"android:textColor="@color/white"android:textSize="@dimen/text_size_12"/></LinearLayout><LinearLayoutandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerInParent="true"android:gravity="center"android:orientation="horizontal"><ImageButtonandroid:id="@+id/music_btn_last"android:layout_width="@dimen/player_icon_size_small"android:layout_height="@dimen/player_icon_size_small"android:layout_margin="@dimen/player_icon_margin"android:background="@drawable/img_button_transprant"android:src="@mipmap/icon_last"android:scaleType="centerCrop"/><ImageButtonandroid:id="@+id/music_btn_paly"android:layout_width="@dimen/player_icon_size_big"android:layout_height="@dimen/player_icon_size_big"android:background="@drawable/img_button_transprant"android:src="@mipmap/icon_pause"android:scaleType="centerCrop"/><ImageButtonandroid:id="@+id/music_btn_next"android:layout_width="@dimen/player_icon_size_small"android:layout_height="@dimen/player_icon_size_small"android:layout_margin="@dimen/player_icon_margin"android:background="@drawable/img_button_transprant"android:src="@mipmap/icon_next"android:scaleType="centerCrop"/></LinearLayout><LinearLayoutandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerVertical="true"android:layout_alignParentEnd="true"><ImageButtonandroid:id="@+id/music_btn_mode"android:layout_width="@dimen/player_icon_size_small"android:layout_height="@dimen/player_icon_size_small"android:layout_margin="@dimen/player_icon_margin"android:background="@drawable/img_button_transprant"android:src="@mipmap/icon_order"android:scaleType="centerCrop"/></LinearLayout></RelativeLayout></LinearLayout>
</LinearLayout>
<!--============dimens.xml=============-->
<dimen name="player_icon_size_big">42dp</dimen>
<dimen name="player_icon_size_small">30dp</dimen>
<dimen name="player_icon_margin">10dp</dimen>
<dimen name="player_layout_padding">10dp</dimen>
<dimen name="player_img_size">85dp</dimen><!--text size sp-->
<dimen name="text_size_10">10sp</dimen>
<dimen name="text_size_12">12sp</dimen>
<dimen name="text_size_14">14sp</dimen>
<dimen name="text_size_16">16sp</dimen>
<dimen name="text_size_18">18sp</dimen>
<dimen name="text_size_20">20sp</dimen>
<dimen name="text_size_22">22sp</dimen>
<dimen name="text_size_24">24sp</dimen>
<dimen name="text_size_32">32sp</dimen>
<dimen name="text_size_50">50sp</dimen><dimen name="padding_1">1dp</dimen>
<dimen name="padding_2">2dp</dimen>
<dimen name="padding_3">3dp</dimen>
<dimen name="padding_5">5dp</dimen>
<dimen name="padding_10">10dp</dimen>
<dimen name="padding_15">15dp</dimen><dimen name="margin_1">1dp</dimen>
<dimen name="margin_3">3dp</dimen>
<dimen name="margin_5">5dp</dimen>
<dimen name="margin_10">10dp</dimen>
<dimen name="margin_15">15dp</dimen>
ListView_LocalSong_Adapter.java(自定义列表适配器)

在这里插入图片描述

import android.content.Context;
import android.graphics.Color;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.TextView;import com.nepalese.harinetest.R;import java.util.List;/*** @author nepalese on 2024/11/24* @usage*/
public class ListView_LocalSong_Adapter extends BaseAdapter {private Context context;private LayoutInflater inflater;private List<AudioBean> data;private interListenerSongList listener;public ListView_LocalSong_Adapter(Context context, interListenerSongList listener, List<AudioBean> list) {this.context = context;this.inflater = LayoutInflater.from(context);this.listener = listener;this.data = list;}@Overridepublic int getCount() {return data == null ? 0 : data.size();}@Overridepublic Object getItem(int position) {return null;}@Overridepublic long getItemId(int position) {return position;}static class Holder {private TextView tvOrder, tvName, tvArtist;private LinearLayout root;private ImageButton ibList;}@Overridepublic View getView(int position, View convertView, ViewGroup parent) {Holder holder;if (convertView == null) {holder = new Holder();convertView = inflater.inflate(R.layout.layout_song_list_local, null);holder.root = convertView.findViewById(R.id.layout_list_root);holder.tvOrder = convertView.findViewById(R.id.tv_order);holder.tvName = convertView.findViewById(R.id.tvLocalName);holder.tvArtist = convertView.findViewById(R.id.tvLocalArtist);holder.ibList = convertView.findViewById(R.id.ibLocalSongList);convertView.setTag(holder);} else {holder = (Holder) convertView.getTag();}holder.tvOrder.setText(String.valueOf(position + 1));holder.tvName.setText(data.get(position).getName());holder.tvArtist.setText(data.get(position).getArtist());if (position % 2 == 0) {holder.root.setBackgroundColor(Color.parseColor("#4D03A9F4"));}else{holder.root.setBackgroundColor(Color.TRANSPARENT);}if (VMPlayer.getInstance().getCurState() >= VMPlayer.STATE_PREPARED && VMPlayer.getInstance().getCurMusic().getDisplayName().equals(data.get(position).getDisplayName())) {holder.tvName.setTextColor(Color.RED);} else {holder.tvName.setTextColor(Color.BLACK);}//内部项点击监听
//        holder.ibList.setOnClickListener(v -> {
//            listener.onItemClick(v);
//        });holder.ibList.setTag(position);return convertView;}public interface interListenerSongList {void onItemClick(View view);}
}
layout_song_list_local.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:id="@+id/layout_list_root"android:layout_width="match_parent"android:layout_height="wrap_content"android:padding="@dimen/padding_5"android:gravity="center_vertical"android:descendantFocusability="blocksDescendants"android:orientation="horizontal"><TextViewandroid:id="@+id/tv_order"android:layout_margin="@dimen/margin_5"android:layout_width="35dp"android:layout_height="wrap_content"android:gravity="center"android:textSize="@dimen/text_size_18"android:textColor="@color/black" /><LinearLayoutandroid:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:orientation="vertical"><TextViewandroid:id="@+id/tvLocalName"android:layout_width="match_parent"android:layout_height="wrap_content"android:singleLine="true"android:ellipsize="end"android:layout_marginBottom="@dimen/margin_3"android:textColor="@color/black"android:textSize="@dimen/text_size_18"/><TextViewandroid:id="@+id/tvLocalArtist"android:layout_width="match_parent"android:layout_height="wrap_content"android:singleLine="true"android:ellipsize="end"android:textColor="@color/gray"android:textSize="@dimen/text_size_14"/></LinearLayout><ImageButtonandroid:id="@+id/ibLocalSongList"android:layout_width="@dimen/icon_30"android:layout_height="@dimen/icon_30"android:src="@mipmap/icon_list_gray"android:scaleType="fitCenter"android:padding="@dimen/padding_2"android:focusable="false"android:background="@drawable/selector_button_transparent"/>
</LinearLayout>
前端使用
public class AudioPlayActivity extends AppCompatActivity implements ListView_LocalSong_Adapter.interListenerSongList, iPlayBack {private static final String TAG = "AudioPlayActivity";private Context context;private VirgoSimplePlayer simplePlayer;private ListView listView;private ListView_LocalSong_Adapter adapter;private final List<AudioBean> audioList = new ArrayList<>();@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_audio_play);context = getApplicationContext();init();}private void init() {initUI();initData();setListener();}private void initUI() {simplePlayer = findViewById(R.id.simplePlayer);listView = findViewById(R.id.listviewAudio);}private void initData() {VMPlayer.getInstance().registerCallback(this);simplePlayer.synInfo();audioList.addAll(VMPlayer.getInstance().getBeanList());adapter = new ListView_LocalSong_Adapter(context, this, audioList);listView.setAdapter(adapter);}private void setListener() {listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {@Overridepublic void onItemClick(AdapterView<?> parent, View view, int position, long id) {VMPlayer.getInstance().play(position);}});}@Overrideprotected void onDestroy() {release();super.onDestroy();}private void release() {VMPlayer.getInstance().unregisterCallback(this);}@Overridepublic void onItemClick(View view) {//}@Overridepublic void onListChange() {audioList.clear();audioList.addAll(VMPlayer.getInstance().getBeanList());updateListView();}//@Overridepublic void onChangeSong(@NonNull AudioBean bean) {if (simplePlayer != null) {simplePlayer.notifySongChanged(bean.getName() + " - " + bean.getArtist(), bean.getDuration());}//刷新列表updateListView();}@Overridepublic void onPlayCompleted() {//自动播放下一首VMPlayer.getInstance().playNext();}@Overridepublic void onPlayStateChanged(boolean isPlaying) {if (simplePlayer != null) {simplePlayer.notifyStateChanged(isPlaying);}}@Overridepublic void onProcessChanged(int process) {if (simplePlayer != null) {simplePlayer.notifyProcessChanged(process);}}@Overridepublic void onPlayError(int state, String error) {
//        Toast.makeText(context, error, Toast.LENGTH_LONG).show();Log.d(TAG, "onPlayError: " + error);}private final int MSG_UPDATE_LIST = 1;private void updateListView(){handler.sendEmptyMessage(MSG_UPDATE_LIST);}private final Handler handler = new Handler(new Handler.Callback() {@Overridepublic boolean handleMessage(@NonNull Message msg) {if(msg.what == MSG_UPDATE_LIST){if (adapter != null) {adapter.notifyDataSetChanged();}}return false;}});
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"android:orientation="vertical"tools:context=".AudioPlayActivity"><ListViewandroid:id="@+id/listviewAudio"android:layout_width="match_parent"android:layout_height="0dp"android:layout_weight="1"android:divider="@color/white"android:dividerHeight="0dp" /><com.nepalese.harinetest.musicplayer.VirgoSimplePlayerandroid:id="@+id/simplePlayer"android:layout_width="match_parent"android:layout_height="wrap_content"/></LinearLayout>

相关文章:

Android -- 简易音乐播放器

Android – 简易音乐播放器 播放器功能&#xff1a;* 1. 播放模式&#xff1a;单曲、列表循环、列表随机&#xff1b;* 2. 后台播放&#xff08;单例模式&#xff09;&#xff1b;* 3. 多位置同步状态回调&#xff1b;处理模块&#xff1a;* 1. 提取文件信息&#xff1a;音频文…...

【开源免费】基于Vue和SpringBoot的技术交流分享平台(附论文)

博主说明&#xff1a;本文项目编号 T 053 &#xff0c;文末自助获取源码 \color{red}{T053&#xff0c;文末自助获取源码} T053&#xff0c;文末自助获取源码 目录 一、系统介绍二、演示录屏三、启动教程四、功能截图五、文案资料5.1 选题背景5.2 国内外研究现状5.3 可行性分析…...

Python异步编程新写法:asyncio模块的最新实践

Python异步编程新写法&#xff1a;asyncio模块的最新实践 引言1. 异步编程基础2. 旧写法的问题3. 最新的写法4. 代码解析5. 最佳实践6. 总结7. 参考资料 引言 在现代编程中&#xff0c;异步编程已经成为提高程序性能和响应能力的重要手段。Python的asyncio模块为开发者提供了一…...

【Docker】Docker配置远程访问

配置Docker的远程访问&#xff0c;你需要按照以下步骤进行操作&#xff1a; 1. 在Docker宿主机上配置Docker守护进程监听TCP端口 Docker守护进程默认只监听UNIX套接字&#xff0c;要实现远程访问&#xff0c;需要修改配置以监听TCP端口。 ‌方法一&#xff1a;修改Docker服务…...

网络安全入门之网络安全工具分享-含初期所有工具(附百度网盘链接)

网络安全基础工具 抓包工具 burpsuite 这是一款十分经典的抓包改包工具&#xff0c;在全球范围内使用十分广泛&#xff0c;并且其内置各种插件&#xff0c;具有爆破&#xff0c;自动识别验证码&#xff0c;加解密发包等多种功能 专业版破解网盘链接&#xff1a; 通过百度网…...

玩转 uni-app 静态资源 static 目录的条件编译

一. 前言 老生常谈&#xff0c;了解 uni-app 的开发都知道&#xff0c;uni-app 可以同时支持编译到多个平台&#xff0c;如小程序、H5、移动端 App 等。它的多端编译能力是 uni-app 的一大特点&#xff0c;让开发者可以使用同一套代码基于 Vue.js 的语法编写程序&#xff0c;然…...

Docker 容器隔离关键技术:Seccomp

Docker 容器隔离关键技术&#xff1a;Seccomp 在 Docker 容器中&#xff0c;Seccomp&#xff08;Secure Computing Mode&#xff09; 是一种内核安全机制&#xff0c;用来限制容器内的程序可以调用哪些系统调用&#xff08;Syscalls&#xff09;。通过列清单的方式&#xff0c…...

【大模型】深度解析 NLP 模型5大评估指标及 应用案例:从 BLEU、ROUGE、PPL 到METEOR、BERTScore

在自然语言处理&#xff08;NLP&#xff09;领域&#xff0c;无论是机器翻译、文本生成&#xff0c;还是问答系统开发&#xff0c;模型性能评估指标始终是开发者绕不开的工具。BLEU、ROUGE、PPL&#xff08;困惑度&#xff09;、METEOR 和 BERTScore 是五个最具代表性的指标&am…...

LinuxC高级

gdb调试工具 gdb调试的作用 gdb用于调试代码中逻辑错误&#xff0c;而非语法错误 gdb调试流程 生成可以使用gdb调试的执行文件 gcc -g xxx.c ---> 生成的文件可以使用gdb调试 进入gdb工具 gdb 可执行文件 ---> 使用gdb工具开始调试可执行文件 r/run&#xff1a;运行代码 …...

实现PDF文档加密,访问需要密码

01. 背景 今天下午老板神秘兮兮的来问我&#xff0c;能不能做个文档加密功能&#xff0c;就是那种用户下载打开需要密码才能打开的那种效果。boss都发话了&#xff0c;那必须可以。 需求&#xff1a;将pdf文档经过加密处理&#xff0c;客户下载pdf文档&#xff0c;打开文档需要…...

LangChain——加载知识库文本文档 PDF文档

文档加载 这涵盖了如何加载目录中的所有文档。 在底层&#xff0c;默认情况下使用 UnstructedLoader。需要安装依赖 pip install unstructuredpython导入方式 from langchain_community.document_loaders import DirectoryLoader我们可以使用 glob 参数来控制加载特定类型文…...

深度学习2:从零开始掌握PyTorch:数据操作不再是难题

文章目录 一、导读二、张量的定义与基本操作三、广播机制四、索引与切片五、内存管理六、与其他Python对象的转换本文是经过严格查阅相关权威文献和资料,形成的专业的可靠的内容。全文数据都有据可依,可回溯。特别申明:数据和资料已获得授权。本文内容,不涉及任何偏颇观点,…...

MyBatis的if标签的基本使用

在MyBatis框架中&#xff0c;if标签用于在构建SQL语句时&#xff0c;根据参数条件判断的结果&#xff0c;动态地选择加入或不加where条件中。 一 常见使用 在使用MyBatis处理查询逻辑的时候&#xff0c;常用的是判断一些参数是否为空&#xff0c;列举常用的几种情况展示 1.1…...

【Azure Cache for Redis】Redis的导出页面无法配置Storage SAS时通过az cli来完成

问题描述 在Azure Redis的导出页面&#xff0c;突然不能配置Storage Account的SAS作为授权方式。 image.png 那么是否可以通过AZ CLI或者是Powershell来实现SAS的配置呢&#xff1f; 问题解答 可以的。使用 az redis export 可以实现 az redis export --container --prefix[--a…...

【微服务】Nacos

一、安装 1、官网地址&#xff1a;https://nacos.io/download/nacos-server/ 2、启动&#xff1a;找到bin目录下的startup.cmd双击启动&#xff0c;或者打开一个命令窗口输入&#xff1a; startup.cmd -m standalone双击启动后如下&#xff1a;可以访问控制台地址 访问后的…...

5、定义与调用函数

大家好,欢迎来到Python函数入门课程! 在编程中,函数就像一个可以重复使用的代码块,它接受输入(参数),执行特定的任务,并可能返回一个结果。想象一下,函数就像一个厨房里的搅拌机,你放入水果(参数),按下按钮(调用函数),它就会帮你制作出美味的果汁(返回值)。…...

Linux 网络编程之TCP套接字

前言 上一期我们对UDP套接字进行了介绍并实现了简单的UDP网络程序&#xff0c;本期我们来介绍TCP套接字&#xff0c;以及实现简单的TCP网络程序&#xff01; &#x1f389;目录 前言 1、TCP 套接字API详解 1.1 socket 1.2 bind 1.3 listen 1.4 accept 1.5 connect 2、…...

前海湾地铁的腾通数码大厦背后的临时免费停车点探寻

临时免费停车点&#xff1a;前海湾地铁的腾通数码大厦背后的桂湾大街&#xff0c;目前看不仅整条桂湾大街停了​车&#xff0c;而且还有工地餐点。可能是这个区域还是半工地状态&#xff0c;故暂时还不会有​罚单的情况出现。 中建三局腾讯数码大厦项目部A栋 广东省深圳市南山…...

OpenCV相机标定与3D重建(7)鱼眼镜头立体校正的函数stereoRectify()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 cv::fisheye::stereoRectify 是 OpenCV 中用于鱼眼镜头立体校正的函数。该函数计算两个相机之间的校正变换&#xff0c;使得从两个相机拍摄的图像…...

前端如何获取unpkg的资源链接

在现代前端开发中&#xff0c;快速获取和使用npm包是一个常见需求。unpkg是一个全球性的CDN服务&#xff0c;它为npm上的每个包提供了快速访问。通过unpkg&#xff0c;你可以轻松地通过URL获取任何npm包的文件。本文将介绍如何获取unpkg的资源链接。 unpkg简介 unpkg是一个快…...

7.4.分块查找

一.分块查找的算法思想&#xff1a; 1.实例&#xff1a; 以上述图片的顺序表为例&#xff0c; 该顺序表的数据元素从整体来看是乱序的&#xff0c;但如果把这些数据元素分成一块一块的小区间&#xff0c; 第一个区间[0,1]索引上的数据元素都是小于等于10的&#xff0c; 第二…...

树莓派超全系列教程文档--(62)使用rpicam-app通过网络流式传输视频

使用rpicam-app通过网络流式传输视频 使用 rpicam-app 通过网络流式传输视频UDPTCPRTSPlibavGStreamerRTPlibcamerasrc GStreamer 元素 文章来源&#xff1a; http://raspberry.dns8844.cn/documentation 原文网址 使用 rpicam-app 通过网络流式传输视频 本节介绍来自 rpica…...

通过Wrangler CLI在worker中创建数据库和表

官方使用文档&#xff1a;Getting started Cloudflare D1 docs 创建数据库 在命令行中执行完成之后&#xff0c;会在本地和远程创建数据库&#xff1a; npx wranglerlatest d1 create prod-d1-tutorial 在cf中就可以看到数据库&#xff1a; 现在&#xff0c;您的Cloudfla…...

Qt Widget类解析与代码注释

#include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this); }Widget::~Widget() {delete ui; }//解释这串代码&#xff0c;写上注释 当然可以&#xff01;这段代码是 Qt …...

跨链模式:多链互操作架构与性能扩展方案

跨链模式&#xff1a;多链互操作架构与性能扩展方案 ——构建下一代区块链互联网的技术基石 一、跨链架构的核心范式演进 1. 分层协议栈&#xff1a;模块化解耦设计 现代跨链系统采用分层协议栈实现灵活扩展&#xff08;H2Cross架构&#xff09;&#xff1a; 适配层&#xf…...

CMake 从 GitHub 下载第三方库并使用

有时我们希望直接使用 GitHub 上的开源库,而不想手动下载、编译和安装。 可以利用 CMake 提供的 FetchContent 模块来实现自动下载、构建和链接第三方库。 FetchContent 命令官方文档✅ 示例代码 我们将以 fmt 这个流行的格式化库为例,演示如何: 使用 FetchContent 从 GitH…...

SpringTask-03.入门案例

一.入门案例 启动类&#xff1a; package com.sky;import lombok.extern.slf4j.Slf4j; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cache.annotation.EnableCach…...

【JavaWeb】Docker项目部署

引言 之前学习了Linux操作系统的常见命令&#xff0c;在Linux上安装软件&#xff0c;以及如何在Linux上部署一个单体项目&#xff0c;大多数同学都会有相同的感受&#xff0c;那就是麻烦。 核心体现在三点&#xff1a; 命令太多了&#xff0c;记不住 软件安装包名字复杂&…...

Java面试专项一-准备篇

一、企业简历筛选规则 一般企业的简历筛选流程&#xff1a;首先由HR先筛选一部分简历后&#xff0c;在将简历给到对应的项目负责人后再进行下一步的操作。 HR如何筛选简历 例如&#xff1a;Boss直聘&#xff08;招聘方平台&#xff09; 直接按照条件进行筛选 例如&#xff1a…...

A2A JS SDK 完整教程:快速入门指南

目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库&#xff…...