iOS 視頻直播彈幕系統(tǒng)完整的實(shí)現(xiàn)

來(lái)源丨搜狐技術(shù)產(chǎn)品丨

介紹

彈幕誕生于日本的視頻平臺(tái)狗准,后來(lái)被B站這種短視頻平臺(tái)引入到國(guó)內(nèi)傻丝,并在國(guó)內(nèi)發(fā)展壯大。后來(lái)逐漸被長(zhǎng)視頻平臺(tái)所接受俄占,現(xiàn)在視頻相關(guān)的應(yīng)用基本上都會(huì)有彈幕管怠。

但是長(zhǎng)視頻彈幕和B站這類的短視頻彈幕還不太一樣,短視頻平臺(tái)有自己特有的彈幕文化颠放,所以彈幕更注重和用戶的互動(dòng)排惨。長(zhǎng)視頻平臺(tái)還是以看劇為主吭敢,彈幕類似于評(píng)論的功能碰凶,所以不能影響用戶看劇,彈幕不能太密集鹿驼,而且相互之間最好不要有遮蓋欲低,否則會(huì)對(duì)視頻內(nèi)容會(huì)有比較明顯的影響。

本篇文章主要從長(zhǎng)視頻平臺(tái)的角度來(lái)講彈幕的實(shí)現(xiàn)原理畜晰,但其實(shí)短視頻平臺(tái)的彈幕也是同樣的原理砾莱,區(qū)別在于短視頻可能彈幕種類會(huì)多一些。

技術(shù)實(shí)現(xiàn)

畫布

以我公司應(yīng)用為例凄鼻,有iPhoneiPad兩個(gè)平臺(tái)腊瑟,在iPhone平臺(tái)上有橫豎屏的概念,都需要展示彈幕块蚌。在iPad上有大小屏的概念闰非,也需要都展示彈幕。彈幕的技術(shù)方案肯定是兩個(gè)平臺(tái)用一套峭范,但需要考慮跨不同設(shè)備和屏幕的情況财松。

所以,對(duì)于這個(gè)問(wèn)題,我通過(guò)畫布的概念來(lái)解決通用性的問(wèn)題辆毡。畫布并不區(qū)分屏幕大小和比例的概念菜秦,只是單純的用來(lái)展示彈幕,并不處理其他業(yè)務(wù)邏輯舶掖,通過(guò)一個(gè)Render類來(lái)控制畫布的渲染球昨。對(duì)于不同設(shè)備上的差異,例如iPad字體大一些眨攘,iPhone字體小一些這種情況褪尝,通過(guò)config類來(lái)進(jìn)行控制,畫布內(nèi)部不做判斷期犬。

小屏上畫布會(huì)根據(jù)比例少展示一些河哑,大屏上則多展示一些。字體變大畫布也會(huì)根據(jù)比例和左右間距進(jìn)行控制龟虎,保證展示比例是對(duì)的璃谨,并且在屏幕寬高發(fā)生改變后,自動(dòng)適應(yīng)新的尺寸鲤妥,不會(huì)出現(xiàn)彈幕銜接斷開的問(wèn)題佳吞,例如iPad上大小屏切換。外部在使用時(shí)棉安,只需要傳入一個(gè)frame即可底扳,不需要關(guān)注畫布內(nèi)部的調(diào)整。

彈幕軌道

從屏幕上來(lái)看贡耽,可以看到彈幕一般都是一行一行的衷模。為了方便對(duì)彈幕視圖進(jìn)行管理,以及后續(xù)的擴(kuò)展工作蒲赂,我對(duì)彈幕設(shè)計(jì)了“軌道”的概念阱冶。每一行都是一個(gè)軌道,對(duì)彈幕進(jìn)行橫向的管理滥嘴,這一行包括速度木蹬、末端彈幕、高度等參數(shù)若皱,這些參數(shù)適用于這一行的所有彈幕镊叁。軌道是一個(gè)虛擬的概念,并沒有對(duì)應(yīng)的視圖走触。

軌道有對(duì)應(yīng)的類來(lái)實(shí)現(xiàn)晦譬,類中會(huì)包含一個(gè)數(shù)組,數(shù)組中有這一行所有的彈幕饺汹。這個(gè)思路有點(diǎn)像玩過(guò)的一款游戲-節(jié)奏大師蛔添,里面也有音樂(lè)軌道的概念,每個(gè)軌道上對(duì)應(yīng)不同速度和顏色的音符,音符數(shù)量也是不固定的迎瞧,根據(jù)節(jié)奏來(lái)決定夸溶。

