Add voice memo recording.

// FREEBIE
This commit is contained in:
Matthew Chen 2017-05-04 22:10:37 -04:00
parent 45c8695ab4
commit 608cb70a3b
2 changed files with 145 additions and 50 deletions

View File

@ -31,7 +31,6 @@ extern NSString *const OWSMessagesViewControllerDidAppearNotification;
@interface MessagesViewController : JSQMessagesViewController <UIImagePickerControllerDelegate,
UINavigationControllerDelegate,
UITextViewDelegate,
AVAudioRecorderDelegate,
AVAudioPlayerDelegate,
UIGestureRecognizerDelegate>

View File

@ -97,11 +97,11 @@ typedef enum : NSUInteger {
- (void)didPasteAttachment:(SignalAttachment * _Nullable)attachment;
- (void)didStartVoiceMemo;
- (void)gestureDidStartVoiceMemo;
- (void)didEndVoiceMemo;
- (void)gestureDidEndVoiceMemo;
- (void)didCancelVoiceMemo;
- (void)gestureDidCancelVoiceMemo;
@end
@ -382,16 +382,16 @@ typedef enum : NSUInteger {
case UIGestureRecognizerStateFailed:
if (self.isRecordingVoiceMemo) {
self.isRecordingVoiceMemo = NO;
[self.textViewPasteDelegate didCancelVoiceMemo];
[self.textViewPasteDelegate gestureDidCancelVoiceMemo];
}
break;
case UIGestureRecognizerStateBegan:
if (self.isRecordingVoiceMemo) {
self.isRecordingVoiceMemo = NO;
[self.textViewPasteDelegate didCancelVoiceMemo];
[self.textViewPasteDelegate gestureDidCancelVoiceMemo];
}
self.isRecordingVoiceMemo = YES;
[self.textViewPasteDelegate didStartVoiceMemo];
[self.textViewPasteDelegate gestureDidStartVoiceMemo];
break;
case UIGestureRecognizerStateChanged:
// TODO:
@ -399,7 +399,7 @@ typedef enum : NSUInteger {
case UIGestureRecognizerStateEnded:
if (self.isRecordingVoiceMemo) {
self.isRecordingVoiceMemo = NO;
[self.textViewPasteDelegate didEndVoiceMemo];
[self.textViewPasteDelegate gestureDidEndVoiceMemo];
}
break;
}
@ -409,7 +409,6 @@ typedef enum : NSUInteger {
{
if (self.isRecordingVoiceMemo) {
self.isRecordingVoiceMemo = NO;
[self.textViewPasteDelegate didCancelVoiceMemo];
}
}
@ -913,6 +912,10 @@ typedef enum : NSUInteger {
selector:@selector(resetContentAndLayout)
name:UIApplicationWillEnterForegroundNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(applicationWillResignActive:)
name:UIApplicationWillResignActiveNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(cancelReadTimer)
name:UIApplicationDidEnterBackgroundNotification
@ -928,11 +931,19 @@ typedef enum : NSUInteger {
name:UIApplicationWillEnterForegroundNotification
object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIApplicationDidEnterBackgroundNotification
object:nil];
name:UIApplicationWillResignActiveNotification
object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIApplicationDidEnterBackgroundNotification
object:nil];
}
}
- (void)applicationWillResignActive:(NSNotification *)notification
{
[self cancelVoiceMemo];
}
- (void)initializeTextView {
[self.inputToolbar.contentView.textView setFont:[UIFont ows_dynamicTypeBodyFont]];
@ -1189,7 +1200,7 @@ typedef enum : NSUInteger {
[self cancelReadTimer];
[self saveDraft];
[((OWSMessagesComposerTextView *)self.inputToolbar.contentView.textView)cancelVoiceMemoIfNecessary];
[self cancelVoiceMemo];
}
- (void)startExpirationTimerAnimations
@ -3145,46 +3156,113 @@ typedef enum : NSUInteger {
#pragma mark - Audio
- (void)recordAudio {
// Define the recorder setting
NSArray *pathComponents = [NSArray
arrayWithObjects:[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject],
[NSString stringWithFormat:@"%lld.m4a", [NSDate ows_millisecondTimeStamp]],
nil];
NSURL *outputFileURL = [NSURL fileURLWithPathComponents:pathComponents];
- (void)startRecordingVoiceMemo
{
OWSAssert([NSThread isMainThread]);
DDLogInfo(@"startRecordingVoiceMemo");
NSString *temporaryDirectory = NSTemporaryDirectory();
NSString *filename = [NSString stringWithFormat:@"%lld.m4a", [NSDate ows_millisecondTimeStamp]];
NSString *filepath = [temporaryDirectory stringByAppendingPathComponent:filename];
NSURL *fileURL = [NSURL fileURLWithPath:filepath];
// Setup audio session
AVAudioSession *session = [AVAudioSession sharedInstance];
[session setCategory:AVAudioSessionCategoryPlayAndRecord error:nil];
NSMutableDictionary *recordSetting = [[NSMutableDictionary alloc] init];
[recordSetting setValue:[NSNumber numberWithInt:kAudioFormatMPEG4AAC] forKey:AVFormatIDKey];
[recordSetting setValue:[NSNumber numberWithFloat:44100.0] forKey:AVSampleRateKey];
[recordSetting setValue:[NSNumber numberWithInt:2] forKey:AVNumberOfChannelsKey];
NSError *error;
[session setCategory:AVAudioSessionCategoryRecord error:&error];
if (error) {
DDLogError(@"%@ Couldn't configure audio session: %@", self.tag, error);
[self resetRecordingVoiceMemo];
OWSAssert(0);
return;
}
// Initiate and prepare the recorder
_audioRecorder = [[AVAudioRecorder alloc] initWithURL:outputFileURL settings:recordSetting error:NULL];
_audioRecorder.delegate = self;
_audioRecorder.meteringEnabled = YES;
[_audioRecorder prepareToRecord];
self.audioRecorder = [[AVAudioRecorder alloc] initWithURL:fileURL
settings:@{
AVFormatIDKey : @(kAudioFormatMPEG4AAC),
AVSampleRateKey : @(44100),
AVNumberOfChannelsKey : @(2),
AVEncoderBitRateKey: @(128 * 1024),
}
error:&error];
if (error) {
DDLogError(@"%@ Couldn't create audioRecorder: %@", self.tag, error);
[self resetRecordingVoiceMemo];
OWSAssert(0);
return;
}
self.audioRecorder.meteringEnabled = YES;
if (![self.audioRecorder prepareToRecord]) {
DDLogError(@"%@ audioRecorder couldn't prepareToRecord.", self.tag);
[self resetRecordingVoiceMemo];
OWSAssert(0);
return;
}
if (![self.audioRecorder record]) {
DDLogError(@"%@ audioRecorder couldn't record.", self.tag);
[self resetRecordingVoiceMemo];
OWSAssert(0);
return;
}
}
- (void)audioRecorderDidFinishRecording:(AVAudioRecorder *)recorder successfully:(BOOL)flag {
if (flag) {
NSData *audioData = [NSData dataWithContentsOfURL:recorder.url];
SignalAttachment *attachment =
[SignalAttachment attachmentWithData:audioData dataUTI:(NSString *)kUTTypeMPEG4Audio filename:nil];
if (!attachment ||
[attachment hasError]) {
DDLogWarn(@"%@ %s Invalid attachment: %@.",
self.tag,
__PRETTY_FUNCTION__,
attachment ? [attachment errorName] : @"Missing data");
[self showErrorAlertForAttachment:attachment];
} else {
[self tryToSendAttachmentIfApproved:attachment];
}
- (void)endRecordingVoiceMemo
{
OWSAssert([NSThread isMainThread]);
DDLogInfo(@"endRecordingVoiceMemo");
if (!self.audioRecorder) {
DDLogError(@"%@ Missing audioRecorder", self.tag);
OWSAssert(0);
return;
}
[self.audioRecorder stop];
NSData *audioData = [NSData dataWithContentsOfURL:self.audioRecorder.url];
self.audioRecorder = nil;
if (!audioData) {
DDLogError(@"%@ Couldn't load audioRecorder data", self.tag);
OWSAssert(0);
return;
}
SignalAttachment *attachment =
[SignalAttachment attachmentWithData:audioData dataUTI:(NSString *)kUTTypeMPEG4Audio filename:nil];
if (!attachment || [attachment hasError]) {
DDLogWarn(@"%@ %s Invalid attachment: %@.",
self.tag,
__PRETTY_FUNCTION__,
attachment ? [attachment errorName] : @"Missing data");
[self showErrorAlertForAttachment:attachment];
} else {
[self tryToSendAttachmentIfApproved:attachment skipApprovalDialog:YES];
}
}
- (void)cancelRecordingVoiceMemo
{
OWSAssert([NSThread isMainThread]);
DDLogInfo(@"cancelRecordingVoiceMemo");
[self resetRecordingVoiceMemo];
}
- (void)resetRecordingVoiceMemo
{
OWSAssert([NSThread isMainThread]);
[self.audioRecorder stop];
self.audioRecorder = nil;
}
#pragma mark Accessory View
@ -3478,25 +3556,43 @@ typedef enum : NSUInteger {
completion:nil];
}
- (void)didStartVoiceMemo
- (void)gestureDidStartVoiceMemo
{
DDLogError(@"didStartVoiceMemo");
OWSAssert([NSThread isMainThread]);
DDLogInfo(@"gestureDidStartVoiceMemo");
[((OWSMessagesInputToolbar *)self.inputToolbar)showVoiceMemoUI];
[self startRecordingVoiceMemo];
}
- (void)didEndVoiceMemo
- (void)gestureDidEndVoiceMemo
{
DDLogError(@"didEndVoiceMemo");
OWSAssert([NSThread isMainThread]);
DDLogInfo(@"gestureDidEndVoiceMemo");
[((OWSMessagesInputToolbar *)self.inputToolbar) hideVoiceMemoUI:YES];
[self endRecordingVoiceMemo];
}
- (void)didCancelVoiceMemo
- (void)gestureDidCancelVoiceMemo
{
DDLogError(@"didCancelVoiceMemo");
OWSAssert([NSThread isMainThread]);
DDLogInfo(@"gestureDidCancelVoiceMemo");
[((OWSMessagesInputToolbar *)self.inputToolbar) hideVoiceMemoUI:NO];
[self cancelRecordingVoiceMemo];
}
- (void)cancelVoiceMemo
{
OWSAssert([NSThread isMainThread]);
[((OWSMessagesComposerTextView *)self.inputToolbar.contentView.textView)cancelVoiceMemoIfNecessary];
[((OWSMessagesInputToolbar *)self.inputToolbar) hideVoiceMemoUI:NO];
[self cancelRecordingVoiceMemo];
}
#pragma mark - UIScrollViewDelegate