多媒体——音频——利用MediaPlayer播放音频

利用MediaPlayer播放音频

 

若要在App内部自己播音,可使用媒体播放器MediaPlayer,具体的实现步骤如下:


(1)声明音频类型的实体对象;


(2)通过内容解析器查询系统的音频库,把符合条件的音频记录依次添加到音频列表;


(3)找到若干音频文件之后,再利用MediaPlayer来播音;

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

布局:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="5dp">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="点击音频列表开始播放"
        android:textColor="@color/black"
        android:textSize="17sp" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:layout_margin="2dp"
        android:orientation="horizontal">

        <TextView
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="3"
            android:gravity="left|center"
            android:text="音频名称"
            android:textColor="@color/black"
            android:textSize="15sp" />

        <TextView
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:gravity="right|center"
            android:text="总时长"
            android:textColor="@color/black"
            android:textSize="15sp" />

    </LinearLayout>

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/rv_audio"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />

</LinearLayout>

 

 

 

 

 

 

 

 

 

AudioRecyclerAdapter
package com.example.myapplication.adapter;

import android.annotation.SuppressLint;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;

import androidx.recyclerview.widget.RecyclerView;

import com.example.myapplication.R;
import com.example.myapplication.bean.MediaInfo;
import com.example.myapplication.util.MediaUtil;
import com.example.myapplication.widget.RecyclerExtras;

import java.util.List;

public class AudioRecyclerAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    private Context mContext; // 声明一个上下文对象
    private List<MediaInfo> mAudioList; // 声明一个音频信息列表

    public AudioRecyclerAdapter(Context context, List<MediaInfo> audio_list) {
        mContext = context;
        mAudioList = audio_list;
    }

    // 获取列表项的个数
    public int getItemCount() {
        return mAudioList.size();
    }

    // 创建列表项的视图持有者
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup vg, int viewType) {
        // 根据布局文件item_audio.xml生成视图对象
        View v = LayoutInflater.from(mContext).inflate(R.layout.item_audio, vg, false);
        return new ItemHolder(v);
    }

    // 绑定列表项的视图持有者
    public void onBindViewHolder(RecyclerView.ViewHolder vh, @SuppressLint("RecyclerView") final int position) {
        ItemHolder holder = (ItemHolder) vh;
        MediaInfo audio = mAudioList.get(position);
        holder.tv_name.setText(audio.getTitle()); // 显示音频名称
        holder.tv_duration.setText(MediaUtil.formatDuration(audio.getDuration())); // 显示音频时长
        if (audio.getProgress() >= 0) { // 正在播放
            holder.ll_progress.setVisibility(View.VISIBLE);
            holder.pb_audio.setMax(audio.getDuration()); // 设置进度条的最大值,也就是媒体的播放时长
            holder.pb_audio.setProgress(audio.getProgress()); // 设置进度条的播放进度,也就是已播放的进度
            holder.tv_progress.setText(MediaUtil.formatDuration(audio.getProgress())); // 显示已播放时长
        } else { // 没在播放
            holder.ll_progress.setVisibility(View.GONE);
        }
        // 列表项的点击事件需要自己实现
        holder.ll_audio.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mOnItemClickListener != null) {
                    mOnItemClickListener.onItemClick(v, position);
                }
            }
        });
    }

    // 获取列表项的类型
    public int getItemViewType(int position) {
        return 0;
    }

    // 获取列表项的编号
    public long getItemId(int position) {
        return position;
    }

    // 定义列表项的视图持有者
    public class ItemHolder extends RecyclerView.ViewHolder {
        public LinearLayout ll_audio; // 声明音频列表的线性布局对象
        public TextView tv_name; // 声明音频名称的文本视图对象
        public TextView tv_duration; // 声明总时长的文本视图对象
        public LinearLayout ll_progress; // 声明进度区域的线性布局对象
        public ProgressBar pb_audio; // 声明音频播放的进度条对象
        public TextView tv_progress; // 声明已播放时长的文本视图对象

        public ItemHolder(View v) {
            super(v);
            ll_audio = v.findViewById(R.id.ll_audio);
            tv_name = v.findViewById(R.id.tv_name);
            tv_duration = v.findViewById(R.id.tv_duration);
            ll_progress = v.findViewById(R.id.ll_progress);
            pb_audio = v.findViewById(R.id.pb_audio);
            tv_progress = v.findViewById(R.id.tv_progress);
        }

    }

    // 声明列表项的点击监听器对象
    private RecyclerExtras.OnItemClickListener mOnItemClickListener;
    public void setOnItemClickListener(RecyclerExtras.OnItemClickListener listener) {
        this.mOnItemClickListener = listener;
    }

}

 

 

 

 

 

 

 

