Skip to content

廣場與直播

最後更新:2026-04-09


功能說明

廣場 Tab 是 App 的社群與直播中心,提供使用者觀看主播直播、聊天互動、送禮物、領紅包、參與競猜投票、查看活動等功能。支援全螢幕直播(娛樂主播)與賽事嵌入直播(體育直播)兩種模式。廣場本身是 H5 網頁,所有主播列表、活動、資訊流均由後台維護,點擊互動後跳轉至原生頁面。

使用者流程

廣場主頁

  1. 點擊「廣場」Tab → 載入 H5 URL 到 STBridgeH5WebView
  2. H5 頁面顯示主播列表、活動、資訊流
  3. 點擊主播卡片 → JS Bridge 通知原生端跳轉至 StreamerViewController

進入主播直播間

  1. 點擊主播卡片(需登入)→ StreamerViewController
  2. 直播房間載入主播影片流
  3. 聊天區顯示即時訊息
  4. 可操作:送禮物、搶紅包、參與競猜、轉動轉盤
  5. 點擊小化按鈕 → 浮動小窗
  6. 投屏(AirPlay):AirPlayAlertView

送禮流程

  1. 點擊禮物按鈕 → 開啟 STGiftListViewController
  2. 載入禮物列表(STAPI+LiveGiftListRequest)與錢包餘額
  3. 選擇禮物 → 選擇數量 → 點擊送出
  4. 呼叫 STAPI+SendGiftRequest 送出禮物
  5. 成功:播放禮物動畫(全螢幕禮物播 GiftFullScreenView
  6. 金幣不足(code 300000):回傳不足額,提示充值
  7. 充值跳轉:全螢幕模式先收回 Sheet → 跳轉 RechargeViewController

頁面跳轉

  • 點擊「廣場」Tab → H5 廣場主頁(BBMineNewsViewController
  • 廣場 H5 點擊主播 → StreamerViewController(需登入)
  • 廣場 H5 點擊充值 → RechargeViewController
  • 廣場 H5 點擊客服 → 客服選擇器
  • H5 → H5 內部跳轉 → BBMineNewsViewController(push 新 WebView)
  • 主播直播間小化 → 浮動小窗
  • 全螢幕模式 → STFullAmuseHostViewController

技術視角(開發看這裡)

相關檔案

類型檔案路徑
ViewController(廣場主頁 H5 容器)/Users/user/Work/bbsport-new/BBSport/Tab/广场/Activity/C/BBMineNewsViewController.swift
WebView(H5 Bridge 核心)/Users/user/Work/bbsport-new/BBSport/Tools/STWebViewManager/STBridgeH5WebView.swift
Bridge API(JS→原生跳轉)/Users/user/Work/bbsport-new/BBSport/Tools/STWebViewManager/STWebviewBridgeApi.swift
ViewController(主播直播)/Users/user/Work/bbsport-new/BBSport/Tab/广场/Stream/StreamerViewController.swift
ViewModel(主播直播)/Users/user/Work/bbsport-new/BBSport/Tab/广场/Stream/StreamerViewModel/StreamerViewModel.swift
ViewController(全螢幕)/Users/user/Work/bbsport-new/BBSport/Tab/广场/FullScreenLive/STFullAmuseHostViewController.swift
ViewModel(全螢幕)/Users/user/Work/bbsport-new/BBSport/Tab/广场/FullScreenLive/VM/STFullAmuseHostViewModel.swift
ViewController(活動)/Users/user/Work/bbsport-new/BBSport/Tab/广场/Activity/C/BBActivityViewController.swift
ViewModel(禮物列表)/Users/user/Work/bbsport-new/BBSport/Tab/广场/FullScreenLive/Gift/VM/STGiftListViewModel.swift
ViewController(禮物列表)/Users/user/Work/bbsport-new/BBSport/Tab/广场/FullScreenLive/Gift/STGiftListViewController.swift
View(禮物列表)/Users/user/Work/bbsport-new/BBSport/Tab/广场/FullScreenLive/Gift/STGiftListView.swift
ViewModel(直播競猜)/Users/user/Work/bbsport-new/BBSport/Tab/广场/FullScreenLive/LiveBetGame/LiveBetGameViewModel.swift
ViewModel(聊天)/Users/user/Work/bbsport-new/BBSport/Tab/广场/FullScreenLive/STChatView/VM/STChatViewModel.swift
Manager(聊天 Socket)/Users/user/Work/bbsport-new/BBSport/Tab/广场/FullScreenLive/STChatView/STChatManager.swift
API(聊天相關)/Users/user/Work/bbsport-new/BBSport/Tab/广场/FullScreenLive/STChatView/STChatAPI.swift
Model(直播資訊)/Users/user/Work/bbsport-new/BBSport/Tab/体育/Sport/EventDetail/Views/VideoAndStream/Models/LiveInfoMessageModel.swift
播放器管理/Users/user/Work/bbsport-new/BBSport/Tools/AVPlayerManager/STAVPlayerManager.swift
播放器 View/Users/user/Work/bbsport-new/BBSport/Tools/AVPlayerManager/STAVPlayerView.swift
播放器 Item/Users/user/Work/bbsport-new/BBSport/Tools/AVPlayerManager/STPlayerItem.swift
播放器影片層 View/Users/user/Work/bbsport-new/BBSport/Tools/AVPlayerManager/STVideoPlayerLayerView.swift
直播資料元件/Users/user/Work/bbsport-new/BBSport/STUIKit/STFoundation/Live/LiveComponent.swift
全螢幕背景 View/Users/user/Work/bbsport-new/BBSport/Tab/广场/FullScreenLive/V/STFullAmuseBGView.swift
全螢幕聊天 View/Users/user/Work/bbsport-new/BBSport/Tab/广场/FullScreenLive/V/STFullAmuseChatView.swift
全螢幕更多功能 View/Users/user/Work/bbsport-new/BBSport/Tab/广场/FullScreenLive/V/STFullAmuseMoreFeaturesView.swift
全螢幕主播資訊 View/Users/user/Work/bbsport-new/BBSport/Tab/广场/FullScreenLive/V/STFullAmuseHostInfoView.swift
全螢幕引導 View/Users/user/Work/bbsport-new/BBSport/Tab/广场/FullScreenLive/V/STFullAmuseGuideView.swift
全螢幕 AirPlay 狀態 View/Users/user/Work/bbsport-new/BBSport/Tab/广场/FullScreenLive/V/STFullAmuseAirPlayStatusView.swift
全螢幕入場動畫 View/Users/user/Work/bbsport-new/BBSport/Tab/广场/FullScreenLive/V/STFullAmuseEntryAnimationView.swift
全螢幕轉盤遊戲/Users/user/Work/bbsport-new/BBSport/Tab/广场/FullScreenLive/STFullAmuseHostViewController+TurnTableGame.swift
全螢幕更多直播/Users/user/Work/bbsport-new/BBSport/Tab/广场/FullScreenLive/STFullAmuseHostViewController+MoreLive.swift
全螢幕紅包小球/Users/user/Work/bbsport-new/BBSport/Tab/广场/FullScreenLive/STFullAmuseHostViewController+BallView.swift
紅包雨 ViewModel/Users/user/Work/bbsport-new/BBSport/Tools/RainRedPacket/RainRedPacketViewModel.swift
Model(活動)/Users/user/Work/bbsport-new/BBSport/Tab/广场/Activity/M/BBActivityModel.swift
Cell(活動)/Users/user/Work/bbsport-new/BBSport/Tab/广场/Activity/V/BBActivityCell.swift
View(導航選擇)/Users/user/Work/bbsport-new/BBSport/Tab/广场/Activity/V/BBNavSelectionView.swift
View(用戶導航選擇)/Users/user/Work/bbsport-new/BBSport/Tab/广场/Activity/V/BBUserNavSelectionView.swift
View(主播標題)/Users/user/Work/bbsport-new/BBSport/Tab/广场/Stream/Header/StreamerTitleView.swift
Model(快速遊戲)/Users/user/Work/bbsport-new/BBSport/Tab/广场/Stream/QuickGame/QuickGameModel.swift
View(快速遊戲)/Users/user/Work/bbsport-new/BBSport/Tab/广场/Stream/QuickGame/QuickGameView.swift
ViewModel(快速遊戲)/Users/user/Work/bbsport-new/BBSport/Tab/广场/Stream/QuickGame/QuickGameViewModel.swift
View(開始遊戲按鈕)/Users/user/Work/bbsport-new/BBSport/Tab/广场/Stream/QuickGame/StartGameButton.swift
Extension(直播紅包小球)/Users/user/Work/bbsport-new/BBSport/Tab/广场/Stream/StreamerViewController+BallView.swift
Extension(直播更多直播)/Users/user/Work/bbsport-new/BBSport/Tab/广场/Stream/StreamerViewController+MoreLive.swift
Extension(直播轉盤遊戲)/Users/user/Work/bbsport-new/BBSport/Tab/广场/Stream/StreamerViewController+TurnTableGame.swift
Model(主播類型)/Users/user/Work/bbsport-new/BBSport/Tab/广场/FullScreenLive/M/STHostType.swift
View(更多直播新版)/Users/user/Work/bbsport-new/BBSport/Tab/广场/FullScreenLive/V/NewFullAmuseMoreLiveView.swift
API(爆料論壇)/Users/user/Work/bbsport-new/BBSport/API/STAPI/STAPI+DisclosureForumRequest.swift

API

功能說明EndpointMethod主要參數
聊天舉報列表api/forehead/live/chat/report/catePOST無參數
大額曬單設定api/forehead/live/showorder/configPOST無參數
直播訂閱資訊api/forehead/live/user/subscribe/infoPOST無參數
紅包查詢(主播房間)api/forehead/live/get/room/redEnvelopePOSTLiveRoomId: Int — 直播間 ID
領取紅包api/forehead/live/submit/receive/redEnvelopePOSTLiveRoomId: Int — 直播間 ID, sendRecordId: String — 紅包發送記錄 ID
金幣商城餘額api/forehead/activity/goldmall/balancePOST無參數(需 Authorization)
禮物列表api/forehead/live/load/giftPOSTmatchType: String — 直播類型(體育/娛樂)
送禮物api/forehead/live/submit/gift/presentPOSTliveRoomId: String — 直播間 ID, giftId: String — 禮物 ID, giftCount: String — 數量。成功 code=1/429,金幣不足 code=300000(data 回傳不足額)
禮物排行榜 Top5api/forehead/live/room/gift/top5POSTliveRoomId: Int — 直播間 ID

資料模型

LivePrivilegeType(特權主播類型)

swift
enum LivePrivilegeType: Int, Codable {
    case None       = 0   // 無特權
    case WhiteList  = 1   // 白名單(一般特權)
    case Proxy      = 2   // 代理特權
}

特權主播完整判斷邏輯LiveComponent.filterLanguage):

  1. 排除已下播(liveStatus == .Finish)的主播
  2. 依語言分組:-1(無語言)、0(普通話)、1(粵語)
  3. 每個語言組內按 sort 升序排列
  4. 判斷特權:若 liveUserGroupId > 0liveGroupUserType > 0,代表該主播為特權/樂享主播
  5. 檢查使用者是否在特權名單:讀取 IFUserModel.sharedInstance().liveRoomIdSpecial(逗號分隔的房間 ID 字串),比對 liveRoomId
  6. 排序優先級:樂享主播(liveGroupUserType > 0)排最前 > 純特權主播(liveUserGroupId > 0 && liveGroupUserType == 0)> 一般主播
  7. 訪客或無特權權限的使用者只能看到一般主播

getLiveStatus 特權判斷LiveComponent.getLiveStatus):

  • 單主播時:直接取第一順位主播的 privilegeType(因篩選已將特權排前)
  • 多主播時:遍歷所有 liveUserGroupId > 0 || liveGroupUserType > 0 的主播,Proxy 優先於 WhiteList

