123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412 |
- #import "FlutterSoundPlugin.h"
- #import <AVFoundation/AVFoundation.h>
- // post fix with _FlutterSound to avoid conflicts with common libs including path_provider
- NSString* GetDirectoryOfType_FlutterSound(NSSearchPathDirectory dir) {
- NSArray* paths = NSSearchPathForDirectoriesInDomains(dir, NSUserDomainMask, YES);
- return [paths.firstObject stringByAppendingString:@"/"];
- }
- @implementation FlutterSoundPlugin{
- NSURL *audioFileURL;
- AVAudioRecorder *audioRecorder;
- AVAudioPlayer *audioPlayer;
- NSTimer *timer;
- NSTimer *dbPeakTimer;
- }
- double subscriptionDuration = 0.01;
- double dbPeakInterval = 0.8;
- bool shouldProcessDbLevel = false;
- FlutterMethodChannel* _channel;
- - (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag {
- NSLog(@"audioPlayerDidFinishPlaying");
- NSNumber *duration = [NSNumber numberWithDouble:audioPlayer.duration * 1000];
- NSNumber *currentTime = [NSNumber numberWithDouble:audioPlayer.currentTime * 1000];
- NSString* status = [NSString stringWithFormat:@"{\"duration\": \"%@\", \"current_position\": \"%@\"}", [duration stringValue], [currentTime stringValue]];
- /*
- NSDictionary *status = @{
- @"duration" : [duration stringValue],
- @"current_position" : [currentTime stringValue],
- };
- */
- [_channel invokeMethod:@"audioPlayerDidFinishPlaying" arguments:status];
- [self stopTimer];
- }
- - (void) stopTimer{
- if (timer != nil) {
- [timer invalidate];
- timer = nil;
- }
- }
- - (void)updateRecorderProgress:(NSTimer*) timer
- {
- NSNumber *currentTime = [NSNumber numberWithDouble:audioRecorder.currentTime * 1000];
- [audioRecorder updateMeters];
- NSString* status = [NSString stringWithFormat:@"{\"current_position\": \"%@\"}", [currentTime stringValue]];
- /*
- NSDictionary *status = @{
- @"current_position" : [currentTime stringValue],
- };
- */
- [_channel invokeMethod:@"updateRecorderProgress" arguments:status];
- }
- - (void)updateProgress:(NSTimer*) timer
- {
- NSNumber *duration = [NSNumber numberWithDouble:audioPlayer.duration * 1000];
- NSNumber *currentTime = [NSNumber numberWithDouble:audioPlayer.currentTime * 1000];
- if ([duration intValue] == 0 && timer != nil) {
- [self stopTimer];
- return;
- }
- NSString* status = [NSString stringWithFormat:@"{\"duration\": \"%@\", \"current_position\": \"%@\"}", [duration stringValue], [currentTime stringValue]];
- /*
- NSDictionary *status = @{
- @"duration" : [duration stringValue],
- @"current_position" : [currentTime stringValue],
- };
- */
- [_channel invokeMethod:@"updateProgress" arguments:status];
- }
- - (void)updateDbPeakProgress:(NSTimer*) dbPeakTimer
- {
- NSNumber *normalizedPeakLevel = [NSNumber numberWithDouble:MIN(pow(10.0, [audioRecorder peakPowerForChannel:0] / 20.0) * 160.0, 160.0)];
- [_channel invokeMethod:@"updateDbPeakProgress" arguments:normalizedPeakLevel];
- }
- - (void)startRecorderTimer
- {
- dispatch_async(dispatch_get_main_queue(), ^{
- self->timer = [NSTimer scheduledTimerWithTimeInterval: subscriptionDuration
- target:self
- selector:@selector(updateRecorderProgress:)
- userInfo:nil
- repeats:YES];
- });
- }
- - (void)startTimer
- {
- dispatch_async(dispatch_get_main_queue(), ^{
- self->timer = [NSTimer scheduledTimerWithTimeInterval:subscriptionDuration
- target:self
- selector:@selector(updateProgress:)
- userInfo:nil
- repeats:YES];
- });
- }
- - (void)startDbTimer
- {
- dispatch_async(dispatch_get_main_queue(), ^{
- self->dbPeakTimer = [NSTimer scheduledTimerWithTimeInterval:dbPeakInterval
- target:self
- selector:@selector(updateDbPeakProgress:)
- userInfo:nil
- repeats:YES];
- });
- }
- + (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
- FlutterMethodChannel* channel = [FlutterMethodChannel
- methodChannelWithName:@"flutter_sound"
- binaryMessenger:[registrar messenger]];
- FlutterSoundPlugin* instance = [[FlutterSoundPlugin alloc] init];
- [registrar addMethodCallDelegate:instance channel:channel];
- _channel = channel;
- }
- - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
- if ([@"startRecorder" isEqualToString:call.method]) {
- NSString* path = (NSString*)call.arguments[@"path"];
- NSNumber* sampleRateArgs = (NSNumber*)call.arguments[@"sampleRate"];
- NSNumber* numChannelsArgs = (NSNumber*)call.arguments[@"numChannels"];
- NSNumber* iosQuality = (NSNumber*)call.arguments[@"iosQuality"];
- NSNumber* bitRate = (NSNumber*)call.arguments[@"bitRate"];
- float sampleRate = 44100;
- if (![sampleRateArgs isKindOfClass:[NSNull class]]) {
- sampleRate = [sampleRateArgs integerValue];
- }
- int numChannels = 2;
- if (![numChannelsArgs isKindOfClass:[NSNull class]]) {
- numChannels = [numChannelsArgs integerValue];
- }
- [self startRecorder:path:[NSNumber numberWithInt:numChannels]:[NSNumber numberWithInt:sampleRate]:iosQuality:bitRate result:result];
- } else if ([@"stopRecorder" isEqualToString:call.method]) {
- [self stopRecorder:result];
- } else if ([@"startPlayer" isEqualToString:call.method]) {
- NSString* path = (NSString*)call.arguments[@"path"];
- [self startPlayer:path result:result];
- } else if ([@"stopPlayer" isEqualToString:call.method]) {
- [self stopPlayer:result];
- } else if ([@"pausePlayer" isEqualToString:call.method]) {
- [self pausePlayer:result];
- } else if ([@"resumePlayer" isEqualToString:call.method]) {
- [self resumePlayer:result];
- } else if ([@"seekToPlayer" isEqualToString:call.method]) {
- NSNumber* sec = (NSNumber*)call.arguments[@"sec"];
- [self seekToPlayer:sec result:result];
- } else if ([@"setSubscriptionDuration" isEqualToString:call.method]) {
- NSNumber* sec = (NSNumber*)call.arguments[@"sec"];
- [self setSubscriptionDuration:[sec doubleValue] result:result];
- } else if ([@"setVolume" isEqualToString:call.method]) {
- NSNumber* volume = (NSNumber*)call.arguments[@"volume"];
- [self setVolume:[volume doubleValue] result:result];
- }
- else if ([@"setDbPeakLevelUpdate" isEqualToString:call.method]) {
- NSNumber* intervalInSecs = (NSNumber*)call.arguments[@"intervalInSecs"];
- [self setDbPeakLevelUpdate:[intervalInSecs doubleValue] result:result];
- }
- else if ([@"setDbLevelEnabled" isEqualToString:call.method]) {
- BOOL enabled = [call.arguments[@"enabled"] boolValue];
- [self setDbLevelEnabled:enabled result:result];
- }
- else {
- result(FlutterMethodNotImplemented);
- }
- }
- - (void)setSubscriptionDuration:(double)duration result: (FlutterResult)result {
- subscriptionDuration = duration;
- result(@"setSubscriptionDuration");
- }
- - (void)setDbPeakLevelUpdate:(double)intervalInSecs result: (FlutterResult)result {
- dbPeakInterval = intervalInSecs;
- result(@"setDbPeakLevelUpdate");
- }
- - (void)setDbLevelEnabled:(BOOL)enabled result: (FlutterResult)result {
- shouldProcessDbLevel = enabled == YES;
- result(@"setDbLevelEnabled");
- }
- - (void)startRecorder :(NSString*)path :(NSNumber*)numChannels :(NSNumber*)sampleRate :(NSNumber*)iosQuality :(NSNumber*)bitRate result: (FlutterResult)result {
- if ([path class] == [NSNull class]) {
- audioFileURL = [NSURL fileURLWithPath:[GetDirectoryOfType_FlutterSound(NSCachesDirectory) stringByAppendingString:@"sound.aac"]];
- } else {
- audioFileURL = [NSURL fileURLWithPath:path];
- }
- NSMutableDictionary *audioSettings = [NSMutableDictionary dictionaryWithObjectsAndKeys:
- [NSNumber numberWithFloat:[sampleRate doubleValue]],AVSampleRateKey,
- [NSNumber numberWithInt: kAudioFormatMPEG4AAC],AVFormatIDKey,
- [NSNumber numberWithInt: [numChannels intValue]],AVNumberOfChannelsKey,
- [NSNumber numberWithInt: [iosQuality intValue]],AVEncoderAudioQualityKey,nil];
-
- // If bitrate is defined, the use it, otherwise use the OS default
- if(![bitRate isEqual:[NSNull null]]) {
- [audioSettings setValue:[NSNumber numberWithInt: [bitRate intValue]]
- forKey:AVEncoderBitRateKey];
- }
- [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategorySoloAmbient error:nil];
- // Setup audio session
- AVAudioSession *session = [AVAudioSession sharedInstance];
- [session setCategory:AVAudioSessionCategoryPlayAndRecord error:nil];
- // set volume default to speaker
- UInt32 doChangeDefaultRoute = 1;
- AudioSessionSetProperty(kAudioSessionProperty_OverrideCategoryDefaultToSpeaker, sizeof(doChangeDefaultRoute), &doChangeDefaultRoute);
-
- // set up for bluetooth microphone input
- UInt32 allowBluetoothInput = 1;
- AudioSessionSetProperty (kAudioSessionProperty_OverrideCategoryEnableBluetoothInput,sizeof (allowBluetoothInput),&allowBluetoothInput);
-
- audioRecorder = [[AVAudioRecorder alloc]
- initWithURL:audioFileURL
- settings:audioSettings
- error:nil];
- [audioRecorder setDelegate:self];
- if([audioRecorder prepareToRecord]) {
- [audioRecorder record];
- [self startRecorderTimer];
- [audioRecorder setMeteringEnabled:shouldProcessDbLevel];
- }
- if(shouldProcessDbLevel == true) {
- [self startDbTimer];
- }
- NSString *filePath = self->audioFileURL.path;
- result(filePath);
- }
- - (void)stopRecorder:(FlutterResult)result {
- [audioRecorder stop];
- // Stop Db Timer
- [dbPeakTimer invalidate];
- dbPeakTimer = nil;
- [self stopTimer];
-
- AVAudioSession *audioSession = [AVAudioSession sharedInstance];
- [audioSession setActive:NO error:nil];
- NSString *filePath = audioFileURL.absoluteString;
- result(filePath);
- }
- - (void)startPlayer:(NSString*)path result: (FlutterResult)result {
- bool isRemote = false;
- if ([path class] == [NSNull class]) {
- audioFileURL = [NSURL fileURLWithPath:[GetDirectoryOfType_FlutterSound(NSCachesDirectory) stringByAppendingString:@"sound.aac"]];
- } else {
- NSURL *remoteUrl = [NSURL URLWithString:path];
- if(remoteUrl && remoteUrl.scheme && remoteUrl.host){
- audioFileURL = remoteUrl;
- isRemote = true;
- } else {
- audioFileURL = [NSURL URLWithString:path];
- }
- }
- if (isRemote) {
- NSURLSessionDataTask *downloadTask = [[NSURLSession sharedSession]
- dataTaskWithURL:audioFileURL completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
- // NSData *data = [NSData dataWithContentsOfURL:audioFileURL];
-
- // We must create a new Audio Player instance to be able to play a different Url
- self->audioPlayer = [[AVAudioPlayer alloc] initWithData:data error:nil];
- self->audioPlayer.delegate = self;
- // Able to play in silent mode
- [[AVAudioSession sharedInstance]
- setCategory: AVAudioSessionCategoryPlayback
- error: nil];
- // Able to play in background
- [[AVAudioSession sharedInstance] setActive: YES error: nil];
- [[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
- [self->audioPlayer play];
- [self startTimer];
- NSString *filePath = self->audioFileURL.absoluteString;
- result(filePath);
- }];
- [downloadTask resume];
- } else {
- NSData *imageData = [NSData dataWithContentsOfFile:path];
- // if (!audioPlayer) { // Fix sound distoring when playing recorded audio again.
- // audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:audioFileURL error:nil];
- // audioPlayer.delegate = self;
- // }
- self->audioPlayer = [[AVAudioPlayer alloc] initWithData:imageData error:nil];
- self->audioPlayer.delegate = self;
- // Able to play in silent mode
- [[AVAudioSession sharedInstance]
- setCategory: AVAudioSessionCategoryPlayback
- error: nil];
- [audioPlayer play];
- [self startTimer];
- NSString *filePath = audioFileURL.absoluteString;
- result(filePath);
- }
- }
- - (void)stopPlayer:(FlutterResult)result {
- if (audioPlayer) {
- if (timer != nil) {
- [timer invalidate];
- timer = nil;
- }
- [audioPlayer stop];
- audioPlayer = nil;
- result(@"stop play");
- } else {
- result([FlutterError
- errorWithCode:@"Audio Player"
- message:@"player is not set"
- details:nil]);
- }
- }
- - (void)pausePlayer:(FlutterResult)result {
- if (audioPlayer && [audioPlayer isPlaying]) {
- [audioPlayer pause];
- if (timer != nil) {
- [timer invalidate];
- timer = nil;
- }
- result(@"pause play");
- } else {
- result([FlutterError
- errorWithCode:@"Audio Player"
- message:@"player is not set"
- details:nil]);
- }
- }
- - (void)resumePlayer:(FlutterResult)result {
- if (!audioFileURL) {
- result([FlutterError
- errorWithCode:@"Audio Player"
- message:@"fileURL is not defined"
- details:nil]);
- return;
- }
- if (!audioPlayer) {
- result([FlutterError
- errorWithCode:@"Audio Player"
- message:@"player is not set"
- details:nil]);
- return;
- }
- [[AVAudioSession sharedInstance]
- setCategory: AVAudioSessionCategoryPlayback
- error: nil];
- [audioPlayer play];
- [self startTimer];
- NSString *filePath = audioFileURL.absoluteString;
- result(filePath);
- }
- - (void)seekToPlayer:(nonnull NSNumber*) time result: (FlutterResult)result {
- if (audioPlayer) {
- audioPlayer.currentTime = [time doubleValue] / 1000;
- [self updateProgress:nil];
- result([time stringValue]);
- } else {
- result([FlutterError
- errorWithCode:@"Audio Player"
- message:@"player is not set"
- details:nil]);
- }
- }
- - (void)setVolume:(double) volume result: (FlutterResult)result {
- if (audioPlayer) {
- [audioPlayer setVolume: volume];
- result(@"volume set");
- } else {
- result([FlutterError
- errorWithCode:@"Audio Player"
- message:@"player is not set"
- details:nil]);
- }
- }
- @end
|