MediaInfo
package com.example.myapplication.bean;

public class MediaInfo {
    private long id; // 编号
    private String title; // 标题
    private int duration; // 播放时长
    private long size; // 文件大小
    private String path; // 文件路径
    private int progress=-1; // 播放进度

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    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;
    }

    public String getPath() {
        return path;
    }

    public void setPath(String path) {
        this.path = path;
    }

    public int getProgress() {
        return progress;
    }

    public void setProgress(int progress) {
        this.progress = progress;
    }

}

 

 

 

 

 

 

 

 

 

MediaUtil
package com.example.myapplication.util;

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.media.MediaMetadataRetriever;
import android.net.Uri;
import android.os.Environment;
import android.util.Log;

import java.io.File;

@SuppressLint("DefaultLocale")
public class MediaUtil {
    private final static String TAG = "MediaUtil";

    // 格式化播放时长(mm:ss)
    public static String formatDuration(int milliseconds) {
        int seconds = milliseconds / 1000;
        int hour = seconds / 3600;
        int minute = seconds / 60;
        int second = seconds % 60;
        String str;
        if (hour > 0) {
            str = String.format("%02d:%02d:%02d", hour, minute, second);
        } else {
            str = String.format("%02d:%02d", minute, second);
        }
        return str;
    }

    // 获得音视频文件的缓存路径
    public static String getRecordFilePath(Context context, String dir_name, String extend_name) {
        String path = "";
        File recordDir = new File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).toString() + "/" + dir_name + "/");
        if (!recordDir.exists()) {
            recordDir.mkdirs();
        }
        try {
            File recordFile = File.createTempFile(DateUtil.getNowDateTime(), extend_name, recordDir);
            path = recordFile.getAbsolutePath();
            Log.d(TAG, "dir_name=" + dir_name + ", extend_name=" + extend_name + ", path=" + path);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return path;
    }

    // 获取视频文件中的某帧图片
    public static Bitmap getOneFrame(Context ctx, Uri uri) {
        MediaMetadataRetriever retriever = new MediaMetadataRetriever();
        retriever.setDataSource(ctx, uri);
        // 获得视频的播放时长,大于1秒的取第1秒处的帧图,不足1秒的取第0秒处的帧图
        String duration = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
        Log.d(TAG, "duration="+duration);
        int pos = (Integer.parseInt(duration)/1000)>1 ? 1 : 0;
        // 获取指定时间的帧图,注意getFrameAtTime方法的时间单位是微秒
        return retriever.getFrameAtTime(pos * 1000 * 1000);
    }
}

 

 

 

 

 

 

 

 

 

 

 

 

 

RecyclerExtras

 

package com.example.myapplication.widget;

import android.view.View;

public class RecyclerExtras
{


    // 定义一个循环视图列表项的点击监听器接口
    public interface OnItemClickListener
    {

        void onItemClick(View view, int position);
    }

    // 定义一个循环视图列表项的长按监听器接口
    public interface OnItemLongClickListener
    {

        void onItemLongClick(View view, int position);
    }

    // 定义一个循环视图列表项的删除监听器接口
    public interface OnItemDeleteClickListener
    {

        void onItemDeleteClick(View view, int position);
    }

}

 

 

 

 

 

 

 

FileUtil
package com.example.myapplication.util;

import android.content.Context;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Build;
import android.provider.MediaStore;
import android.util.Log;

import androidx.core.content.FileProvider;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;

public class FileUtil {
    private final static String TAG = "FileUtil";

