@@ -0,0 +1,921 @@
|
||||
/**
|
||||
* Web SDK v1.2.0 接入层。
|
||||
* 页面/点击/导航沿用 jha-1043 的口径,短剧使用最新版 dramaEvent/dramaStatusChange。
|
||||
*/
|
||||
(function (global) {
|
||||
"use strict";
|
||||
|
||||
var state = {
|
||||
initialized: false,
|
||||
initializing: false,
|
||||
queue: [],
|
||||
page: { key: "internal_other", name: "站内其他页面" },
|
||||
referrer: { key: "direct", name: "直接访问" },
|
||||
userType: "normal",
|
||||
pageStartedAt: Date.now(),
|
||||
dramaViewSent: false,
|
||||
adShown: {},
|
||||
};
|
||||
|
||||
function stringValue(value, fallback) {
|
||||
var result = value == null ? "" : String(value).trim();
|
||||
return result || (fallback == null ? "" : String(fallback));
|
||||
}
|
||||
|
||||
function integer(value, fallback) {
|
||||
var result = parseInt(value, 10);
|
||||
return isFinite(result) ? result : (fallback || 0);
|
||||
}
|
||||
|
||||
function clamp(value, min, max) {
|
||||
var result = Number(value);
|
||||
if (!isFinite(result)) result = min;
|
||||
return Math.max(min, Math.min(max, result));
|
||||
}
|
||||
|
||||
function sdkAvailable() {
|
||||
return !!(global.WebSDK && global.WebSDK.track);
|
||||
}
|
||||
|
||||
function logError() {}
|
||||
|
||||
function invoke(method, payload) {
|
||||
if (!state.initialized || !sdkAvailable()) {
|
||||
return new Promise(function (resolve) {
|
||||
state.queue.push([method, payload, resolve]);
|
||||
});
|
||||
}
|
||||
try {
|
||||
var fn = global.WebSDK.track[method];
|
||||
if (typeof fn !== "function") {
|
||||
var missing = new Error("SDK method does not exist: " + method);
|
||||
logError(missing);
|
||||
return Promise.resolve({ sent: false, ok: false, reason: "unknown_method" });
|
||||
}
|
||||
var result = fn(payload);
|
||||
return Promise.resolve(result).then(function (response) {
|
||||
if (response && response.ok === false) {
|
||||
logError(method + " rejected: " + JSON.stringify(response));
|
||||
}
|
||||
return response;
|
||||
}).catch(function (error) {
|
||||
logError(error);
|
||||
return { sent: false, ok: false, reason: "exception" };
|
||||
});
|
||||
} catch (error) {
|
||||
logError(error);
|
||||
return Promise.resolve({ sent: false, ok: false, reason: "exception" });
|
||||
}
|
||||
}
|
||||
|
||||
function flushQueue() {
|
||||
var pending = state.queue.splice(0, state.queue.length);
|
||||
pending.forEach(function (entry) {
|
||||
Promise.resolve(invoke(entry[0], entry[1])).then(entry[2]);
|
||||
});
|
||||
}
|
||||
|
||||
function currentChannel(value) {
|
||||
var channel = stringValue(value);
|
||||
if (!channel && global.ChannelAttribution && typeof global.ChannelAttribution.get === "function") {
|
||||
channel = stringValue(global.ChannelAttribution.get());
|
||||
}
|
||||
if (!channel) channel = stringValue(global.__CHANNEL_CODE__, "organic");
|
||||
var normalized = channel.toLowerCase();
|
||||
if (["self", "natural", "official", "channel", "channel://natural"].indexOf(normalized) >= 0) {
|
||||
return "organic";
|
||||
}
|
||||
if (!channel || channel.length > 50 || !/^[A-Za-z0-9_.-]+$/.test(channel) ||
|
||||
["{}", "0", "-", "unknown"].indexOf(normalized) >= 0) {
|
||||
return "organic";
|
||||
}
|
||||
return channel;
|
||||
}
|
||||
|
||||
function normalizeUid(value) {
|
||||
var uid = stringValue(value);
|
||||
if (uid === "0" || uid.length < 4 || uid.length > 32 || !/^[A-Za-z0-9_]+$/.test(uid) ||
|
||||
["u_1001", "stress_user", "u_lab"].indexOf(uid.toLowerCase()) >= 0) return "";
|
||||
return uid;
|
||||
}
|
||||
|
||||
function inferPage(path) {
|
||||
path = stringValue(path, "/").replace(/\/$/, "") || "/";
|
||||
if (path === "/") return { key: "home", name: "首页" };
|
||||
var rules = [
|
||||
[/^\/series-play(?:\/|$)/, "series_play", "AI成人剧集播放页"],
|
||||
[/^\/series(?:\/|$)/, "series", "AI成人剧集"],
|
||||
[/^\/play(?:\/|$)/, "play", "短剧播放页"],
|
||||
[/^\/detail(?:\/|$)/, "detail", "短剧详情"],
|
||||
[/^\/list(?:\/|$)/, "list", "短剧列表"],
|
||||
[/^\/rank(?:\/|$)/, "rank", "排行榜"],
|
||||
[/^\/topic-detail(?:\/|$)/, "topic_detail", "专题详情"],
|
||||
[/^\/topic(?:\/|$)/, "topic", "专题列表"],
|
||||
[/^\/tags(?:\/|$)/, "tags", "标签详情"],
|
||||
[/^\/search(?:-mobile)?(?:\/|$)/, "search", "搜索结果页"],
|
||||
[/^\/history(?:\/|$)/, "history", "观看历史"],
|
||||
[/^\/favorites(?:\/|$)/, "favorites", "我的收藏"],
|
||||
[/^\/profile(?:\/|$)/, "profile", "个人中心"],
|
||||
];
|
||||
for (var i = 0; i < rules.length; i += 1) {
|
||||
if (rules[i][0].test(path)) return { key: rules[i][1], name: rules[i][2] };
|
||||
}
|
||||
return { key: "internal_other", name: "站内其他页面" };
|
||||
}
|
||||
|
||||
function readReferrer() {
|
||||
try {
|
||||
var saved = global.sessionStorage.getItem("__tracker_last_page");
|
||||
if (saved) {
|
||||
var parsed = JSON.parse(saved);
|
||||
if (parsed && parsed.key) return parsed;
|
||||
}
|
||||
} catch (error) {}
|
||||
try {
|
||||
if (document.referrer) {
|
||||
var url = new URL(document.referrer, global.location.href);
|
||||
if (url.host === global.location.host) return inferPage(url.pathname);
|
||||
return { key: "external", name: url.host || "外部站点" };
|
||||
}
|
||||
} catch (error) {}
|
||||
return { key: "direct", name: "直接访问" };
|
||||
}
|
||||
|
||||
function rememberPage() {
|
||||
try { global.sessionStorage.setItem("__tracker_last_page", JSON.stringify(state.page)); } catch (error) {}
|
||||
}
|
||||
|
||||
function init(options) {
|
||||
if (state.initialized || state.initializing) return;
|
||||
options = options || {};
|
||||
var inferred = inferPage(global.location && global.location.pathname);
|
||||
state.page = {
|
||||
key: stringValue(options.pageKey, inferred.key),
|
||||
name: stringValue(options.pageName, inferred.name),
|
||||
};
|
||||
state.userType = options.userType === "vip" ? "vip" : "normal";
|
||||
state.referrer = readReferrer();
|
||||
state.initializing = true;
|
||||
|
||||
var attempts = 0;
|
||||
var timer = global.setInterval(function () {
|
||||
attempts += 1;
|
||||
if (!sdkAvailable() && attempts < 100) return;
|
||||
global.clearInterval(timer);
|
||||
if (!sdkAvailable()) {
|
||||
state.initializing = false;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
var result = global.WebSDK.init({
|
||||
appId: stringValue(options.appId),
|
||||
uid: normalizeUid(options.uid),
|
||||
channel: currentChannel(options.channel),
|
||||
encryptedConfig: stringValue(options.encryptedConfig),
|
||||
appVersion: stringValue(options.appVersion, "1.0.0"),
|
||||
disableAutoEvents: ["app_page_view", "page_click"],
|
||||
});
|
||||
Promise.resolve(result).then(function () {
|
||||
state.initialized = true;
|
||||
state.initializing = false;
|
||||
__commonFieldsReady = null;
|
||||
loadCommonFields();
|
||||
flushQueue();
|
||||
onReady();
|
||||
}).catch(function (error) {
|
||||
state.initializing = false;
|
||||
logError(error);
|
||||
});
|
||||
} catch (error) {
|
||||
state.initializing = false;
|
||||
logError(error);
|
||||
}
|
||||
}, 50);
|
||||
}
|
||||
|
||||
function whenDOMReady(callback) {
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", callback, { once: true });
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
|
||||
function trackPageView(extra) {
|
||||
extra = extra || {};
|
||||
invoke("appPageView", {
|
||||
user_type: stringValue(extra.user_type, state.userType),
|
||||
page_key: state.page.key,
|
||||
page_name: state.page.name,
|
||||
referrer_page_key: state.referrer.key,
|
||||
referrer_page_name: state.referrer.name,
|
||||
current_page_key: state.page.key,
|
||||
current_page_name: state.page.name,
|
||||
page_load_time: pageLoadDuration(),
|
||||
recommend_trace_id: stringValue(extra.recommend_trace_id),
|
||||
});
|
||||
rememberPage();
|
||||
}
|
||||
|
||||
function pageLoadDuration() {
|
||||
try {
|
||||
if (global.performance && typeof global.performance.getEntriesByType === "function") {
|
||||
var entries = global.performance.getEntriesByType("navigation");
|
||||
if (entries && entries[0]) {
|
||||
var navigation = entries[0];
|
||||
var end = navigation.loadEventEnd || navigation.domContentLoadedEventEnd || global.performance.now();
|
||||
if (isFinite(end)) return Math.max(0, Math.round(end));
|
||||
}
|
||||
}
|
||||
if (global.performance && typeof global.performance.now === "function") {
|
||||
return Math.max(0, Math.round(global.performance.now()));
|
||||
}
|
||||
} catch (error) {}
|
||||
return Math.max(0, Date.now() - state.pageStartedAt);
|
||||
}
|
||||
|
||||
function trackPageClick(event) {
|
||||
var doc = document.documentElement || {};
|
||||
var viewportWidth = global.innerWidth || doc.clientWidth || 0;
|
||||
var viewportHeight = global.innerHeight || doc.clientHeight || 0;
|
||||
var x = integer(event.clientX, 0);
|
||||
var y = integer(event.clientY, 0);
|
||||
invoke("pageClick", {
|
||||
page_key: state.page.key,
|
||||
page_name: state.page.name,
|
||||
click_page_x: x,
|
||||
click_page_y: y,
|
||||
click_x_percent: viewportWidth ? Math.round(clamp(x / viewportWidth * 100, 0, 100)) : 0,
|
||||
click_y_percent: viewportHeight ? Math.round(clamp(y / viewportHeight * 100, 0, 100)) : 0,
|
||||
screen_width: integer(global.screen && global.screen.width, 0),
|
||||
screen_height: integer(global.screen && global.screen.height, 0),
|
||||
recommend_trace_id: "",
|
||||
});
|
||||
}
|
||||
|
||||
function trackNavigation(anchor) {
|
||||
if (!anchor || (!anchor.hasAttribute("data-navigation-key") && !anchor.hasAttribute("data-nav-key")) ||
|
||||
!anchor.getAttribute("href")) return;
|
||||
var href = anchor.getAttribute("href");
|
||||
if (!href || href.charAt(0) === "#" || /^javascript:/i.test(href)) return;
|
||||
var label = stringValue(anchor.getAttribute("aria-label") || anchor.textContent).replace(/\s+/g, " ");
|
||||
var navigationKey = stringValue(
|
||||
anchor.getAttribute("data-navigation-key") || anchor.getAttribute("data-nav-key")
|
||||
);
|
||||
if (!/^[A-Za-z][A-Za-z0-9_]{0,63}$/.test(navigationKey)) {
|
||||
navigationKey = "";
|
||||
try {
|
||||
var url = new URL(href, global.location.href);
|
||||
if (url.origin !== global.location.origin) {
|
||||
navigationKey = "nav_external";
|
||||
} else {
|
||||
var targetPage = inferPage(url.pathname);
|
||||
if (targetPage.key !== "internal_other") {
|
||||
navigationKey = "nav_" + targetPage.key;
|
||||
} else {
|
||||
var firstSegment = stringValue(url.pathname.split("/").filter(Boolean)[0])
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9]+/g, "_")
|
||||
.replace(/^_+|_+$/g, "")
|
||||
.slice(0, 48);
|
||||
navigationKey = firstSegment ? "nav_" + firstSegment : "nav_home";
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
navigationKey = "nav_other";
|
||||
}
|
||||
}
|
||||
invoke("navigation", {
|
||||
navigation_key: navigationKey.toLowerCase(),
|
||||
navigation_name: stringValue(
|
||||
anchor.getAttribute("data-navigation-name") || anchor.getAttribute("data-nav-name"),
|
||||
label || navigationKey
|
||||
).slice(0, 255),
|
||||
});
|
||||
}
|
||||
|
||||
function adPayload(node) {
|
||||
var id = stringValue(node && node.getAttribute("data-ad-id"));
|
||||
if (!id) return null;
|
||||
var adType = stringValue(node.getAttribute("data-ad-type"), "banner");
|
||||
var allSlots = document.querySelectorAll('[data-track-ad="external"][data-ad-id]');
|
||||
var slotPosition = Array.prototype.indexOf.call(allSlots, node) + 1;
|
||||
if (slotPosition <= 0) slotPosition = 1;
|
||||
var fallbackSlotKey = state.page.key + "_content_banner_" + slotPosition;
|
||||
var fallbackSlotName = state.page.name + "内容横幅第" + slotPosition + "位";
|
||||
if (adType === "interstitial") {
|
||||
fallbackSlotKey = state.page.key + "_popup_" + slotPosition;
|
||||
fallbackSlotName = state.page.name + "弹窗广告位";
|
||||
}
|
||||
return {
|
||||
page_key: state.page.key,
|
||||
page_name: state.page.name,
|
||||
ad_slot_key: stringValue(
|
||||
node.getAttribute("data-ad-slot-key"),
|
||||
fallbackSlotKey
|
||||
),
|
||||
ad_slot_name: stringValue(
|
||||
node.getAttribute("data-ad-slot-name"),
|
||||
fallbackSlotName
|
||||
),
|
||||
ad_id: id,
|
||||
creative_id: stringValue(node.getAttribute("data-creative-id")),
|
||||
ad_type: adType,
|
||||
};
|
||||
}
|
||||
|
||||
function bindAds() {
|
||||
var selector = '[data-track-ad="external"][data-ad-id]';
|
||||
var nodes = document.querySelectorAll(selector);
|
||||
function show(node) {
|
||||
trackExternalAd(node, "show");
|
||||
}
|
||||
if (global.IntersectionObserver) {
|
||||
var observer = new IntersectionObserver(function (entries) {
|
||||
entries.forEach(function (entry) {
|
||||
if (!entry.isIntersecting) return;
|
||||
show(entry.target);
|
||||
observer.unobserve(entry.target);
|
||||
});
|
||||
}, { threshold: 0.1 });
|
||||
function observe(node) {
|
||||
if (!node || node.__trackerAdObserved) return;
|
||||
node.__trackerAdObserved = true;
|
||||
observer.observe(node);
|
||||
}
|
||||
Array.prototype.forEach.call(nodes, observe);
|
||||
if (global.MutationObserver) {
|
||||
new MutationObserver(function (records) {
|
||||
records.forEach(function (record) {
|
||||
Array.prototype.forEach.call(record.addedNodes || [], function (node) {
|
||||
if (!node || node.nodeType !== 1) return;
|
||||
if (node.matches && node.matches(selector)) observe(node);
|
||||
if (node.querySelectorAll) {
|
||||
Array.prototype.forEach.call(node.querySelectorAll(selector), observe);
|
||||
}
|
||||
});
|
||||
});
|
||||
}).observe(document.documentElement, { childList: true, subtree: true });
|
||||
}
|
||||
} else {
|
||||
Array.prototype.forEach.call(nodes, show);
|
||||
}
|
||||
}
|
||||
|
||||
function trackExternalAd(node, eventType) {
|
||||
if (!node || (eventType !== "show" && eventType !== "click")) return;
|
||||
var payload = adPayload(node);
|
||||
if (!payload) return;
|
||||
var key = payload.ad_slot_key + "|" + payload.ad_id;
|
||||
if (!state.adShown[key]) {
|
||||
state.adShown[key] = true;
|
||||
invoke("adImpression", payload);
|
||||
}
|
||||
if (eventType === "click" && node.getAttribute("data-ad-clickable") === "1") {
|
||||
return invoke("adClick", payload);
|
||||
}
|
||||
}
|
||||
|
||||
function bindExternalAdEvents() {
|
||||
document.addEventListener("external-ad:track", function (event) {
|
||||
var detail = event && event.detail ? event.detail : {};
|
||||
trackExternalAd(detail.element, stringValue(detail.eventType));
|
||||
});
|
||||
}
|
||||
|
||||
function trackAdvertising(node, eventType) {
|
||||
if (!node || ["show", "click", "close"].indexOf(eventType) < 0) return;
|
||||
invoke("advertising", {
|
||||
event_type: eventType,
|
||||
advertising_key: stringValue(node.getAttribute("data-advertising-key"), "dialog_ad"),
|
||||
advertising_name: stringValue(node.getAttribute("data-advertising-name"), "弹窗广告"),
|
||||
advertising_id: stringValue(node.getAttribute("data-advertising-id") || node.getAttribute("data-dialog-ad-id")),
|
||||
});
|
||||
}
|
||||
|
||||
function bindAdvertisingEvents() {
|
||||
document.addEventListener("dialog-ad:track", function (event) {
|
||||
var detail = event && event.detail ? event.detail : {};
|
||||
var node = detail.element;
|
||||
if (!node || !node.getAttribute) return;
|
||||
trackAdvertising(node, stringValue(detail.eventType));
|
||||
});
|
||||
}
|
||||
|
||||
function dramaRoot() {
|
||||
return document.querySelector("[data-track-drama]");
|
||||
}
|
||||
|
||||
function dramaContext(root) {
|
||||
root = root || dramaRoot();
|
||||
if (!root) return null;
|
||||
var data = root.dataset || {};
|
||||
var dramaID = stringValue(data.dramaId);
|
||||
var videoID = stringValue(data.videoId);
|
||||
if (!dramaID || !videoID) return null;
|
||||
return {
|
||||
media_id: stringValue(data.mediaId),
|
||||
drama_id: dramaID,
|
||||
drama_title: stringValue(data.dramaTitle, "未命名短剧"),
|
||||
episode: Math.max(1, integer(data.episode, 1)),
|
||||
total_episode: Math.max(1, integer(data.totalEpisode, 1)),
|
||||
video_id: videoID,
|
||||
drama_type_id: stringValue(data.dramaTypeId, "default"),
|
||||
drama_type_name: stringValue(data.dramaTypeName, "默认分类"),
|
||||
drama_content_type: "short_drama",
|
||||
recommend_trace_id: stringValue(data.recommendTraceId),
|
||||
drama_tag_key: stringValue(data.dramaTagKey, "default"),
|
||||
drama_tag_name: stringValue(data.dramaTagName, "默认标签"),
|
||||
drama_duration: Math.max(0, integer(data.dramaDuration, 0)),
|
||||
};
|
||||
}
|
||||
|
||||
var dramaBehaviorNames = {
|
||||
ep_view: "展示",
|
||||
ep_play: "播放",
|
||||
ep_pause: "暂停",
|
||||
ep_forward: "快进",
|
||||
ep_rewind: "快退",
|
||||
ep_complete: "播放完成",
|
||||
ep_share: "分享",
|
||||
};
|
||||
|
||||
function videoPlaybackState(video) {
|
||||
if (!video) return null;
|
||||
if (!video.__trackerPlaybackState) {
|
||||
video.__trackerPlaybackState = {
|
||||
startedAt: 0,
|
||||
elapsedMs: 0,
|
||||
seekFrom: null,
|
||||
lastPosition: Number(video.currentTime) || 0,
|
||||
};
|
||||
}
|
||||
return video.__trackerPlaybackState;
|
||||
}
|
||||
|
||||
function beginPlayback(video) {
|
||||
var playback = videoPlaybackState(video);
|
||||
if (playback && !playback.startedAt) playback.startedAt = Date.now();
|
||||
}
|
||||
|
||||
function settlePlayback(video) {
|
||||
var playback = videoPlaybackState(video);
|
||||
if (!playback || !playback.startedAt) return;
|
||||
playback.elapsedMs += Math.max(0, Date.now() - playback.startedAt);
|
||||
playback.startedAt = 0;
|
||||
}
|
||||
|
||||
function playbackSeconds(video, keepDuration) {
|
||||
var playback = videoPlaybackState(video);
|
||||
if (!playback) return 0;
|
||||
var now = Date.now();
|
||||
var activeMs = playback.startedAt ? Math.max(0, now - playback.startedAt) : 0;
|
||||
var seconds = Math.max(0, Math.round((playback.elapsedMs + activeMs) / 1000));
|
||||
if (!keepDuration) {
|
||||
playback.elapsedMs = 0;
|
||||
if (playback.startedAt) playback.startedAt = now;
|
||||
}
|
||||
return seconds;
|
||||
}
|
||||
|
||||
function videoMetrics(video, context, keepDuration) {
|
||||
var duration = video && isFinite(video.duration) ? Math.round(video.duration) : context.drama_duration;
|
||||
var current = video && isFinite(video.currentTime) ? Math.round(video.currentTime) : 0;
|
||||
return {
|
||||
duration: Math.max(0, duration),
|
||||
current: Math.max(0, current),
|
||||
played: playbackSeconds(video, keepDuration),
|
||||
progress: duration > 0 ? Math.round(clamp(current / duration * 100, 0, 100)) : 0,
|
||||
speed: Math.max(1, Math.round(((video && video.playbackRate) || 1) * 100)),
|
||||
};
|
||||
}
|
||||
|
||||
function trackDramaEvent(behaviorKey, video, options) {
|
||||
var context = dramaContext();
|
||||
if (!context) return;
|
||||
options = options || {};
|
||||
var metrics = videoMetrics(video, context, options.keepPlaybackDuration === true);
|
||||
context.drama_duration = metrics.duration;
|
||||
context.play_duration = metrics.played;
|
||||
context.play_progress = options.forceProgress != null
|
||||
? Math.round(clamp(options.forceProgress, 0, 100))
|
||||
: behaviorKey === "ep_complete" ? 100 : metrics.progress;
|
||||
context.play_speed = metrics.speed;
|
||||
context.play_behavior_key = behaviorKey;
|
||||
context.play_behavior_name = dramaBehaviorNames[behaviorKey] || behaviorKey;
|
||||
return invoke("dramaEvent", context);
|
||||
}
|
||||
|
||||
function trackDramaStatusChange(targetEpisode, targetVideoID, behaviorOverride) {
|
||||
var context = dramaContext();
|
||||
if (!context) return;
|
||||
var nextEpisode = Math.max(1, integer(targetEpisode, context.episode));
|
||||
var videoID = stringValue(targetVideoID, context.video_id);
|
||||
var behavior = stringValue(behaviorOverride);
|
||||
var names = {
|
||||
ep_auto_play: "自动播放",
|
||||
ep_previous: "上一集",
|
||||
ep_next: "下一集",
|
||||
ep_switch: "切换剧集",
|
||||
ep_unlock: "解锁",
|
||||
};
|
||||
if (!behavior) {
|
||||
if (nextEpisode === context.episode && videoID === context.video_id) return;
|
||||
behavior = "ep_switch";
|
||||
if (nextEpisode === context.episode + 1) behavior = "ep_next";
|
||||
else if (nextEpisode === context.episode - 1) behavior = "ep_previous";
|
||||
}
|
||||
if (!names[behavior]) return;
|
||||
return invoke("dramaStatusChange", {
|
||||
media_id: context.media_id,
|
||||
drama_id: context.drama_id,
|
||||
drama_title: context.drama_title,
|
||||
episode: nextEpisode,
|
||||
video_id: videoID,
|
||||
pre_episode: context.episode,
|
||||
drama_behavior_key: behavior,
|
||||
drama_behavior_name: names[behavior],
|
||||
});
|
||||
}
|
||||
|
||||
function trackDramaUnlock() {
|
||||
var context = dramaContext();
|
||||
if (!context) return Promise.resolve({ sent: false, ok: false, reason: "no_drama_context" });
|
||||
return trackDramaStatusChange(context.episode, context.video_id, "ep_unlock");
|
||||
}
|
||||
|
||||
function isMainDramaVideo(root, video) {
|
||||
var mode = video && video.__trackerDramaMode;
|
||||
if (!mode && root) {
|
||||
mode = root.getAttribute("data-play-mode") === "preview" ? "preview" : "main";
|
||||
}
|
||||
return !!(
|
||||
root &&
|
||||
video &&
|
||||
video.tagName === "VIDEO" &&
|
||||
root.contains(video) &&
|
||||
mode !== "preview"
|
||||
);
|
||||
}
|
||||
|
||||
function rememberDramaVideoMode(root, video) {
|
||||
if (!root || !video || video.tagName !== "VIDEO" || !root.contains(video)) return;
|
||||
video.__trackerDramaMode = root.getAttribute("data-play-mode") === "preview" ? "preview" : "main";
|
||||
}
|
||||
|
||||
function prepareDramaModeChange(nextMode) {
|
||||
var root = dramaRoot();
|
||||
var video = root && root.querySelector("video");
|
||||
if (!root || !video) return;
|
||||
var previousMode = video.__trackerDramaMode ||
|
||||
(root.getAttribute("data-play-mode") === "preview" ? "preview" : "main");
|
||||
nextMode = nextMode === "preview" ? "preview" : "main";
|
||||
if (previousMode === nextMode) return;
|
||||
|
||||
if (previousMode === "main") {
|
||||
var playback = video.__trackerPlaybackState;
|
||||
var wasPlaying = !!(playback && (playback.startedAt || playback.elapsedMs > 0));
|
||||
settlePlayback(video);
|
||||
if (wasPlaying) trackDramaEvent("ep_pause", video);
|
||||
}
|
||||
video.__trackerPlaybackState = null;
|
||||
video.__trackerDramaMode = nextMode;
|
||||
}
|
||||
|
||||
function bindDramaEvents() {
|
||||
var root = dramaRoot();
|
||||
if (!root) return;
|
||||
if (!state.dramaViewSent) {
|
||||
state.dramaViewSent = true;
|
||||
trackDramaEvent("ep_view", null);
|
||||
}
|
||||
document.addEventListener("play", function (event) {
|
||||
rememberDramaVideoMode(root, event.target);
|
||||
if (isMainDramaVideo(root, event.target)) {
|
||||
trackDramaEvent("ep_play", event.target);
|
||||
}
|
||||
}, true);
|
||||
document.addEventListener("playing", function (event) {
|
||||
rememberDramaVideoMode(root, event.target);
|
||||
if (isMainDramaVideo(root, event.target)) beginPlayback(event.target);
|
||||
}, true);
|
||||
document.addEventListener("loadedmetadata", function (event) {
|
||||
rememberDramaVideoMode(root, event.target);
|
||||
}, true);
|
||||
document.addEventListener("waiting", function (event) {
|
||||
if (isMainDramaVideo(root, event.target)) settlePlayback(event.target);
|
||||
}, true);
|
||||
document.addEventListener("pause", function (event) {
|
||||
if (isMainDramaVideo(root, event.target) && !event.target.ended) {
|
||||
var playback = event.target.__trackerPlaybackState;
|
||||
var wasPlaying = !!(playback && (playback.startedAt || playback.elapsedMs > 0));
|
||||
settlePlayback(event.target);
|
||||
if (event.target.seeking || !wasPlaying) return;
|
||||
trackDramaEvent("ep_pause", event.target);
|
||||
}
|
||||
}, true);
|
||||
document.addEventListener("timeupdate", function (event) {
|
||||
if (!isMainDramaVideo(root, event.target) || event.target.seeking) return;
|
||||
videoPlaybackState(event.target).lastPosition = Number(event.target.currentTime) || 0;
|
||||
}, true);
|
||||
document.addEventListener("seeking", function (event) {
|
||||
if (!isMainDramaVideo(root, event.target)) return;
|
||||
var playback = videoPlaybackState(event.target);
|
||||
if (playback.seekFrom != null) return;
|
||||
settlePlayback(event.target);
|
||||
playback.seekFrom = playback.lastPosition;
|
||||
}, true);
|
||||
document.addEventListener("seeked", function (event) {
|
||||
if (!isMainDramaVideo(root, event.target)) return;
|
||||
var playback = videoPlaybackState(event.target);
|
||||
if (playback.seekFrom == null) return;
|
||||
var from = playback.seekFrom;
|
||||
var target = Number(event.target.currentTime) || 0;
|
||||
playback.seekFrom = null;
|
||||
playback.lastPosition = target;
|
||||
if (Math.abs(target - from) >= 0.5) {
|
||||
trackDramaEvent(target > from ? "ep_forward" : "ep_rewind", event.target);
|
||||
}
|
||||
if (!event.target.paused && !event.target.ended) beginPlayback(event.target);
|
||||
}, true);
|
||||
document.addEventListener("ended", function (event) {
|
||||
if (isMainDramaVideo(root, event.target)) {
|
||||
settlePlayback(event.target);
|
||||
trackDramaEvent("ep_complete", event.target, { keepPlaybackDuration: true });
|
||||
trackDramaEvent("ep_play", event.target, { forceProgress: 100 });
|
||||
}
|
||||
}, true);
|
||||
|
||||
function reportPendingDramaProgress() {
|
||||
var video = root.querySelector("video");
|
||||
if (!isMainDramaVideo(root, video)) return false;
|
||||
var playback = video.__trackerPlaybackState;
|
||||
if (!playback || (!playback.startedAt && playback.elapsedMs <= 0)) return false;
|
||||
var pendingMs = playback.elapsedMs +
|
||||
(playback.startedAt ? Math.max(0, Date.now() - playback.startedAt) : 0);
|
||||
if (pendingMs < 500) return false;
|
||||
settlePlayback(video);
|
||||
trackDramaEvent("ep_play", video);
|
||||
return true;
|
||||
}
|
||||
|
||||
function flushSDKAfterEnqueue() {
|
||||
if (!global.WebSDK || typeof global.WebSDK.flush !== "function") return;
|
||||
function flush() {
|
||||
Promise.resolve(global.WebSDK.flush()).catch(logError);
|
||||
}
|
||||
Promise.resolve().then(function () {
|
||||
return Promise.resolve();
|
||||
}).then(flush);
|
||||
global.setTimeout(flush, 50);
|
||||
global.setTimeout(flush, 200);
|
||||
}
|
||||
|
||||
document.addEventListener("visibilitychange", function () {
|
||||
var video = root.querySelector("video");
|
||||
if (document.visibilityState === "hidden") {
|
||||
if (reportPendingDramaProgress()) flushSDKAfterEnqueue();
|
||||
if (isMainDramaVideo(root, video) && !video.paused && !video.ended) beginPlayback(video);
|
||||
return;
|
||||
}
|
||||
if (document.visibilityState === "visible" && isMainDramaVideo(root, video) &&
|
||||
!video.paused && !video.ended) {
|
||||
beginPlayback(video);
|
||||
}
|
||||
});
|
||||
global.addEventListener("beforeunload", function () {
|
||||
if (reportPendingDramaProgress()) flushSDKAfterEnqueue();
|
||||
});
|
||||
global.addEventListener("pagehide", function () {
|
||||
if (reportPendingDramaProgress()) flushSDKAfterEnqueue();
|
||||
});
|
||||
}
|
||||
|
||||
function bindSearchEvents() {
|
||||
var container = document.querySelector("[data-search-results]");
|
||||
if (!container) return;
|
||||
var keyword = stringValue(container.getAttribute("data-search-keyword"));
|
||||
if (!keyword) return;
|
||||
var grid = container.querySelector("[data-search-grid]");
|
||||
var cards = grid ? Array.prototype.slice.call(grid.querySelectorAll("[data-search-item-id]")) : [];
|
||||
var resultCount = Math.max(0, integer(container.getAttribute("data-search-result-count"), cards.length));
|
||||
var pageNum = Math.max(1, integer(container.getAttribute("data-search-page"), 1));
|
||||
var pageSize = Math.max(1, integer(container.getAttribute("data-search-page-size"), cards.length || 1));
|
||||
if (pageNum === 1) {
|
||||
invoke("keywordSearch", {
|
||||
keyword: keyword,
|
||||
search_result_count: resultCount,
|
||||
search_content_type: "",
|
||||
search_trace_id: "",
|
||||
search_id: "",
|
||||
});
|
||||
}
|
||||
if (!grid) return;
|
||||
grid.addEventListener("click", function (event) {
|
||||
var target = event.target && event.target.closest ? event.target : null;
|
||||
var resultLink = target && target.closest("[data-search-result-click]");
|
||||
var card = resultLink && resultLink.closest("[data-search-item-id]");
|
||||
if (!card || !grid.contains(card)) return;
|
||||
var position = cards.indexOf(card);
|
||||
if (position < 0) return;
|
||||
invoke("keywordClick", {
|
||||
keyword: keyword,
|
||||
click_item_id: stringValue(card.getAttribute("data-search-item-id")),
|
||||
click_item_type_key: "short_drama",
|
||||
click_item_type_name: "短剧",
|
||||
click_position: (pageNum - 1) * pageSize + position + 1,
|
||||
search_trace_id: "",
|
||||
});
|
||||
}, true);
|
||||
}
|
||||
|
||||
function bindClicks() {
|
||||
document.addEventListener("click", function (event) {
|
||||
var target = event.target && event.target.closest ? event.target : null;
|
||||
if (!target) return;
|
||||
var popupClose = target.closest(
|
||||
"[data-dialog-ad-close],[data-ad-close],[data-register-close]," +
|
||||
"[data-download-close],[data-login-close],[data-recharge-close]," +
|
||||
"[data-play-coin-close],[data-setting-password-close],[data-drawer-close]"
|
||||
);
|
||||
if (!popupClose) trackPageClick(event);
|
||||
var switcher = target.closest("[data-drama-switch]");
|
||||
if (switcher) {
|
||||
trackDramaStatusChange(switcher.getAttribute("data-target-episode"), switcher.getAttribute("data-target-video-id"));
|
||||
}
|
||||
if (target.closest("[data-share-page]") && dramaRoot()) {
|
||||
var root = dramaRoot();
|
||||
var video = root.querySelector("video");
|
||||
trackDramaEvent("ep_share", isMainDramaVideo(root, video) ? video : null);
|
||||
}
|
||||
var ad = target.closest('[data-track-ad="external"][data-ad-clickable="1"][data-ad-id]');
|
||||
if (ad) {
|
||||
trackExternalAd(ad, "click");
|
||||
}
|
||||
trackNavigation(target.closest("a[data-navigation-key],a[data-nav-key]"));
|
||||
}, true);
|
||||
}
|
||||
|
||||
function onReady() {
|
||||
whenDOMReady(function () {
|
||||
Promise.resolve(loadCommonFields()).then(function () {
|
||||
trackPageView();
|
||||
});
|
||||
bindClicks();
|
||||
bindAds();
|
||||
bindExternalAdEvents();
|
||||
bindAdvertisingEvents();
|
||||
bindDramaEvents();
|
||||
bindSearchEvents();
|
||||
try {
|
||||
global.dispatchEvent(new CustomEvent("tracker:ready", { detail: state.page }));
|
||||
} catch (error) {}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
var COMMON_FIELD_KEYS = [
|
||||
"sid",
|
||||
"device_id",
|
||||
"device",
|
||||
"device_fingerprint",
|
||||
"fp_version",
|
||||
];
|
||||
var __commonFields = null;
|
||||
var __commonFieldsReady = null;
|
||||
|
||||
function emptyCommonFields() {
|
||||
var out = {};
|
||||
for (var i = 0; i < COMMON_FIELD_KEYS.length; i++) {
|
||||
out[COMMON_FIELD_KEYS[i]] = "";
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function normalizeCommonFields(fields) {
|
||||
var out = emptyCommonFields();
|
||||
fields = fields || {};
|
||||
for (var i = 0; i < COMMON_FIELD_KEYS.length; i++) {
|
||||
var k = COMMON_FIELD_KEYS[i];
|
||||
if (fields[k] != null) out[k] = String(fields[k]);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function publishCommonFields(fields) {
|
||||
__commonFields = normalizeCommonFields(fields);
|
||||
try {
|
||||
global.__trackerCommonFields = __commonFields;
|
||||
} catch (e) {}
|
||||
try {
|
||||
if (typeof CustomEvent === "function") {
|
||||
global.dispatchEvent(
|
||||
new CustomEvent("tracker:common-fields", { detail: __commonFields })
|
||||
);
|
||||
}
|
||||
} catch (e) {}
|
||||
return __commonFields;
|
||||
}
|
||||
|
||||
function loadCommonFields() {
|
||||
if (__commonFieldsReady) return __commonFieldsReady;
|
||||
__commonFieldsReady = new Promise(function (resolve) {
|
||||
function finish(fields) {
|
||||
resolve(publishCommonFields(fields));
|
||||
}
|
||||
if (!global.WebSDK || typeof global.WebSDK.getCommonFields !== "function") {
|
||||
finish(emptyCommonFields());
|
||||
return;
|
||||
}
|
||||
try {
|
||||
Promise.resolve(global.WebSDK.getCommonFields(COMMON_FIELD_KEYS.slice()))
|
||||
.then(function (fields) {
|
||||
finish(fields || emptyCommonFields());
|
||||
})
|
||||
.catch(function (err) {
|
||||
logError(err);
|
||||
finish(emptyCommonFields());
|
||||
});
|
||||
} catch (err) {
|
||||
logError(err);
|
||||
finish(emptyCommonFields());
|
||||
}
|
||||
});
|
||||
return __commonFieldsReady;
|
||||
}
|
||||
|
||||
function getCommonFieldsCached(keys) {
|
||||
return loadCommonFields().then(function (all) {
|
||||
if (!Array.isArray(keys) || !keys.length) return all;
|
||||
var out = {};
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
var k = String(keys[i] || "").trim();
|
||||
if (k) out[k] = Object.prototype.hasOwnProperty.call(all, k) ? all[k] : "";
|
||||
}
|
||||
return out;
|
||||
});
|
||||
}
|
||||
|
||||
function getCommonFieldCached(key) {
|
||||
var k = String(key || "").trim();
|
||||
if (!k) return Promise.resolve("");
|
||||
return getCommonFieldsCached([k]).then(function (m) {
|
||||
return m[k] || "";
|
||||
});
|
||||
}
|
||||
|
||||
function encodeSDKCommonHeader(fields) {
|
||||
var payload = normalizeCommonFields(fields);
|
||||
try {
|
||||
return btoa(unescape(encodeURIComponent(JSON.stringify(payload))));
|
||||
} catch (e) {
|
||||
logError(e);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
function getSDKCommonHeader() {
|
||||
return loadCommonFields().then(function (fields) {
|
||||
return encodeSDKCommonHeader(fields);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
global.trackerInit = init;
|
||||
global.tracker = {
|
||||
init: init,
|
||||
pageView: trackPageView,
|
||||
dramaEvent: trackDramaEvent,
|
||||
dramaStatusChange: trackDramaStatusChange,
|
||||
dramaUnlock: trackDramaUnlock,
|
||||
prepareDramaModeChange: prepareDramaModeChange,
|
||||
advertising: function (payload) { return invoke("advertising", payload || {}); },
|
||||
externalAd: trackExternalAd,
|
||||
getDramaContext: function () {
|
||||
var context = dramaContext();
|
||||
return context ? Object.assign({}, context) : {};
|
||||
},
|
||||
keywordSearch: function (payload) { return invoke("keywordSearch", payload || {}); },
|
||||
keywordClick: function (payload) { return invoke("keywordClick", payload || {}); },
|
||||
getCommonFields: getCommonFieldsCached,
|
||||
getCommonField: getCommonFieldCached,
|
||||
getCommonFieldsSync: function () { return __commonFields; },
|
||||
encodeSDKCommonHeader: encodeSDKCommonHeader,
|
||||
getSDKCommonHeader: getSDKCommonHeader,
|
||||
flush: function () {
|
||||
if (!global.WebSDK || typeof global.WebSDK.flush !== "function") return Promise.resolve();
|
||||
return Promise.resolve(global.WebSDK.flush()).catch(logError);
|
||||
},
|
||||
setUid: function (uid) {
|
||||
if (global.WebSDK && global.WebSDK.setUid) global.WebSDK.setUid(normalizeUid(uid));
|
||||
},
|
||||
setChannel: function (channel) {
|
||||
if (global.WebSDK && global.WebSDK.setChannel) global.WebSDK.setChannel(currentChannel(channel));
|
||||
},
|
||||
setContext: function (context) {
|
||||
if (global.WebSDK && global.WebSDK.setContext) global.WebSDK.setContext(context || {});
|
||||
},
|
||||
};
|
||||
|
||||
init(global.__TRACKER_BOOT__ || {});
|
||||
})(window);
|
||||
Reference in New Issue
Block a user