局域網內端到端的聊天項目(七)

效果圖:
iPhone.gif
iPod.gif
  • 上一篇已實現單個及多個視頻的發送
  • 接下來實現 語音的錄制/傳輸/播放功能
  • 同樣通過鍵盤工具條來觸發
  • 通過封裝語音錄制/播放工具類來獲取資源
一.鍵盤工具條新增語音按鈕
IMG_2575(20171205-115040).jpg
// 新增三種狀態
typedef NS_ENUM(NSInteger, RecordVoiceState)
{
    RecordVoiceStateBegin = 0,  // 開始
    RecordVoiceStateFinish = 1, // 結束
    RecordVoiceStateCancle = 2  // 取消
};

// 新增delegate回調
@protocol ESKeyBoardToolViewDelegate <NSObject>
@optional
/// 錄音 開始 結束
- (void)ESKeyBoardToolViewRecordWithState:(RecordVoiceState)state;
@end

// 新增語音/語音錄制按鈕
@interface ESKeyBoardToolView () <UITextViewDelegate>
/// 語音按鈕
@property (nonatomic, strong) UIButton *voiceButton;
/// 開始錄音按鈕
@property (nonatomic, strong) UIButton *beginRecordButton;
@end

// 語音/開始錄制按鈕的監聽
- (void)setupInit
{
    // 語音按鈕
    self.voiceButton = [[UIButton alloc] init];
    [self.voiceButton setImage:[UIImage imageNamed:@"音頻_bt"] forState:UIControlStateNormal];
    [self.voiceButton setImage:[UIImage imageNamed:@"文本_bt"] forState:UIControlStateSelected];
    [self.voiceButton addTarget:self action:@selector(voiceButtonDidClick:) forControlEvents:UIControlEventTouchUpInside];
    [self addSubview:self.voiceButton];
    
    // 開始錄音按鈕
    self.beginRecordButton = [[UIButton alloc] init];
    self.beginRecordButton.backgroundColor = [UIColor colorWithRed:231.0/255.0 green:232.0/255.0 blue:238.0/255.0 alpha:0.5];
    [self.beginRecordButton setTitle:@"按住 說話" forState:UIControlStateNormal];
    [self.beginRecordButton setTitle:@"松開 結束" forState:UIControlStateHighlighted];
    [self.beginRecordButton setBackgroundImage:[UIImage imageWithColor:[UIColor grayColor]] forState:UIControlStateHighlighted];
    [self.beginRecordButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
    [self.beginRecordButton setTitleColor:[UIColor blackColor] forState:UIControlStateSelected];
    [self.beginRecordButton addTarget:self action:@selector(recordButtonDidBegin:) forControlEvents:UIControlEventTouchDown];
    [self.beginRecordButton addTarget:self action:@selector(recordButtonDidFinish:) forControlEvents:UIControlEventTouchUpInside];
    [self.beginRecordButton addTarget:self action:@selector(recordButtonDidCancle:) forControlEvents:UIControlEventTouchUpOutside];
    [self addSubview:self.beginRecordButton];
    self.beginRecordButton.hidden = YES;
}
#pragma mark - event
// 語音按鈕
- (void)voiceButtonDidClick:(UIButton *)button{
    button.selected = !button.selected;
    self.beginRecordButton.hidden = !button.selected;
    if (button.selected) {
        [self.inputTextView resignFirstResponder];
        [self exitKeyBoardInputView];
    }
}
// 開始錄音
- (void)recordButtonDidBegin:(UIButton *)button{
    if ([self.delegate respondsToSelector:@selector(ESKeyBoardToolViewRecordWithState:)]) {
        [self.delegate ESKeyBoardToolViewRecordWithState:RecordVoiceStateBegin];
    }
}
// 發送錄音
- (void)recordButtonDidFinish:(UIButton *)button{
    if ([self.delegate respondsToSelector:@selector(ESKeyBoardToolViewRecordWithState:)]) {
        [self.delegate ESKeyBoardToolViewRecordWithState:RecordVoiceStateFinish];
    }
}
// 取消
- (void)recordButtonDidCancle:(UIButton *)button{
    if ([self.delegate respondsToSelector:@selector(ESKeyBoardToolViewRecordWithState:)]) {
        [self.delegate ESKeyBoardToolViewRecordWithState:RecordVoiceStateCancle];
    }
}
二.語音錄制/播放工具類 VoiceManager.h
//
//  VoiceManager.h
//  ZPS
//
//  Created by 張海軍 on 2017/12/4.
//  Copyright ? 2017年 baoqianli. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface VoiceManager : NSObject
/// 當前錄音url地址
@property (nonatomic, strong) NSURL *currentRecordUrl;
+ (instancetype)voiceManagerShare;
// 開始錄制
- (void)beginRecordWithURL:(NSURL *)url;
// 停止/完成錄制
- (void)stopRecordCompletion:(void(^)(BOOL finished,float duration))completion;
// 取消錄制
- (void)cancleRecord;
// 播放
- (void)playAudioWithURL:(NSURL *)url;
@end
VoiceManager.m 包含錄制時音量大小的動畫 (在keyWindow上加一個view)
// 開始錄音
- (void)beginRecordWithURL:(NSURL *)url{
    self.currentRecordUrl = url;
    [self getAudioRecorderWithUrl:url];
    [self.audioRecorder record];
    [self volumeBgView];
    if (!self.timer) {
        self.timer = [NSTimer scheduledTimerWithTimeInterval:0 target:self selector:@selector(volumeChange) userInfo:nil repeats:YES];
    }
}

// 停止 / 完成
- (void)stopRecordCompletion:(void (^)(BOOL finish,float duration))completion{
    self.stopCompletion = completion;
    [self.audioRecorder stop];
    [self recordStopHandle];
}

// 取消
- (void)cancleRecord{
    [self.audioRecorder stop];
    BOOL delete = [self.audioRecorder deleteRecording];
    self.volumeStateLabel.text = @"取消發送";
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [self recordStopHandle];
    });
    NSLog(@"delete = %zd",delete);
}

