Android实现语音发送&播放功能以及示例代码

本文链接:https://blog.csdn.net/qq_40785165/article/details/109658968

大家好,我是小黑,一个还没秃头的程序员~~~

这是我第一次写文章,也是希望将我以后的学习经历分享给大家,希望大家喜欢!

简单的事你重复做,你就是专家;重复的事你认真做,你就是赢家。

之前在一个聊天室项目中实现了发送图片之后,我又想着实现一个发送语音的功能,包括录音、计时、播放、耳机与外放切换,先看一下效果图

可以看到发送语音的功能是由点击语音功能模块后弹出的对话框来实现的,点击开始按钮会开始录音,点击完成释放资源并上传录音到服务器,最终刷新录音列表,列表行点击事件会进行录音播放并监听耳机的连接广播。

*注:以下代码中的颜色与尺寸均为项目中定义好的,可替换成自己想要的值。

本次功能开发需要添加的权限如下:

<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />

(一)先定义一个对话框的样式dialog_microphone,代码如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center"
    android:background="@drawable/frame_grey_white_edge"
    android:gravity="center"
    android:orientation="vertical"
    android:paddingLeft="@dimen/b20"
    android:paddingTop="@dimen/b50"
    android:paddingRight="@dimen/b20"
    android:paddingBottom="@dimen/b50">
​
    <ImageView
        android:layout_width="@dimen/b120"
        android:layout_height="@dimen/b120"
        android:src="@mipmap/icon_microphone" />
​
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="@dimen/b50"
        android:text="点击外部区域,取消发送" />
​
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="@dimen/b20">
​
        <com.qmuiteam.qmui.widget.roundwidget.QMUIRoundButton
            android:id="@+id/btn_start"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:padding="@dimen/b10"
            android:text="开始"
            android:textColor="@color/color_white"
            android:textSize="@dimen/b28"
            app:qmui_backgroundColor="@color/color_orange_main"
            app:qmui_borderColor="@color/color_white"
            app:qmui_radius="@dimen/b10" />
​
        <com.qmuiteam.qmui.widget.roundwidget.QMUIRoundButton
            android:id="@+id/btn_ok"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:padding="@dimen/b10"
            android:text="完成"
            android:textColor="@color/color_white"
            android:textSize="@dimen/b28"
            app:qmui_backgroundColor="@color/color_orange_main"
            app:qmui_borderColor="@color/color_white"
            app:qmui_radius="@dimen/b10" />
​
    </LinearLayout>
</LinearLayout>

代码中QMUIRoundButton是QMUI框架的按钮控件,感兴趣的小伙伴可以自行百度了解一下,或者换成普通的按钮控件即可,frame_grey_white_edge代码如下:

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <solid android:color="@color/color_gray_e8" />
    <corners android:radius="@dimen/b20" />
    <stroke android:color="@color/color_white" />
</shape>

(二)我们可以使用MediaRecorder的Api进行录音,我将所用到的Api都整理到类--MediaHelper中去,代码如下:

public class MediaHelper {
    private MediaRecorder mMediaRecorder;
    private String mPath;//文件夹
    private String mFilePath;//文件
​
    private static MediaHelper mInstance;
​
    private MediaHelper(String path) {
        mPath = path;
    }
​
    /**
     * 准备播放后的回调
     * 这个时候文件夹里已经有文件生成了
     * 如果不去释放资源将会一直进行录音
     */
    public interface MediaStateListener {
        void preparedDone();
    }
​
    public MediaStateListener mMediaStateListener;
​
    public void setMediaStateListener(MediaStateListener mediaStateListener) {
        mMediaStateListener = mediaStateListener;
    }
​
​
    /**
     * 单例模式获取 MediaHelper
     * 双检锁/双重校验锁
     *
     * @param path
     * @return
     */
    public static MediaHelper getInstance(String path) {
        if (mInstance == null) {
            synchronized (MediaHelper.class) {
                if (mInstance == null) {
                    mInstance = new MediaHelper(path);
                }
            }
        }
​
        return mInstance;
    }
​
    /**
     * 准备录音
     */
    public void prepare() {
​
        try {
            File fileDir = new File(mPath);
            boolean b = !fileDir.exists();
            if (b) {
                fileDir.mkdirs();
            }
​
            String fileName = System.currentTimeMillis() + ".amr"; // 文件名字
            File file = new File(fileDir, fileName);  // 文件路径
​
            mMediaRecorder = new MediaRecorder();
            mFilePath = file.getAbsolutePath();
            //设置保存文件的路径
            if (Build.VERSION.SDK_INT < 26) {
                //若api低于26,调用setOutputFile(String path)
                mMediaRecorder.setOutputFile(file.getAbsolutePath());
            } else {
                //若API高于26 使用setOutputFile(File path)
                mMediaRecorder.setOutputFile(new File(file.getAbsolutePath()));
            }
            mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);    // 设置MediaRecorder的音频源为麦克风
            mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.AMR_NB);    // 设置音频的格式
            mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);    // 设置音频的编码为AMR_NB