軌道還有一個(gè)好處在于,對(duì)于不同速度的彈幕比較好控制凶硅。例如騰訊視頻的彈幕其實(shí)是不同速度的缝裁,但是你仔細(xì)觀察的話,可以發(fā)現(xiàn)他們的彈幕是“奇偶行不同速”足绅,也就是奇數(shù)行一個(gè)速度捷绑,偶數(shù)行一個(gè)速度,讓人從感官上來(lái)覺得所有彈幕的速度都不一樣氢妈。如果通過(guò)軌道的方式就很好實(shí)現(xiàn)粹污,不同的軌道根據(jù)當(dāng)前所在行數(shù),對(duì)發(fā)出的彈幕設(shè)置不同的速度即可首量。

有時(shí)候看視頻過(guò)程中會(huì)從右側(cè)出現(xiàn)一條活動(dòng)彈幕壮吩,可能是視頻中的梗,也可能是類似于廣告的互動(dòng)加缘。但是活動(dòng)彈幕出現(xiàn)時(shí)一般是單行清屏的鸭叙,也就是和普通彈幕是互斥的,展示活動(dòng)彈幕的時(shí)候前后沒有普通彈幕拣宏。這種通過(guò)軌道的方式也比較好實(shí)現(xiàn)沈贝,每條彈幕都對(duì)應(yīng)一個(gè)時(shí)間段,根據(jù)活動(dòng)彈幕的時(shí)間和速度勋乾,將活動(dòng)彈幕展示的前后時(shí)間宋下,將這段時(shí)間軌道暫時(shí)關(guān)閉,只保留活動(dòng)彈幕即可市俊。

輪詢

每條彈幕都對(duì)應(yīng)著一個(gè)展示時(shí)間杨凑,所以需要每隔一段時(shí)間就找一下有沒有需要展示的彈幕滤奈。我設(shè)計(jì)的方案是通過(guò)輪詢摆昧,來(lái)驅(qū)動(dòng)彈幕展示。

通過(guò)CADisplayLink來(lái)進(jìn)行輪訓(xùn)蜒程,將frameInterval設(shè)置為60绅你,即每秒輪詢一次。在輪詢的回調(diào)中查找有沒有要展示的彈幕昭躺,有的話就從上到下查找每條軌道忌锯,某條軌道有位置可以展示的話就交給這條軌道展示,如果所有軌道都有正在展示的彈幕领炫,則將此條彈幕丟棄偶垮。是否有位置是根據(jù)屏幕最右側(cè),最后一條彈幕是否已經(jīng)展示完全,并且后面有空余位置來(lái)決定的似舵。

對(duì)于取數(shù)據(jù)的部分脚猾,數(shù)據(jù)和視圖的邏輯是分離的,相互之間并沒有耦合關(guān)系砚哗。取數(shù)據(jù)時(shí)只是從一個(gè)很小的字典中龙助,根據(jù)時(shí)間取出所用的彈幕數(shù)并轉(zhuǎn)化為model。字典的數(shù)據(jù)很少蛛芥,最多十秒的數(shù)據(jù)提鸟,而且這里并不會(huì)接觸到讀數(shù)據(jù)庫(kù)的操作,也沒有網(wǎng)絡(luò)請(qǐng)求的邏輯仅淑,這些都是獨(dú)立的邏輯称勋,后面會(huì)講到。

彈幕視圖

經(jīng)逞木梗看視頻的同學(xué)應(yīng)該會(huì)知道铣缠,彈幕的展示形式有很多,有帶明星頭像的昆禽、有帶點(diǎn)贊數(shù)的蝗蛙、帶矩形背景色的,很多種展示形態(tài)醉鳖。為了更好的對(duì)視圖進(jìn)行組織捡硅,所以我采用的就是很普通的UIView的展示形式,并沒有為了性能去做很復(fù)雜的渲染操作盗棵。

UIView的好處主要就是方便做布局和子視圖管理壮韭,但在屏幕上做動(dòng)畫時(shí),是對(duì)CALayer進(jìn)行渲染的纹因。也就是說(shuō)UIView就是用來(lái)做視圖組織喷屋,并不會(huì)直接參與渲染,這也符合蘋果的設(shè)計(jì)理念瞭恰。

復(fù)用池

彈幕是一個(gè)高頻使用的控件屯曹,所以不能一直頻繁創(chuàng)建,以及添加和移除視圖惊畏,會(huì)對(duì)性能有影響恶耽。所以就像很多同學(xué)設(shè)計(jì)的模塊一樣,我也引入了緩存池的概念颜启,我這里叫復(fù)用池偷俭。

彈幕復(fù)用池和UITableView的復(fù)用池類似,離開屏幕的彈幕會(huì)被放在復(fù)用池中等待復(fù)用缰盏,下次直接從復(fù)用池中取而不重新創(chuàng)建涌萤。彈幕視圖做的工作就是接收新的model對(duì)象淹遵,并根據(jù)彈幕類型進(jìn)行不同的視圖布局。

