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

我会带着你远行 2024-04-03 09:15 220阅读 0赞

利用MediaPlayer播放音频

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

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

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

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

42a1b19aeb0e202aa331ab225ee61acc.png

6f8609324cfcb72353c5d8ffb3bcb773.png

布局:

  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2. android:layout_width="match_parent"
  3. android:layout_height="match_parent"
  4. android:orientation="vertical"
  5. android:padding="5dp">
  6. <TextView
  7. android:layout_width="match_parent"
  8. android:layout_height="wrap_content"
  9. android:gravity="center"
  10. android:text="点击音频列表开始播放"
  11. android:textColor="@color/black"
  12. android:textSize="17sp" />
  13. <LinearLayout
  14. android:layout_width="match_parent"
  15. android:layout_height="40dp"
  16. android:layout_margin="2dp"
  17. android:orientation="horizontal">
  18. <TextView
  19. android:layout_width="0dp"
  20. android:layout_height="match_parent"
  21. android:layout_weight="3"
  22. android:gravity="left|center"
  23. android:text="音频名称"
  24. android:textColor="@color/black"
  25. android:textSize="15sp" />
  26. <TextView
  27. android:layout_width="0dp"
  28. android:layout_height="match_parent"
  29. android:layout_weight="1"
  30. android:gravity="right|center"
  31. android:text="总时长"
  32. android:textColor="@color/black"
  33. android:textSize="15sp" />
  34. </LinearLayout>
  35. <androidx.recyclerview.widget.RecyclerView
  36. android:id="@+id/rv_audio"
  37. android:layout_width="match_parent"
  38. android:layout_height="0dp"
  39. android:layout_weight="1" />
  40. </LinearLayout>

