Skip to content

Commit 3706b75

Browse files
committed
CB-9487: Support getting amplitude for recording
1 parent e5b663a commit 3706b75

File tree

7 files changed

+162
-30
lines changed

7 files changed

+162
-30
lines changed

README.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@ The following constants are reported as the only parameter to the
9292

9393
### Methods
9494

95+
- `media.getCurrentAmplitude`: Returns the current position within an audio file.
96+
9597
- `media.getCurrentPosition`: Returns the current position within an audio file.
9698

9799
- `media.getDuration`: Returns the duration of an audio file.
@@ -120,6 +122,47 @@ The following constants are reported as the only parameter to the
120122
- __duration__: The duration of the media, in seconds.
121123

122124

125+
## media.getCurrentAmplitude
126+
127+
Returns the current amplitude of the current recording.
128+
129+
media.getCurrentAmplitude(mediaSuccess, [mediaError]);
130+
131+
### Supported Platforms
132+
133+
- Android
134+
- iOS
135+
136+
### Parameters
137+
138+
- __mediaSuccess__: The callback that is passed the current amplitude (0.0 - 1.0).
139+
140+
- __mediaError__: (Optional) The callback to execute if an error occurs.
141+
142+
### Quick Example
143+
144+
// Audio player
145+
//
146+
var my_media = new Media(src, onSuccess, onError);
147+
148+
// Record audio
149+
my_media.startRecord();
150+
151+
mediaTimer = setInterval(function () {
152+
// get media amplitude
153+
my_media.getCurrentAmplitude(
154+
// success callback
155+
function (amp) {
156+
console.log(amp + "%");
157+
},
158+
// error callback
159+
function (e) {
160+
console.log("Error getting amp=" + e);
161+
}
162+
);
163+
}, 1000);
164+
165+
123166
## media.getCurrentPosition
124167

125168
Returns the current position within an audio file. Also updates the `Media` object's `position` parameter.

src/android/AudioHandler.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,10 @@ else if (action.equals("release")) {
164164
else if (action.equals("messageChannel")) {
165165
messageChannel = callbackContext;
166166
return true;
167+
} else if (action.equals("getCurrentAmplitudeAudio")) {
168+
float f = this.getCurrentAmplitudeAudio(args.getString(0));
169+
callbackContext.sendPluginResult(new PluginResult(status, f));
170+
return true;
167171
}
168172
else { // Unrecognized action.
169173
return false;
@@ -471,5 +475,16 @@ else if(PermissionHelper.hasPermission(this, permissions[RECORD_AUDIO]))
471475

472476
}
473477

474-
478+
/**
479+
* Get current amplitude of recording.
480+
* @param id The id of the audio player
481+
* @return amplitude
482+
*/
483+
public float getCurrentAmplitudeAudio(String id) {
484+
AudioPlayer audio = this.players.get(id);
485+
if (audio != null) {
486+
return (audio.getCurrentAmplitude());
487+
}
488+
return 0;
489+
}
475490
}

src/android/AudioPlayer.java

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -501,13 +501,13 @@ private boolean readyPlayer(String file) {
501501
sendErrorStatus(MEDIA_ERR_ABORTED);
502502
}
503503
return false;//we´re not ready yet
504-
}
504+
}
505505
else {
506506
//reset the audio file
507507
player.seekTo(0);
508508
player.pause();
509-
return true;
510-
}
509+
return true;
510+
}
511511
} else {
512512
//reset the player
513513
this.player.reset();
@@ -598,4 +598,23 @@ else if (value != null) {
598598

599599
this.handler.sendEventMessage("status", statusDetails);
600600
}
601+
602+
/**
603+
* Get current amplitude of recording.
604+
*
605+
* @return amplitude or 0 if not recording
606+
*/
607+
public float getCurrentAmplitude() {
608+
if (this.recorder != null) {
609+
try{
610+
if (this.state == STATE.MEDIA_RUNNING) {
611+
return (float) this.recorder.getMaxAmplitude() / 32762;
612+
}
613+
}
614+
catch (Exception e) {
615+
e.printStackTrace();
616+
}
617+
}
618+
return 0;
619+
}
601620
}

src/ios/CDVSound.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,8 +110,9 @@ typedef NSUInteger CDVMediaMsg;
110110