并且彈幕只會(huì)在創(chuàng)建時(shí)被addSubview一次负溪,當(dāng)彈幕離開屏幕不會(huì)被從父視圖移除合呐,這樣彈幕從復(fù)用池中取出時(shí)也不需要被addSubview。當(dāng)動(dòng)畫執(zhí)行完成后笙以,彈幕就直接留在動(dòng)畫結(jié)束的位置淌实,下次做動(dòng)畫時(shí)彈幕會(huì)自動(dòng)回到fromValue的位置。實(shí)際上視圖結(jié)構(gòu)就如上圖所示猖腕,灰色區(qū)域就是可視區(qū)域拆祈。

系統(tǒng)彈幕

在視頻剛開始時(shí)會(huì)有引導(dǎo)信息,比如引導(dǎo)用戶發(fā)彈幕倘感,或者提示彈幕有多少條放坏,這個(gè)我們叫做系統(tǒng)彈幕。系統(tǒng)彈幕一般是展示到屏幕中間時(shí)老玛,才開始展示后續(xù)彈幕淤年。但是要精確的計(jì)算到彈幕到達(dá)屏幕中間,然后再展示后續(xù)彈幕蜡豹,這種的采用清除前后特定時(shí)間段的彈幕就不太精確麸粮,所以我們采用的是另一套實(shí)現(xiàn)方案。

系統(tǒng)彈幕的實(shí)現(xiàn)是通過(guò)一個(gè)更高精度的CADisplayLink進(jìn)行輪詢檢測(cè)镜廉,也就是把frameInterval設(shè)置的更小弄诲,我這里設(shè)置的是10,也就是每秒檢測(cè)六次娇唯。但是進(jìn)行檢測(cè)時(shí)不能直接用CALayer進(jìn)行判斷齐遵,需要使用presentationLayer也就是屏幕上正在展示的layer進(jìn)行檢測(cè),通過(guò)這個(gè)layer獲取到的frame和屏幕上顯示的才是一致的塔插。

這里簡(jiǎn)單介紹一下CALayer的結(jié)構(gòu)梗摇,我們都知道UIView是對(duì)CALayer的一層封裝,實(shí)際上屏幕上的顯示都是通過(guò)layer來(lái)實(shí)現(xiàn)的想许,而layer本身也分為以下三層伶授,并有不同的功能。

  • presentationLayer伸刃,其本身是當(dāng)前幀的一個(gè)拷貝谎砾,每次獲取都是一個(gè)新的對(duì)象,和動(dòng)畫過(guò)程中屏幕上顯示的位置是一樣的捧颅。
  • modelLayer,表示layer動(dòng)畫完成后的真實(shí)值较雕,如果打印一下modelLayerlayer的話碉哑,發(fā)現(xiàn)二者其實(shí)是一個(gè)對(duì)象挚币。
  • renderLayer,渲染幀扣典,應(yīng)用程序會(huì)根據(jù)視圖層級(jí)妆毕,構(gòu)成由layer組成的渲染樹,renderLayer就代表layer在渲染樹中的對(duì)象贮尖。

炫彩彈幕

在播放彈幕的過(guò)程中笛粘,我們可以看到有漸變顏色的彈幕,我們叫做“炫彩彈幕”湿硝。這種彈幕有一個(gè)很明顯的特征薪前,就是其顏色是漸變的。這時(shí)候要考慮性能的問(wèn)題关斜,因?yàn)椴シ鸥咔逡曨l時(shí)本身性能消耗就很大示括,在彈幕量比較大的情況下,會(huì)造成更多的性能消耗痢畜,所以減少性能消耗就是很重要的垛膝,漸變彈幕可能會(huì)使性能消耗加劇。

對(duì)于漸變文字丁稀,一般都是通過(guò)mask的方式實(shí)現(xiàn)吼拥,下面放一個(gè)CAGradientLayer做漸變,上面蓋一個(gè)文字的layer线衫。但是這種會(huì)觸發(fā)離屏渲染扔罪,會(huì)導(dǎo)致性能下降,并不能用這種方案桶雀。經(jīng)過(guò)我們的嘗試矿酵,決定用設(shè)置漸變文字顏色的方式解決。

CGFloat scale = [UIScreen mainScreen].scale;
UIGraphicsBeginImageContextWithOptions(imageSize, NO, scale);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
CGColorSpaceRef colorSpace = CGColorGetColorSpace([[colors lastObject] CGColor]);
CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (CFArrayRef)ar, NULL);
CGPoint start = CGPointMake(0.0, 0.0);
CGPoint end = CGPointMake(imageSize.width, 0.0);
CGContextDrawLinearGradient(context, gradient, start, end, kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation);
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
CGGradientRelease(gradient);
CGContextRestoreGState(context);
UIGraphicsEndImageContext();