    // 把字符串保存到指定路径的文本文件
    public static void saveText(String path, String txt) {
        // 根据指定的文件路径构建文件输出流对象
        try (FileOutputStream fos = new FileOutputStream(path)) {
            fos.write(txt.getBytes()); // 把字符串写入文件输出流
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 从指定路径的文本文件中读取内容字符串
    public static String openText(String path) {
        String readStr = "";
        // 根据指定的文件路径构建文件输入流对象
        try (FileInputStream fis = new FileInputStream(path)) {
            byte[] b = new byte[fis.available()];
            fis.read(b); // 从文件输入流读取字节数组
            readStr = new String(b); // 把字节数组转换为字符串
        } catch (Exception e) {
            e.printStackTrace();
        }
        return readStr; // 返回文本文件中的文本字符串
    }

    // 把位图数据保存到指定路径的图片文件
    public static void saveImage(String path, Bitmap bitmap) {
        // 根据指定的文件路径构建文件输出流对象
        try (FileOutputStream fos = new FileOutputStream(path)) {
            // 把位图数据压缩到文件输出流中
            bitmap.compress(Bitmap.CompressFormat.JPEG, 80, fos);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 从指定路径的图片文件中读取位图数据
    public static Bitmap openImage(String path) {
        Bitmap bitmap = null; // 声明一个位图对象
        // 根据指定的文件路径构建文件输入流对象
        try (FileInputStream fis = new FileInputStream(path)) {
            // 从文件输入流中解码位图数据
            bitmap = BitmapFactory.decodeStream(fis);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return bitmap; // 返回图片文件中的位图数据
    }

    // 检查文件是否存在,以及文件路径是否合法
    public static boolean checkFileUri(Context ctx, String path) {
        boolean result = true;
        File file = new File(path);
        if (!file.exists() || !file.isFile() || file.length() <= 0) {
            result = false;
        }
        try
        {
            Uri uri = Uri.parse(path); // 根据指定路径创建一个Uri对象

            // 兼容Android7.0,把访问文件的Uri方式改为FileProvider
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
            {

//                // 通过FileProvider获得文件的Uri访问方式
//                uri = FileProvider.getUriForFile(ctx,
//                        ctx.getPackageName()+".fileProvider", new File(path));
            }
        }
        catch (Exception e)   // 该路径可能不存在
        {
            e.printStackTrace();
            result = false;
        }
        return result;
    }

    // 把指定uri保存为存储卡文件
    public static void saveFileFromUri(Context ctx, Uri src, String dest) {
        try (InputStream is = ctx.getContentResolver().openInputStream(src);
             OutputStream os = new FileOutputStream(dest);) {
            int byteCount = 0;
            byte[] bytes = new byte[8096];
            while ((byteCount = is.read(bytes)) != -1){
                os.write(bytes, 0, byteCount);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 从content://media/external/file/这样的Uri中获取文件路径
    public static String getPathFromContentUri(Context context, Uri uri) {
        String path = uri.toString();
        if (path.startsWith("content://")) {
            String[] proj = new String[]{ // 媒体库的字段名称数组
                    MediaStore.Video.Media._ID, // 编号
                    MediaStore.Video.Media.TITLE, // 标题
                    MediaStore.Video.Media.SIZE, // 文件大小
                    MediaStore.Video.Media.MIME_TYPE, // 文件类型
                    MediaStore.Video.Media.DATA // 文件大小
            };
            try (Cursor cursor = context.getContentResolver().query(uri,
                    proj, null, null, null)) {
                cursor.moveToFirst(); // 把游标移动到开头
                if (cursor.getString(4) != null) {
                    path = cursor.getString(4);
                }
                Log.d(TAG, cursor.getLong(0) + " " + cursor.getString(1)
                        + " " + cursor.getLong(2) + " " + cursor.getString(3)
                        + " " + cursor.getString(4));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return path;
    }

}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

MainActivity
package com.example.myapplication;

import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import android.database.Cursor;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.provider.MediaStore;
import android.util.Log;
import android.view.View;
import com.example.myapplication.adapter.AudioRecyclerAdapter;
import com.example.myapplication.bean.MediaInfo;
import com.example.myapplication.util.FileUtil;
import com.example.myapplication.widget.RecyclerExtras;

import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;

public class MainActivity extends AppCompatActivity implements RecyclerExtras.OnItemClickListener {
    private final static String TAG = "AudioPlayActivity";
    private RecyclerView rv_audio; // 音频列表的循环视图
    private List<MediaInfo> mAudioList = new ArrayList<MediaInfo>(); // 音频列表
    private Uri mAudioUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; // 音频库的Uri
    private String[] mAudioColumn = new String[]{ // 媒体库的字段名称数组
            MediaStore.Audio.Media._ID, // 编号
            MediaStore.Audio.Media.TITLE, // 标题
            MediaStore.Audio.Media.DURATION, // 播放时长
            MediaStore.Audio.Media.SIZE, // 文件大小
            MediaStore.Audio.Media.DATA}; // 文件路径
    private AudioRecyclerAdapter mAdapter; // 音频列表的适配器
    private MediaPlayer mMediaPlayer = new MediaPlayer(); // 媒体播放器
    private Timer mTimer = new Timer(); // 计时器
    private int mLastPosition = -1; // 上次播放的音频序号

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        rv_audio = findViewById(R.id.rv_audio);
        loadAudioList(); // 加载音频列表
        showAudioList(); // 显示音频列表
    }

    // 加载音频列表
    private void loadAudioList() {
        mAudioList.clear(); // 清空音频列表
        // 通过内容解析器查询音频库,并返回结果集的游标。记录结果按照修改时间降序返回
        Cursor cursor = getContentResolver().query(mAudioUri, mAudioColumn,null, null, "date_modified desc");

        if (cursor != null)
        {

            // 下面遍历结果集,并逐个添加到音频列表。简单起见只挑选前十个音频
            for (int i=0; i<10 && cursor.moveToNext(); i++)
            {

                MediaInfo audio = new MediaInfo(); // 创建一个音频信息对象
                audio.setId(cursor.getLong(0)); // 设置音频编号
                audio.setTitle(cursor.getString(1)); // 设置音频标题
                audio.setDuration(cursor.getInt(2)); // 设置音频时长
                audio.setSize(cursor.getLong(3)); // 设置音频大小
                audio.setPath(cursor.getString(4)); // 设置音频路径
                Log.d(TAG, audio.getTitle() + " " + audio.getDuration() + " " + audio.getSize() + " " + audio.getPath());

                if (!FileUtil.checkFileUri(this, audio.getPath()))
                {

                    i--;
                    continue;
                }
                mAudioList.add(audio); // 添加至音频列表
            }
            cursor.close(); // 关闭数据库游标
        }
    }

    // 显示音频列表
    private void showAudioList()
    {

        // 创建一个水平方向的线性布局管理器
        LinearLayoutManager manager = new LinearLayoutManager(this, RecyclerView.VERTICAL, false);

        rv_audio.setLayoutManager(manager); // 设置循环视图的布局管理器

        mAdapter = new AudioRecyclerAdapter(this, mAudioList); // 创建音频列表的线性适配器

        mAdapter.setOnItemClickListener(this); // 设置线性列表的点击监听器

        rv_audio.setAdapter(mAdapter); // 设置循环视图的列表适配器
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mTimer.cancel(); // 取消计时器
        if (mMediaPlayer.isPlaying()) { // 是否正在播放
            mMediaPlayer.stop(); // 结束播放
        }
        mMediaPlayer.release(); // 释放媒体播放器
    }

    @Override
    public void onItemClick(View view, final int position) {
        if (mLastPosition!=-1 && mLastPosition!=position) {
            MediaInfo last_audio = mAudioList.get(mLastPosition);
            last_audio.setProgress(-1); // 当前进度设为-1表示没在播放
            mAudioList.set(mLastPosition, last_audio);
            mAdapter.notifyItemChanged(mLastPosition); // 刷新此处的列表项
        }
        mLastPosition = position;
        final MediaInfo audio = mAudioList.get(position);
        Log.d(TAG, "onItemClick position="+position+",audio.getPath()="+audio.getPath());
        mTimer.cancel(); // 取消计时器
        mMediaPlayer.reset(); // 重置媒体播放器
        // mMediaPlayer.setVolume(0.5f, 0.5f); // 设置音量,可选
        mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); // 设置音频流的类型为音乐
        try {
            mMediaPlayer.setDataSource(audio.getPath()); // 设置媒体数据的文件路径
            mMediaPlayer.prepare(); // 媒体播放器准备就绪
            mMediaPlayer.start(); // 媒体播放器开始播放
        } catch (Exception e) {
            e.printStackTrace();
        }
        mTimer = new Timer(); // 创建一个计时器
        mTimer.schedule(new TimerTask() {
            @Override
            public void run() {
                audio.setProgress(mMediaPlayer.getCurrentPosition()); // 设置进度条的当前进度
                mAudioList.set(position, audio);
                // 界面刷新操作需要在主线程执行,故而向处理器发送消息,由处理器在主线程更新界面
                mHandler.sendEmptyMessage(position);
                Log.d(TAG, "CurrentPosition="+mMediaPlayer.getCurrentPosition()+",position="+position);
            }
        }, 0, 1000); // 计时器每隔一秒就更新进度条上的播放进度
    }

    private Handler mHandler = new Handler(Looper.myLooper()) {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            mAdapter.notifyItemChanged(msg.what); // 刷新此处的列表项
        }
    };

}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

posted @ 2022-10-04 21:43  小白龙白龙马  阅读(419)  评论(0编辑  收藏  举报