​
            mMediaRecorder.prepare();
            mMediaRecorder.start();
​
            if (mMediaStateListener != null) {
                mMediaStateListener.preparedDone();
            }
        } catch (IllegalStateException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
​
    }
​
    /**
     * 释放资源
     */
    public void release() {
        mMediaRecorder.stop();
        mMediaRecorder.release();
        mMediaRecorder = null;
    }
​
    /**
     * 取消
     */
    public void cancel() {
        release();
        //删除相应的录音
        if (mFilePath != null) {
            File file = new File(mFilePath);
            if (file.exists()) {
                file.delete();
            }
            mFilePath = null;
        }
    }
    //获取生成的文件路径
    public String getFilePath() {
        return mFilePath;
        }
}

  (三) 对话框中通过点击相应按钮调用上述类中的相应的方法,我这里有个对话框的类叫MicrophoneDialog,代码如下:

public class MicrophoneDialog extends BaseDialog implements MediaHelper.MediaStateListener {
    public static final int EXTRA_START = 1;//开始录制
    public static final int EXTRA_UPDATE_TIME = 2;//更新时长
    private AudioListener mAudioListener;
    private long mTime;//时长
    private String mPath = Constants.APK_PATH;
    private boolean authDismiss;//是否是自动关闭的,自动关闭的不触发关闭监听
​
    private MediaHelper mMediaHelper;
    private boolean isRecording;
    private String TAG = "MicrophoneDialog";
​
    public void setAudioListener(AudioListener audioListener) {
        mAudioListener = audioListener;
    }
​
    @Override
    public void preparedDone() {
        mHandler.sendEmptyMessage(EXTRA_START);
    }
​
    public interface AudioListener {
        void finish(long time, String filePath);
​
        void cancel();
    }
​
    public void dismiss(boolean authDismiss) {
        this.authDismiss = authDismiss;
        dismiss();
    }
​
    public MicrophoneDialog(Context context) {
        super(context);
    }
​
    @Override
    public int getViewId() {
        return R.layout.dialog_microphone;
    }
​
    @Override
    public void initBasic(Bundle savedInstanceState) {
        mMediaHelper = MediaHelper.getInstance(mPath);
​
        setOnDismissListener(new OnDismissListener() {
            @Override
            public void onDismiss(DialogInterface dialog) {
                if (!authDismiss) {
                    mMediaHelper.release();
                    isRecording = false;
                    mTime = 0;
                    if (mAudioListener != null) {
                        mAudioListener.cancel();
                    }
                }
            }
        });
        mMediaHelper.setMediaStateListener(this);
    }
​
    @OnClick({R.id.btn_ok, R.id.btn_start})
    public void onViewClicked(View view) {
        switch (view.getId()) {
            case R.id.btn_start:
                QToast.showToast("开始录音");
                mMediaHelper.prepare();
                break;
            case R.id.btn_ok:
                mMediaHelper.release();//要上传音频前释放资源,否则没办法上传音频
                QToast.showToast("结束录音");
                isRecording = false;
                Log.e(TAG, "onViewClicked: " + mTime + "," + mMediaHelper.getFilePath());
                if (mAudioListener != null) {
                    mAudioListener.finish(mTime / 1000, mMediaHelper.getFilePath());
                    mTime = 0;
                }
                break;
        }
    }
​
    /**
     * 这里使用Handle是为了在子线程中更新ui
     * 尽管我现在子线程只用来计时,没有更新ui
     * 但是万一以后会更新ui呢
     */
    @SuppressLint("HandlerLeak")
    private Handler mHandler = new Handler() {
​
        public void handleMessage(android.os.Message msg) {
            switch (msg.what) {
                case EXTRA_START:
                    isRecording = true;
                    //开始计时
                    postDelayed(mRunnable, 1000);
                    break;
                case EXTRA_UPDATE_TIME:
                    postDelayed(mRunnable, 1000);
                    break;
​
            }
        }
    };
    /**
     * 开启个子线程计算时长
     */
    private Runnable mRunnable = new Runnable() {
        @Override
        public void run() {
            if (isRecording) {
                mTime += 1000;
                mHandler.sendEmptyMessage(EXTRA_UPDATE_TIME);//TODO 通知修改时长显示
            }
        }
    };

上述代码中练习了Handle的使用,本来是为了在子线程中更新ui的,但是还是偷了懒,延时计时使用Thread.sleep也是可以的,用到了Butterknife框架进行控件声明以及点击事件声明,BaseDialog是封装好的基类,小伙伴们可将initBasic()方法中的代码移至onCreate()中去即可,需要注意的是上传文件之前需要先释放资源,否则会影响上传文件接口的调用,对话框做完之后就是在activity或者fragment中去显示对话框即可,录音完成之后调用相应的后台接口进行文件上传即可,随后返回录音地址,使用recyclerview开发录音列表,通过行点击进行录音播放,播放实现的效果有:

1.点击相同子项播放与重置播放,点击不同子项需要释放上一个资源重置下一个资源
2.监听有线耳机与蓝牙耳机的拔插,修改播放参数

(四)播放的代码如下:

//变量声明以及定义
private MediaPlayer mMediaPlayer;
private HeadSetReceiver mHeadSetReceiver;//广播监听
private AudioManager mAudioManager = null;
private int index;//当前点击的是不是本身
......
registerHeadsetReceiver();
mAudioManager = (AudioManager) mActivity.getSystemService(Context.AUDIO_SERVICE);//切换耳机等播放模式
mAudioManager.setMode(AudioManager.MODE_NORMAL);//普通模式
mMediaPlayer = new MediaPlayer();//播放
......
//点击事件,先判断是否是音频类型的消息
 if (item.getMessageType() == 3) {
   if (!mMediaPlayer.isPlaying()) {//没在播放就播放
        play(item);
    } else {//同样的音频在播放就释放并重置,如果是新的音频就接着播放新的
            mMediaPlayer.reset();
            mMediaPlayer.stop();
            mMediaPlayer.release();
            mMediaPlayer = new MediaPlayer();
            if (position != index) {
                play(item);//播放
            }
      }
   }
   index = position;
   ......
   //播放录音的方法
     private void play(MessageBean item) {
        try {
            mMediaPlayer = new MediaPlayer();
            mMediaPlayer.setDataSource(HttpHelper.picDomain + item.getMessage_content());//域名+文件路径
            mMediaPlayer.prepare();
            mMediaPlayer.start();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

(五)耳机(有线耳机&蓝牙耳机)拔插的监听以及注册监听代码如下:

 class HeadSetReceiver extends BroadcastReceiver {
​
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if (BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
                BluetoothAdapter defaultAdapter = BluetoothAdapter.getDefaultAdapter();
                //记得加上蓝牙权限
                if (BluetoothHeadset.STATE_AUDIO_DISCONNECTED == defaultAdapter.getProfileConnectionState(BluetoothHeadset.HEADSET)) {
                    QToast.showToast("耳机未连接");
                    mAudioManager.setSpeakerphoneOn(true);
                } else {
                    QToast.showToast("耳机已连接");
                    mAudioManager.setSpeakerphoneOn(false);
                }
            } else if (intent.hasExtra("state")) {
                if (intent.getIntExtra("state", 0) == 0) {
                    QToast.showToast("耳机未连接");
                    mAudioManager.setSpeakerphoneOn(true);
                } else {
                    QToast.showToast("耳机已连接");
                    mAudioManager.setSpeakerphoneOn(false);
​
                }
            }
        }
    }
    private void registerHeadsetReceiver() {
        mHeadSetReceiver = new HeadSetReceiver();
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction("android.intent.action.HEADSET_PLUG");
        registerReceiver(mHeadSetReceiver, intentFilter);
        IntentFilter bluetoothFilter = new IntentFilter(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
        registerReceiver(mHeadSetReceiver, bluetoothFilter);
    }

(六)页面销毁时要释放资源(onDestroy)

if (mMediaPlayer != null) {
            mMediaPlayer.stop();
            mMediaPlayer.reset();
            mMediaPlayer.release();
            mMediaPlayer = null;
        }
        unregisterReceiver(mHeadSetReceiver);

到此为止,语音发送以及点击播放的功能就实现了,效果就是开头的两张静态图,因为没有什么花里胡哨的界面交互效果,所以就不放上gif效果了,感兴趣的小伙伴可以自己动手试一试,本项目的前后台均为本人完成,有疑惑的可以扫描下方二维码添加我微信,欢迎大家来与我交流Android前后台技术,大家共同进步!也欢迎大家订阅我的微信公众号(也是刚搞起来的),我会继续分享一些有趣的学习经历,最后,祝大家万事如意,身体健康,谢谢大家的支持与阅读!

  • 10
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
实现在线交流一般需要使用即时通讯(IM)SDK。环信是一款常用的IM SDK,可以帮助开发者快速实现在线交流功能。下面是使用环信实现在线交流的完整流程及代码: 1. 注册环信账号并创建应用 首先,需要在环信官网注册账号并创建应用。注册时需要填写邮箱、手机号和密码,并验证邮箱和手机号。创建应用时需要填写应用名称和应用类型,选择使用的IM协议(HTTP或XMPP),并获取Appkey。 2. 集成环信SDK 在Android Studio中创建一个新项目,然后将环信SDK集成进来。可以使用Gradle依赖或手动添加jar包的方式集成。 使用Gradle依赖的方式: 在项目的build.gradle文件中添加以下代码: ``` allprojects { repositories { mavenCentral() } } ``` 在app的build.gradle文件中添加以下代码: ``` dependencies { implementation 'com.hyphenate:hyphenate-sdk:3.7.0' } ``` 手动添加jar包的方式: 将下载的环信SDK解压后,将libs目录中的所有jar包复制到项目的libs目录下。 在app的build.gradle文件中添加以下代码: ``` dependencies { implementation files('libs/easemob-sdk-3.7.0.jar') implementation files('libs/easemob-sdk-chat-3.7.0.jar') implementation files('libs/easemob-sdk-voice-3.7.0.jar') } ``` 3. 初始化环信SDK 在Application中初始化环信SDK,代码如下: ``` public class MyApplication extends Application { @Override public void onCreate() { super.onCreate(); EMOptions options = new EMOptions(); options.setAppKey("your_app_key"); EMClient.getInstance().init(this, options); EMClient.getInstance().setDebugMode(true); } } ``` 其中,your_app_key为在环信官网创建应用时获取到的Appkey。 4. 注册和登录 注册和登录分别使用EMClient.getInstance().createAccount和EMClient.getInstance().login方法实现。在注册和登录成功后,需要保存用户的用户名和密码,以便下次自动登录。 注册代码如下: ``` EMClient.getInstance().createAccount(username, password, new EMCallBack() { @Override public void onSuccess() { // 注册成功 } @Override public void onError(int code, String error) { // 注册失败 } @Override public void onProgress(int progress, String status) { // 注册过程中的进度回调 } }); ``` 登录代码如下: ``` EMClient.getInstance().login(username, password, new EMCallBack() { @Override public void onSuccess() { // 登录成功 } @Override public void onError(int code, String error) { // 登录失败 } @Override public void onProgress(int progress, String status) { // 登录过程中的进度回调 } }); ``` 5. 发送消息 使用EMClient.getInstance().chatManager().sendMessage方法发送消息。需要先创建一个EMMessage对象,设置消息的类型、内容、接收方等信息,然后调用sendMessage方法发送消息。 文本消息的发送代码如下: ``` EMMessage message = EMMessage.createTxtSendMessage(content, toUsername); EMClient.getInstance().chatManager().sendMessage(message); ``` 图片、语音、视频等多媒体消息的发送方式类似,只需要创建对应的EMMessage对象即可。 6. 接收消息 接收消息需要注册一个EMMessageListener,在收到新的消息时,会回调onMessageReceived方法。需要在主线程中更新UI。 ``` EMClient.getInstance().chatManager().addMessageListener(new EMMessageListener() { @Override public void onMessageReceived(List<EMMessage> messages) { // 收到新消息 runOnUiThread(new Runnable() { @Override public void run() { // 更新UI } }); } // 其他回调方法 }); ``` 7. 聊天界面 聊天界面可以使用RecyclerView实现。每个消息可以根据类型分为不同的布局,例如文本消息使用TextView,图片消息使用ImageView等。需要根据消息的发送方和接收方,判断消息是自己发送的还是别人发送的,从而设置不同的布局样式。 8. 代码示例 完整的代码示例可以参考环信官方文档:https://docs.easemob.com/im/300androidclientintegration/20androidsdkinitialize

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值