實(shí)現(xiàn)方式就是先開辟一個(gè)上下文矗积,用來(lái)進(jìn)行圖片繪制全肮,隨后對(duì)上下文進(jìn)行一個(gè)漸變的繪制,最后獲取到一個(gè)UIImage棘捣,并將圖片賦值給UILabeltextColor即可辜腺。

從離屏檢測(cè)來(lái)看,并未發(fā)生離屏渲染乍恐,fps也始終保持在一個(gè)很高的水平评疗。

暫停和開始

彈幕是隨視頻播放和暫停的,所以需要對(duì)彈幕提供暫停和繼續(xù)的支持茵烈,對(duì)于這塊我采用的CAMediaTiming協(xié)議來(lái)處理百匆,可以通過(guò)此協(xié)議對(duì)動(dòng)畫的過(guò)程進(jìn)行控制。

代碼中加0.05是為了避免彈幕在暫停時(shí)導(dǎo)致的回跳呜投,所以加上一個(gè)時(shí)間差加匈。具體原因是因?yàn)橥ㄟ^(guò)convertTime:fromLayer:方法計(jì)算得到的時(shí)間存璃,和屏幕上彈幕的位置依然存在一個(gè)微弱的時(shí)間差,而導(dǎo)致渲染時(shí)視圖位置發(fā)生回跳雕拼,這個(gè)0.05是一個(gè)實(shí)踐得來(lái)的經(jīng)驗(yàn)值纵东。

- (void)pauseAnimation {
    // 增加判斷條件,避免重復(fù)調(diào)用
    if (self.layer.speed == 0.f) {
        return;
    }
    CFTimeInterval pausedTime = [self.layer convertTime:CACurrentMediaTime() fromLayer:nil];
    self.layer.speed = 0.f;
    self.layer.timeOffset = pausedTime + 0.05f;
}

- (void)resumeAnimation {
    // 增加判斷條件啥寇,避免重復(fù)調(diào)用
    if (self.layer.speed == 1.f) {
        return;
    }
    CFTimeInterval pausedTime = self.layer.timeOffset;
    self.layer.speed = 1.0;
    self.layer.timeOffset = 0.0;
    self.layer.beginTime = 0.0;
    CFTimeInterval timeSincePause = [self.layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime;
    self.layer.beginTime = timeSincePause;
}

CAMediaTiming協(xié)議是用來(lái)對(duì)動(dòng)畫過(guò)程控制的一個(gè)協(xié)議偎球,例如通過(guò)CoreAnimation創(chuàng)建的動(dòng)畫,CALayer遵守了這個(gè)協(xié)議辑甜。這樣如果需要對(duì)動(dòng)畫進(jìn)行控制的話衰絮,不需要引用一個(gè)CABasicAnimation對(duì)象,然后再修改動(dòng)畫屬性這種方式對(duì)動(dòng)畫流程進(jìn)行控制栈戳,只需要直接對(duì)layer的屬性進(jìn)行修改即可岂傲。

下面是CAMediaTiming協(xié)議中一些關(guān)鍵的屬性,在上文中也用到了其中的部分屬性子檀。

  • beginTime镊掖,動(dòng)畫開始時(shí)間,可以控制動(dòng)畫延遲展示褂痰。一般是一個(gè)絕對(duì)時(shí)間亩进,為了保證準(zhǔn)確性,最好先對(duì)當(dāng)前layer進(jìn)行一個(gè)轉(zhuǎn)換缩歪,延遲展示在后面加對(duì)應(yīng)的時(shí)間即可归薛。
  • duration,動(dòng)畫結(jié)束時(shí)間匪蝙。
  • speed主籍,動(dòng)畫執(zhí)行速度,默認(rèn)是1逛球。動(dòng)畫最終執(zhí)行時(shí)間=duration/speed千元,也就是duration是3秒,speed是2颤绕,最終動(dòng)畫執(zhí)行時(shí)間是1.5秒幸海。
  • timeOffset,控制動(dòng)畫進(jìn)程奥务,主要用來(lái)結(jié)合speed來(lái)對(duì)動(dòng)畫進(jìn)行暫停和開始物独。
  • repeatCount,重復(fù)執(zhí)行次數(shù)氯葬,和repeatDuration互斥挡篓。
  • repeatDuration,重復(fù)執(zhí)行時(shí)間溢谤,如果時(shí)間不是duration的倍數(shù)瞻凤,最后一次的動(dòng)畫會(huì)執(zhí)行不完整憨攒。
  • autoreverses世杀,動(dòng)畫反轉(zhuǎn)阀参,在動(dòng)畫執(zhí)行完成后,是否按照原先的過(guò)程反向執(zhí)行一次瞻坝。此屬性會(huì)對(duì)duration有一個(gè)疊加效果蛛壳,如果duration是1s,autoreverses設(shè)置為YES后時(shí)間就是2s所刀。
  • fillMode衙荐,如果想要?jiǎng)赢嬙陂_始時(shí),就停留在fromValue的位置浮创,就可以設(shè)置為kCAFillModeBackwards忧吟。如果想要?jiǎng)赢嫿Y(jié)束時(shí)停留在toValue的位置,就設(shè)置為kCAFillModeForwards斩披,如果兩種都要就設(shè)置為kCAFillModeBoth溜族,默認(rèn)是kCAFillModeRemoved,即動(dòng)畫結(jié)束后移除垦沉。

