「视频直播技术详解」系列之五:延迟优化

​关于直播的技术文章不少,成体系的不多。我们将用七篇文章,更系统化地介绍当下大热的视频直播各环节的关键技术,帮助视频直播创业者们更全面、深入地了解视频直播技术,更好地技术选型。

本系列文章大纲如下:

(一)采集

(二)处理

(三)编码和封装

(四)推流和传输

(五)延迟优化

(六)现代播放器原理

(七)SDK 性能测试模型

在上一篇推流和传输中,关于「直播的第一公里」的关键因素我们展开了详细的介绍。本篇是《解密视频直播技术》系列之五:延迟优化。


 

我们在很多线上和线下场合分享了如何优化直播体验,详细讲解了各部分造成低延迟和卡顿的原因和相应的优化原理。实际上,音视频的直播系统是一个复杂的工程系统,要做到非常低延迟的直播,需要复杂的系统工程优化和对各组件非常熟悉的掌握。这里面我们再分享几个简单而常用的调优技巧。

编码优化

1. 确保 Codec 开启了最低延迟的设置。Codec 一般都会有低延迟优化的开关,对于 H.264 来说其效果尤其明显。很多人可能不知道 H.264 的解码器正常情况下会在显示之前缓存一定的视频帧,对于 QCIF 分辨率大小的视频(176 × 144)一般会缓存 16 帧,对于 720P 的视频则缓存 5 帧。对于第一帧的读取来说,这是一个很大的延迟。如果你的视频不是使用 H.264 来编码压缩的,确保没有使用到 B 帧,它对延迟也会有较大的影响,因为视频中 B 帧的解码依赖于前后的视频帧,会增加延迟。

2. 编码器一般都会有码控造成的延迟,一般也叫做初始化延迟或者视频缓存检验器 VBV 的缓存大小,把它当成编码器和解码器比特流之间的缓存,在不影响视频质量的情况下可以将其设置得尽可能小也可以降低延迟。

3. 如果是仅仅优化首开延迟,可以在视频帧间插入较多的关键帧,这样客户端收到视频流之后可以尽快解码。但如果需要优化传输过程中的累计延迟,尽可能少使用关键帧也就是 I 帧(GOP 变大),在保证同等视频质量的情况下,I 帧越多,码率越大,传输所需的网络带宽越多,也就意味着累计延迟可能越大。这个优化效果可能在秒级延迟的系统中不是很明显,但是在 100 ms 甚至更低延迟的系统中就会非常明显。同时,尽量使用 AAC-LC Codec 来编码音频,HE-AAC 或者 HE-AAC V2 虽然编码效率高,但是编码所需时间更长,而产生更大体积的音频造成的传输延迟对于视频流的传输来说影响更小。

4. 不要使用视频 MJPEG 的视频压缩格式,至少使用不带 B 帧的 MPEG4 视频压缩格式(Simple profile),甚至最好使用 H.264 baseline profile(X264 还有一个「-tune zerolatency」的优化开关)。这样一个简单的优化可以降低延迟,因为它能够以更低的码率编码全帧率视频。

5. 如果使用了 FFmpeg,降低「-probesize 」和「 -analyze duration」参数的值,这两个值用于视频帧信息监测和用于监测的时长,这两个值越大对编码延迟的影响越大,在直播场景下对于视频流来说 analyzeduration 参数甚至没有必要设定。

6. 固定码率编码 CBR 可以一定程度上消除网络抖动影响,如果能够使用可变码率编码 VBR 可以节省一些不必要的网络带宽,降低一定的延迟。因此建议尽量使用 VBR 进行编码。

传输协议优化

1. 在服务端节点和节点之间尽量使用 RTMP 而非基于 HTTP 的 HLS 协议进行传输,这样可以降低整体的传输延迟。这个主要针对终端用户使用 HLS 进行播放的情况。

2. 如果终端用户使用 RTMP 来播放,尽量在靠近推流端的收流节点进行转码,这样传输的视频流比原始视频流更小。

3. 如果有必要,可以使用定制的 UDP 协议来替换 TCP 协议,省去弱网环节下的丢包重传可以降低延迟。它的主要缺点在于,基于 UDP 协议进行定制的协议的视频流的传输和分发不够通用,CDN 厂商支持的是标准的传输协议。另一个缺点在于可能出现丢包导致的花屏或者模糊(缺少关键帧的解码参考),这就要求协议定制方在 UDP 基础之上做好丢包控制。

