欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

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

程序员文章站 2023-12-17 10:36:28
本篇内容通过步骤详细给大家讲解了ios视频直播弹幕的原理以及实现代码分析,以下就是全部内容: 1.弹幕的实现性分析 首先,从视觉上明确当前弹幕所具有的功能 从屏幕...

本篇内容通过步骤详细给大家讲解了ios视频直播弹幕的原理以及实现代码分析,以下就是全部内容:

1.弹幕的实现性分析

首先,从视觉上明确当前弹幕所具有的功能

从屏幕右侧滑入左侧,直至完全消失

不管是长的弹幕,还是短的弹幕,速度一致(可能有的需求是依据弹幕长度,调整速度)

有弹幕轨道,不是随机产生的弹幕

弹幕不会进行重叠

接下来从功能角度思考需要做什么

重用机制,类似tableview有一个重用池,每个弹幕就是一个cell,当有弹幕发送的时候,如果当前的重用池没有控件,则创建一个新的控件,如果重用池里面有控件,则拿出这个控件,开始做动画,在动画结束后重新将该控件重归重用池。

速度要求一致的话,需要考虑几点,首先如下图所示,红色代表弹幕起始位置,蓝色代表弹幕终止位置,长度代表它们的实际长度。当我们设定动画的时候,采用[uiview animationwithduration.....]这个动画,设定duration为3s的话那么弹幕1的速度为(屏幕宽度+弹幕1宽度)/3,弹幕2的速度为(屏幕宽度+弹幕2宽度)/3,因为弹幕2长度大于弹幕1的长度,所以弹幕2的速度大于弹幕1的速度。(对于依据弹幕长度调整速度的需求来说,这里相对简单一些,不需要专门去计算速度,唯一麻烦的是需要考虑速度不一致带来的重叠问题)

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

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.测试结论

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

上一篇:

下一篇: