@@ -34,7 +34,9 @@ class AudioStreamerPlugin : FlutterPlugin, RequestPermissionsResultListener, Eve
3434
3535 // / Variables (i.e. will change value)
3636 private var eventSink: EventSink ? = null
37- private var recording = false
37+ @Volatile private var recording = false
38+ private var recordingThread: Thread ? = null
39+
3840
3941 private var currentActivity: Activity ? = null
4042
@@ -48,16 +50,19 @@ class AudioStreamerPlugin : FlutterPlugin, RequestPermissionsResultListener, Eve
4850 methodChannel.setMethodCallHandler {
4951 call, result ->
5052 if (call.method == " getSampleRate" ) {
51- // Sample rate never changes, so return the given sample rate.
52- result.success(audioRecord?.getSampleRate())
53+ if (::audioRecord.isInitialized) {
54+ result.success(audioRecord.sampleRate)
55+ } else {
56+ result.error(" UNAVAILABLE" , " AudioRecord not initialized." , null )
57+ }
5358 } else {
5459 result.notImplemented()
5560 }
5661 }
5762 }
5863
5964 override fun onDetachedFromEngine (@NonNull binding : FlutterPlugin .FlutterPluginBinding ) {
60- recording = false
65+ stopRecording()
6166 }
6267
6368 override fun onDetachedFromActivity () {
@@ -83,20 +88,19 @@ class AudioStreamerPlugin : FlutterPlugin, RequestPermissionsResultListener, Eve
8388 */
8489 override fun onListen (arguments : Any? , events : EventSink ? ) {
8590 this .eventSink = events
86- recording = true
8791 sampleRate = (arguments as Map <* , * >)[" sampleRate" ] as Int
8892 if (sampleRate < 4000 || sampleRate > 48000 ) {
8993 events!! .error(" SampleRateError" , " A sample rate of " + sampleRate + " Hz is not supported by Android." , null )
9094 return
9195 }
92- streamMicData ()
96+ startRecording ()
9397 }
9498
9599 /* *
96100 * Called from Flutter, which cancels the stream.
97101 */
98102 override fun onCancel (arguments : Any? ) {
99- recording = false
103+ stopRecording()
100104 }
101105
102106 /* *
@@ -105,11 +109,36 @@ class AudioStreamerPlugin : FlutterPlugin, RequestPermissionsResultListener, Eve
105109 override fun onRequestPermissionsResult (requestCode : Int , permissions : Array <String >, grantResults : IntArray ): Boolean {
106110 val requestAudioPermissionCode = 200
107111 when (requestCode) {
108- requestAudioPermissionCode -> if (grantResults[0 ] == PackageManager .PERMISSION_GRANTED ) return true
112+ requestAudioPermissionCode -> if (grantResults.isNotEmpty() && grantResults [0 ] == PackageManager .PERMISSION_GRANTED ) return true
109113 }
110114 return false
111115 }
112116
117+ private fun startRecording () {
118+ if (recording) {
119+ Log .w(logTag, " Recording is already in progress" )
120+ return
121+ }
122+ recording = true
123+ recordingThread = Thread { streamMicData() }
124+ recordingThread?.start()
125+ }
126+
127+ private fun stopRecording () {
128+ if (! recording) {
129+ Log .w(logTag, " Recording is not in progress" )
130+ return
131+ }
132+ recording = false
133+ try {
134+ recordingThread?.join()
135+ } catch (e: InterruptedException ) {
136+ e.printStackTrace()
137+ }
138+ recordingThread = null
139+ }
140+
141+
113142 /* *
114143 * Starts recording and streaming audio data from the mic.
115144 * Uses a buffer array of size 512. Whenever buffer is full, the content is sent to Flutter.
@@ -119,39 +148,51 @@ class AudioStreamerPlugin : FlutterPlugin, RequestPermissionsResultListener, Eve
119148 * https://www.newventuresoftware.com/blog/record-play-and-visualize-raw-audio-data-in-android
120149 */
121150 private fun streamMicData () {
122- Thread (
123- Runnable {
124- Process .setThreadPriority(Process .THREAD_PRIORITY_AUDIO )
125- val audioBuffer = ShortArray (bufferSize / 2 )
126- audioRecord = AudioRecord (
127- MediaRecorder .AudioSource .DEFAULT ,
128- sampleRate,
129- AudioFormat .CHANNEL_IN_MONO ,
130- AudioFormat .ENCODING_PCM_16BIT ,
131- bufferSize,
132- )
133- if (audioRecord.state != AudioRecord .STATE_INITIALIZED ) {
134- Log .e(logTag, " Audio Record can't initialize!" )
135- return @Runnable
136- }
137- /* * Start recording loop */
138- audioRecord.startRecording()
139- while (recording) {
140- /* * Read data into buffer */
141- audioRecord.read(audioBuffer, 0 , audioBuffer.size)
151+ Process .setThreadPriority(Process .THREAD_PRIORITY_AUDIO )
152+
153+ val audioBuffer = ShortArray (bufferSize / 2 )
154+ try {
155+ audioRecord = AudioRecord (
156+ MediaRecorder .AudioSource .DEFAULT ,
157+ sampleRate,
158+ AudioFormat .CHANNEL_IN_MONO ,
159+ AudioFormat .ENCODING_PCM_16BIT ,
160+ bufferSize,
161+ )
162+
163+ if (audioRecord.state != AudioRecord .STATE_INITIALIZED ) {
164+ Log .e(logTag, " Audio Record can't initialize!" )
165+ eventSink?.error(" MIC_ERROR" , " Audio Record can't initialize!" , null )
166+ recording = false
167+ return
168+ }
169+
170+ audioRecord.startRecording()
171+
172+ while (recording) {
173+ val readSize = audioRecord.read(audioBuffer, 0 , audioBuffer.size)
174+ if (readSize > 0 ) {
175+ val audioBufferList = ArrayList <Double >()
176+ for (i in 0 until readSize) {
177+ val normalizedImpulse = audioBuffer[i].toDouble() / maxAmplitude.toDouble()
178+ audioBufferList.add(normalizedImpulse)
179+ }
142180 Handler (Looper .getMainLooper()).post {
143- // / Convert to list in order to send via EventChannel.
144- val audioBufferList = ArrayList <Double >()
145- for (impulse in audioBuffer) {
146- val normalizedImpulse = impulse.toDouble() / maxAmplitude.toDouble()
147- audioBufferList.add(normalizedImpulse)
148- }
149- eventSink!! .success(audioBufferList)
181+ eventSink?.success(audioBufferList)
150182 }
151183 }
152- audioRecord.stop()
184+ }
185+ } catch (e: Exception ) {
186+ Log .e(logTag, " Error while recording audio" , e)
187+ eventSink?.error(" MIC_ERROR" , " Error while recording audio" , e.message)
188+ } finally {
189+ if (::audioRecord.isInitialized) {
190+ if (audioRecord.recordingState == AudioRecord .RECORDSTATE_RECORDING ) {
191+ audioRecord.stop()
192+ }
153193 audioRecord.release()
154- },
155- ).start()
194+ }
195+ recording = false
196+ }
156197 }
157- }
198+ }
0 commit comments