传输网络优化

1. 我们曾经介绍过实时流传输网络,它是一种新型的节点自组织的网状传输网络,既适合国内多运营商网络条件下的传输优化,也适合众多海外直播的需求。

2. 在服务端节点中缓存当前 GOP,配合播放器端优化视频首开时间。

3. 服务端实时记录每个视频流流向每个环节时的秒级帧率和码率,实时监控码率和帧率的波动。

4. 客户端(推流和播放)通过查询服务端准实时获取当前最优节点(5 秒一次),准实时下线当前故障节点和线路。

推流、播放优化

1. 考察发送端系统自带的网络 buffer 大小,系统可能在发送数据之前缓存数据,这个参数的调优也需要找到一个平衡点。

2. 播放端缓存控制对于视频的首开延迟也有较大影响,如果仅优化首开延迟,可以在 0 缓存情况下在数据到达的时候立即解码。但如果在弱网环境下为了消除网络抖动造成的影响,设置一定的缓存也有必要,因此需要在直播的稳定性和首开延迟优化上找到平衡,调整优化缓冲区大小这个值。

3. 播放端动态 buffer 策略,这是上面播放端缓存控制的改进版本。如果只是做 0 缓存和固定大小的缓存之间进行选择找到平衡,最终还是会选择一个固定大小的缓存,这对亿级的移动互联网终端用户来说并不公平,他们不同的网络状况决定了这个固定大小的缓存并不完全合适。因此,我们可以考虑一种「动态 buffer 策略」,在播放器开启的时候采用非常小甚至 0 缓存的策略,通过对下载首片视频的耗时来决定下一个时间片的缓存大小,同时在播放过程中实时监测当前网络,实时调整播放过程中缓存的大小。这样即可做到极低的首开时间,又可能够尽量消除网络抖动造成的影响。

4. 动态码率播放策略。除了动态调整 buffer 大小的策略之外,也可以利用实时监测的网络信息来动态调整播放过程中的码率,在网络带宽不足的情况下降低码率进行播放,减少延迟。

以上,是我们在低延迟优化方面的部分技巧。实际上我们优化低延迟的时候并不是只关注「低延迟」,而是在保证其它条件不影响用户体验的情况下尽量做到低延迟,因此它的内容涉及到更多广泛的话题。而视频直播的优化也包含方方面面,这里只分享了其中经过我们实践的部分。随着实践的积累,我们接下来会在线上和线下分享更多关于视频直播甚至点播的优化技巧。

 

本文作者: 何李石@七牛云布道师,更多云行业技术洞见请访问七牛云博客

QQ空间/朋友圈类界面的搭建

类似于QQ空间的布局主要是在说说信息、点赞、回复三大部分的自适应布局上。

当我们需要搭建类似QQ空间、微信朋友圈的界面的时候,可做如下操作:

  1. 创建一个对应的model类;
  2. 创建一个对应model类的frameModel类,并将对应的model封装进这个frameModel类。frameModel类是将model对应显示的data的控件frame转换为一个可持久化的frame,这样一来,就可以在第3布容易很多;
  3. 创建一个talbleviewcell,根据 model可能显示的对象,初始化cell,并将frameModel封装进talbleviewcell。

我在这里写了一些测试的代码,大家可以参考一下。

如下是model的实现 (BasicModel 为我定义的basic类,内有归档持久化操作)

QQ空间/朋友圈类界面的搭建QQ空间/朋友圈类界面的搭建

 1 #import "BasicModel.h"
 2 
 3 @interface RadioModel : BasicModel<NSCopying>
 4 /**
 5  *  内容
 6  */
 7 @property(nonatomic, copy)NSString *msgContent;
 8 /**
 9  *  昵称
10  */
11 @property(nonatomic, copy)NSString *publisherNickName;
12 /**
13  *  头像
14  */
15 @property(nonatomic, copy)NSString *publisherImg;
16 /**
17  *  时间
18  */
19 @property(nonatomic, copy)NSString *publishTime;
20 /**
21  *  评论数组 
22  */
23 @property(nonatomic, copy)NSMutableArray *commentsArray;
24 /**
25  *  点赞数组   (点赞者昵称)
26  */
27 @property(nonatomic, copy)NSMutableArray *thumbArray;
28 
29 
30 @end

View Code

 