MatchType(直播類型)

swift
enum MatchType: Int, Codable {
    case Sport        = 0   // 賽事
    case Entertainment = 1  // 娛樂
}

LiveStreamStatus(直播狀態)

swift
enum LiveStreamStatus: Int, Codable {
    case UnStream  = 0   // 未開始
    case Streaming = 1   // 直播中
    case Finish    = 2   // 已結束
}

LiveStreamShowType(直播展示類型)

swift
enum LiveStreamShowType: String, Codable {
    case Sport             = "体育赛事直播"
    case Vip               = "高V私密直播"
    case Tutorial          = "官方新手教学"
    case TransferTutorial  = "客服充提直播"
    case Welfare           = "彩蛋福利直播"
    case Excellent         = "精彩集锦直播"
    case Other             = "其他娱乐直播"
    case Proxy             = "专线独享直播"
}

LiveInfoMessageModel 完整欄位

屬性型別預設值說明
hostIdInt0主播 ID
hostNameString""主播名稱
hostPhotoIdString""主播頭像 ID
liveRoomIdInt0直播間 ID
liveStatusLiveStreamStatus.UnStream直播狀態(0: 未開始, 1: 直播中, 2: 已結束)
matchIdString""賽事 ID
videoSource[String: String][:]視訊源地址(key 如 "m3u8""rtmp"
hostLanguageInt-1主播語言(0: 普通話, 1: 粵語, -1: 未設定)
activityStatusInt0活動狀態(0: 正常, 1: 禁用, 2: 刪除)
benefitsScenesIdInt0直播間專屬客服場景代碼
sortInt-1排序(由小到大)
liveUserGroupIdInt0使用者是否在特權主播名單內
liveGroupUserTypeInt0直播間代理類型(0: 會員, 1: 特權主播/代理)
privilegeTypeLivePrivilegeType.None特權顯示類型
benefitsAssistantSwitchInt0是否顯示專屬客服(0: 否)
liveHostTypeShowLiveStreamShowType.Other顯示特權字段
liveHostTypeInt0特權類型(用於聊天室)
hostPictureIdString""主播場次圖
leagueString""聯賽名
startDateInt0開播時間
subscribeStatusInt0訂閱狀態(0: 未訂閱, 1: 已訂閱)
liveStreamIdString""直播流 ID
masterTeamNameString""主隊名
guestTeamNameString""客隊名
matchTypeString""判斷來源(簡體字串:"体育"、"娱乐")
watcherCountInt0觀看人數
hostTagsString""主播標籤
backgroundImageString""背景圖
extString""擴展 JSON 字串(含 marquee 跑馬燈、bennefitsAssistantWord 客服提示、isHorizontalScreen 橫豎屏)
popupMatchBoolfalse是否彈出賽事
languageStrString?計算屬性語言字串("普通话"/"粤语"/nil)
voidSourceUrlString計算屬性videoSource["m3u8"],優先 M3U8

ext JSON 解析方法

  • getExtDictionary() → 解析 ext 為 [String: Any]
  • getAnnounceWord() → 取 marquee 跑馬燈文字
  • getBennefitsAssistantWord() → 取專屬客服提示文字
  • isHorizontalScreen() → 是否橫屏(TransferTutorial 強制橫屏;其餘讀 ext 的 isHorizontalScreen,0=豎屏/1=橫屏,預設豎屏)

StreamerViewModel 完整屬性

屬性型別說明
goBackChangeToView(() -> Void)?返回事件回呼
privilegeTypeLivePrivilegeType特權主播類型,didSet 觸發 privilegeTypeDidChangeToView
subscribeHostSatusHandler(() -> Void)?主播狀態變更回呼
subscribeEntertainmentHandler(([LiveInfoMessageModel]) -> Void)?娛樂主播列表更新回呼
subscribLiveInfoHandeler(() -> Void)?訂閱主播資訊回呼
videoUrlStringString影片 URL,didSet 驅動播放器
fetchGiftRankListHandler((STGiftRankListModel) -> Void)?禮物排行榜回呼
didReceivedNewMessage(([STChatMessageModel]) -> Void)?收到新訊息回呼
didReceivedGiftList(() -> Void)?收到禮物列表回呼
didReceivedEmojiList(() -> Void)?收到表情列表回呼
didReceivedAutoMessage(([ChatMessageModel]) -> Void)?收到系統訊息回呼
didCustomService((String?) -> Void)?點擊客服回呼
showRedPacket((STRedPacketInfoModel) -> Void)?紅包小球顯示回呼
shouldDisplayRainRedPacket((STRedPacketInfoModel) -> Void)?紅包雨顯示回呼
shouldDisplayLuckyDraw((STLuckDrawModel?) -> Void)?抽獎顯示回呼
shouldDisplayVote((ChatVoteModel?) -> Void)?投票顯示回呼
liveBetGameViewModelLiveBetGameViewModel主播競猜 ViewModel(lazy)
hotInPlayRankViewModelSTHotInPlayRankViewModel熱門投注排行 ViewModel
chatViewModelSTChatViewModel聊天 ViewModel(lazy)
clearScreenClosure(() -> Void)?清屏回呼
matchIdInt賽事 ID
hostIdInt主播 ID
isH5OpenLiveBool是否從 H5 開啟
toastOpenLiveBool開播推送 Toast
sendRecordIdInt紅包發送記錄 ID
liveInfoModels[LiveInfoMessageModel]?當前娛樂主播 model 列表
subscribeInfoDataSubscribeInfoData?訂閱資訊(已訂閱的主播/賽事 ID 集合)
chatEventsSTBindingVariable<[STChatEventModel]>活動列表綁定變數
chatEventBallHideStatus(Bool, Bool)活動小球隱藏狀態
streamerModels[LiveInfoMessageModel]其他主播列表(排除當前主播)
currentStreamerLiveInfoMessageModel?當前主播,didSet 更新聊天室與競猜
openChatRoomAccesstimeDate?開啟聊天室時間
chatManagerSTChatManager聊天 Socket 管理器(private(set))
rainRedPacketViewModelRainRedPacketViewModel紅包雨 ViewModel(lazy)
chIdString頻道 ID(計算屬性)
chatGiftList[STGiftModel]禮物列表,didSet 觸發 didReceivedGiftList
chatEmojiList[[STEmojiModel]]表情列表,didSet 觸發 didReceivedEmojiList

JS Bridge 支援的 NativeType

type跳轉目標
funAnchor主播直播間(需登入)
sportDetail賽事詳情
rechargeIndex充值頁
welfareCenter福利中心
openCustomerService客服

播放器技術細節

播放器架構

App 使用 Apple AVPlayerAVFoundation)作為唯一的播放器框架,無 IJKPlayer 或其他第三方播放器。

