利用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); // 刷新此处的列表项
}
};
}