import 'package:analytics_sdk/analytics_sdk.dart'; import 'package:analytics_sdk/manager/page_name_manager.dart'; import 'package:analytics_sdk/entity/ad_click_event.dart'; import 'package:analytics_sdk/entity/ad_impression_event.dart'; import 'package:analytics_sdk/entity/comic_event.dart'; import 'package:analytics_sdk/entity/keyword_click_event.dart'; import 'package:analytics_sdk/entity/keyword_search_event.dart'; import 'package:analytics_sdk/entity/novel_event.dart'; import 'package:analytics_sdk/entity/video_event.dart'; import 'package:analytics_sdk/enum/click_item_type_enum.dart'; import 'package:analytics_sdk/enum/read_behavior_enum.dart'; import 'package:analytics_sdk/enum/video_content_type_enum.dart'; import 'package:analytics_sdk/enum/video_event_enum.dart'; import 'package:get/get.dart'; import 'package:jydj/tools_base/debug_log.dart'; import 'package:video_player/video_player.dart'; import '../hj_model/cartoon_media_info.dart'; import '../hj_model/media_content.dart'; import '../hj_model/splash/ads_model.dart'; import '../hj_model/video_model.dart'; //页面枚举 enum TrackEventPageType { start('start', '启动页'), // 启动页广告 home('home', '首页'), // 首页公告弹窗 acgHome('acg_home', 'acg首页'), // 首页acg live('live', '直播列表'), // 直播顶部广告 liveDetail('live_detail', '直播详情页'), // 直播详情页广告 commPost('comm_post', '社区首页'), // 顶部公共广告 commImg('comm_img', '社区图集'), // 顶部公共广告 commNovel('comm_novel', '社区小说'), // 顶部公共广告 commGroup('comm_group', '社区群聊'), // 顶部公共广告 darkweb('darkweb', '暗网'), // 直播顶部广告 mine('mine', '我的'), // 我的页面广告 search('search', '搜索页'), // 搜索页广告 videoPlay('video_play', '视频播放页'), // 视频播放页广告 topicDetail('topic_detail', '专题详情页'), // 专题详情顶部广告 appList('app_list', '应用推荐'), // 应用等 postDetail('post_detail', '社区详情页'), // 应用等 imChatCenter('im_chat_center', '私信中心'), // IM 私信会话列表 imChatDetail('im_chat_detail', '私信详情'); // IM 私信聊天页 final String pagekey; final String pageName; const TrackEventPageType(this.pagekey, this.pageName); } //搜索枚举 enum TrackEventSearchType { video('video', 'video'), // 视频 comic('comic', '漫画'), // 漫画 acg('acg', 'acg'), // acg user('user', '用户'), // 用户 image('image', 'image'), // image post('post', '帖子'); // 帖子 final String clickTypeKey; final String clickTypeName; const TrackEventSearchType(this.clickTypeKey, this.clickTypeName); } /// 广告埋点类型(对应 payload 的 ad_type 字段) enum AdType { smallIcon('small_icon'), verticalIcon('vertical_icon'), moreIcon('more_icon'), bigBanner('big_banner'), smallBanner('small_banner'), banner('banner'), feed('feed'), splash('splash'), dialog('dialog'); /// 上报字符串值 final String value; const AdType(this.value); /// 字符串反查枚举,找不到返回 null static AdType? fromValue(String? value) => values.firstWhereOrNull((e) => e.value == value); } class AdExposureModel { final TrackEventPageType? pageType; //页面位置信息 AdType? adType; //广告类型,banner 类组件会就地改写(如 ..adType = AdType.bigBanner) final int? tabIndex; //tabIndex AdExposureModel({ this.pageType, this.adType, this.tabIndex, }); } /// 广告曝光统一管理(业务级) class AdExposureManager { static final Set _exposedKeys = {}; /// 返回 true = 允许曝光 static bool tryExpose(String key) => _exposedKeys.add(key); } //广告曝光,只记录一次曝光 mixin AdExposureMixin { void adExposure({ required AdsInfoModel adInfo, AdExposureModel? exposureModel, }) { final pageKey = exposureModel?.pageType?.pagekey ?? ''; final slotKey = adInfo.position?.toString() ?? ''; final adId = adInfo.id?.toString() ?? ''; // 业务唯一曝光 Key,最终裁判在这里 final exposureKey = '$pageKey|$slotKey|$adId|tab${exposureModel?.tabIndex ?? 0}'; if (!AdExposureManager.tryExpose(exposureKey)) return; AnalyticsSdk.instance.track(AdImpressionEvent( pageKey: pageKey, pageName: exposureModel?.pageType?.pageName ?? '', adSlotKey: slotKey, adId: adId, creativeId: adId, adType: exposureModel?.adType?.value ?? '', // 传广告类型字符串,不传默认空字符串 )); } } final adClickManager = AdClickManager(); //广告点击 class AdClickManager { // 单例 static final AdClickManager _ins = AdClickManager._(); factory AdClickManager() => _ins; AdClickManager._(); //广告点击事件 void adClicks({ required AdsInfoModel? adInfo, required Map payload, TrackEventPageType? page, }) { if (adInfo == null) return; try { final adId = adInfo.id?.toString() ?? ''; AnalyticsSdk.instance.track(AdClickEvent( pageKey: page?.pagekey ?? '', pageName: page?.pageName ?? '', adSlotKey: adInfo.position?.toString() ?? '', adId: adId, creativeId: adId, adType: payload['ad_type'] ?? '', )); } catch (e) { debugLog('adClicks', e.toString()); } } } final searchClickManager = SearchClickManager(); class SearchClickManager { // 单例 static final SearchClickManager _ins = SearchClickManager._(); factory SearchClickManager() => _ins; SearchClickManager._(); //搜索埋点 void searchKeyword({ required String? keyword, //搜索关键词 required int? count, //搜索结果数量 required String? traceId, //搜索引擎的trace_id。没有对接搜索推荐引擎的上报空字符串。 required String? searchId, //搜索引擎列表的video_id(视频),novel_id(小说),comic_id(漫画), 可视窗口有多个,使用英文逗号分隔。 }) { AnalyticsSdk.instance.track(KeywordSearchEvent( keyword: keyword ?? '', searchResultCount: count ?? 0, searchTraceId: traceId ?? '', // 未接搜索引擎时传空字符串 searchId: searchId ?? '', // 未接搜索引擎时传空字符串 )); } //搜索点击事件 void searchClicks({ required String? keyword, required TrackEventSearchType clickType, required String? id, required int? index, required String? traceId, //搜索引擎的trace_id。没有对接搜索推荐引擎的上报空字符串。 }) { AnalyticsSdk.instance.track(KeywordClickEvent( keyword: keyword ?? "", clickItemId: id ?? '', clickItemType: ClickItemTypeEnum(clickType.clickTypeKey, clickType.clickTypeName), clickPosition: (index ?? 0) + 1, searchTraceId: traceId ?? '', // 未接搜索引擎时传空字符串 )); } } class TrackVideoManager { //上次上报播放完成的秒级时间戳,用于防重 static int _lastComplete = 0; //埋点字段不收空值,统一兜默认值 static String _or(String? value, String fallback) => (value?.isEmpty ?? true) ? fallback : value!; //视频事件 static void send( VideoEventEnum event, VideoModel? model, VideoPlayerController? ctr, { bool isPreVideo = false, }) { if (isPreVideo) return; //预览视频的不上报 if (ctr == null) return; //播放器为空不上报 final duration = ctr.value.duration.inSeconds; if (duration == 0) return; //时长为0不上报,避免除0异常 final position = ctr.value.position.inSeconds; final progress = position * 100 ~/ duration; if (event == VideoEventEnum.VIDEO_COMPLETE) { if (progress < 90) { //播放进度不足90%, 转为播放事件上报 event = VideoEventEnum.VIDEO_PLAY; } else { // 自动播放完,业务监听会多次处理, 视频播放完成事件防重 final now = DateTime.now().millisecondsSinceEpoch ~/ 1000; if (now - _lastComplete < 2) { debugLog("video complete 重复处理"); return; } _lastComplete = now; } } //新增SDK埋点 AnalyticsSdk.instance.track(VideoEvent( videoId: model?.id ?? '', videoTitle: model?.title ?? '', videoTypeId: _or(model?.videoTypeId, 'default'), videoTypeName: _or(model?.videoTypeName, '默认分类'), videoTagKey: _or(model?.tags?.map((e) => e.id).join(','), 'default'), videoTagName: _or(model?.tags?.map((e) => e.name).join(','), '默认标签'), videoDuration: duration, playDuration: position, playProgress: progress, videoBehavior: event, videoContentType: model?.contentType == 'video' ? VideoContentTypeEnum.video : VideoContentTypeEnum.shortVideo, recommendTraceId: '', // 未接推荐引擎传 '' mediaId: model?.lsjId ?? '', )); } //漫画行为 展示, 下一页, 上一页,读完 static void comicEvent( ReadBehaviorEnum behavior, //行为 { CartoonMediaInfo? model, int? readProgress = 0, //章节百分比 int? pageNo = 1, MediaContent? mediaContent, }) { AnalyticsSdk.instance.track(ComicEvent( comicId: model?.id ?? '', comicTitle: model?.title ?? '', comicTypeId: _or(model?.mId, 'default'), comicTypeName: _or(model?.moduleName, '默认分类'), comicTagKey: _or(model?.tagIds, 'default'), comicTagName: _or(model?.tagNames, '默认标签'), readProgress: readProgress ?? 0, pageNo: pageNo ?? 0, comicBehavior: behavior, mediaId: model?.lsjId ?? '', chapterId: _or(mediaContent?.id, '1'), chapterName: _or(mediaContent?.name, '第一章'), )); } //小说行为 展示, 下一页, 上一页,读完 static void novelEvent( ReadBehaviorEnum behavior, { CartoonMediaInfo? model, //小说模型 int? readProgress = 0, //章节百分比 int? pageNo = 1, String? chapterId, String? chapterName, }) { AnalyticsSdk.instance.track(NovelEvent( novelId: model?.id ?? '', novelTitle: model?.title ?? '', novelTypeId: _or(model?.mId, 'default'), novelTypeName: _or(model?.moduleName, '默认分类'), novelTagKey: _or(model?.tagIds, 'default'), novelTagName: _or(model?.tagNames, '默认标签'), readProgress: readProgress ?? 0, pageNo: pageNo ?? 0, novelBehavior: behavior, mediaId: model?.lsjId ?? '', chapterId: _or(chapterId, '1'), chapterName: _or(chapterName, '第一章'), )); } } /// 页面 pageKey -> 中文名映射,app 启动时注册一次(main.dart) /// key 就是 Get.to 自动生成的路由名(去掉斜杠后的页面类名),改类名要同步改这里 void addMappings() { PageNameMapper.addMappings({ 'AiHomePage': 'AI首页', 'AiSquarePage': 'AI广场', 'CartoonDetailPage': '漫画详情页', 'CartoonReadPage': '漫画阅读页', 'CartoonRecommendPage': '漫画推荐页', 'CartoonSectionListPage': '漫画专题更多页', 'CartoonSectionSubPage': '漫画专题列表页', 'CartoonSubGridPage': '漫画海角页', 'CartoonSubModulePage': '漫画亚模块页', 'NovelDetailPage': '小说详情页', 'TextNovelReadPage': '文字小说阅读页', 'VoiceNovelReadPage': '有声小说阅读页', 'CommunityModulePage': '社区模块页', 'CommunityTabPage': '社区Tab页', 'SourceSubModulePage': '社区资源亚模块页', 'CommunityMainPage': '社区主页', 'TagDetailSubView': '标签详情页', 'CommunityTagDetailPage': '社区标签详情页', 'CommunityImagePage': '社区图片详情页', 'PostDetailPage': '社区帖子详情页', 'ChoseTopicPage': '发布选择话题页', 'PublishPage': '帖子发布页', 'PublishRulePage': '发布规则页', 'PicsDetailPage': '色图详情页', 'HomeTabSectionPage': '首页Tab专题页', 'HomeTabSortPage': '首页Tab分类页', 'SearchACGResultPage': '搜索ACG结果页', 'SearchResultMainPage': '搜索结果主页面', 'SearchVideoResultPage': '搜索视频结果页', 'SectionAllPage': '原创达人', 'SpecialTopicsDetailPage': '视频专题详情页', 'CartoonTagPage': '漫画标签页', 'VideoTagPage': '视频标签页', 'DramaTagPage': '短剧标签页', //DramaTagDetailPage.routeName,改名要同步 'DarkHomePage': '暗网首页', 'HomeMainPage': '视频主页', 'LiveDetailPage': '直播详情页', 'LiveMainPage': '直播主页', 'LiveSubPage': '直播子页面', 'MainPage': '主页面', 'AiPaintPage': 'AI绘画页', 'AiItvPage': 'AI图生视频页', 'AINovelSubPage': 'AI小说首页', 'AiNovelDetailPage': 'AI小说详情页', 'AIChangeFacePage': 'AI换脸页', 'AIFaceChangeSubPage': 'AI换脸子页', 'AIStripSubPage': 'AI脱衣首页', 'AIGrilFriendPage': 'AI女友页', 'AiH5Page': 'AI H5页', 'AIMainRecordPage': 'AI生成记录主页', 'AiMainItvPage': 'AI图生视频主页', 'AIModDetailPage': 'AI换脸模版页', 'AiRecordPage': 'AI生成记录子页', 'AdvancePage': '高级设置页', 'ExchangeVipPage': '兑换会员页', 'IntegralExchangePage': '积分兑换页', 'OnlinePayPage': '在线支付页', 'MineCollectPage': '我的收藏页', 'MineBuyPage': '我的购买页', 'MineHistoryPage': '历史记录页', 'MineLikePage': '我的喜欢页', 'MineAccountIdentityPage': '账号凭证页', 'RecordsPage': '明细综合页', 'WithdrawalPage': '提现页', 'MessageCenterPage': '消息中心页', 'ChatDetailPage': '私信详情页', 'MineDrivePage': '加群开车页', 'MineFollowSubPage': '关注子页面', 'MineFollowingPage': '我的关注页', 'MinePublishPostPage': '创作中心', 'BankCardHomePage': '银行卡管理页', 'BindPhonePage': '绑定手机号页', 'InviteBindPage': '绑定邀请码', 'MineExchangeCodePage': '兑换码页', 'MineFindAccountPage': '找回账号', 'MinePasswordPage': '手势密码', 'MineScanLoginPage': '扫码登录', 'MineSettingPage': '设置中心', 'MineSettingProfilePage': '设置资料页', 'SettingProfilePage': '编辑资料', 'SettingAvatarPage': '设置头像', 'SettingNickNamePage': '设置昵称', 'MineSharePage': '分享邀请', 'MineShareRecordPage': '邀请记录', 'VideoCachePage': '下载缓存', 'MineChargeCoinPage': '金币充值', 'MineChargeVipPage': '会员充值', 'MineFeedbackPage': '意见反馈', 'MineHelpPage': '帮助反馈', 'MineQAPage': '常见问题', 'LouFengAdPage': '应用推荐', 'MineIncomePage': '分享邀请', 'RecommendAppPage': '推荐应用页', 'MoreFreeVideoPage': '更多免费视频', 'ShareDataListPage': '推广数据列表', 'ShareDetailsPage': '推广数据', 'WelfareHomePage': '应用推荐首页', 'SignDailyPage': '每日签到', 'HomeMinePage': '我的', 'PreSalePage': '预售详情页', 'ShortVideoMainPage': '抖音首页', 'ShortVideoPage': '抖音', 'SplashPage': '启动页', 'RankMainPage': '排行榜主页', 'RankSubPage': '排行榜子页', 'UserCenterPage': '用户首页', 'GroupChatPage': '群聊页', 'GroupChatDetailPage': '群聊详情页', 'SimpleVideoPlayerPage': '简单视频播放页', 'VideoFullPage': '全屏视频播放', 'VideoPage': '视频播放页', 'H5Page': 'H5页面', 'SearchMainPage': '搜索页', 'VideoAllTypePage': '视频片库', 'AcgGameRecommendPage': 'ACG游戏推荐页', 'AcgGameDetailPage': 'ACG游戏详情页', 'CartoonSubDetailPage': '漫画子详情页', 'MessageCenterSubPage': '消息中心子页', 'MinePublishSubPage': '创作中心子页', 'Home': '主页Tab', 'Community': '社区Tab', 'ShortVideo': '抖音Tab', 'AI': 'AI Tab', 'Mine': '我的Tab', 'Pic': ' 色图Tab', 'Novel': '小说Tab', }); }