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 – 简易音乐播放器 播放器功能:* 1. 播放模式:单曲、列表循环、列表随机;* 2. 后台播放(单例模式);* 3. 多位置同步状态回调;处理模块:* 1. 提取文件信息:音频文…...

【开源免费】基于Vue和SpringBoot的技术交流分享平台(附论文)
博主说明:本文项目编号 T 053 ,文末自助获取源码 \color{red}{T053,文末自助获取源码} T053,文末自助获取源码 目录 一、系统介绍二、演示录屏三、启动教程四、功能截图五、文案资料5.1 选题背景5.2 国内外研究现状5.3 可行性分析…...
Python异步编程新写法:asyncio模块的最新实践
Python异步编程新写法:asyncio模块的最新实践 引言1. 异步编程基础2. 旧写法的问题3. 最新的写法4. 代码解析5. 最佳实践6. 总结7. 参考资料 引言 在现代编程中,异步编程已经成为提高程序性能和响应能力的重要手段。Python的asyncio模块为开发者提供了一…...

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

网络安全入门之网络安全工具分享-含初期所有工具(附百度网盘链接)
网络安全基础工具 抓包工具 burpsuite 这是一款十分经典的抓包改包工具,在全球范围内使用十分广泛,并且其内置各种插件,具有爆破,自动识别验证码,加解密发包等多种功能 专业版破解网盘链接: 通过百度网…...

玩转 uni-app 静态资源 static 目录的条件编译
一. 前言 老生常谈,了解 uni-app 的开发都知道,uni-app 可以同时支持编译到多个平台,如小程序、H5、移动端 App 等。它的多端编译能力是 uni-app 的一大特点,让开发者可以使用同一套代码基于 Vue.js 的语法编写程序,然…...
Docker 容器隔离关键技术:Seccomp
Docker 容器隔离关键技术:Seccomp 在 Docker 容器中,Seccomp(Secure Computing Mode) 是一种内核安全机制,用来限制容器内的程序可以调用哪些系统调用(Syscalls)。通过列清单的方式,…...

【大模型】深度解析 NLP 模型5大评估指标及 应用案例:从 BLEU、ROUGE、PPL 到METEOR、BERTScore
在自然语言处理(NLP)领域,无论是机器翻译、文本生成,还是问答系统开发,模型性能评估指标始终是开发者绕不开的工具。BLEU、ROUGE、PPL(困惑度)、METEOR 和 BERTScore 是五个最具代表性的指标&am…...

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

实现PDF文档加密,访问需要密码
01. 背景 今天下午老板神秘兮兮的来问我,能不能做个文档加密功能,就是那种用户下载打开需要密码才能打开的那种效果。boss都发话了,那必须可以。 需求:将pdf文档经过加密处理,客户下载pdf文档,打开文档需要…...
LangChain——加载知识库文本文档 PDF文档
文档加载 这涵盖了如何加载目录中的所有文档。 在底层,默认情况下使用 UnstructedLoader。需要安装依赖 pip install unstructuredpython导入方式 from langchain_community.document_loaders import DirectoryLoader我们可以使用 glob 参数来控制加载特定类型文…...

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

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

