Bỏ qua để đến nội dung
VLivev1.1.2

05 — Tracking emit-ack (đo lường vòng đời overlay)

Cơ chế tracking emit-ack đo vòng đời overlay từ khi server đẩy đến khi client nhận, render và người dùng phản hồi.

Xem Markdown

Cơ chế này trả lời câu hỏi của phía vận hành: “Overlay server đẩy đi có thực sự tới máy người dùng, có render được, và người dùng có trả lời không?” — đo bằng chính client, gửi ngược về server qua sự kiện socket emit-ack.

Bật/tắt bằng cờ build FEATURE_TRACKING_ACK_OVERLAY trong interactive/build.gradle (hiện đang true). Toàn bộ lớp tracking là internal — app không tương tác trực tiếp.


graph LR
A["received<br/>SDK nhận payload từ socket"] --> B["rendered<br/>WebView hiển thị xong / lỗi / timeout / bị huỷ"]
B --> C["answer<br/>người dùng bấm gửi câu trả lời"]
C --> D1["answer-succeeded"]
C --> D2["answer-failed"]
StageÝ nghĩaKết thúc phiên?
receivedPayload hợp lệ đã vào SDKKhông
renderedChốt kết quả hiển thị, kèm statusCó, nếu status != success
answerBắt đầu gửi câu trả lờiKhông
answer-succeededAPI trả success = trueCó
answer-failedAPI lỗi / từ chối / lỗi mạngCó

status của rendered: success | failed | timeout | cancelled.

Mỗi stage chỉ được gửi một lần cho mỗi trackingId (emittedStages là Set); nếu emit ném lỗi thì stage được gỡ khỏi set để có thể thử lại ở lần kích hoạt sau.


// emit("emit-ack", payload)
{
"trackingId": "<_trackingId lấy từ payload overlay>",
"event": "overlay",
"stage": "received | rendered | answer | answer-succeeded | answer-failed",
"deviceId": "<android-id>",
"clientContext": { /* xem bên dưới */ }
}
TrườngNguồn
profileoverlay.legacyIframe hoặc overlay.wsInteractive
transportModelegacyIframe / wsInteractive
overlayTypemetadata.type → typeTimeline → type
durationduration của overlay (giây)
sdkVersionversionName.versionCode (ví dụ 1.2.18)
platformtv nếu DEVICE_TYPE chứa “tv”, ngược lại android
os"Android <release>"
deviceClasstv / tablet / mobile_large / mobile / mobile_small
viewportWidth, viewportHeightdisplayMetrics
StageTrường bổ sung
receivedemitTimestamp, receiveTimestamp, receiveLatencyMs, ackLatencyMs, timeline{received, ackSent}
renderedstatus, receiveLatencyMs, renderLatencyMs, ackLatencyMs, timeline{attached, renderStart, loadComplete, readyReceived, ackSent}, failureReason?, failureStep?, custom.cancelReason?
answer*actionType (answer_submit), actionResult (attempted/success/failure), actionLatencyMs, occurrenceId (= timelineId), failureReason?, failureStep?, custom{timelineId, typeTimeline, mediaId, apiMessage?, apiDescription?, error?}

EmitAckTrackingManager.resolveProfile() xét URL src của overlay:

Điều kiệnProfileTimeout renderMốc chốt rendered
src chứa ?ws=1 hoặc &ws=1overlay.wsInteractive8000 msChỉ khi web gửi LOADED_OVERLAY (readyReceived)
còn lạioverlay.legacyIframe5000 msonPageFinished hoặc LOADED_OVERLAY, cái nào đến trước

Nghĩa là template kiểu mới (ws=1) phải chủ động báo sẵn sàng, còn template cũ chỉ cần tải xong trang.