發(fā)送彈幕

插入彈幕

現(xiàn)在彈幕一般都會(huì)結(jié)合劇中主角煌抒,以及各種文字顏色讓你去選擇,通過(guò)這些功能也可以帶來(lái)一部分付費(fèi)用戶厕倍。當(dāng)發(fā)送一條彈幕時(shí)寡壮,會(huì)從上到下查找軌道凹联,查找軌道時(shí)是通過(guò)presentationLayer來(lái)進(jìn)行frame的判斷采桃,如果layer的最右邊不在屏幕外,并且距離右側(cè)屏幕還有一定空隙属瓣,項(xiàng)目中寫的是10pt组民,則表示有空位可以插入下一條彈幕棒仍,這條彈幕會(huì)被放在這條軌道上。

如果當(dāng)前軌道沒有空位置邪乍,則從上到下逐條查找軌道降狠,直到找到有空位的軌道。如果當(dāng)前屏幕上彈幕較多庇楞,所有軌道都沒有空位榜配,則這一條彈幕會(huì)被拋棄。

如果是自己發(fā)的彈幕吕晌,這個(gè)是必須要展示出來(lái)的蛋褥,因?yàn)橛脩舭l(fā)的彈幕要在界面上給用戶一個(gè)反饋。對(duì)于自己發(fā)的彈幕睛驳,會(huì)有一個(gè)插隊(duì)操作烙心,優(yōu)先級(jí)比其他彈幕都要高膜廊。自己發(fā)的彈幕并不入本地?cái)?shù)據(jù)庫(kù),只是進(jìn)行一個(gè)網(wǎng)絡(luò)請(qǐng)求傳給服務(wù)器淫茵,以及在界面上進(jìn)行展示爪瓜。

選擇角色

在上面的圖片中可以看到,文本之前會(huì)有角色和角色名匙瘪,這些都是獨(dú)立于輸入文字之外的铆铆。用戶如果刪除完輸入的文字之后,再點(diǎn)擊刪除要把角色也一起刪除掉丹喻。輸入框頁(yè)面構(gòu)成是一個(gè)UITextField薄货,左邊的角色頭像和角色名是一個(gè)自定義View,被當(dāng)做textFieldleftView來(lái)展示碍论。如果刪除的話就是將leftViewnil即可谅猾。

問(wèn)題在于,如果使用UIControlEventEditingChanged的事件鳍悠,只能獲取到文本發(fā)生變化時(shí)的內(nèi)容税娜,如果輸入框的文字已經(jīng)被刪完,而角色是一個(gè)leftView贼涩,但由于文本已經(jīng)為空巧涧,則無(wú)法再獲取到刪除事件,也就不能把角色刪除掉遥倦。

對(duì)于這個(gè)問(wèn)題谤绳,我們找到了下面的協(xié)議來(lái)實(shí)現(xiàn)。UITextField遵守UITextInput協(xié)議袒哥,但UITextInput協(xié)議繼承自UIKeyInput協(xié)議缩筛,所以也就擁有下面兩個(gè)方法。下面兩個(gè)方法分別在插入文字堡称,以及點(diǎn)擊刪除按鈕時(shí)調(diào)用瞎抛,即使文本已經(jīng)為空,依然可以收到deleteBackward的回調(diào)却紧。在這個(gè)回調(diào)里就可以判斷文本是否為空桐臊,如果為空則刪除角色即可。

@protocol UIKeyInput <UITextInputTraits>
@property(nonatomic, readonly) BOOL hasText;
- (void)insertText:(NSString *)text;
- (void)deleteBackward;
@end

彈幕設(shè)置

參數(shù)調(diào)整

彈幕一般都不是一種形態(tài)晓殊,很多參數(shù)都是可以調(diào)整的断凶,對(duì)于iPhoneiPad兩個(gè)平臺(tái)參數(shù)還不一樣,調(diào)整范圍也不一樣巫俺。這些參數(shù)肯定是不能放在業(yè)務(wù)代碼里進(jìn)行判斷的认烁,這樣各種判斷條件散落在項(xiàng)目中,會(huì)導(dǎo)致代碼耦合嚴(yán)重。

