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

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
        //filename
        String displayName = path.substring(path.lastIndexOf("/") + 1);//歌手 - 歌名.mp3
        String 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;
    }

    @Override
    public 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);//记忆播放模式 默认列表循环 1
        curIndex = ShareDao.getAudioIndex(context);//记忆播放位置 0
        errTime = 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;
    }

    @Override
    public 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();
        }
    }

    /**
     * 播放|继续播放
     */
    @Override
    public 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
     */
    @Override
    public void play(int index) {
        if (isValid()) {
            curIndex = index;
            prepareAndPlay();
        } else {
            notifyError("未设置播放列表!");
        }
    }

    /**
     * 临时播放某个音频文件
     *
     * @param bean AudioBean
     */
    @Override
    public void play(AudioBean bean) {
        if (bean == null) {
            notifyError("指定歌曲为空!");
            return;
        }

        curState = STATE_PREPARED;
        playResource(bean);
    }

    /**
     * 更换播放列表
     *
     * @param list  新列表
     * @param index 开始位置,默认:0
     */
    @Override
    public void play(List<AudioBean> list, int index) {
        if (list == null || list.isEmpty()) {
            notifyError("新列表为空!");
            return;
        }

        curIndex = index;
        setPlayList(list);
        prepareAndPlay();
    }

    /**
     * 上一首
     */
    @Override
    public 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("未设置播放列表!");
        }
    }

    /**
     * 下一首
     */
    @Override
    public 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("未设置播放列表!");
        }
    }

    /**
     * 暂停播放
     */
    @Override
    public void pause() {
        if (isPlaying()) {
            curState = STATE_PAUSE;
            mediaPlayer.pause();
            notifyStateChanged(false);
        }
    }

    /**
     * 跳转播放进度
     *
     * @param progress p
     */
    @Override
    public void seekTo(int progress) {
        if (isValid()) {
            if (curState > STATE_PREPARED) {
                aimSeek = 0;
                mediaPlayer.seekTo(progress);
            } else {
                aimSeek = progress;
            }
        }
    }

    /**
     * 设置播放列表
     *
     * @param beans b
     */
    @Override
    public 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
     */
    @Override
    public void setPlayMode(int mode) {
        if (mode == this.curMode) {
            return;
        }
        this.curMode = mode;
        ShareDao.setAudioMode(context, mode);
        Log.d(TAG, "setPlayMode: " + curMode);
    }

    /**
     * 是否正在播放
     */
    @Override
    public boolean isPlaying() {
        return isValid() && mediaPlayer.isPlaying();
    }

    /**
     * 当前播放进度
     */
    @Override
    public int getCurProgress() {
        return mediaPlayer.getCurrentPosition();
    }

    /**
     * 当前播放器状态
     */
    @Override
    public int getCurState() {
        return curState;
    }

    @Override
    public int getCurMode() {
        return curMode;
    }

    /**
     * 获取当前播放音频信息
     * 可空
     */
    @Override
    public AudioBean getCurMusic() {
        if (isValid()) {
            return curBean;
        }
        return null;
    }

    /**
     * 注销播放器
     */
    @Override
    public 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;
    }

    /**
     * 注册播放器回调
     */
    @Override
    public void registerCallback(iPlayBack callback) {
        iPlayBacks.add(callback);
    }

    /**
     * 注销播放器回调
     */
    @Override
    public void unregisterCallback(iPlayBack callback) {
        iPlayBacks.remove(callback);
    }

    @Override
    public 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() {
        @Override
        public 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() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {

            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {

            }

            @Override
            public 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">

    <ImageView
        android:layout_width="@dimen/player_img_size"
        android:layout_height="@dimen/player_img_size"
        android:src="@mipmap/img_cover_default"
        android:scaleType="centerCrop"/>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:layout_marginStart="10dp">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:orientation="vertical">

            <TextView
                android: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"/>

            <SeekBar
                android: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>

        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

            <LinearLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerVertical="true"
                android:layout_marginLeft="5dp"
                android:orientation="horizontal">

                <TextView
                    android: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"/>

                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="/"
                    android:textColor="@color/white"
                    android:textSize="@dimen/text_size_12"/>

                <TextView
                    android: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>

            <LinearLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerInParent="true"
                android:gravity="center"
                android:orientation="horizontal">

                <ImageButton
                    android: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"/>

                <ImageButton
                    android: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"/>

                <ImageButton
                    android: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>

            <LinearLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerVertical="true"
                android:layout_alignParentEnd="true">

                <ImageButton
                    android: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;
    }

    @Override
    public int getCount() {
        return data == null ? 0 : data.size();
    }

    @Override
    public Object getItem(int position) {
        return null;
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    static class Holder {
        private TextView tvOrder, tvName, tvArtist;
        private LinearLayout root;
        private ImageButton ibList;
    }

    @Override
    public 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">

    <TextView
        android: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" />

    <LinearLayout
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:orientation="vertical">

        <TextView
            android: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"/>

        <TextView
            android: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>

    <ImageButton
        android: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<>();

    @Override
    protected 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() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                VMPlayer.getInstance().play(position);
            }
        });
    }

    @Override
    protected void onDestroy() {
        release();
        super.onDestroy();
    }

    private void release() {
        VMPlayer.getInstance().unregisterCallback(this);
    }

    @Override
    public void onItemClick(View view) {
        //
    }

    @Override
    public void onListChange() {
        audioList.clear();
        audioList.addAll(VMPlayer.getInstance().getBeanList());
        updateListView();
    }

    //
    @Override
    public void onChangeSong(@NonNull AudioBean bean) {
        if (simplePlayer != null) {
            simplePlayer.notifySongChanged(bean.getName() + " - " + bean.getArtist(), bean.getDuration());
        }//刷新列表
        updateListView();
    }

    @Override
    public void onPlayCompleted() {
        //自动播放下一首
        VMPlayer.getInstance().playNext();
    }

    @Override
    public void onPlayStateChanged(boolean isPlaying) {
        if (simplePlayer != null) {
            simplePlayer.notifyStateChanged(isPlaying);
        }
    }

    @Override
    public void onProcessChanged(int process) {
        if (simplePlayer != null) {
            simplePlayer.notifyProcessChanged(process);
        }
    }

    @Override
    public 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() {
        @Override
        public 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">

    <ListView
        android: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.VirgoSimplePlayer
        android:id="@+id/simplePlayer"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

</LinearLayout>

http://www.kler.cn/a/416590.html

相关文章:

  • conan2包管理菜鸟入门之------------------交叉编译和静态编译
  • 基于智能物联网关的车辆超重AI检测应用
  • 如何解决服务器扫描出的ASP木马问题
  • 智能探针技术:实现可视、可知、可诊的主动网络运维策略
  • js 16进制加密
  • ArcGIS 软件中路网数据的制作
  • 带外配置IP
  • 深拷贝的实现
  • PKO-LSSVM-Adaboost班翠鸟优化最小二乘支持向量机结合AdaBoost分类模型
  • webrtc视频会议学习(三)
  • redis的数据删除策略
  • Java线程池种类及具体应用场景
  • NeurIPS'24 | FlowDCN:基于可变形卷积的任意分辨率图像生成模型
  • 测绘坐标数据封装处理
  • jquery-picture-cut 任意文件上传(CVE-2018-9208)
  • 宇信科技JAVA笔试(2024-11-26日 全部AK)
  • 【算法day3】链表:增删改查及其应用
  • MySQL数据库表的操作
  • MySQL更新JSON字段key:value形式
  • Flink解决延迟数据问题
  • PostgreSQL 中Identity Columns生成一个唯一的标识符
  • Grafana插件安装并接入zabbix数据源
  • 速盾高防cdn支持移动端独立缓存
  • 基于 LlamaFactory 的 LoRA 微调模型支持 vllm 批量推理的实现
  • Go语言技巧:快速统一字符串中的换行符,解决跨平台问题
  • T507 buildroot linux4.9之RTC8563开发调试