本文面向整合方 Android 開發者,涵蓋安裝、鑑權、完整整合流程、事件處理與最佳實務,說明如何穩定整合 HappyOyster Android SDK。
HappyOyster 是一個 AI 世界探索產品。透過整合 HappyOyster Android SDK,你的 App 可以進入由 AI 即時生成的「世界」,以世界探索(adventure)、即時導演(directing)或角色演繹(acting)三種模式進行即時互動影片體驗。
本文面向整合方 Android 開發者,涵蓋安裝、鑑權、完整整合流程、事件處理與最佳實務,說明如何穩定整合。具體 API(簽章、參數、回傳、錯誤碼、資料模型)請參考 HappyOyster Android SDK API Reference。
能做什麼
- 開始一次體驗(Travel):用一次性憑證進入一個已就緒的世界,SDK 自動建立即時影片連線。
- 即時播放:SDK 回傳一個影片 View,你掛載到版面配置即可播放 AI 即時生成的畫面。
- 即時互動:
- 即時導演模式(directing):傳送文字指令驅動劇情。
- 角色演繹(acting):同樣傳送文字指令;暫停 / 恢復可用;不要回溯,不要
sendCommand。用進房回包的aspectRatio定播放器方向(詳見模式適配)。 - 世界探索模式(adventure):傳送方向 / 視角 / 動作控制指令與世界互動。
- 過程控制:暫停 / 恢復(即時導演與角色演繹)、回溯(僅即時導演)、結束(三種模式均可)。
- 狀態與錯誤回調:透過事件監聽即時感知體驗狀態與異常。
說明SDK 不負責世界的建立與管理,也不直接暴露底層即時通訊細節——這些由你的伺服器端或 SDK 內部處理。你只需聚焦「開始體驗 → 播放 → 互動 → 結束」。
安裝
HappyOyster SDK(cn.happyoyster:opensdk)發佈在 Maven Central;其底層即時通訊引擎(阿里雲 ARTC)發佈在阿里雲 Maven。兩個儲存庫都需要宣告。
環境要求
項目 | 要求 |
|---|---|
minSdk | 24(Android 7.0)及以上 |
compileSdk | 36 |
JDK | 11 位元組碼目標(宿主工具鏈建議 JDK 11 及以上) |
語言 | Kotlin(協程 |
ABI |
|
網路 | 需要可存取公網 |
在工程根 settings.gradle.kts 中加入儲存庫:
dependencyResolutionManagement {
repositories {
google()
mavenCentral() // Happy Oyster SDK(cn.happyoyster:opensdk)
maven("https://maven.aliyun.com/repository/public") // 实时通信引擎(阿里云 ARTC)
}
}
在 Maven Central 檢視最新正式發佈的 Release,將下方 <version> 替換為該具體版本號,在模組 build.gradle.kts 中新增相依性:
dependencies {
implementation("cn.happyoyster:opensdk:<version>")
}
使用具體版本號鎖定依賴;升級前請閱讀發佈說明,升級後重新編譯宿主程式碼。
說明SDK 以瘦 AAR 形式發佈,不內嵌任何第三方相依性;即時通訊引擎等傳遞相依性在解析時自動從上述儲存庫拉取,因此阿里雲 Maven 儲存庫必不可少,缺失會導致 com.aliyun.aio:AliVCSDK_ARTC 解析失敗。
權限
SDK 函式庫自身僅宣告 INTERNET。即時通訊引擎會自動合併少量網路 / 藍牙 / 音訊設定類權限,函式庫 manifest 不包含錄音與攝影機權限。影片畫面為純訂閱播放,SDK 不向遠端傳送真實音訊。
宿主必須自行宣告:當你的 targetSdk 為 33 及以上時,需在宿主 AndroidManifest.xml 宣告通知權限(即時通訊引擎包含前台服務):
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
世界探索模式(sendCommand)建議宣告錄音權限:世界探索模式的即時控制指令透過一路靜音音訊流建立「推流者身分」並承載上行通道,SDK 不會錄製或上傳你的真實音訊。RECORD_AUDIO 並非 DataChannel 的硬前置——即使未授予,靜音流仍以推流者身分存在、上行通道通常可用。但為兼顧各機型的穩定性,仍建議:如果你的 App 會用到 sendCommand,在宿主 AndroidManifest.xml 宣告該權限,並在呼叫前於執行期申請。
<uses-permission android:name="android.permission.RECORD_AUDIO" />
關於錯誤碼 105004:105004 表示即時通道未就緒 / 傳送失敗(如 DataChannel 連線中斷或即時通道異常),並非缺少權限的必然結果——它不由未授予 RECORD_AUDIO 直接觸發。即時導演模式(sendInstruct)與影片播放不受影響。若你的 App 不使用世界探索模式,則無需該權限。
如需裁剪合併進來的權限,可用 tools:node="remove"。
鑑權模型
SDK 不獲取、不刷新 token,保持輕量。鑑權分兩層:
- 百煉閘道 API Key:由你的 App 透過
updateToken(token)注入為 Bearer token。SDK 只儲存最新一個,不持久化、不刷新;Key 變化後由你再次注入。閘道要求包括startTravel在內的 SDK 請求均使用此 Bearer。Bearer Key 過期或無效時,SDK 拋出SDKError(101002);此時應重新獲取並注入 Bearer Key(updateToken),而非重新換取 ticket。驗證 / Demo 階段可直接用主百煉 API Key 作為 Bearer(updateToken)跑通流程。生產環境務必改用你的伺服器端簽發的短期 token,切勿把長期 API Key 打包進 App 發佈。 - 一次性體驗憑證
ticket:由你的伺服器端呼叫開放平台換取並下發給用戶端,僅用於一次startTravel;體驗結束(正常或異常)後即失效,不可複用。ticket 層級的憑證錯誤由六位伺服器端 code 識別(如401010= ticket 無效 / 過期,401011= ticket 已使用),SDK 原樣透傳。
你的伺服器端負責建立 / 管理 World、換取 Travel ticket 並下發給用戶端;Android SDK 只消費 token 與 ticket,不提供 World 管理介面。初始化時必須透過 SDKConfig.apiHost 傳入你帳號的百煉 API Host(形如 llm-xxxx.ap-southeast-1.maas.aliyuncs.com,在百炼控制台 API Key 頁的「API Host」處複製),並透過無預設值的必填參數 SDKConfig.model 傳入拆分後的模型名(happyoyster-1.0-directing / happyoyster-1.0-acting / happyoyster-1.0-adventure,與所用 Open API 入口一致)。SDK 將請求位址補全為 https://{apiHost}/api/v2/apps/{model}/openapi/v1/{endpoint},你無需自行拼接。
建構 SDKConfig 時遺漏 model 會編譯失敗;傳入空白 model 時,initialize 同步拋出 SDKError(100002)(raw = "SDKConfig.model must not be blank"),不會建立或替換 runtime,也不會發起網路請求。API Host、model 與注入的 API Key 必須相符所需的帳號、區域和模型授權,否則閘道通常回傳 AccessDenied(執行期以 SDKError(106003) 拋出,AccessDenied 原文見 SDKError.raw;排查清單見 API Reference 錯誤碼表 106003 行)。出於安全,強烈建議用戶端注入伺服器端簽發的短期 token作為 Bearer,而非把長期 API Key 打包進 App 或寫入程式碼儲存庫。
說明
地域合規提示:- 為支援適用法律法規及資料合規要求的落實,如您的目標使用者包括美國使用者,您必須在 SDK 初始化時為面向該等使用者的服務配置美國地區 API Host。
- 開發者應確保設定正確,並依法承擔因未按上述要求設定所產生的相應責任。
快速開始
import cn.happyoyster.opensdk.* // 入口类均在此包下:HappyOyster、SDKConfig、TravelStatusValue、ModeValue、SDKError 等
// 跟踪当前体验状态与本次 Travel 元数据,互动前据此判断是否可发送。
@Volatile private var currentStatus: TravelStatusValue? = null
@Volatile private var currentTravel: StartTravelData? = null
// 1) 初始化(建议在 Application.onCreate)
// apiHost 必填:你账号的百炼 API Host(百炼控制台 API Key 页复制)
// model 必填且无默认值:包含版本的完整模型名称;可用值以百炼官方 HappyOyster 系列模型文档为准
HappyOyster.initialize(
applicationContext,
SDKConfig(
apiHost = "llm-xxxx.ap-southeast-1.maas.aliyuncs.com",
model = "happyoyster-1.0",
),
)
// 2) 注入百炼网关 API Key(作为 Bearer token)
HappyOyster.updateToken(bailianApiKey)
// 3) 监听 SDK 事件:用 onStatusChanged 驱动宿主状态机与互动能力门控
HappyOyster.addListener(object : HappyOysterListener {
override fun onStatusChanged(status: TravelStatusValue) {
currentStatus = status
when (status) {
// running 才允许互动;世界探索模式的 sendCommand 仅在 running 有效。
TravelStatusValue.Running -> markInteractionAllowed()
// paused 是 pauseTravel 真正生效的信号(异步,见下);此时仍可 sendInstruct。
TravelStatusValue.Paused -> markTravelPaused()
// 终态:SDK 已自行结束并释放资源,清理宿主侧本次体验状态。
TravelStatusValue.Completed, TravelStatusValue.Failed -> clearActiveTravel()
// init / pending:尚未就绪,互动不可用。
else -> markInteractionBlocked()
}
}
override fun onError(error: SDKError) {
// 统一错误回调,见 API Reference 错误码节
}
})
// 4) 开始体验(ticket 由你的服务端下发)
lifecycleScope.launch {
try {
// 同一 App 同一时刻只允许一个并发 Travel(alirtc 限制:即使换一个 ticket
// 也无法在同一 App 内并发开始第二个 Travel)。已有体验进行中时再次调用会抛出
// SDKError(103004),其 raw 仅携带当前正在播放 ticket 的脱敏摘要,不包含完整 ticket。
val travel: StartTravelData = HappyOyster.startTravel(ticket)
currentTravel = travel
// 先检视返回的 Travel 元数据再决定如何互动:
// travel.mode —— directing / adventure / acting
// travel.creationModel —— Simple / ScriptList
// travel.aspectRatio —— 仅 acting 有值,用于定播放器方向
val canSendInstruct = (travel.mode == ModeValue.Directing || travel.mode == ModeValue.Acting) &&
travel.creationModel != CreationModelValue.ScriptList
// 注意:ScriptList 模式下调用 sendInstruct 会抛 SDKError(103002),剧本内容不通过本 SDK 管理。
// 5) 挂载视频 View(SDK 返回 view,你加入布局)
// 角色演绎:先按 travel.aspectRatio 定好容器方向,再 attachVideo() 挂载(见「模式适配」)——
// 远端视图以裁剪填充绑定,容器方向与 aspectRatio 不符会裁掉画面。
val videoView = HappyOyster.attachVideo()
binding.videoContainer.addView(videoView)
// 6) 运行中互动——必须先等到 running 再发送:
// - sendCommand(世界探索):仅 running 有效;init/pending/paused 调用返回 103002。
// - sendInstruct(实时导演 / 角色演绎):running 或 paused 均可;init/pending 调用返回 103002。
if (canSendInstruct && currentStatus == TravelStatusValue.Running) {
HappyOyster.sendInstruct("突然下起了大雨")
}
} catch (e: SDKError) {
// 处理开始失败(例如 103004:已有体验在播放)
}
}
// 7) 暂停 / 恢复(实时导演与角色演绎;回溯仅实时导演)
// pauseTravel 是异步的:方法返回只代表「受理」,真正暂停以 onStatusChanged(paused) 为准。
// 务必等到 paused 回调后再允许调用 resumeTravel。
lifecycleScope.launch {
if ((currentTravel?.mode == ModeValue.Directing || currentTravel?.mode == ModeValue.Acting) &&
currentStatus == TravelStatusValue.Running
) {
HappyOyster.pauseTravel() // 受理;宿主记录 pausing,等待状态回调
// …收到 onStatusChanged(Paused) 后…
}
}
lifecycleScope.launch {
if (currentStatus == TravelStatusValue.Paused) {
HappyOyster.resumeTravel() // 仅在 paused 后调用
}
}
// 8) 结束体验(SDK 自动断开实时连接并释放资源)
lifecycleScope.launch { HappyOyster.endTravel() }
事件訂閱與錯誤處理
用 onStatusChanged 驅動宿主狀態機與互動能力門控;用 onError 統一接收執行期錯誤。事件介面與完整錯誤碼見 HappyOyster Android SDK API Reference。
- 在
HappyOyster.initialize(...)成功回傳後立即註冊監聽器;addListener/removeListener必須在initialize之後呼叫(在初始化前呼叫會拋出SDKError(100001))。 - 以
onStatusChanged(Running)為門控,running後才允許互動呼叫;sendCommand(世界探索模式)僅running有效。 - 同時處理
onError,不要只 catchstartTravel;致命錯誤同時終止體驗(見 API Reference 錯誤碼節)。
- 在合適的生命週期(如
onDestroy)呼叫removeListener,避免記憶體洩漏。
- 重複註冊監聽但不 removeListener。
指令傳送:sendInstruct 與 sendCommand
sendInstruct(即時導演 / 角色演繹)
sendInstruct 用於即時導演(directing)與角色演繹下傳送文字指令驅動畫面,在 running 或 paused 狀態均可呼叫。paused 態下 SDK 不會自動 resume——由宿主決定是否先呼叫 resumeTravel 再發 instruct(節流、狀態校驗等契約細節見 API Reference)。角色演繹世界的 creationModel 恆為 Simple,因此 ScriptList 限制只作用於即時導演。
sendCommand(世界探索模式)
sendCommand 用於世界探索模式(adventure)下傳送方向 / 視角 / 動作控制指令,僅 running 有效。SDK 內建 42 ms(24fps)latest-wins 節流,宿主可按遊戲幀率高頻呼叫,SDK 自動合併;無需宿主手動限速。單次動作呼叫一次即可;持續動作需按幀續調、鬆手時顯式發一次 None 復位(單次 / 持續最佳實務、節流 / 失敗透出等契約細節見 API Reference)。
建議做法:在宿主的世界探索模式互動介面提供三組獨立指令入口(移動方向 + 視角方向 + 動作互動),維護三組目前按下狀態,並在每次呼叫時傳送完整的三欄位快照。未按下的維度填 "None";如果不同維度同時按下,必須保留並傳送各維度目前值,不能因為更新一個維度就把其他維度重設為 "None"。
暫停 / 恢復 / 回溯
適用模式:暫停 / 恢復適用於即時導演與角色演繹,兩者行為完全一致(含下面的 3 s 屏障);回溯僅即時導演。世界探索模式下呼叫 pauseTravel / resumeTravel / rewindTravel 會被 SDK 以 103003 拒絕。此外伺服器端下發的 version 必須是本模式的 v2 識別碼(即時導演 storyV2、角色演繹 actingV2),否則暫停 / 恢復回傳 103002。
暫停是非同步的:pauseTravel 方法回傳僅代表受理,真正暫停以 onStatusChanged(Paused) 為準。在「呼叫 pauseTravel」到「收到 Paused 回調」之間,宿主可將本機體驗狀態標記為 pausing;只有收到 Paused 後,才應允許呼叫 resumeTravel 或依賴 paused 態的 rewindTravel。
SDK 內部 pause→reopen 屏障:收到 Paused 後即可按契約呼叫 resumeTravel / rewindTravel;若呼叫發生在暫停確認後的 3 s settle 視窗內,SDK 的 suspend 方法會先等待剩餘時間,再向伺服器端傳送會重新開啟即時房間的請求。宿主無需另加 pause→resume 延時;自然等待已滿 3 s 時 SDK 不增加延遲。
宿主端呼叫冷卻(建議):建議宿主在 resumeTravel 回傳成功後的 3 s 內暫緩再次發起 pauseTravel,避免過於頻繁的切換。SDK 本身不強制這個冷卻。
回溯:rewindTravel 僅在 paused 狀態可用;回溯後伺服器端自動 resume,SDK 自動重連 RTC,無需宿主干預。典型序列:pause → wait:paused → rewindTravel(sec)。回溯秒數 rewindToSec 應取 4 的整數倍(如 4、8、12),非整數倍由伺服器端向下取整(如 7→4),實際生效秒數以回傳的 resumedAtSec 為準。僅即時導演支援回溯:在角色演繹與世界探索模式下呼叫 rewindTravel,會被 SDK 在本機以 103003 拒絕,不會發出任何 HTTP 請求;宿主應隱藏回溯入口,而不是只把按鈕置灰。
非同步語意、3× 重試退避(resumeTravel 退避 1 s / 2 s / 3 s)等契約細節見 HappyOyster Android SDK API Reference。
日誌
SDK Logcat tag:HappyOysterSDK。SDK 提供兩路互不影響的日誌輸出:
- 內建 Logcat(預設關閉):僅當
SDKConfig.logcatEnabled = true時 SDK 才寫 Logcat(預設false,完全靜默)。SDKConfig.logLevel過濾其最低等級,預設INFO已含重建會話所需的生命週期錨點(初始化、Travel 起止 / 狀態遷移、RTC 入會 / 首幀);僅看報錯可降為WARN,深入排障可升到DEBUG/VERBOSE。logLevel不影響logHandler。 - 宿主回調
logHandler(建議):接收 SDK 每一條LogRecord(全量 firehose),與logLevel/logcatEnabled完全獨立,可轉發到宿主自有日誌系統(Logcat、檔案、崩潰平台等)。回調須快速、非阻塞且禁止在回調內回調 SDK;回調拋出的異常被靜默捕捉;LogRecord.message已脫敏,不含 Bearer token、ticket、RTC token 等明文。
HappyOyster.initialize(
context,
SDKConfig(
apiHost = "llm-xxxx.ap-southeast-1.maas.aliyuncs.com", // 必填:你账号的百炼 API Host
model = "happyoyster-1.0", // 必填且无默认值:完整模型名称及版本
logcatEnabled = true, // 开启内置 Logcat(默认 false)
logLevel = LogLevel.DEBUG, // 仅影响内置 Logcat
logHandler = { record -> // 可选:转发到宿主自有 Logcat tag
android.util.Log.d("MyApp/SDK", record.message, record.throwable)
},
),
)
adb logcat -s HappyOysterSDK # 仅内置 Logcat 开启时有输出
adb logcat -s HappyOysterSDK MyApp # 同时查看宿主 App 日志(MyApp 换成你的 tag)
會話與請求對帳:日誌中的 travelId(即 encryptedTravelId)以全值輸出,是發往伺服器端的會話鍵——排查某次會話或提工單時附上它,即可對齊用戶端與伺服器端日誌。每條 HTTP 回應日誌還帶伺服器端 requestId(reqId= 欄位,缺失時為 -)用於定位單次請求;該 ID 僅出現在日誌中,不進入任何公開返回值。
生命週期與記憶體
- 在
Application.onCreate呼叫initialize,全域一次。 - idle 狀態下再次呼叫
initialize(例如切換 API 區域或model)會關閉上一個 idle runtime 並重設已註冊監聽器、Feature Gate 狀態;回傳後請重新addListener。Travel 正在 starting、active 或 ending 時再次初始化會以103004拒絕;務必先等待endTravel()完成。initialize不會再丟棄進行中的 Travel。 - 體驗與宿主生命週期綁定:在
Activity/Fragment的onDestroy(或 ViewModelonCleared)中呼叫endTravel,確保即時連線與資源釋放。 attachVideo()回傳的 View 在結束時從版面配置移除(container.removeAllViews()),並removeListener。- SDK 只持有 application context,你也不要把 Activity 傳給 SDK。
協程與執行緒
- 業務方法是
suspend,可在任意 coroutine context 的lifecycleScope/viewModelScope中呼叫;SDK 會在內部主 dispatcher 協調狀態與 RTC 操作,HTTP 仍不阻塞主執行緒。 - 呼叫方取消 coroutine 時,SDK 會取消該呼叫仍在途的 HTTP 請求(如有),並原樣傳播
CancellationException,不會轉換為SDKError;不要把它當成業務錯誤捕捉或吞掉。取消不代表伺服器端已經受理的請求會被回滾。 attachVideo()、sendCommand()在主執行緒呼叫;離主執行緒呼叫會同步拋出IllegalStateException。
token 管理
- Bearer API Key 有效期有限,建議在進入體驗前確保 token 新鮮;收到
onError(101002)(token 過期)時重新獲取 Bearer Key 並updateToken,無需重新換取 ticket。
錯誤恢復
- 對致命錯誤:清理本次體驗狀態(包括移除視訊 View)、提示使用者、允許重新開始。
- 對網路抖動(
106001)和上游服務臨時異常(106003):可做有限次重試。 - 初始化同步拋出
100002時,為model空白;填寫完整模型名稱及版本後重新初始化。非空model的名稱 / 版本錯誤、模型已下線、尚未發佈或未授權時,閘道通常回傳 AccessDenied 並映射為106003;結合SDKError.raw檢查model、API Host、API Key 的帳號與區域是否相符。
(致命 / 非致命分類見 HappyOyster Android SDK API Reference 錯誤碼節。)
模式適配
- 用
startTravel回傳的mode決定可開放的互動能力:即時導演與角色演繹使用文字指令sendInstruct(角色演繹額外用aspectRatio定播放器方向,隱藏回溯),世界探索模式使用控制指令sendCommand。 - 世界探索模式(adventure)可選用多載
startTravel(ticket, maxExperienceTimeSec)限制本次體驗最大時長(秒,到時自動結束);合法檔位由伺服器端設定(目前60/90/120,預設60),即時導演與角色演繹 忽略該值(伺服器端忽略並回顯null)。傳入不支援的值時伺服器端回傳400000且本次啟動失敗。參數細節見 API Reference。
角色演繹:按 aspectRatio 定播放器方向
StartTravelData.aspectRatio 僅角色演繹(acting)非空:"9:16"(直向螢幕,伺服器端建立世界時的預設值)或 "16:9"(橫向螢幕);世界探索與即時導演均為 null。畫幅在建立世界時(由你的伺服器端透過 Open API 指定)就已確定,對用戶端是唯讀結果。SDK 原樣保留未識別的取值,宿主須把不認識的值等同 null 處理,回退到自己的預設方向。
時機:在 startTravel() 回傳之後、呼叫 attachVideo() 把回傳的 SurfaceView 掛進版面配置之前確定播放容器方向。SDK 只有在宿主掛載檢視後才開始綁定並轉譯遠端串流,所以此刻定方向仍然趕在首幀之前。該值隨 startTravel() 的回傳值交付,不保證在 SDK 加入即時通訊頻道之前完成——只需保證在 attachVideo() 掛載前定好方向即可。
後果:遠端檢視以裁剪填充(clip-to-fill)方式綁定——容器方向與 aspectRatio 不一致會裁掉畫面(例如把 9:16 直向螢幕流放進 16:9 容器,上下會被切掉大半),而不是留黑邊。
// startTravel() 返回之后、attachVideo() 之前:先按 aspectRatio 定好容器方向
val ratio: Float = when (travel.aspectRatio) { // 宽 / 高
"9:16" -> 9f / 16f // 竖屏(角色演绎的服务端默认值)
"16:9" -> 16f / 9f // 横屏
else -> HOST_DEFAULT_RATIO // null 或未识别取值:回退宿主自己的默认方向
}
// 用 ratio 定容器方向,例如:
// - Compose:Modifier.fillMaxWidth().aspectRatio(ratio)
// - View 体系:容器放在 ConstraintLayout 里,宽度撑满、高度由宽高比决定(XML 中 height=0dp)
binding.videoContainer.updateLayoutParams<ConstraintLayout.LayoutParams> {
dimensionRatio = ratio.toString()
}
// 容器方向确定后,再挂载 SDK 返回的视频 View
val videoView = HappyOyster.attachVideo()
binding.videoContainer.addView(videoView)
說明帳號未開通角色演繹(acting)規格(或該規格總閘關閉)時,建立世界與進房都會被伺服器端拒絕:startTravel 以 403007 失敗(進房前拒絕,不建立 Travel),SDK 原樣透傳。該碼不可按容量不足重試,應提示聯絡開通。
完整範例(ViewModel + Activity 片段)
class TravelViewModel : ViewModel() {
private val listener = object : HappyOysterListener {
override fun onStatusChanged(status: TravelStatusValue) {
_status.value = status
}
override fun onError(error: SDKError) {
_error.value = error
}
}
init { HappyOyster.addListener(listener) }
fun start(ticket: String) = viewModelScope.launch {
try {
val travel = HappyOyster.startTravel(ticket)
_travel.value = travel
} catch (e: SDKError) {
_error.value = e
}
}
fun send(text: String) = viewModelScope.launch {
runCatching { HappyOyster.sendInstruct(text) }
}
fun stop() = viewModelScope.launch { runCatching { HappyOyster.endTravel() } }
override fun onCleared() {
HappyOyster.removeListener(listener)
viewModelScope.launch { runCatching { HappyOyster.endTravel() } }
}
}
// Activity 中挂载视频
val videoView = HappyOyster.attachVideo()
binding.videoContainer.addView(videoView)
// 结束时
binding.videoContainer.removeAllViews()
DevOps 排障指南
當整合過程中出現問題(無法進入 Travel、黑屏無畫面、中途斷流、暫停 / 恢復異常等),開放 SDK 日誌是最快的定位手段。本章給出一套標準排障流程,便於你自查,也便於向我們回饋時一次性提供足夠資訊。
開啟日誌
排障時按需選擇日誌輸出(詳見日誌):快速自查用 logcatEnabled = true 配合 adb logcat,排障建議把 logLevel 升到 DEBUG 或 VERBOSE(預設 INFO 已含完整生命週期時間軸,僅關心報錯可降到 WARN);整合自有系統則用 logHandler 接收全量 LogRecord(不受 logLevel 影響),寫入你的檔案或崩潰平台。
會話相關 ID:travelId
SDK 日誌中的 travelId(即 StartTravelData.encryptedTravelId)以全值輸出,是貫穿一次會話的唯一識別碼,也是發往伺服器端的會話鍵。它是把用戶端日誌與伺服器端會話記錄對齊的關鍵——回饋問題時請務必帶上出問題會話的 travelId。
採集日誌
重現問題時,將 SDK 日誌落盤:
# 清空历史,复现问题,然后抓取 SDK 日志到文件
adb logcat -c
adb logcat -s HappyOysterSDK > happyoyster-sdk.log
# 需要同时看你自己 App 的日志时(把 MyApp 换成你的 tag):
adb logcat -s HappyOysterSDK MyApp > happyoyster-sdk.log
回饋問題時請提供
為避免多輪往返,提交問題時請一併附上:
項目 | 說明 |
|---|---|
SDK 版本 |
|
會話 | 出問題會話的 |
發生時間 | 出問題的大致時間點(精確到分鐘即可) |
錯誤碼 | 捕捉到的 |
重現步驟 | 操作路徑 + 預期結果 + 實際結果 |
執行環境 | 裝置型號、Android 版本、網路環境(WiFi/蜂窩) |
日誌檔案 | 「採集日誌」步驟採集的 |
關於脫敏與日誌可分享性
SDK 日誌在設計上即為可安全分享:憑證類資訊(Bearer token、Travel ticket、RTC token)、RTC 內部識別碼與媒體位址在寫入日誌前已脫敏,不會出現明文;travelId 屬會話識別碼(非憑證),保留全值僅用於對帳。即便如此,仍建議透過可信管道傳輸日誌檔案,不要公開貼上到不受控的平台。