多媒体——音频——利用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); // 刷新此处的列表项 } }; }