Voice Recorder is one of the most basic features of any smartphone and it has been there since and before smartphones came into existence. It is quite a simple utility which enables users to record their voice or any other sound for that matter and the recording gets stored locally and can be listened to any time in the future as per the requirement. So here is an Audio Recording in Android Example.
In this post, we are going to explore how to implement the voice recording feature in an android application. I hope you’re excited to learn it. So, without any further delay, let’s get started with it.
Table of Contents
Creating a new project
- Go ahead and create a new project by any name that you think is okay for this project.
- Now, as the files get loaded and the gradle is built, we’ll have activity_main.xml and MainActivity.java
- MainActivity will be configured as the main recording screen.
Importing images
- Before proceeding to the coding part, we need certain images in the upcoming screens. So, download the zip of images from below, extract it and move all the images to the drawables directory in your android project without changing their names.
[download id=”5273″ template=”images”]
Editing App Theme
- If you have seen the output video of this project, then you must have noticed that this project does not use the default action bar. Hence, we need to change the AppTheme to get rid of the default action bar . To do so, go to the values directory in the res directory and there you’ll find the styles.xml file. Update it as shown below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<resources> <!-- Base application theme. --> <style name="AppTheme" parent="Theme.AppCompat.NoActionBar"> <!-- Customize your theme here. --> <item name="colorPrimary">@android:color/white</item> <item name="colorPrimaryDark">@android:color/darker_gray</item> <item name="colorAccent">@android:color/holo_red_light</item> <item name="android:actionMenuTextColor">@android:color/holo_red_light</item> <item name="android:homeAsUpIndicator">@drawable/ic_arrow_back</item> </style> </resources> |
Importing RecyclerView library
- We’d be using recyclerView in this project to display the list of recordings. To import it, just copy the dependency given below into the dependencies section of your build.gradle file of your project and sync it.
1 2 3 |
compile 'com.android.support:recyclerview-v7:26.0.0-alpha1' |
Getting started with the screeens
- It’d be a pretty basic app with just two activities. One would have the record button to start the recording and other necessary buttons to stop and save the recording. And the other activity would be where the user would be able to view all the previous recordings and listen to them as well . So, let’s get started with the recording screen.
Recording Screen (MainActivity)
Designing the recording screen(The xml part )
MainActivity is the recording screen and in it we have a chronometer to be used as a timer when the recording is on, we also have a seekbar, then we have the play, pause, record and stop buttons to manage the recording procecss.
Below is the code for activity_main.xml. Update your activity_main.xml file with the code below to design your recording screen.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 |
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@android:color/white" tools:context="net.simplifiedcoding.voicerecorderapp.MainActivity"> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="wrap_content" /> <LinearLayout android:id="@+id/linearLayoutRecorder" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:layout_below="@+id/toolbar" android:layout_marginTop="20dp" > <Chronometer android:id="@+id/chronometerTimer" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="60sp" android:textColor="@android:color/darker_gray" android:layout_gravity="center_horizontal" /> <LinearLayout android:id="@+id/linearLayoutPlay" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:paddingStart="10dp" android:paddingEnd="10dp" android:visibility="gone" > <ImageView android:id="@+id/imageViewPlay" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/ic_play" android:layout_gravity="center_vertical" android:clickable="true" android:tint="@android:color/darker_gray" android:focusable="true" android:background="?android:attr/selectableItemBackground" /> <SeekBar android:id="@+id/seekBar" android:layout_width="match_parent" android:layout_weight="1" android:layout_gravity="center_vertical" android:layout_height="wrap_content" /> </LinearLayout> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" android:layout_gravity="center_horizontal" android:gravity="center_vertical" android:layout_marginTop="10dp" > <ImageView android:id="@+id/imageViewRecord" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/ic_microphone" android:background="?android:attr/selectableItemBackground" android:layout_marginEnd="10dp" android:clickable="true" android:focusable="true" /> <ImageView android:id="@+id/imageViewStop" android:layout_width="42dp" android:layout_height="42dp" android:src="@drawable/ic_stop" android:tint="@android:color/darker_gray" android:background="?android:attr/selectableItemBackground" android:layout_gravity="center_vertical" android:visibility="gone" android:clickable="true" android:focusable="true" /> </LinearLayout> </LinearLayout> </RelativeLayout> |
- In the above screen as the activity is displayed, we’ll see the timer(chronometer) with the record button. The play button and seekbar would remain hidden i.e. their visibility is set to gone before the recording is completed. Once the recording is done and the stop button is hit, the seekbar along with the play button would show up in order to play the currently recorded entry.
- The layout produced by the above xml code is shown below:-
Setting up the recording process ( The Java Part )
Configuring required permissions in Manifest and in runtime
- Before proceeding with the code of MainActivity.java , we need to mention certain permissions which are required for this project. To do so, go to AndroidManifest.xml and mention the permissions as shown below:-
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="net.simplifiedcoding.voicerecorderapp"> <!--Required Permissions--> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.RECORD_AUDIO" /> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name=".RecordingListActivity" /> </application> </manifest> |
- For marshmallow and above android versions, we need to accept the permissions in the runtime.
- To do so, go to MainActivity.java file and paste the code of two methods( getPermissionToRecordAudio() and onRequestPermissionsResult() ) given below:-
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
@RequiresApi(api = Build.VERSION_CODES.M) public void getPermissionToRecordAudio() { // 1) Use the support library version ContextCompat.checkSelfPermission(...) to avoid // checking the build version since Context.checkSelfPermission(...) is only available // in Marshmallow // 2) Always check for permission (even if permission has already been granted) // since the user can revoke permissions at any time through Settings if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED ) { // The permission is NOT already granted. // Check if the user has been asked about this permission already and denied // it. If so, we want to give more explanation about why the permission is needed. // Fire off an async request to actually get the permission // This will show the standard permission request dialog UI requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.RECORD_AUDIO, Manifest.permission.WRITE_EXTERNAL_STORAGE}, RECORD_AUDIO_REQUEST_CODE); } } // Callback with the request from calling requestPermissions(...) @RequiresApi(api = Build.VERSION_CODES.M) @Override public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) { // Make sure it's our original READ_CONTACTS request if (requestCode == RECORD_AUDIO_REQUEST_CODE) { if (grantResults.length == 3 && grantResults[0] == PackageManager.PERMISSION_GRANTED && grantResults[1] == PackageManager.PERMISSION_GRANTED && grantResults[2] == PackageManager.PERMISSION_GRANTED){ //Toast.makeText(this, "Record Audio permission granted", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(this, "You must give permissions to use this app. App is exiting.", Toast.LENGTH_SHORT).show(); finishAffinity(); } } } |
- As you paste this code in MainActivity.java file, you shall see some error like shown below:-
- To resolve this, just copy and the paste the line shown below in MainActivity.java at the top.
1 2 3 |
private int RECORD_AUDIO_REQUEST_CODE =123 ; |
- Now to finally ask permissions in the runtime, call the method  getPermissionToRecordAudio()  in the onCreate() method just as shown below:-
- Now, Run the app and if the device’s android version is marshmallow and above, you shall see a pop up asking for permissions as shown below:-
- Now, permissions are perfectly set up
Initializing Views
- Declare the following references as given below in the MainActivity.java at the top.
1 2 3 4 5 6 7 |
private Toolbar toolbar; private Chronometer chronometer; private ImageView imageViewRecord, imageViewPlay, imageViewStop; private SeekBar seekBar; private LinearLayout linearLayoutRecorder, linearLayoutPlay; |
- Next, copy the method initViews given below in MainActivity.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
private void initViews() { /** setting up the toolbar **/ toolbar = (Toolbar) findViewById(R.id.toolbar); toolbar.setTitle("Voice Recorder"); toolbar.setTitleTextColor(getResources().getColor(android.R.color.black)); setSupportActionBar(toolbar); linearLayoutRecorder = (LinearLayout) findViewById(R.id.linearLayoutRecorder); chronometer = (Chronometer) findViewById(R.id.chronometerTimer); chronometer.setBase(SystemClock.elapsedRealtime()); imageViewRecord = (ImageView) findViewById(R.id.imageViewRecord); imageViewStop = (ImageView) findViewById(R.id.imageViewStop); imageViewPlay = (ImageView) findViewById(R.id.imageViewPlay); linearLayoutPlay = (LinearLayout) findViewById(R.id.linearLayoutPlay); seekBar = (SeekBar) findViewById(R.id.seekBar); imageViewRecord.setOnClickListener(this); imageViewStop.setOnClickListener(this); imageViewPlay.setOnClickListener(this); } |
- As you paste intiViews() in MainActivity.java , you shall see an error as shown below:-
- To resolve this, make MainActivity.java implement View.OnClickListener as shown below:-
- Next, just add the onClick method shown below to MainActivity.java
1 2 3 4 5 6 |
@Override public void onClick(View view) { } |
- Next, Call the method initViews() in onCreate() below the method getPermissionToRecordAudio() as shown below:-
handling button Clicks
- We have three imageViews( imageViewPlay, imageViewStop, imageViewRecord ) in this screen(activity_main.xml ) to play, stop and record. It’s time to handle the clicks of these imageViews as the click listener have already been set.
- To begin with, let’s handle the click of imageViewRecord. To do so, first import the methods which would be called on the click of imageViewRecord. Copy the code of methods (prepareforRecording(), startRecording(), stopPlaying() ) given below and paste it in MainActivity.java.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
private void prepareforRecording() { TransitionManager.beginDelayedTransition(linearLayoutRecorder); imageViewRecord.setVisibility(View.GONE); imageViewStop.setVisibility(View.VISIBLE); linearLayoutPlay.setVisibility(View.GONE); } private void startRecording() { //we use the MediaRecorder class to record mRecorder = new MediaRecorder(); mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); mRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); /**In the lines below, we create a directory VoiceRecorderSimplifiedCoding/Audios in the phone storage * and the audios are being stored in the Audios folder **/ File root = android.os.Environment.getExternalStorageDirectory(); File file = new File(root.getAbsolutePath() + "/VoiceRecorderSimplifiedCoding/Audios"); if (!file.exists()) { file.mkdirs(); } fileName = root.getAbsolutePath() + "/VoiceRecorderSimplifiedCoding/Audios/" + String.valueOf(System.currentTimeMillis() + ".mp3"); Log.d("filename",fileName); mRecorder.setOutputFile(fileName); mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); try { mRecorder.prepare(); mRecorder.start(); } catch (IOException e) { e.printStackTrace(); } lastProgress = 0; seekBar.setProgress(0); stopPlaying(); //starting the chronometer chronometer.setBase(SystemClock.elapsedRealtime()); chronometer.start(); } private void stopPlaying() { try{ mPlayer.release(); }catch (Exception e){ e.printStackTrace(); } mPlayer = null; //showing the play button imageViewPlay.setImageResource(R.drawable.ic_play); chronometer.stop(); } |
- As you do, you shall get error just as shown below:-
- To resolve this, just copy the references given below at the top in the MainActivity.java.
1 2 3 4 5 6 7 8 |
private MediaRecorder mRecorder; private MediaPlayer mPlayer; private String fileName = null; private int lastProgress = 0; private Handler mHandler = new Handler(); private boolean isPlaying = false; |
- In the prepareRecording() method, we just make sure the only the record button is visible and the rest all buttons are hidden.
- The code of startRecording()Â method is quite self explanatory Just follow the comments.
- In the stopPlaying() method, we just stop the currently playing audio if it’s playing before the recording starts.
- Now, call the methods prepareRecording() and startRecording()Â in the onClick method just as shown below:-
1 2 3 4 5 6 7 8 9 |
@Override public void onClick(View view) { if( view == imageViewRecord ){ prepareforRecording(); startRecording(); } } |
- Next, we need to set the onClick functionality of imageViewStop to stop the recording.
- Â To do so, just import the methods( preapareforStop() and stopRecording() ) shown below into MainActivity.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
private void prepareforStop() { TransitionManager.beginDelayedTransition(linearLayoutRecorder); imageViewRecord.setVisibility(View.VISIBLE); imageViewStop.setVisibility(View.GONE); linearLayoutPlay.setVisibility(View.VISIBLE); } private void stopRecording() { try{ mRecorder.stop(); mRecorder.release(); }catch (Exception e){ e.printStackTrace(); } mRecorder = null; //starting the chronometer chronometer.stop(); chronometer.setBase(SystemClock.elapsedRealtime()); //showing the play button Toast.makeText(this, "Recording saved successfully.", Toast.LENGTH_SHORT).show(); } |
- In the prepareforStop() method, we just hide the stop button and make the play and record button visible with the seekbar.
- The code for stopRecording() method is quite self explanatory.
- Now, call the above methods in the OnClick method as shown below:-
1 2 3 4 5 6 7 8 9 10 11 12 |
@Override public void onClick(View view) { if( view == imageViewRecord ){ prepareforRecording(); startRecording(); }else if( view == imageViewStop ){ prepareforStop(); stopRecording(); } } |
- Next, we need to set the OnClick functionality of imageViewPlay. To do so, again we need to import certain methods.
- So, go ahead and import the methods ( startPlaying() and seekUpdation() ) into MainActivity.java as given below:-
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
private void startPlaying() { mPlayer = new MediaPlayer(); try { //fileName is global string. it contains the Uri to the recently recorded audio. mPlayer.setDataSource(fileName); mPlayer.prepare(); mPlayer.start(); } catch (IOException e) { Log.e("LOG_TAG", "prepare() failed"); } //making the imageview pause button imageViewPlay.setImageResource(R.drawable.ic_pause); seekBar.setProgress(lastProgress); mPlayer.seekTo(lastProgress); seekBar.setMax(mPlayer.getDuration()); seekUpdation(); chronometer.start(); /** once the audio is complete, timer is stopped here**/ mPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { @Override public void onCompletion(MediaPlayer mp) { imageViewPlay.setImageResource(R.drawable.ic_play); isPlaying = false; chronometer.stop(); } }); /** moving the track as per the seekBar's position**/ seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { if( mPlayer!=null && fromUser ){ //here the track's progress is being changed as per the progress bar mPlayer.seekTo(progress); //timer is being updated as per the progress of the seekbar chronometer.setBase(SystemClock.elapsedRealtime() - mPlayer.getCurrentPosition()); lastProgress = progress; } } @Override public void onStartTrackingTouch(SeekBar seekBar) { } @Override public void onStopTrackingTouch(SeekBar seekBar) { } }); } Runnable runnable = new Runnable() { @Override public void run() { seekUpdation(); } }; private void seekUpdation() { if(mPlayer != null){ int mCurrentPosition = mPlayer.getCurrentPosition() ; seekBar.setProgress(mCurrentPosition); lastProgress = mCurrentPosition; } mHandler.postDelayed(runnable, 100); } |
- In startPlaying() method above , we start the timer which progresses as per the recorded audio track. We also call the seekUpdation() method in it so that the seekBar progresses as per the track. It does this by calling a runnable (mentioned in the above code ) periodically in an interval of 100ms . Also, we have set up the OnSeekBarChangeListener for the seekbar so as to seek the audio track as per the seekbar .
- To understand the flow of seekBar being updated, refer the image given below along with the code, you’ll get it.
- To understand how the recording is being played, it’s very simple. You just need to declare the MediaPlayer object just like we have mPlayer in the MainActivity. It is initialized in the startPlaying method and the data to be played is fed to it through the Uri( fileName contains the path in startPlaying method ) i.e the complete local address to the audio file.
- Now, just call the methods above in the onClick for the imageViewPlay as shown below:-
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
@Override public void onClick(View view) { if( view == imageViewRecord ){ prepareforRecording(); startRecording(); }else if( view == imageViewStop ){ prepareforStop(); stopRecording(); }else if( view == imageViewPlay ){ if( !isPlaying && fileName != null ){ isPlaying = true; startPlaying(); }else{ isPlaying = false; stopPlaying(); } } } |
- On the click of imageViewPlay, if the audio is playing, then it shall stopPlaying the audio and vice-versa.
- Run the app now and you should be able to record, stop and play the recorded audio.
Recording List Screen
- Create a new activity by the name RecordingListActivity. As the gradle is built and the files get loaded, you’ll have the activity_recording_list.xml and RecordingListActivity.java.
Designing the recording list screen
- This xml file of this activity will mainly have a recyclerview( to display the list of recordings ) and a textView ( to show up if there’s no recordings yet ).
- Update your activity_recording_list.xml file with one shown below :-
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@android:color/white" tools:context="net.simplifiedcoding.voicerecorderapp.RecordingListActivity"> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="wrap_content" /> <android.support.v7.widget.RecyclerView android:id="@+id/recyclerViewRecordings" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_below="@+id/toolbar" /> <TextView android:id="@+id/textViewNoRecordings" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="No Recordings found." android:visibility="gone" android:layout_centerInParent="true" android:textColor="@android:color/darker_gray" /> </RelativeLayout> |
Setting up the Recording list screen
- Since the layout of this activity has a recyclerView, so now we need a model class for a recording and then we need an adapter for recordings.
Creating the model class
- So, go ahead and create a class by the name Recording.java and copy the code given below into it.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
package net.simplifiedcoding.voicerecorderapp; /** * Created by Manish on 10/3/2017. */ public class Recording { String Uri, fileName; boolean isPlaying = false; public Recording(String uri, String fileName, boolean isPlaying) { Uri = uri; this.fileName = fileName; this.isPlaying = isPlaying; } public String getUri() { return Uri; } public String getFileName() { return fileName; } public boolean isPlaying() { return isPlaying; } public void setPlaying(boolean playing){ this.isPlaying = playing; } } |
Creating the item layout for recyclerview
- For recyclerview, we need a layout in order to configure how each item would look in the list. To do so, create a new layout resource file by the name recording_item_layout.xml and copy the xml design given below into it.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@android:color/white" android:paddingTop="10dp" android:paddingBottom="10dp" android:paddingStart="10dp" > <ImageView android:id="@+id/imageViewPlay" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/ic_play" android:layout_gravity="center_vertical" android:clickable="true" android:tint="@android:color/darker_gray" android:focusable="true" android:background="?android:attr/selectableItemBackground" /> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:layout_toEndOf="@+id/imageViewPlay" android:layout_marginStart="30dp" > <TextView android:id="@+id/textViewRecordingname" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="18sp" android:text="Voice 001" android:textColor="@android:color/holo_red_light" android:textStyle="bold" /> <SeekBar android:id="@+id/seekBar" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="10dp" android:visibility="gone" /> <View android:layout_width="match_parent" android:layout_height="0.5dp" android:background="@android:color/darker_gray" android:layout_marginTop="15dp" android:layout_marginEnd="20dp" /> </LinearLayout> </RelativeLayout> |
- According to the above xml design, the item will have a play/pause imageview, a textview to display the name of the file and a seekbar to display the progress of the song.
Creating the RecyclerView Adapter
- Go ahead and create a class by the name RecordingAdapter and copy the code given below:-
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
package dev.android.manish.voicerecorderappsimplified; import android.content.Context; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import java.util.ArrayList; /** * Created by Manish on 10/8/2017. */ public class RecordingAdapter extends RecyclerView.Adapter<RecordingAdapter.ViewHolder>{ private ArrayList<Recording> recordingArrayList; private Context context; public RecordingAdapter(Context context, ArrayList<Recording> recordingArrayList){ this.context = context; this.recordingArrayList = recordingArrayList; } @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = LayoutInflater.from(context).inflate(R.layout.recording_item_layout,parent,false); return new ViewHolder(view); } @Override public void onBindViewHolder(ViewHolder holder, int position) { } @Override public int getItemCount() { return recordingArrayList.size(); } public class ViewHolder extends RecyclerView.ViewHolder { public ViewHolder(View itemView) { super(itemView); } } } |
- Go through the adapter code above and if you have implemented RecyclerView before , then you must have noticed that nothing has been done in the adapter yet. If you’re finding it diffcult to implement RecyclerView, then go through the tutorial for recyclerView in the link given below and then come back.
Android RecyclerView and CardView Tutorial
- Now update the ViewHolder class in the adapter as shown below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 |
public class ViewHolder extends RecyclerView.ViewHolder { ImageView imageViewPlay; SeekBar seekBar; TextView textViewName; private String recordingUri; private int lastProgress = 0; private Handler mHandler = new Handler(); ViewHolder holder; public ViewHolder(View itemView) { super(itemView); imageViewPlay = itemView.findViewById(R.id.imageViewPlay); seekBar = itemView.findViewById(R.id.seekBar); textViewName = itemView.findViewById(R.id.textViewRecordingname); } public void manageSeekBar(ViewHolder holder){ holder.seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { if( mPlayer!=null && fromUser ){ mPlayer.seekTo(progress); } } @Override public void onStartTrackingTouch(SeekBar seekBar) { } @Override public void onStopTrackingTouch(SeekBar seekBar) { } }); } private void markAllPaused() { for( int i=0; i < recordingArrayList.size(); i++ ){ recordingArrayList.get(i).setPlaying(false); recordingArrayList.set(i,recordingArrayList.get(i)); } notifyDataSetChanged(); } Runnable runnable = new Runnable() { @Override public void run() { seekUpdation(holder); } }; private void seekUpdation(ViewHolder holder) { this.holder = holder; if(mPlayer != null){ int mCurrentPosition = mPlayer.getCurrentPosition() ; holder.seekBar.setMax(mPlayer.getDuration()); holder.seekBar.setProgress(mCurrentPosition); lastProgress = mCurrentPosition; } mHandler.postDelayed(runnable, 100); } private void stopPlaying() { try{ mPlayer.release(); }catch (Exception e){ e.printStackTrace(); } mPlayer = null; isPlaying = false; } private void startPlaying(final Recording audio, final int position) { mPlayer = new MediaPlayer(); try { mPlayer.setDataSource(recordingUri); mPlayer.prepare(); mPlayer.start(); } catch (IOException e) { Log.e("LOG_TAG", "prepare() failed"); } //showing the pause button seekBar.setMax(mPlayer.getDuration()); isPlaying = true; mPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { @Override public void onCompletion(MediaPlayer mp) { audio.setPlaying(false); notifyItemChanged(position); } }); } } |
- In the above code, we just declared and initialized objects for the ui elements in the recording_item_layout.xml and some other variables which would be used later in this post. Then, we imported a list of methods which are quite self explanatory but still I’ll try add something to it. The list of method in the ViewHolder class is as follows:-
- manageSeekBar()- It’s purpose is to move the track as per the progress of the seekBar. For instance, if the seekBar is seeked to certain position, then the track is moved to that point accordingly and that’s what is done in this method. It’s no magic anyway. It just has OnSeekBarChangeListener.
- markAllPaused()- As the name suggests, it simply pauses all the audios in the list. This method is called when some audio is being played and then some other audio is tapped to play.
- seekUpdation()- Now you must be familiar with this method because we used it in the previous activity for the purpose of updating seekbar as the track progresses.
- startPlaying() and stopPlaying() – Of course, we used them too in the previous activity and you also know what they do.
- Next, we need to set an onClick listener to the imageViewPlay in the recording_item_layout.xml file. To do so, copy the code given below to the constructor of the ViewHolder class.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
imageViewPlay.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { int position = getAdapterPosition(); Recording recording = recordingArrayList.get(position); recordingUri = recording.getUri(); if( isPlaying ){ stopPlaying(); if( position == last_index ){ recording.setPlaying(false); stopPlaying(); notifyItemChanged(position); }else{ markAllPaused(); recording.setPlaying(true); notifyItemChanged(position); startPlaying(recording,position); last_index = position; } }else { if( recording.isPlaying() ){ recording.setPlaying(false); stopPlaying(); Log.d("isPlayin","True"); }else { startPlaying(recording,position); recording.setPlaying(true); seekBar.setMax(mPlayer.getDuration()); Log.d("isPlayin","False"); } notifyItemChanged(position); last_index = position; } } }); |
- Final ViewHolder class is here below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 |
public class ViewHolder extends RecyclerView.ViewHolder { ImageView imageViewPlay; SeekBar seekBar; TextView textViewName; private String recordingUri; private int lastProgress = 0; private Handler mHandler = new Handler(); ViewHolder holder; public ViewHolder(View itemView) { super(itemView); imageViewPlay = itemView.findViewById(R.id.imageViewPlay); seekBar = itemView.findViewById(R.id.seekBar); textViewName = itemView.findViewById(R.id.textViewRecordingname); imageViewPlay.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { int position = getAdapterPosition(); Recording recording = recordingArrayList.get(position); recordingUri = recording.getUri(); if( isPlaying ){ stopPlaying(); if( position == last_index ){ recording.setPlaying(false); stopPlaying(); notifyItemChanged(position); }else{ markAllPaused(); recording.setPlaying(true); notifyItemChanged(position); startPlaying(recording,position); last_index = position; } }else { startPlaying(recording,position); recording.setPlaying(true); seekBar.setMax(mPlayer.getDuration()); Log.d("isPlayin","False"); notifyItemChanged(position); last_index = position; } } }); } public void manageSeekBar(ViewHolder holder){ holder.seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { if( mPlayer!=null && fromUser ){ mPlayer.seekTo(progress); } } @Override public void onStartTrackingTouch(SeekBar seekBar) { } @Override public void onStopTrackingTouch(SeekBar seekBar) { } }); } private void markAllPaused() { for( int i=0; i < recordingArrayList.size(); i++ ){ recordingArrayList.get(i).setPlaying(false); recordingArrayList.set(i,recordingArrayList.get(i)); } notifyDataSetChanged(); } Runnable runnable = new Runnable() { @Override public void run() { seekUpdation(holder); } }; private void seekUpdation(ViewHolder holder) { this.holder = holder; if(mPlayer != null){ int mCurrentPosition = mPlayer.getCurrentPosition() ; holder.seekBar.setMax(mPlayer.getDuration()); holder.seekBar.setProgress(mCurrentPosition); lastProgress = mCurrentPosition; } mHandler.postDelayed(runnable, 100); } private void stopPlaying() { try{ mPlayer.release(); }catch (Exception e){ e.printStackTrace(); } mPlayer = null; isPlaying = false; } private void startPlaying(final Recording audio, final int position) { mPlayer = new MediaPlayer(); try { mPlayer.setDataSource(recordingUri); mPlayer.prepare(); mPlayer.start(); } catch (IOException e) { Log.e("LOG_TAG", "prepare() failed"); } //showing the pause button seekBar.setMax(mPlayer.getDuration()); isPlaying = true; mPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { @Override public void onCompletion(MediaPlayer mp) { audio.setPlaying(false); notifyItemChanged(position); } }); } } |
Configuring onBindViewHolder
- To give a final touch to this adapter we just need to set up the OnBindViewHolder method. To do so, import the method shown below in the RecordingAdapter.java class.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
private void setUpData(ViewHolder holder, int position) { Recording recording = recordingArrayList.get(position); holder.textViewName.setText(recording.getFileName()); if( recording.isPlaying() ){ holder.imageViewPlay.setImageResource(R.drawable.ic_pause); TransitionManager.beginDelayedTransition((ViewGroup) holder.itemView); holder.seekBar.setVisibility(View.VISIBLE); holder.seekUpdation(holder); }else{ holder.imageViewPlay.setImageResource(R.drawable.ic_play); TransitionManager.beginDelayedTransition((ViewGroup) holder.itemView); holder.seekBar.setVisibility(View.GONE); } holder.manageSeekBar(holder); } |
- In the above code, we set the data to items as per their position. Now call this method in the OnBindViewHolder just as shown below:-
- Now the adapter is complete and I know you’re struggling to understand the flow of the adapter. Never mind . I’ll try to simplify it in the later course of the post.
- Here’s the final code of RecordingAdapter.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 |
package dev.android.manish.voicerecorderappsimplified; import android.content.Context; import android.media.MediaPlayer; import android.os.Handler; import android.support.v7.widget.RecyclerView; import android.transition.TransitionManager; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.SeekBar; import android.widget.TextView; import java.io.IOException; import java.util.ArrayList; /** * Created by Manish on 10/8/2017. */ public class RecordingAdapter extends RecyclerView.Adapter<RecordingAdapter.ViewHolder>{ private ArrayList<Recording> recordingArrayList; private Context context; private MediaPlayer mPlayer; private boolean isPlaying = false; private int last_index = -1; public RecordingAdapter(Context context, ArrayList<Recording> recordingArrayList){ this.context = context; this.recordingArrayList = recordingArrayList; } @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = LayoutInflater.from(context).inflate(R.layout.recording_item_layout,parent,false); return new ViewHolder(view); } @Override public void onBindViewHolder(ViewHolder holder, int position) { setUpData(holder,position); } @Override public int getItemCount() { return recordingArrayList.size(); } private void setUpData(ViewHolder holder, int position) { Recording recording = recordingArrayList.get(position); holder.textViewName.setText(recording.getFileName()); if( recording.isPlaying() ){ holder.imageViewPlay.setImageResource(R.drawable.ic_pause); TransitionManager.beginDelayedTransition((ViewGroup) holder.itemView); holder.seekBar.setVisibility(View.VISIBLE); holder.seekUpdation(holder); }else{ holder.imageViewPlay.setImageResource(R.drawable.ic_play); TransitionManager.beginDelayedTransition((ViewGroup) holder.itemView); holder.seekBar.setVisibility(View.GONE); } holder.manageSeekBar(holder); } public class ViewHolder extends RecyclerView.ViewHolder { ImageView imageViewPlay; SeekBar seekBar; TextView textViewName; private String recordingUri; private int lastProgress = 0; private Handler mHandler = new Handler(); ViewHolder holder; public ViewHolder(View itemView) { super(itemView); imageViewPlay = itemView.findViewById(R.id.imageViewPlay); seekBar = itemView.findViewById(R.id.seekBar); textViewName = itemView.findViewById(R.id.textViewRecordingname); imageViewPlay.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { int position = getAdapterPosition(); Recording recording = recordingArrayList.get(position); recordingUri = recording.getUri(); if( isPlaying ){ stopPlaying(); if( position == last_index ){ recording.setPlaying(false); stopPlaying(); notifyItemChanged(position); }else{ markAllPaused(); recording.setPlaying(true); notifyItemChanged(position); startPlaying(recording,position); last_index = position; } }else { startPlaying(recording,position); recording.setPlaying(true); seekBar.setMax(mPlayer.getDuration()); Log.d("isPlayin","False"); notifyItemChanged(position); last_index = position; } } }); } public void manageSeekBar(ViewHolder holder){ holder.seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { if( mPlayer!=null && fromUser ){ mPlayer.seekTo(progress); } } @Override public void onStartTrackingTouch(SeekBar seekBar) { } @Override public void onStopTrackingTouch(SeekBar seekBar) { } }); } private void markAllPaused() { for( int i=0; i < recordingArrayList.size(); i++ ){ recordingArrayList.get(i).setPlaying(false); recordingArrayList.set(i,recordingArrayList.get(i)); } notifyDataSetChanged(); } Runnable runnable = new Runnable() { @Override public void run() { seekUpdation(holder); } }; private void seekUpdation(ViewHolder holder) { this.holder = holder; if(mPlayer != null){ int mCurrentPosition = mPlayer.getCurrentPosition() ; holder.seekBar.setMax(mPlayer.getDuration()); holder.seekBar.setProgress(mCurrentPosition); lastProgress = mCurrentPosition; } mHandler.postDelayed(runnable, 100); } private void stopPlaying() { try{ mPlayer.release(); }catch (Exception e){ e.printStackTrace(); } mPlayer = null; isPlaying = false; } private void startPlaying(final Recording audio, final int position) { mPlayer = new MediaPlayer(); try { mPlayer.setDataSource(recordingUri); mPlayer.prepare(); mPlayer.start(); } catch (IOException e) { Log.e("LOG_TAG", "prepare() failed"); } //showing the pause button seekBar.setMax(mPlayer.getDuration()); isPlaying = true; mPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { @Override public void onCompletion(MediaPlayer mp) { audio.setPlaying(false); notifyItemChanged(position); } }); } } } |
Configuring the RecordingListActivity.java
- In this class, our sole aim is to fetch the Recordings from the storage, parse it in the form of ArrayList and hence set Adapter to recyclerView.
- Now to begin with, just copy the object declarations given below to the RecordingListActivity.java at the top.
1 2 3 4 5 6 7 |
private Toolbar toolbar; private RecyclerView recyclerViewRecordings; private ArrayList<Recording> recordingArraylist; private RecordingAdapter recordingAdapter; private TextView textViewNoRecordings; |
- Next, import the method initViews() from below in the RecordingListActivity.java to initialize the views in activity_recording_list.xml.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
private void initViews() { recordingArraylist = new ArrayList<Recording>(); /** setting up the toolbar **/ toolbar = (Toolbar) findViewById(R.id.toolbar); toolbar.setTitle("Recording List"); toolbar.setTitleTextColor(getResources().getColor(android.R.color.black)); setSupportActionBar(toolbar); /** enabling back button ***/ getSupportActionBar().setDisplayHomeAsUpEnabled(true); /** setting up recyclerView **/ recyclerViewRecordings = (RecyclerView) findViewById(R.id.recyclerViewRecordings); recyclerViewRecordings.setLayoutManager(new LinearLayoutManager(this,LinearLayoutManager.VERTICAL, false)); recyclerViewRecordings.setHasFixedSize(true); textViewNoRecordings = (TextView) findViewById(R.id.textViewNoRecordings); } |
- Now call the above method in onCreateMethod as shown below:-
- Now import the method ( fetchRecordings())Â in which all the recordings would be fetched and data would be parsed into ArrayList and also the method( setAdaptertoRecyclerView() ) in which the adapter is being set to the recyclerView.
-
Here is the code for fetchRecordings():-
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
private void fetchRecordings() { File root = android.os.Environment.getExternalStorageDirectory(); String path = root.getAbsolutePath() + "/VoiceRecorderSimplifiedCoding/Audios"; Log.d("Files", "Path: " + path); File directory = new File(path); File[] files = directory.listFiles(); Log.d("Files", "Size: "+ files.length); if( files!=null ){ for (int i = 0; i < files.length; i++) { Log.d("Files", "FileName:" + files[i].getName()); String fileName = files[i].getName(); String recordingUri = root.getAbsolutePath() + "/VoiceRecorderSimplifiedCoding/Audios/" + fileName; Recording recording = new Recording(recordingUri,fileName,false); recordingArraylist.add(recording); } textViewNoRecordings.setVisibility(View.GONE); recyclerViewRecordings.setVisibility(View.VISIBLE); setAdaptertoRecyclerView(); }else{ textViewNoRecordings.setVisibility(View.VISIBLE); recyclerViewRecordings.setVisibility(View.GONE); } } |
-
Here is the code for setAdaptertoRecyclerView():-
1 2 3 4 5 6 |
private void setAdaptertoRecyclerView() { recordingAdapter = new RecordingAdapter(this,recordingArraylist); recyclerViewRecordings.setAdapter(recordingAdapter); } |
- Next, just call the method fetchRecordings() in onCreate method below initViews just as shown below:-
- Recording list screen is almost complete. We just need to set the action of the back button in the toolbar. To do so, copy the method given below to the RecordingListActivity.java file
12345678910111213141516@Overridepublic boolean onOptionsItemSelected(MenuItem item) {switch (item.getItemId()){case android.R.id.home:this.finish();return true;default:return super.onOptionsItemSelected(item);}} - Here’s the final code for RecordingListActivity.java.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 |
package dev.android.manish.voicerecorderappsimplified; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.Toolbar; import android.util.Log; import android.view.MenuItem; import android.view.View; import android.widget.TextView; import java.io.File; import java.util.ArrayList; public class RecordingListActivity extends AppCompatActivity { private Toolbar toolbar; private RecyclerView recyclerViewRecordings; private ArrayList<Recording> recordingArraylist; private RecordingAdapter recordingAdapter; private TextView textViewNoRecordings; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_recording_list); initViews(); fetchRecordings(); } private void initViews() { recordingArraylist = new ArrayList<Recording>(); /** setting up the toolbar **/ toolbar = (Toolbar) findViewById(R.id.toolbar); toolbar.setTitle("Recording List"); toolbar.setTitleTextColor(getResources().getColor(android.R.color.black)); setSupportActionBar(toolbar); /** enabling back button ***/ getSupportActionBar().setDisplayHomeAsUpEnabled(true); /** setting up recyclerView **/ recyclerViewRecordings = (RecyclerView) findViewById(R.id.recyclerViewRecordings); recyclerViewRecordings.setLayoutManager(new LinearLayoutManager(this,LinearLayoutManager.VERTICAL, false)); recyclerViewRecordings.setHasFixedSize(true); textViewNoRecordings = (TextView) findViewById(R.id.textViewNoRecordings); } private void fetchRecordings() { File root = android.os.Environment.getExternalStorageDirectory(); String path = root.getAbsolutePath() + "/VoiceRecorderSimplifiedCoding/Audios"; Log.d("Files", "Path: " + path); File directory = new File(path); File[] files = directory.listFiles(); Log.d("Files", "Size: "+ files.length); if( files!=null ){ for (int i = 0; i < files.length; i++) { Log.d("Files", "FileName:" + files[i].getName()); String fileName = files[i].getName(); String recordingUri = root.getAbsolutePath() + "/VoiceRecorderSimplifiedCoding/Audios/" + fileName; Recording recording = new Recording(recordingUri,fileName,false); recordingArraylist.add(recording); } textViewNoRecordings.setVisibility(View.GONE); recyclerViewRecordings.setVisibility(View.VISIBLE); setAdaptertoRecyclerView(); }else{ textViewNoRecordings.setVisibility(View.VISIBLE); recyclerViewRecordings.setVisibility(View.GONE); } } private void setAdaptertoRecyclerView() { recordingAdapter = new RecordingAdapter(this,recordingArraylist); recyclerViewRecordings.setAdapter(recordingAdapter); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()){ case android.R.id.home: this.finish(); return true; default: return super.onOptionsItemSelected(item); } } } |
- That’s it. The recording list activity is all set. We just need to hook it to the MainActivity.java. We’ll do it by using options menu in MainActivity on click of which it shall go to recordinglist activity.
- To do so, in the res folder, create a diretory by the name menu. Now in the menu folder, create a menu resource file of the name list_menu.xml and copy the xml from below into it.:-
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <item android:id="@+id/item_list" android:title="List" app:showAsAction="always|withText" /> </menu> |
- Now, go to MainActivity.java and import the methods given below. These methods will initialize options menu and handle the click actions.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
@Override public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.list_menu,menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()){ case R.id.item_list: Intent intent = new Intent(this, RecordingListActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent); return true; default: return super.onOptionsItemSelected(item); } } |
- Here’s the final code for MainActivity.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 |
package dev.android.manish.voicerecorderappsimplified; import android.Manifest; import android.content.Intent; import android.content.pm.PackageManager; import android.media.MediaPlayer; import android.media.MediaRecorder; import android.os.Build; import android.os.Handler; import android.os.SystemClock; import android.support.annotation.NonNull; import android.support.annotation.RequiresApi; import android.support.v4.content.ContextCompat; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.support.v7.widget.Toolbar; import android.transition.TransitionManager; import android.util.Log; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.widget.Chronometer; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.SeekBar; import android.widget.Toast; import java.io.File; import java.io.IOException; public class MainActivity extends AppCompatActivity implements View.OnClickListener { private int RECORD_AUDIO_REQUEST_CODE =123 ; private Toolbar toolbar; private Chronometer chronometer; private ImageView imageViewRecord, imageViewPlay, imageViewStop; private SeekBar seekBar; private LinearLayout linearLayoutRecorder, linearLayoutPlay; private MediaRecorder mRecorder; private MediaPlayer mPlayer; private String fileName = null; private int lastProgress = 0; private Handler mHandler = new Handler(); private boolean isPlaying = false; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { getPermissionToRecordAudio(); } //initializingViews initViews(); } private void initViews() { /** setting up the toolbar **/ toolbar = (Toolbar) findViewById(R.id.toolbar); toolbar.setTitle("Voice Recorder"); toolbar.setTitleTextColor(getResources().getColor(android.R.color.black)); setSupportActionBar(toolbar); linearLayoutRecorder = (LinearLayout) findViewById(R.id.linearLayoutRecorder); chronometer = (Chronometer) findViewById(R.id.chronometerTimer); chronometer.setBase(SystemClock.elapsedRealtime()); imageViewRecord = (ImageView) findViewById(R.id.imageViewRecord); imageViewStop = (ImageView) findViewById(R.id.imageViewStop); imageViewPlay = (ImageView) findViewById(R.id.imageViewPlay); linearLayoutPlay = (LinearLayout) findViewById(R.id.linearLayoutPlay); seekBar = (SeekBar) findViewById(R.id.seekBar); imageViewRecord.setOnClickListener(this); imageViewStop.setOnClickListener(this); imageViewPlay.setOnClickListener(this); } @RequiresApi(api = Build.VERSION_CODES.M) public void getPermissionToRecordAudio() { // 1) Use the support library version ContextCompat.checkSelfPermission(...) to avoid // checking the build version since Context.checkSelfPermission(...) is only available // in Marshmallow // 2) Always check for permission (even if permission has already been granted) // since the user can revoke permissions at any time through Settings if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED ) { // The permission is NOT already granted. // Check if the user has been asked about this permission already and denied // it. If so, we want to give more explanation about why the permission is needed. // Fire off an async request to actually get the permission // This will show the standard permission request dialog UI requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.RECORD_AUDIO, Manifest.permission.WRITE_EXTERNAL_STORAGE}, RECORD_AUDIO_REQUEST_CODE); } } // Callback with the request from calling requestPermissions(...) @RequiresApi(api = Build.VERSION_CODES.M) @Override public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) { // Make sure it's our original READ_CONTACTS request if (requestCode == RECORD_AUDIO_REQUEST_CODE) { if (grantResults.length == 3 && grantResults[0] == PackageManager.PERMISSION_GRANTED && grantResults[1] == PackageManager.PERMISSION_GRANTED && grantResults[2] == PackageManager.PERMISSION_GRANTED){ //Toast.makeText(this, "Record Audio permission granted", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(this, "You must give permissions to use this app. App is exiting.", Toast.LENGTH_SHORT).show(); finishAffinity(); } } } @Override public void onClick(View view) { if( view == imageViewRecord ){ prepareforRecording(); startRecording(); }else if( view == imageViewStop ){ prepareforStop(); stopRecording(); }else if( view == imageViewPlay ){ if( !isPlaying && fileName != null ){ isPlaying = true; startPlaying(); }else{ isPlaying = false; stopPlaying(); } } } private void prepareforRecording() { TransitionManager.beginDelayedTransition(linearLayoutRecorder); imageViewRecord.setVisibility(View.GONE); imageViewStop.setVisibility(View.VISIBLE); linearLayoutPlay.setVisibility(View.GONE); } private void startRecording() { //we use the MediaRecorder class to record mRecorder = new MediaRecorder(); mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); mRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); /**In the lines below, we create a directory named VoiceRecorderSimplifiedCoding/Audios in the phone storage * and the audios are being stored in the Audios folder **/ File root = android.os.Environment.getExternalStorageDirectory(); File file = new File(root.getAbsolutePath() + "/VoiceRecorderSimplifiedCoding/Audios"); if (!file.exists()) { file.mkdirs(); } fileName = root.getAbsolutePath() + "/VoiceRecorderSimplifiedCoding/Audios/" + String.valueOf(System.currentTimeMillis() + ".mp3"); Log.d("filename",fileName); mRecorder.setOutputFile(fileName); mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); try { mRecorder.prepare(); mRecorder.start(); } catch (IOException e) { e.printStackTrace(); } lastProgress = 0; seekBar.setProgress(0); stopPlaying(); //starting the chronometer chronometer.setBase(SystemClock.elapsedRealtime()); chronometer.start(); } private void stopPlaying() { try{ mPlayer.release(); }catch (Exception e){ e.printStackTrace(); } mPlayer = null; //showing the play button imageViewPlay.setImageResource(R.drawable.ic_play); chronometer.stop(); } private void prepareforStop() { TransitionManager.beginDelayedTransition(linearLayoutRecorder); imageViewRecord.setVisibility(View.VISIBLE); imageViewStop.setVisibility(View.GONE); linearLayoutPlay.setVisibility(View.VISIBLE); } private void stopRecording() { try{ mRecorder.stop(); mRecorder.release(); }catch (Exception e){ e.printStackTrace(); } mRecorder = null; //starting the chronometer chronometer.stop(); chronometer.setBase(SystemClock.elapsedRealtime()); //showing the play button Toast.makeText(this, "Recording saved successfully.", Toast.LENGTH_SHORT).show(); } private void startPlaying() { mPlayer = new MediaPlayer(); Log.d("instartPlaying",fileName); try { mPlayer.setDataSource(fileName); mPlayer.prepare(); mPlayer.start(); } catch (IOException e) { Log.e("LOG_TAG", "prepare() failed"); } //making the imageview pause button imageViewPlay.setImageResource(R.drawable.ic_pause); seekBar.setProgress(lastProgress); mPlayer.seekTo(lastProgress); seekBar.setMax(mPlayer.getDuration()); seekUpdation(); chronometer.start(); /** once the audio is complete, timer is stopped here**/ mPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { @Override public void onCompletion(MediaPlayer mp) { imageViewPlay.setImageResource(R.drawable.ic_play); isPlaying = false; chronometer.stop(); } }); /** moving the track as per the seekBar's position**/ seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { if( mPlayer!=null && fromUser ){ //here the track's progress is being changed as per the progress bar mPlayer.seekTo(progress); //timer is being updated as per the progress of the seekbar chronometer.setBase(SystemClock.elapsedRealtime() - mPlayer.getCurrentPosition()); lastProgress = progress; } } @Override public void onStartTrackingTouch(SeekBar seekBar) { } @Override public void onStopTrackingTouch(SeekBar seekBar) { } }); } Runnable runnable = new Runnable() { @Override public void run() { seekUpdation(); } }; private void seekUpdation() { if(mPlayer != null){ int mCurrentPosition = mPlayer.getCurrentPosition() ; seekBar.setProgress(mCurrentPosition); lastProgress = mCurrentPosition; } mHandler.postDelayed(runnable, 100); } @Override public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.list_menu,menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()){ case R.id.item_list: Intent intent = new Intent(this, RecordingListActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent); return true; default: return super.onOptionsItemSelected(item); } } } |
- Now run the app and you shall see an option by the name List in the top right corner as shown below:-
- Click on the LIST option and it shall take you to the RecordingListActivity as shown below:-
- Hit the play button on any item and the audio shall begin to play. That’s it. Your voice recorder app is all set.
- Hurrah! You made your own voice recorder app.
- If you’re interested in understanding the flow in RecordingAdapter, then continue reading the post.
Understanding the flow in RecordingAdapter
- Alright, in the Recording.java model class, we have the three parameters viz. Uri, name and isPlaying . Uri contains the path of audio file, name is simply the name of the audio file and isPlaying is to check if it is being played currently or not.
- Now in the setUpdata() method in the adapter, we check if the current item is being played. If it is being played, then the button in the form of imageview is set to be a pause button and vice versa. If it is playing currently, then we also call the seekUpdation() method in the ViewHolder so as to update the seekbar as per the progress of the audio file being played. Also, we call the method manageSeekbar in ViewHolder which moves the track being played as per the seekbar’s progress.
- Look into the constructor of the ViewHolder class in RecordingAdapter and you shall see OnClick listener has been set to the imageViewPlay. In the onClick method, we get position of item clicked so as to get the Uri which is further used to play it. Now here, we use a boolean by the name isPlaying which is global variable in the class ViewHolder. If any audio is being played, global isPlaying is true and vice versa. Similary, a global variable by the name last_index has been maintained which contains the index of the last item played.
- Consider an audio file having certain position in Arraylist is already playing and hence it’s positon will be copied to last_index. If now imageViewPlay which is now a pause button for this audio file is hit, it’ll go to the onClick method. In the onClick method, isPlaying would be true and also position== last_index and hence the playing will simply stop. If the position would have been different from last_index, then that would have been the case of clicking some other item. In that case too, the audio is stopped but it is to be updated in the data so that the imageViewplay again becomes a play button. To do so, we have the markAllPaused() in which we run a loop and mark isPlaying for all the items set to false. Thence, the recording which was clicked is played.
- I hope you got the point what I tried to explain here. If not, feel free to ping me in the comments.
- Looking for source code? Find it in the link below.
[sociallocker id=1372] Android Voice Recorder App Source Code [/sociallocker]
So thats all for this Audio Recording in Android Example. I hope you found it useful, and if you think it is useful then please give us a SHARE. Thank You 🙂