Compare commits

...
11 Commits
Author SHA1 Message Date
Li ZongYing 230e4fea2b action sign 2024-01-10 23:14:49 +08:00
Li ZongYing 0d26912321 action sign 2024-01-10 23:12:35 +08:00
Li ZongYing af9ff5c8dd action sign 2024-01-10 22:39:49 +08:00
Li ZongYing f0f43e0fda dns cache 2024-01-10 22:11:13 +08:00
Li ZongYing 61f62121f2 menu auto close 2024-01-10 18:01:25 +08:00
Li ZongYing d0c7627e35 fix 13 2024-01-08 22:40:56 +08:00
Li ZongYing c10d8b688f add retry 2024-01-08 20:07:53 +08:00
Li ZongYing 0bfac7d679 cctv8k 2024-01-07 21:37:51 +08:00
Li ZongYing c49246f5c7 cctv8k 2024-01-07 21:36:08 +08:00
Li ZongYing 930c7957ee get token on switch 2024-01-05 18:24:44 +08:00
Li ZongYing 57d42bc917 support vip 2024-01-05 14:53:54 +08:00
27 changed files with 316 additions and 214 deletions
+3
View File
@@ -33,6 +33,9 @@ jobs:
signingKeyBase64: ${{ secrets.KEYSTORE }}
keyStorePassword: ${{ secrets.KEYSTORE_PASSWORD }}
keyPassword: ${{ secrets.ALIAS_PASSWORD }}
# env:
# override default build-tools version (29.0.3) -- optional
# BUILD_TOOLS_VERSION: "30.0.2"
- name: Create Release
id: create_release
+10
View File
@@ -14,6 +14,16 @@
## 更新日志
### v1.3.3
* 部分错误会提示用户
* 菜单3秒钟后自动关闭
### v1.3.2
* 增加重试,减少因网络问题导致的播放失败
* 优化横幅banner
### v1.2.6
* 支持安卓4.2
+1
View File
@@ -6,6 +6,7 @@ plugins {
android {
namespace 'com.lizongying.mytv'
compileSdk 33
buildToolsVersion "29.0.3"
viewBinding {
enabled = true
+1 -2
View File
@@ -12,12 +12,11 @@
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:usesCleartextTraffic="true"
android:theme="@style/Theme.MyTV">
<activity
android:keepScreenOn="true"
android:name=".MainActivity"
android:banner="@drawable/logo"
android:banner="@drawable/banner"
android:exported="true"
android:icon="@drawable/logo"
android:logo="@drawable/logo"
Binary file not shown.
Binary file not shown.
@@ -32,10 +32,17 @@ class CardPresenter(
cardView.tag = tvViewModel.videoUrl.value
if (tvViewModel.logo.value != null) {
Glide.with(viewHolder.view.context)
.load(tvViewModel.logo.value)
.centerInside()
.into(cardView.mainImageView)
if (tvViewModel.title.value == "CCTV8K 超高清") {
Glide.with(viewHolder.view.context)
.load(R.drawable.cctv8k)
.centerInside()
.into(cardView.mainImageView)
} else {
Glide.with(viewHolder.view.context)
.load(tvViewModel.logo.value)
.centerInside()
.into(cardView.mainImageView)
}
cardView.setBackgroundColor(Color.WHITE)
cardView.setMainImageScaleType(ImageView.ScaleType.CENTER_INSIDE)
@@ -36,9 +36,16 @@ class InfoFragment : Fragment() {
fun show(tvViewModel: TVViewModel) {
binding.textView.text = tvViewModel.title.value
Glide.with(this)
.load(tvViewModel.logo.value)
.into(binding.infoLogo)
if (tvViewModel.title.value == "CCTV8K 超高清") {
Glide.with(this)
.load(R.drawable.cctv8k)
.into(binding.infoLogo)
} else {
Glide.with(this)
.load(tvViewModel.logo.value)
.into(binding.infoLogo)
}
val program = tvViewModel.getProgramOne()
if (program != null) {
binding.infoDesc.text = program.name
@@ -35,6 +35,9 @@ class MainActivity : FragmentActivity() {
private lateinit var gestureDetector: GestureDetector
private val handler = Handler()
private val delay: Long = 3000
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
@@ -86,6 +89,7 @@ class MainActivity : FragmentActivity() {
if (mainFragment.isHidden) {
transaction.show(mainFragment)
keepRunnable()
} else {
transaction.hide(mainFragment)
}
@@ -93,6 +97,15 @@ class MainActivity : FragmentActivity() {
transaction.commit()
}
fun keepRunnable() {
handler.removeCallbacks(hideRunnable)
handler.postDelayed(hideRunnable, delay)
}
private val hideRunnable = Runnable {
supportFragmentManager.beginTransaction().hide(mainFragment).commit()
}
private fun mainFragmentIsHidden(): Boolean {
return mainFragment.isHidden
}
@@ -6,7 +6,7 @@ import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.util.Log
import android.view.View
import android.widget.Toast
import androidx.leanback.app.BrowseSupportFragment
import androidx.leanback.widget.ArrayObjectAdapter
import androidx.leanback.widget.HeaderItem
@@ -29,7 +29,7 @@ import kotlinx.coroutines.launch
class MainFragment : BrowseSupportFragment() {
var itemPosition: Int = 0
private var request: Request? = null
private var request: Request = Request()
private var rowsAdapter: ArrayObjectAdapter? = null
@@ -52,9 +52,9 @@ class MainFragment : BrowseSupportFragment() {
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
sharedPref = activity?.getPreferences(Context.MODE_PRIVATE)
activity?.let { request.initYSP(it) }
request = activity?.let { Request(it) }
sharedPref = activity?.getPreferences(Context.MODE_PRIVATE)
loadRows()
@@ -64,6 +64,13 @@ class MainFragment : BrowseSupportFragment() {
handler.post(mUpdateProgramRunnable)
tvListViewModel.getTVListViewModel().value?.forEach { tvViewModel ->
tvViewModel.errInfo.observe(viewLifecycleOwner) { _ ->
if (tvViewModel.errInfo.value != null
&& tvViewModel.id.value == itemPosition
) {
Toast.makeText(context, tvViewModel.errInfo.value, Toast.LENGTH_SHORT).show()
}
}
tvViewModel.ready.observe(viewLifecycleOwner) { _ ->
// not first time && channel not change
@@ -79,10 +86,10 @@ class MainFragment : BrowseSupportFragment() {
if (tvViewModel.change.value != null) {
val title = tvViewModel.title.value
Log.i(TAG, "switch $title")
if (tvViewModel.pid.value != null) {
Log.i(TAG, "request $title")
if (tvViewModel.pid.value != "") {
Log.i(TAG, "request $title ${tvViewModel.pid.value}")
lifecycleScope.launch(Dispatchers.IO) {
tvViewModel.let { request?.fetchData(it) }
tvViewModel.let { request.fetchData(it) }
}
(activity as? MainActivity)?.showInfoFragment(tvViewModel)
setSelectedPosition(
@@ -107,10 +114,6 @@ class MainFragment : BrowseSupportFragment() {
fragmentReady()
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
}
override fun onDestroy() {
super.onDestroy()
handler.removeCallbacks(mUpdateProgramRunnable)
@@ -125,11 +128,11 @@ class MainFragment : BrowseSupportFragment() {
if (timestamp - tvViewModel.programUpdateTime > 60) {
if (tvViewModel.program.value!!.isEmpty()) {
tvViewModel.programUpdateTime = timestamp
request?.fetchProgram(tvViewModel)
request.fetchProgram(tvViewModel)
} else {
if (timestamp - tvViewModel.program.value!!.last().et < 600) {
tvViewModel.programUpdateTime = timestamp
request?.fetchProgram(tvViewModel)
request.fetchProgram(tvViewModel)
}
}
}
@@ -214,10 +217,7 @@ class MainFragment : BrowseSupportFragment() {
ready++
Log.i(TAG, "ready $ready")
if (ready == 3) {
// request?.fetchPage()
// tvListViewModel.getTVViewModel(0)?.let { request?.fetchProgram(it) }
// request.fetchPage()
val tvViewModel = tvListViewModel.getTVViewModel(itemPosition)
tvViewModel?.changed()
@@ -281,10 +281,6 @@ class MainFragment : BrowseSupportFragment() {
}
}
fun tvViewModel(): TVViewModel? {
return tvListViewModel.getTVViewModel(itemPosition)
}
private fun setupEventListeners() {
onItemViewClickedListener = ItemViewClickedListener()
onItemViewSelectedListener = ItemViewSelectedListener()
@@ -317,6 +313,7 @@ class MainFragment : BrowseSupportFragment() {
) {
if (item is TVViewModel) {
tvListViewModel.setItemPositionCurrent(item.id.value!!)
(activity as MainActivity).keepRunnable()
}
}
}
@@ -14,9 +14,6 @@ import androidx.media3.common.Player
import androidx.media3.common.VideoSize
import androidx.media3.common.util.UnstableApi
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.exoplayer.analytics.AnalyticsListener
import androidx.media3.exoplayer.source.LoadEventInfo
import androidx.media3.exoplayer.source.MediaLoadData
import androidx.media3.ui.PlayerView
import com.lizongying.mytv.databinding.PlayerBinding
import com.lizongying.mytv.models.TVViewModel
@@ -80,71 +77,8 @@ class PlayerFragment : Fragment() {
val videoUrlCurrent =
tvViewModel.videoIndex.value?.let { tvViewModel.videoUrl.value?.get(it) }
playerView?.player?.run {
val mediaItem = MediaItem.Builder()
tvViewModel.id.value?.let { mediaItem.setMediaId(it.toString()) }
videoUrlCurrent?.let { mediaItem.setUri(it) }
setMediaItem(mediaItem.build())
videoUrlCurrent?.let { setMediaItem(MediaItem.fromUri(it)) }
prepare()
// val httpDataSource = DefaultHttpDataSource.Factory()
// val hls = HlsMediaSource.Factory(httpDataSource).createMediaSource(
// MediaItem.fromUri(
// Uri.parse(videoUrlCurrent)
// )
// )
// val analyticsListener: AnalyticsListener= MyAnalyticsListener()
// val exoPlayer = playerView?.player as ExoPlayer
// exoPlayer.addAnalyticsListener(analyticsListener)
// exoPlayer.setMediaSource(hls)
// exoPlayer.playWhenReady = true
//
//
// exoPlayer.let {
// val parameters =
// TrackSelectionParameters.Builder().setPreferredAudioMimeType("application/id3").build()
// // 更新轨道选择器参数
// exoPlayer.trackSelector?.parameters = parameters
// Log.i(TAG, "parameters $parameters")
// }
//
// // 获取当前轨道组
//
// playerView?.player = exoPlayer
// playerView?.player?.prepare()
//
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
// val codecList = MediaCodecList(MediaCodecList.ALL_CODECS)
// val codecInfos = codecList.codecInfos
//
// for (codecInfo in codecInfos) {
//
// val supportedTypes = codecInfo.supportedTypes
// for (type in supportedTypes) {
// Log.d("supportedTypes", "$type")
//// if (type.equals(androidx.media3.exoplayer.mediacode, ignoreCase = true)) {
//// Log.d("AudioCodecChecker", "Device supports MPEG-L2")
//// return
//// }
// }
// }
//
// Log.d("AudioCodecChecker", "Device does not support MPEG-L2")
// }
}
}
@UnstableApi
class MyAnalyticsListener : AnalyticsListener {
override fun onLoadStarted(
eventTime: AnalyticsListener.EventTime,
loadEventInfo: LoadEventInfo,
mediaLoadData: MediaLoadData
) {
super.onLoadStarted(eventTime, loadEventInfo, mediaLoadData)
// Log.i(TAG, "loadEventInfo.uri ${loadEventInfo.uri} ${mediaLoadData.trackFormat.toString()}")
}
}
@@ -7,14 +7,14 @@ import android.util.Base64
import android.util.Log
import com.lizongying.mytv.Utils.getDateFormat
import com.lizongying.mytv.api.ApiClient
import com.lizongying.mytv.api.BtraceClient
import com.lizongying.mytv.api.Info
import com.lizongying.mytv.api.LiveInfo
import com.lizongying.mytv.api.LiveInfoRequest
import com.lizongying.mytv.api.ProtoClient
import com.lizongying.mytv.api.YSP
import com.lizongying.mytv.api.YSPApiService
import com.lizongying.mytv.api.YSPBtraceService
import com.lizongying.mytv.api.YSPProtoService
import com.lizongying.mytv.api.YSPTokenService
import com.lizongying.mytv.models.TVViewModel
import com.lizongying.mytv.proto.Ysp.cn.yangshipin.oms.common.proto.pageModel
import com.lizongying.mytv.proto.Ysp.cn.yangshipin.omstv.common.proto.epgProgramModel
@@ -26,11 +26,12 @@ import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec
class Request(var context: Context) {
class Request {
private var yspTokenService: YSPTokenService = ApiClient().yspTokenService
private var yspApiService: YSPApiService = ApiClient().yspApiService
private var yspBtraceService: YSPBtraceService = ApiClient().yspBtraceService
private var yspProtoService: YSPProtoService = ApiClient().yspProtoService
private var ysp: YSP? = null
private var yspApiService: YSPApiService? = null
private var yspBtraceService: YSPBtraceService? = null
private var yspProtoService: YSPProtoService? = null
// TODO onDestroy
private val handler = Handler(Looper.getMainLooper())
@@ -59,7 +60,6 @@ class Request(var context: Context) {
"CGTN阿拉伯语频道" to "CGTN 阿拉伯语频道",
"CGTN西班牙语频道" to "CGTN 西班牙语频道",
"CGTN外语纪录频道" to "CGTN 纪录频道",
"东方卫视" to "东方卫视",
"湖南卫视" to "湖南卫视",
"湖北卫视" to "湖北卫视",
@@ -83,26 +83,22 @@ class Request(var context: Context) {
"海南卫视" to "海南卫视",
)
init {
if (context is MainActivity) {
ysp = YSP(context)
}
yspApiService = ApiClient().yspApiService
yspBtraceService = BtraceClient().yspBtraceService
yspProtoService = ProtoClient().yspProtoService
fun initYSP(context: Context) {
ysp = YSP(context)
}
fun fetchData(tvModel: TVViewModel) {
fun fetchVideo(tvModel: TVViewModel, cookie: String) {
if (::myRunnable.isInitialized) {
handler.removeCallbacks(myRunnable)
}
tvModel.seq = 0
val data = ysp?.switch(tvModel)
val title = tvModel.title.value
tvModel.seq = 0
val data = ysp?.switch(tvModel)
val request = data?.let { LiveInfoRequest(it) }
request?.let { yspApiService?.getLiveInfo(it) }
request?.let { yspApiService.getLiveInfo(cookie, it) }
?.enqueue(object : Callback<LiveInfo> {
override fun onResponse(call: Call<LiveInfo>, response: Response<LiveInfo>) {
if (response.isSuccessful) {
@@ -127,30 +123,86 @@ class Request(var context: Context) {
Log.i(TAG, "$title url $url")
tvModel.addVideoUrl(url)
tvModel.allReady()
tvModel.retryTimes = 0
myRunnable = MyRunnable(tvModel)
handler.post(myRunnable)
} else {
Log.e(TAG, "$title key error")
tvModel.firstSource()
if (tvModel.retryTimes < tvModel.retryMaxTimes) {
tvModel.retryTimes++
fetchVideo(tvModel, cookie)
}
}
} else {
Log.e(TAG, "$title url error $request")
tvModel.firstSource()
if (liveInfo?.data?.errinfo != null && liveInfo.data.errinfo == "应版权方要求,暂停提供直播信号,请点击观看其他精彩节目") {
Log.e(TAG, "$title error ${liveInfo.data.errinfo}")
tvModel.setErrInfo(liveInfo.data.errinfo)
} else {
Log.e(TAG, "$title url error $request $liveInfo")
if (tvModel.retryTimes < tvModel.retryMaxTimes) {
tvModel.retryTimes++
fetchVideo(tvModel, cookie)
}
}
}
} else {
Log.e(TAG, "$title status error")
tvModel.firstSource()
if (tvModel.retryTimes < tvModel.retryMaxTimes) {
tvModel.retryTimes++
fetchVideo(tvModel, cookie)
}
}
}
override fun onFailure(call: Call<LiveInfo>, t: Throwable) {
Log.e(TAG, "$title request error")
tvModel.firstSource()
if (tvModel.retryTimes < tvModel.retryMaxTimes) {
tvModel.retryTimes++
fetchVideo(tvModel, cookie)
}
}
})
}
fun fetchVideo(tvModel: TVViewModel) {
yspTokenService.getInfo()
.enqueue(object : Callback<Info> {
override fun onResponse(call: Call<Info>, response: Response<Info>) {
if (response.isSuccessful) {
val info = response.body()
val token = info?.data?.token
Log.i(TAG, "info success $token")
val cookie =
"guid=1; vplatform=109; yspopenid=vu0-8lgGV2LW9QjDeuBFsX8yMnzs37Q3_HZF6XyVDpGR_I; vusession=$token"
fetchVideo(tvModel, cookie)
} else {
Log.e(TAG, "info status error")
if (tvModel.retryTimes < tvModel.retryMaxTimes) {
tvModel.retryTimes++
fetchVideo(tvModel)
}
}
}
override fun onFailure(call: Call<Info>, t: Throwable) {
Log.e(TAG, "info request error $t")
if (tvModel.retryTimes < tvModel.retryMaxTimes) {
tvModel.retryTimes++
fetchVideo(tvModel)
}
}
})
}
fun fetchData(tvModel: TVViewModel) {
if (tvModel.needToken) {
fetchVideo(tvModel)
} else {
val cookie = "guid=1; vplatform=109"
fetchVideo(tvModel, cookie)
}
}
inner class MyRunnable(private val tvModel: TVViewModel) : Runnable {
override fun run() {
fetchBtrace(tvModel)
@@ -164,7 +216,7 @@ class Request(var context: Context) {
val guid = ysp?.getGuid()!!
val pid = tvModel.pid.value!!
val sid = tvModel.sid.value!!
yspBtraceService?.kvcollect(
yspBtraceService.kvcollect(
c_timestamp = ysp?.generateGuid()!!,
guid = guid,
c_guid = guid,
@@ -177,13 +229,12 @@ class Request(var context: Context) {
ftime = getDateFormat("yyyy-MM-dd HH:mm:ss"),
seq = tvModel.seq.toString(),
)
?.enqueue(object : Callback<Void> {
.enqueue(object : Callback<Void> {
override fun onResponse(call: Call<Void>, response: Response<Void>) {
if (response.isSuccessful) {
// Log.d(TAG, "$title kvcollect success")
// Log.d(TAG, "$title kvcollect success")
} else {
Log.e(TAG, "$title kvcollect status error")
tvModel.firstSource()
}
}
@@ -195,7 +246,7 @@ class Request(var context: Context) {
}
fun fetchPage() {
yspProtoService?.getPage()?.enqueue(object : Callback<pageModel.Response> {
yspProtoService.getPage().enqueue(object : Callback<pageModel.Response> {
override fun onResponse(
call: Call<pageModel.Response>,
response: Response<pageModel.Response>
@@ -210,7 +261,7 @@ class Request(var context: Context) {
}
Log.i(
TAG,
"${item.channelName} ,${item.tvLogo},${item.pid},${item.streamId}"
"${item.channelName},${item.pid},${item.streamId}"
)
var channelType = "央视频道"
if (item?.channelType === "weishi") {
@@ -239,8 +290,8 @@ class Request(var context: Context) {
fun fetchProgram(tvViewModel: TVViewModel) {
val title = tvViewModel.title.value
yspProtoService?.getProgram(tvViewModel.programId.value!!, getDateFormat("yyyyMMdd"))
?.enqueue(object : Callback<epgProgramModel.Response> {
yspProtoService.getProgram(tvViewModel.programId.value!!, getDateFormat("yyyyMMdd"))
.enqueue(object : Callback<epgProgramModel.Response> {
override fun onResponse(
call: Call<epgProgramModel.Response>,
response: Response<epgProgramModel.Response>
@@ -25,8 +25,4 @@ data class TV(
", programId='" + programId + '\'' +
'}'
}
companion object {
internal const val serialVersionUID = 727566175075960653L
}
}
+37 -34
View File
@@ -7,6 +7,7 @@ object TVList {
private var mappingLogo = mapOf(
"CCTV4K 超高清" to "https://resources.yangshipin.cn/assets/oms/image/202306/3e9d06fd7244d950df5838750f1c6ac3456e172b51caca2c16d2282125b111e8.png?imageMogr2/format/webp",
"CCTV8K 超高清" to "https://static.wikia.nocookie.net/logos/images/6/69/CCTV8K.png/revision/latest/scale-to-width-down/120?cb=20230104110835&path-prefix=vi",
"CCTV1 综合" to "https://resources.yangshipin.cn/assets/oms/image/202306/d57905b93540bd15f0c48230dbbbff7ee0d645ff539e38866e2d15c8b9f7dfcd.png?imageMogr2/format/webp",
"CCTV2 财经" to "https://resources.yangshipin.cn/assets/oms/image/202306/20115388de0207131af17eac86c33049b95d69eaff064e55653a1b941810a006.png?imageMogr2/format/webp",
"CCTV3 综艺" to "https://resources.yangshipin.cn/assets/oms/image/202306/7b7a65c712450da3deb6ca66fbacf4f9aee00d3f20bd80eafb5ada01ec63eb3a.png?imageMogr2/format/webp",
@@ -31,10 +32,10 @@ object TVList {
"CGTN 阿拉伯语频道" to "https://resources.yangshipin.cn/assets/oms/image/202306/2e44e2aa3e7a1cedf07fd0ae59fe69e86a60a2632660a006e3e9e7397b2d107e.png?imageMogr2/format/webp",
"CGTN 西班牙语频道" to "https://resources.yangshipin.cn/assets/oms/image/202309/7c337e3dbe64402ec7e4678a619a4a6d95144e42f35161181ff78e143b7cf67a.png?imageMogr2/format/webp",
"CGTN 纪录频道" to "https://resources.yangshipin.cn/assets/oms/image/202309/74d3ac436a7e374879578de1d87a941fbf566d39d5632b027c5097891ed32bd5.png?imageMogr2/format/webp",
"风云剧场" to "https://resources.yangshipin.cn/assets/oms/image/202306/4d549e53e6d0f632d5a633d1945280797b153e588f919221a07faa869812cc89.png?imageMogr2/format/webp",
"第一剧场" to "https://resources.yangshipin.cn/assets/oms/image/202306/a556bd7d93ce65e18f243a8892b5604f4faa994a4897315914216a710a706208.png?imageMogr2/format/webp",
"怀旧剧场" to "https://resources.yangshipin.cn/assets/oms/image/202306/5661bd04fecdb6e899f801147a22ab5d3a475bf2b62e30aec2c0023190ebc9b1.png?imageMogr2/format/webp",
"世界地理" to "https://resources.yangshipin.cn/assets/oms/image/202306/bb3c6c9e145d698137f5bb64a582021a01b51344b929003630eb769ea65832a9.png?imageMogr2/format/webp",
"风云音乐" to "https://resources.yangshipin.cn/assets/oms/image/202306/bbf1d024c5228b8dd128b0e3cb1717d173fab4ee84c3a4c8a57b1a215362ca3b.png?imageMogr2/format/webp",
"兵器科技" to "https://resources.yangshipin.cn/assets/oms/image/202306/4c6b6a6d3839889f34d33db3c2f80233b26b74d3489b393487635f8704e70796.png?imageMogr2/format/webp",
"风云足球" to "https://resources.yangshipin.cn/assets/oms/image/202306/cd1e2bb52b06a991de168733e5ff0f1d85adc8042d40c8f393f723543e5dd08a.png?imageMogr2/format/webp",
@@ -44,7 +45,6 @@ object TVList {
"央视台球" to "https://resources.yangshipin.cn/assets/oms/image/202306/10e14a92478011aa6c3c8562e62127f3b1908e29fcd78e4b2b24b9e6d3ec2fbc.png?imageMogr2/format/webp",
"电视指南" to "https://resources.yangshipin.cn/assets/oms/image/202306/244d72c0eb1615ed7d51c2f5db5a67f306aa3f58c05bc2d34de3aa7e956dc8c9.png?imageMogr2/format/webp",
"卫生健康" to "https://resources.yangshipin.cn/assets/oms/image/202306/54a6863656fdfd8f803be193ddf22441c5000a108833889816fd2d8911715ce8.png?imageMogr2/format/webp",
"东方卫视" to "https://resources.yangshipin.cn/assets/oms/image/202306/9bd372ca292a82ce3aa08772b07efc4af1f85c21d1f268ea33440c49e9a0a488.png?imageMogr2/format/webp",
"湖南卫视" to "https://resources.yangshipin.cn/assets/oms/image/202306/4120e89d3079d08aa17d382f69a2308ec70839b278367763c34a34666c75cb88.png?imageMogr2/format/webp",
"湖北卫视" to "https://resources.yangshipin.cn/assets/oms/image/202306/7a6be5a2bb1dc53a945c016ff1f525dc4a84c51db371c15c89aa55404b0ba784.png?imageMogr2/format/webp",
@@ -69,6 +69,7 @@ object TVList {
)
private var mappingEPG = mapOf(
"CCTV4K 超高清" to "600002264",
"CCTV8K 超高清" to "600156816",
"CCTV1 综合" to "600001859",
"CCTV2 财经" to "600001800",
"CCTV3 综艺" to "600001801",
@@ -93,10 +94,10 @@ object TVList {
"CGTN 阿拉伯语频道" to "600084782",
"CGTN 西班牙语频道" to "600084744",
"CGTN 记录频道" to "600084781",
"风云剧场" to "600099658",
"第一剧场" to "600099655",
"怀旧剧场" to "600099620",
"世界地理" to "600099637",
"风云音乐" to "600099660",
"兵器科技" to "600099649",
"风云足球" to "600099636",
@@ -106,7 +107,6 @@ object TVList {
"央视台球" to "600099652",
"电视指南" to "600099656",
"卫生健康" to "600099651",
"东方卫视" to "600002483",
"湖南卫视" to "600002475",
"湖北卫视" to "600002508",
@@ -131,20 +131,21 @@ object TVList {
)
private var mappingVideo = mapOf(
"CCTV4K 超高清" to arrayOf("600002264", "2000266303"),
"CCTV8K 超高清" to arrayOf("600156816", "2020603421"),
"CCTV1 综合" to arrayOf("600001859", "2000210103"),
"CCTV2 财经" to arrayOf("600001800", "2000203603"),
// "CCTV3 综艺" to arrayOf("600001801",""),
"CCTV3 综艺" to arrayOf("600001801", "2000203803"),
"CCTV4 中文国际" to arrayOf("600001814", "2000204803"),
"CCTV5 体育" to arrayOf("600001818", "2000205103"),
"CCTV5+ 体育赛事" to arrayOf("600001817", "2000204503"),
// "CCTV6 电影" to arrayOf("600001802",""),
"CCTV6 电影" to arrayOf("600001802", "2000203303"),
"CCTV7 国防军事" to arrayOf("600004092", "2000510003"),
// "CCTV8 电视剧" to arrayOf("600001803",""),
"CCTV8 电视剧" to arrayOf("600001803", "2000203903"),
"CCTV9 记录" to arrayOf("600004078", "2000499403"),
"CCTV10 科教" to arrayOf("600001805", "2000203503"),
"CCTV11 戏曲" to arrayOf("600001806", "2000204103"),
"CCTV12 社会与法" to arrayOf("600001807", "2000202603"),
// "CCTV13 新闻" to arrayOf("600001811",""),
// "CCTV13 新闻" to arrayOf("600001811","2000204603"),
"CCTV14 少儿" to arrayOf("600001809", "2000204403"),
"CCTV15 音乐" to arrayOf("600001815", "2000205003"),
"CCTV16 奥林匹克" to arrayOf("600098637", "2012375003"),
@@ -155,20 +156,19 @@ object TVList {
"CGTN 阿拉伯语频道" to arrayOf("600084782", "2010155203"),
"CGTN 西班牙语频道" to arrayOf("600084744", "2010152503"),
"CGTN 记录频道" to arrayOf("600084781", "2010155403"),
// "风云剧场" to arrayOf("600099658",""),
// "第一剧场" to arrayOf("600099655",""),
// "怀旧剧场" to arrayOf("600099620",""),
// "风云音乐" to arrayOf("600099660",""),
// "兵器科技" to arrayOf("600099649",""),
// "风云足球" to arrayOf("600099636",""),
// "高尔夫网球" to arrayOf("600099659",""),
// "女性时尚" to arrayOf("600099650",""),
// "央视文化精品" to arrayOf("600099653",""),
// "央视台球" to arrayOf("600099652",""),
// "电视指南" to arrayOf("600099656",""),
// "卫生健康" to arrayOf("600099651",""),
"风云剧场" to arrayOf("600099658", "2012513603"),
"第一剧场" to arrayOf("600099655", "2012514403"),
"怀旧剧场" to arrayOf("600099620", "2012511203"),
"世界地理" to arrayOf("600099637", "2012513303"),
"风云音乐" to arrayOf("600099660", "2012514103"),
"兵器科技" to arrayOf("600099649", "2012513403"),
"风云足球" to arrayOf("600099636", "2012514203"),
"高尔夫网球" to arrayOf("600099659", "2012512503"),
"女性时尚" to arrayOf("600099650", "2012513903"),
"央视文化精品" to arrayOf("600099653", "2012513803"),
"央视台球" to arrayOf("600099652", "2012513703"),
"电视指南" to arrayOf("600099656", "2012514003"),
"卫生健康" to arrayOf("600099651", "2012513503"),
"东方卫视" to arrayOf("600002483", "2000292403"),
"湖南卫视" to arrayOf("600002475", "2000296203"),
"湖北卫视" to arrayOf("600002508", "2000294503"),
@@ -197,6 +197,7 @@ object TVList {
val tvs = """
央视频道
CCTV4K 超高清,
CCTV8K 超高清,
CCTV1 综合,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226231/index.m3u8
CCTV2 财经,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226195/index.m3u8
CCTV3 综艺,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226397/index.m3u8
@@ -215,24 +216,19 @@ CCTV14 少儿,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226193/inde
CCTV15 音乐,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225785/index.m3u8
CCTV16 奥林匹克,http://39.134.24.162/dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226921/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226921/index.m3u8
CCTV17 农业农村,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226198/index.m3u8
CGTN,http://live.cgtn.com/1000/prog_index.m3u8
CGTN 法语频道,https://livefr.cgtn.com/1000f/prog_index.m3u8
CGTN 俄语频道,http://liveru.cgtn.com/1000r/prog_index.m3u8
CGTN 阿拉伯语频道,http://livear.cgtn.com/1000a/prog_index.m3u8
CGTN 西班牙语频道,http://livees.cgtn.com/500e/prog_index.m3u8;http://livees.cgtn.com/1000e/prog_index.m3u8
CGTN 纪录频道,https://livedoc.cgtn.com/500d/prog_index.m3u8
电视指南,http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226987/index.m3u8
风云足球,http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226984/index.m3u8
风云剧场,http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226950/index.m3u8
风云音乐,http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226953/index.m3u8
央视台球,http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226956/index.m3u8
第一剧场,http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226959/index.m3u8
女性时尚,http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226969/index.m3u8
怀旧剧场,http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226972/index.m3u8
世界地理,
风云音乐,http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226953/index.m3u8
兵器科技,http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226975/index.m3u8
风云足球,http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226984/index.m3u8
高尔夫网球,http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226978/index.m3u8
女性时尚,http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226969/index.m3u8
央视文化精品,http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226981/index.m3u8
央视台球,http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226956/index.m3u8
电视指南,http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226987/index.m3u8
卫生健康,
地方频道
东方卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226217/index.m3u8
湖南卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226307/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226211/index.m3u8
@@ -255,6 +251,13 @@ CGTN 纪录频道,https://livedoc.cgtn.com/500d/prog_index.m3u8
四川卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225768/index.m3u8;http://39.134.24.166/dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225768/index.m3u8
东南卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226341/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225766/index.m3u8
海南卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226465/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225769/index.m3u8
国际频道
CGTN,http://live.cgtn.com/1000/prog_index.m3u8
CGTN 法语频道,https://livefr.cgtn.com/1000f/prog_index.m3u8
CGTN 俄语频道,http://liveru.cgtn.com/1000r/prog_index.m3u8
CGTN 阿拉伯语频道,http://livear.cgtn.com/1000a/prog_index.m3u8
CGTN 西班牙语频道,http://livees.cgtn.com/500e/prog_index.m3u8;http://livees.cgtn.com/1000e/prog_index.m3u8
CGTN 纪录频道,https://livedoc.cgtn.com/500d/prog_index.m3u8
""".trimIndent()
val map: MutableMap<String, MutableList<TV>> = mutableMapOf()
@@ -1,17 +1,51 @@
package com.lizongying.mytv.api
import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.converter.protobuf.ProtoConverterFactory
class ApiClient {
private val yspUrl = "https://player-api.yangshipin.cn/"
private val myUrl = "https://lyrics.run/"
private val protoUrl = "https://capi.yangshipin.cn/"
private val traceUrl = "https://btrace.yangshipin.cn/"
private var okHttpClient = OkHttpClient.Builder()
.dns(DnsCache())
.build()
val yspApiService: YSPApiService by lazy {
Retrofit.Builder()
.baseUrl(yspUrl)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.build().create(YSPApiService::class.java)
}
val yspTokenService: YSPTokenService by lazy {
Retrofit.Builder()
.baseUrl(myUrl)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.build().create(YSPTokenService::class.java)
}
val yspProtoService: YSPProtoService by lazy {
Retrofit.Builder()
.baseUrl(protoUrl)
.client(okHttpClient)
.addConverterFactory(ProtoConverterFactory.create())
.build().create(YSPProtoService::class.java)
}
val yspBtraceService: YSPBtraceService by lazy {
Retrofit.Builder()
.baseUrl(traceUrl)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.build().create(YSPBtraceService::class.java)
}
}
@@ -1,17 +0,0 @@
package com.lizongying.mytv.api
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
class BtraceClient {
private val yspUrl = "https://btrace.yangshipin.cn/"
val yspBtraceService: YSPBtraceService by lazy {
Retrofit.Builder()
.baseUrl(yspUrl)
.addConverterFactory(GsonConverterFactory.create())
.build().create(YSPBtraceService::class.java)
}
}
@@ -0,0 +1,23 @@
package com.lizongying.mytv.api
import okhttp3.Dns
import java.net.InetAddress
import java.util.concurrent.ConcurrentHashMap
class DnsCache : Dns {
private val dnsCache: MutableMap<String, List<InetAddress>> = ConcurrentHashMap()
override fun lookup(hostname: String): List<InetAddress> {
dnsCache[hostname]?.let {
return it
}
val addresses = InetAddress.getAllByName(hostname).toList()
if (addresses.isNotEmpty()) {
dnsCache[hostname] = addresses
}
return addresses
}
}
@@ -0,0 +1,11 @@
package com.lizongying.mytv.api
data class Info(
val code: Int?,
val msg: String?,
val data: InfoData,
)
data class InfoData(
val token: String,
)
@@ -15,6 +15,7 @@ data class LiveInfo(
data class LiveInfoData(
val chanll: String,
val playurl: String,
val errinfo: String,
)
data class LiveInfoRequest(
@@ -1,16 +0,0 @@
package com.lizongying.mytv.api
import retrofit2.Retrofit
import retrofit2.converter.protobuf.ProtoConverterFactory
class ProtoClient {
private val protoUrl = "https://capi.yangshipin.cn/"
val yspProtoService: YSPProtoService by lazy {
Retrofit.Builder()
.baseUrl(protoUrl)
.addConverterFactory(ProtoConverterFactory.create())
.build().create(YSPProtoService::class.java)
}
}
@@ -70,6 +70,8 @@ class YSP(var context: Context) {
defn = "fhd"
randStr = getRand()
guid = newGuid()
timeStr = getTimeStr()
// guid = "lq3oqitm_1e15dnzgjnb"
@@ -105,6 +107,15 @@ class YSP(var context: Context) {
return guid
}
fun newGuid(): String {
guid = generateGuid()
with(sharedPref!!.edit()) {
putString("guid", guid)
apply()
}
return guid
}
fun getRand(): String {
var n = ""
val e = "ABCDEFGHIJKlMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
@@ -13,20 +13,9 @@ interface YSPApiService {
"content-type: application/json;charset=UTF-8",
"referer: https://www.yangshipin.cn/",
"yspappid: 519748109",
"cookie: guid=1; vplatform=109"
)
fun getLiveInfo(
@Body request: LiveInfoRequest,
): Call<LiveInfo>
@POST("v1/player/get_live_info")
@Headers(
"content-type: application/json;charset=UTF-8",
"referer: https://www.yangshipin.cn/",
"yspappid: 519748109",
"cookie: guid=1; vplatform=109; yspopenid=vu0-8lgGV2LW9QjDeuBFsX8yMnzs37Q3_HZF6XyVDpGR_I; vusession=mTIVXnDeVUg0FcP6Xo4kaXd9fxlc70a558kySW4phQU"
)
fun getLiveInfo2(
@Header("cookie") cookie: String,
@Body request: LiveInfoRequest,
): Call<LiveInfo>
}
@@ -0,0 +1,11 @@
package com.lizongying.mytv.api
import retrofit2.Call
import retrofit2.http.GET
interface YSPTokenService {
@GET("my-tv/v1/info")
fun getInfo(
): Call<Info>
}
@@ -19,8 +19,14 @@ class TVViewModel(private var tv: TV) : ViewModel() {
private var rowPosition: Int = 0
private var itemPosition: Int = 0
var retryTimes: Int = 0
var retryMaxTimes: Int = 5
var programUpdateTime: Long = 0
private val _errInfo = MutableLiveData<String>()
val errInfo: LiveData<String>
get() = _errInfo
private val _programId = MutableLiveData<String>()
val programId: LiveData<String>
get() = _programId
@@ -65,10 +71,31 @@ class TVViewModel(private var tv: TV) : ViewModel() {
val ready: LiveData<Boolean>
get() = _ready
private var mMinimumLoadableRetryCount = 3
private var mMinimumLoadableRetryCount = 5
var seq = 0
var needToken = false
private val channelsNeedToken = arrayOf(
"CCTV3 综艺",
"CCTV6 电影",
"CCTV8 电视剧",
"风云剧场",
"第一剧场",
"怀旧剧场",
"世界地理",
"风云音乐",
"兵器科技",
"风云足球",
"高尔夫网球",
"女性时尚",
"央视文化精品",
"央视台球",
"电视指南",
"卫生健康",
)
fun addVideoUrl(url: String) {
if (_videoUrl.value?.isNotEmpty() == true) {
if (_videoUrl.value!!.last().contains("cctv.cn")) {
@@ -119,6 +146,10 @@ class TVViewModel(private var tv: TV) : ViewModel() {
_pid.value = tv.pid
_sid.value = tv.sid
_program.value = mutableListOf()
if (tv.title in channelsNeedToken) {
needToken = true
}
}
fun getRowPosition(): Int {
@@ -137,6 +168,10 @@ class TVViewModel(private var tv: TV) : ViewModel() {
itemPosition = position
}
fun setErrInfo(info: String) {
_errInfo.value = info
}
fun update(t: TV) {
tv = t
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

-1
View File
@@ -2,7 +2,6 @@
<style name="CustomImageCardViewStyle" parent="Widget.Leanback.ImageCardViewStyle">
<!-- <item name="infoVisibility">activated</item>-->
<item name="lbImageCardViewType">Title|Content</item>
</style>