元件說明
STAVPlayerManager全域單例播放器管理器,管理 AVPlayer 實例、AirPlay、重試、前後景切換
STAVPlayerView播放器 UI View,承載影片圖層和動畫圖層
STPlayerItem繼承 AVPlayerItem,監聽 status 變化通知 delegate
STVideoPlayerLayerView影片圖層 View

VideoSourceType(播放源類型)

swift
enum VideoSourceType {
    case live           // 賽事主播
    case video          // 賽事視頻
    case animation      // 動畫
    case entertainment  // 全螢幕娛樂主播
    case shortVideo     // 短視頻
    case hostLive       // 主播視頻
    case noneSource     // 不明
}

串流協議

  • HLS(M3U8):主要使用。LiveInfoMessageModel.getVoidSourceUrl() 優先取 videoSource["m3u8"]
  • RTMP:程式碼中已被註解掉(// if let rtmp = self.videoSource["rtmp"]),目前不使用
  • 視頻源 URL 由 videoSource 字典提供,key 對應協議名

播放器生命週期

  1. 初始化setupPlayer() 設定 URL、來源類型、佈局方向等參數
  2. 影片播放:建立 AVURLAssetSTPlayerItemAVPlayer → 添加至 STAVPlayerView
  3. 重試機制:5 秒為週期,最多重試 3 次。若仍失敗顯示播放錯誤
  4. 前後景處理:進背景暫停(AirPlay 除外);回前景自動播放
  5. 循環播放:短視頻/主播視頻 isLoopPlay = true 時,播完回到開頭重播
  6. AirPlay 投屏:監聽 AVAudioSession.routeChangeNotification,偵測 .airPlay 設備
  7. 浮動小窗isFloating = true 時,播放器 View 移至 UIApplication.currentWindow 上方
  8. 關閉:取消 pending seeks、停止播放、清除 UI 和狀態

視頻 Referer 處理

賽事視頻(sourceType == .video)會在 AVURLAsset 的 HTTP Header 加入 Referer 欄位。

直播源取得策略(getUrl

  • 賽事主播:從 SportDataManager 取得正在直播的主播列表,優先選樂享(liveGroupUserType > 0)→ 特權(liveUserGroupId > 0)→ 一般
  • 賽事視頻:從 LiveComponent.getVideoSource() 取得,支援多線路切換
  • 動畫:從 LiveComponent.getAnimationUrlString()SportDataManager 的 eventInfo

LiveComponent 輪詢服務

LiveComponent.registerLiveServices() 啟動兩個輪詢:

  • streamer:定期呼叫 EventDetailChatService.fetchStreamerList() 更新主播列表
  • sportEventLiveInfo:定期呼叫 STAPI.VideoUrlRequest() 更新視頻/動畫源

視頻源支援最多 4 組(videoSource1~videoSource4),動畫源支援 2 組(animateSource1~animateSource2),每組依 fbIdobIdupId 分平台儲存。

送禮流程技術細節

STGiftListViewModel

屬性/方法說明
models: [STGiftModel]禮物列表
currentModel: STGiftModel?當前選中的禮物
isRecharge / isGoRecharge充值狀態標記
paycheckBlock餘額不足回呼(需要金額)
paycheckCoinBlock金幣不足回呼(需要金額)
didSendGiftBlock送禮成功回呼(含是否全螢幕)
sendGiftHandler送禮動作回呼
updateGoldBalance金幣餘額更新回呼
updateBalance錢包餘額更新回呼
refreshGoldBeanBalance()呼叫 GoldMallBalanceRequest 刷新金幣
refreshBeanBalance()透過 MGJRouter 取得所有錢包餘額
rechargeBalance(direction:)跳轉充值頁(全螢幕先收 Sheet 再跳轉)

送禮調用鏈

使用者選禮 → STGiftListView.sendGift
  → STChatInputView → STChatViewModel.sendGift
    → StreamerViewModel.sendGift(STGiftModel)
      → STChatManager.sendGift(STGiftModel)
        → STAPI.SendGiftRequest(liveRoomId, giftId, giftCount)

全螢幕模式相關

STFullAmuseHostViewController

滿屏娛樂主播控制器,包含:

  • bigViewModel: STFullAmuseHostViewModel — 全螢幕 ViewModel
  • bgVideoView: STFullAmuseBGView — 背景播放器 View(點擊觸發清屏)
  • guideView: STFullAmuseGuideView — 新手引導
  • giftFullScreenView: GiftFullScreenView? — 全螢幕禮物動效
  • customServiceView: EventDetailCustomServiceView — 專屬客服
  • chatEventEntryView / chatEventSmallButton — 活動入口與小球

Extension 拆分

  • +TurnTableGame:轉盤遊戲互動
  • +MoreLive:更多直播間切換
  • +BallView:紅包小球 UI

STFullAmuseHostViewModel 關鍵屬性

屬性說明
avatarUrlString主播頭像 URL
hostIsStreaming主播是否直播中
privilegeType特權主播類型
titleString / subTitleString主播資訊卡標題/副標題
marqueeList跑馬燈文字列表
videoUrlString影片播放 URL
giftLiveAnimationHandler禮物動畫訊息回呼
shouldDisplayRainRedPacket紅包雨顯示回呼
didShowActivityMarquee活動中獎跑馬燈

實作重點

  1. 廣場本質是 H5:廣場 Tab 載入 IFMainSwitch.getSportInformationUrl() 的 H5 頁面,非原生列表

  2. JS Bridge 兩條路徑:H5→H5(jumpToH5Page)push 新 BBMineNewsViewController;H5→原生(jumpToNativePage)依 NativeType 分流

  3. 主播類型分支:娛樂主播(StreamerViewController)全螢幕 + 聊天 + 禮物;體育直播嵌入 STEventDetailViewController

  4. 廣場 Tab icon 動畫:選中時播放 Transition 動畫;Loop 圖示依「是否直播中」選擇 _live vs _no_live

  5. 紅包雨機制:Socket 通知 → 生成 STBallItemViewProtocol 小球 UI → 點擊搶紅包

  6. 播放器為 AVPlayer 單例STAVPlayerManager.shared 全域管理,支援浮動小窗、AirPlay、5 秒重試(最多 3 次)

  7. 影片源優先 M3U8getVoidSourceUrl() 只取 videoSource["m3u8"],RTMP 已停用

  8. 特權主播排序:樂享 > 純特權 > 一般。使用者需在 liveRoomIdSpecial 名單內才可見特權主播

  9. 送禮金幣不足處理SendGiftRequest 回傳 code 300000 時,data 為不足金額,觸發充值提示

  10. 橫豎屏判斷TransferTutorial(客服充提直播)強制橫屏;其餘由 ext.isHorizontalScreen 決定


API 呼叫流程

進入廣場 Tab

StreamerViewController.viewDidLoad
  ├─ [API 1] GET ssapi/anchor/search {pid:"bb", matchSource:"all"}
  │  → 回傳主播列表 [LiveInfoMessageModel]

  └─ [並行] POST api/forehead/live/user/subscribe/info
     → 回傳已關注主播清單

點擊主播進入全屏直播

STFullAmuseHostViewController.init(hostId, matchId)

  ├─ [API 1] GET api/forehead/user/chat/token/v2 {liveHostType}
  │  → 回傳 chatToken, chatBanInfo, chatRule
  │  (失敗重試 3 次)

  ├─ WebSocket 連線
  │  └─ BBSocketServices.openSocket(url: getChatSocketUrl2())
  │     └─ 連線成功 → 訂閱歷史訊息 (cmd: L01)
  │        └─ 送出進場動畫 (cmd: P02)
  │        └─ 啟動心跳 (每 30 秒 ping)

  └─ [並行載入] fetchChatData()
     ├─ POST api/forehead/common/pic/load {categoryId:"20104"} → 公告跑馬燈
     ├─ POST api/forehead/live/match/message/select → 自動訊息
     ├─ POST api/forehead/live/load/gift {matchType} → 禮物列表
     ├─ POST api/forehead/live/emoji/list {matchType} → 表情包
     ├─ POST api/forehead/live/get/room/redEnvelope {liveRoomId} → 紅包狀態
     ├─ POST api/forehead/live/get/room/redEnvelope/template → 紅包雨
     ├─ POST api/forehead/live/activity/lottery/start/info → 抽獎資訊
     ├─ POST api/forehead/live/activity/select {liveRoomId} → 活動列表
     └─ POST api/forehead/common/pic/load {categoryId:"40101,40102"} → 聊天設定

觀看直播中 (持續)

├─ WebSocket 接收即時訊息 (M01/G01/G02/G06/G07/P001/P002/P003)
├─ 心跳: 每 30 秒 ping
└─ [API] POST api/forehead/live/submit/watch {liveRoomId, matchId} → 回報觀看

關注 / 取消關注

關注: [API] POST api/forehead/live/subscribe {hostId}
取消: [API] POST api/forehead/live/unsubscribe {hostId}
  └─ 完成後 → POST api/forehead/live/user/subscribe/info 刷新狀態

送禮

用戶選禮物 → 確認
  └─ [API] POST api/forehead/live/submit/gift/present
     {liveRoomId, giftId, giftCount}
     ├─ code 1/429 → 成功
     └─ code 300000 → 餘額不足 (data = 差額)