Android -- 简易音乐播放器

news2024/12/23 18:11:42

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.coloradmin.cn/o/2252472.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基础Web安全|SQL注入

基础Web安全 URI Uniform Resource Identifier&#xff0c;统一资源标识符&#xff0c;用来唯一的标识一个资源。 URL Uniform Resource Locator&#xff0c;统一资源定位器&#xff0c;一种具体的URI&#xff0c;可以标识一个资源&#xff0c;并且指明了如何定位这个资源…

ESG研究报告白皮书与ESG治理报告合集(2020-2023年)

一.资料范围&#xff1a;&#xff08;1&#xff09;ESG白皮书及指南;&#xff08;2&#xff09;ESG研究报告,&#xff08;3&#xff09;ESG治理报告分析&#xff08;4&#xff09;上市公司ESG报告&#xff08;知名企业&#xff09; 二、资料用途&#xff1a;可以分析研究企业E…

WPF指示灯的实现方式

WPF指示灯的实现方式 样式 XAML <Button x:Name"Btn1" Width"25" Height"25" Grid.Row"0" Grid.Column"1" Margin"10 5 5 5 "><Button.Template><ControlTemplate TargetType"Button"…

初识QT第一天

思维导图 利用Qt尝试做出原神登陆界面 import sys from PyQt6.QtGui import QIcon, QPixmap, QMovie from PyQt6.QtWidgets import QApplication, QWidget, QLabel, QPushButton, QLineEdit# 封装原神窗口类 class Genshin(QWidget):# 构造函数def __init__(self):# 初始化父类…

【Linux】线程池设计 + 策略模式

&#x1f308; 个人主页&#xff1a;Zfox_ &#x1f525; 系列专栏&#xff1a;Linux 目录 一&#xff1a;&#x1f525; 线程池 1-1 ⽇志与策略模式1-2 线程池设计1-3 线程安全的单例模式1-3-1 什么是单例模式1-3-2 单例模式的特点1-3-3 饿汉实现⽅式和懒汉实现⽅式1-3-4 饿汉…

vim插件管理器vim-plug替代vim-bundle

文章目录 vim-plug与vim-bundle对比vim-plug安装vim-plug管理安装插件相关文章 vim-plug与vim-bundle对比 vim-plug 和 vim-bundle 都是 Vim 的插件管理器&#xff0c;但它们有一些关键的区别。以下是两者的主要对比&#xff1a; 易用性和简洁性 vim-plug: 易用性: vim-plug …

107.【C语言】数据结构之二叉树求总节点和第K层节点的个数

目录 1.求二叉树总的节点的个数 1.容易想到的方法 代码 缺陷 思考:能否在TreeSize函数内定义静态变量解决size的问题呢? 其他写法 运行结果 2.最好的方法:分而治之 代码 运行结果 2.求二叉树第K层节点的个数 错误代码 运行结果 修正 运行结果 其他写法 1.求二…

【代码随想录day48】【C++复健】739. 每日温度;496.下一个更大元素 I;503.下一个更大元素II

739. 每日温度 一顿操作猛如虎&#xff0c;一看击败5%。一眼顶针&#xff0c;鉴定为在存栈的时候把值和下标一起存了&#xff0c;所以导致了问题。 class Solution { public:vector<int> dailyTemperatures(vector<int>& temperatures) {stack<vector<…

vscode + conda + qt联合开发

安装vscode 安装conda 清华大学开源软件镜像(Anaconda下载)_清华大学镜像-CSDN博客 conda create新建一个环境&#xff0c;激活这个环境&#xff0c;然后安装pyside6 pip install pyside6 -i https://pypi.tuna.tsinghua.edu.cn/simple 安装成功后输入 pip list查看是否安装…

第十一篇 绘图matplotlib.pyplot的使用

文章目录 摘要安装方法入门案例使用plt绘图使用ax绘图plt.figure参数plot参数案例一 绘制红色实心的点状图案例二 绘制红色的破折线图案例三 绘制两条线颜色总结设置标题、轴名称、图例使用plt实现绘图使用ax实现绘图legend()中loc设置刻度plt自定义刻度ax自定义刻度plt.title …