對(duì)于這個(gè)問(wèn)題却嗡,我們的實(shí)現(xiàn)方式是通過(guò)BarrageConfig來(lái)區(qū)分不同平臺(tái)舶沛,將兩個(gè)平臺(tái)的數(shù)值差異都放在這個(gè)類中。業(yè)務(wù)部分直接讀取屬性即可窗价,不需要做任何判斷如庭,包括退出進(jìn)程的持久化也在內(nèi)部完成,這樣就可以讓業(yè)務(wù)部分使用無(wú)感知舌镶,也保證了各個(gè)類中的數(shù)值統(tǒng)一柱彻。

當(dāng)有任何參數(shù)的改動(dòng)豪娜,都可以對(duì)BarrageConfig進(jìn)行修改餐胀,然后調(diào)用RenderlayoutBarrageSubviews進(jìn)行渲染即可。因?yàn)檎{(diào)整參數(shù)之后瘤载,屏幕上已經(jīng)顯示的彈幕也需要跟著變否灾,而且變得過(guò)程中還是在動(dòng)畫執(zhí)行過(guò)程中,動(dòng)畫執(zhí)行不能斷掉鸣奔,所以對(duì)動(dòng)畫的處理就很重要墨技。這部分處理起來(lái)比較復(fù)雜,就不詳細(xì)講了挎狸。

點(diǎn)贊

彈幕還會(huì)有點(diǎn)贊和長(zhǎng)按的功能扣汪,點(diǎn)贊一般是點(diǎn)擊屏幕然后出現(xiàn)一個(gè)選擇視圖,點(diǎn)擊點(diǎn)贊后有一個(gè)動(dòng)畫效果锨匆。長(zhǎng)按就是選中一個(gè)彈幕崭别,識(shí)別到手勢(shì)長(zhǎng)按之后,右側(cè)出現(xiàn)一個(gè)舉報(bào)頁(yè)面恐锣。

這兩個(gè)手勢(shì)我用taplongPress兩個(gè)手勢(shì)來(lái)處理茅主,并給longPress設(shè)置了一個(gè)0.2s的識(shí)別時(shí)間,將這兩種手勢(shì)的識(shí)別交給系統(tǒng)去做土榴,這樣也比較省事诀姚。

這兩個(gè)手勢(shì)都加到Render上,而不是每個(gè)彈幕視圖對(duì)應(yīng)一個(gè)手勢(shì)玷禽,這樣管理起來(lái)也比較簡(jiǎn)單赫段。這樣在手勢(shì)識(shí)別時(shí),就需要先找到手勢(shì)觸摸點(diǎn)矢赁,再根據(jù)觸摸點(diǎn)查找對(duì)應(yīng)的彈幕視圖糯笙,查找的時(shí)候依然通過(guò)presentationLayer來(lái)查找區(qū)域,而不能用視圖做查找坯台。

- (void)singleTapHandle:(UITapGestureRecognizer *)tapGestureRecognizer {
    CGPoint touchLocation = [tapGestureRecognizer locationInView:self.barrageRender];
    __block BOOL barrageLiked = NO;
    weakifyself;
    [self enumerateObjectsUsingBlock:^(SVBarrageItemLabelView *itemLabel, NSUInteger index, BOOL *stop) {
        strongifyself;
        if ([itemLabel.layer.presentationLayer hitTest:touchLocation] && barrageLiked == NO) {
            barrageLiked = YES;
            [self likeAction:itemLabel withTouchLocation:touchLocation];
            *stop = YES;
        }
    }];
}

彈幕廣告

廣告

對(duì)于這么好的一個(gè)展示位置炬丸,廣告部門必然不會(huì)放過(guò)。在視頻播放過(guò)程中,會(huì)根據(jù)金主爸爸投放要求稠炬,在指定的時(shí)間展示一個(gè)廣告彈幕焕阿,并且這個(gè)彈幕的形態(tài)還是不固定的。也就是說(shuō)大小首启、動(dòng)畫形式都不能確定暮屡,而且這條彈幕還要在最上層展示。

對(duì)于這個(gè)問(wèn)題毅桃,我們采用的方案是褒纲,給廣告專門留了一個(gè)視圖,視圖層級(jí)高于Render钥飞,在初始化廣告SDK的時(shí)候傳給SDK莺掠,這樣就把廣告彈幕的控制交給SDK,我們不做處理读宙。

圖層管理

播放器上存在很多圖層彻秆,播控、彈幕Render结闸、廣告之類的唇兑,看得到的和看不到的有很多。對(duì)于這個(gè)問(wèn)題桦锄,播放器創(chuàng)建了一個(gè)繼承自NSObject的視圖管理器扎附,這個(gè)視圖管理器可以對(duì)視圖進(jìn)行分層管理。

播放器上的視圖结耀,都需要調(diào)用指定的方法留夜,將自己加到對(duì)應(yīng)的圖層上,移除也需要調(diào)用對(duì)應(yīng)的方法饼记。當(dāng)需要調(diào)整前后順序時(shí)香伴,修改定義的枚舉即可。

數(shù)據(jù)分離

前面一直說(shuō)的都是視圖的部分具则,沒有涉及數(shù)據(jù)的部分即纲,這是因?yàn)閁I和數(shù)據(jù)其實(shí)是解耦和的,二者并沒有強(qiáng)耦合博肋,所以可以單獨(dú)拿出來(lái)講。數(shù)據(jù)部分的設(shè)計(jì)匪凡,類似于播放器的local server方案,將請(qǐng)求數(shù)據(jù)到本地唇跨,和從本地讀取數(shù)據(jù)做了一個(gè)拆分稠通。

