iOS 視頻直播彈幕的實現

  彈幕,並非一個多麼複雜的功能。dom

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的內容,存儲彈幕的頭像,暱稱,和消息內容ui

@interface BarrageModel : NSObject
/** 用戶暱稱 */
@property(nonatomic,copy)NSString *userName;
/** 消息內容 */
@property(nonatomic,copy)NSString *userMsg;
/** 用戶頭像 */
@property(nonatomic,copy)NSString *userHeadImageUrl;
@end

   接下來對BarrageView內容進行編輯,註釋已經儘量的詳細,所以很少作介紹atom

   在.h文件中spa

#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]; });
    //於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, 14) options:NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading attributes:@{ NSFontAttributeName:[UIFont systemFontOfSize:14] } 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裏面code

#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.測試結論

  經一個小時的定時器測試,內存沒有增長。對象

相關文章
相關標籤/搜索