跳轉到

Android 自訂螢幕佈局

arctos links Android SDK 提供方便的自訂螢幕佈局功能。我們可以在程式碼中建立螢幕佈局,通訊的主持人只要呼叫一個簡單的方法即可即時完成切換。

本文件會詳細解釋自訂螢幕佈局主持人切換螢幕佈局的流程。


自訂螢幕佈局

基本觀念

螢幕佈局是透過建立一個模板(VideoLayoutTemplate)實例來完成。在自訂的模板當中,我們會設定不同圖層(Layer,每個圖層則會指定繪製範圍(Rect,並且賦予圖層不同的標籤(Label,藉此指定要在此圖層上繪製哪個來源資料。SDK 會自動判斷資料來源類別,並根據模板中定義的圖層順序繪製到對應標籤的圖層上。

主持人若要切換螢幕佈局,只需要呼叫 ArctosLinks.ConferenceManager.hostSwitchScreenTemplate() 方法,傳入模板(VideoLayoutTemplate)的實例,即可改變螢幕佈局。更動後的螢幕佈局,將立即改變當前會議室所有人看到的畫面。


模板 (VideoLayoutTemplate)

「模板 (VideoLayoutTemplate)」 指影像的版面配置。當影像資料需要進行繪製時,它會依照 VideoLayoutTemplate 的設定,將資料繪製到畫面的指定範圍。

實作上 VideoLayoutTemplate 是個抽象類別。APP 必須自行設計出繼承自 VideoLayoutTemplate 的類別,複寫 layers 屬性。然後,將新類別的實體提供給 arctos links SDK 使用。此抽象類別的定義如下:

abstract class VideoLayoutTemplate {
    abstract val layers: LinkedHashMap<String, Layer>
}

從上面的程式碼可以看出,實際上 VideoLayoutTemplate 抽象類別是多個「圖層 (layer)」的集合。實際上,每個圖層擁有自己的「繪製範圍 (Rect)」、「圖片縮放方式 (ScaleType)」與資料類別(YUV 或 RGB)的設定。


圖層 (Layer)

Layer 包含了兩個屬性,分別是代表了圖層繪製範圍的 area,以及圖片縮放方式的 scaleType:

class Layer(
    val area: Rect,
    val scaleType: ScaleType
)

關於繪製範圍,首先我們把整個畫布理解成一個 2D 的平面,而此平面的 x, y 軸範圍皆介於 -1 到 1 之間。這個平面的四個頂點為左下角(-1, -1)、右下角(1, -1)、左上角(-1, 1)、右上角(1, 1):

img_01

我們可以把圖層想像為不同大小的方形紙張,放置到這個平面上的指定位置。這個紙張所標示出來的方形區塊,就是我們的「繪製範圍(Rect)」。


繪製範圍 (Rect)

實作上,Rect 由四個浮點數屬性定義:

class Rect(
    left: Float,
    top: Float,
    right: Float,
    bottom: Float
)

四個屬性分別代表 Rect 標示出的範圍的左、上、右、下的 x 軸或 y 軸座標。例如,以下這個 Rect 所標示出的範圍顯示如圖:

val area = Rect(left = 0f, top = 0.5f, right = 1f, bottom = 0f)

img_02

Warning

在創建 Rect 實體的時候,請確保 right 大於 left,top 大於 bottom。如果設定錯誤,可能會造成意料之外的翻轉或是變形現象。

Rect 類別中,也有一些預先定義好的 Rect 常數可以使用。大致上,你可以從其名稱了解它的大概位置和範圍。使用方式可以參考後面的範例說明

val FULL_SCREEN = Rect(left = -1f, top = 1f, right = 1f, bottom = -1f)
val TOP_LEFT = Rect(left = -1f, top = 1f, right = 0f, bottom = 0f)
val TOP_RIGHT = Rect(left = 0f, top = 1f, right = 1f, bottom = 0f)
val BOTTOM_LEFT = Rect(left = -1f, top = 0f, right = 0f, bottom = -1f)
val BOTTOM_RIGHT = Rect(left = 0f, top = 0f, right = 1f, bottom = -1f)
val HALF_LEFT = Rect(left = -1f, top = 1f, right = 0f, bottom = -1f)
val HALF_RIGHT = Rect(left = 0f, top = 1f, right = 1f, bottom = -1f)

圖片縮放方式 (ScaleType)

想像一下,當我們要把輸入畫面繪製到圖層上時,若是輸入資料的長寬比,與圖層自己定義的範圍 Rect 擁有的長寬比不同時,我們就需要考慮圖片的縮放方式,以避免變形問題。

我們的 Layer 參考了 Android 的 ImageView ScaleType ,定義了以下五種縮放方式:

enum class ScaleType {
    CENTER_CROP,
    FIT_XY,
    FIT_CENTER,
    FIT_START,
    FIT_END
}
  • CENTER_CROP:預設的縮放方式。視需要等比例放大輸入畫面的長寬,直到完全蓋滿 Layer 範圍。輸入畫面的一部分可能會被截掉,畫面不會變形。
  • FIT_XY:視需要延展、放大或縮小輸入畫面的長寬,直到完全蓋滿 Layer 範圍。輸入畫面不會被截掉,但畫面可能變形。
  • FIT_CENTER:視需要等比例縮小輸入畫面的長寬,直到輸入畫面全部都會顯示在 Layer 範圍。若有部分 Layer 範圍無法覆蓋到,畫面會置中對齊。輸入畫面不會被截掉,畫面不會變形。
  • FIT_START:視需要等比例縮小輸入畫面的長寬,直到輸入畫面全部都會顯示在 Layer 範圍。若有部分 Layer 範圍無法覆蓋到,畫面會靠左側或上側對齊。輸入畫面不會被截掉,畫面不會變形。
  • FIT_END:視需要等比例縮小輸入畫面的長寬,直到輸入畫面全部都會顯示在 Layer 範圍。若有部分 Layer 範圍無法覆蓋到,畫面會靠右側或下側對齊。輸入畫面不會被截掉,畫面不會變形。

Info

一般視訊通訊中,以 CENTER_CROPFIT_CENTER 兩種縮放方式最為常見。


圖層順序性

圖層加入的順序會決定每個圖層繪製的順序。最先加入的圖層會最早被繪製。然而,若圖層的範圍有所重疊,則先繪製的圖層會被後繪製的圖層給蓋過去。

abstract class VideoLayoutTemplate {
    abstract val layers: LinkedHashMap<String, Layer>
}

實作上,在 VideoLayoutTemplatelayers 屬性,實際上是個有順序性的列表(LinkedHashMap)。因此在覆寫此欄位的時候,請把要先繪製的圖層(例如背景)放在列表的前面,要比較後繪製的圖層放在列表的後面。更多使用方式,請參考後續章節的範例


圖層資料格式

每個圖層只能接受一種格式的輸入,它可以是 RGB 格式或是 YUV 格式。我們定義了以下兩種 Layer 的延伸類別:

class YuvLayer @JvmOverloads constructor(area: Rect, scaleType: ScaleType = ScaleType.CENTER_CROP) : Layer(area, scaleType)
class RgbLayer @JvmOverloads constructor(area: Rect, scaleType: ScaleType = ScaleType.CENTER_CROP) : Layer(area, scaleType)

在設計自己的 VideoLayoutTemplate 時,請根據實際需求決定該處圖層是要接受哪種資料格式。

在 arctos links SDK 中,若該圖層要顯示相機或視訊串流,會使用 YuvLayer。若要顯示白板筆畫,則會使用 RgbLayer


標籤 (Label)

在創建 VideoLayoutTemplate 時,要分別賦予每個圖層一個對應的字串標籤。例如以下的自訂模板:

class VideoLayoutFullScreen : VideoLayoutTemplate() {
    override val layers: LinkedHashMap<String, Layer> = linkedMapOf(Label.LOCAL to YuvLayer(FULL_SCREEN, CENTER_CROP))
}

這個模板就是將標籤 Label.LOCAL 對應到一個 YuvLayer。

在 SDK 中,我們根據現在的視訊會議室場景,定義了以下五種標籤。圖層必須指定對應到這五個標籤的其中一種:

  • Label.LOCAL 主持人本地的畫面,通常是手機的前、後相機畫面
  • Label.SOURCE_B 會議室中的第一位參與者
  • Label.SOURCE_C 會議室中的第二位參與者
  • Label.SOURCE_D 會議室中的第三位參與者
  • Label.WHITEBOARD 白板畫筆繪製的圖層

特殊圖層:白板圖層

白板圖層定義了畫筆功能作畫的位置。在使用上,必須遵守兩個規則:

  1. 標籤(Label)必須是 Label.WHITEBOARD
  2. 圖層本身必須是 RgbLayer

白板圖層的使用方式可以參考復面的範例


範例

以下方式會建立一個全螢幕模板:

class VideoLayoutFullScreen : VideoLayoutTemplate() {
    override val layers: LinkedHashMap<String, Layer> = linkedMapOf(
        Label.LOCAL to YuvLayer(FULL_SCREEN, CENTER_CROP)
    )
}

以下方式將畫面切分成左、右兩邊:

class VideoLayoutDivided : VideoLayoutTemplate() {
    override val layers: LinkedHashMap<String, Layer> = linkedMapOf(
        (Label.LOCAL to YuvLayer(HALF_LEFT, CENTER_CROP)),
        (Label.SOURCE_B to YuvLayer(HALF_RIGHT, CENTER_CROP))
    )
}

以下方式建立一個「畫中畫」模板, 自己的畫面在左上角的一個小區塊,通訊對方的畫面佔據整個背景,並且加入白板圖層。

Warning

注意此模板的圖層順序。我們希望 LOCAL 小畫面可以疊在 SOURCE_B 上方,因此 LOCAL 圖層在比較後面的位置。然後因為白板圖層的筆畫會在所有畫面的最上方,因此 WHITEBOARD 圖層在最後面的位置。

class VideoLayoutViewInView : VideoLayoutTemplate() {
    override val layers: LinkedHashMap<String, Layer> = linkedMapOf(
        (Label.SOURCE_B to YuvLayer(Rect.FULL_SCREEN, Layer.ScaleType.FIT_CENTER)),
        (Label.LOCAL to YuvLayer(
            Rect(left = -1.0f, top = 1.0f, right = -0.3f, bottom = 0.3f),
            Layer.ScaleType.CENTER_CROP
        )),
        (Label.WHITEBOARD to RgbLayer(Rect.FULL_SCREEN, Layer.ScaleType.FIT_CENTER))
    )
}

切換螢幕佈局

在完成自訂模板後,主持人可直接使用 ArctosLinks.ConferenceManager.hostSwitchScreenTemplate() 方法,傳入一個定義好的模板(VideoLayoutTemplate)之實例,即可改變會議室中所有人看到的螢幕佈局。

import com.arctos.sdk.links.core.application.ArctosLinks

private fun hostSwitchScreenTemplate(layout: VideoLayoutTemplate) {
    runCatching {
        ArctosLinks.getInstance(Context)
            .conferenceManager
            .hostSwitchScreenTemplate(layout)
            .getOrThrow()
    }.onSuccess {
        Log.d(TAG, "Successfully switched screen template")
    }.onFailure {
        Log.d(TAG, "Failed to switch screen template: ${it.message}")
    }
}