5e0e91d8ed4b982a2d80359a3bc4416d.png

  1. AudioRecyclerAdapter
  2. package com.example.myapplication.adapter;
  3. import android.annotation.SuppressLint;
  4. import android.content.Context;
  5. import android.view.LayoutInflater;
  6. import android.view.View;
  7. import android.view.ViewGroup;
  8. import android.widget.LinearLayout;
  9. import android.widget.ProgressBar;
  10. import android.widget.TextView;
  11. import androidx.recyclerview.widget.RecyclerView;
  12. import com.example.myapplication.R;
  13. import com.example.myapplication.bean.MediaInfo;
  14. import com.example.myapplication.util.MediaUtil;
  15. import com.example.myapplication.widget.RecyclerExtras;
  16. import java.util.List;
  17. public class AudioRecyclerAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
  18. private Context mContext; // 声明一个上下文对象
  19. private List<MediaInfo> mAudioList; // 声明一个音频信息列表
  20. public AudioRecyclerAdapter(Context context, List<MediaInfo> audio_list) {
  21. mContext = context;
  22. mAudioList = audio_list;
  23. }
  24. // 获取列表项的个数
  25. public int getItemCount() {
  26. return mAudioList.size();
  27. }
  28. // 创建列表项的视图持有者
  29. public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup vg, int viewType) {
  30. // 根据布局文件item_audio.xml生成视图对象
  31. View v = LayoutInflater.from(mContext).inflate(R.layout.item_audio, vg, false);
  32. return new ItemHolder(v);
  33. }
  34. // 绑定列表项的视图持有者
  35. public void onBindViewHolder(RecyclerView.ViewHolder vh, @SuppressLint("RecyclerView") final int position) {
  36. ItemHolder holder = (ItemHolder) vh;
  37. MediaInfo audio = mAudioList.get(position);
  38. holder.tv_name.setText(audio.getTitle()); // 显示音频名称
  39. holder.tv_duration.setText(MediaUtil.formatDuration(audio.getDuration())); // 显示音频时长
  40. if (audio.getProgress() >= 0) { // 正在播放
  41. holder.ll_progress.setVisibility(View.VISIBLE);
  42. holder.pb_audio.setMax(audio.getDuration()); // 设置进度条的最大值,也就是媒体的播放时长
  43. holder.pb_audio.setProgress(audio.getProgress()); // 设置进度条的播放进度,也就是已播放的进度
  44. holder.tv_progress.setText(MediaUtil.formatDuration(audio.getProgress())); // 显示已播放时长
  45. } else { // 没在播放
  46. holder.ll_progress.setVisibility(View.GONE);
  47. }
  48. // 列表项的点击事件需要自己实现
  49. holder.ll_audio.setOnClickListener(new View.OnClickListener() {
  50. @Override
  51. public void onClick(View v) {
  52. if (mOnItemClickListener != null) {
  53. mOnItemClickListener.onItemClick(v, position);
  54. }
  55. }
  56. });
  57. }
  58. // 获取列表项的类型
  59. public int getItemViewType(int position) {
  60. return 0;
  61. }
  62. // 获取列表项的编号
  63. public long getItemId(int position) {
  64. return position;
  65. }
  66. // 定义列表项的视图持有者
  67. public class ItemHolder extends RecyclerView.ViewHolder {
  68. public LinearLayout ll_audio; // 声明音频列表的线性布局对象
  69. public TextView tv_name; // 声明音频名称的文本视图对象
  70. public TextView tv_duration; // 声明总时长的文本视图对象
  71. public LinearLayout ll_progress; // 声明进度区域的线性布局对象
  72. public ProgressBar pb_audio; // 声明音频播放的进度条对象
  73. public TextView tv_progress; // 声明已播放时长的文本视图对象
  74. public ItemHolder(View v) {
  75. super(v);
  76. ll_audio = v.findViewById(R.id.ll_audio);
  77. tv_name = v.findViewById(R.id.tv_name);
  78. tv_duration = v.findViewById(R.id.tv_duration);
  79. ll_progress = v.findViewById(R.id.ll_progress);
  80. pb_audio = v.findViewById(R.id.pb_audio);
  81. tv_progress = v.findViewById(R.id.tv_progress);
  82. }
  83. }
  84. // 声明列表项的点击监听器对象
  85. private RecyclerExtras.OnItemClickListener mOnItemClickListener;
  86. public void setOnItemClickListener(RecyclerExtras.OnItemClickListener listener) {
  87. this.mOnItemClickListener = listener;
  88. }
  89. }
  90. MediaInfo
  91. package com.example.myapplication.bean;
  92. public class MediaInfo {
  93. private long id; // 编号
  94. private String title; // 标题
  95. private int duration; // 播放时长
  96. private long size; // 文件大小
  97. private String path; // 文件路径
  98. private int progress=-1; // 播放进度
  99. public long getId() {
  100. return id;
  101. }
  102. public void setId(long id) {
  103. this.id = id;
  104. }
  105. public String getTitle() {
  106. return title;
  107. }
  108. public void setTitle(String title) {
  109. this.title = title;
  110. }
  111. public int getDuration() {
  112. return duration;
  113. }
  114. public void setDuration(int duration) {
  115. this.duration = duration;
  116. }
  117. public long getSize() {
  118. return size;
  119. }
  120. public void setSize(long size) {
  121. this.size = size;
  122. }
  123. public String getPath() {
  124. return path;
  125. }
  126. public void setPath(String path) {
  127. this.path = path;
  128. }
  129. public int getProgress() {
  130. return progress;
  131. }
  132. public void setProgress(int progress) {
  133. this.progress = progress;
  134. }
  135. }
  136. MediaUtil
  137. package com.example.myapplication.util;
  138. import android.annotation.SuppressLint;
  139. import android.content.Context;
  140. import android.graphics.Bitmap;
  141. import android.media.MediaMetadataRetriever;
  142. import android.net.Uri;
  143. import android.os.Environment;
  144. import android.util.Log;
  145. import java.io.File;
  146. @SuppressLint("DefaultLocale")
  147. public class MediaUtil {
  148. private final static String TAG = "MediaUtil";
  149. // 格式化播放时长(mm:ss)
  150. public static String formatDuration(int milliseconds) {
  151. int seconds = milliseconds / 1000;
  152. int hour = seconds / 3600;
  153. int minute = seconds / 60;
  154. int second = seconds % 60;
  155. String str;
  156. if (hour > 0) {
  157. str = String.format("%02d:%02d:%02d", hour, minute, second);
  158. } else {
  159. str = String.format("%02d:%02d", minute, second);
  160. }
  161. return str;
  162. }
  163. // 获得音视频文件的缓存路径
  164. public static String getRecordFilePath(Context context, String dir_name, String extend_name) {
  165. String path = "";
  166. File recordDir = new File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).toString() + "/" + dir_name + "/");
  167. if (!recordDir.exists()) {
  168. recordDir.mkdirs();
  169. }
  170. try {
  171. File recordFile = File.createTempFile(DateUtil.getNowDateTime(), extend_name, recordDir);
  172. path = recordFile.getAbsolutePath();
  173. Log.d(TAG, "dir_name=" + dir_name + ", extend_name=" + extend_name + ", path=" + path);
  174. } catch (Exception e) {
  175. e.printStackTrace();
  176. }
  177. return path;
  178. }
  179. // 获取视频文件中的某帧图片
  180. public static Bitmap getOneFrame(Context ctx, Uri uri) {
  181. MediaMetadataRetriever retriever = new MediaMetadataRetriever();
  182. retriever.setDataSource(ctx, uri);
  183. // 获得视频的播放时长,大于1秒的取第1秒处的帧图,不足1秒的取第0秒处的帧图
  184. String duration = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
  185. Log.d(TAG, "duration="+duration);
  186. int pos = (Integer.parseInt(duration)/1000)>1 ? 1 : 0;
  187. // 获取指定时间的帧图,注意getFrameAtTime方法的时间单位是微秒
  188. return retriever.getFrameAtTime(pos * 1000 * 1000);
  189. }
  190. }
  191. RecyclerExtras
  192. package com.example.myapplication.widget;
  193. import android.view.View;
  194. public class RecyclerExtras
  195. {
  196. // 定义一个循环视图列表项的点击监听器接口
  197. public interface OnItemClickListener
  198. {
  199. void onItemClick(View view, int position);
  200. }
  201. // 定义一个循环视图列表项的长按监听器接口
  202. public interface OnItemLongClickListener
  203. {
  204. void onItemLongClick(View view, int position);
  205. }
  206. // 定义一个循环视图列表项的删除监听器接口
  207. public interface OnItemDeleteClickListener
  208. {
  209. void onItemDeleteClick(View view, int position);
  210. }
  211. }
  212. FileUtil
  213. package com.example.myapplication.util;
  214. import android.content.Context;
  215. import android.database.Cursor;
  216. import android.graphics.Bitmap;
  217. import android.graphics.BitmapFactory;
  218. import android.net.Uri;
  219. import android.os.Build;
  220. import android.provider.MediaStore;
  221. import android.util.Log;
  222. import androidx.core.content.FileProvider;
  223. import java.io.File;
  224. import java.io.FileInputStream;
  225. import java.io.FileOutputStream;
  226. import java.io.InputStream;
  227. import java.io.OutputStream;
  228. public class FileUtil {
  229. private final static String TAG = "FileUtil";
  230. // 把字符串保存到指定路径的文本文件
  231. public static void saveText(String path, String txt) {
  232. // 根据指定的文件路径构建文件输出流对象
  233. try (FileOutputStream fos = new FileOutputStream(path)) {
  234. fos.write(txt.getBytes()); // 把字符串写入文件输出流
  235. } catch (Exception e) {
  236. e.printStackTrace();
  237. }
  238. }
  239. // 从指定路径的文本文件中读取内容字符串
  240. public static String openText(String path) {
  241. String readStr = "";
  242. // 根据指定的文件路径构建文件输入流对象
  243. try (FileInputStream fis = new FileInputStream(path)) {
  244. byte[] b = new byte[fis.available()];
  245. fis.read(b); // 从文件输入流读取字节数组
  246. readStr = new String(b); // 把字节数组转换为字符串
  247. } catch (Exception e) {
  248. e.printStackTrace();
  249. }
  250. return readStr; // 返回文本文件中的文本字符串
  251. }
  252. // 把位图数据保存到指定路径的图片文件
  253. public static void saveImage(String path, Bitmap bitmap) {
  254. // 根据指定的文件路径构建文件输出流对象
  255. try (FileOutputStream fos = new FileOutputStream(path)) {
  256. // 把位图数据压缩到文件输出流中
  257. bitmap.compress(Bitmap.CompressFormat.JPEG, 80, fos);
  258. } catch (Exception e) {
  259. e.printStackTrace();
  260. }
  261. }
  262. // 从指定路径的图片文件中读取位图数据
  263. public static Bitmap openImage(String path) {
  264. Bitmap bitmap = null; // 声明一个位图对象
  265. // 根据指定的文件路径构建文件输入流对象
  266. try (FileInputStream fis = new FileInputStream(path)) {
  267. // 从文件输入流中解码位图数据
  268. bitmap = BitmapFactory.decodeStream(fis);
  269. } catch (Exception e) {
  270. e.printStackTrace();
  271. }
  272. return bitmap; // 返回图片文件中的位图数据
  273. }
  274. // 检查文件是否存在,以及文件路径是否合法
  275. public static boolean checkFileUri(Context ctx, String path) {
  276. boolean result = true;
  277. File file = new File(path);
  278. if (!file.exists() || !file.isFile() || file.length() <= 0) {
  279. result = false;
  280. }
  281. try
  282. {
  283. Uri uri = Uri.parse(path); // 根据指定路径创建一个Uri对象
  284. // 兼容Android7.0,把访问文件的Uri方式改为FileProvider
  285. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
  286. {
  287. // // 通过FileProvider获得文件的Uri访问方式
  288. // uri = FileProvider.getUriForFile(ctx,
  289. // ctx.getPackageName()+".fileProvider", new File(path));
  290. }
  291. }
  292. catch (Exception e) // 该路径可能不存在
  293. {
  294. e.printStackTrace();
  295. result = false;
  296. }
  297. return result;
  298. }
  299. // 把指定uri保存为存储卡文件
  300. public static void saveFileFromUri(Context ctx, Uri src, String dest) {
  301. try (InputStream is = ctx.getContentResolver().openInputStream(src);
  302. OutputStream os = new FileOutputStream(dest);) {
  303. int byteCount = 0;
  304. byte[] bytes = new byte[8096];
  305. while ((byteCount = is.read(bytes)) != -1){
  306. os.write(bytes, 0, byteCount);
  307. }
  308. } catch (Exception e) {
  309. e.printStackTrace();
  310. }
  311. }
  312. // 从content://media/external/file/这样的Uri中获取文件路径
  313. public static String getPathFromContentUri(Context context, Uri uri) {
  314. String path = uri.toString();
  315. if (path.startsWith("content://")) {
  316. String[] proj = new String[]{ // 媒体库的字段名称数组
  317. MediaStore.Video.Media._ID, // 编号
  318. MediaStore.Video.Media.TITLE, // 标题
  319. MediaStore.Video.Media.SIZE, // 文件大小
  320. MediaStore.Video.Media.MIME_TYPE, // 文件类型
  321. MediaStore.Video.Media.DATA // 文件大小
  322. };
  323. try (Cursor cursor = context.getContentResolver().query(uri,
  324. proj, null, null, null)) {
  325. cursor.moveToFirst(); // 把游标移动到开头
  326. if (cursor.getString(4) != null) {
  327. path = cursor.getString(4);
  328. }
  329. Log.d(TAG, cursor.getLong(0) + " " + cursor.getString(1)
  330. + " " + cursor.getLong(2) + " " + cursor.getString(3)
  331. + " " + cursor.getString(4));
  332. } catch (Exception e) {
  333. e.printStackTrace();
  334. }
  335. }
  336. return path;
  337. }
  338. }
  339. MainActivity
  340. package com.example.myapplication;
  341. import androidx.appcompat.app.AppCompatActivity;
  342. import androidx.recyclerview.widget.LinearLayoutManager;
  343. import androidx.recyclerview.widget.RecyclerView;
  344. import android.database.Cursor;
  345. import android.media.AudioManager;
  346. import android.media.MediaPlayer;
  347. import android.net.Uri;
  348. import android.os.Bundle;
  349. import android.os.Handler;
  350. import android.os.Looper;
  351. import android.os.Message;
  352. import android.provider.MediaStore;
  353. import android.util.Log;
  354. import android.view.View;
  355. import com.example.myapplication.adapter.AudioRecyclerAdapter;
  356. import com.example.myapplication.bean.MediaInfo;
  357. import com.example.myapplication.util.FileUtil;
  358. import com.example.myapplication.widget.RecyclerExtras;
  359. import java.util.ArrayList;
  360. import java.util.List;
  361. import java.util.Timer;
  362. import java.util.TimerTask;
  363. public class MainActivity extends AppCompatActivity implements RecyclerExtras.OnItemClickListener {
  364. private final static String TAG = "AudioPlayActivity";
  365. private RecyclerView rv_audio; // 音频列表的循环视图
  366. private List<MediaInfo> mAudioList = new ArrayList<MediaInfo>(); // 音频列表
  367. private Uri mAudioUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; // 音频库的Uri
  368. private String[] mAudioColumn = new String[]{ // 媒体库的字段名称数组
  369. MediaStore.Audio.Media._ID, // 编号
  370. MediaStore.Audio.Media.TITLE, // 标题
  371. MediaStore.Audio.Media.DURATION, // 播放时长
  372. MediaStore.Audio.Media.SIZE, // 文件大小
  373. MediaStore.Audio.Media.DATA}; // 文件路径
  374. private AudioRecyclerAdapter mAdapter; // 音频列表的适配器
  375. private MediaPlayer mMediaPlayer = new MediaPlayer(); // 媒体播放器
  376. private Timer mTimer = new Timer(); // 计时器
  377. private int mLastPosition = -1; // 上次播放的音频序号
  378. @Override
  379. protected void onCreate(Bundle savedInstanceState) {
  380. super.onCreate(savedInstanceState);
  381. setContentView(R.layout.activity_main);
  382. rv_audio = findViewById(R.id.rv_audio);
  383. loadAudioList(); // 加载音频列表
  384. showAudioList(); // 显示音频列表
  385. }
  386. // 加载音频列表
  387. private void loadAudioList() {
  388. mAudioList.clear(); // 清空音频列表
  389. // 通过内容解析器查询音频库,并返回结果集的游标。记录结果按照修改时间降序返回
  390. Cursor cursor = getContentResolver().query(mAudioUri, mAudioColumn,null, null, "date_modified desc");
  391. if (cursor != null)
  392. {
  393. // 下面遍历结果集,并逐个添加到音频列表。简单起见只挑选前十个音频
  394. for (int i=0; i<10 && cursor.moveToNext(); i++)
  395. {
  396. MediaInfo audio = new MediaInfo(); // 创建一个音频信息对象
  397. audio.setId(cursor.getLong(0)); // 设置音频编号
  398. audio.setTitle(cursor.getString(1)); // 设置音频标题
  399. audio.setDuration(cursor.getInt(2)); // 设置音频时长
  400. audio.setSize(cursor.getLong(3)); // 设置音频大小
  401. audio.setPath(cursor.getString(4)); // 设置音频路径
  402. Log.d(TAG, audio.getTitle() + " " + audio.getDuration() + " " + audio.getSize() + " " + audio.getPath());
  403. if (!FileUtil.checkFileUri(this, audio.getPath()))
  404. {
  405. i--;
  406. continue;
  407. }
  408. mAudioList.add(audio); // 添加至音频列表
  409. }
  410. cursor.close(); // 关闭数据库游标
  411. }
  412. }
  413. // 显示音频列表
  414. private void showAudioList()
  415. {
  416. // 创建一个水平方向的线性布局管理器
  417. LinearLayoutManager manager = new LinearLayoutManager(this, RecyclerView.VERTICAL, false);
  418. rv_audio.setLayoutManager(manager); // 设置循环视图的布局管理器
  419. mAdapter = new AudioRecyclerAdapter(this, mAudioList); // 创建音频列表的线性适配器
  420. mAdapter.setOnItemClickListener(this); // 设置线性列表的点击监听器
  421. rv_audio.setAdapter(mAdapter); // 设置循环视图的列表适配器
  422. }
  423. @Override
  424. protected void onDestroy() {
  425. super.onDestroy();
  426. mTimer.cancel(); // 取消计时器
  427. if (mMediaPlayer.isPlaying()) { // 是否正在播放
  428. mMediaPlayer.stop(); // 结束播放
  429. }
  430. mMediaPlayer.release(); // 释放媒体播放器
  431. }
  432. @Override
  433. public void onItemClick(View view, final int position) {
  434. if (mLastPosition!=-1 && mLastPosition!=position) {
  435. MediaInfo last_audio = mAudioList.get(mLastPosition);
  436. last_audio.setProgress(-1); // 当前进度设为-1表示没在播放
  437. mAudioList.set(mLastPosition, last_audio);
  438. mAdapter.notifyItemChanged(mLastPosition); // 刷新此处的列表项
  439. }
  440. mLastPosition = position;
  441. final MediaInfo audio = mAudioList.get(position);
  442. Log.d(TAG, "onItemClick position="+position+",audio.getPath()="+audio.getPath());
  443. mTimer.cancel(); // 取消计时器
  444. mMediaPlayer.reset(); // 重置媒体播放器
  445. // mMediaPlayer.setVolume(0.5f, 0.5f); // 设置音量,可选
  446. mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); // 设置音频流的类型为音乐
  447. try {
  448. mMediaPlayer.setDataSource(audio.getPath()); // 设置媒体数据的文件路径
  449. mMediaPlayer.prepare(); // 媒体播放器准备就绪
  450. mMediaPlayer.start(); // 媒体播放器开始播放
  451. } catch (Exception e) {
  452. e.printStackTrace();
  453. }
  454. mTimer = new Timer(); // 创建一个计时器
  455. mTimer.schedule(new TimerTask() {
  456. @Override
  457. public void run() {
  458. audio.setProgress(mMediaPlayer.getCurrentPosition()); // 设置进度条的当前进度
  459. mAudioList.set(position, audio);
  460. // 界面刷新操作需要在主线程执行,故而向处理器发送消息,由处理器在主线程更新界面
  461. mHandler.sendEmptyMessage(position);
  462. Log.d(TAG, "CurrentPosition="+mMediaPlayer.getCurrentPosition()+",position="+position);
  463. }
  464. }, 0, 1000); // 计时器每隔一秒就更新进度条上的播放进度
  465. }
  466. private Handler mHandler = new Handler(Looper.myLooper()) {
  467. @Override
  468. public void handleMessage(Message msg) {
  469. super.handleMessage(msg);
  470. mAdapter.notifyItemChanged(msg.what); // 刷新此处的列表项
  471. }
  472. };
  473. }

ff19f60bd94a5d0ed91aa540e1bb4c43.png

3fc516f3bacbf5a829dbcc204f39c312.png

c7acba08034cf606df4d8835dfac9d32.png

c03a2da036e0cb521813a1b9fb6fbbe4.png

5c12dbfef0e9653030e48d69425f7258.png

00ebbd63b5ca9780896d8db21c6440a4.png

75f3c73bedd21414fa6e21a313898d28.png

发表评论

表情:
评论列表 (有 0 条评论,220人围观)

还没有评论,来说两句吧...

相关阅读

    相关 多媒体音频

    播放放在项目中的音频,功能可以做到点击播放、点击暂停、拖动进度条决定音频的播放进度,下面先给大家介绍一下关于多媒体和它的方法: ![Center][] 效果图: !