代码详解iOS视频直播弹幕功能

本篇内容通过步骤详细给大家讲解了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 <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 = 1;  //检查是否可以取弹幕数据进行动画  [self checkStartAnimatiom];}-(void)checkStartAnimatiom{  //当有数据信息的时候  if (self.dataArr.count>0) {    if (self.resuingArr.count>0) { //当重用池里面有备用的弹幕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];  });}-(void)startAnimationWithView:(UIView *)view{  //取出第一条数据  BarrageModel *barrageModel = [self.dataArr firstObject];  //计算昵称的长度  CGSize nameSize = [barrageModel.userName boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, 14) options:NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading attributes:@{                                                                         } context:nil].size;  //计算消息的长度  CGSize msgSize = [barrageModel.userMsg boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, 14) options:NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading attributes:@{                                                               NSFontAttributeName:[UIFont systemFontOfSize:14]} context:nil].size;   UIImageView *headImageView; //头像  UILabel *userNameLabel;   //昵称  UILabel *userMsgLabel;   //消息内容  //进行赋值,宽度适应  for (UIView *subView in view.subviews) {    if (subView.tag == 1000) {      headImageView = (UIImageView *)subView;      headImageView.image = [UIImage imageNamed:@""];          }else if (subView.tag == 1001){      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, 0, CGRectGetWidth(headImageView.frame) + 4 + (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)/100;  //最小间距运行时间为:弹幕从屏幕外完全移入屏幕内的时间 + 间距的时间  _minSpaceTime = (view.frame.size.width + 30)/100;  //最后做动画的view  _lastAnimateView = view;    //弹幕UI开始动画  [UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionCurveLinear animations:^{    //运行至左侧屏幕外    CGRect frame = view.frame;    view.frame = CGRectMake(-frame.size.width, 0, frame.size.width, frame.size.height);  } completion:^(BOOL finished) {    //动画结束重新回到右侧初始位置    view.frame = CGRectMake(SCREEN_WIDTH, 0, 0, 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, 0, 0, CGRectGetHeight(self.frame))];  view.backgroundColor = [UIColor colorWithWhite:0 alpha:0.3];  UIImageView *headImageView = [[UIImageView alloc] initWithFrame:CGRectMake(2, 2, CGRectGetHeight(self.frame)-4, CGRectGetHeight(self.frame)-4)];  headImageView.layer.cornerRadius = headImageView.frame.size.width/2;  headImageView.layer.masksToBounds = YES;  headImageView.tag = 1000;  headImageView.backgroundColor = [UIColor redColor];  [view addSubview:headImageView];  UILabel *userNameLabel = [[UILabel alloc] initWithFrame:CGRectMake(CGRectGetMaxX(headImageView.frame) + 2, 0, 0,14)];  userNameLabel.font = [UIFont systemFontOfSize:14];  userNameLabel.tag = 1001;  [view addSubview:userNameLabel];  UILabel *userMsgLabel = [[UILabel alloc] initWithFrame:CGRectMake(CGRectGetMaxX(headImageView.frame)+2, CGRectGetMaxY(userNameLabel.frame), 0, 14)];  userMsgLabel.font = [UIFont systemFontOfSize:14];  userMsgLabel.tag = 1002;  [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(0,200, SCREEN_WIDTH, 34)];
 [self.view addSubview:_barrageViewOne];
 //创建第二个弹幕轨道
 _barrageViewTwo = [[BarrageView alloc]initWithFrame:CGRectMake(0,300, SCREEN_WIDTH, 34)];
 [self.view addSubview:_barrageViewTwo];
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
 NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(sendMessage) userInfo:nil repeats:YES];
 [timer fire];
}
-(void)sendMessage
{
 BarrageModel *model = [[BarrageModel alloc]init];
 model.userName = @[@"张三",@"李四",@"王五",@"赵六",@"七七",@"八八",@"九九",@"十十",@"十一",@"十二",@"十三",@"十四"][arc4random()%12];
 model.userMsg = @[@"阿达个人",@"都是vsqe12qwe",@"胜多负少的凡人歌",@"委屈翁二群二",@"12312",@"热帖柔荑花",@"发彼此彼此",@"OK泼墨",@"人体有图图",@"额外热无若无",@"微软将围"][arc4random()%11];
 //计算当前做动画的弹幕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.测试结论

经一个小时的定时器测试,内存没有增加。

(0)

相关推荐

  • 代码详解iOS视频直播弹幕功能

    本篇内容通过步骤详细给大家讲解了iOS视频直播弹幕的原理以及实现代码分析,以下就是全部内容: 1.弹幕的实现性分析 首先,从视觉上明确当前弹幕所具有的功能 从屏幕右侧滑入左侧,直至完全消失 不管是长的弹幕,还是短的弹幕,速度一致(可能有的需求是依据弹幕长度,调整速度) 有弹幕轨道,不是随机产生的弹幕 弹幕不会进行重叠 接下来从功能角度思考需要做什么 重用机制,类似tableView有一个重用池,每个弹幕就是一个cell,当有弹幕发送的时候,如果当前的重用池没有控件,则创建一个新的控件,如果重用池

  • 代码详解ios键盘收起问题

    在开发过程中,我们经常会用到UITextField.UITextView等文本框,然后这些文本框在点击之后会自动成为第一响应者(FirstResponder),并自动弹出软键盘.然而,没有自动定义好的软键盘的回收.今天,我在开发过程中就遇到了这个问题,首先,软键盘收起会发生在两种情况下,一是当前区域为非第一响应者,二是当前区域的输入结束.具体解决方案有两种: 1.在当前页面设置点击事件,当点击事件发生时,注销当前视图的第一响应者或者设置当前摄入结束.当点击事件发生在非选中区域时,则键盘会自动回收

  • 使用Go基于WebSocket构建千万级视频直播弹幕系统的代码详解

    (1)业务复杂度介绍 开门见山,假设一个直播间同时500W人在线,那么1秒钟1000条弹幕,那么弹幕系统的推送频率就是: 500W * 1000条/秒=50亿条/秒 ,想想B站2019跨年晚会那次弹幕系统得是多么的NB,况且一个大型网站不可能只有一个直播间! 使用Go做WebSocket开发无非就是三种情况: 使用Go原生自带的库,也就是 golang.org/x/net ,但是这个官方库真是出了奇Bug多 使用GitHub大佬 gorilla/websocket 库,可以结合到某些Web开发框

  • java使用FFmpeg合成视频和音频并获取视频中的音频等操作(实例代码详解)

    FFmpeg是一套可以用来记录.转换数字音频.视频,并能将其转化为流的开源计算机程序. ffmpeg命令参数如下: 通用选项 -L license -h 帮助 -fromats 显示可用的格式,编解码的,协议的... -f fmt 强迫采用格式fmt -I filename 输入文件 -y 覆盖输出文件 -t duration 设置纪录时间 hh:mm:ss[.xxx]格式的记录时间也支持 -ss position 搜索到指定的时间 [-]hh:mm:ss[.xxx]的格式也支持 -title

  • Java 使用 FFmpeg 处理视频文件示例代码详解

    目前在公司做一个小东西,里面用到了 FFmpeg 简单处理音视频,感觉功能特别强大,在做之前我写了一个小例子,现在记录一下分享给大家,希望大家遇到这个问题知道解决方案. FFmpeg是一套可以用来记录.转换数字音频.视频,并能将其转化为流的开源计算机程序.采用LGPL或GPL许可证.它提供了录制.转换以及流化音视频的完整解决方案.它包含了非常先进的音频/视频编解码库libavcodec,为了保证高可移植性和编解码质量,libavcodec里很多code都是从头开发的. FFmpeg在Linux平

  • 在Ubuntu上搭建一个基于webrtc的多人视频聊天服务实例代码详解

    WebRTC,即Web Real-Time Communication,web实时通信技术.简单地说就是在web浏览器里面引入实时通信,包括音视频通话等. 在疫情期间哪里也去不了,在家没事就研究webrtc视频直播技术,网上找了些教程最终都不太能顺利跑起来的,可能是文章写的比较老,使用的一些开源组件已经更新了,有些配置已经不太一样了,所以按照以前的步骤会有问题.折腾了一阵终于跑起来了,记录一下. 一个简单的聊天室html页面 这个页面使用simple-webrtc来实现webrtc的通讯,sim

  • Android使用Realm数据库实现App中的收藏功能(代码详解)

    前 言 App数据持久化功能是每个App必不可少的功能,而Android最常用的数据持久化方式主要有以下的五种方式: 使用SharedPreferences存储数据: 文件存储数据: SQLite数据库存储数据: 使用ContentProvider存储数据: 网络存储数据. 其中前四种都是缓存数据到本地,这篇主要讲的是使用第三种方式来实现App中的收藏功能,不过不用Android原生自带SQLite数据库来存储数据,而是使用第三方的Realm数据库来来存储数据. Realm 本质上是一个嵌入式数

  • jQuery实现区域打印功能代码详解

    使用CSS控制打印样式,需要设置样式media="print",并且将页面中不需要打印的元素的样式display属性设置为none.如DEMO中,我将页头页脚及其他不需要打印的元素的样式设置如下: <style type="text/css" media="print"> #header,.top_title,#jqprint,#footer,#cssprint h3{display:none} </style> 用jQu

  • iOS 隐藏tabbar代码详解

    -(void)viewWillAppear:(BOOL)animated { NSArray *array=self.tabBarController.view.subviews; UIView *view=array[2]; view.frame=CGRectMake(0, [UIScreen mainScreen ].bounds.size.height, [UIScreen mainScreen ].bounds.size.width, 49); [UIView commitAnimati

  • BootstrapTable加载按钮功能实例代码详解

    1      html <!--工具栏--> <div id="toolbar" class="btn-group"> <div style="float:left;margin-right: 10px"> <button class="btn btn-danger"onclick="openModal('add',0,'')">增加</button&g

随机推荐