QQ空间/朋友圈类界面的搭建QQ空间/朋友圈类界面的搭建

 1 #import "RadioModel.h"
 2 #import <objc/runtime.h>
 3 
 4 @implementation RadioModel
 5 
 6 -(id)copyWithZone:(NSZone *)zone{
 7     RadioModel *model = [[RadioModel alloc] init];
 8     
 9     u_int count;
10     objc_property_t *propertyList = class_copyPropertyList([self class], &count);
11     
12     for (int index = 0; index < count; index++) {
13         objc_property_t property = propertyList[index];
14         NSString *str = [NSString stringWithUTF8String:property_getName(property)];
15         
16         id value = [self valueForKey:str];
17         [model setValue:value forKey:str];
18     }
19     
20     free(propertyList);
21     
22     return model;
23 }
24 
25 @end

View Code

 

如下是frameModel的实现:

 

QQ空间/朋友圈类界面的搭建QQ空间/朋友圈类界面的搭建

 1 #import "BasicModel.h"
 2 //#import <Foundation/Foundation.h>
 3 //#import <UIKit/UIKit.h>
 4 #import "RadioModel.h"
 5 
 6 @interface RadioModelFrame : BasicModel
 7 /**
 8  *  头像尺寸
 9  */
10 @property(nonatomic, copy)NSValue *iconFrameValue;
11 /**
12  *  昵称尺寸
13  */
14 @property(nonatomic, copy)NSValue *nickNameFrameValue;
15 /**
16  *  内容尺寸
17  */
18 @property(nonatomic, copy)NSValue *contentFrameValue;
19 /**
20  *  时间尺寸
21  */
22 @property(nonatomic, copy)NSValue *timeFrameValue;
23 /**
24  *  点赞按钮的尺寸
25  */
26 @property(nonatomic, copy)NSValue *thumbBtnFrameValue;
27 /**
28  *  评论按钮的尺寸
29  */
30 @property(nonatomic, copy)NSValue *commentBtnFrameValue;
31 /**
32  *  不包含赞、评论内容的高度
33  */
34 @property(nonatomic, copy)NSString *cellHeight;
35 /**
36  *  点赞人尺寸
37  */
38 @property(nonatomic, copy)NSValue *thumbPersonFrameValue;
39 /**
40  *  评论内容尺寸
41  */
42 //@property(nonatomic, assign)NSValue *commentsFrameValue;
43 /**
44  *  评论内容数据源
45  */
46 @property(nonatomic, strong)NSMutableArray *commentsArray;
47 /**
48  *  评论内容背景
49  */
50 @property(nonatomic, copy)NSValue *commentsBackGroundFrameValue;
51 /**
52  *  评论数据
53  */
54 @property(nonatomic, strong)RadioModel *radioModel;
55 
56 @end

View Code

 