// 開始播放
- (void)playAudioWithURL:(NSURL *)url{
    if (url == nil) {
        return;
    }
    [self getAudioPlayerWithUrl:url];
    [self.audioPlayer prepareToPlay];
    [self.audioPlayer play];
}


///  設置音頻會話
-(void)setAudioSession{
    AVAudioSession *audioSession=[AVAudioSession sharedInstance];
    //設置為播放和錄音狀態,以便可以在錄制完之后播放錄音
    [audioSession setCategory:AVAudioSessionCategoryPlayAndRecord error:nil];
    [audioSession setActive:YES error:nil];
    NSError *audioError = nil;
    BOOL success = [audioSession overrideOutputAudioPort:AVAudioSessionPortOverrideSpeaker error:&audioError];
    if(!success){
        NSLog(@"error doing outputaudioportoverride - %@", [audioError localizedDescription]);
    }
}

/// 取得錄音文件設置
-(NSDictionary *)getAudioSetting{
    NSMutableDictionary *dicM=[NSMutableDictionary dictionary];
    //設置錄音格式
    [dicM setObject:@(kAudioFormatLinearPCM) forKey:AVFormatIDKey];
    //設置錄音采樣率,8000是電話采樣率,對于一般錄音已經夠了
    [dicM setObject:@(44100) forKey:AVSampleRateKey];
    //設置通道,這里采用單聲道
    [dicM setObject:@(1) forKey:AVNumberOfChannelsKey];
    //每個采樣點位數,分為8、16、24、32
    [dicM setObject:@(16) forKey:AVLinearPCMBitDepthKey];
    //是否使用浮點數采樣
    [dicM setObject:@(YES) forKey:AVLinearPCMIsFloatKey];
    //....其他設置等
    return dicM;
}
三.點擊開始錄音后的回調處理
#pragma mark - ESKeyBoardToolViewDelegate
// 語音相關
- (void)ESKeyBoardToolViewRecordWithState:(RecordVoiceState)state{
    switch (state) {
        case RecordVoiceStateBegin:{
            NSString *fileName = [[NSString stringWithFormat:@"%zd",[[NSDate date] timeIntervalSinceReferenceDate]] stringByAppendingString:@".caf"];
            NSString *savePath = [[SocketManager shareSockManager].dataSavePath stringByAppendingPathComponent:[fileName lastPathComponent]];
            [[VoiceManager voiceManagerShare] beginRecordWithURL:[NSURL fileURLWithPath:savePath]];
        }
            break;
        case RecordVoiceStateFinish:{
            WS(weakSelf);
            [[VoiceManager voiceManagerShare] stopRecordCompletion:^(BOOL finished,float duration) {
                if (duration < 1.0) {
                    NSLog(@"時長小于1s 不發送");
                    return ;
                }
                ChatMessageModel *messageM = [ChatMessageModel new];
                messageM.isFormMe = YES;
                messageM.userName = [UIDevice currentDevice].name;
                messageM.chatMessageType = ChatMessageAudio;
                messageM.mediaMessageUrl = [VoiceManager voiceManagerShare].currentRecordUrl;
                messageM.mediaDuration = duration;
                messageM.fileName = [[NSString stringWithFormat:@"%zd",[[NSDate date] timeIntervalSinceReferenceDate]] stringByAppendingString:@".caf"];
                NSData *audioData = [NSData dataWithContentsOfURL:messageM.mediaMessageUrl options:NSDataReadingMappedIfSafe error:nil];
                messageM.fileSize = audioData.length;
                [weakSelf sendMessageWithItem:messageM];
            }];
        }
            break;
        case RecordVoiceStateCancle:{
            [[VoiceManager voiceManagerShare] cancleRecord];
        }
            break;
            
        default:
            break;
    }
}
四.SocketManager 類中對語音不需要進行單獨的處理 跟視頻/圖片的傳輸方式是一樣的
/// 發送數據
- (void)sendMessageWithItem:(ChatMessageModel *)item{
    item.atSendArrayIndex = self.needSendMoreItems.count;
    [self.needSendMoreItems addObject:item];
    if (self.needSendMoreItems.count < 2) {
        [self sendOneMessageItem:item];
    }else{
        
    }
}

