1+ package com.muedsa.tvbox.danmaku.tencent
2+
3+ import com.kuaishou.akdanmaku.data.DanmakuItemData
4+ import com.muedsa.tvbox.danmaku.DanmakuProvider
5+ import com.muedsa.tvbox.model.DanmakuEpisode
6+ import com.muedsa.tvbox.model.DanmakuMedia
7+ import com.muedsa.tvbox.model.tencent.TencentVideoBarrageContentStyle
8+ import com.muedsa.tvbox.model.tencent.TencentVideoInfo
9+ import com.muedsa.tvbox.model.tencent.TencentVideoPageDataReq
10+ import com.muedsa.tvbox.model.tencent.TencentVideoPageParams
11+ import com.muedsa.tvbox.model.tencent.TencentVideoSearchReq
12+ import com.muedsa.tvbox.tool.LenientJson
13+ import kotlinx.coroutines.delay
14+ import timber.log.Timber
15+ import kotlin.time.Duration.Companion.milliseconds
16+ import kotlin.uuid.ExperimentalUuidApi
17+ import kotlin.uuid.Uuid
18+
19+ class TencentDanmakuProvider (
20+ private val tencentVideoApiService : TencentVideoApiService ,
21+ ) : DanmakuProvider {
22+
23+ override val name: String = " 腾讯"
24+
25+ @OptIn(ExperimentalUuidApi ::class )
26+ override suspend fun searchMedia (keyword : String ): List <DanmakuMedia > {
27+ val req = TencentVideoSearchReq (
28+ query = keyword,
29+ filterValue = " firstTabid=150" ,
30+ uuid = Uuid .random().toString().uppercase()
31+ )
32+ val resp = tencentVideoApiService.search(req)
33+ return resp.data?.normalList?.itemList?.mapNotNull { it.videoInfo }
34+ ?.mapNotNull {
35+ val siteInfo = it.playSites.find {
36+ it.enName == " qq" && it.playSourceType == 1 && ! it.episodeInfoList.isEmpty()
37+ }
38+ if (siteInfo != null ) {
39+ val firstEpisode = siteInfo.episodeInfoList[0 ]
40+ DanmakuMedia (
41+ provider = name,
42+ mediaId = firstEpisode.cid,
43+ mediaName = " ${it.titleWithoutEm} (${it.typeName} )" ,
44+ publishDate = " ${it.year} " ,
45+ episodes = emptyList(),
46+ extendData = LenientJson .encodeToString(it)
47+ )
48+ } else null
49+ } ? : emptyList()
50+
51+ }
52+
53+ override suspend fun getMediaEpisodes (media : DanmakuMedia ): DanmakuMedia ? {
54+ val videoInfo = LenientJson .decodeFromString<TencentVideoInfo >(media.extendData!! )
55+ val firstEpisode = videoInfo.playSites.find {
56+ it.enName == " qq" && it.playSourceType == 1 && ! it.episodeInfoList.isEmpty()
57+ }!! .episodeInfoList[0 ]
58+ val req = TencentVideoPageDataReq (
59+ pageParams = TencentVideoPageParams (
60+ cid = media.mediaId,
61+ vid = firstEpisode.id,
62+ pageId = " vsite_episode_list"
63+ )
64+ )
65+ val resp = tencentVideoApiService.getPageData(req)
66+ val episodes =
67+ resp.data?.moduleListDatas?.get(0 )?.moduleDatas?.get(0 )?.itemDataLists?.itemDatas?.filter {
68+ it.itemType == " 1"
69+ }
70+ if (episodes.isNullOrEmpty()) return null
71+
72+ return DanmakuMedia (
73+ provider = name,
74+ mediaId = media.mediaId,
75+ mediaName = media.mediaName,
76+ publishDate = media.publishDate,
77+ episodes = episodes.mapNotNull {
78+ val cid = it.itemParams?.get(" cid" )
79+ val vid = it.itemParams?.get(" vid" )
80+ val playTitle = it.itemParams?.get(" play_title" )
81+ val duration = it.itemParams?.get(" duration" )
82+ if (cid != null && vid != null && playTitle != null && duration != null ) {
83+ DanmakuEpisode (
84+ provider = name,
85+ mediaId = cid,
86+ mediaName = media.mediaName,
87+ episodeId = vid,
88+ episodeName = playTitle,
89+ extendData = duration,
90+ )
91+ } else null
92+ },
93+ extendData = media.extendData
94+ )
95+ }
96+
97+ @OptIn(ExperimentalStdlibApi ::class )
98+ override suspend fun getEpisodeDanmakuList (episode : DanmakuEpisode ): List <DanmakuItemData > {
99+ val duration = episode.extendData!! .toDouble() * 1000
100+ val list = mutableListOf<DanmakuItemData >()
101+ var startMs = 0
102+ var endMs = ONCE_OFFSET
103+ while (startMs < duration) {
104+ try {
105+ val resp = tencentVideoApiService.barrageSegment(
106+ vid = episode.episodeId,
107+ startMs = startMs,
108+ endMs = endMs,
109+ )
110+ resp.barrageList.forEach {
111+ var color = 0xFF_FF_FF
112+ if (! it.contentStyle.isNullOrBlank()) {
113+ LenientJson .decodeFromString<TencentVideoBarrageContentStyle >(it.contentStyle)
114+ .color?.let {
115+ color = it.hexToInt()
116+ }
117+ }
118+ list.add(
119+ DanmakuItemData (
120+ danmakuId = it.id.toLong(),
121+ position = it.timeOffset.toLong(),
122+ content = it.content,
123+ mode = DanmakuItemData .DANMAKU_MODE_ROLLING ,
124+ textSize = 25 ,
125+ textColor = color,
126+ score = 9 ,
127+ danmakuStyle = DanmakuItemData .DANMAKU_STYLE_NONE
128+ )
129+ )
130+ }
131+ } catch (throwable: Throwable ) {
132+ Timber .e(throwable)
133+ }
134+ startMs = startMs + ONCE_OFFSET
135+ endMs = startMs + ONCE_OFFSET
136+ delay(100 .milliseconds)
137+ }
138+ return list
139+ }
140+
141+ companion object {
142+ const val ONCE_OFFSET = 2 * 60 * 1000
143+ }
144+ }
0 commit comments