sequenceDiagram
participant WS as Socket
participant M as EmitAckTrackingManager
participant V as OverlayView
participant T as OverlayRenderTracker
participant WV as WebView
WS->>M: normalizeAndIngest("overlay", raw)
Note over M: tách _trackingId, _emitTimestamp<br/>tạo session, xác định profile
M-->>WS: emit-ack {stage: "received"}
V->>T: attach() → hẹn giờ timeout (5s / 8s)
V->>T: renderStart()
V->>WV: loadUrl()
alt tải xong
WV->>T: pageFinished() / readyReceived()
T->>M: signalRendered(status = success)
M-->>WS: emit-ack {stage: "rendered", status: "success"}
else lỗi tải
WV->>T: failed("load_error" | "http_error" | "ssl_error")
T->>M: signalRendered(status = failed, failureStep = "render")
M-->>WS: emit-ack {stage: "rendered", status: "failed"}
else quá hạn
T->>M: signalRendered(status = timeout, failureStep = "render")
M-->>WS: emit-ack {stage: "rendered", status: "timeout"}
else bị gỡ trước khi render
V->>T: cancel("removed" | "cleared")
T->>M: signalRendered(status = cancelled)
M-->>WS: emit-ack {stage: "rendered", status: "cancelled"}
end

OverlayRenderTracker chạy trên Handler(Looper.getMainLooper()), dùng cờ terminalSent để đảm bảo chỉ phát một tín hiệu kết thúc, kể cả khi timeout và pageFinished xảy ra gần nhau.


Đây là danh sách đầy đủ các điểm SDK cố ý không hiển thị overlay — rất hữu ích khi điều tra “vì sao overlay không hiện”:

cancelReasonNơi phát sinhNguyên nhân
invalid_payloadSocket / OverlayManagerThiếu mediaContentId/timelineId, hoặc JSON không parse được thành Overlay
short_durationSocket (ex-overlay)duration <= 10 giây
login_requiredOverlayManagerTenant vtvlive, chưa đăng nhập, overlay không allowAnonymous
socket_not_connectedOverlayManagerSocket đã rớt tại thời điểm xử lý
missing_timeline_idOverlayManagertimelineId rỗng
duplicate_overlayOverlayManagerTrùng timelineId với overlay/predict/lucky/headline đang hiển thị
already_answeredOverlayManagerPref ex_<userId|deviceId> đã ghi timelineId này
target_mismatchWebViewManagementtargets không chứa all và không chứa nền tảng hiện tại
invalid_layoutWebViewManagement / OverlayViewKhông suy được alignment từ metadata.pos
expired_durationWebViewManagementduration - (now - missTime) <= 0
superseded_cacheWebViewManagementOverlay cũ trong cache bị overlay mới thay thế khi mở khoá
removed, clearedOverlayViewWebView bị gỡ / xoá toàn bộ
socket_disconnected, sdk_disconnect, leave_room, clear_all, destroySocket / ManagerKết thúc phiên

Tham sốGiá trị
Số phiên tối đa256 (vượt thì loại phiên cũ nhất — LinkedHashMap truy cập-gần-nhất)
TTL mỗi phiên30 phút
Chu kỳ dọn nền5 phút (thread daemon EmitAckTrackingCleanup)

Phiên bị xoá ngay khi: rendered với status khác success, answer-succeeded, answer-failed, hoặc finish()/cancelAll() khi rời phòng / ngắt socket.


7. Chuẩn hoá payload (TrackingPayloadNormalizer)

Phần tiêu đề “7. Chuẩn hoá payload (TrackingPayloadNormalizer)”
flowchart LR
A["raw JSON từ socket"] --> B{"có trường 'data'?"}
B -->|"có"| C["lấy raw.data làm payload"]
B -->|"không"| D["lấy raw làm payload"]
C --> E["trackingId = raw._trackingId ?? payload._trackingId"]
D --> E
E --> F["emitTimestamp = _emitTimestamp đầu tiên > 0"]
F --> G["gắn lại _trackingId, _emitTimestamp vào payload"]

Nhờ bước này, phần còn lại của SDK luôn làm việc với payload đã “mở gói”, đồng thời giữ được metadata tracking.


  • Tracking không thêm quyền, không gọi API riêng — dùng chính socket đang mở.
  • Ở chế độ VOD không có tracking (không có socket để emit).
  • Nếu app muốn tắt hoàn toàn: sửa FEATURE_TRACKING_ACK_OVERLAY thành false và build lại AAR.

← API Reference · Về mục lục · Tiếp: Lỗi thường gặp →