- (void)sendOneMessageItem:(ChatMessageModel *)item{
    self.currentSendItem = item;
    NSData *textData = [self creationMessageDataWithItem:item];
    [self writeMediaMessageWithData:textData];
}

// 創建消息體
- (NSData *)creationMessageDataWithItem:(ChatMessageModel *)item{
    NSMutableDictionary *messageData = [NSMutableDictionary dictionary];
    messageData[@"fileName"] = item.fileName;
    messageData[@"userName"] = item.userName;
    messageData[@"chatMessageType"] = [NSNumber numberWithInt:item.chatMessageType];
    messageData[@"fileSize"] = [NSNumber numberWithInteger:item.fileSize];
    messageData[@"mediaDuration"] = [NSNumber numberWithFloat:item.mediaDuration];
    if (item.chatMessageType == ChatMessageText) {
        messageData[@"messageContent"] = item.messageContent;
    }else if (item.chatMessageType == ChatMessageImage || item.chatMessageType == ChatMessageVideo || item.chatMessageType == ChatMessageAudio){
        item.isWaitAcceptFile = YES;
        messageData[@"isWaitAcceptFile"] = [NSNumber numberWithBool:YES];
    }
    NSString *bodStr = [NSString hj_dicToJsonStr:messageData];
    return [bodStr dataUsingEncoding:NSUTF8StringEncoding];
}

// 圖片或者視頻文件傳輸
- (void)imageOrVideoFileSend:(ChatMessageModel *)sendItem{
    if (sendItem.chatMessageType == ChatMessageImage) {
        NSData *sendData = UIImagePNGRepresentation(sendItem.temImage);
        [self writeMediaMessageWithData:sendData];
    }else if (sendItem.chatMessageType == ChatMessageVideo){
        PHAsset *asset = (PHAsset *)sendItem.asset;
        [ZPPublicMethod getfilePath:asset Complete:^(NSURL *fileUrl) {
            dispatch_sync(dispatch_get_main_queue(), ^{
                sendItem.mediaMessageUrl = fileUrl;
                NSData *sendData = [NSData dataWithContentsOfURL:sendItem.mediaMessageUrl options:NSDataReadingMappedIfSafe error:nil];
                [self writeMediaMessageWithData:sendData];
            });
        }];
    }else if (sendItem.chatMessageType == ChatMessageAudio){
        NSData *sendData = [NSData dataWithContentsOfURL:sendItem.mediaMessageUrl options:NSDataReadingMappedIfSafe error:nil];
        [self writeMediaMessageWithData:sendData];
    }
    
}

// 傳輸數據到服務端
- (void)writeMediaMessageWithData:(NSData *)sendData{
    self.currentSendTag += 1;
    self.currentSendItem.sendTag = self.currentSendTag;
    if (self.clientSocketArray.count > 0) {
        GCDAsyncSocket *clientSocket = [self.clientSocketArray firstObject];
        [clientSocket writeData:sendData withTimeout:-1 tag:self.currentSendItem.sendTag];
    }else{
        [self.tcpSocketManager writeData:sendData withTimeout:-1 tag:self.currentSendItem.sendTag];
    }
}

// 媒體文件接受完成后發送的消息
- (void)sendMediaAcceptEndMessage{
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSData *data = [FILE_ACCEPT_END dataUsingEncoding:NSUTF8StringEncoding];
        self.currentSendItem = nil;
        if (self.clientSocketArray.count > 0) {
            GCDAsyncSocket *clientSocket = [self.clientSocketArray firstObject];
            [clientSocket writeData:data withTimeout:-1 tag:-99999];
        }else{
            [self.tcpSocketManager writeData:data withTimeout:-1 tag:-99999];
        }
    });
}

// 發送下一個消息體
- (void)sendNextMessage{
    if (self.needSendMoreItems.count > 0) {
        [self sendOneMessageItem:[self.needSendMoreItems firstObject]];
    }
}
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,242評論 25 708
  • 發現 關注 消息 iOS 第三方庫、插件、知名博客總結 作者大灰狼的小綿羊哥哥關注 2017.06.26 09:4...
    肇東周閱讀 12,229評論 4 61
  • 姑娘 牽著手 走在烈日下 走在青石板上 走在斑駁的古城中 這是我從未有過的體驗 我想彈起吉他為你唱首歌 一首只屬于...
    呂院長閱讀 280評論 2 3
  • 楊朱學派:兩千年絕學及新詮釋 【作 者】李伯聰 楊朱其人兩千多年來蒙受惡名,其事跡無載于正史。但許多可靠的證據表明...
    gdlyz閱讀 1,324評論 0 2
  • 【初心】 寶貝,因為出生時你嗆到了羊水 所以你的出生讓家人揪心...
    愛華王閱讀 436評論 3 6