banner
DIYgod

Hi, DIYgod

写代码是热爱,写到世界充满爱!
github
twitter
follow
bilibili
telegram
email
steam
playstation
nintendo switch

BAS 高級彈幕

寫在前面#

如果你好奇 B 站的工作有不有趣,或者我來 B 站之後都在做什麼,那麼這篇文章或許可以解答你的部分疑問。

來 B 站後,除了負責 HTML5 播放器的一些模塊、重構和日常維護以外,BAS 高級彈幕算是我半年來負責開發的最大一個項目了。

本文整理自今天在公司的超極電磁波分享(據說我還是有史以來年齡最小的講師 (=・ω・=),也算是對這個項目的一个階段性總結。

什麼是 BAS 彈幕#

BAS,全稱 Bilibili Animation Script,是新一代 bilibili 高級彈幕腳本語言,是一種用來描述高級彈幕樣式、交互和動畫的文本。

BAS 彈幕是用 BAS 描述的高級彈幕,由元素和動畫組成,元素分為文本對象、交互按鈕、path 對象,動畫分為簡單動畫、串聯動畫、並聯動畫。

BAS 彈幕主要面向字幕君等高端玩家,可以用於字幕、特效、交互應用、遊戲、純彈幕作品等場景。

目前 Web 端已經上線,移動端也基本開發完成,期望在明年的活動或拜年祭中能夠用到。

我們做了幾個視頻來直觀展示 BAS 彈幕:

第一個是毛醬大佬做的彈幕 PV:https://www.bilibili.com/video/av257649/

第二個是彈幕實現的爛蘋果:https://www.bilibili.com/video/av18682336/

第三個是交互按鈕的演示:https://www.bilibili.com/video/av16558829/index_3.html#page=3,另外像哔哩哔哩排行榜周刊這種也是很好的應用場景

字幕應用方面可以很容易實現一個字幕文件轉 BAS 之類的工具,今後會繼續做這樣的事情。

幾種高級彈幕比較#

mode7#

彈幕通過界面設置,不需要編寫代碼,使用簡單但功能比較局限。

bas1

mode8#

即代碼彈幕,功能非常強大但需要編寫代碼,使用複雜、安全性較差,而且只支持 Flash 平台。

bas2

mode9#

即 BAS 彈幕,是 mode 7 和 mode 8 的折中方案。

跟 mode 7 相比,mode 9 需要編寫腳本,使用稍複雜,但支持交互、圖形和更複雜的動畫,功能要強大得多。

跟 mode 8 相比,mode 9 簡化了語法,改用聲明式,使用更簡單;更安全,播放器解析實現,不合法腳本不會放行,程序可控,而不是直接操作彈幕;可以跨平台。

bas3

使用 BAS 彈幕#

發送權限#

首先權限上對 BAS 彈幕的發送權限有著嚴格的限制,設計上一般用戶需要先用硬幣購買,然後等待 UP 主確認之後才可以使用,字幕君等有特殊權限的用戶才可以直接使用,但目前只開放給字幕君使用,字幕君使用並完善之後再考慮擴大使用場景。

硬幣購買UP 主確認
一般會員
UP 主×
VIP×
字幕君 / 管理員××

發送入口#

  1. bas4
  2. bas5

注意沒有發送權限時入口是隱藏的,這時可以在試驗室中進行嘗試。

編寫腳本#

下面我們來嘗試編寫一些簡單的腳本,BAS 腳本非常簡單,它是一種聲明式描述性的腳本,語法易用,保證了對象塊和操作塊的獨立性。

嘗試 BAS 彈幕最簡單的方法是使用文檔上的例子和試驗室,你可以在瀏覽器新標籤頁中打開它,跟著例子嘗試一些基礎用法。

以文本對象為例,一個簡單的帶漸隱動畫的文本對象是這樣的:

def text demo {
    content = "BAS"
}
set demo {
    alpha = 0
} 5s

這樣我們就成功創建了一條漸隱的 BAS 彈幕,看起來很簡單,但是 js 在背後做了大量工作,js 會先把 BAS 腳本解析成 js 可識別的對象,應用上默認屬性,再把它渲染到播放器裡,同時開始動畫,這時候就可以在播放器左上角看到一個漸隱的白色文本。

定位#

定位也非常簡單,BAS 彈幕的定位由彈幕錨點(anchorX anchorY)和舞台位置(x y)共同決定。錨點是彈幕的中心點,(0, 0) 為彈幕的左上角,(1, 1) 為彈幕的右下角。

def text tl {
    content = "左上"
    x = 0
    y = 0
    anchorX = 0
    anchorY = 0
}
def text tr {
    content = "右上"
    x = 100%
    y = 0
    anchorX = 1
    anchorY = 0
}
def text bl {
    content = "左下"
    x = 0
    y = 100%
    anchorX = 0
    anchorY = 1
}
def text br {
    content = "右下"
    x = 100%
    y = 100%
    anchorX = 1
    anchorY = 1
}
def text c {
    content = "中心"
    x = 50%
    y = 50%
    anchorX = 0.5
    anchorY = 0.5
}

彈幕舞台#

彈幕舞台是彈幕的繪製範圍,彈幕舞台默認為視頻的真實區域,此外文本對象可以通過 parent 屬性把其他文本對象指定為父級元素,以父級元素作為舞台進行繪製,父級元素會影響子級元素的定位、縮放等。

def text a {
    content = "□"
    fontSize = 40%
    x = 0
    y = 0
    color = 0xffff00
}
def text b {
    parent = "a"
    content = "□"
    fontSize = 20%
    x = 0
    y = 0
    color = 0xff00ff
}
set a {
    x = 50%
    y = 0
} 2s
then set a {} 3s
set b {
    y = 50%
} 3s
then set b {} 3s

生命週期#

生命週期是 BAS 的另一個重要概念,生命週期是彈幕存活的時間,沒有指定 duration 屬性時,元素生命週期為動畫總時間,沒有動畫時默認為 4s。生命週期結束後元素就會在舞台中被清除。

def text a {
    content = "BAS"
}
def text a {
    content = "BAS"
    duration = 10s
}
def text a {
    content = "BAS"
}
set a {} 10s

自適應#

位置和字號為百分比值時可以根據舞台大小自適應,可以實現各平台、不同播放器大小時彈幕的一致性效果,使彈幕在不同情況下相對於視頻的位置和大小是固定的,位置坐標為當前舞台寬高 * 百分比值 px,字號為當前舞台寬度 * 百分比值 px。

def text c {
    content = "BAS"
    x = 50%
    y = 50%
    anchorX = 0.5
    anchorY = 0.5
    fontSize = 5%
}

這時候改變播放器大小,彈幕大小也會跟隨播放器改變,結果是它相對於視頻的位置和大小是固定的。

交互#

目前只有交互按鈕支持一些簡單的點擊效果,轉跳到視頻某個時間點、新窗口打開其他視頻等。

seek 按鈕:

def button c {
    text = "跳轉到30分鐘"
    x = 35%
    y = 45%
    fontSize = 5%
    textColor = 0xffffff
    fillColor = 0x80D8FF
    target = seek {
        time = 30m
    }
}

av 跳轉按鈕:

def button c {
    text = "av1714157"
    x = 35%
    y = 45%
    fontSize = 5%
    textColor = 0xffffff
    fillColor = 0x80D8FF
    duration = 2s
    target = av {
        av = 1714157
        page = 1
        time = 20.5s500ms
    }
}

bangumi 跳轉按鈕:

def button c {
    text = "第22話 春風"
    x = 35%
    y = 45%
    fontSize = 5%
    textColor = 0xffffff
    fillColor = 0x80D8FF
    duration = 2s
    target = bangumi {
        seasonId = 1699
        episodeId = 80041
        time = 1m30s
    }
}

圖形#

可以使用 path 對象繪製 svg 圖形,d 屬性對應 svg 的路徑。

def path p {
    d = "M30.828,30.422 18.997,16.260 Z"
    viewBox="0 0 32 34"
    x = 45%
    y = 45%
    scale = 3
    borderWidth = 1
    borderColor = 0xffffff
    borderAlpha = 0.8
    fillColor = 0x00a1d6
    fillAlpha = 0.8
}

動畫#

動畫分為簡單動畫、串聯動畫、並聯動畫。

彈幕的屬性有可漸變、不可漸變、不可變之分,只有可漸變屬性才有正常的動畫效果,對非漸變屬性設置新值會立即生效,對不可變的屬性設值將被忽略。原則上某一屬性在一個 set 語句中最多只能出現一次,在實現上,如果多次出現,以最後一次為準。

串聯動畫以先後順序運行。

def text a {
  content = "BAS"
}
set a {
    color = 0x000000
} 1s
then set a {
    alpha = 0
} 1s

並聯動畫同時進行,並聯相同屬性時,以最後一次為準,之前衝突的動畫會被忽略,由於技術限制,x y rotateX rotateY rotateZ scale 視為相同屬性。

def text a {
  content = "BAS"
}
set a {
    color = 0x000000
} 1s
set a {
    alpha = 0
} 1s

BAS 彈幕的前端實現#

從 BAS 腳本到渲染在瀏覽器的 DOM 元素主要有以下步驟:

  1. 將 BAS 腳本解析成 js 對象(https://github.com/aristotle9/as3cc
  2. 應用默認值、計算百分比值
  3. 監控生命週期
  4. 解決屬性衝突
  5. 繪製元素、應用樣式和動畫
  6. 綁定交互事件

定位#

定位由 BAS 腳本的錨點(anchorX anchorY)和位置(x y)共同決定,實現上使用兩個嵌套的 DOM 元素,外部元素定位舞台位置,內部元素定位彈幕錨點,比如一個居中的文本對象:

def text c {
    content = "BAS"
    x = 50%
    y = 50%
    anchorX = 0.5
    anchorY = 0.5
}

渲染出的 DOM 結構大概是這樣:

<div style="transform:translate((舞台寬度*50%)px, (舞台高度*50%)px);">
  <div style="transform:translate(-50%,-50%);">BAS彈幕</div>
</div>

動畫#

動畫上考慮到瀏覽器兼容性和易用性,CSS3 的 animation 是最佳的選擇,其中涉及屬性有:

屬性描述
@keyframes定義動畫
animation-name對應 @keyframes 動畫的名稱
animation-duration動畫完成一個週期的時間
animation-play-state動畫運行或者暫停
animation-timing-function動畫的速度曲線

簡單動畫#

沿用上面的例子:

def text demo {
    content = "BAS"
}
set demo {
    alpha = 0
} 5s

這樣一條 BAS 腳本渲染出的 DOM 結構大概是這樣:

<style>
@keyframes a1 {
    100% { opacity:0; }
}</style>
<div style="animation-name:a1;animation-duration:5s;opacity:1;">BAS</div>

keyframes 定義動畫關鍵幀,動畫結束時透明度為零;animation-duration 對應動畫的時間 5s。

並聯動畫#

定義多個 keyframes 實現多個動畫同時運行。

def text a {
    content = "BAS"
}
set a {
    color = 0x000000
} 1s
set a {
    alpha = 0
} 1s
<style>
@keyframes a1 {
    100% { color:#000000; }
}
@keyframes a2 {
    100% { opacity:0; }
}
</style>
<div style="animation-name:a1,a2;animation-duration:1s,1s;opacity:1;color:#ffffff;">BAS</div>

串聯動畫#

使用 animation-delay 錯開不同動畫開始的時間,實現串聯的效果。

def text a {
  content = "BAS"
}
set a {
    color = 0x000000
} 1s
then set a {
    alpha = 0
} 1s
<style>
@keyframes a1 {
    100% { color:#000000; }
}
@keyframes a2 {
    0% { color:#000000; }
    100% { color:#000000;opacity:0; }
}
</style>
<div style="animation-name:a1,a2;animation-duration:1s,1s;animation-delay:0s,1s;opacity:1;color:#ffffff;">BAS</div>

狀態控制#

開始#

animation-play-state: running

暫停#

animation-play-state: paused

中間狀態#

設置 animation-delay 為負值就可以實現從某個中間狀態開始動畫。

比如彈幕的生命週期對應視頻的 1s 到 5s,視頻跳轉到 4s 時,需要設置彈幕 animation-delay 屬性為 -1s。

結束#

生命週期結束需要及時清除元素,原理是 animation 動畫結束會觸發 animationend 事件,該事件觸發時清除掉元素即可。元素沒有動畫時需要指定一個空動畫。

<style>
@keyframes a1 {
    100% { }
}
</style>
載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。