請(qǐng)求數(shù)據(jù)

彈幕數(shù)據(jù)量比較大改橘,肯定是不能一次都請(qǐng)求下來(lái)的,這樣很容易造成請(qǐng)求失敗的情況飞主。所以這塊采取的是五分鐘一個(gè)分片數(shù)據(jù)高诺,在當(dāng)前的五分鐘彈幕快播完的前十秒,開始請(qǐng)求下一個(gè)時(shí)間段的彈幕虱而。如果拖動(dòng)進(jìn)度條,則拖動(dòng)完成后開始請(qǐng)求新位置的彈幕胖烛。在每次請(qǐng)求前都會(huì)查一下庫(kù),數(shù)據(jù)是否已存在。

請(qǐng)求數(shù)據(jù)由業(yè)務(wù)部分驅(qū)動(dòng)众旗,請(qǐng)求數(shù)據(jù)后并不會(huì)直接拿來(lái)使用,而是存入本地?cái)?shù)據(jù)庫(kù)贡歧,這部分比較像服務(wù)器往本地寫ts分片的操作滩租。數(shù)據(jù)庫(kù)存儲(chǔ)的部分,推薦使用WCDB利朵,彈幕這塊主要都是批量數(shù)據(jù)處理律想,而WCDB對(duì)于批量數(shù)據(jù)的處理,性能高于FMDB绍弟。

取數(shù)據(jù)

取數(shù)據(jù)同樣由業(yè)務(wù)層驅(qū)動(dòng)技即,為了減少頻繁進(jìn)行數(shù)據(jù)庫(kù)讀寫,每隔十秒鐘進(jìn)行一次數(shù)據(jù)庫(kù)批量讀取樟遣,并轉(zhuǎn)換為model返回給上層而叼。彈幕模塊在內(nèi)存中維護(hù)了一個(gè)字典,字典以時(shí)間為key豹悬,數(shù)組為value葵陵,因?yàn)橥粫r(shí)間可能會(huì)有多條彈幕。

從數(shù)據(jù)庫(kù)批量獲取的數(shù)據(jù)會(huì)被保存到字典中瞻佛,上層業(yè)務(wù)層在使用數(shù)據(jù)時(shí)脱篙,都是通過(guò)字典來(lái)獲取數(shù)據(jù),這樣也實(shí)現(xiàn)了數(shù)據(jù)層和業(yè)務(wù)層的一個(gè)解耦和。上層業(yè)務(wù)層每隔一秒從字典中讀取一次數(shù)據(jù)绊困,并通過(guò)數(shù)據(jù)找到合適的軌道忍弛,將數(shù)據(jù)傳給合適的軌道來(lái)處理。

彈幕防擋探索

現(xiàn)在很多視頻網(wǎng)站都上線了彈幕防遮擋方案考抄,對(duì)于視頻中的人物细疚,彈幕會(huì)在其下方展示,而不會(huì)遮擋住人物川梅。還有的應(yīng)用針對(duì)彈幕遮擋進(jìn)行了新的探索疯兼,即成為付費(fèi)會(huì)員后,可以選擇只有自己喜歡的愛豆不被遮擋贫途,其他人依然被遮擋吧彪。

語(yǔ)義分割

根據(jù)業(yè)務(wù)場(chǎng)景我們分析,首先需要把人像部分分割出來(lái)丢早,獲取到人像的位置之后才能做后續(xù)的操作姨裸。所以人像分割的部分采取語(yǔ)義分割的方式實(shí)現(xiàn)傀缩,提前對(duì)視頻關(guān)鍵幀進(jìn)行標(biāo)注赡艰,這個(gè)工作量是很龐大的慷垮,所以需要一個(gè)專門的標(biāo)注團(tuán)隊(duì)去完成料身。根據(jù)標(biāo)注后的模型芹血,通過(guò)機(jī)器學(xué)習(xí)的方式祟牲,讓計(jì)算機(jī)可以準(zhǔn)確的識(shí)別出人的位置说贝,并導(dǎo)出多邊形路徑乡恕。

