Tất cả API dưới đây thuộc package gốc vn.interactive.
activity: Activity, // phải là FragmentActivity/AppCompatActivity
overlayView: OverlayView, // view trong layout, phủ vùng video
overlayData: OverlayData, // cấu hình môi trường + tenant
overlayEventListener: OverlayEventListener? = null
) : OnOverlayListener, LifecycleOverlay
class OverlayManager implements OnOverlayListener , LifecycleOverlay {
Activity activity , // phải là FragmentActivity/AppCompatActivity
OverlayView overlayView , // view trong layout, phủ vùng video
OverlayData overlayData , // cấu hình môi trường + tenant
OverlayEventListener overlayEventListener // có thể null
Thành phần Kiểu Mô tả valueTimePlayInt (chỉ đọc)Giây phát gần nhất mà app đã bơm vào qua receiveTrackingTime receiveTrackingTime(Int) -> UnitBơm thời gian phát (giây) cho luồng VOD. Gọi như hàm: overlayManager.receiveTrackingTime(12)
Hàm Mô tả Ghi chú initToken(token: String, userId: String, bjName: String = "")Khai báo/xoá phiên đăng nhập Gọi trước joinRoom. Đăng xuất: initToken("", ""). Nếu đang Live sẽ tự vào lại phòng joinRoom(mediaId: String, type: Int)Bắt đầu phiên tương tác cho một nội dung type = 1 LIVE, type = 2 VOD. Tự cleanUp() phiên cũreleaseAll()Giải phóng toàn bộ: socket, WebView, dialog, tracking Gọi ở onDestroy() hoặc khi thoát nội dung fetchInteractiveStatus(mediaId: String, callback: (Boolean) -> Unit)Hỏi server nội dung này có tương tác không Lỗi mạng → trả false showChartsInteractive(mediaId: String, userId: String, backgroundColorHeader: String? = null)Mở bảng xếp hạng (DialogFragment toàn màn hình) backgroundColorHeader là mã hex, ví dụ "#139DED"hideChartsInteractive()Đóng bảng xếp hạng isDialogCharShowing(): BooleanBảng xếp hạng có đang hiển thị không addPlayerListener(listener: PlayerChangeListener)Cấp trạng thái player cho SDK (phục vụ sự kiện ckst) Gọi sau joinRoom để listener được gắn cả vào socket sendAction(data: JSONObject)(implement OnOverlayListener) Xử lý hành động từ WebViewSDK tự gọi, app không cần gọi onClickAdsOverlay(url: String)(implement OnOverlayListener) Mở URL quảng cáo bằng trình duyệt ngoàiSDK tự gọi
LifecycleOverlay hiện là interface rỗng (các hàm onResume/onStop/onDestroy đã được comment
trong mã) — SDK không tự bám vòng đời Activity ngoài việc observe LiveData.
val overlayData = OverlayData. Builder ()
. setEnvironment (EnvInteractive.PROD) // mặc định PROD
. setTenantId ( "vtvlive" ) // mặc định "" — nên luôn set
. setDebug ( false ) // mặc định false
OverlayData overlayData = new OverlayData. Builder ()
. setEnvironment ( EnvInteractive . PROD ) // mặc định PROD
. setTenantId ( " vtvlive " ) // mặc định "" — nên luôn set
. setDebug ( false ) // mặc định false
Thuộc tính Kiểu Mặc định Ảnh hưởng envInteractiveEnvInteractivePRODURL socket, URL REST, URL bảng xếp hạng tenantIdString""Header X-TENANT-ID (socket + REST), query BXH, luật đăng nhập của tenant vtvlive debugBooleanfalseCờ dự phòng, hiện chưa đổi hành vi log (log phụ thuộc BuildConfig.DEBUG của SDK) mediaId, userIdString?nullĐược SDK gán khi mở bảng xếp hạng
enum class EnvInteractive { DEV, STG, PROD }
enum EnvInteractive { DEV , STG , PROD }
interface OverlayEventListener {
fun onHandleMessage (msg: EventOverlay)
fun onHandleLiveViews (liveViews: Int)
fun onShowITR (show: Boolean)
takeControl: Boolean = false ,
webViewError: Boolean = false ,
overlayConfig: OverlayConfig? = null
interface DefaultOverlayEventListener : OverlayEventListener // alias, không thêm hàm
interface OverlayEventListener {
void onHandleMessage ( EventOverlay msg ) ;
void onHandleLiveViews ( int liveViews ) ;
void onShowITR ( boolean show ) ;
void onFocusOverlay ( boolean takeControl , boolean webViewError , OverlayConfig overlayConfig ) ;
// mặc định: takeControl = false, webViewError = false, overlayConfig = null
interface DefaultOverlayEventListener extends OverlayEventListener { // alias, không thêm hàm
Callback Khi nào được gọi Việc app nên làm onHandleMessage(NEED_LOGIN)Người dùng thao tác với overlay yêu cầu đăng nhập Mở màn hình đăng nhập, sau đó gọi initToken() onHandleMessage(NEED_PAY_ONG)Server trả PM402 (không đủ OnG) Mở màn hình nạp onHandleLiveViews(n)Socket liveviews Hiển thị số người xem (đã cộng 230 ) onShowITR(true)Overlay mở phần thống kê/dialog Ẩn bớt UI của app nếu cần onFocusOverlay(takeControl = true, …)WebView nạp xong (onPageFinished) Điều chỉnh layout nếu overlay là popup onFocusOverlay(webViewError = true, …)Lỗi tải trang / HTTP / SSL Ghi log, có thể báo người dùng onFocusOverlay(takeControl = false, …)Overlay bị gỡ (INTERACTIVE_HIDE_ANALYTICS) Khôi phục layout
enum class EventOverlay {
NEED_LOGIN, // SDK có phát
NEED_PAY_ONG, // SDK có phát
TOURNAMENT_BET, // đã khai báo, chưa dùng
SUCCESSFUL_REPLY_SUBMITTED, // đã khai báo, chưa dùng
FAILED_REPLY_SUBMITTED, // đã khai báo, chưa dùng
TAKE_FOCUS // đã khai báo, chưa dùng
NEED_LOGIN , // SDK có phát
NEED_PAY_ONG , // SDK có phát
TOURNAMENT_BET , // đã khai báo, chưa dùng
SUCCESSFUL_REPLY_SUBMITTED , // đã khai báo, chưa dùng
FAILED_REPLY_SUBMITTED , // đã khai báo, chưa dùng
TAKE_FOCUS // đã khai báo, chưa dùng
interface PlayerChangeListener {
suspend fun currentPosition (): Long
fun onPlayerStateChanged (): Pair<Boolean, Int> // (isPlaying, playbackState)
interface PlayerChangeListener {
android . util . Pair < Boolean , Integer > onPlayerStateChanged () ; // (isPlaying, playbackState)
playbackState theo quy ước ExoPlayer: 1 = IDLE, 2 = BUFFERING, 3 = READY, 4 = ENDED.
SDK ánh xạ: 1 → "idle", 3 → isPlaying ? "playing" : "paused", còn lại → "".
ConstraintLayout tuỳ biến, là khung chứa mọi overlay.
Thành phần công khai Mô tả overlayList: HashMap<OverlayWebView, OverlayConfig>Các WebView đang gắn addListener(eventListener, overlayListener)SDK tự gọi trong OverlayManager.init getCurrentOrientation(): IntHướng màn hình hiện tại getLastOverlay(): OverlayWebView?WebView gắn gần nhất getOverlayConfig(): OverlayConfig?Config của overlay dạng popup (nếu có) removeWebView(view), removeWebViews(), clearViews()Gỡ view và huỷ tracking tương ứng
App chỉ cần khai báo view này trong XML; các hàm còn lại do SDK dùng.
Trường Kiểu Ý nghĩa srcStringURL template web metadataMetadata(pos, size)Vị trí + kích thước theo tỉ lệ durationLongThời lượng hiển thị (giây) targetsString?Nền tảng áp dụng (all, androidmobile, androidtv…) timelineIdString?Định danh mốc kịch bản (khoá chống trùng, chống trả lời lại) mediaContentIdString?Nội dung áp dụng typeTimelineInt?0/1/2 mini game, 3 predict, 4 headline, 5 lucky isPopupOverlayBooleantrue → dùng ràng buộc popup theo 9 vị trí; false → inline trên/dướiallowAnonymousBooleanCho phép người chưa đăng nhập xem/tương tác requiredFanClubBoolean?Bắt buộc là fanclub startTimeShowOverlayLong?(VOD) giây bắt đầu hiện requiredFocus, frequencyLimit, ts, sendTime, serverTimeTrường phụ trợ từ server
data class Metadata( val pos: OverlayPosition, val size: OverlaySize)
data class OverlayPosition( val x: Int, val y: Int, var alignment: OverlayAlignment? = null )
data class OverlaySize( val w: Float, val h: Float) // 0..1 — tỉ lệ so với OverlayView
private final OverlayPosition pos ;
private final OverlaySize size ;
public Metadata ( OverlayPosition pos , OverlaySize size ) {
public OverlayPosition getPos () { return pos; }
public OverlaySize getSize () { return size; }
private OverlayAlignment alignment ; // mặc định null
public OverlayPosition ( int x , int y ) {
public OverlayPosition ( int x , int y , OverlayAlignment alignment ) {
this . alignment = alignment;
public int getX () { return x; }
public int getY () { return y; }
public OverlayAlignment getAlignment () { return alignment; }
public void setAlignment ( OverlayAlignment alignment ) { this . alignment = alignment; }
private final float h ; // 0..1 — tỉ lệ so với OverlayView
public OverlaySize ( float w , float h ) {
public float getW () { return w; }
public float getH () { return h; }
x: 0 trái, 1 giữa, 2 phải. y: 0 trên, 1 giữa, 2 dưới →
OverlayAlignment.{LeftTop, LeftCenter, LeftBottom, CenterTop, Center, CenterBottom, RightTop, RightCenter, RightBottom}.
data class OverlayConfig(
val popupOverlay: Boolean = false , // true nếu là overlay inline dạng dải trên/dưới
val alignment: String? = null , // "top" | "bottom" | "left" | "right"
val ratio: Float = 0F // tỉ lệ chiều cao so với OverlayView
private final boolean popupOverlay ; // true nếu là overlay inline dạng dải trên/dưới
private final String alignment ; // "top" | "bottom" | "left" | "right"
private final float ratio ; // tỉ lệ chiều cao so với OverlayView
public OverlayConfig ( boolean popupOverlay , String alignment , float ratio ) {
this . popupOverlay = popupOverlay;
this . alignment = alignment;
public boolean isPopupOverlay () { return popupOverlay; }
public String getAlignment () { return alignment; }
public float getRatio () { return ratio; }
data class ActionReportResponse(
var success: Boolean = false ,
var result: ActionResponseMessage = ActionResponseMessage (), // message, status, description, data
var failureReason: String? = null ,
var failureStep: String? = null ,
var error: String? = null ,
class ActionReportResponse {
private boolean success = false ;
private ActionResponseMessage result = new ActionResponseMessage () ; // message, status, description, data
private String failureReason = null ;
private String failureStep = null ;
private String error = null ;
public boolean isSuccess () { return success; }
public void setSuccess ( boolean success ) { this . success = success; }
public ActionResponseMessage getResult () { return result; }
public void setResult ( ActionResponseMessage result ) { this . result = result; }
public String getFailureReason () { return failureReason; }
public void setFailureReason ( String failureReason ) { this . failureReason = failureReason; }
public String getFailureStep () { return failureStep; }
public void setFailureStep ( String failureStep ) { this . failureStep = failureStep; }
public String getError () { return error; }
public void setError ( String error ) { this . error = error; }
Tên sự kiện Hằng số Kiểu dữ liệu Xử lý overlayConst.OVERLAYJSONObjectOverlay realtime ex-overlayConst.EX_OVERLAYJSONObjectOverlay bù (bỏ nếu duration <= 10) overlay-recallConst.OVERLAY_RECALLJSONObject | ByteArrayThu hồi overlay theo timelineId predict-timelineConst.PREDICT_TIMELINEByteArrayACTIVE / INACTIVE / POWER / ENDlucky-eventConst.LUCKY_EVENTByteArrayACTIVE / INACTIVE / POWER / ENDpredict-resultConst.PREDICT_RESULTByteArraySố liệu answer1 / answer2 update-resultConst.UPDATE_RESULTByteArrayĐẩy xuống web qua UPDATE_DATA_INTERACTIVE liveviewsConst.LIVE_VIEWSJSONObjectSố người xem dataConst.FAN_CLUBByteArrayTrạng thái fanclub ckstConst.CKSTJSONObjectServer hỏi trạng thái player
Tên Khi nào Payload join-roomSau khi socket connect {deviceId, mediaContentId, os, versionSDK, tenantId, isActivelyDisconnectSocket}leave-roomleaveRoom()như trên receive-messagePayload có traceLock = true Payload gốc (ByteArray UTF-8) emit-ackCác mốc tracking Xem 05 — Tracking ckstTrả lời sự kiện ckst {action: "CHECK_PLAYER_STATUS", sessionId, data: {deviceId, os, mediaContentId, playerStatus}}
Header Giá trị X-TENANT-IDtenantIdvBuildConfig.BUILD_VERSION (ngày build SDK)osBuildConfig.DEVICE_TYPE (androidmobile / androidtv)deviceIdSettings.Secure.ANDROID_IDAuthorizationBearer <jwt> hoặc cookie nguyên văn
Tuỳ chọn kết nối: chỉ dùng websocket, tự reconnect, delay 5000ms (cả reconnectionDelay lẫn
reconnectionDelayMax), randomizationFactor = 0.4.
Base URL = Const.INTERACTIVE_URL (suy ra từ EnvInteractive).
Interceptor của BaseRequest tự thêm header X-TENANT-ID.
Method Đường dẫn Header Dùng cho GET/api/ctc/scenario/v2/vod/{mediaContentId}/{os}AuthorizationTải kịch bản VOD GET/api/scenario/getActiveOverlay/{mediaContentId}— Lấy overlay đang hoạt động (bù) POST/api/actionsAuthorizationGửi hành động / câu trả lời GET/api/public/vip-statusAuthorizationKiểm tra VIP trước khi trả lời GET/api/media/interactive-status/{mediaId}x-tenant-id, CookieNội dung có tương tác không
Thân request /api/actions (SDK bổ sung vào JSON do web gửi lên):
"type" : " INTERACTIVE_ACTION_REPORT " ,
"deviceId" : " <android-id> " , // SDK thêm
"os" : " (os, androidmobile) " , // SDK thêm
"requiredFanClub" : false ,
"media" : " <mediaId> " , // SDK thêm
"socket_id" : " <socket id> " // SDK thêm (chỉ khi Live)
// ...các trường do template web gửi
Tên interface: Android (Const.JS_INTERFACE_NAME).
Android . postMessage ( JSON . stringify ({ key: " LOADED_OVERLAY " }));
Android . postMessage ( JSON . stringify ({ type: " INTERACTIVE_ACTION_REPORT " , data: { ... } }));
Giá trị Hằng số Ý nghĩa key = LOADED_OVERLAYConst.LOADED_OVERLAYTemplate sẵn sàng nhận dữ liệu type = INTERACTIVE_SHOW_ANALYTICSOverlay hiển thị type = INTERACTIVE_HIDE_ANALYTICSOverlay đóng type = INTERACTIVE_ACTION_REPORTTrả lời / hành động (có data.action = "CLICK_ADS" cho quảng cáo) type = INTERACTIVE_OPEN_ANALYTICSMở dialog predict/lucky type = INTERACTIVE_CLOSE_ANALYTICSĐóng dialog predict/lucky
window . addEventListener ( " message " , ( e ) => { /* e.data chứa key + data */ });
keyNội dung GET_DATA_INTERACTIVEDữ liệu overlay ban đầu UPDATE_DATA_INTERACTIVECập nhật kết quả realtime UPDATE_CONFIRMXác nhận trả lời thành công UPDATE_COUTERSố liệu predict (answer1, answer2) UPDATE_NUMBER_LUCKYKết quả lucky
Tham số Khi nào được thêm os=<deviceType>Nếu src chưa có &os logged=falseChưa đăng nhập và overlay không allowAnonymous duration=<giây còn lại>Luôn (tính từ missTime) fanclub=falseOverlay yêu cầu fanclub nhưng người dùng chưa thuộc fanclub u=<userId>Riêng PredictDialog / LuckyDialog timelineId, tenantId, deviceType, osRiêng overlay VOD (dựng trong parseTimeLineListForVOD)
Các biến toàn cục do SDK quản lý (app không nên ghi trực tiếp): Const.USER_COOKIE,
Const.USER_ID, Const.BJ_NAME, Const.IS_FAN_CLUB, Const.IS_VIP, Const.BET_AMOUNT,
Const.INTERACTIVE_URL, Const.INTERACTIVE_SOCKET_URL.
Loại Nội dung hiển thị Kích hoạt bởi SUGGEST_FAN_CLUBCảnh báo cần vào fanclub (kèm Const.BJ_NAME) Overlay requiredFanClub SUGGEST_BET“Bạn có đồng ý sử dụng N OnG để trả lời không?” betAmount > 0 và không phải VIPSUGGEST_ANSWER_REJECTdescription từ API, mặc định “Trả lời không thành công!”API trả success = false SUGGEST_ANSWER_PAYMENT_REJECT“Vui lòng nạp OnG!” Mã PM402 SUGGEST_PREDICT_END“Dự đoán đã kết thúc!” Sự kiện predict END SUGGEST_LUCKY_END“Sự kiện đã kết thúc!” Lucky kết thúc SUGGEST_ANSWER_SUCCESS, SUGGEST_ERROR_OCCUR, SUGGEST_LOGINĐã định nghĩa, chưa dùng trong luồng hiện tại