iOS 视频直播弹幕的实现
弹幕,并不是一个多么复杂的功能。
1.弹幕的实现性分析
首先,从视觉上明确当前弹幕所具有的功能
- 从屏幕右侧滑入左侧,直至完全消失
- 不管是长的弹幕,还是短的弹幕,速度一致(可能有的需求是依据弹幕长度,调整速度)
- 有弹幕轨道,不是随机产生的弹幕
- 弹幕不会进行重叠
接下来从功能角度思考需要做什么
- 重用机制,类似tableView有一个重用池,每个弹幕就是一个cell,当有弹幕发送的时候,如果当前的重用池没有控件,则创建一个新的控件,如果重用池里面有控件,则拿出这个控件,开始做动画,在动画结束后重新将该控件重归重用池。
- 速度要求一致的话,需要考虑几点,首先如下图所示,红色代表弹幕起始位置,蓝色代表弹幕终止位置,长度代表它们的实际长度。当我们设定动画的时候,采用[UIView animationWithDuration.....]这个动画,设定duration为3s的话那么弹幕1的速度为(屏幕宽度+弹幕1宽度)/3,弹幕2的速度为(屏幕宽度+弹幕2宽度)/3,因为弹幕2长度大于弹幕1的长度,所以弹幕2的速度大于弹幕1的速度。(对于依据弹幕长度调整速度的需求来说,这里相对简单一些,不需要专门去计算速度,唯一麻烦的是需要考虑速度不一致带来的重叠问题)
2.开始准备
精通数学公式V=S/t (V代表速度,S代表路程,t代表时间)(*^__^*)
3.正式开始
创建一个View,命名为BarrageView,以及存储弹幕数据的对象BarrageModel
以下为BarrageModel.h的内容,存储弹幕的头像,昵称,和消息内容
@interface BarrageModel : NSObject
/** 用户昵称 */
@property(nonatomic,copy)NSString *userName;
/** 消息内容 */
@property(nonatomic,copy)NSString *userMsg;
/** 用户头像 */
@property(nonatomic,copy)NSString *userHeadImageUrl;
@end
接下来对BarrageView内容进行编辑,注释已经尽可能的详细,因此不多做介绍
在.h文件中
#import <UIKit/UIKit.h>
@class BarrageModel;
@interface BarrageView : UIView /**
* 记录当前最后一个弹幕View,通过这个View来计算是显示在哪个弹幕轨道上
*/
@property(nonatomic,retain) UIView *lastAnimateView;
/**
* 发送弹幕
*
* @param msgModel 弹幕数据Model
*/
-(void)barrageSendMsg:(BarrageModel *)msgModel;
@end
在.m文件中
#import "BarrageView.h"
#import "BarrageModel.h" //屏幕的尺寸
#define SCREEN_FRAME [[UIScreen mainScreen] bounds]
//屏幕的高度
#define SCREEN_HEIGHT CGRectGetHeight(SCREEN_FRAME)
//屏幕的宽度
#define SCREEN_WIDTH CGRectGetWidth(SCREEN_FRAME) @interface BarrageView()
{
CGFloat _minSpaceTime; /** 最小间距时间 */
}
/** 数据源 */
@property (nonatomic,retain)NSMutableArray *dataArr; /** 弹幕UI的重用池 */
@property (nonatomic,retain)NSMutableArray *resuingArr;
@end @implementation BarrageView - (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self setInterface];
}
return self;
}
-(void)setInterface
{
//初始化弹幕数据源,以及重用池
self.dataArr = [NSMutableArray array];
self.resuingArr = [NSMutableArray array]; //创建第一个弹幕加入重用池作为备用
UIView *view = [self createUI];
[self.resuingArr addObject:view]; //设置弹幕数据的初始轮询时间
_minSpaceTime = ; //检查是否可以取弹幕数据进行动画
[self checkStartAnimatiom];
}
-(void)checkStartAnimatiom
{
//当有数据信息的时候
if (self.dataArr.count>) { if (self.resuingArr.count>) { //当重用池里面有备用的弹幕UI时 //在重用池中,取出第一个弹幕UI
UIView *view = [self.resuingArr firstObject];
[self.resuingArr removeObject:view];
//取出的这个弹幕UI开始动画
[self startAnimationWithView:view]; }else{ //当重用池没有备用的弹幕UI时 //重新创建一个弹幕UI
UIView *view = [self createUI];
//拿着这个弹幕UI开始动画
[self startAnimationWithView:view];
}
}
//延迟执行,在主线程中不能调用sleep()进行延迟执行
//调用自身方法,构成一个无限循环,不停的轮询检查是否有弹幕数据
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_minSpaceTime * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self checkStartAnimatiom];
});
//于2017年12月29日更新,以上代码会导致内存泄漏,修改如下
__weak typeof(self) weakSelf = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_minSpaceTime * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
__strong typeof(self) strongSelf = weakSelf;
[strongSelf checkStartAnimatiom];
});
} -(void)startAnimationWithView:(UIView *)view
{
//取出第一条数据
BarrageModel *barrageModel = [self.dataArr firstObject]; //计算昵称的长度
CGSize nameSize = [barrageModel.userName boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, ) options:NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading attributes:@{
NSFontAttributeName:[UIFont systemFontOfSize:]
} context:nil].size;
//计算消息的长度
CGSize msgSize = [barrageModel.userMsg boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, ) options:NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading attributes:@{
NSFontAttributeName:[UIFont systemFontOfSize:]
} context:nil].size; UIImageView *headImageView; //头像
UILabel *userNameLabel; //昵称
UILabel *userMsgLabel; //消息内容 //进行赋值,宽度适应
for (UIView *subView in view.subviews) {
if (subView.tag == ) {
headImageView = (UIImageView *)subView;
headImageView.image = [UIImage imageNamed:@""]; }else if (subView.tag == ){
userNameLabel = (UILabel *)subView;
userNameLabel.text = barrageModel.userName;
//重新设置名称Label宽度
CGRect nameRect = userNameLabel.frame;
nameRect.size.width = nameSize.width;
userNameLabel.frame = nameRect;
}else{
userMsgLabel = (UILabel *)subView;
userMsgLabel.text = barrageModel.userMsg;
//重新设置消息内容Label宽度
CGRect msgRect = userMsgLabel.frame;
msgRect.size.width = msgSize.width;
userMsgLabel.frame = msgRect;
}
} //重新设置弹幕的总体宽度 = 头像宽度 + 头像左右两侧距离 + (如果名字宽度大于消息内容宽度,以名字宽度为基准,如果名字宽度小于消息内容宽度,以消息内容宽度为基准)
view.frame = CGRectMake(SCREEN_WIDTH, , CGRectGetWidth(headImageView.frame) + + (CGRectGetWidth(userNameLabel.frame)>CGRectGetWidth(userMsgLabel.frame)?CGRectGetWidth(userNameLabel.frame):CGRectGetWidth(userMsgLabel.frame)), CGRectGetHeight(self.frame)); //不管弹幕长短,速度要求一致。 V(速度) 为固定值 = 100(可根据实际自己调整)
// S = 屏幕宽度+弹幕的宽度 V = 100(可根据实际自己调整)
// V(速度) = S(路程)/t(时间) -------> t(时间) = S(路程)/V(速度);
CGFloat duration = (view.frame.size.width+SCREEN_WIDTH)/; //最小间距运行时间为:弹幕从屏幕外完全移入屏幕内的时间 + 间距的时间
_minSpaceTime = (view.frame.size.width + )/; //最后做动画的view
_lastAnimateView = view; //弹幕UI开始动画
[UIView animateWithDuration:duration delay: options:UIViewAnimationOptionCurveLinear animations:^{
//运行至左侧屏幕外
CGRect frame = view.frame;
view.frame = CGRectMake(-frame.size.width, , frame.size.width, frame.size.height);
} completion:^(BOOL finished) {
//动画结束重新回到右侧初始位置
view.frame = CGRectMake(SCREEN_WIDTH, , , CGRectGetHeight(self.frame));
//重新加入重用池
[self.resuingArr addObject:view];
}]; //将这个弹幕数据移除
[self.dataArr removeObject:barrageModel];
} #pragma mark public method
-(void)barrageSendMsg:(BarrageModel *)msgModel{
//添加弹幕数据
[self.dataArr addObject:msgModel];
} #pragma mark 创建控件
-(UIView *)createUI
{
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(SCREEN_WIDTH, , , CGRectGetHeight(self.frame))];
view.backgroundColor = [UIColor colorWithWhite: alpha:0.3]; UIImageView *headImageView = [[UIImageView alloc] initWithFrame:CGRectMake(, , CGRectGetHeight(self.frame)-, CGRectGetHeight(self.frame)-)];
headImageView.layer.cornerRadius = headImageView.frame.size.width/;
headImageView.layer.masksToBounds = YES;
headImageView.tag = ;
headImageView.backgroundColor = [UIColor redColor];
[view addSubview:headImageView]; UILabel *userNameLabel = [[UILabel alloc] initWithFrame:CGRectMake(CGRectGetMaxX(headImageView.frame) + , , ,)];
userNameLabel.font = [UIFont systemFontOfSize:];
userNameLabel.tag = ;
[view addSubview:userNameLabel]; UILabel *userMsgLabel = [[UILabel alloc] initWithFrame:CGRectMake(CGRectGetMaxX(headImageView.frame)+, CGRectGetMaxY(userNameLabel.frame), , )];
userMsgLabel.font = [UIFont systemFontOfSize:];
userMsgLabel.tag = ;
[view addSubview:userMsgLabel];
[self addSubview:view];
return view;
}
最后在vc里面
#import "ViewController.h"
#import "BarrageView.h"
#import "BarrageModel.h" //屏幕的尺寸
#define SCREEN_FRAME [[UIScreen mainScreen] bounds]
//屏幕的高度
#define SCREEN_HEIGHT CGRectGetHeight(SCREEN_FRAME)
//屏幕的宽度
#define SCREEN_WIDTH CGRectGetWidth(SCREEN_FRAME) @interface ViewController ()
/** 第一个弹幕轨道 */
@property (nonatomic,retain)BarrageView *barrageViewOne;
/** 第二个弹幕轨道 */
@property (nonatomic,retain)BarrageView *barrageViewTwo;
@end @implementation ViewController - (void)viewDidLoad {
[super viewDidLoad];
//创建第一个弹幕轨道
_barrageViewOne = [[BarrageView alloc]initWithFrame:CGRectMake(,, SCREEN_WIDTH, )];
[self.view addSubview:_barrageViewOne];
//创建第二个弹幕轨道
_barrageViewTwo = [[BarrageView alloc]initWithFrame:CGRectMake(,, SCREEN_WIDTH, )];
[self.view addSubview:_barrageViewTwo]; }
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval: target:self selector:@selector(sendMessage) userInfo:nil repeats:YES];
[timer fire];
}
-(void)sendMessage
{
BarrageModel *model = [[BarrageModel alloc]init];
model.userName = @[@"张三",@"李四",@"王五",@"赵六",@"七七",@"八八",@"九九",@"十十",@"十一",@"十二",@"十三",@"十四"][arc4random()%];
model.userMsg = @[@"阿达个人",@"都是vsqe12qwe",@"胜多负少的凡人歌",@"委屈翁二群二",@"",@"热帖柔荑花",@"发彼此彼此",@"OK泼墨",@"人体有图图",@"额外热无若无",@"微软将围"][arc4random()%];
//计算当前做动画的弹幕UI的位置
CGFloat onePositon = _barrageViewOne.lastAnimateView.layer.presentationLayer.frame.size.width + _barrageViewOne.lastAnimateView.layer.presentationLayer.frame.origin.x;
//计算当前做动画的弹幕UI的位置
CGFloat twoPositon = _barrageViewTwo.lastAnimateView.layer.presentationLayer.frame.size.width + _barrageViewTwo.lastAnimateView.layer.presentationLayer.frame.origin.x;
if ( onePositon < twoPositon ) {
[_barrageViewOne barrageSendMsg:model];
}else{
[_barrageViewTwo barrageSendMsg:model];
}
}
@end
4.测试结论
经一个小时的定时器测试,内存没有增加。
iOS 视频直播弹幕的实现的更多相关文章
- 最近这么火的iOS视频直播
快速集成iOS基于RTMP的视频推流 http://www.jianshu.com/p/8ea016b2720e iOS视频直播初窥:高仿<喵播APP> http://www.jiansh ...
- iOS视频直播初窥:高仿<喵播APP>
视频直播初窥 视频直播,可以分为 采集,前处理,编码,传输, 服务器处理,解码,渲染 采集: iOS系统因为软硬件种类不多, 硬件适配性比较好, 所以比较简单. 而Android端市面上机型众多, 要 ...
- iOS 视频直播
ijkplayer 是一款做视频直播的框架, 基于ffmpeg, 支持 Android 和 iOS, 网上也有很多集成说明, 但是个人觉得还是不够详细, 在这里详细的讲一下在 iOS 中如何集成ijk ...
- Android&iOS视频直播之旅
现在的移动互联网时代,大家的网速真是越来越快,高带宽的WIFI和覆盖率极大的4G,4G+把手机观看视频直播推上了风口浪尖,越来越多的应用在玩手机视频直播,我们做的应用里也要嵌入视频直播. 这篇文章里我 ...
- iOS视频直播
视频直播技术点 视频直播,可以分为 采集,前处理,编码,传输, 服务器处理,解码,渲染 采集: iOS系统因为软硬件种类不多, 硬件适配性比较好, 所以比较简单. 而Android端市面上机型众多, ...
- IOS 视频直播/智能家居(一行行敲代码,从零开始)lesson:1整体架构
本文转载至 http://blog.csdn.net/u014011807/article/details/47144027 前段时间由于工作需要做了一个视频直播/智能家居类的应用.算是对iOS音视频 ...
- iOS视频直播用到的协议
一 .流媒体 1 - 伪流媒体 1.1 扫盲:边下载边播放1.2 伪流媒体:视频不是实时播放的,先把视频放在数据库,再供客户端访问,比如:优酷,爱奇艺等 1.3 特点: 边下边存,文件会保存.遵守了 ...
- 视频直播SDK-ios版
IOS视频直播接入说明 一.名词解释 分辨率:用于计算机视频处理的图像,以水平和垂直方向上所能显示的像素数来表示分辨率.常见视频分辨率的有1080P即1920x1080,720P即1080x720,6 ...
- 超强教程:如何搭建一个 iOS 系统的视频直播 App?
现今,直播市场热火朝天,不少人喜欢在手机端安装各类直播 App,便于随时随地观看直播或者自己当主播.作为开发者来说,搭建一个稳定性强.延迟率低.可用性强的直播平台,需要考虑到部署视频源.搭建聊天室.优 ...
随机推荐
- asp.net上传文件限制解决方案
环境:VS2012,IIS7 利用web uploader实现了一个文件上传的功能,但是遇到上传大小的限制,在web.config的<system.web>节点下添加如下代码: <h ...
- D - Zhenya moves from the dormitory URAL - 2015
After moving from his parents’ place Zhenya has been living in the University dormitory for a month. ...
- SAXReader简单实例解析HTML
转载自:http://blog.csdn.net/seayqrain/article/details/5024068# 使用SAXReader需要导入dom4j-full.jar包. dom4j是一个 ...
- 初入servlet:Allocate exception for servlet
老板,来一碗错误垫垫肚子! 如果以下几个错误都符合,估计就是这个原因了. 页面报错如下: java.lang.NoClassDefFoundError:IllegalName: firstDemo/T ...
- 【20171106早】BeEF 工具初探
老黑今天接触BeEF工具,首先要了解这个工具能够做什么? 0x01:功能介绍 专业文档:点击这里 通俗的说就是可以控制别的浏览器,获取浏览器的信息.然后做something 专业的说就是好用的渗透测试 ...
- DOM 遍历-同胞
在 DOM 树中水平遍历 有许多有用的方法让我们在 DOM 树进行水平遍历: siblings() next() nextAll() nextUntil() prev() prevAll() prev ...
- jQuery选择器(ID选择器)第一节
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/stri ...
- 微信小程序语音识别服务搭建全过程解析(https api开放,支持新接口mp3录音、老接口silk录音)
silk v3(或新录音接口mp3)录音转olami语音识别和语义处理的api服务(ubuntu16.04服务器上实现) 重要的写在前面 重要事项一: 所有相关更新,我优先更新到我个人博客中,其它地方 ...
- ABP框架个人开发实战(1)_环境搭建
前言 之前关注ABP框架有一阵子了,一直没有潜下心来实际研究一下.最近想自己建站,以后有自己的功能开发项目,可以在自己的站点上开发,并一步步的完善,所以找个比较好用的框架迫在眉睫,选来选去,决定用AB ...
- 14.javaweb AJAX技术详解
一.简介 1, ajax:在不重新加载网页的前提下,与服务器交换数据并更新部分网页的技巧,但其本身并不是一种新技术 2, 核心:XMLHttpRequest对象.AJAX技术主要是通过此对象完成的 ...