Files
featherVoice/QXLive/Mine(音域)/View/歌手认证/QXAudioRecorderView.m
2025-12-04 14:11:00 +08:00

747 lines
27 KiB
Objective-C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// QXAudioRecorderView.m
// QXLive
//
// Created by 启星 on 2025/11/13.
//
#import "QXAudioRecorderView.h"
#import <AVFoundation/AVFoundation.h>
#import "UIButton+QX.h"
#import "QXCOSUploadManager.h"
#import "QXMineNetwork.h"
@interface QXAudioRecorderView() <AVAudioRecorderDelegate, AVAudioPlayerDelegate,UIGestureRecognizerDelegate>
// UI Components
@property (nonatomic, strong) UILabel *titleLabel;
@property (nonatomic, strong) UIView *recordContentView;
@property (nonatomic, strong) UIImageView *recordButton;
@property (nonatomic, strong) CAShapeLayer *progressLayer;
@property (nonatomic, strong) UILabel *timeLabel;
@property (nonatomic, strong) UILabel *stateLabel;
@property (nonatomic, strong) UIButton *playButton;
@property (nonatomic, strong) UIButton *resetButton;
@property (nonatomic, strong) UIButton *authBtn;
// Audio Components
@property (nonatomic, strong) AVAudioRecorder *audioRecorder;
@property (nonatomic, strong) AVAudioPlayer *audioPlayer;
@property (nonatomic, strong) NSURL *originalAudioFileURL;
@property (nonatomic, strong) NSURL *mp3AudioFileURL;
@property (nonatomic, strong) NSURL *tempAudioFileURL; // 临时拼接文件
// Recording State
@property (nonatomic, assign) AudioRecorderState state;
@property (nonatomic, assign) NSTimeInterval currentDuration; // 当前段时长
@property (nonatomic, assign) NSTimeInterval totalDuration; // 总时长
@property (nonatomic, assign) NSInteger recordingSessionCount; // 录制段数
@property (nonatomic, strong) NSTimer *recordingTimer;
@property (nonatomic, assign) BOOL isCancelled;
@property (nonatomic, strong) NSMutableArray<NSURL *> *recordedSegments; // 录制的音频段
@end
@implementation QXAudioRecorderView
- (instancetype)initWithFrame:(CGRect)frame {
return [self initWithFrame:frame maxDuration:300.0];
}
- (instancetype)initWithFrame:(CGRect)frame maxDuration:(NSTimeInterval)maxDuration {
self = [super initWithFrame:frame];
if (self) {
_maxDuration = maxDuration;
_showPlaybackButton = YES;
_state = AudioRecorderStateReady;
_recordedSegments = [NSMutableArray array];
_recordingSessionCount = 0;
[self setupUI];
[self setupAudioSession];
[self setupGestureRecognizers];
}
return self;
}
- (void)setupUI {
self.backgroundColor = [UIColor whiteColor];
[self addRoundedCornersWithRadius:16 byRoundingCorners:(UIRectCornerTopLeft|UIRectCornerTopRight)];
self.authBtn = [[UIButton alloc] initWithFrame:CGRectMake(SCREEN_WIDTH-ScaleWidth(57)-16, 16, ScaleWidth(57), ScaleWidth(20))];
[self.authBtn setImage:[UIImage imageNamed:@"singer_auth_btn"] forState:(UIControlStateNormal)];
[self.authBtn addTarget:self action:@selector(authAction) forControlEvents:(UIControlEventTouchUpInside)];
[self addSubview:self.authBtn];
self.titleLabel = [[UILabel alloc] initWithFrame:CGRectMake((SCREEN_WIDTH-150)/2, 18, 150, 24)];
self.titleLabel.textColor = RGB16(0x333333);
self.titleLabel.font = [UIFont boldSystemFontOfSize:16];
self.titleLabel.text = @"我的认证";
self.titleLabel.textAlignment = NSTextAlignmentCenter;
[self addSubview:self.titleLabel];
self.recordContentView = [[UIView alloc] init];
[self addSubview:self.recordContentView];
// 录制按钮
self.recordButton = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"singer_record_btn"]];
self.recordButton.userInteractionEnabled = YES;
[self.recordContentView addSubview:self.recordButton];
// 进度圆环
self.progressLayer = [CAShapeLayer layer];
self.progressLayer.fillColor = [UIColor clearColor].CGColor;
self.progressLayer.strokeColor = QXConfig.themeColor.CGColor;
self.progressLayer.lineWidth = 4;
self.progressLayer.strokeEnd = 0;
// 确保圆环从顶部开始
self.progressLayer.strokeStart = 0; // 从起点开始
self.progressLayer.strokeEnd = 0; // 初始进度为0
// 如果需要动画效果可以设置transform
self.progressLayer.transform = CATransform3DIdentity;
self.progressLayer.lineCap = kCALineCapRound;
[self.recordContentView.layer addSublayer:self.progressLayer];
// 时间标签
self.timeLabel = [[UILabel alloc] init];
self.timeLabel.text = [self formattedTime:0];
self.timeLabel.textAlignment = NSTextAlignmentCenter;
self.timeLabel.font = [UIFont systemFontOfSize:12];
self.timeLabel.textColor = RGB16A(0x000000, 0.45);
[self addSubview:self.timeLabel];
// 状态标签
self.stateLabel = [[UILabel alloc] init];
self.stateLabel.text = @"长按录音";
self.stateLabel.textAlignment = NSTextAlignmentCenter;
self.stateLabel.font = [UIFont systemFontOfSize:14];
self.stateLabel.textColor = [UIColor grayColor];
[self addSubview:self.stateLabel];
//
// // 录制段数标签
// self.sessionLabel = [[UILabel alloc] init];
// self.sessionLabel.text = @"";
// self.sessionLabel.textAlignment = NSTextAlignmentCenter;
// self.sessionLabel.font = [UIFont systemFontOfSize:12];
// self.sessionLabel.textColor = [UIColor lightGrayColor];
// [self addSubview:self.sessionLabel];
// 试听按钮
self.playButton = [[UIButton alloc] init];
[self.playButton setTitle:@"试听" forState:UIControlStateNormal];
[self.playButton setTitleColor:RGB16A(0x000000, 0.45) forState:(UIControlStateNormal)];
self.playButton.titleLabel.font = [UIFont systemFontOfSize:12];
[self.playButton setImage:[UIImage imageNamed:@"singer_try_listen"] forState:(UIControlStateNormal)];
[self.playButton addTarget:self action:@selector(playButtonTapped) forControlEvents:UIControlEventTouchUpInside];
// self.playButton.hidden = YES;
[self addSubview:self.playButton];
// 重置按钮
self.resetButton = [[UIButton alloc] init];
[self.resetButton setTitle:@"重置" forState:UIControlStateNormal];
[self.resetButton setImage:[UIImage imageNamed:@"singer_reset_record"] forState:(UIControlStateNormal)];
[self.resetButton setTitleColor:RGB16A(0x000000, 0.45) forState:(UIControlStateNormal)];
self.resetButton.titleLabel.font = [UIFont systemFontOfSize:12];
[self.resetButton addTarget:self action:@selector(resetButtonTapped) forControlEvents:UIControlEventTouchUpInside];
// self.resetButton.hidden = YES;
[self addSubview:self.resetButton];
[self.recordContentView bringSubviewToFront:self.recordButton];
}
- (void)setupAudioSession {
// AVAudioSession *audioSession = [AVAudioSession sharedInstance];
// NSError *error = nil;
// [audioSession setCategory:AVAudioSessionCategoryPlayAndRecord error:&error];
// [audioSession setActive:YES error:&error];
//
// if (error) {
// NSLog(@"Audio session setup error: %@", error);
// }
AVAudioSession *audioSession = [AVAudioSession sharedInstance];
NSError *error = nil;
// 使用 Playback 类别,会自动使用扬声器
[audioSession setCategory:AVAudioSessionCategoryPlayback error:&error];
// 或者使用 PlayAndRecord 但覆盖默认输出
[audioSession setCategory:AVAudioSessionCategoryPlayAndRecord
withOptions:AVAudioSessionCategoryOptionDefaultToSpeaker
error:&error];
[audioSession setActive:YES error:&error];
if (error) {
NSLog(@"Audio session setup error: %@", error);
}
}
- (void)setupGestureRecognizers {
// 长按手势 - 开始录制
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPress:)];
longPress.minimumPressDuration = 0.3;
longPress.delegate = self;
[self.recordButton addGestureRecognizer:longPress];
}
-(BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer{
return YES;
}
- (void)layoutSubviews {
[super layoutSubviews];
CGFloat centerX = CGRectGetWidth(self.bounds) / 2;
// 时间标签
self.timeLabel.frame = CGRectMake((SCREEN_WIDTH-150)/2, self.titleLabel.bottom+4, 150, 30);
// 录制按钮
self.recordContentView.frame = CGRectMake(centerX - 50.5, self.timeLabel.bottom+22, 101, 101);
self.recordButton.frame = CGRectMake(11, 11, 80, 80);
// 进度圆环
CGRect progressRect = CGRectInset(self.recordContentView.bounds, 4, 4);
UIBezierPath *progressPath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(CGRectGetMidX(progressRect), CGRectGetMidY(progressRect))
radius:progressRect.size.width / 2
startAngle:-M_PI_2 // 从顶部开始(-90度
endAngle:-M_PI_2 + M_PI * 2 // 顺时针一圈
clockwise:YES];
self.progressLayer.path = progressPath.CGPath;
// // 状态标签
self.stateLabel.frame = CGRectMake(0, self.recordContentView.bottom, CGRectGetWidth(self.bounds), 20);
//
// // 段数标签
// self.sessionLabel.frame = CGRectMake(0, 205, CGRectGetWidth(self.bounds), 20);
// 按钮
self.playButton.frame = CGRectMake(SCREEN_WIDTH-38-35, self.recordContentView.top+35, 38, 60);
self.resetButton.frame = CGRectMake(35, self.recordContentView.top+35, 38, 60);
[self.playButton qx_layoutButtonNOSizeToFitWithEdgeInsetsStyle:(QXButtonEdgeInsetsStyleTop) imageTitleSpace:2];
[self.resetButton qx_layoutButtonNOSizeToFitWithEdgeInsetsStyle:(QXButtonEdgeInsetsStyleTop) imageTitleSpace:2];
}
-(void)authAction{
///去认证
NSInteger minSecond = 60;
#if DEBUG
minSecond = 5;
# else
minSecond = 60;
#endif
if ((int)self.totalDuration < minSecond) {
NSString *toast = [NSString stringWithFormat:@"录音时长不得小于%ld秒",minSecond];
showToast(toast);
return;
}
if (self.originalAudioFileURL==nil) {
showToast(@"录制文件不存在");
return;
}
if (self.delegate && [self.delegate respondsToSelector:@selector(didClickAuthWithFileUrl:)]) {
[self.delegate didClickAuthWithFileUrl:self.originalAudioFileURL];
}
}
#pragma mark - Gesture Handlers
- (void)handleLongPress:(UILongPressGestureRecognizer *)gesture {
CGPoint touchPoint = [gesture locationInView:self];
switch (gesture.state) {
case UIGestureRecognizerStateBegan:
[self stopPlayback];
NSLog(@"开始录制 - 第%ld段", (long)self.recordingSessionCount + 1);
self.isCancelled = NO;
[self startRecording];
break;
case UIGestureRecognizerStateChanged:
// 检查是否上滑取消
if (touchPoint.y < CGRectGetMinY(self.recordButton.frame) - 20) {
if (!self.isCancelled) {
NSLog(@"取消录制");
self.isCancelled = YES;
[self updateUIForCancelled];
}
} else {
if (self.isCancelled) {
NSLog(@"恢复录制");
self.isCancelled = NO;
[self updateUIForRecording];
}
}
break;
case UIGestureRecognizerStateEnded:
case UIGestureRecognizerStateCancelled:
NSLog(@"结束录制 - 当前段时长: %.1f秒", self.currentDuration);
if (self.isCancelled) {
[self cancelRecording];
} else {
[self stopRecording];
}
break;
default:
break;
}
}
#pragma mark - Recording Control
- (void)startRecording {
if (self.state == AudioRecorderStateRecording) return;
// 配置录音器
NSURL *audioFileURL = [self generateAudioFileURL];
NSDictionary *recordSettings = @{
AVFormatIDKey: @(kAudioFormatLinearPCM),
AVSampleRateKey: @44100.0,
AVNumberOfChannelsKey: @1,
AVEncoderAudioQualityKey: @(AVAudioQualityHigh)
};
NSError *error = nil;
self.audioRecorder = [[AVAudioRecorder alloc] initWithURL:audioFileURL settings:recordSettings error:&error];
self.audioRecorder.delegate = self;
self.audioRecorder.meteringEnabled = YES;
if (error) {
NSLog(@"录音器初始化失败: %@", error);
if ([self.delegate respondsToSelector:@selector(qxAudioRecorderView:didFailWithError:)]) {
[self.delegate qxAudioRecorderView:self didFailWithError:error];
}
return;
}
if ([self.audioRecorder record]) {
self.state = AudioRecorderStateRecording;
self.recordingSessionCount++;
[self startRecordingTimer];
[self updateUIForRecording];
if ([self.delegate respondsToSelector:@selector(qxAudioRecorderView:didChangeState:)]) {
[self.delegate qxAudioRecorderView:self didChangeState:self.state];
}
}
}
- (void)stopRecording {
if (self.state != AudioRecorderStateRecording) return;
[self.recordingTimer invalidate];
self.recordingTimer = nil;
NSTimeInterval segmentDuration = self.currentDuration;
[self.audioRecorder stop];
// 保存录制的音频段
if (segmentDuration > 0.5) { // 只保存超过0.5秒的有效录音
[self.recordedSegments addObject:self.audioRecorder.url];
self.totalDuration += segmentDuration;
NSLog(@"保存音频段,时长: %.1f秒,总时长: %.1f秒", segmentDuration, self.totalDuration);
}
self.audioRecorder = nil;
self.currentDuration = 0;
self.state = AudioRecorderStateStopped;
[self updateUIForStopped];
// 如果有录音段,合并音频
if (self.recordedSegments.count > 0) {
[self mergeAudioSegments];
}
if ([self.delegate respondsToSelector:@selector(qxAudioRecorderView:didChangeState:)]) {
[self.delegate qxAudioRecorderView:self didChangeState:self.state];
}
}
- (void)cancelRecording {
if (self.state != AudioRecorderStateRecording) return;
[self.recordingTimer invalidate];
self.recordingTimer = nil;
[self.audioRecorder stop];
[self.audioRecorder deleteRecording];
self.audioRecorder = nil;
self.currentDuration = 0;
self.state = AudioRecorderStateStopped;
[self updateUIForStopped];
if ([self.delegate respondsToSelector:@selector(qxAudioRecorderView:didChangeState:)]) {
[self.delegate qxAudioRecorderView:self didChangeState:self.state];
}
}
#pragma mark - Audio Merging
- (void)mergeAudioSegments {
if (self.recordedSegments.count == 0) return;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 创建合并文件
NSURL *mergedFileURL = [self generateAudioFileURLWithExtension:@"m4a"];
// 创建音频组合
AVMutableComposition *composition = [AVMutableComposition composition];
CMTime currentTime = kCMTimeZero;
for (NSURL *audioURL in self.recordedSegments) {
AVURLAsset *audioAsset = [AVURLAsset assetWithURL:audioURL];
CMTimeRange timeRange = CMTimeRangeMake(kCMTimeZero, audioAsset.duration);
// 添加音频轨道
AVMutableCompositionTrack *audioTrack = [composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
AVAssetTrack *sourceAudioTrack = [[audioAsset tracksWithMediaType:AVMediaTypeAudio] firstObject];
if (sourceAudioTrack) {
[audioTrack insertTimeRange:timeRange ofTrack:sourceAudioTrack atTime:currentTime error:nil];
currentTime = CMTimeAdd(currentTime, audioAsset.duration);
}
}
// 导出合并的音频
AVAssetExportSession *exportSession = [AVAssetExportSession exportSessionWithAsset:composition presetName:AVAssetExportPresetAppleM4A];
exportSession.outputURL = mergedFileURL;
exportSession.outputFileType = AVFileTypeAppleM4A;
[exportSession exportAsynchronouslyWithCompletionHandler:^{
dispatch_async(dispatch_get_main_queue(), ^{
if (exportSession.status == AVAssetExportSessionStatusCompleted) {
self.originalAudioFileURL = mergedFileURL;
NSLog(@"音频合并完成,总时长: %.1f秒", self.totalDuration);
// 转换为MP3
[self convertToMP3];
} else {
NSLog(@"音频合并失败: %@", exportSession.error);
}
});
}];
});
}
#pragma mark - Reset Function
- (void)resetRecording {
NSLog(@"重置录音");
[self stopPlayback];
[self cancelRecording];
// 清理所有录音段
for (NSURL *segmentURL in self.recordedSegments) {
[[NSFileManager defaultManager] removeItemAtURL:segmentURL error:nil];
}
[self.recordedSegments removeAllObjects];
// 清理合并文件
if (self.originalAudioFileURL) {
[[NSFileManager defaultManager] removeItemAtURL:self.originalAudioFileURL error:nil];
self.originalAudioFileURL = nil;
}
if (self.mp3AudioFileURL) {
[[NSFileManager defaultManager] removeItemAtURL:self.mp3AudioFileURL error:nil];
self.mp3AudioFileURL = nil;
}
// 重置状态
self.totalDuration = 0;
self.currentDuration = 0;
self.recordingSessionCount = 0;
self.state = AudioRecorderStateReady;
[self updateUIForReady];
if ([self.delegate respondsToSelector:@selector(qxAudioRecorderView:didChangeState:)]) {
[self.delegate qxAudioRecorderView:self didChangeState:self.state];
}
}
#pragma mark - Playback Control
- (void)playButtonTapped {
if (self.state == AudioRecorderStatePlaying) {
[self stopPlayback];
} else {
[self playRecording];
}
}
- (void)playRecording {
if (self.state == AudioRecorderStatePlaying) return;
NSURL *playbackURL = self.mp3AudioFileURL ?: self.originalAudioFileURL;
if (!playbackURL){
showToast(@"您还未录制声音");
return;
}
// 播放前切换到扬声器
// AVAudioSession *audioSession = [AVAudioSession sharedInstance];
// NSError *error = nil;
// [audioSession setCategory:AVAudioSessionCategoryPlayback error:&error];
// [audioSession overrideOutputAudioPort:AVAudioSessionPortOverrideSpeaker error:&error];
// [audioSession setActive:YES error:&error];
NSError *playerError = nil;
self.audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:playbackURL error:&playerError];
self.audioPlayer.delegate = self;
if (playerError) {
NSLog(@"播放器初始化失败: %@", playerError);
return;
}
if ([self.audioPlayer play]) {
self.state = AudioRecorderStatePlaying;
[self updateUIForPlaying];
if ([self.delegate respondsToSelector:@selector(qxAudioRecorderView:didChangeState:)]) {
[self.delegate qxAudioRecorderView:self didChangeState:self.state];
}
}
}
- (void)stopPlayback {
if (self.state != AudioRecorderStatePlaying) return;
[self.audioPlayer stop];
self.audioPlayer = nil;
self.state = AudioRecorderStateStopped;
[self updateUIForStopped];
if ([self.delegate respondsToSelector:@selector(qxAudioRecorderView:didChangeState:)]) {
[self.delegate qxAudioRecorderView:self didChangeState:self.state];
}
}
- (void)resetButtonTapped {
[self resetRecording];
}
#pragma mark - Timer
- (void)startRecordingTimer {
self.currentDuration = 0;
self.recordingTimer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(updateRecordingProgress) userInfo:nil repeats:YES];
}
- (void)updateRecordingProgress {
if (self.state != AudioRecorderStateRecording) return;
self.currentDuration += 0.1;
// 更新UI
[self updateProgress];
[self updateTimeLabel];
// 通知代理进度更新
if ([self.delegate respondsToSelector:@selector(qxAudioRecorderView:didUpdateProgress:totalDuration:)]) {
[self.delegate qxAudioRecorderView:self didUpdateProgress:self.currentDuration totalDuration:self.totalDuration + self.currentDuration];
}
// 检查是否达到最大时长
if ((self.totalDuration + self.currentDuration) >= self.maxDuration) {
[self stopRecording];
}
}
#pragma mark - UI Updates
- (void)updateUIForReady {
self.progressLayer.strokeEnd = 0;
self.timeLabel.text = [self formattedTime:0];
self.stateLabel.text = @"长按录音";
//
// self.playButton.hidden = YES;
// self.resetButton.hidden = YES;
}
- (void)updateUIForRecording {
self.stateLabel.text = self.isCancelled ? @"松开手指取消" : @"录音中...松开结束";
// self.playButton.hidden = YES;
// self.resetButton.hidden = YES;
}
- (void)updateUIForStopped {
self.stateLabel.text = @"继续录音";
if (self.totalDuration > 0) {
if (self.showPlaybackButton) {
// self.playButton.hidden = NO;
// self.resetButton.hidden = NO;
[self.playButton setTitle:@"试听" forState:UIControlStateNormal];
[self.playButton setImage:[UIImage imageNamed:@"singer_try_listen"] forState:UIControlStateNormal];
}
} else {
// self.playButton.hidden = YES;
// self.resetButton.hidden = YES;
}
// self.stateLabel.textColor = [UIColor grayColor];
}
- (void)updateUIForPlaying {
// self.stateLabel.text = @"试听中...";
// self.stateLabel.textColor = [UIColor blueColor];
[self.playButton setTitle:@"停止" forState:UIControlStateNormal];
[self.playButton setImage:[UIImage imageNamed:@"singer_listen_stop"] forState:UIControlStateNormal];
}
- (void)updateUIForCancelled {
// self.stateLabel.text = @"松开手指取消";
// self.stateLabel.textColor = [UIColor redColor];
}
- (void)updateProgress {
CGFloat totalProgress = (self.totalDuration + self.currentDuration) / self.maxDuration;
self.progressLayer.strokeEnd = totalProgress;
// // 根据进度改变颜色
// if (totalProgress > 0.8) {
// self.progressLayer.strokeColor = [UIColor redColor].CGColor;
// } else if (totalProgress > 0.6) {
// self.progressLayer.strokeColor = [UIColor orangeColor].CGColor;
// } else {
// self.progressLayer.strokeColor = [UIColor colorWithRed:0.0 green:0.48 blue:1.0 alpha:1.0].CGColor;
// }
}
- (void)updateTimeLabel {
NSTimeInterval currentTotal = self.totalDuration + self.currentDuration;
self.timeLabel.text = [self formattedTime:currentTotal];
}
#pragma mark - MP3 Conversion
- (void)convertToMP3 {
// if (!self.originalAudioFileURL) return;
//
// dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// NSString *mp3FileName = [NSString stringWithFormat:@"%@.mp3", [[NSUUID UUID] UUIDString]];
// NSString *mp3FilePath = [NSTemporaryDirectory() stringByAppendingPathComponent:mp3FileName];
// self.mp3AudioFileURL = [NSURL fileURLWithPath:mp3FilePath];
//
// @try {
// int read, write;
//
// FILE *pcm = fopen([[self.originalAudioFileURL path] UTF8String], "rb");
// FILE *mp3 = fopen([mp3FilePath UTF8String], "wb");
//
// const int PCM_SIZE = 8192;
// const int MP3_SIZE = 8192;
// short int pcm_buffer[PCM_SIZE * 2];
// unsigned char mp3_buffer[MP3_SIZE];
//
// lame_t lame = lame_init();
// lame_set_in_samplerate(lame, 44100);
// lame_set_VBR(lame, vbr_default);
// lame_init_params(lame);
//
// do {
// read = (int)fread(pcm_buffer, 2 * sizeof(short int), PCM_SIZE, pcm);
// if (read == 0)
// write = lame_encode_flush(lame, mp3_buffer, MP3_SIZE);
// else
// write = lame_encode_buffer_interleaved(lame, pcm_buffer, read, mp3_buffer, MP3_SIZE);
//
// fwrite(mp3_buffer, write, 1, mp3);
//
// } while (read != 0);
//
// lame_close(lame);
// fclose(mp3);
// fclose(pcm);
// }
// @catch (NSException *exception) {
// NSLog(@"MP3转换失败: %@", exception);
// self.mp3AudioFileURL = nil;
// }
// @finally {
// dispatch_async(dispatch_get_main_queue(), ^{
// if (self.mp3AudioFileURL && [self.delegate respondsToSelector:@selector(audioRecorderView:didFinishRecordingWithMP3File:duration:)]) {
// [self.delegate audioRecorderView:self didFinishRecordingWithMP3File:self.mp3AudioFileURL duration:self.totalDuration];
// }
// });
// }
// });
}
#pragma mark - Utility Methods
- (NSURL *)generateAudioFileURL {
return [self generateAudioFileURLWithExtension:@"caf"];
}
- (NSURL *)generateAudioFileURLWithExtension:(NSString *)extension {
long long time = [[NSDate date] timeIntervalSince1970];
NSString *fileName = [NSString stringWithFormat:@"%lld.%@", time ,extension];
NSString *filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:fileName];
return [NSURL fileURLWithPath:filePath];
}
- (NSString *)formattedTime:(NSTimeInterval)time {
int minutes = (int)time / 60;
int seconds = (int)time % 60;
int maxMinutes = (int)_maxDuration / 60;
int maxSeconds = (int)_maxDuration % 60;
return [NSString stringWithFormat:@"%02d:%02d | %02d:%02d", minutes, seconds,maxMinutes,maxSeconds];
}
- (NSString *)getRecordingStatus {
if (self.totalDuration > 0) {
return [NSString stringWithFormat:@"已录制%.1f秒,共%ld段", self.totalDuration, (long)self.recordingSessionCount];
} else {
return @"未开始录制";
}
}
#pragma mark - AVAudioRecorderDelegate
- (void)audioRecorderDidFinishRecording:(AVAudioRecorder *)recorder successfully:(BOOL)flag {
NSLog(@"录音完成: %@", flag ? @"成功" : @"失败");
}
- (void)audioRecorderEncodeErrorDidOccur:(AVAudioRecorder *)recorder error:(NSError *)error {
NSLog(@"录音编码错误: %@", error);
if ([self.delegate respondsToSelector:@selector(qxAudioRecorderView:didFailWithError:)]) {
[self.delegate qxAudioRecorderView:self didFailWithError:error];
}
}
#pragma mark - AVAudioPlayerDelegate
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag {
[self stopPlayback];
}
- (void)audioPlayerDecodeErrorDidOccur:(AVAudioPlayer *)player error:(NSError *)error {
NSLog(@"播放解码错误: %@", error);
[self stopPlayback];
}
#pragma mark - Public Properties
- (NSURL *)currentAudioFileURL {
return self.mp3AudioFileURL ?: self.originalAudioFileURL;
}
- (void)dealloc {
[self.recordingTimer invalidate];
[self.audioRecorder stop];
[self.audioPlayer stop];
}
@end