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數據源方法中,取出信息即將賦值之前多了一層判斷,如果是圖片信息,采用下面的方法賦值。

 

關於基本發送流程哪里忘了可以查看普通文本信息的發送方法:http://www.cnblogs.com/dsxniubility/p/4307073.html

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是正在播放

 

如果你不是在董鉑然博客園看到本文,請點擊查看原文

到此之后,就完成了完整的圖片及音頻文件的發送。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM