xmpp整理筆記:發送圖片資訊和聲音資訊,xmpp圖片資訊
圖片和音頻檔案發送的基本思路就是:
先將圖片轉化成二進位檔案,然後將二進位檔案進行base64編碼,編碼後成字串。在即將發送的message內添加一個子節點,節點的stringValue(節點的值)設定這個編碼後的字串。然後訊息發出後取出訊息檔案的時候,通過messageType 先判斷是不是圖片資訊,如果是圖片資訊先通過自己之前設定的節點名稱,把這個子節點的stringValue取出來,應該是一個base64之後的字串,
往期回顧:
xmpp整理筆記:聊天資訊的發送與顯示 http://www.cnblogs.com/dsxniubility/p/4307073.html
xmpp整理筆記:環境的快速配置(附安裝包) http://www.cnblogs.com/dsxniubility/p/4304570.html
xmpp整理筆記:xmppFramework架構的匯入和介紹 http://www.cnblogs.com/dsxniubility/p/4307057.html
xmpp整理筆記:使用者網路連接及好友管理 http://www.cnblogs.com/dsxniubility/p/4307066.html
一。圖片發送
如果你不是在董鉑然部落格園看到本文,請點擊查看原文
圖片是通過介面的加號點擊彈出相簿介面,然後點擊相簿中的某張圖片,相簿退下,圖片發出
- (IBAction)setPhoto { UIImagePickerController *picker = [[UIImagePickerController alloc]init]; picker.delegate = self; [self presentViewController:picker animated:YES completion:nil];}
這是加號點擊方法,之後設定UIImagePickerController的代理,然後再遵守對應的協議
這裡需要注意的是,遵守了UIImagePickerControllerDelegate的 同時還必須要遵守 UINavigationControllerDelegate。協議
下面就是彈出相簿點擊了一張圖片後觸發的代理方法,都是常用方法在此也不過多解釋。
#pragma mark - ******************** imgPickerController代理方法- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info{ UIImage *image = info[UIImagePickerControllerOriginalImage]; NSData *data = UIImagePNGRepresentation(image); [self sendMessageWithData:data bodyName:@"image"]; [self dismissViewControllerAnimated:YES completion:nil];}
其中的sendMessageWithData: bodyName: 是自訂的方法
此方法的功能就是傳入一個data二進位檔案 和 檔案的類型,就把這個檔案發出去。
之所有在後面有bodyName,讓使用者傳入一個類型名,是為了區分發送圖片和發送音頻
方法內代碼如下:
/** 發送二進位檔案 */- (void)sendMessageWithData:(NSData *)data bodyName:(NSString *)name{ XMPPMessage *message = [XMPPMessage messageWithType:@"chat" to:self.chatJID]; [message addBody:name]; // 轉換成base64的編碼 NSString *base64str = [data base64EncodedStringWithOptions:0]; // 設定節點內容 XMPPElement *attachment = [XMPPElement elementWithName:@"attachment" stringValue:base64str]; // 包含子節點 [message addChild:attachment]; // 發送訊息 [[SXXMPPTools sharedXMPPTools].xmppStream sendElement:message];}
這個方法內流程就是一開始說得,先編碼再發送。這個自訂的方法同樣適用於發送音頻資訊。
二。圖片的顯示
這個是在tableView資料來源方法中,取出資訊即將賦值之前多了一層判斷,如果是圖片資訊,採用下面的方法賦值。
關於基本發送流程哪裡忘了可以查看普通文本資訊的發送方法:
if ([message.body isEqualToString:@"image"]) { XMPPMessage *msg = message.message; for (XMPPElement *node in msg.children) { // 取出訊息的解碼 NSString *base64str = node.stringValue; NSData *data = [[NSData alloc]initWithBase64EncodedString:base64str options:0]; UIImage *image = [[UIImage alloc]initWithData:data]; // 把圖片在label中顯示 NSTextAttachment *attach = [[NSTextAttachment alloc]init]; attach.image = [image scaleImageWithWidth:200]; NSAttributedString *attachStr = [NSAttributedString attributedStringWithAttachment:attach]; // 用了這個label的屬性賦值方法,就可以忽略那個普通的賦值方法 cell.messageLabel.attributedText = attachStr; [self.view endEditing:YES]; } }
這其中用到了一個 scaleImageWithWidth:方法,這個方法是傳入一個允許的最大寬度width,然後這個方法內部先判斷,如片大小是否超過最大值,如果沒有超過最大值就是圖片有多大發多大,如果圖片的尺寸超過了最大寬度,就把圖片的整體尺寸都等比例縮小到正好等於最大寬度的尺寸。這其中要用到Quartz2D的內容相關的知識。
這個方法可以寫成UIimage的分類,代碼如下
/** 把圖片縮小到指定的寬度範圍內為止 */- (UIImage *)scaleImageWithWidth:(CGFloat)width{ if (self.size.width <width || width <= 0) { return self; } CGFloat scale = self.size.width/width; CGFloat height = self.size.height/scale; CGRect rect = CGRectMake(0, 0, width, height); // 開始上下文 目標大小是 這麼大 UIGraphicsBeginImageContext(rect.size); // 在指定地區內繪製映像 [self drawInRect:rect]; // 從上下文中獲得繪製結果 UIImage *resultImage = UIGraphicsGetImageFromCurrentImageContext(); // 關閉上下文返回結果 UIGraphicsEndImageContext(); return resultImage;}
三。音訊發送
音訊發送,與之前圖片的發送,有一定的相似,也有一些不同。音頻發送的核心思想,是按下按鈕開始錄音,鬆開手結束錄音並且儲存錄音。因此需要處理按鈕的按下和抬手兩個監聽方法。但是其中有一個蘋果的bug: 自訂的按鈕無法同時處理TouchUpInSide 和 TouchDown。 就是按下按鈕不鬆手是一個列印,手一鬆開一個列印。這是不行的,都是手一松兩個同時列印。(除非按鈕特別大,一般小按鈕無法同時監聽這兩個點擊事件)。但是蘋果內建的系統按鈕卻可以,不管多小,比如buttonWithTypeAdd(小加號按鈕)都可以,因此設定點擊聲音按鈕之後下面出現一個inputView,上面是可以同時處理這兩個時間的按鈕。通過這個按鈕來控制開始錄音和結束錄音。儲存之後,也是轉化成data二進位檔案,然後再通過base64編碼。然後加入子節點,和圖片類似發過去。接收的時候,也是取出節點內的stringValue解碼。但是顯示在tableview的cell中的是聲音的時間,點擊這個cell觸發聲音播放時間。從而播放音頻。播放時cell內部的某些樣式變化也是可以控制的。
先把介面中的聲音按鈕的點擊事件連線。
- (IBAction)setRecord { // 切換焦點,彈出錄音按鈕 [self.recordText becomeFirstResponder];}
其實就是自己隨便寫了個textField 點擊時就讓他擷取焦點,然後下面彈出一個輸入框上面有按鈕
這個textField的懶載入如下
- (UITextField *)recordText { if (_recordText == nil) { _recordText = [[UITextField alloc] init]; UIButton *btn = [UIButton buttonWithType:UIButtonTypeContactAdd]; _recordText.inputView = btn; [btn addTarget:self action:@selector(startRecord) forControlEvents:UIControlEventTouchDown]; [btn addTarget:self action:@selector(stopRecord) forControlEvents:UIControlEventTouchUpInside]; [self.inputMessageView addSubview:_recordText]; } return _recordText;}
對於音頻檔案的一系列處理操作,最好抽出一個工具類寫好,然後在需要的時候直接調用,並且以後其他項目也可以拖過去直接使用。
首先需要用到的屬性如下。
@interface SXRecordTools ()<AVAudioPlayerDelegate>/** 錄音器 */@property(nonatomic,strong) AVAudioRecorder *recorder;/** 錄音地址 */@property(nonatomic,strong) NSURL *recordURL;/** 播放器 */@property(nonatomic,strong) AVAudioPlayer *player;/** 播放完成時回調 */@property(nonatomic,copy) void (^palyCompletion)();@end
至於其中的開始錄音和結束錄音方法如下
/** 開始錄音 */- (void)startRecord{ [self.recorder record];}/** 停止錄音 */- (void)stopRecordSuccess:(void (^)(NSURL *url,NSTimeInterval time))success andFailed:(void (^)())failed{ // 只有在這裡才能取到currentTime NSTimeInterval time = self.recorder.currentTime; [self.recorder stop]; if (time < 1.5) { if (failed) { failed(); } }else{ if (success) { success(self.recordURL,time); } }}
開始錄音和結束錄音,架構中都自己有方法。主要是判斷了一下,音訊時間長度,小於1.5秒會回調錄音失敗的代碼塊。
這裡需要注意的是, recorder.currentTime 當前錄音的時間長度,只有在這個方法中才能取到,出了方法就取不到值了。
然後在控制器中,那個小加號按鈕的按下和抬起的監聽方法中調用工具類中的方法
#pragma mark - ******************** 錄音方法- (void)startRecord { NSLog(@"開始錄音"); [[SXRecordTools sharedRecorder] startRecord];}- (void)stopRecord { NSLog(@"停止錄音"); [[SXRecordTools sharedRecorder] stopRecordSuccess:^(NSURL *url, NSTimeInterval time) { // 發送聲音資料 NSData *data = [NSData dataWithContentsOfURL:url]; [self sendMessageWithData:data bodyName:[NSString stringWithFormat:@"audio:%.1f秒", time]]; } andFailed:^{ [[[UIAlertView alloc] initWithTitle:@"提示" message:@"時間太短" delegate:nil cancelButtonTitle:@"確定" otherButtonTitles:nil, nil] show]; }];}
可以清楚的看到,發送聲音調sendMessageWithData:時把聲音的時間長度當做參數bodyName 傳入。 然後就會將這個字串存到message的子節點內發出。
四。音頻檔案的顯示
也是和圖片一樣,對於本行取出的資訊先判斷是不是音頻資訊,如果是,遍曆節點,取出字串,並且截取了一下,截取掉“audio:”,讓tableView的cell中只顯示 時間長度,
else if ([message.body hasPrefix:@"audio"]){ XMPPMessage *msg = message.message; for (XMPPElement *node in msg.children) { NSString *base64str = node.stringValue; NSData *data = [[NSData alloc]initWithBase64EncodedString:base64str options:0]; NSString *newstr = [message.body substringFromIndex:6]; cell.messageLabel.text = newstr; cell.audioData = data; } }
這個audioData是個專門用來存放音效檔的資訊。但是表格是可以重用的,為了讓一個剛剛重用的cell裡面的音頻檔案別形成衝突,疊加。建議在剛取出cell時就加上一行
cell.audioData = nil;
五。關於音效檔的播放
雖然,架構自己就有音效檔的播放方法,但是還需要做很多附加操作,建議先在工具類中寫一個方法,就是播放data檔案,並且設定完成後的回調代碼。即playData:completion: 。在播放的方法中先判斷聲音是否現正播放,如果現正播放則不做任何操作。然後在方法中設定player的代理,這樣可以通過代理方法來監聽音效檔何時播放完,觸發代理方法。因此這個傳入的completion代碼塊必須要先用成員變數記錄下,然後在音效檔播放完的代理方法中再執行此代碼塊
- (void)playData:(NSData *)data completion:(void(^)())completion{ // 判斷是否現正播放 if (self.player.isPlaying) { [self.player stop]; } // 記錄塊代碼 self.palyCompletion = completion; // 監聽播放器播放狀態 self.player = [[AVAudioPlayer alloc]initWithData:data error:NULL]; self.player.delegate = self; [self.player play];}
代理方法在音效檔播放完的代理方法中再執行儲存的代碼塊
#pragma mark - ******************** 完成播放時的代理方法- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag{ if (self.palyCompletion) { self.palyCompletion(); }}
工具類中的方法寫完了之後,可以去外面調用了。給自己這個自訂的SXChatCell添加一個點擊方法。預設情況下按鈕是預設顏色的,點擊時顏色變成紅色,然後播放完成時的回調代碼再把顏色恢複成預設顏色。
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { // 如果有音頻資料,直接播放音頻 if (self.audioData != nil) { // 播放音頻 self.messageLabel.textColor = [UIColor redColor]; // 如果單例的塊代碼中包含self,一定使用weakSelf __weak SXChatCell *weakSelf = self; [[SXRecordTools sharedRecorder] playData:self.audioData completion:^{ weakSelf.messageLabel.textColor = [UIColor blackColor]; }]; }}
紅色的那個cell是現正播放
如果你不是在董鉑然部落格園看到本文,請點擊查看原文
到此之後,就完成了完整的圖片及音頻檔案的發送。