Skip to content

Commit 06f327e

Browse files
✨ Added onRecorderStateChanged stream, onCurrentDuration stream and recordedDuration to get recorded duration for android
1 parent 8f97a05 commit 06f327e

File tree

9 files changed

+109
-19
lines changed

9 files changed

+109
-19
lines changed

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
## 1.0.2 (Unreleased)
22

33
- Now, calling `stopAllPlayers` is not mandatory for disposing streams and it will also not dispose controller. With last remaining player they will be disposed (Streams can be re-initialised by creating a new PlayerController).
4-
- Added legacy normalization with this fixed [#144](https://github.com/SimformSolutionsPvtLtd/audio_waveforms/issues/144).
4+
- Added legacy normalization with this fixed [#144](https://github.com/SimformSolutionsPvtLtd/audio_waveforms/issues/144).
5+
- Added `onRecorderStateChanged` stream to monitor Recorder state changes.
6+
- Added `onCurrentDuration` stream to get latest recorded audio duration
57

68
## 1.0.1
79

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,9 @@ controller.androidOutputFormat = AndroidOutputFormat.mpeg4; // Changing and
9999
controller.iosEncoder = IosEncoder.kAudioFormatMPEG4AAC; // Changing ios encoder
100100
controller.sampleRate = 44100; // Updating sample rate
101101
controller.bitRate = 48000; // Updating bitrate
102+
controller.onRecorderStateChanged.listen((state){}); // Listening to recorder state changes
103+
controller.onCurrentDuration.listen((duration){}); // Listening to current duration updates
104+
controller.recordedDuration; // Get recorded audio duration
102105
controller.currentScrolledDuration; // Current duration position notifier
103106
```
104107

android/src/main/kotlin/com/simform/audio_waveforms/AudioRecorder.kt

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package com.simform.audio_waveforms
33
import android.Manifest
44
import android.app.Activity
55
import android.content.pm.PackageManager
6+
import android.media.MediaMetadataRetriever
7+
import android.media.MediaMetadataRetriever.METADATA_KEY_DURATION
68
import android.media.MediaRecorder
79
import android.os.Build
810
import android.util.Log
@@ -20,6 +22,8 @@ private const val RECORD_AUDIO_REQUEST_CODE = 1001
2022
class AudioRecorder : PluginRegistry.RequestPermissionsResultListener {
2123
private var permissions = arrayOf(Manifest.permission.RECORD_AUDIO)
2224
private var useLegacyNormalization = false
25+
private var mediaMetadataRetriever = MediaMetadataRetriever()
26+
private var audioInfoArrayList = ArrayList<String>()
2327

2428
fun getDecibel(result: MethodChannel.Result, recorder: MediaRecorder?) {
2529
if (useLegacyNormalization) {
@@ -68,12 +72,25 @@ class AudioRecorder : PluginRegistry.RequestPermissionsResultListener {
6872
reset()
6973
release()
7074
}
71-
result.success(path)
75+
val duration = getDuration(path)
76+
audioInfoArrayList.add(path)
77+
audioInfoArrayList.add(duration)
78+
result.success(audioInfoArrayList)
7279
} catch (e: IllegalStateException) {
7380
Log.e(LOG_TAG, "Failed to stop recording")
7481
}
7582
}
7683

84+
private fun getDuration(path: String): String {
85+
mediaMetadataRetriever.setDataSource(path)
86+
val duration = mediaMetadataRetriever.extractMetadata(METADATA_KEY_DURATION)
87+
return duration ?: "-1"
88+
}
89+
90+
fun releaseMetaDataRetriever(){
91+
mediaMetadataRetriever.release()
92+
}
93+
7794
fun startRecorder(result: MethodChannel.Result, recorder: MediaRecorder?, useLegacy: Boolean) {
7895
try {
7996
useLegacyNormalization = useLegacy

android/src/main/kotlin/com/simform/audio_waveforms/AudioWaveformsPlugin.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,14 +55,15 @@ class AudioWaveformsPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
5555
checkPathAndInitialiseRecorder(result, encoder, outputFormat, sampleRate, bitRate)
5656
}
5757
Constants.startRecording -> {
58-
var useLegacyNormalization =
58+
val useLegacyNormalization =
5959
(call.argument(Constants.useLegacyNormalization) as Boolean?) ?: false
6060
audioRecorder.startRecorder(result, recorder, useLegacyNormalization)
6161
}
6262
Constants.stopRecording -> {
6363
audioRecorder.stopRecording(result, recorder, path!!)
6464
recorder = null
6565
}
66+
Constants.releaseMetaDataRetriever -> audioRecorder.releaseMetaDataRetriever()
6667
Constants.pauseRecording -> audioRecorder.pauseRecording(result, recorder)
6768
Constants.resumeRecording -> audioRecorder.resumeRecording(result, recorder)
6869
Constants.getDecibel -> audioRecorder.getDecibel(result, recorder)

android/src/main/kotlin/com/simform/audio_waveforms/Utils.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ object Constants {
6262
const val onCurrentExtractedWaveformData = "onCurrentExtractedWaveformData"
6363
const val waveformData = "waveformData"
6464
const val useLegacyNormalization = "useLegacyNormalization"
65+
const val releaseMetaDataRetriever = "releaseMetaDataRetriever"
6566
}
6667

6768
enum class FinishMode(val value:Int) {

example/lib/main.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ class _HomeState extends State<Home> {
5353
}
5454

5555
void _initialiseControllers() {
56-
recorderController = RecorderController(useLegacyNormalization: true)
56+
recorderController = RecorderController()
5757
..androidEncoder = AndroidEncoder.aac
5858
..androidOutputFormat = AndroidOutputFormat.mpeg4
5959
..iosEncoder = IosEncoder.kAudioFormatMPEG4AAC

lib/src/base/audio_waveforms_interface.dart

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,10 @@ class AudioWaveformsInterface {
6363
}
6464

6565
///platform call to stop recording
66-
Future<String?> stop() async {
67-
final isRecording =
66+
Future<List<String?>?> stop() async {
67+
final audioInfo =
6868
await _methodChannel.invokeMethod(Constants.stopRecording);
69-
return isRecording;
69+
return List.from(audioInfo ?? []);
7070
}
7171

7272
///platform call to resume recording.
@@ -90,6 +90,10 @@ class AudioWaveformsInterface {
9090
return hasPermission ?? false;
9191
}
9292

93+
Future<void> releaseMetaDataRetriever() async {
94+
await _methodChannel.invokeMethod(Constants.releaseMetaDataRetriever);
95+
}
96+
9397
///platform call to prepare player
9498
Future<bool> preparePlayer(String path, String key, [double? volume]) async {
9599
var result = await _methodChannel.invokeMethod(Constants.preparePlayer, {

lib/src/base/constants.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,5 @@ class Constants {
4343
static const String onCurrentExtractedWaveformData =
4444
"onCurrentExtractedWaveformData";
4545
static const String useLegacyNormalization = "useLegacyNormalization";
46+
static const String releaseMetaDataRetriever = "releaseMetaDataRetriever";
4647
}

lib/src/controllers/recorder_controller.dart

Lines changed: 73 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,37 @@ class RecorderController extends ChangeNotifier {
7070

7171
bool _useLegacyNormalization = false;
7272

73+
/// Provides currently recorded audio duration. Use [onCurrentDuration]
74+
/// stream to get latest events duration.
75+
Duration elapsedDuration = Duration.zero;
76+
77+
/// Provides duration of recorded audio file when recording has been stopped.
78+
/// Until recording has been stopped, this duration will be
79+
/// zero(Duration.zero). Also, once new recording is started this duration
80+
/// will be reset to zero.
81+
Duration recordedDuration = Duration.zero;
82+
83+
Timer? _recorderTimer;
84+
7385
final ValueNotifier<int> _currentScrolledDuration = ValueNotifier(0);
7486

87+
final StreamController<Duration> _currentDurationController =
88+
StreamController.broadcast();
89+
90+
/// A stream to get current duration of currently recording audio file.
91+
/// Events are emitted every 50 milliseconds which means current duration is
92+
/// accurate to 50 milliseconds. To get Fully accurate duration use
93+
/// [recordedDuration] after stopping the recording.
94+
Stream<Duration> get onCurrentDuration => _currentDurationController.stream;
95+
96+
final StreamController<RecorderState> _recorderStateController =
97+
StreamController.broadcast();
98+
99+
/// A Stream to monitor change in RecorderState. Events are emitted whenever
100+
/// there is change in the RecorderState.
101+
Stream<RecorderState> get onRecorderStateChanged =>
102+
_recorderStateController.stream;
103+
75104
/// A class having controls for recording audio and other useful handlers.
76105
///
77106
/// Use [useLegacyNormalization] parameter to use normalization before
@@ -140,15 +169,15 @@ class RecorderController extends ChangeNotifier {
140169
_isRecording = await AudioWaveformsInterface.instance.resume();
141170
if (_isRecording) {
142171
_startTimer();
143-
_recorderState = RecorderState.recording;
172+
_setRecorderState(RecorderState.recording);
144173
} else {
145174
throw "Failed to resume recording";
146175
}
147176
notifyListeners();
148177
return;
149178
}
150179
if (Platform.isIOS) {
151-
_recorderState = RecorderState.initialized;
180+
_setRecorderState(RecorderState.initialized);
152181
}
153182
if (_recorderState.isInitialized) {
154183
_isRecording = await AudioWaveformsInterface.instance.record(
@@ -161,15 +190,15 @@ class RecorderController extends ChangeNotifier {
161190
useLegacyNormalization: _useLegacyNormalization,
162191
);
163192
if (_isRecording) {
164-
_recorderState = RecorderState.recording;
193+
_setRecorderState(RecorderState.recording);
165194
_startTimer();
166195
} else {
167196
throw "Failed to start recording";
168197
}
169198
notifyListeners();
170199
}
171200
} else {
172-
_recorderState = RecorderState.stopped;
201+
_setRecorderState(RecorderState.stopped);
173202
notifyListeners();
174203
}
175204
}
@@ -192,7 +221,7 @@ class RecorderController extends ChangeNotifier {
192221
bitRate: bitRate ?? this.bitRate,
193222
);
194223
if (initialized) {
195-
_recorderState = RecorderState.initialized;
224+
_setRecorderState(RecorderState.initialized);
196225
} else {
197226
throw "Failed to initialize recorder";
198227
}
@@ -223,8 +252,9 @@ class RecorderController extends ChangeNotifier {
223252
if (_isRecording) {
224253
throw "Failed to pause recording";
225254
}
255+
_recorderTimer?.cancel();
226256
_timer?.cancel();
227-
_recorderState = RecorderState.paused;
257+
_setRecorderState(RecorderState.paused);
228258
}
229259
notifyListeners();
230260
}
@@ -242,14 +272,21 @@ class RecorderController extends ChangeNotifier {
242272
/// left of for previous recording.
243273
Future<String?> stop([bool callReset = true]) async {
244274
if (_recorderState.isRecording || _recorderState.isPaused) {
245-
final path = await AudioWaveformsInterface.instance.stop();
246-
247-
if (path != null) {
275+
final audioInfo = await AudioWaveformsInterface.instance.stop();
276+
if (audioInfo != null) {
248277
_isRecording = false;
249278
_timer?.cancel();
250-
_recorderState = RecorderState.stopped;
279+
_recorderTimer?.cancel();
280+
if (audioInfo[1] != null) {
281+
var duration = int.tryParse(audioInfo[1]!);
282+
if (duration != null) {
283+
recordedDuration = Duration(milliseconds: duration);
284+
}
285+
}
286+
elapsedDuration = Duration.zero;
287+
_setRecorderState(RecorderState.stopped);
251288
if (callReset) reset();
252-
return path;
289+
return audioInfo[0];
253290
} else {
254291
throw "Failed stop recording";
255292
}
@@ -282,6 +319,13 @@ class RecorderController extends ChangeNotifier {
282319

283320
/// Gets decibel by every defined frequency
284321
void _startTimer() {
322+
recordedDuration = Duration.zero;
323+
const duration = Duration(milliseconds: 50);
324+
_recorderTimer = Timer.periodic(duration, (_) {
325+
elapsedDuration += duration;
326+
_currentDurationController.add(elapsedDuration);
327+
});
328+
285329
_timer = Timer.periodic(
286330
updateFrequency,
287331
(timer) async {
@@ -351,13 +395,30 @@ class RecorderController extends ChangeNotifier {
351395
_currentScrolledDuration.value = duration;
352396
}
353397

398+
void _setRecorderState(RecorderState state) {
399+
_recorderStateController.add(state);
400+
_recorderState = state;
401+
}
402+
403+
/// This function is to release resources taken by android native
404+
/// MetaDataRetriever.
405+
void _release() {
406+
AudioWaveformsInterface.instance.releaseMetaDataRetriever();
407+
}
408+
354409
/// Releases any resources taken by this recorder and with this
355410
/// controller is also disposed.
356411
@override
357412
void dispose() async {
358-
if (_timer != null) _timer!.cancel();
359413
if (recorderState != RecorderState.stopped) await stop();
414+
if (Platform.isAndroid) _release();
360415
_currentScrolledDuration.dispose();
416+
_currentDurationController.close();
417+
_recorderStateController.close();
418+
_recorderTimer?.cancel();
419+
_timer?.cancel();
420+
_timer = null;
421+
_recorderTimer = null;
361422
super.dispose();
362423
}
363424
}

0 commit comments

Comments
 (0)