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

     

     

     

  • 相关阅读:
    图片懒加载原理-实例二
    节流函数(throttle)的原理
    防抖动函数(debounce)的原理
    立即执行函数(immediate)的原理
    图片懒加载原理-实例三
    图片懒加载原理-实例四:首屏加载
    js运算符优先级
    java实现链栈
    java实现栈
    静态链表以及几种表的比较
  • 原文地址:https://www.cnblogs.com/xiaobaibailongma/p/16754569.html
Copyright © 2020-2023  润新知