111111
- (void)startRecordingAudio:(CDVInvokedUrlCommand*)command;
112112
- (void)stopRecordingAudio:(CDVInvokedUrlCommand*)command;
113+
- (void)getCurrentAmplitudeAudio:(CDVInvokedUrlCommand*)command;
113114

114115
- (void)setVolume:(CDVInvokedUrlCommand*)command;
115116
- (void)setRate:(CDVInvokedUrlCommand*)command;
116117

117-
@end
118+
@end

src/ios/CDVSound.m

Lines changed: 51 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ - (NSString*)createMediaErrorWithCode:(CDVMediaError)code message:(NSString*)mes
206206

207207
[errorDict setObject:[NSNumber numberWithUnsignedInteger:code] forKey:@"code"];
208208
[errorDict setObject:message ? message:@"" forKey:@"message"];
209-
209+
210210
NSData* jsonData = [NSJSONSerialization dataWithJSONObject:errorDict options:0 error:nil];
211211
return [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
212212
}
@@ -290,7 +290,7 @@ - (void)setRate:(CDVInvokedUrlCommand*)command
290290
float customRate = [rate floatValue];
291291
[avPlayer setRate:customRate];
292292
}
293-
293+
294294
[[self soundCache] setObject:audioFile forKey:mediaId];
295295
}
296296
}
@@ -420,7 +420,7 @@ - (BOOL)prepareToPlay:(CDVAudioFile*)audioFile withId:(NSString*)mediaId
420420
if ([resourceURL isFileURL]) {
421421
audioFile.player = [[CDVAudioPlayer alloc] initWithContentsOfURL:resourceURL error:&playerError];
422422
} else {
423-
/*
423+
/*
424424
NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:resourceURL];
425425
NSString* userAgent = [self.commandDelegate userAgent];
426426
if (userAgent) {
@@ -541,10 +541,10 @@ - (void)seekToAudio:(CDVInvokedUrlCommand*)command
541541
} else if (avPlayer != nil) {
542542
int32_t timeScale = avPlayer.currentItem.asset.duration.timescale;
543543
CMTime timeToSeek = CMTimeMakeWithSeconds(posInSeconds, timeScale);
544-
544+
545545
BOOL isPlaying = (avPlayer.rate > 0 && !avPlayer.error);
546546
BOOL isReadyToSeek = (avPlayer.status == AVPlayerStatusReadyToPlay) && (avPlayer.currentItem.status == AVPlayerItemStatusReadyToPlay);
547-
547+
548548
// CB-10535:
549549
// When dealing with remote files, we can get into a situation where we start playing before AVPlayer has had the time to buffer the file to be played.
550550
// To avoid the app crashing in such a situation, we only seek if both the player and the player item are ready to play. If not ready, we send an error back to JS land.
@@ -613,7 +613,7 @@ - (void)getCurrentPositionAudio:(CDVInvokedUrlCommand*)command
613613
}
614614

615615
CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDouble:position];
616-
616+
617617
NSString* jsString = [NSString stringWithFormat:@"%@(\"%@\",%d,%.3f);", @"cordova.require('cordova-plugin-media.Media').onStatus", mediaId, MEDIA_POSITION, position];
618618
[self.commandDelegate evalJs:jsString];
619619
[self.commandDelegate sendPluginResult:result callbackId:callbackId];
@@ -636,7 +636,7 @@ - (void)startRecordingAudio:(CDVInvokedUrlCommand*)command
636636

637637
void (^startRecording)(void) = ^{
638638
NSError* __autoreleasing error = nil;
639-
639+
640640
if (audioFile.recorder != nil) {
641641
[audioFile.recorder stop];
642642
audioFile.recorder = nil;
@@ -656,22 +656,28 @@ - (void)startRecordingAudio:(CDVInvokedUrlCommand*)command
656656
return;
657657
}
658658
}
659-
659+
660660
// create a new recorder for each start record
661+
NSDictionary *audioSettings = @{AVFormatIDKey: @(kAudioFormatMPEG4AAC),
662+
AVSampleRateKey: @(44100),
663+
AVNumberOfChannelsKey: @(1),
664+
AVEncoderAudioQualityKey: @(AVAudioQualityMedium)
665+
};
661666
audioFile.recorder = [[CDVAudioRecorder alloc] initWithURL:audioFile.resourceURL settings:nil error:&error];
662-
667+
663668
bool recordingSuccess = NO;
664669
if (error == nil) {
665670
audioFile.recorder.delegate = weakSelf;
666671
audioFile.recorder.mediaId = mediaId;
672+
audioFile.recorder.meteringEnabled = YES;
667673
recordingSuccess = [audioFile.recorder record];
668674
if (recordingSuccess) {
669675
NSLog(@"Started recording audio sample '%@'", audioFile.resourcePath);
670676
jsString = [NSString stringWithFormat:@"%@(\"%@\",%d,%d);", @"cordova.require('cordova-plugin-media.Media').onStatus", mediaId, MEDIA_STATE, MEDIA_RUNNING];
671677
[weakSelf.commandDelegate evalJs:jsString];
672678
}
673679
}
674-
680+
675681
if ((error != nil) || (recordingSuccess == NO)) {
676682
if (error != nil) {
677683
errorMsg = [NSString stringWithFormat:@"Failed to initialize AVAudioRecorder: %@\n", [error localizedFailureReason]];
@@ -686,7 +692,7 @@ - (void)startRecordingAudio:(CDVInvokedUrlCommand*)command
686692
[weakSelf.commandDelegate evalJs:jsString];
687693
}
688694
};
689-
695+
690696
SEL rrpSel = NSSelectorFromString(@"requestRecordPermission:");
691697
if ([self hasAudioSession] && [self.avSession respondsToSelector:rrpSel])
692698
{
@@ -710,7 +716,7 @@ - (void)startRecordingAudio:(CDVInvokedUrlCommand*)command
710716
} else {
711717
startRecording();
712718
}
713-
719+
714720
} else {
715721
// file did not validate
716722
NSString* errorMsg = [NSString stringWithFormat:@"Could not record audio at '%@'", audioFile.resourcePath];
@@ -831,6 +837,39 @@ - (void)onReset
831837
[[self soundCache] removeAllObjects];
832838
}
833839

840+
- (void)getCurrentAmplitudeAudio:(CDVInvokedUrlCommand*)command
841+
{
842+
NSString* callbackId = command.callbackId;
843+
NSString* mediaId = [command argumentAtIndex:0];
844+
845+
#pragma unused(mediaId)
846+
CDVAudioFile* audioFile = [[self soundCache] objectForKey:mediaId];
847+
float amplitude = 0; // The linear 0.0 .. 1.0 value
848+
849+
if ((audioFile != nil) && (audioFile.recorder != nil) && [audioFile.recorder isRecording]) {
850+
[audioFile.recorder updateMeters];
851+
float minDecibels = -60.0f; // Or use -60dB, which I measured in a silent room.
852+
float decibels = [audioFile.recorder averagePowerForChannel:0];
853+
if (decibels < minDecibels) {
854+
amplitude = 0.0f;
855+
} else if (decibels >= 0.0f) {
856+
amplitude = 1.0f;
857+
} else {
858+
float root = 2.0f;
859+
float minAmp = powf(10.0f, 0.05f * minDecibels);
860+
float inverseAmpRange = 1.0f / (1.0f - minAmp);
861+
float amp = powf(10.0f, 0.05f * decibels);
862+
float adjAmp = (amp - minAmp) * inverseAmpRange;
863+
amplitude = powf(adjAmp, 1.0f / root);
864+
}
865+
}
866+
CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDouble:amplitude];
867+
868+
NSString* jsString = [NSString stringWithFormat:@"%@(\"%@\",%d,%.3f);", @"cordova.require('cordova-plugin-media.Media').onStatus", mediaId, MEDIA_POSITION, amplitude];
869+
[self.commandDelegate evalJs:jsString];
870+
[self.commandDelegate sendPluginResult:result callbackId:callbackId];
871+
}
872+
834873
@end
835874

836875
@implementation CDVAudioFile

tests/tests.js

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
// increased timeout for actual playback to give device chance to download and play mp3 file
2626
// some emulators can be REALLY slow at this, so two minutes
2727
var ACTUAL_PLAYBACK_TEST_TIMEOUT = 2 * 60 * 1000;
28-
28+
2929
var isWindows = cordova.platformId == 'windows8' || cordova.platformId == 'windows';
3030
// detect whether audio hardware is available and enabled
3131
var isAudioSupported = isWindows ? Windows.Media.Devices.MediaDevice.getDefaultAudioRenderId(Windows.Media.Devices.AudioDeviceRole.default) : true;
@@ -168,7 +168,14 @@ exports.defineAutoTests = function () {
168168
media1.release();
169169
});
170170

171-
it("media.spec.15 should return MediaError for bad filename", function (done) {
171+
it("media.spec.15 should contain a getCurrentAmplitude function", function () {
172+
var media1 = new Media("dummy");
173+
expect(media1.getCurrentAmplitude).toBeDefined();
174+
expect(typeof media1.getCurrentAmplitude).toBe('function');
175+
media1.release();
176+
});
177+
178+
it("media.spec.16 should return MediaError for bad filename", function (done) {
172179
//bb10 dialog pops up, preventing tests from running
173180
if (cordova.platformId === 'blackberry10') {
174181
pending();
@@ -203,7 +210,7 @@ exports.defineAutoTests = function () {
203210
}
204211
});
205212

206-
it("media.spec.16 position should be set properly", function (done) {
213+
it("media.spec.17 position should be set properly", function (done) {
207214
// no audio hardware available
208215
if (!isAudioSupported) {
209216
pending();
@@ -233,7 +240,7 @@ exports.defineAutoTests = function () {
233240
media.play();
234241
}, ACTUAL_PLAYBACK_TEST_TIMEOUT);
235242

236-
it("media.spec.17 duration should be set properly", function (done) {
243+
it("media.spec.18 duration should be set properly", function (done) {
237244
if (!isAudioSupported || cordova.platformId === 'blackberry10') {
238245
pending();
239246
}
@@ -262,7 +269,7 @@ exports.defineAutoTests = function () {
262269
media.play();
263270
}, ACTUAL_PLAYBACK_TEST_TIMEOUT);
264271

265-
it("media.spec.20 should be able to resume playback after pause", function (done) {
272+
it("media.spec.19 should be able to resume playback after pause", function (done) {
266273
if (!isAudioSupported || cordova.platformId === 'blackberry10') {
267274
pending();
268275
}
@@ -297,15 +304,15 @@ exports.defineAutoTests = function () {
297304
}
298305
};
299306
media = new Media(mediaFile, successCallback, failed.bind(null, done, 'media1 = new Media - Error creating Media object. Media file: ' + mediaFile, context), statusChange);
300-
307+
301308
// CB-10535: Play after a few secs, to give allow enough buffering of media file before seeking
302309
setTimeout(function() {
303310
media.play();
304311
}, 4000);
305-
312+
306313
}, ACTUAL_PLAYBACK_TEST_TIMEOUT);
307314

308-
it("media.spec.21 should be able to seek through file", function (done) {
315+
it("media.spec.20 should be able to seek through file", function (done) {
309316
if (!isAudioSupported || cordova.platformId === 'blackberry10') {
310317
pending();
311318
}
@@ -330,23 +337,23 @@ exports.defineAutoTests = function () {
330337
}
331338
};
332339
media = new Media(mediaFile, successCallback, failed.bind(null, done, 'media1 = new Media - Error creating Media object. Media file: ' + mediaFile, context), statusChange);
333-
340+
334341
// CB-10535: Play after a few secs, to give allow enough buffering of media file before seeking
335342
setTimeout(function() {
336343
media.play();
337344
}, 4000);
338-
345+
339346
}, ACTUAL_PLAYBACK_TEST_TIMEOUT);
340347
});
341348

342-
it("media.spec.18 should contain a setRate function", function () {
349+
it("media.spec.21 should contain a setRate function", function () {
343350
var media1 = new Media("dummy");
344351
expect(media1.setRate).toBeDefined();
345352
expect(typeof media1.setRate).toBe('function');
346353
media1.release();
347354
});
348355

349-
it("media.spec.19 playback rate should be set properly using setRate", function (done) {
356+
it("media.spec.22 playback rate should be set properly using setRate", function (done) {
350357
if (cordova.platformId !== 'ios') {
351358
expect(true).toFailWithMessage('Platform does not supported this feature');
352359
pending();

0 commit comments

Comments
 (0)