CPDFSoundPlayBar.m 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373
  1. //
  2. // CPDFSoundPlayBar.m
  3. // ComPDFKit_Tools
  4. //
  5. // Copyright © 2014-2024 PDF Technologies, Inc. All Rights Reserved.
  6. //
  7. // THIS SOURCE CODE AND ANY ACCOMPANYING DOCUMENTATION ARE PROTECTED BY INTERNATIONAL COPYRIGHT LAW
  8. // AND MAY NOT BE RESOLD OR REDISTRIBUTED. USAGE IS BOUND TO THE ComPDFKit LICENSE AGREEMENT.
  9. // UNAUTHORIZED REPRODUCTION OR DISTRIBUTION IS SUBJECT TO CIVIL AND CRIMINAL PENALTIES.
  10. // This notice may not be removed from this file.
  11. //
  12. #import "CPDFSoundPlayBar.h"
  13. #import "CAnnotStyle.h"
  14. #import <AVFAudio/AVFAudio.h>
  15. #import <AVFoundation/AVFoundation.h>
  16. #import <ComPDFKit/ComPDFKit.h>
  17. typedef NS_ENUM(NSInteger, CPDFAudioState) {
  18. CPDFAudioState_Pause,
  19. CPDFAudioState_Recording,
  20. CPDFAudioState_Playing,
  21. };
  22. #define SOUND_TMP_DICT [NSTemporaryDirectory() stringByAppendingPathComponent:@"soundCache"]
  23. @interface CPDFSoundPlayBar ()<AVAudioPlayerDelegate>
  24. @property (nonatomic, strong) CAnnotStyle *annotStyle;
  25. @property (nonatomic, strong) NSTimer *voiceTimer;
  26. @property (nonatomic, strong) UIButton *playButton;
  27. @property (nonatomic, strong) UIButton *closeButton;
  28. @property (nonatomic, strong) UIButton *sureButton;
  29. @property (nonatomic, strong) UILabel *timeDisplayLabel;
  30. @property (nonatomic, strong) NSDateFormatter *formatter;
  31. @property (nonatomic, strong) AVAudioRecorder *avAudioRecorder;
  32. @property (nonatomic, strong) AVAudioPlayer *avAudioPlayer;
  33. @property (nonatomic,assign) CPDFSoundState soundState;
  34. @property (nonatomic,assign) CPDFAudioState state;
  35. @end
  36. @implementation CPDFSoundPlayBar
  37. #pragma mark - Initializers
  38. - (instancetype)initWithStyle:(CAnnotStyle *)annotStyle {
  39. if (self = [super init]) {
  40. self.annotStyle = annotStyle;
  41. [self setDateFormatter];
  42. [self initWithView];
  43. }
  44. return self;
  45. }
  46. #pragma mark - Accessors
  47. - (void)setDateFormatter {
  48. if (nil == _formatter) {
  49. _formatter = [[NSDateFormatter alloc] init];
  50. _formatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_GB"];
  51. [_formatter setTimeZone:[NSTimeZone timeZoneWithName:@"GMT"]];
  52. _formatter.dateFormat = @"HH:mm:ss";
  53. }
  54. }
  55. #pragma mark - Public
  56. - (void)showInView:(UIView *)subView soundState:(CPDFSoundState)soundState {
  57. self.soundState = soundState;
  58. if(CPDFSoundStatePlay == soundState) {
  59. self.frame = CGRectMake((subView.frame.size.width - 146.0)/2, subView.frame.size.height - 120 - 10, 146.0, 40);
  60. self.sureButton.hidden = YES;
  61. self.closeButton.frame = CGRectMake(self.frame.size.width - 8 - 24, (self.frame.size.height - 24.0)/2, 24, 24);
  62. [self.playButton setImage:[UIImage imageNamed:@"CPDFSoundImageNamePlay" inBundle:[NSBundle bundleForClass:self.class] compatibleWithTraitCollection:nil] forState:UIControlStateNormal];
  63. } else if (CPDFSoundStateRecord == soundState) {
  64. self.frame = CGRectMake((subView.frame.size.width - 174.0)/2, subView.frame.size.height - 120 - 10, 174.0, 40);
  65. self.sureButton.hidden = NO;
  66. self.sureButton.frame = CGRectMake(self.frame.size.width - 8 - 24, (self.frame.size.height - 24.0)/2, 24, 24);
  67. self.closeButton.frame = CGRectMake(self.frame.size.width - 8 - 24 - self.sureButton.frame.size.width - 10, (self.frame.size.height - 24.0)/2, 24, 24);
  68. [self.playButton setImage:[UIImage imageNamed:@"CPDFSoundImageNamePlay" inBundle:[NSBundle bundleForClass:self.class] compatibleWithTraitCollection:nil] forState:UIControlStateNormal];
  69. }
  70. self.layer.cornerRadius = 5.0;
  71. self.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleTopMargin;
  72. [subView addSubview:self];
  73. }
  74. - (void)setURL:(NSURL *)url {
  75. if (self.soundState == CPDFSoundStateRecord) {
  76. [self audioRecorderInitWithURL:url];
  77. }else if (self.soundState == CPDFSoundStatePlay){
  78. [self audioPlayerInitWithURL:url];
  79. }
  80. [self setDateFormatter];
  81. }
  82. - (void)startRecord {
  83. self.state = CPDFAudioState_Recording;
  84. AVAudioSession *audioSession = [AVAudioSession sharedInstance];
  85. [audioSession setCategory :AVAudioSessionCategoryPlayAndRecord error:nil];
  86. [audioSession setActive:YES error:nil];
  87. if ([_avAudioRecorder prepareToRecord]) {
  88. [_avAudioRecorder record];
  89. }else{
  90. NSLog(@"error:unprepare to record!");
  91. return;
  92. }
  93. }
  94. - (void)stopRecord {
  95. self.state = CPDFAudioState_Pause;
  96. [self stopTimer];
  97. if (_avAudioRecorder.currentTime > 0.1) {
  98. [_avAudioRecorder stop];
  99. NSURL *url = _avAudioRecorder.url;
  100. NSString *path = [url path];
  101. NSFileManager *manager = [NSFileManager defaultManager];
  102. if ([manager fileExistsAtPath:path]) {
  103. if ([self.delegate respondsToSelector:@selector(soundPlayBarRecordFinished:withFile:)]) {
  104. [self.delegate soundPlayBarRecordFinished:self withFile:path];
  105. }
  106. }
  107. } else {
  108. [_avAudioRecorder stop];
  109. }
  110. [self removeFromSuperview];
  111. }
  112. - (void)startAudioPlay {
  113. self.state = CPDFAudioState_Playing;
  114. NSError* err = nil;
  115. [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback withOptions:AVAudioSessionCategoryOptionDuckOthers error:&err];
  116. [[AVAudioSession sharedInstance] setActive:YES error:&err];
  117. if (err) {
  118. return;
  119. }
  120. [self startTimer];
  121. [_avAudioPlayer play];
  122. }
  123. - (void)stopAudioPlay{
  124. self.state = CPDFAudioState_Pause;
  125. if (_avAudioPlayer.playing) {
  126. [_avAudioPlayer stop];
  127. }
  128. [self stopTimer];
  129. _timeDisplayLabel.text = @"00:00:00";
  130. [self.playButton setImage:[UIImage imageNamed:@"CPDFSoundImageNameStop" inBundle:[NSBundle bundleForClass:self.class] compatibleWithTraitCollection:nil] forState:UIControlStateNormal];
  131. }
  132. #pragma mark - Private
  133. - (void)initWithView {
  134. self.backgroundColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0.8];
  135. self.playButton = [UIButton buttonWithType:UIButtonTypeCustom];
  136. self.playButton.frame = CGRectMake(8, 8, 24, 24);
  137. [self.playButton setImage:[UIImage imageNamed:@"CPDFSoundImageNamePlay" inBundle:[NSBundle bundleForClass:self.class] compatibleWithTraitCollection:nil] forState:UIControlStateNormal];
  138. [self.playButton addTarget:self action:@selector(buttonItemClicked_Play:) forControlEvents:UIControlEventTouchUpInside];
  139. [self addSubview:self.playButton];
  140. _timeDisplayLabel = [[UILabel alloc] init];
  141. _timeDisplayLabel.backgroundColor = [UIColor clearColor];
  142. _timeDisplayLabel.textColor = [UIColor whiteColor];
  143. _timeDisplayLabel.font = [UIFont systemFontOfSize:13.0];
  144. _timeDisplayLabel.text = @"00:00:00";
  145. [_timeDisplayLabel sizeToFit];
  146. _timeDisplayLabel.frame = CGRectMake(CGRectGetMaxX(self.playButton.frame)+ 10, 8, _timeDisplayLabel.frame.size.width + 20, 24.0);
  147. [self addSubview:_timeDisplayLabel];
  148. self.sureButton = [UIButton buttonWithType:UIButtonTypeCustom];
  149. self.sureButton.frame = CGRectMake(self.frame.size.width - 8 - 24, 8, 24, 24);
  150. self.sureButton.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin;
  151. [self.sureButton setImage:[UIImage imageNamed:@"CPDFSoundImageNameSure" inBundle:[NSBundle bundleForClass:self.class] compatibleWithTraitCollection:nil] forState:UIControlStateNormal];
  152. [self.sureButton addTarget:self action:@selector(buttonItemClicked_Sure:) forControlEvents:UIControlEventTouchUpInside];
  153. [self addSubview:self.sureButton];
  154. self.closeButton = [UIButton buttonWithType:UIButtonTypeCustom];
  155. self.closeButton.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleTopMargin;
  156. self.closeButton.frame = CGRectMake(self.frame.size.width - 8 - 24 - self.sureButton.frame.size.width - 10, 8, 24, 24);
  157. self.closeButton.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin;
  158. [self.closeButton setImage:[UIImage imageNamed:@"CPDFSoundImageNameClose" inBundle:[NSBundle bundleForClass:self.class] compatibleWithTraitCollection:nil] forState:UIControlStateNormal];
  159. [self.closeButton addTarget:self action:@selector(buttonItemClicked_Close:) forControlEvents:UIControlEventTouchUpInside];
  160. [self addSubview:self.closeButton];
  161. }
  162. - (void)startAudioRecord {
  163. [self setURL:nil];
  164. [self startTimer];
  165. [self startRecord];
  166. }
  167. - (void)startTimer {
  168. if (_voiceTimer) {
  169. [_voiceTimer invalidate];
  170. _voiceTimer = nil;
  171. }
  172. _voiceTimer = [NSTimer scheduledTimerWithTimeInterval:1.0/30.0 target:self selector:@selector(reflashAsTimeGoesBy) userInfo:nil repeats:YES];
  173. }
  174. - (void)stopTimer {
  175. if (_voiceTimer) {
  176. [_voiceTimer invalidate];
  177. _voiceTimer = nil;
  178. }
  179. }
  180. - (void)audioRecorderInitWithURL:(NSURL *)url {
  181. NSMutableDictionary *recordSetting = [[NSMutableDictionary alloc] init];
  182. [recordSetting setValue:[NSNumber numberWithInt:kAudioFormatLinearPCM] forKey:AVFormatIDKey];
  183. [recordSetting setValue:[NSNumber numberWithFloat:11025] forKey:AVSampleRateKey];
  184. [recordSetting setValue:[NSNumber numberWithInt:2] forKey:AVNumberOfChannelsKey];
  185. [recordSetting setValue:[NSNumber numberWithInt:16] forKey:AVLinearPCMBitDepthKey];
  186. [recordSetting setValue:[NSNumber numberWithInt:AVAudioQualityHigh] forKey:AVEncoderAudioQualityKey];
  187. if (!url) {
  188. NSString *path = NULL;
  189. NSFileManager *manager = [NSFileManager defaultManager];
  190. BOOL isDict = NO;
  191. BOOL dictOK = NO;
  192. if ([manager fileExistsAtPath:SOUND_TMP_DICT isDirectory:&isDict] && isDict){
  193. dictOK = YES;
  194. }else{
  195. if ([manager createDirectoryAtPath:SOUND_TMP_DICT withIntermediateDirectories:NO attributes:nil error:nil]) {
  196. dictOK = YES;
  197. }
  198. }
  199. if (dictOK) {
  200. for (NSInteger i=0; i<NSIntegerMax; i++) {
  201. path = [NSString stringWithFormat:@"%@/%@_%ld.%s",SOUND_TMP_DICT,@"tmp",i,"wav"];
  202. if (![manager fileExistsAtPath:path]) {
  203. break;
  204. }
  205. }
  206. }else{
  207. NSLog(@"tmp file dict error!");
  208. }
  209. url = [NSURL fileURLWithPath:path];
  210. }
  211. if (_avAudioRecorder) {
  212. _avAudioRecorder = nil;
  213. }
  214. NSError *error = nil;
  215. _avAudioRecorder = [[AVAudioRecorder alloc]initWithURL:url settings:recordSetting error:&error];
  216. _avAudioRecorder.meteringEnabled = YES;
  217. }
  218. - (void)audioPlayerInitWithURL:(NSURL *)url {
  219. if (_avAudioPlayer) {
  220. _avAudioPlayer = nil;
  221. }
  222. NSError *error = nil;
  223. _avAudioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:&error];
  224. [_avAudioPlayer setVolume:1.0];
  225. _avAudioPlayer.delegate = self;
  226. }
  227. #pragma mark - Action
  228. - (IBAction)buttonItemClicked_Play:(id)sender {
  229. if (self.soundState == CPDFSoundStateRecord) {
  230. if(CPDFAudioState_Pause == self.state) {
  231. [self.playButton setImage:[UIImage imageNamed:@"CPDFSoundImageNamePlay" inBundle:[NSBundle bundleForClass:self.class] compatibleWithTraitCollection:nil] forState:UIControlStateNormal];
  232. [self startTimer];
  233. [self startRecord];
  234. _state = CPDFAudioState_Recording;
  235. } else {
  236. [self.playButton setImage:[UIImage imageNamed:@"CPDFSoundImageNameRec" inBundle:[NSBundle bundleForClass:self.class] compatibleWithTraitCollection:nil] forState:UIControlStateNormal];
  237. [self stopTimer];
  238. [_avAudioRecorder pause];
  239. self.state = CPDFAudioState_Pause;
  240. }
  241. } else if(self.soundState == CPDFSoundStatePlay) {
  242. if(CPDFAudioState_Pause == self.state) {
  243. [self.playButton setImage:[UIImage imageNamed:@"CPDFSoundImageNamePlay" inBundle:[NSBundle bundleForClass:self.class] compatibleWithTraitCollection:nil] forState:UIControlStateNormal];
  244. [self startTimer];
  245. [_avAudioPlayer play];
  246. _state = CPDFAudioState_Playing;
  247. } else {
  248. [self.playButton setImage:[UIImage imageNamed:@"CPDFSoundImageNameStop" inBundle:[NSBundle bundleForClass:self.class] compatibleWithTraitCollection:nil] forState:UIControlStateNormal];
  249. [self stopTimer];
  250. [_avAudioPlayer pause];
  251. _state = CPDFAudioState_Pause;
  252. }
  253. }
  254. }
  255. - (IBAction)buttonItemClicked_Sure:(id)sender {
  256. if (self.soundState == CPDFSoundStateRecord) {
  257. [self stopRecord];
  258. }
  259. }
  260. - (IBAction)buttonItemClicked_Close:(id)sender {
  261. [self removeFromSuperview];
  262. if (self.soundState == CPDFSoundStateRecord) {
  263. [_avAudioRecorder stop];
  264. if ([self.delegate respondsToSelector:@selector(soundPlayBarRecordCancel:)]) {
  265. [self.delegate soundPlayBarRecordCancel:self];
  266. }
  267. } else if(self.soundState == CPDFSoundStatePlay) {
  268. [self stopAudioPlay];
  269. if ([self.delegate respondsToSelector:@selector(soundPlayBarPlayClose:)]) {
  270. [self.delegate soundPlayBarPlayClose:self];
  271. }
  272. }
  273. }
  274. - (void)reflashAsTimeGoesBy {
  275. NSTimeInterval time;
  276. if (self.soundState == CPDFSoundStateRecord) {
  277. time = _avAudioRecorder.currentTime;
  278. [_avAudioRecorder updateMeters];
  279. NSDate *dateToShow = [NSDate dateWithTimeIntervalSince1970:time];
  280. NSString *stringToShow = [_formatter stringFromDate:dateToShow];
  281. _timeDisplayLabel.text = stringToShow;
  282. if (time >= 3600 ) {
  283. [self stopRecord];
  284. }
  285. }else if (self.soundState == CPDFSoundStatePlay){
  286. time = _avAudioPlayer.currentTime;
  287. NSDate *dateToShow = [NSDate dateWithTimeIntervalSince1970:time];
  288. NSString *stringToShow = [_formatter stringFromDate:dateToShow];
  289. _timeDisplayLabel.text = stringToShow;
  290. }
  291. }
  292. #pragma mark - AvaudioPlayer delegate
  293. - (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag
  294. {
  295. [self stopAudioPlay];
  296. }
  297. - (void)audioPlayerEndInterruption:(AVAudioPlayer *)player {
  298. [self stopAudioPlay];
  299. }
  300. @end