QQ空间/朋友圈类界面的搭建QQ空间/朋友圈类界面的搭建

 1 #import "RadioModelFrame.h"
 2 #import "SCUtil.h"
 3 @implementation RadioModelFrame
 4 
 5 -(void)setRadioModel:(RadioModel *)radioModel{
 6     _radioModel = radioModel;
 7     
 8     float cellH;
 9     
10     //头像
11     CGFloat iconX = WIDTHINIPHONE6(10);
12     CGFloat iconY = HEIGTHINIPHONE6(5);
13     CGFloat iconH = WIDTHINIPHONE6(60);
14     CGFloat iconW = WIDTHINIPHONE6(60);
15     self.iconFrameValue = [NSValue valueWithCGRect:CGRectMake(iconX, iconY, iconW, iconH)];
16     
17     //昵称
18     CGFloat nickNameX = CGRectGetMaxX(CGRectMake(iconX, iconY, iconW, iconH)) + WIDTHINIPHONE6(5);
19     CGSize nickNameSize = [SCUtil sizeWithString:self.radioModel.publisherNickName font:HEADFONT size:CGSizeMake(SCREENWIDTH - WIDTHINIPHONE6(90), HEIGTHINIPHONE6(30))];
20     CGFloat nickNameY = iconY + HEIGTHINIPHONE6(20);
21     CGFloat nickNameW = nickNameSize.width;
22     CGFloat nickNameH = nickNameSize.height;
23     self.nickNameFrameValue = [NSValue valueWithCGRect:CGRectMake(nickNameX, nickNameY, nickNameW, nickNameH)];
24     
25     //内容内容
26     CGFloat contentX = nickNameX;
27     CGFloat contentY = CGRectGetMaxY(CGRectMake(nickNameX, nickNameY, nickNameW, nickNameH)) + HEIGTHINIPHONE6(10);
28     CGSize contentSize = [SCUtil sizeWithString:self.radioModel.msgContent font:HEADFONT size:CGSizeMake(SCREENWIDTH- WIDTHINIPHONE6(80), 10000000)];
29     CGFloat contentW = contentSize.width;
30     CGFloat contentH = contentSize.height;
31     self.contentFrameValue = [NSValue valueWithCGRect:CGRectMake(contentX, contentY, contentW, contentH)];
32     cellH = contentH + HEIGTHINIPHONE6(60);
33     
34     //时间显示
35     CGFloat timeX = nickNameX;
36     CGFloat timeY = cellH;
37     CGFloat timeW = WIDTHINIPHONE6(100);
38     CGFloat timeH = HEIGTHINIPHONE6(20);
39     self.timeFrameValue = [NSValue valueWithCGRect:CGRectMake(timeX, timeY, timeW, timeH)];
40     
41     //评论、点赞按钮 
42     CGFloat segY = cellH;
43     CGFloat segW = WIDTHINIPHONE6(30);
44     CGFloat segH = WIDTHINIPHONE6(30);
45     self.thumbBtnFrameValue = [NSValue valueWithCGRect:CGRectMake(SCREENWIDTH - WIDTHINIPHONE6(75), segY, segW, segH)];
46     self.commentBtnFrameValue = [NSValue valueWithCGRect:CGRectMake(SCREENWIDTH - WIDTHINIPHONE6(40), segY, segW, segH)];
47     
48     cellH = CGRectGetMaxY(CGRectMake(SCREENWIDTH - WIDTHINIPHONE6(100), segY, segW, segH));
49     
50     //点赞人显示
51     CGFloat thumX = nickNameX;
52     CGFloat thumY = cellH + HEIGTHINIPHONE6(0);
53     CGSize thumSize = [SCUtil sizeWithString:[_radioModel.thumbArray firstObject] font:FONT15TXT size:CGSizeMake((SCREENWIDTH - CGRectGetMinX([self.nickNameFrameValue CGRectValue]) - WIDTHINIPHONE6(30)), 10000000)];
54     CGFloat thumW = thumSize.width;
55     CGFloat thumH = thumSize.height;
56     if (thumSize.width > 1) {
57         thumW = thumSize.width + WIDTHINIPHONE6(20);
58     }else
59         thumH = 0.00f;
60     
61     _thumbPersonFrameValue = [NSValue valueWithCGRect:CGRectMake(thumX, thumY, thumW, thumH)];
62     if (thumH != 0.0f) {
63         cellH += CGRectGetMaxY(CGRectMake(thumX, thumY, thumW, thumH));
64     }
65     //评论内容显示
66     CGFloat commentHeight = CGRectGetMaxY(CGRectMake(thumX, thumY, thumW, thumH)) + HEIGTHINIPHONE6(10);
67     if ([_radioModel.commentsArray count]) {
68         CGFloat commentX = nickNameX;
69         for (int i = 0; i < [self.radioModel.commentsArray count]; i++) {
70             NSDictionary *dictionry = [self.radioModel.commentsArray objectAtIndex:i];
71             NSString *commentName = [[dictionry objectForKey:@"CommentatorName"] stringByAppendingString:@""];
72             NSString *commentContent = [dictionry objectForKey:@"CommentContent"];
73             
74             CGSize commentSize = [SCUtil sizeWithString:[commentName stringByAppendingString:commentContent] font:FONT15TXT size:CGSizeMake(SCREENWIDTH - WIDTHINIPHONE6(100), 100000000)];
75             CGFloat commentY = commentHeight;
76             CGFloat commentW = commentSize.width + WIDTHINIPHONE6(10);
77             CGFloat commentH = commentSize.height;
78             
79             cellH += commentH + HEIGTHINIPHONE6(2);
80             commentHeight += commentH + HEIGTHINIPHONE6(2);
81             CGRect segframe = CGRectMake(commentX, commentY, commentW, commentH);
82             [self.commentsArray addObject:[NSValue valueWithCGRect:segframe]];
83         }
84         cellH = CGRectGetMaxY([(NSValue *)[self.commentsArray lastObject] CGRectValue]) + HEIGTHINIPHONE6(10);
85         self.cellHeight = [NSString stringWithFormat:@"%.f",cellH];
86         CGFloat backGroundW = SCREENWIDTH - WIDTHINIPHONE6(90);
87         self.commentsBackGroundFrameValue = [NSValue valueWithCGRect:CGRectMake(nickNameX - WIDTHINIPHONE6(5), CGRectGetMaxY(CGRectMake(thumX, thumY, thumW, thumH)) + HEIGTHINIPHONE6(5), backGroundW + WIDTHINIPHONE6(10), commentHeight - CGRectGetMaxY(CGRectMake(thumX, thumY, thumW, thumH)))];
88     }else{
89         self.commentsBackGroundFrameValue = [NSValue valueWithCGRect:CGRectMake(nickNameX - WIDTHINIPHONE6(5), CGRectGetMaxY(CGRectMake(thumX, thumY, thumW, thumH)), 0, 0)];
90         self.cellHeight = [NSString stringWithFormat:@"%.f",CGRectGetMaxY(CGRectMake(thumX, thumY, thumW, thumH))];
91     }
92 }
93 
94 -(NSMutableArray *)commentsArray{
95     if (!_commentsArray) {
96         _commentsArray = [[NSMutableArray alloc] init];
97     }
98     return _commentsArray;
99 }