【Azure Cache for Redis】Redis的导出页面无法配置Storage SAS时通过az cli来完成
问题描述 在Azure Redis的导出页面,突然不能配置Storage Account的SAS作为授权方式。 image.png 那么是否可以通过AZ CLI或者是Powershell来实现SAS的配置呢? 问题解答 可以的。使用 az redis export 可以实现 az redis export --container --prefix[--a…...

【微服务】Nacos
一、安装 1、官网地址:https://nacos.io/download/nacos-server/ 2、启动:找到bin目录下的startup.cmd双击启动,或者打开一个命令窗口输入: startup.cmd -m standalone双击启动后如下:可以访问控制台地址 访问后的…...
5、定义与调用函数
大家好,欢迎来到Python函数入门课程! 在编程中,函数就像一个可以重复使用的代码块,它接受输入(参数),执行特定的任务,并可能返回一个结果。想象一下,函数就像一个厨房里的搅拌机,你放入水果(参数),按下按钮(调用函数),它就会帮你制作出美味的果汁(返回值)。…...

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

前海湾地铁的腾通数码大厦背后的临时免费停车点探寻
临时免费停车点:前海湾地铁的腾通数码大厦背后的桂湾大街,目前看不仅整条桂湾大街停了车,而且还有工地餐点。可能是这个区域还是半工地状态,故暂时还不会有罚单的情况出现。 中建三局腾讯数码大厦项目部A栋 广东省深圳市南山…...
OpenCV相机标定与3D重建(7)鱼眼镜头立体校正的函数stereoRectify()的使用
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 cv::fisheye::stereoRectify 是 OpenCV 中用于鱼眼镜头立体校正的函数。该函数计算两个相机之间的校正变换,使得从两个相机拍摄的图像…...
前端如何获取unpkg的资源链接
在现代前端开发中,快速获取和使用npm包是一个常见需求。unpkg是一个全球性的CDN服务,它为npm上的每个包提供了快速访问。通过unpkg,你可以轻松地通过URL获取任何npm包的文件。本文将介绍如何获取unpkg的资源链接。 unpkg简介 unpkg是一个快…...

python打卡day49
知识点回顾: 通道注意力模块复习空间注意力模块CBAM的定义 作业:尝试对今天的模型检查参数数目,并用tensorboard查看训练过程 import torch import torch.nn as nn# 定义通道注意力 class ChannelAttention(nn.Module):def __init__(self,…...
Leetcode 3576. Transform Array to All Equal Elements
Leetcode 3576. Transform Array to All Equal Elements 1. 解题思路2. 代码实现 题目链接:3576. Transform Array to All Equal Elements 1. 解题思路 这一题思路上就是分别考察一下是否能将其转化为全1或者全-1数组即可。 至于每一种情况是否可以达到…...
DeepSeek 赋能智慧能源:微电网优化调度的智能革新路径
目录 一、智慧能源微电网优化调度概述1.1 智慧能源微电网概念1.2 优化调度的重要性1.3 目前面临的挑战 二、DeepSeek 技术探秘2.1 DeepSeek 技术原理2.2 DeepSeek 独特优势2.3 DeepSeek 在 AI 领域地位 三、DeepSeek 在微电网优化调度中的应用剖析3.1 数据处理与分析3.2 预测与…...

工业安全零事故的智能守护者:一体化AI智能安防平台
前言: 通过AI视觉技术,为船厂提供全面的安全监控解决方案,涵盖交通违规检测、起重机轨道安全、非法入侵检测、盗窃防范、安全规范执行监控等多个方面,能够实现对应负责人反馈机制,并最终实现数据的统计报表。提升船厂…...

8k长序列建模,蛋白质语言模型Prot42仅利用目标蛋白序列即可生成高亲和力结合剂
蛋白质结合剂(如抗体、抑制肽)在疾病诊断、成像分析及靶向药物递送等关键场景中发挥着不可替代的作用。传统上,高特异性蛋白质结合剂的开发高度依赖噬菌体展示、定向进化等实验技术,但这类方法普遍面临资源消耗巨大、研发周期冗长…...

CMake基础:构建流程详解
目录 1.CMake构建过程的基本流程 2.CMake构建的具体步骤 2.1.创建构建目录 2.2.使用 CMake 生成构建文件 2.3.编译和构建 2.4.清理构建文件 2.5.重新配置和构建 3.跨平台构建示例 4.工具链与交叉编译 5.CMake构建后的项目结构解析 5.1.CMake构建后的目录结构 5.2.构…...

【网络安全产品大调研系列】2. 体验漏洞扫描
前言 2023 年漏洞扫描服务市场规模预计为 3.06(十亿美元)。漏洞扫描服务市场行业预计将从 2024 年的 3.48(十亿美元)增长到 2032 年的 9.54(十亿美元)。预测期内漏洞扫描服务市场 CAGR(增长率&…...
在 Nginx Stream 层“改写”MQTT ngx_stream_mqtt_filter_module
1、为什么要修改 CONNECT 报文? 多租户隔离:自动为接入设备追加租户前缀,后端按 ClientID 拆分队列。零代码鉴权:将入站用户名替换为 OAuth Access-Token,后端 Broker 统一校验。灰度发布:根据 IP/地理位写…...

苍穹外卖--缓存菜品
1.问题说明 用户端小程序展示的菜品数据都是通过查询数据库获得,如果用户端访问量比较大,数据库访问压力随之增大 2.实现思路 通过Redis来缓存菜品数据,减少数据库查询操作。 缓存逻辑分析: ①每个分类下的菜品保持一份缓存数据…...

C# 求圆面积的程序(Program to find area of a circle)
给定半径r,求圆的面积。圆的面积应精确到小数点后5位。 例子: 输入:r 5 输出:78.53982 解释:由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982,因为我们只保留小数点后 5 位数字。 输…...