Unity-Particle System属性介绍(一)基本属性

什么是ParticleSystem 粒子系统是Unity中用于模拟大量粒子的行为的组件。每个粒子都有一个生命周期&#xff0c;包括出生、运动、颜色变化、大小变化和死亡等。粒子系统可以用来创建烟雾、火焰、水、雨、雪、尘埃、闪电和其他各种视觉效果。 开始 在项目文件下创建一个Vfx文件…

计算机的错误计算(一百七十二)

摘要 探讨 MATLAB 对于算式 的计算误差。 例1. 在 MATLAB 中计算 的值。 直接贴图吧&#xff1a; 这样&#xff0c;MATLAB 的输出中只有3位正确数字&#xff0c;有效数字的错误率为 (16-3)/16 81.25% . 因为16位的正确输出为 0.2971242332737277e-18&#xff08;ISReals…

Flink四大基石之CheckPoint(检查点) 的使用详解

目录 一、Checkpoint 剖析 State 与 Checkpoint 概念区分 设置 Checkpoint 实战 执行代码所需的服务与遇到的问题 二、重启策略解读 重启策略意义 代码示例与效果展示 三、SavePoint 与 Checkpoint 异同 操作步骤详解 四、总结 在大数据流式处理领域&#xff0c;Ap…

S4 UPA of AA :新资产会计概览

通用并行会计&#xff08;Universal Parallel Accounting&#xff09;可以支持每个独立的分类账与其他模块集成&#xff0c;UPA主要是为了支持平行评估、多货币类型、财务合并、多准则财务报告的复杂业务需求 在ML层面UPA允许根据不同的分类账规则对物料进行评估&#xff0c;并…

Vue3学习宝典

1.ref函数调用的方式生成响应式数据&#xff0c;可以传复杂和简单数据类型 <script setup> // reactive接收一个对象类型的数据 import { reactive } from vue;// ref用函数调用的方式生成响应式数据&#xff0c;可以传复杂和简单数据类型 import { ref } from vue // 简…

Linux——基础命令(2) 文件内容操作

目录 ​编辑 文件内容操作 1.Vim &#xff08;1&#xff09;移动光标 &#xff08;2&#xff09;复制 &#xff08;3&#xff09;剪切 &#xff08;4&#xff09;删除 &#xff08;5&#xff09;粘贴 &#xff08;6&#xff09;替换,撤销,查找 &#xff08;7&#xff…

openwrt利用nftables在校园网环境下开启nat6 (ipv6 nat)

年初写过一篇openwrt在校园网环境下开启ipv6 nat的文章&#xff0c;利用ip6tables控制ipv6的流量。然而从OpenWrt22版本开始&#xff0c;系统内置的防火墙变为nftables&#xff0c;因此配置方法有所改变。本文主要参考了OpenWRT使用nftables实现IPv6 NAT 这篇文章。 友情提示 …

go语言的成神之路-筑基篇-gin框架渲染模板

第一节-gin框架渲染模板 因为电脑打不开了&#xff0c;所以用朋友的电脑来写的&#xff0c;也是体验了一次从零开始用vscode配置环境&#xff0c;忙活了一上午才配置好环境。太难配置了。好了废话不多说开始今天的进修之旅。 今天开始gin框架的正式学习希望大家认真观看并检查…

【软考网工笔记】网络基础理论——网络层

文章目录 中断处理过程数据包组装RIPRSVPipv4RIPv1 & RIPv2HFC 混合光纤同轴电缆&#xff08;Hybrid Fiber Coax&#xff0c;简称HFC&#xff09;BGP (边界网关协议)BGP-4 协议的四种报文ICMP 协议数字语音电子邮件协议MPLS 多协议标记交换ipv6DHCPDNS名称解析过程查询顺序…

linux网络抓包工具

linux网络抓包工具 一、tcpdump1.1 基本用法1.2 龙芯平台实例操作 二、wireshark2.1 主要功能2.2 龙芯平台实例操作 一、tcpdump tcpdump 指令可列出经过指定网络界面的数据包文件头&#xff0c;可以将网络中传送的数据包的 “头” 完全截获下来提供分析。它支持针对网络层、协…