Abstract: The requirement is to develop functions similar to WeChat's voice sending, without voice to text. I have read some codes online, but they cannot be used directly. Some code logic is problematic, so I want to post my own code for reference only.
Function:
a. Set the maximum recording time and recording countdown (for the convenience of testing, the maximum duration is set to 15 seconds, and the start countdown is set to 7 seconds)
b. Check recording and storage permissions before recording
Source code:
1. DialogManager for recording dialog management:
/** * Function: Recording dialog management class */ public class DialogManager { private builder; private AlertDialog dialog; private ImageView mIcon; private ImageView mVoice; private TextView mLabel; private Context context; /** * Constructing method * * @param context Activity level Context */ public DialogManager(Context context) { = context; } /** * Display the recording dialog box */ public void showRecordingDialog() { builder = new (context, ); LayoutInflater inflater = (context); View view = (.audio_recorder_dialog, null); mIcon = (.iv_dialog_icon); mVoice = (.iv_dialog_voice); mLabel = (.tv_dialog_label); (view); dialog = (); (); (false); } /** * Status when playing */ public void recording() { if (dialog != null && ()) { //Show status (); (); (); (.ic_audio_recorder); (.ic_audio_v1); (.audio_record_dialog_up_to_cancel); } } /** * Show the dialog box you want to cancel */ public void wantToCancel() { if (dialog != null && ()) { //Show status (); (); (); (.ic_audio_cancel); (.audio_record_dialog_release_to_cancel); } } /** * Display dialog box with too short time */ public void tooShort() { if (dialog != null && ()) { //Show status (); (); (); (.audio_record_dialog_too_short); } } // Show the canceled dialog box public void dismissDialog() { if (dialog != null && ()) { //Show status (); dialog = null; } } /** * Displays the dialog box for updating volume level * * @param level 1-7 */ public void updateVoiceLevel(int level) { if (dialog != null && ()) { //Show status (); (); (); int resId = ().getIdentifier("ic_audio_v" + level, "drawable", ()); (resId); } } public void updateTime(int time) { if (dialog != null && ()) { //Show status (); (); (); (time + "s"); } } }
2. AudioManager for recording management
/** * Function: Recording management */ public class AudioManager { private MediaRecorder mMediaRecorder; private String mDir; private String mCurrentFilePath; private static AudioManager mInstance; private boolean isPrepared; private AudioManager(String dir) { = dir; } //Singleton Mode: Instantiate AudioManager here and pass in the recording file address public static AudioManager getInstance(String dir) { if (mInstance == null) { synchronized () { if (mInstance == null) { mInstance = new AudioManager(dir); } } } return mInstance; } /** * Callback is ready */ public interface AudioStateListener { void wellPrepared(); } public AudioStateListener mListener; /** * Callback method */ public void setOnAudioStateListener(AudioStateListener listener) { mListener = listener; } /** * Prepare */ public void prepareAudio() { try { isPrepared = false; File dir = (mDir); String fileName = generateFileName(); File file = new File(dir, fileName); mCurrentFilePath = (); ("AudioManager").i("audio file name :" + mCurrentFilePath); mMediaRecorder = new MediaRecorder(); //Set the output file (mCurrentFilePath); //Set the audio source of MediaRecorder to microphone (); //Set audio format (.MPEG_4); //Set the audio format to AAC (); //Prepare for recording (); //start (); //Prepare to end isPrepared = true; if (mListener != null) { (); } } catch (Exception e) { (); } } /** * Name of randomly generated file */ private String generateFileName() { return ().toString() + ".m4a"; } public int getVoiceLevel(int maxLevel) { if (isPrepared) { try { //Get the maximum amplitude getMaxAmplitude() 1-32767 return maxLevel * () / 32768 + 1; } catch (Exception e) { } } return 1; } /** * Free up resources */ public void release() { if (mMediaRecorder != null) { (); (); mMediaRecorder = null; } } public void cancel() { release(); if (mCurrentFilePath != null) { File file = new File(mCurrentFilePath); (file); mCurrentFilePath = null; } } public String getCurrentFilePath() { return mCurrentFilePath; } }
3. Customize the recording button AudioRecorderButton
/** * Function: Recording button */ public class AudioRecorderButton extends AppCompatButton { private Context mContext; //Cancel the recording Y-axis displacement private static final int DISTANCE_Y_CANCEL = 80; //Maximum recording time limit private static final int AUDIO_RECORDER_MAX_TIME = 15; //Countdown time for recording private static final int AUDIO_RECORDER_COUNT_DOWN = 7; //state private static final int STATE_NORMAL = 1;// Default status private static final int STATE_RECORDING = 2;// Recording private static final int STATE_WANT_TO_CANCEL = 3;// Hope to cancel //Current status private int mCurrentState = STATE_NORMAL; //The recording has started private boolean isRecording = false; //Whether onLongClick is triggered private boolean mReady; private DialogManager mDialogManager; private AudioManager mAudioManager; private audioManager; public AudioRecorderButton(Context context) { this(context, null); } public AudioRecorderButton(Context context, AttributeSet attrs) { super(context, attrs); = context; mDialogManager = new DialogManager(context); audioManager = () (Context.AUDIO_SERVICE); String dir = ("Audios");//Create a folder mAudioManager = (dir); (new () { @Override public void wellPrepared() { (MSG_AUDIO_PREPARED); } }); //Press the button to prepare for recording, including start setOnLongClickListener(new OnLongClickListener() { @Override public boolean onLongClick(View v) { //First determine whether you have recording and storage permissions. If you have, start recording. If you have no permissions, apply for permissions. int hasAudioPermission = (mContext, .RECORD_AUDIO); int hasStoragePermission = (mContext, .WRITE_EXTERNAL_STORAGE); if (hasAudioPermission == PackageManager.PERMISSION_GRANTED && hasStoragePermission == PackageManager.PERMISSION_GRANTED) { mReady = true; (); } else { RxPermissions permissions = new RxPermissions((FragmentActivity) mContext); Disposable disposable = (.RECORD_AUDIO, .WRITE_EXTERNAL_STORAGE) .subscribe(new Consumer<Boolean>() { @Override public void accept(Boolean granted) { if (!granted) { ("The voice sending function requires recording and storage permissions"); } } }); } return true; } }); } private static final int MSG_AUDIO_PREPARED = 0X110; private static final int MSG_VOICE_CHANGED = 0X111; private static final int MSG_DIALOG_DISMISS = 0X112; private static final int MSG_TIME_OUT = 0x113; private static final int UPDATE_TIME = 0x114; private boolean mThreadFlag = false; //Recording time private float mTime; //Get the volume size Runnable private Runnable mGetVoiceLevelRunnable = new Runnable() { @Override public void run() { while (isRecording) { try { (100); mTime += 0.1f; (MSG_VOICE_CHANGED); if (mTime >= AUDIO_RECORDER_MAX_TIME) {//If the time exceeds 60 seconds, the recording will automatically end while (!mThreadFlag) {//The recording has been completed and the recording does not need to be completed again to avoid problems (); (); if (audioFinishRecorderListener != null) { //Call back first, then Reset, otherwise the time in the callback will be 0 (mTime, ()); (MSG_TIME_OUT); } mThreadFlag = !mThreadFlag; } isRecording = false; } else if (mTime >= AUDIO_RECORDER_COUNT_DOWN) { (UPDATE_TIME); } } catch (InterruptedException e) { (); } } } }; private Handler mHandler = new Handler(new () { @Override public boolean handleMessage(Message msg) { switch () { case MSG_AUDIO_PREPARED: (); isRecording = true; new Thread(mGetVoiceLevelRunnable).start(); break; case MSG_VOICE_CHANGED: ((7)); break; case MSG_DIALOG_DISMISS: (); break; case MSG_TIME_OUT: reset(); break; case UPDATE_TIME: int countDown = (int) (AUDIO_RECORDER_MAX_TIME - mTime); (countDown); break; } return true; } }); /** * Callback after recording is completed */ public interface AudioFinishRecorderListener { /** * @param seconds Duration * @param filePath file */ void onFinish(float seconds, String filePath); } private AudioFinishRecorderListener audioFinishRecorderListener; public void setAudioFinishRecorderListener(AudioFinishRecorderListener listener) { audioFinishRecorderListener = listener; } onAudioFocusChangeListener = new () { @Override public void onAudioFocusChange(int focusChange) { if (focusChange == .AUDIOFOCUS_LOSS) { (onAudioFocusChangeListener); } } }; public void myRequestAudioFocus() { (onAudioFocusChangeListener, .STREAM_MUSIC, .AUDIOFOCUS_GAIN_TRANSIENT); } @Override public boolean onTouchEvent(MotionEvent event) { ("AudioManager").i("x :" + () + "-Y:" + ()); switch (()) { case MotionEvent.ACTION_DOWN: mThreadFlag = false; isRecording = true; changeState(STATE_RECORDING); myRequestAudioFocus(); break; case MotionEvent.ACTION_MOVE: if (isRecording) { //Judge whether you want to cancel based on the coordinates of x and y if (() < 0 && (()) > DISTANCE_Y_CANCEL) { changeState(STATE_WANT_TO_CANCEL); } else { changeState(STATE_RECORDING); } } break; case MotionEvent.ACTION_UP: //If longClick does not trigger if (!mReady) { reset(); return (event); } //Triggered onLongClick not ready, but prepared has started //So eliminate folder if (!isRecording || mTime < 1.0f) { (); (); (MSG_DIALOG_DISMISS, 1000); } else if (mCurrentState == STATE_RECORDING) {//The normal recording ends (); (); if (audioFinishRecorderListener != null) { (mTime, ()); } } else if (mCurrentState == STATE_WANT_TO_CANCEL) { (); (); } reset(); (onAudioFocusChangeListener); break; } return (event); } /** * Recovery status Flag bit */ private void reset() { isRecording = false; mTime = 0; mReady = false; changeState(STATE_NORMAL); } /** * Change state */ private void changeState(int state) { if (mCurrentState != state) { mCurrentState = state; switch (state) { case STATE_NORMAL: setText(.audio_record_button_normal); break; case STATE_RECORDING: if (isRecording) { (); } setText(.audio_record_button_recording); break; case STATE_WANT_TO_CANCEL: (); setText(.audio_record_button_cancel); break; } } } }
4、DialogStyle
<!--App Base Theme--> <style name="AppThemeParent" parent=""> <!--The status bar does not display:22Before--> <item name="android:windowNoTitle">true</item> <item name="android:windowAnimationStyle">@style/ActivityAnimTheme</item><!--ActivityAnimation--> <item name="actionOverflowMenuStyle">@style/MenuStyle</item><!--toolbarMenu Style--> </style> <!--DialogStyleActivity--> <style name="ActivityDialogStyle" parent="AppThemeParent"> <item name="android:windowBackground">@android:color/transparent</item> <!-- Floating onActivityAbove --> <item name="android:windowIsFloating">true</item> <!-- frame --> <item name="android:windowFrame">@null</item> <!-- DialogBlur effect outside the area --> <item name="android:backgroundDimEnabled">true</item> <!-- translucent --> <item name="android:windowIsTranslucent">true</item> <!-- Dialog进入及退出Animation --> <item name="android:windowAnimationStyle">@style/ActivityDialogAnimation</item> </style> <!--Audio Recorder Dialog--> <style name="AudioRecorderDialogStyle" parent="ActivityDialogStyle"> <!-- DialogBlur effect outside the area --> <item name="android:backgroundDimEnabled">false</item> </style> <!-- DialogAnimation:Gradually in and out--> <style name="ActivityDialogAnimation" parent="@android:style/"> <item name="android:windowEnterAnimation">@anim/fade_in</item> <item name="android:windowExitAnimation">@anim/fade_out</item> </style>
5、DialogLayout
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:andro android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/audio_recorder_dialog_bg" android:gravity="center" android:orientation="vertical" android:padding="20dp"> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal"> <ImageView android: android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/ic_audio_recorder" /> <ImageView android: android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/ic_audio_v1" /> </LinearLayout> <TextView android: android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="15dp" android:text="@string/audio_record_dialog_up_to_cancel" android:textColor="@color/white" android:textSize="15dp" /> </LinearLayout>
6. Used strings
<!--AudioRecord--> <string name="audio_record_button_normal">Press and hold&#160;Talking</string><string name="audio_record_button_recording">release&#160;End</string><string name="audio_record_button_cancel">release手指&#160;Cancel send</string><string name="audio_record_dialog_up_to_cancel">Stroke on your fingers,Cancel send</string> <string name="audio_record_dialog_release_to_cancel">release手指,Cancel send</string> <string name="audio_record_dialog_too_short">Recording time is too short</string>
7. Use: The button style does not need to be written in a custom button, which is convenient for use
<.base_library. android: android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/audio_record_button_normal" /> AudioRecorderButton audioRecorderButton = findViewById(.btn_audio_recorder); (new () { @Override public void onFinish(float seconds, String filePath) { (seconds + "Second:" + filePath); } });
The above is all the content of this article. I hope it will be helpful to everyone's study and I hope everyone will support me more.