View Code

 

 tableviewcell的实现:

QQ空间/朋友圈类界面的搭建QQ空间/朋友圈类界面的搭建

 1 #import <UIKit/UIKit.h>
 2 #import "RadioModelFrame.h"
 3 
 4 @protocol addBtnActionDelegate <NSObject>
 5 
 6 
 7 -(void)supportAction:(RadioModelFrame *)frameModel;
 8 
 9 -(void)sendMessage:(NSString *)message withModel:(RadioModelFrame *)frameModel;
10 
11 @end
12 
13 @interface SquareTableViewCell : UITableViewCell
14 /**
15  *  cell的视图尺寸类
16  */
17 @property(nonatomic, strong) RadioModelFrame *radioModelFrame;
18 
19 +(instancetype)cellWithTableView:(UITableView *)tableView andRadioModel:(RadioModel *)model;
20 
21 @property(nonatomic, weak)id<addBtnActionDelegate>delegate;
22 
23 -(instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier andRadioModel:(RadioModel *)model;
24 
25 @end

View Code

QQ空间/朋友圈类界面的搭建QQ空间/朋友圈类界面的搭建

  1 #import "SquareTableViewCell.h"
  2 #import "RadioModel.h"
  3 #import "Masonry.h"
  4 #import "SCUtil.h"
  5 
  6 @interface SquareTableViewCell ()<UITextFieldDelegate>
  7 
  8 //@property(nonatomic, copy)RadioModel *radioModel;
  9 
 10 @property(nonatomic, strong)NSMutableArray *thumbArray;
 11 
 12 @property(nonatomic, strong)NSMutableArray *commentsViewArray;
 13 
 14 @property(nonatomic, weak) UIImageView *iconImgView;
 15 
 16 @property(nonatomic, weak) UILabel *nameLb;
 17 
 18 @property(nonatomic, weak) UILabel *contentLb;
 19 
 20 @property(nonatomic, weak) UILabel *timeLb;
 21 /**
 22  *  选择操作按钮(暂定“赞”、“评论”、“转发”三个选择)
 23  */
 24 @property(nonatomic, weak)UIButton *supportBtn;
 25 
 26 @property(nonatomic, weak)UIButton *commentsBtn;
 27 
 28 //@property(nonatomic, copy)UIButton *shareBtn;
 29 /**
 30  *  点赞人展示
 31  */
 32 @property(nonatomic, weak) UILabel *thumbPersonLb;
 33 
 34 @property(nonatomic, weak) UIImageView *commentsBackGroundView;
 35 /**
 36  *  文本输入框
 37  */
 38 @property(nonatomic, weak) UITextField *textField;
 39 
 40 @end
 41 
 42 @implementation SquareTableViewCell
 43 
 44 +(instancetype)cellWithTableView:(UITableView *)tableView andRadioModel:(RadioModel *)model
 45 {
 46     static NSString *identifier = @"RadioModelCell";
 47     
 48     SquareTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
 49     
 50     if (!cell) {
 51         cell = [[SquareTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier andRadioModel:model];
 52         cell.selectionStyle = UITableViewCellSelectionStyleNone;
 53     }
 54     return cell;
 55 }
 56 
 57 -(id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier andRadioModel:(RadioModel *)model
 58 {
 59     self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
 60     if (self) {
 61         self.selectionStyle = UITableViewCellSelectionStyleNone;
 62         //头像
 63         UIImageView *imgView = [[UIImageView alloc] init];
 64         [imgView.layer setMasksToBounds:YES];
 65         [imgView.layer setCornerRadius:WIDTHINIPHONE6(30)];
 66         [self.contentView addSubview:imgView];
 67         self.iconImgView = imgView;
 68         
 69         //昵称
 70         UILabel *nameLb = [[UILabel alloc] init];
 71         nameLb.font = HEADFONT;
 72         //nameLb.textColor = REDCOLOR;
 73         [self.contentView addSubview:nameLb];
 74         self.nameLb = nameLb;
 75         
 76         //广播内容
 77         UILabel *radioLb = [[UILabel alloc] init];
 78         radioLb.font = HEADFONT;
 79         radioLb.numberOfLines = 0;
 80         [self.contentView addSubview:radioLb];
 81         self.contentLb = radioLb;
 82         
 83         //时间显示
 84         UILabel *timeLb = [[UILabel alloc] init];
 85         timeLb.font = FONT12TXT;
 86         timeLb.textColor = GRAYBACK;
 87         [self.contentView addSubview:timeLb];
 88         self.timeLb = timeLb;
 89         
 90         //点赞按钮
 91         UIButton *thumBtn = [UIButton buttonWithType:UIButtonTypeCustom];
 92         [thumBtn setImage:[UIImage imageNamed:@"thumb"] forState:UIControlStateNormal];
 93 //        [thumBtn setBackgroundColor:REDCOLOR];
 94         [thumBtn addTarget:self action:@selector(supportAction) forControlEvents:UIControlEventTouchUpInside];
 95         [self.contentView addSubview:thumBtn];
 96         _supportBtn = thumBtn;
 97         
 98         //评论按钮
 99         UIButton *commentBtn = [UIButton buttonWithType:0];
100         [commentBtn setImage:[UIImage imageNamed:@"comment"] forState:0];
101 //        [commentBtn setBackgroundColor:[UIColor blueColor]];
102         [commentBtn addTarget:self action:@selector(commentToPerson) forControlEvents:UIControlEventTouchUpInside];
103         [self.contentView addSubview:commentBtn];
104         self.commentsBtn = commentBtn;
105         
106         //点赞的所有人
107         UILabel *supportLb = [[UILabel alloc] init];
108         supportLb.textColor = [UIColor colorWithRed:251/255.f green:25/255.f blue:255/255.f alpha:1];
109         supportLb.font = FONT15TXT;
110         [self.contentView addSubview:supportLb];
111         self.thumbPersonLb = supportLb;
112         
113         //评论的背景图
114         UIImageView *commentsBackImgView = [[UIImageView alloc] init];
115         commentsBackImgView.backgroundColor =  [UIColor colorWithRed:242/255.0 green:242/255.0 blue:242/255.0 alpha:1.0];
116         [self.contentView addSubview:commentsBackImgView];
117         self.commentsBackGroundView = commentsBackImgView;
118         
119         //输入框
120         UITextField *textfield = [[UITextField alloc] init];
121         textfield.delegate = self;
122         textfield.placeholder = @"评论";
123         textfield.borderStyle = UITextBorderStyleRoundedRect;
124         textfield.returnKeyType = UIReturnKeySend;
125         UIButton *clickBtn = [UIButton buttonWithType:UIButtonTypeCustom];
126         clickBtn.frame = CGRectMake(0, 0, WIDTHINIPHONE6(50), TABBARHEIGHT);
127         [clickBtn setBackgroundColor:DOMINANTCOLOR];
128         [clickBtn setTitle:@"发送" forState:UIControlStateNormal];
129         clickBtn.frame = CGRectMake(20, 20, WIDTHINIPHONE6(50), HEIGTHINIPHONE6(30));
130         [clickBtn addTarget:self action:@selector(sendComment) forControlEvents:UIControlEventTouchUpInside];
131         textfield.rightView = clickBtn;
132         textfield.rightViewMode = UITextFieldViewModeAlways;
133         [self.contentView addSubview:textfield];
134         self.textField = textfield;
135     }
136     return self;
137 }
138 
139 -(void)setRadioModelFrame:(RadioModelFrame *)radioModelFrame{
140     _radioModelFrame = radioModelFrame;
141     [self removeOldComments];
142     
143     [self settingData];
144     
145     [self settingFrame];
146 }
147 
148 //防止cell重叠
149 -(void)removeOldComments{
150     for (int i = 0; i < [self.commentsViewArray count]; i++) {
151         UILabel *commentsLb = [self.commentsViewArray objectAtIndex:i];
152         if (commentsLb.subviews) {
153             [commentsLb removeFromSuperview];
154         }
155     }
156     [self.commentsViewArray removeAllObjects];
157 }
158 
159 -(void)settingData{
160     RadioModel *radio = self.radioModelFrame.radioModel;
161     //显示头像
162     [self.iconImgView sd_setImageWithURL:[NSURL URLWithString:(NSString *)radio.publisherImg] placeholderImage:[UIImage imageNamed:@"squareCell"]];
163     //显示昵称
164     if ([SCUtil checkTelNumber:radio.publisherNickName]) {
165         NSRange range1 = NSMakeRange(0, 3);
166         NSRange range2 = NSMakeRange(7, 4);
167         NSString *displayStr1 = [radio.publisherNickName substringWithRange:range1];
168         NSString *displayStr2 = [radio.publisherNickName substringWithRange:range2];
169         NSString *name = [[displayStr1 stringByAppendingString:@"***"] stringByAppendingString:displayStr2];
170         self.nameLb.text = name;
171     }else
172         self.nameLb.text = radio.publisherNickName;
173     
174     //显示广播内容
175     self.contentLb.text = radio.msgContent;
176     //显示时间
177     self.timeLb.text = radio.publishTime;
178     
179     User *user = [NSKeyedUnarchiver unarchiveObjectWithFile:LOGINCACHEPATH];
180     //点赞人 、点赞按钮
181     self.thumbPersonLb.textColor = REDCOLOR;
182     self.thumbPersonLb.numberOfLines = 0;
183     if ([radio.thumbArray count]) {  //显示点赞者
184         if (![SCUtil isBlankString:[radio.thumbArray firstObject]]) {
185             _thumbPersonLb.text = [@"" stringByAppendingString:[radio.thumbArray firstObject]];
186         }else
187             _thumbPersonLb.text = @"";
188         
189         if (![SCUtil isBlankString:user.nickName]) {
190             if ([_thumbPersonLb.text containsString:user.nickName]) {
191                 [_supportBtn setImage:[UIImage imageNamed:@"thumb2"] forState:UIControlStateNormal];
192             }else
193                 [_supportBtn setImage:[UIImage imageNamed:@"thumb"] forState:UIControlStateNormal];
194         }else{
195             [_supportBtn setImage:[UIImage imageNamed:@"thumb"] forState:UIControlStateNormal];
196         }
197     }else{
198         [_supportBtn setImage:[UIImage imageNamed:@"thumb"] forState:UIControlStateNormal];
199     }
200     
201     //评论按钮
202         [self.commentsBtn setImage:[UIImage imageNamed:@"comment"] forState:UIControlStateNormal];
203     
204     //显示评论
205     for (int i = 0; i < [radio.commentsArray count]; i++) {
206         UILabel *commentLb = [[UILabel alloc] init];
207         commentLb.font = FONT15TXT;
208         commentLb.numberOfLines = 0;
209         
210         NSDictionary *dictionary = radio.commentsArray[i];
211         NSString *nameStr = [[dictionary objectForKey:@"CommentatorName"] stringByAppendingString:@""];
212         NSString *contentStr = [dictionary objectForKey:@"CommentContent"];
213         NSString *string = [nameStr stringByAppendingString:contentStr];
214         
215         NSMutableAttributedString *attriButeStr = [[NSMutableAttributedString alloc] initWithString:string];
216         [attriButeStr addAttribute:NSForegroundColorAttributeName value:[UIColor colorWithRed:251/255.f green:25/255.f blue:25/255.f alpha:1] range:NSMakeRange(0, nameStr.length)];
217         
218         commentLb.attributedText = attriButeStr;
219         [self.contentView addSubview:commentLb];
220         [self.commentsViewArray addObject:commentLb];
221     }
222     
223 }
224 
225 -(void)settingFrame{
226     self.iconImgView.frame = [self.radioModelFrame.iconFrameValue CGRectValue];
227     self.nameLb.frame = [self.radioModelFrame.nickNameFrameValue CGRectValue];
228     self.contentLb.frame = [self.radioModelFrame.contentFrameValue CGRectValue];
229     self.timeLb.frame =[self.radioModelFrame.timeFrameValue CGRectValue];
230     
231     //按钮
232     self.commentsBtn.frame =[self.radioModelFrame.commentBtnFrameValue CGRectValue];
233     self.supportBtn.frame = [self.radioModelFrame.thumbBtnFrameValue CGRectValue];
234     
235     //点赞人字符串
236     self.thumbPersonLb.frame = [self.radioModelFrame.thumbPersonFrameValue CGRectValue];
237     
238     for (int i = 0; i < [self.radioModelFrame.commentsArray count]; i++) {
239         if ([_commentsViewArray count] == 1) {
240             ((UILabel *)[_commentsViewArray objectAtIndex:0]).frame = [(NSValue *)[_radioModelFrame.commentsArray objectAtIndex:0] CGRectValue];
241         }
242         else
243             ((UILabel *)[_commentsViewArray objectAtIndex:i]).frame = [(NSValue *)[_radioModelFrame.commentsArray objectAtIndex:i] CGRectValue];
244     }
245     
246     //评论的尺寸
247     self.commentsBackGroundView.frame = [self.radioModelFrame.commentsBackGroundFrameValue CGRectValue];
248     
249     //文本
250     if (CGRectGetHeight(self.commentsBackGroundView.frame) > 1) {
251         self.textField.frame = CGRectMake(self.commentsBackGroundView.frame.origin.x, CGRectGetMaxY(self.commentsBackGroundView.frame) + HEIGTHINIPHONE6(5), self.commentsBackGroundView.frame.size.width, HEIGTHINIPHONE6(30));
252     }else if (CGRectGetHeight(self.thumbPersonLb.frame) > 1 ) {
253         self.textField.frame = CGRectMake(self.contentLb.frame.origin.x, CGRectGetMaxY(self.thumbPersonLb.frame) + HEIGTHINIPHONE6(5), SCREENWIDTH - WIDTHINIPHONE6(80), HEIGTHINIPHONE6(30));
254     }else{
255         self.textField.frame = CGRectMake(self.contentLb.frame.origin.x, CGRectGetMaxY(self.supportBtn.frame) + HEIGTHINIPHONE6(5), SCREENWIDTH - WIDTHINIPHONE6(80), HEIGTHINIPHONE6(30));
256     }
257     
258 }
259 
260 -(void)supportAction{
261     [self.delegate supportAction:_radioModelFrame];
262     [self.textField resignFirstResponder];
263 }
264 
265 -(void)commentToPerson{
266     [self.textField becomeFirstResponder];
267 }
268 
269 -(void)sendComment{
270     if (![SCUtil isBlankString:self.textField.text]) {
271         [self.delegate sendMessage:self.textField.text withModel:_radioModelFrame];
272         if (![SCUtil isBlankString:self.textField.text]) {
273             self.textField.text = @"";
274         }
275         [self.textField resignFirstResponder];
276     }
277 }
278 
279 -(BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string{
280     if ([string isEqualToString:@"/n"]) {
281         if (![SCUtil isBlankString:self.textField.text]) {
282             [self.delegate sendMessage:self.textField.text withModel:_radioModelFrame];
283             if (![SCUtil isBlankString:self.textField.text]) {
284                 self.textField.text = @"";
285             }
286         }
287         [textField resignFirstResponder];
288         return NO;
289     }
290     return YES;
291 }
292 
293 -(NSMutableArray *)commentsViewArray{
294     if (!_commentsViewArray) {
295         _commentsViewArray = [[NSMutableArray alloc] init];
296     }
297     return _commentsViewArray;
298 }
299 
300 -(NSMutableArray *)thumbArray{
301     if (!_thumbArray) {
302         _thumbArray = [[NSMutableArray alloc] init];
303     }
304     return _thumbArray;
305 }
306 
307 @end

View Code

 

一般按照上边的方法基本能够实现了要求,当然,我这里为方便并没有加入图片显示,方正步骤都类似,只是多加个参数而已。

 

一号互联(企业免费通信协同软件) V3.4.5.1110官方版

一号互联是一款帮助企业、个人管理通讯录、通话记录,可以做免费的CRM及通信协同工具。具有公司组织架构、个人云通讯录、客户通讯录、通话信息管理以及日历提醒功能,极大方便您的工作与日常生活!云通讯录/通话记录,信息全存储PC点击拨号,拨打电话更方便来去电弹屏,联系人信息一目了然SIP电话功能,分机订阅,来电同振;
历史版本