這里面還涉及一個(gè)問(wèn)題,就是近景識(shí)別和遠(yuǎn)景識(shí)別的問(wèn)題运杭,機(jī)器進(jìn)行識(shí)別時(shí)只需要識(shí)別近景人物辆憔,遠(yuǎn)景人物并不需要進(jìn)行識(shí)別虱咧,否則彈幕展示效果會(huì)受到很大影響腕巡。語(yǔ)義分割可以通過(guò)Google的Mask_RCNN來(lái)實(shí)現(xiàn)绘沉。

客戶端實(shí)現(xiàn)方案

客戶端的實(shí)現(xiàn)方案是通過(guò)人像的多邊形路徑车伞,對(duì)原視頻摳出人像并導(dǎo)出一個(gè)新的視頻帖世。在播放的時(shí)候?qū)嶋H上是前后兩個(gè)播放器在播放,彈幕夾在兩個(gè)播放器中間來(lái)實(shí)現(xiàn)的赂弓。并且前面的人像層需要做邊緣虛化翔怎,讓彈幕的過(guò)渡顯得自然些赤套,否則會(huì)太突兀容握。

這種方案的過(guò)渡效果會(huì)好一些剔氏。因?yàn)閷?duì)每一幀視頻進(jìn)行切割的時(shí)候,每一幀并不能保證相鄰幀切割的邊緣相差都不大羊苟,也就是相鄰近的幀邊緣不能保證很好的銜接蜡励,這樣就容易出現(xiàn)視頻連續(xù)性的問(wèn)題阻桅。前后兩個(gè)播放器疊加的方案占遥,兩個(gè)層的視頻內(nèi)容實(shí)際上是銜接很緊密的输瓜,把彈幕層去掉你根本看不出來(lái)這是兩層播放器尤揣,所以連續(xù)性的問(wèn)題就不明顯了北戏。

前端實(shí)現(xiàn)方案

前端的實(shí)現(xiàn)方案是服務(wù)端將多邊形路徑放在一個(gè)svg文件中嗜愈,并將文件下發(fā)給前端蠕嫁,前端通過(guò)cssmask?image遮罩實(shí)現(xiàn)的剃毒。通過(guò)遮罩把人像部分摳出來(lái)赘阀,人像之外依然是黑色區(qū)域基公,黑色是可顯示區(qū)域,和iOS的mask屬性類似欠痴。

B站是最開始做彈幕防擋的喇辽,現(xiàn)在B站已經(jīng)不局限于真人彈幕防擋了菩咨,現(xiàn)在很多番劇中的動(dòng)漫人物也支持彈幕防擋√卣迹可以看下面的視頻感受一下是目。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末懊纳,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子闺兢,更是在濱河造成了極大的恐慌屋谭,老刑警劉巖桐磁,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件所意,死亡現(xiàn)場(chǎng)離奇詭異扶踊,居然都是意外死亡秧耗,警方通過(guò)查閱死者的電腦和手機(jī)舶治,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)珠闰,“玉大人伏嗜,你說(shuō)我怎么就攤上這事≌豕欤” “怎么了卷扮?”我有些...
    開封第一講書人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵衔瓮,是天一觀的道長(zhǎng)热鞍。 經(jīng)常有香客問(wèn)我薇宠,道長(zhǎng)澄港,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任狱意,我火速辦了婚禮详囤,結(jié)果婚禮上藏姐,老公的妹妹穿的比我還像新娘羔杨。我一直安慰自己娃属,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著卵皂,像睡著了一般灯变。 火紅的嫁衣襯著肌膚如雪殴玛。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,144評(píng)論 1 285
  • 那天添祸,我揣著相機(jī)與錄音滚粟,去河邊找鬼。 笑死刃泌,一個(gè)胖子當(dāng)著我的面吹牛凡壤,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播耙替,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼亚侠,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤镶殷,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后帽馋,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡匈仗,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年坠韩,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了氢惋。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡捆姜,死狀恐怖磕仅,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情兼贸,我是刑警寧澤螺垢,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響亲配,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一晴股、第九天 我趴在偏房一處隱蔽的房頂上張望寂呛。 院中可真熱鬧强胰,春花似錦、人聲如沸杏节。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)腰根。三九已至册养,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間芬为,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工唯袄, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留阎抒,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓遇汞,卻偏偏與公主長(zhǎng)得像迂烁,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子递鹉,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容