Compare commits

...
26 Commits
Author SHA1 Message Date
Li ZongYing 4fb27ba502 fix channel number did not disappear 2024-02-05 06:12:01 +08:00
李宗英 1714633cb7 Merge pull request #205 from LeGend-wLw/main
移除硬编码,更改为json格式,并支持从服务器上动态更新channels。
2024-02-04 22:46:08 +08:00
LeGend-wLw f7b024f6da 防止多次初始化 2024-02-04 16:42:56 +08:00
LeGend-wLw dd8df63b01 添加初始化TVList行为 2024-02-04 16:42:38 +08:00
LeGend-wLw a4c95efd71 添加从服务器上获取channel.json逻辑,但未启用 2024-02-04 16:37:59 +08:00
LeGend-wLw 9f32fa3673 移除MustToken和needToken,移动到TV类中 2024-02-04 16:37:20 +08:00
LeGend-wLw b3d0093ab0 修改为从json文件中读取channel信息 2024-02-04 16:36:34 +08:00
LeGend-wLw 5798d3a5fe 更改TV类中成员,使能和json对应 2024-02-04 16:36:05 +08:00
LeGend-wLw 4eb90de79d 当前转换成json的channel信息 2024-02-04 16:35:38 +08:00
LeGend-wLw b8232971e2 服务器地址,未更新 2024-02-04 16:35:20 +08:00
LeGend-wLw bc3681b0a6 将从tvModel中获取needToken和MustToken改成从TV类中获取needToken和MustToken 2024-02-04 16:34:59 +08:00
Li ZongYing 088808c3fd fix retry bug 2024-02-03 20:07:08 +08:00
Li ZongYing 73239a6677 replay 2024-02-01 17:04:02 +08:00
Li ZongYing cc2514a81a performance optimization 2024-01-28 16:00:33 +08:00
Li ZongYing bcf9bab25f add channel 2024-01-27 19:52:36 +08:00
Li ZongYing f7eebb3f7a channel allow one num 2024-01-25 12:21:16 +08:00
Li ZongYing 0f4a33a3fc change channel order 2024-01-25 11:37:06 +08:00
Li ZongYing c17d62c698 rename to setting 2024-01-19 13:49:43 +08:00
Li ZongYing 33d3da97e5 fix 13 2024-01-18 22:19:04 +08:00
Li ZongYing ba95ce5374 number selection 2024-01-18 21:42:06 +08:00
Li ZongYing e1ee909071 optimize memu hiding 2024-01-17 10:19:50 +08:00
Li ZongYing 1fd0951f92 channel reversal 2024-01-16 19:04:43 +08:00
Li ZongYing 42b072f5ec support KEYCODE_CHANNEL_UP & KEYCODE_CHANNEL_DOWN 2024-01-14 12:09:12 +08:00
Li ZongYing a23b1e11ce fix the menu did not appear 2024-01-14 10:15:45 +08:00
Li ZongYing 1ac6d87c3c clean log 2024-01-12 10:13:29 +08:00
Li ZongYing b075a7db81 trust all certs 2024-01-11 19:33:31 +08:00
31 changed files with 2062 additions and 676 deletions
+57 -1
View File
@@ -1,6 +1,6 @@
# 我的电视
安卓电视直播软件,内置直播源
电视直播软件,安装即可使用
## 使用
@@ -14,6 +14,46 @@
## 更新日志
### v1.5.2(通用版)
* 修复APP恢复后频道号、频道列表不自动消失的问题
### v1.5.1(高版本专用)
* 性能优化
### v1.5.0(通用版)
* 修复部分情况下APP切换后无法继续播放的问题
### v1.4.9(高版本专用)
* 同步v1.4.8
### v1.4.8(通用版)
* 频道号从1开始,CCTV5+为18
* 提高CCTV6清晰度
* 增加天津卫视、新疆卫视
### v1.4.7(高版本专用)
* 修复部分用户CCTV13播放过程中卡住的问题
* 调整CCTV的频道顺序
### v1.4.6(通用版)
* 10以下频道不再需要先按0
### v1.4.5(高版本专用)
* 数字选台配置
### v1.4.4(通用版)
* 优化图标显示
* 增加换台反转
### v1.3.3
* 部分错误会提示用户
@@ -54,6 +94,22 @@
adb install my-tv.apk
```
## TODO
* 音量不同
* 大湾区卫视、广东4k超高清、广东珠江
* CETV教育频道
* CHC高清三个电影频道
* 地方频道
* 收藏夹
* 自定义源
* 凤凰卫视、凤凰资讯台
* 海外
* 1.5.0 无法安装,1.5.1 可以安装
* 获取系统时间
* 选中的图标比例能否相差更大
* 自动重连
## 赞赏
![image](./screenshots/appreciate.jpeg)
+2 -2
View File
@@ -5,7 +5,7 @@ plugins {
android {
namespace 'com.lizongying.mytv'
compileSdk 33
compileSdk 34
viewBinding {
enabled = true
@@ -90,7 +90,7 @@ static def VersionName() {
}
dependencies {
def media3_version = "1.1.1"
def media3_version = "1.2.1"
implementation "androidx.media3:media3-ui:$media3_version"
+6 -4
View File
@@ -9,17 +9,19 @@
<uses-permission android:name="android.permission.INTERNET" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:largeHeap="true"
android:icon="@drawable/logo"
android:logo="@drawable/logo"
android:banner="@drawable/banner"
android:label="@string/app_name"
android:supportsRtl="true"
android:networkSecurityConfig="@xml/network"
android:usesCleartextTraffic="true"
android:theme="@style/Theme.MyTV">
<activity
android:keepScreenOn="true"
android:name=".MainActivity"
android:banner="@drawable/banner"
android:exported="true"
android:icon="@drawable/logo"
android:logo="@drawable/logo"
android:screenOrientation="landscape">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
Binary file not shown.
Binary file not shown.
@@ -31,23 +31,31 @@ class CardPresenter(
cardView.setMainImageDimensions(CARD_WIDTH, CARD_HEIGHT)
cardView.tag = tvViewModel.videoUrl.value
if (tvViewModel.logo.value != null) {
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)
}
when (tvViewModel.title.value) {
"CCTV8K 超高清" -> Glide.with(viewHolder.view.context)
.load(R.drawable.cctv8k)
.centerInside()
.into(cardView.mainImageView)
cardView.setBackgroundColor(Color.WHITE)
cardView.setMainImageScaleType(ImageView.ScaleType.CENTER_INSIDE)
"天津卫视" -> Glide.with(viewHolder.view.context)
.load(R.drawable.tianjin)
.centerInside()
.into(cardView.mainImageView)
"新疆卫视" -> Glide.with(viewHolder.view.context)
.load(R.drawable.xinjiang)
.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)
tvViewModel.program.observe(owner) { _ ->
val program = tvViewModel.getProgramOne()
if (program != null) {
@@ -0,0 +1,83 @@
package com.lizongying.mytv
import android.os.Bundle
import android.os.Handler
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.lizongying.mytv.databinding.ChannelBinding
import com.lizongying.mytv.models.TVViewModel
class ChannelFragment : Fragment() {
private var _binding: ChannelBinding? = null
private val binding get() = _binding!!
private val handler = Handler()
private val delay: Long = 3000
private var channel = 0
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = ChannelBinding.inflate(inflater, container, false)
_binding!!.root.visibility = View.GONE
(activity as MainActivity).fragmentReady()
return binding.root
}
fun show(tvViewModel: TVViewModel) {
handler.removeCallbacks(hideRunnable)
handler.removeCallbacks(playRunnable)
binding.channelContent.text = (tvViewModel.id.value?.plus(1)).toString()
view?.visibility = View.VISIBLE
handler.postDelayed(hideRunnable, delay)
}
fun show(channel: String) {
this.channel = "${binding.channelContent.text}$channel".toInt()
handler.removeCallbacks(hideRunnable)
handler.removeCallbacks(playRunnable)
if (binding.channelContent.text == "") {
binding.channelContent.text = channel
view?.visibility = View.VISIBLE
handler.postDelayed(playRunnable, delay)
} else {
handler.postDelayed(playRunnable, 0)
}
}
override fun onResume() {
super.onResume()
if (view?.visibility == View.VISIBLE) {
handler.postDelayed(hideRunnable, delay)
}
}
override fun onPause() {
super.onPause()
handler.removeCallbacks(hideRunnable)
handler.removeCallbacks(playRunnable)
}
private val hideRunnable = Runnable {
binding.channelContent.text = ""
view?.visibility = View.GONE
}
private val playRunnable = Runnable {
(activity as MainActivity).play(channel - 1)
binding.channelContent.text = ""
view?.visibility = View.GONE
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
companion object {
private const val TAG = "ChannelFragment"
}
}
@@ -2,7 +2,6 @@ package com.lizongying.mytv
import android.os.Bundle
import android.os.Handler
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@@ -23,25 +22,28 @@ class InfoFragment : Fragment() {
savedInstanceState: Bundle?
): View {
_binding = InfoBinding.inflate(inflater, container, false)
_binding!!.root.visibility = View.GONE
(activity as MainActivity).fragmentReady()
return binding.root
}
override fun onResume() {
super.onResume()
// Use a Handler to delay the fragment transaction
handler.postDelayed(removeRunnable, delay)
}
fun show(tvViewModel: TVViewModel) {
binding.textView.text = tvViewModel.title.value
if (tvViewModel.title.value == "CCTV8K 超高清") {
Glide.with(this)
when (tvViewModel.title.value) {
"CCTV8K 超高清" -> Glide.with(this)
.load(R.drawable.cctv8k)
.into(binding.infoLogo)
} else {
Glide.with(this)
"天津卫视" -> Glide.with(this)
.load(R.drawable.tianjin)
.into(binding.infoLogo)
"新疆卫视" -> Glide.with(this)
.load(R.drawable.xinjiang)
.into(binding.infoLogo)
else -> Glide.with(this)
.load(tvViewModel.logo.value)
.into(binding.infoLogo)
}
@@ -56,10 +58,13 @@ class InfoFragment : Fragment() {
handler.postDelayed(removeRunnable, delay)
}
override fun onResume() {
super.onResume()
handler.postDelayed(removeRunnable, delay)
}
override fun onPause() {
super.onPause()
Log.i(TAG, "onPause")
// Cancel the delayed task when the fragment is paused
handler.removeCallbacks(removeRunnable)
}
@@ -1,6 +1,7 @@
package com.lizongying.mytv
import android.app.AlertDialog
import android.content.Context
import android.content.SharedPreferences
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.content.pm.Signature
@@ -15,11 +16,7 @@ import android.view.KeyEvent
import android.view.MotionEvent
import android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
import android.view.WindowManager
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import android.widget.Toast
import androidx.core.content.ContextCompat
import androidx.fragment.app.FragmentActivity
import com.lizongying.mytv.models.TVViewModel
import java.security.MessageDigest
@@ -30,15 +27,25 @@ class MainActivity : FragmentActivity() {
var playerFragment = PlayerFragment()
private val mainFragment = MainFragment()
private val infoFragment = InfoFragment()
private val channelFragment = ChannelFragment()
private lateinit var settingFragment: SettingFragment
private var doubleBackToExitPressedOnce = false
private lateinit var gestureDetector: GestureDetector
private val handler = Handler()
private val delay: Long = 3000
private val delayHideMain: Long = 5000
private val delayHideSetting: Long = 10000
lateinit var sharedPref: SharedPreferences
private var channelReversal = false
private var channelNum = true
private var versionName = ""
override fun onCreate(savedInstanceState: Bundle?) {
Log.i(TAG, "onCreate")
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
@@ -50,24 +57,51 @@ class MainActivity : FragmentActivity() {
supportFragmentManager.beginTransaction()
.add(R.id.main_browse_fragment, playerFragment)
.add(R.id.main_browse_fragment, infoFragment)
.add(R.id.main_browse_fragment, channelFragment)
.add(R.id.main_browse_fragment, mainFragment)
.hide(infoFragment)
.hide(mainFragment)
.commit()
mainFragment.view?.requestFocus()
}
gestureDetector = GestureDetector(this, GestureListener())
sharedPref = getPreferences(Context.MODE_PRIVATE)
channelReversal = sharedPref.getBoolean(CHANNEL_REVERSAL, channelReversal)
channelNum = sharedPref.getBoolean(CHANNEL_NUM, channelNum)
versionName = getPackageInfo().versionName
settingFragment = SettingFragment(versionName, channelReversal, channelNum)
}
fun showInfoFragment(tvViewModel: TVViewModel) {
infoFragment.show(tvViewModel)
if (channelNum) {
channelFragment.show(tvViewModel)
}
}
private fun showChannel(channel: String) {
if (!mainFragment.isHidden) {
return
}
if (settingFragment.isVisible) {
return
}
if (channelNum) {
channelFragment.show(channel)
}
}
fun play(tvViewModel: TVViewModel) {
Log.i(TAG, "play: ${tvViewModel.getTV()}")
playerFragment.play(tvViewModel)
mainFragment.view?.requestFocus()
}
fun play(itemPosition: Int) {
mainFragment.play(itemPosition)
}
fun prev() {
mainFragment.prev()
}
@@ -77,11 +111,11 @@ class MainActivity : FragmentActivity() {
}
private fun prevSource() {
mainFragment.prevSource()
// mainFragment.prevSource()
}
private fun nextSource() {
mainFragment.nextSource()
// mainFragment.nextSource()
}
fun switchMainFragment() {
@@ -98,12 +132,14 @@ class MainActivity : FragmentActivity() {
}
fun keepRunnable() {
handler.removeCallbacks(hideRunnable)
handler.postDelayed(hideRunnable, delay)
handler.removeCallbacks(hideMain)
handler.postDelayed(hideMain, delayHideMain)
}
private val hideRunnable = Runnable {
supportFragmentManager.beginTransaction().hide(mainFragment).commit()
private val hideMain = Runnable {
if (!mainFragment.isHidden) {
supportFragmentManager.beginTransaction().hide(mainFragment).commit()
}
}
private fun mainFragmentIsHidden(): Boolean {
@@ -138,7 +174,7 @@ class MainActivity : FragmentActivity() {
}
override fun onFling(
e1: MotionEvent,
e1: MotionEvent?,
e2: MotionEvent,
velocityX: Float,
velocityY: Float
@@ -170,88 +206,205 @@ class MainActivity : FragmentActivity() {
}
}
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
when (keyCode) {
KeyEvent.KEYCODE_BACK -> {
if (!mainFragmentIsHidden()) {
hideMainFragment()
return true
}
fun saveChannelReversal(channelReversal: Boolean) {
with(sharedPref.edit()) {
putBoolean(CHANNEL_REVERSAL, channelReversal)
apply()
}
this.channelReversal = channelReversal
}
if (doubleBackToExitPressedOnce) {
super.onBackPressed()
return true
}
fun saveChannelNum(channelNum: Boolean) {
with(sharedPref.edit()) {
putBoolean(CHANNEL_NUM, channelNum)
apply()
}
this.channelNum = channelNum
}
this.doubleBackToExitPressedOnce = true
Toast.makeText(this, "再按一次退出", Toast.LENGTH_SHORT).show()
private fun showSetting() {
if (!mainFragment.isHidden) {
return
}
Handler(Looper.getMainLooper()).postDelayed({
doubleBackToExitPressedOnce = false
}, 2000)
return true
Log.i(TAG, "settingFragment ${settingFragment.isVisible}")
if (!settingFragment.isVisible) {
settingFragment.show(supportFragmentManager, "setting")
handler.removeCallbacks(hideSetting)
handler.postDelayed(hideSetting, delayHideSetting)
} else {
handler.removeCallbacks(hideSetting)
settingFragment.dismiss()
}
}
private val hideSetting = Runnable {
if (settingFragment.isVisible) {
settingFragment.dismiss()
}
}
private fun channelUp() {
if (mainFragment.isHidden) {
if (channelReversal) {
next()
return
}
KeyEvent.KEYCODE_MENU -> {
val versionName = getPackageInfo().versionName
val textView = TextView(this)
textView.text =
"当前版本: $versionName\n获取最新: https://github.com/lizongying/my-tv/releases/"
val imageView = ImageView(this)
val drawable = ContextCompat.getDrawable(this, R.drawable.appreciate)
imageView.setImageDrawable(drawable)
val linearLayout = LinearLayout(this)
linearLayout.orientation = LinearLayout.VERTICAL
linearLayout.addView(textView)
linearLayout.addView(imageView)
val layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
)
imageView.layoutParams = layoutParams
textView.layoutParams = layoutParams
val builder: AlertDialog.Builder = AlertDialog.Builder(this)
builder
.setView(linearLayout)
val dialog: AlertDialog = builder.create()
dialog.show()
return true
}
KeyEvent.KEYCODE_DPAD_CENTER -> {
Log.i(TAG, "KEYCODE_DPAD_CENTER")
switchMainFragment()
}
KeyEvent.KEYCODE_DPAD_UP -> {
if (mainFragment.isHidden) {
prev()
} else {
prev()
} else {
// if (mainFragment.selectedPosition == 0) {
// mainFragment.setSelectedPosition(
// mainFragment.tvListViewModel.maxNum.size - 1,
// false
// )
// }
}
}
}
}
KeyEvent.KEYCODE_DPAD_DOWN -> {
if (mainFragment.isHidden) {
next()
} else {
private fun channelDown() {
if (mainFragment.isHidden) {
if (channelReversal) {
prev()
return
}
next()
} else {
// if (mainFragment.selectedPosition == mainFragment.tvListViewModel.maxNum.size - 1) {
//// mainFragment.setSelectedPosition(0, false)
// hideMainFragment()
// return false
// }
}
}
}
private fun back() {
if (!mainFragmentIsHidden()) {
hideMainFragment()
return
}
if (doubleBackToExitPressedOnce) {
super.onBackPressed()
return
}
doubleBackToExitPressedOnce = true
Toast.makeText(this, "再按一次退出", Toast.LENGTH_SHORT).show()
Handler(Looper.getMainLooper()).postDelayed({
doubleBackToExitPressedOnce = false
}, 2000)
}
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
when (keyCode) {
KeyEvent.KEYCODE_0 -> {
showChannel("0")
return true
}
KeyEvent.KEYCODE_1 -> {
showChannel("1")
return true
}
KeyEvent.KEYCODE_2 -> {
showChannel("2")
return true
}
KeyEvent.KEYCODE_3 -> {
showChannel("3")
return true
}
KeyEvent.KEYCODE_4 -> {
showChannel("4")
return true
}
KeyEvent.KEYCODE_5 -> {
showChannel("5")
return true
}
KeyEvent.KEYCODE_6 -> {
showChannel("6")
return true
}
KeyEvent.KEYCODE_7 -> {
showChannel("7")
return true
}
KeyEvent.KEYCODE_8 -> {
showChannel("8")
return true
}
KeyEvent.KEYCODE_9 -> {
showChannel("9")
return true
}
KeyEvent.KEYCODE_ESCAPE -> {
back()
return true
}
KeyEvent.KEYCODE_BACK -> {
back()
return true
}
KeyEvent.KEYCODE_BOOKMARK -> {
showSetting()
return true
}
KeyEvent.KEYCODE_UNKNOWN -> {
showSetting()
return true
}
KeyEvent.KEYCODE_HELP -> {
showSetting()
return true
}
KeyEvent.KEYCODE_SETTINGS -> {
showSetting()
return true
}
KeyEvent.KEYCODE_MENU -> {
showSetting()
return true
}
KeyEvent.KEYCODE_ENTER -> {
switchMainFragment()
}
KeyEvent.KEYCODE_DPAD_CENTER -> {
switchMainFragment()
}
KeyEvent.KEYCODE_DPAD_UP -> {
channelUp()
}
KeyEvent.KEYCODE_CHANNEL_UP -> {
channelUp()
}
KeyEvent.KEYCODE_DPAD_DOWN -> {
channelDown()
}
KeyEvent.KEYCODE_CHANNEL_DOWN -> {
channelDown()
}
KeyEvent.KEYCODE_DPAD_LEFT -> {
@@ -324,7 +477,7 @@ class MainActivity : FragmentActivity() {
private fun hashSignature(signature: Signature): String {
return try {
val md = MessageDigest.getInstance("SHA-256")
val md = MessageDigest.getInstance("MD5")
md.update(signature.toByteArray())
val digest = md.digest()
digest.let { it -> it.joinToString("") { "%02x".format(it) } }
@@ -334,7 +487,28 @@ class MainActivity : FragmentActivity() {
}
}
override fun onStart() {
Log.i(TAG, "onStart")
super.onStart()
}
override fun onResume() {
Log.i(TAG, "onResume")
super.onResume()
if (!mainFragment.isHidden){
handler.postDelayed(hideMain, delayHideMain)
}
}
override fun onPause() {
Log.i(TAG, "onPause")
super.onPause()
handler.removeCallbacks(hideMain)
}
companion object {
private const val TAG = "MainActivity"
private const val CHANNEL_REVERSAL = "channel_reversal"
private const val CHANNEL_NUM = "channel_num"
}
}
@@ -1,6 +1,5 @@
package com.lizongying.mytv
import android.content.Context
import android.content.SharedPreferences
import android.os.Bundle
import android.os.Handler
@@ -25,19 +24,19 @@ import com.lizongying.mytv.models.TVViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class MainFragment : BrowseSupportFragment() {
var itemPosition: Int = 0
private var request: Request = Request()
private var itemPosition = 0
private var rowsAdapter: ArrayObjectAdapter? = null
private var request = Request()
var tvListViewModel = TVListViewModel()
private var sharedPref: SharedPreferences? = null
private lateinit var sharedPref: SharedPreferences
private var lastVideoUrl: String = ""
private var lastVideoUrl = ""
private val handler = Handler(Looper.getMainLooper())
private lateinit var mUpdateProgramRunnable: UpdateProgramRunnable
@@ -45,16 +44,21 @@ class MainFragment : BrowseSupportFragment() {
private var ready = 0
override fun onCreate(savedInstanceState: Bundle?) {
Log.i(TAG, "onCreate")
super.onCreate(savedInstanceState)
headersState = HEADERS_DISABLED
}
override fun onStart() {
Log.i(TAG, "onStart")
super.onStart()
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
activity?.let { request.initYSP(it) }
sharedPref = activity?.getPreferences(Context.MODE_PRIVATE)
sharedPref = (activity as? MainActivity)?.sharedPref!!
loadRows()
@@ -63,7 +67,7 @@ class MainFragment : BrowseSupportFragment() {
mUpdateProgramRunnable = UpdateProgramRunnable()
handler.post(mUpdateProgramRunnable)
tvListViewModel.getTVListViewModel().value?.forEach { tvViewModel ->
tvListViewModel.tvListViewModel.value?.forEach { tvViewModel ->
tvViewModel.errInfo.observe(viewLifecycleOwner) { _ ->
if (tvViewModel.errInfo.value != null
&& tvViewModel.id.value == itemPosition
@@ -85,9 +89,8 @@ class MainFragment : BrowseSupportFragment() {
tvViewModel.change.observe(viewLifecycleOwner) { _ ->
if (tvViewModel.change.value != null) {
val title = tvViewModel.title.value
Log.i(TAG, "switch $title")
if (tvViewModel.pid.value != "") {
Log.i(TAG, "request $title ${tvViewModel.pid.value}")
Log.i(TAG, "request $title")
lifecycleScope.launch(Dispatchers.IO) {
tvViewModel.let { request.fetchData(it) }
}
@@ -100,7 +103,6 @@ class MainFragment : BrowseSupportFragment() {
if (check(tvViewModel)) {
(activity as? MainActivity)?.play(tvViewModel)
(activity as? MainActivity)?.showInfoFragment(tvViewModel)
setSelectedPosition(
tvViewModel.getRowPosition(), true,
SelectItemViewHolderTask(tvViewModel.getItemPosition())
@@ -114,58 +116,6 @@ class MainFragment : BrowseSupportFragment() {
fragmentReady()
}
override fun onDestroy() {
super.onDestroy()
handler.removeCallbacks(mUpdateProgramRunnable)
with(sharedPref!!.edit()) {
putInt("position", itemPosition)
apply()
}
}
fun updateProgram(tvViewModel: TVViewModel) {
val timestamp = getDateTimestamp()
if (timestamp - tvViewModel.programUpdateTime > 60) {
if (tvViewModel.program.value!!.isEmpty()) {
tvViewModel.programUpdateTime = timestamp
request.fetchProgram(tvViewModel)
} else {
if (timestamp - tvViewModel.program.value!!.last().et < 600) {
tvViewModel.programUpdateTime = timestamp
request.fetchProgram(tvViewModel)
}
}
}
}
inner class UpdateProgramRunnable : Runnable {
override fun run() {
tvListViewModel.getTVListViewModel().value?.filter { it.programId.value != null }
?.forEach { tvViewModel ->
updateProgram(
tvViewModel
)
}
handler.postDelayed(this, 60000)
}
}
fun check(tvViewModel: TVViewModel): Boolean {
val title = tvViewModel.title.value
val videoUrl = tvViewModel.videoIndex.value?.let { tvViewModel.videoUrl.value?.get(it) }
if (videoUrl == null || videoUrl == "") {
Log.e(TAG, "$title videoUrl is empty")
return false
}
if (videoUrl == lastVideoUrl) {
Log.e(TAG, "$title videoUrl is duplication")
return false
}
return true
}
fun toLastPosition() {
setSelectedPosition(
selectedPosition, false,
@@ -189,6 +139,7 @@ class MainFragment : BrowseSupportFragment() {
val cardPresenter = CardPresenter(viewLifecycleOwner)
var idx: Long = 0
context?.let { TVList.init(it) }
for ((k, v) in TVList.list) {
val listRowAdapter = ArrayObjectAdapter(cardPresenter)
for ((idx2, v1) in v.withIndex()) {
@@ -206,49 +157,11 @@ class MainFragment : BrowseSupportFragment() {
adapter = rowsAdapter
itemPosition = sharedPref?.getInt("position", 0)!!
itemPosition = sharedPref.getInt(POSITION, 0)
if (itemPosition >= tvListViewModel.size()) {
itemPosition = 0
tvListViewModel.setItemPosition(itemPosition)
}
}
fun fragmentReady() {
ready++
Log.i(TAG, "ready $ready")
if (ready == 3) {
// request.fetchPage()
val tvViewModel = tvListViewModel.getTVViewModel(itemPosition)
tvViewModel?.changed()
(activity as? MainActivity)?.switchMainFragment()
}
}
fun prev() {
view?.post {
itemPosition--
if (itemPosition == -1) {
itemPosition = tvListViewModel.size() - 1
}
tvListViewModel.setItemPosition(itemPosition)
val tvViewModel = tvListViewModel.getTVViewModel(itemPosition)
tvViewModel?.changed()
}
}
fun next() {
view?.post {
itemPosition++
if (itemPosition == tvListViewModel.size()) {
itemPosition = 0
}
tvListViewModel.setItemPosition(itemPosition)
val tvViewModel = tvListViewModel.getTVViewModel(itemPosition)
tvViewModel?.changed()
}
tvListViewModel.setItemPosition(itemPosition)
}
fun prevSource() {
@@ -297,9 +210,7 @@ class MainFragment : BrowseSupportFragment() {
if (itemPosition != item.id.value!!) {
itemPosition = item.id.value!!
tvListViewModel.setItemPosition(itemPosition)
val tvViewModel = tvListViewModel.getTVViewModel(itemPosition)
tvViewModel?.changed()
tvListViewModel.getTVViewModel(itemPosition)?.changed()
}
(activity as? MainActivity)?.switchMainFragment()
}
@@ -318,12 +229,115 @@ class MainFragment : BrowseSupportFragment() {
}
}
fun check(tvViewModel: TVViewModel): Boolean {
val title = tvViewModel.title.value
val videoUrl = tvViewModel.videoIndex.value?.let { tvViewModel.videoUrl.value?.get(it) }
if (videoUrl == null || videoUrl == "") {
Log.e(TAG, "$title videoUrl is empty")
return false
}
if (videoUrl == lastVideoUrl) {
Log.e(TAG, "$title videoUrl is duplication")
return false
}
return true
}
fun fragmentReady() {
ready++
Log.i(TAG, "ready $ready")
if (ready == 4) {
// request.fetchPage()
tvListViewModel.getTVViewModel(itemPosition)?.changed()
}
}
fun play(itemPosition: Int) {
view?.post {
if (itemPosition > -1 && itemPosition < tvListViewModel.size()) {
this.itemPosition = itemPosition
tvListViewModel.setItemPosition(itemPosition)
tvListViewModel.getTVViewModel(itemPosition)?.changed()
} else {
Toast.makeText(context, "频道不存在", Toast.LENGTH_SHORT).show()
}
}
}
fun prev() {
view?.post {
itemPosition--
if (itemPosition == -1) {
itemPosition = tvListViewModel.size() - 1
}
tvListViewModel.setItemPosition(itemPosition)
tvListViewModel.getTVViewModel(itemPosition)?.changed()
}
}
fun next() {
view?.post {
itemPosition++
if (itemPosition == tvListViewModel.size()) {
itemPosition = 0
}
tvListViewModel.setItemPosition(itemPosition)
tvListViewModel.getTVViewModel(itemPosition)?.changed()
}
}
fun updateProgram(tvViewModel: TVViewModel) {
val timestamp = getDateTimestamp()
if (timestamp - tvViewModel.programUpdateTime > 60) {
if (tvViewModel.program.value!!.isEmpty()) {
tvViewModel.programUpdateTime = timestamp
request.fetchProgram(tvViewModel)
} else {
if (tvViewModel.program.value!!.last().et - timestamp < 600) {
tvViewModel.programUpdateTime = timestamp
request.fetchProgram(tvViewModel)
}
}
}
}
inner class UpdateProgramRunnable : Runnable {
override fun run() {
tvListViewModel.tvListViewModel.value?.filter { it.programId.value != null && it.programId.value != "" }
?.forEach { tvViewModel ->
updateProgram(
tvViewModel
)
}
handler.postDelayed(this, 60000)
}
}
override fun onResume() {
Log.i(TAG, "onResume")
super.onResume()
view!!.requestFocus()
}
override fun onStop() {
Log.i(TAG, "onStop")
super.onStop()
with(sharedPref.edit()) {
putInt(POSITION, itemPosition)
apply()
}
Log.i(TAG, "$POSITION saved")
}
override fun onDestroy() {
Log.i(TAG, "onDestroy")
super.onDestroy()
handler.removeCallbacks(mUpdateProgramRunnable)
}
companion object {
private const val TAG = "MainFragment"
private const val POSITION = "position"
}
}
@@ -74,24 +74,31 @@ class PlayerFragment : Fragment() {
@OptIn(UnstableApi::class)
fun play(tvViewModel: TVViewModel) {
this.tvViewModel = tvViewModel
val videoUrlCurrent =
tvViewModel.videoIndex.value?.let { tvViewModel.videoUrl.value?.get(it) }
playerView?.player?.run {
videoUrlCurrent?.let { setMediaItem(MediaItem.fromUri(it)) }
setMediaItem(MediaItem.fromUri(tvViewModel.getVideoUrlCurrent()))
prepare()
}
}
override fun onStart() {
Log.i(TAG, "onStart")
super.onStart()
if (playerView != null) {
playerView!!.player?.play()
if (playerView != null && playerView!!.player?.isPlaying == false) {
Log.i(TAG, "replay")
playerView!!.player?.prepare()
} else {
Log.i(TAG, "playing")
}
}
override fun onStop() {
super.onStop()
if (playerView != null) {
override fun onResume() {
Log.i(TAG, "onResume")
super.onResume()
}
override fun onPause() {
super.onPause()
if (playerView != null && playerView!!.player?.isPlaying == true) {
playerView!!.player?.stop()
}
}
@@ -103,6 +110,11 @@ class PlayerFragment : Fragment() {
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
companion object {
private const val TAG = "PlaybackVideoFragment"
}
+190 -87
View File
@@ -32,10 +32,16 @@ class Request {
private var yspBtraceService: YSPBtraceService = ApiClient().yspBtraceService
private var yspProtoService: YSPProtoService = ApiClient().yspProtoService
private var ysp: YSP? = null
private var token = ""
// TODO onDestroy
private val handler = Handler(Looper.getMainLooper())
private lateinit var myRunnable: MyRunnable
private lateinit var btraceRunnable: BtraceRunnable
private var tokenRunnable: TokenRunnable = TokenRunnable()
private val regex = Regex("""des_key = "([^"]+).+var des_iv = "([^"]+)""")
private val input =
"""{"mver":"1","subver":"1.2","host":"www.yangshipin.cn/#/tv/home?pid=","referer":"","canvas":"YSPANGLE(Apple,AppleM1Pro,OpenGL4.1)"}""".toByteArray()
private var mapping = mapOf(
"CCTV4K" to "CCTV4K 超高清",
@@ -81,15 +87,26 @@ class Request {
"四川卫视" to "四川卫视",
"福建东南卫视" to "东南卫视",
"海南卫视" to "海南卫视",
"天津卫视" to "天津卫视",
"新疆卫视" to "新疆卫视",
)
init {
handler.post(tokenRunnable)
}
fun initYSP(context: Context) {
ysp = YSP(context)
//TODO 不确定在哪里初始化
TVList.init(context)
}
var call: Call<LiveInfo>? = null
fun fetchVideo(tvModel: TVViewModel, cookie: String) {
if (::myRunnable.isInitialized) {
handler.removeCallbacks(myRunnable)
call?.cancel()
if (::btraceRunnable.isInitialized) {
handler.removeCallbacks(btraceRunnable)
}
val title = tvModel.title.value
@@ -97,112 +114,199 @@ class Request {
tvModel.seq = 0
val data = ysp?.switch(tvModel)
val request = data?.let { LiveInfoRequest(it) }
call = request?.let { yspApiService.getLiveInfo("guid=${ysp?.getGuid()}; $cookie", it) }
request?.let { yspApiService.getLiveInfo(cookie, it) }
?.enqueue(object : Callback<LiveInfo> {
override fun onResponse(call: Call<LiveInfo>, response: Response<LiveInfo>) {
if (response.isSuccessful) {
val liveInfo = response.body()
if (liveInfo?.data?.playurl != null) {
val chanll = liveInfo.data.chanll
val decodedBytes = Base64.decode(
chanll.substring(9, chanll.length - 3),
Base64.DEFAULT
)
val decodedString = String(decodedBytes)
val regex = Regex("""des_key = "([^"]+).+var des_iv = "([^"]+)""")
val matchResult = regex.find(decodedString)
if (matchResult != null) {
val (key, iv) = matchResult.destructured
val keyBytes = Base64.decode(key, Base64.DEFAULT)
val ivBytes = Base64.decode(iv, Base64.DEFAULT)
val url = liveInfo.data.playurl + "&revoi=" + encryptTripleDES(
keyBytes + byteArrayOf(0, 0, 0, 0, 0, 0, 0, 0),
ivBytes
).uppercase()
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")
if (tvModel.retryTimes < tvModel.retryMaxTimes) {
tvModel.retryTimes++
fetchVideo(tvModel, cookie)
}
}
call?.enqueue(object : Callback<LiveInfo> {
override fun onResponse(call: Call<LiveInfo>, response: Response<LiveInfo>) {
if (response.isSuccessful) {
val liveInfo = response.body()
if (liveInfo?.data?.playurl != null) {
val chanll = liveInfo.data.chanll
val decodedBytes = Base64.decode(
chanll.substring(9, chanll.length - 3),
Base64.DEFAULT
)
val decodedString = String(decodedBytes)
val matchResult = regex.find(decodedString)
if (matchResult != null) {
val (key, iv) = matchResult.destructured
val keyBytes = Base64.decode(key, Base64.DEFAULT)
val ivBytes = Base64.decode(iv, Base64.DEFAULT)
val url = liveInfo.data.playurl + "&revoi=" + encryptTripleDES(
keyBytes + byteArrayOf(0, 0, 0, 0, 0, 0, 0, 0),
ivBytes
).uppercase()
// Log.d(TAG, "$title url $url")
tvModel.addVideoUrl(url)
tvModel.allReady()
tvModel.retryTimes = 0
btraceRunnable = BtraceRunnable(tvModel)
handler.post(btraceRunnable)
} else {
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++
Log.e(TAG, "$title key error")
if (tvModel.retryTimes < tvModel.retryMaxTimes) {
tvModel.retryTimes++
if (tvModel.getTV().needToken) {
if (tvModel.tokenRetryTimes == tvModel.tokenRetryMaxTimes) {
if (!tvModel.getTV().mustToken) {
fetchVideo(tvModel, cookie)
}
} else {
token = ""
fetchVideo(tvModel)
}
} else {
fetchVideo(tvModel, cookie)
}
}
}
} else {
Log.e(TAG, "$title status error")
if (tvModel.retryTimes < tvModel.retryMaxTimes) {
tvModel.retryTimes++
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++
if (tvModel.getTV().needToken) {
if (tvModel.tokenRetryTimes == tvModel.tokenRetryMaxTimes) {
if (!tvModel.getTV().mustToken) {
fetchVideo(tvModel, cookie)
}
} else {
token = ""
fetchVideo(tvModel)
}
} else {
fetchVideo(tvModel, cookie)
}
}
}
}
} else {
Log.e(TAG, "$title status error")
if (tvModel.retryTimes < tvModel.retryMaxTimes) {
tvModel.retryTimes++
if (tvModel.getTV().needToken) {
if (tvModel.tokenRetryTimes == tvModel.tokenRetryMaxTimes) {
if (!tvModel.getTV().mustToken) {
fetchVideo(tvModel, cookie)
}
} else {
token = ""
fetchVideo(tvModel)
}
} else {
fetchVideo(tvModel, cookie)
}
}
}
}
override fun onFailure(call: Call<LiveInfo>, t: Throwable) {
Log.e(TAG, "$title request error")
if (tvModel.retryTimes < tvModel.retryMaxTimes) {
tvModel.retryTimes++
override fun onFailure(call: Call<LiveInfo>, t: Throwable) {
Log.e(TAG, "$title request error $t")
if (tvModel.retryTimes < tvModel.retryMaxTimes) {
tvModel.retryTimes++
if (tvModel.getTV().needToken) {
if (tvModel.tokenRetryTimes == tvModel.tokenRetryMaxTimes) {
if (!tvModel.getTV().mustToken) {
fetchVideo(tvModel, cookie)
}
} else {
token = ""
fetchVideo(tvModel)
}
} else {
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 token = response.body()?.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)
if (token == "") {
yspTokenService.getInfo()
.enqueue(object : Callback<Info> {
override fun onResponse(call: Call<Info>, response: Response<Info>) {
if (response.isSuccessful) {
token = response.body()?.data?.token!!
Log.i(TAG, "info success $token")
val cookie =
"versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; deviceModel=120; yspappid=519748109;yspopenid=vu0-8lgGV2LW9QjDeuBFsX8yMnzs37Q3_HZF6XyVDpGR_I; vusession=$token"
fetchVideo(tvModel, cookie)
} else {
Log.e(TAG, "info status error")
if (tvModel.tokenRetryTimes < tvModel.tokenRetryMaxTimes) {
tvModel.tokenRetryTimes++
fetchVideo(tvModel)
} else {
if (!tvModel.getTV().mustToken) {
val cookie =
"versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; deviceModel=120; yspappid=519748109"
fetchVideo(tvModel, cookie)
}
}
}
}
}
override fun onFailure(call: Call<Info>, t: Throwable) {
Log.e(TAG, "info request error $t")
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.tokenRetryTimes < tvModel.tokenRetryMaxTimes) {
tvModel.tokenRetryTimes++
fetchVideo(tvModel)
} else {
if (!tvModel.getTV().mustToken) {
val cookie =
"versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; deviceModel=120; yspappid=519748109"
fetchVideo(tvModel, cookie)
}
}
}
}
})
}
fun fetchData(tvModel: TVViewModel) {
if (tvModel.needToken) {
fetchVideo(tvModel)
})
} else {
val cookie = "guid=1; vplatform=109"
val cookie =
"versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; deviceModel=120; yspappid=519748109;yspopenid=vu0-8lgGV2LW9QjDeuBFsX8yMnzs37Q3_HZF6XyVDpGR_I; vusession=$token"
fetchVideo(tvModel, cookie)
}
}
inner class MyRunnable(private val tvModel: TVViewModel) : Runnable {
fun fetchData(tvModel: TVViewModel) {
if (tvModel.getTV().needToken) {
fetchVideo(tvModel)
} else {
val cookie =
"versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; deviceModel=120; yspappid=519748109"
fetchVideo(tvModel, cookie)
}
}
inner class TokenRunnable : Runnable {
override fun run() {
fetchToken()
handler.postDelayed(this, 600000)
}
}
fun fetchToken() {
yspTokenService.getInfo()
.enqueue(object : Callback<Info> {
override fun onResponse(call: Call<Info>, response: Response<Info>) {
if (response.isSuccessful) {
token = response.body()?.data?.token!!
Log.i(TAG, "info success $token")
} else {
Log.e(TAG, "token status error")
}
}
override fun onFailure(call: Call<Info>, t: Throwable) {
Log.e(TAG, "token request error $t")
}
})
}
inner class BtraceRunnable(private val tvModel: TVViewModel) : Runnable {
override fun run() {
fetchBtrace(tvModel)
handler.postDelayed(this, 60000)
@@ -299,8 +403,10 @@ class Request {
val program = response.body()
if (program != null) {
tvViewModel.addProgram(program.dataListList)
Log.i(TAG, "$title program ${program.dataListList.size}")
Log.d(TAG, "$title program ${program.dataListList.size}")
}
} else {
Log.w(TAG, "$title program error")
}
}
@@ -311,15 +417,12 @@ class Request {
}
private fun encryptTripleDES(key: ByteArray, iv: ByteArray): String {
val plaintext =
"""{"mver":"1","subver":"1.2","host":"www.yangshipin.cn/#/tv/home?pid=","referer":"","canvas":"YSPANGLE(Apple,AppleM1Pro,OpenGL4.1)"}"""
return try {
val keySpec = SecretKeySpec(key, "DESede")
val ivSpec = IvParameterSpec(iv)
val cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding")
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec)
val encryptedBytes = cipher.doFinal(plaintext.toByteArray())
return encryptedBytes.let { it -> it.joinToString("") { "%02x".format(it) } }
return cipher.doFinal(input).let { it -> it.joinToString("") { "%02x".format(it) } }
} catch (e: Exception) {
e.printStackTrace()
""
@@ -0,0 +1,58 @@
package com.lizongying.mytv
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.DialogFragment
import com.lizongying.mytv.databinding.DialogBinding
class SettingFragment(private val versionName: String,
private val channelReversal: Boolean,
private val channelNum: Boolean,
) :
DialogFragment() {
private var _binding: DialogBinding? = null
private val binding get() = _binding!!
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(STYLE_NO_TITLE, 0)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = DialogBinding.inflate(inflater, container, false)
_binding?.version?.text =
"当前版本: $versionName\n获取最新: https://github.com/lizongying/my-tv/releases/"
val switchChannelReversal = _binding?.switchChannelReversal
switchChannelReversal?.isChecked = channelReversal
switchChannelReversal?.setOnCheckedChangeListener { _, isChecked ->
(activity as MainActivity).saveChannelReversal(isChecked)
}
val switchChannelNum = _binding?.switchChannelNum
switchChannelNum?.isChecked = channelNum
switchChannelNum?.setOnCheckedChangeListener { _, isChecked ->
(activity as MainActivity).saveChannelNum(isChecked)
}
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
companion object {
const val TAG = "SettingFragment"
}
}
@@ -7,10 +7,14 @@ data class TV(
var title: String,
var videoUrl: List<String>,
var videoIndex: Int = 0,
var channel: String = "",
var logo: String = "",
var pid: String = "",
var sid: String = "",
var programId: String = "",
var needToken:Boolean=false,
var mustToken:Boolean=false,
) : Serializable {
override fun toString(): String {
+39 -293
View File
@@ -1,308 +1,54 @@
package com.lizongying.mytv
import android.content.Context
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import java.io.File
object TVList {
val list: Map<String, List<TV>> by lazy {
setupTV()
lateinit var list: Map<String, List<TV>>
private val channels = "channels.json"
fun init(context: Context) {
if (::list.isInitialized) {
return
}
synchronized(this) {
if (::list.isInitialized) {
return
}
list = setupTV(context)
}
}
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",
"CCTV4 中文国际" to "https://resources.yangshipin.cn/assets/oms/image/202306/f357e58fdbcc076a3d65e1f958c942b2e14f14342c60736ceed98b092d35356a.png?imageMogr2/format/webp",
"CCTV5 体育" to "https://resources.yangshipin.cn/assets/oms/image/202306/0a6a7138952675983a3d854df7688557b286d59aa06166edae51506f9204d655.png?imageMogr2/format/webp",
"CCTV5+ 体育赛事" to "https://resources.yangshipin.cn/assets/oms/image/202306/649ad76a90bfef55b05db9fe52e006487280f619089099d5dc971e387fc6eff0.png?imageMogr2/format/webp",
"CCTV6 电影" to "https://resources.yangshipin.cn/assets/oms/image/202306/741515efda91f03f455df8a7da4ee11fa9329139c276435cf0a9e2af398d5bf2.png?imageMogr2/format/webp",
"CCTV7 国防军事" to "https://resources.yangshipin.cn/assets/oms/image/202306/b29af94e295ebdf646cefb68122c429b9cd921f498ca20d2d8070252536f9ff9.png?imageMogr2/format/webp",
"CCTV8 电视剧" to "https://resources.yangshipin.cn/assets/oms/image/202306/ad51de94426a0ba039e6dd6a8534ea98ecc813a6176bde87b4f18cc34d6d7590.png?imageMogr2/format/webp",
"CCTV9 记录" to "https://resources.yangshipin.cn/assets/oms/image/202306/2ed1b4deeca179d5db806bb941790f82eb92a1b7299c1c38fe027f95a5caee5e.png?imageMogr2/format/webp",
"CCTV10 科教" to "https://resources.yangshipin.cn/assets/oms/image/202306/aa6157ec65188cd41826e5a2f088c3d6d153205f5f6428258d12c59999e221aa.png?imageMogr2/format/webp",
"CCTV11 戏曲" to "https://resources.yangshipin.cn/assets/oms/image/202306/ed12ed7c7a1034dae4350011fe039284c5d5a836506b28c9e32e3c75299625c0.png?imageMogr2/format/webp",
"CCTV12 社会与法" to "https://resources.yangshipin.cn/assets/oms/image/202306/484083cffaa40df7e659565e8cb4d1cc740158a185512114167aa21fa0c59240.png?imageMogr2/format/webp",
"CCTV13 新闻" to "https://resources.yangshipin.cn/assets/oms/image/202306/266da7b43c03e2312186b4a999e0f060e8f15b10d2cc2c9aa32171819254cf1a.png?imageMogr2/format/webp",
"CCTV14 少儿" to "https://resources.yangshipin.cn/assets/oms/image/202306/af6b603896938dc346fbb16abfc63c12cba54b0ec9d18770a15d347d115f12d5.png?imageMogr2/format/webp",
"CCTV15 音乐" to "https://resources.yangshipin.cn/assets/oms/image/202306/2ceee92188ef684efe0d8b90839c4f3ad450d179dc64d59beff417059453af47.png?imageMogr2/format/webp",
"CCTV16 奥林匹克" to "https://resources.yangshipin.cn/assets/oms/image/202306/53793fa7bacd3a93ff6dc5d2758418985e1f952a316c335d663b572d8bdcd74d.png?imageMogr2/format/webp",
"CCTV17 农业农村" to "https://resources.yangshipin.cn/assets/oms/image/202306/ddef563072f8bad2bea5b9e52674cb7b4ed50efb20c26e61994dfbdf05c1e3c0.png?imageMogr2/format/webp",
"CGTN" to "https://resources.yangshipin.cn/assets/oms/image/202306/a72dff758ca1c17cd0ecc8cedc11b893d208f409d5e6302faa0e9d298848abc3.png?imageMogr2/format/webp",
"CGTN 法语频道" to "https://resources.yangshipin.cn/assets/oms/image/202306/a8d0046a47433d952bf6ed17062deb8bd2184ba9aec0f7781df6bf9487a3ffcf.png?imageMogr2/format/webp",
"CGTN 俄语频道" to "https://resources.yangshipin.cn/assets/oms/image/202306/bf0a820893cbaf20dd0333e27042e1ef9c8806e5b602b6a8c95af399db0bc77a.png?imageMogr2/format/webp",
"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",
"高尔夫网球" to "https://resources.yangshipin.cn/assets/oms/image/202306/cdd1b31ede7a5ad049ed53d9a072422f829e72dd062ed2c19e077fdd01699071.png?imageMogr2/format/webp",
"女性时尚" to "https://resources.yangshipin.cn/assets/oms/image/202306/fa28955ce8b2539d728bf4c6a13a46ff57ad76eae46627f7bcfb1ed8a613d3fc.png?imageMogr2/format/webp",
"央视文化精品" to "https://resources.yangshipin.cn/assets/oms/image/202306/14ac5ce40482cacd3d4b37435222bfe86af2b452a2f04ecbfc1d13d76edd7c57.png?imageMogr2/format/webp",
"央视台球" 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",
"辽宁卫视" to "https://resources.yangshipin.cn/assets/oms/image/202306/ac4ed6058a87c101ae7147ebc38905d0cae047fb73fd277ee5049b84f52bda36.png?imageMogr2/format/webp",
"江苏卫视" to "https://resources.yangshipin.cn/assets/oms/image/202306/380ad685c0c1d5b2c902246b8d2df6d3f9b45e2837abcfe493075bbded597a31.png?imageMogr2/format/webp",
"江西卫视" to "https://resources.yangshipin.cn/assets/oms/image/202306/3c760d0d00463855890e8a1864ea4a6b6dd66b90c29b4ac714a4b17c16519871.png?imageMogr2/format/webp",
"山东卫视" to "https://resources.yangshipin.cn/assets/oms/image/202306/22d403f07a7cf5410b3ad3ddb65a11aa229a32475fac213f5344c9f0ec330ca1.png?imageMogr2/format/webp",
"广东卫视" to "https://resources.yangshipin.cn/assets/oms/image/202306/28886880a4dc0f06fb7e0a528a1def0591d61a65870e29176ede0cc92033bbfd.png?imageMogr2/format/webp",
"广西卫视" to "https://resources.yangshipin.cn/assets/oms/image/202306/54b7e97cb816bb223fe05f3fc44da2c7820eb66e8550c19d23100f2c414ecc38.png?imageMogr2/format/webp",
"重庆卫视" to "https://resources.yangshipin.cn/assets/oms/image/202306/657651f411de2673d1770d9a78b44c1265704f7468cc41d4be7f51d630768494.png?imageMogr2/format/webp",
"河南卫视" to "https://resources.yangshipin.cn/assets/oms/image/202306/74925962148a6d31c85808b6cd4e444c2a54bab393d2c5fc85e960b50e22fa86.png?imageMogr2/format/webp",
"河北卫视" to "https://resources.yangshipin.cn/assets/oms/image/202306/d545becdc81c60197b08c7f47380705e4665ed3fe55efc8b855e486f6e655378.png?imageMogr2/format/webp",
"贵州卫视" to "https://resources.yangshipin.cn/assets/oms/image/202306/4eb45f4781d33d872af027dc01c941559aab55667dd99cc5c22bef7037807b13.png?imageMogr2/format/webp",
"北京卫视" to "https://resources.yangshipin.cn/assets/oms/image/202306/f4f23633c578beea49a3841d88d3490100f029ee349059fa532869db889872c5.png?imageMogr2/format/webp",
"黑龙江卫视" to "https://resources.yangshipin.cn/assets/oms/image/202306/d8273ae9be698ce2db21f5b886ecac95a73429593f93713c60ed8c12c38bf0d3.png?imageMogr2/format/webp",
"浙江卫视" to "https://resources.yangshipin.cn/assets/oms/image/202306/a66c836bd98ba3e41a2e9a570d4b9c50dedc6839e9de333e2e78212ad505f37e.png?imageMogr2/format/webp",
"安徽卫视" to "https://resources.yangshipin.cn/assets/oms/image/202306/f35fa04b51b1ee4984b03578b65403570868ebca03c6c01e11b097f999a58d9b.png?imageMogr2/format/webp",
"深圳卫视" to "https://resources.yangshipin.cn/assets/oms/image/202306/d59fec04c902e3581c617136d02d4b9b8c4cbe64272781ddd3525e80c823edb7.png?imageMogr2/format/webp",
"四川卫视" to "https://resources.yangshipin.cn/assets/oms/image/202306/3276a414ae0eaa0f116f2045cd913367967d0c7c1e978e8621ac3879436c6ed7.png?imageMogr2/format/webp",
"东南卫视" to "https://resources.yangshipin.cn/assets/oms/image/202306/3208fe6564a293c21b711333fb3edb05bb5b406cff840573c9a8d839680a1579.png?imageMogr2/format/webp",
"海南卫视" to "https://resources.yangshipin.cn/assets/oms/image/202306/6e060391fde0469801fc3d84dbf204b4f8d650d251f17d7595a6964c0bb99e81.png?imageMogr2/format/webp",
)
private var mappingEPG = mapOf(
"CCTV4K 超高清" to "600002264",
"CCTV8K 超高清" to "600156816",
"CCTV1 综合" to "600001859",
"CCTV2 财经" to "600001800",
"CCTV3 综艺" to "600001801",
"CCTV4 中文国际" to "600001814",
"CCTV5 体育" to "600001818",
"CCTV5+ 体育赛事" to "600001817",
"CCTV6 电影" to "600001802",
"CCTV7 国防军事" to "600004092",
"CCTV8 电视剧" to "600001803",
"CCTV9 记录" to "600004078",
"CCTV10 科教" to "600001805",
"CCTV11 戏曲" to "600001806",
"CCTV12 社会与法" to "600001807",
"CCTV13 新闻" to "600001811",
"CCTV14 少儿" to "600001809",
"CCTV15 音乐" to "600001815",
"CCTV16 奥林匹克" to "600098637",
"CCTV17 农业农村" to "600001810",
"CGTN" to "600014550",
"CGTN 法语频道" to "600084704",
"CGTN 俄语频道" to "600084758",
"CGTN 阿拉伯语频道" to "600084782",
"CGTN 西班牙语频道" to "600084744",
"CGTN 记录频道" to "600084781",
"风云剧场" to "600099658",
"第一剧场" to "600099655",
"怀旧剧场" to "600099620",
"世界地理" to "600099637",
"风云音乐" to "600099660",
"兵器科技" to "600099649",
"风云足球" to "600099636",
"高尔夫网球" to "600099659",
"女性时尚" to "600099650",
"央视文化精品" to "600099653",
"央视台球" to "600099652",
"电视指南" to "600099656",
"卫生健康" to "600099651",
"东方卫视" to "600002483",
"湖南卫视" to "600002475",
"湖北卫视" to "600002508",
"辽宁卫视" to "600002505",
"江苏卫视" to "600002521",
"江西卫视" to "600002503",
"山东卫视" to "600002513",
"广东卫视" to "600002485",
"广西卫视" to "600002509",
"重庆卫视" to "600002531",
"河南卫视" to "600002525",
"河北卫视" to "600002493",
"贵州卫视" to "600002490",
"北京卫视" to "600002309",
"黑龙江卫视" to "600002498",
"浙江卫视" to "600002520",
"安徽卫视" to "600002532",
"深圳卫视" to "600002481",
"四川卫视" to "600002516",
"东南卫视" to "600002484",
"海南卫视" to "600002506",
)
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", "2000203803"),
"CCTV4 中文国际" to arrayOf("600001814", "2000204803"),
"CCTV5 体育" to arrayOf("600001818", "2000205103"),
"CCTV5+ 体育赛事" to arrayOf("600001817", "2000204503"),
"CCTV6 电影" to arrayOf("600001802", "2000203303"),
"CCTV7 国防军事" to arrayOf("600004092", "2000510003"),
"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","2000204603"),
"CCTV14 少儿" to arrayOf("600001809", "2000204403"),
"CCTV15 音乐" to arrayOf("600001815", "2000205003"),
"CCTV16 奥林匹克" to arrayOf("600098637", "2012375003"),
"CCTV17 农业农村" to arrayOf("600001810", "2000204203"),
"CGTN" to arrayOf("600014550", "2001656803"),
"CGTN 法语频道" to arrayOf("600084704", "2010153503"),
"CGTN 俄语频道" to arrayOf("600084758", "2010152603"),
"CGTN 阿拉伯语频道" to arrayOf("600084782", "2010155203"),
"CGTN 西班牙语频道" to arrayOf("600084744", "2010152503"),
"CGTN 记录频道" to arrayOf("600084781", "2010155403"),
"风云剧场" 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"),
"辽宁卫视" to arrayOf("600002505", "2000281303"),
"江苏卫视" to arrayOf("600002521", "2000295603"),
"江西卫视" to arrayOf("600002503", "2000294103"),
"山东卫视" to arrayOf("600002513", "2000294803"),
"广东卫视" to arrayOf("600002485", "2000292703"),
"广西卫视" to arrayOf("600002509", "2000294203"),
"重庆卫视" to arrayOf("600002531", "2000297803"),
"河南卫视" to arrayOf("600002525", "2000296103"),
"河北卫视" to arrayOf("600002493", "2000293403"),
"贵州卫视" to arrayOf("600002490", "2000293303"),
"北京卫视" to arrayOf("600002309", "2000272103"),
"黑龙江卫视" to arrayOf("600002498", "2000293903"),
"浙江卫视" to arrayOf("600002520", "2000295503"),
"安徽卫视" to arrayOf("600002532", "2000298003"),
"深圳卫视" to arrayOf("600002481", "2000292203"),
"四川卫视" to arrayOf("600002516", "2000295003"),
"东南卫视" to arrayOf("600002484", "2000292503"),
"海南卫视" to arrayOf("600002506", "2000291503"),
)
private var count: Int = 0
private fun setupTV(): Map<String, List<TV>> {
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
CCTV4 中文国际,http://39.134.24.161/dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226191/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226191/index.m3u8
CCTV5 体育,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226395/index.m3u8
CCTV5+ 体育赛事,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226221/index.m3u8
CCTV6 电影,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226393/index.m3u8
CCTV7 国防军事,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226192/index.m3u8
CCTV8 电视剧,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226391/index.m3u8
CCTV9 记录,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226197/index.m3u8
CCTV10 科教,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226189/index.m3u8
CCTV11 戏曲,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226240/index.m3u8
CCTV12 社会与法,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226190/index.m3u8
CCTV13 新闻,https://live-play.cctvnews.cctv.com/cctv/merge_cctv13.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226233/index.m3u8
CCTV14 少儿,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226193/index.m3u8
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
风云剧场,http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226950/index.m3u8
第一剧场,http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226959/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
湖北卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226477/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226194/index.m3u8
辽宁卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226546/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226201/index.m3u8
江苏卫视,http://39.134.24.166/dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226200/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226200/index.m3u8
江西卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226344/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225764/index.m3u8
山东卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226209/index.m3u8
广东卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226216/index.m3u8
广西卫视,http://live.gxrb.com.cn/tv/gxtvlive03/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225770/index.m3u8
重庆卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226409/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226202/index.m3u8
河南卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226480/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225767/index.m3u8
河北卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226406/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225750/index.m3u8
贵州卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226474/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225793/index.m3u8
北京卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221225728/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226222/index.m3u8
黑龙江卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226327/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226215/index.m3u8
浙江卫视,http://hw-m-l.cztv.com/channels/lantian/channel01/1080p.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226199/index.m3u8
安徽卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226391/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226203/index.m3u8
深圳卫视,http://39.134.24.166/dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226205/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226205/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()
private fun setupTV(context: Context): Map<String, List<TV>> {
val map: MutableMap<String, MutableList<TV>> = mutableMapOf()
val appDirectory = Utils.getAppDirectory(context)
var channel = ""
for (i in tvs.split("\n")) {
if (i.trim() == "") {
continue
}
if (!i.contains(",")) {
channel = i.trim()
if (channel == "移动专区") {
break
//检查当前目录下是否存在channels.json
val file = File(appDirectory, channels)
if (!file.exists()) {
//不存在则从assets中拷贝
file.createNewFile()
context.resources.openRawResource(R.raw.channels).use { input ->
file.outputStream().use { output ->
input.copyTo(output)
}
continue
}
val p = i.split(",")
val titleMap = map[channel] ?: mutableListOf()
val tv = TV(
count,
p[0],
p[1].split(";").map { it.trim() }
)
val logo = mappingLogo[tv.title]
if (logo != null && logo != "") {
tv.logo = logo
}
val programId = mappingEPG[tv.title]
if (programId != null && programId != "") {
tv.programId = programId
}
val video = mappingVideo[tv.title]
if (video != null) {
tv.pid = video[0]
tv.sid = video[1]
}
titleMap.add(tv)
map[channel] = titleMap
count++
}
//读取channels.json,并转换为Map<String,LIst<TV>>
val json = file.readText()
//防止类型擦除
val type = object : TypeToken<Array<TV>>() {}.type
Gson().fromJson<Array<TV>>(json, type)?.forEach {
if (map.containsKey(it.channel)) {
map[it.channel]?.add(it)
} else {
map[it.channel] = mutableListOf(it)
}
}
return map
}
}
+63 -2
View File
@@ -1,8 +1,12 @@
package com.lizongying.mytv
import android.content.Context
import android.content.res.Resources
import android.util.TypedValue
import java.io.File
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import java.util.*
import java.io.IOException
object Utils {
fun getDateFormat(format: String): String {
@@ -12,4 +16,61 @@ object Utils {
fun getDateTimestamp(): Long {
return Date().time / 1000
}
fun dpToPx(dp: Float): Int {
return TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, dp, Resources.getSystem().displayMetrics
).toInt()
}
fun dpToPx(dp: Int): Int {
return TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, dp.toFloat(), Resources.getSystem().displayMetrics
).toInt()
}
/**
* 获取可读写的目录
*
* @param context 应用环境信息
*
* @return 可读写的目录
*
*/
fun getAppDirectory(context: Context): File {
return context.filesDir
}
/**
* 更新channels.json
*
* @param context 应用环境信息
*
* @return 无
*
* @throws IOException 网络请求失败
*/
fun updateChannel(context: Context) {
val client = okhttp3.OkHttpClient()
val request = okhttp3.Request.Builder().url(getServerUrl(context)).build()
client.newCall(request).execute().use { response ->
if (!response.isSuccessful) throw IOException("Unexpected code $response")
val body = response.body()
//覆盖channels.json
val file = File(getAppDirectory(context), "channels.json")
if (!file.exists()) {
file.createNewFile()
}
file.writeText(body!!.string())
}
}
/**
* 从res/values/server.xml获取服务器地址
* @param context 应用环境信息
* @return 服务器地址
*/
private fun getServerUrl(context: Context): String {
return context.resources.getString(R.string.server_url)
}
}
@@ -5,6 +5,9 @@ import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.converter.protobuf.ProtoConverterFactory
import javax.net.ssl.SSLContext
import javax.net.ssl.TrustManager
import javax.net.ssl.X509TrustManager
class ApiClient {
@@ -13,9 +16,7 @@ class ApiClient {
private val protoUrl = "https://capi.yangshipin.cn/"
private val traceUrl = "https://btrace.yangshipin.cn/"
private var okHttpClient = OkHttpClient.Builder()
.dns(DnsCache())
.build()
private var okHttpClient = getUnsafeOkHttpClient()
val yspApiService: YSPApiService by lazy {
Retrofit.Builder()
@@ -48,4 +49,40 @@ class ApiClient {
.addConverterFactory(GsonConverterFactory.create())
.build().create(YSPBtraceService::class.java)
}
private fun getUnsafeOkHttpClient(): OkHttpClient {
try {
val trustAllCerts: Array<TrustManager> = arrayOf(
object : X509TrustManager {
override fun checkClientTrusted(
chain: Array<out java.security.cert.X509Certificate>?,
authType: String?
) {
}
override fun checkServerTrusted(
chain: Array<out java.security.cert.X509Certificate>?,
authType: String?
) {
}
override fun getAcceptedIssuers(): Array<java.security.cert.X509Certificate> {
return emptyArray()
}
}
)
val sslContext = SSLContext.getInstance("SSL")
sslContext.init(null, trustAllCerts, java.security.SecureRandom())
return OkHttpClient.Builder()
.sslSocketFactory(sslContext.socketFactory, trustAllCerts[0] as X509TrustManager)
.hostnameVerifier { _, _ -> true }
.dns(DnsCache())
.build()
} catch (e: Exception) {
throw RuntimeException(e)
}
}
}
@@ -51,14 +51,14 @@ class YSP(var context: Context) {
private var signature = ""
private var encryptor: Encryptor? = null
private var sharedPref: SharedPreferences? = null
private lateinit var sharedPref: SharedPreferences
init {
if (context is MainActivity) {
encryptor = Encryptor()
encryptor!!.init(context)
sharedPref = (context as MainActivity).getPreferences(Context.MODE_PRIVATE)
sharedPref = (context as MainActivity).sharedPref
}
guid = getGuid()
@@ -71,18 +71,16 @@ class YSP(var context: Context) {
randStr = getRand()
guid = newGuid()
timeStr = getTimeStr()
if (tvModel.retryTimes > 0) {
guid = newGuid()
}
// guid = "lq3oqitm_1e15dnzgjnb"
// randStr = "BfcCPQp8Hq"
// timeStr = "1702166501"
timeStr = getTimeStr()
cKey =
encryptor!!.encrypt(cnlid, timeStr, appVer, guid, platform)
signature = getSignature()
return """{"cnlid":"$cnlid","livepid":"$livepid","stream":"$stream","guid":"$guid","cKey":"$cKey","adjust":$adjust,"sphttps":"$sphttps","platform":"$platform","cmd":"$cmd","encryptVer":"$encryptVer","dtype":"$dtype","devid":"$devid","otype":"$otype","appVer":"$appVer","app_version":"$appVersion","rand_str":"$randStr","channel":"$channel","defn":"$defn","signature":"$signature"}"""
// return """{"cnlid":"2000203803","livepid":"600001801","stream":"2","guid":"lq1y36mb_ccfwmja9zan","cKey":"--01A9F5E89BB86A0C61F4025BEDE15309B6913A79FD1AF1EE7F5EC9C7605F377D1D2281488385C32DEB9E7D0DD3559CB700BD7AF44DD5C9DE0AE14D94B8214027B5D664C108AEE23532348DCC61B86F7C8FBB6CF14D588E6093A25E97DF6D66F4882AB28F17016472DD43D45EF076B7F505176A5E8DEDF2662E5F9AB12B69CB20BCE1579BE724091F3AF6826AE34B713906F3FE139C3783F80EECBD08416DC525E1","adjust":1,"sphttps":"1","platform":"5910204","cmd":"2","encryptVer":"8.1","dtype":"1","devid":"devid","otype":"ojson","appVer":"V1.0.0","app_version":"V1.0.0","rand_str":"IOS2soOw44","channel":"ysp_tx","defn":"fhd","signature":"6ca945f651817de8c6e6910457ceafd6"}"""
}
private fun getTimeStr(): String {
@@ -96,10 +94,10 @@ class YSP(var context: Context) {
}
fun getGuid(): String {
var guid = sharedPref?.getString("guid", "")
var guid = sharedPref.getString("guid", "")
if (guid == null || guid.length < 18) {
guid = generateGuid()
with(sharedPref!!.edit()) {
with(sharedPref.edit()) {
putString("guid", guid)
apply()
}
@@ -107,9 +105,9 @@ class YSP(var context: Context) {
return guid
}
fun newGuid(): String {
private fun newGuid(): String {
guid = generateGuid()
with(sharedPref!!.edit()) {
with(sharedPref.edit()) {
putString("guid", guid)
apply()
}
@@ -8,7 +8,9 @@ class TVListViewModel : ViewModel() {
var maxNum = mutableListOf<Int>()
private val tvListViewModel = MutableLiveData<MutableList<TVViewModel>>()
private val _tvListViewModel = MutableLiveData<MutableList<TVViewModel>>()
val tvListViewModel: LiveData<MutableList<TVViewModel>>
get() = _tvListViewModel
private val _itemPosition = MutableLiveData<Int>()
val itemPosition: LiveData<Int>
@@ -18,22 +20,20 @@ class TVListViewModel : ViewModel() {
val itemPositionCurrent: LiveData<Int>
get() = _itemPositionCurrent
fun getTVListViewModel(): MutableLiveData<MutableList<TVViewModel>> {
return tvListViewModel
}
fun addTVViewModel(tvViewModel: TVViewModel) {
val currentTVModelList = tvListViewModel.value ?: mutableListOf()
currentTVModelList.add(tvViewModel)
tvListViewModel.value = currentTVModelList
if (_tvListViewModel.value == null) {
_tvListViewModel.value = mutableListOf(tvViewModel)
} else {
_tvListViewModel.value?.add(tvViewModel)
}
}
fun getTVViewModel(id: Int): TVViewModel? {
return tvListViewModel.value?.get(id)
return _tvListViewModel.value?.get(id)
}
fun getTVViewModelCurrent(): TVViewModel? {
return _itemPositionCurrent.value?.let { tvListViewModel.value?.get(it) }
return _itemPositionCurrent.value?.let { _tvListViewModel.value?.get(it) }
}
fun setItemPosition(position: Int) {
@@ -46,6 +46,9 @@ class TVListViewModel : ViewModel() {
}
fun size(): Int {
return tvListViewModel.value!!.size
if (_tvListViewModel.value == null) {
return 0
}
return _tvListViewModel.value!!.size
}
}
@@ -19,8 +19,10 @@ 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 retryTimes = 0
var tokenRetryTimes = 0
var retryMaxTimes = 8
var tokenRetryMaxTimes = 2
var programUpdateTime: Long = 0
private val _errInfo = MutableLiveData<String>()
@@ -75,26 +77,6 @@ class TVViewModel(private var tv: TV) : ViewModel() {
var seq = 0
var needToken = false
private val channelsNeedToken = arrayOf(
"CCTV3 综艺",
"CCTV6 电影",
"CCTV8 电视剧",
"风云剧场",
"第一剧场",
"怀旧剧场",
"世界地理",
"风云音乐",
"兵器科技",
"风云足球",
"高尔夫网球",
"女性时尚",
"央视文化精品",
"央视台球",
"电视指南",
"卫生健康",
)
fun addVideoUrl(url: String) {
if (_videoUrl.value?.isNotEmpty() == true) {
@@ -146,12 +128,9 @@ 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 {
return rowPosition
}
@@ -222,18 +201,25 @@ class TVViewModel(private var tv: TV) : ViewModel() {
mMinimumLoadableRetryCount = minimumLoadableRetryCount
}
/**
* (playerView?.player as ExoPlayer).setMediaSource(tvViewModel.buildSource())
*/
@OptIn(UnstableApi::class)
fun buildSource(videoUrl: String, mHeaders: Map<String, String>?): HlsMediaSource {
fun buildSource(): HlsMediaSource {
val httpDataSource = DefaultHttpDataSource.Factory()
mHeaders?.let { httpDataSource.setDefaultRequestProperties(it) }
return HlsMediaSource.Factory(httpDataSource).createMediaSource(
MediaItem.fromUri(
Uri.parse(videoUrl)
Uri.parse(getVideoUrlCurrent())
)
)
}
fun getVideoUrlCurrent(): String {
return _videoUrl.value!![_videoIndex.value!!]
}
companion object {
private const val TAG = "TVViewModel"
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

+20
View File
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/channel_fragment"
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_gravity="end|top"
android:layout_marginTop="20dp"
android:layout_marginEnd="50dp">
<TextView
android:id="@+id/channel_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_marginTop="0dp"
android:text="99"
android:textColor="#FFEEEEEE"
android:textStyle="bold"
android:textSize="50sp" />
</FrameLayout>
+50
View File
@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/dialog"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="300dp"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingTop="20dp"
android:paddingLeft="7dp"
android:paddingRight="7dp"
android:background="#FF263238"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/app_name"
android:textSize="16sp" />
<TextView
android:id="@+id/version"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="当前版本: $versionName\n获取最新: https://github.com/lizongying/my-tv/releases/"
></TextView>
<Switch
android:id="@+id/switch_channel_reversal"
android:text="@string/title_channel_reversal"
android:layout_marginTop="10dp"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Switch
android:id="@+id/switch_channel_num"
android:text="@string/title_channel_num"
android:layout_marginTop="10dp"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
<ImageView
android:layout_width="300dp"
android:layout_height="wrap_content"
android:src="@drawable/appreciate"
android:background="@color/white" />
</LinearLayout>
</FrameLayout>
-1
View File
@@ -12,7 +12,6 @@
android:layout_width="100dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:contentDescription="@string/logo"
android:padding="10dp" />
<LinearLayout
+954
View File
@@ -0,0 +1,954 @@
[
{
"id": 0,
"videoIndex": 0,
"channel": "央视频道",
"logo": "https://resources.yangshipin.cn/assets/oms/image/202306/d57905b93540bd15f0c48230dbbbff7ee0d645ff539e38866e2d15c8b9f7dfcd.png?imageMogr2/format/webp",
"pid": "600001859",
"sid": "2000210103",
"programId": "600001859",
"needToken": false,
"mustToken": false,
"title": "CCTV1 综合",
"videoUrl": [
"http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226231/index.m3u8"
]
},
{
"id": 1,
"videoIndex": 0,
"channel": "央视频道",
"logo": "https://resources.yangshipin.cn/assets/oms/image/202306/20115388de0207131af17eac86c33049b95d69eaff064e55653a1b941810a006.png?imageMogr2/format/webp",
"pid": "600001800",
"sid": "2000203603",
"programId": "600001800",
"needToken": true,
"mustToken": false,
"title": "CCTV2 财经",
"videoUrl": [
"http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226195/index.m3u8"
]
},
{
"id": 2,
"videoIndex": 0,
"channel": "央视频道",
"logo": "https://resources.yangshipin.cn/assets/oms/image/202306/7b7a65c712450da3deb6ca66fbacf4f9aee00d3f20bd80eafb5ada01ec63eb3a.png?imageMogr2/format/webp",
"pid": "600001801",
"sid": "2000203803",
"programId": "600001801",
"needToken": true,
"mustToken": true,
"title": "CCTV3 综艺",
"videoUrl": [
"http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226397/index.m3u8"
]
},
{
"id": 3,
"videoIndex": 0,
"channel": "央视频道",
"logo": "https://resources.yangshipin.cn/assets/oms/image/202306/f357e58fdbcc076a3d65e1f958c942b2e14f14342c60736ceed98b092d35356a.png?imageMogr2/format/webp",
"pid": "600001814",
"sid": "2000204803",
"programId": "600001814",
"needToken": false,
"mustToken": false,
"title": "CCTV4 中文国际",
"videoUrl": [
"http://39.134.24.161/dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226191/index.m3u8",
"http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226191/index.m3u8"
]
},
{
"id": 4,
"videoIndex": 0,
"channel": "央视频道",
"logo": "https://resources.yangshipin.cn/assets/oms/image/202306/0a6a7138952675983a3d854df7688557b286d59aa06166edae51506f9204d655.png?imageMogr2/format/webp",
"pid": "600001818",
"sid": "2000205103",
"programId": "600001818",
"needToken": true,
"mustToken": false,
"title": "CCTV5 体育",
"videoUrl": [
"http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226395/index.m3u8"
]
},
{
"id": 5,
"videoIndex": 0,
"channel": "央视频道",
"logo": "https://resources.yangshipin.cn/assets/oms/image/202306/741515efda91f03f455df8a7da4ee11fa9329139c276435cf0a9e2af398d5bf2.png?imageMogr2/format/webp",
"pid": "600001802",
"sid": "2013693901",
"programId": "600001802",
"needToken": true,
"mustToken": true,
"title": "CCTV6 电影",
"videoUrl": [
"http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226393/index.m3u8"
]
},
{
"id": 6,
"videoIndex": 0,
"channel": "央视频道",
"logo": "https://resources.yangshipin.cn/assets/oms/image/202306/b29af94e295ebdf646cefb68122c429b9cd921f498ca20d2d8070252536f9ff9.png?imageMogr2/format/webp",
"pid": "600004092",
"sid": "2000510003",
"programId": "600004092",
"needToken": true,
"mustToken": false,
"title": "CCTV7 国防军事",
"videoUrl": [
"http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226192/index.m3u8"
]
},
{
"id": 7,
"videoIndex": 0,
"channel": "央视频道",
"logo": "https://resources.yangshipin.cn/assets/oms/image/202306/ad51de94426a0ba039e6dd6a8534ea98ecc813a6176bde87b4f18cc34d6d7590.png?imageMogr2/format/webp",
"pid": "600001803",
"sid": "2000203903",
"programId": "600001803",
"needToken": true,
"mustToken": true,
"title": "CCTV8 电视剧",
"videoUrl": [
"http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226391/index.m3u8"
]
},
{
"id": 8,
"videoIndex": 0,
"channel": "央视频道",
"logo": "https://resources.yangshipin.cn/assets/oms/image/202306/2ed1b4deeca179d5db806bb941790f82eb92a1b7299c1c38fe027f95a5caee5e.png?imageMogr2/format/webp",
"pid": "600004078",
"sid": "2000499403",
"programId": "600004078",
"needToken": true,
"mustToken": false,
"title": "CCTV9 记录",
"videoUrl": [
"http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226197/index.m3u8"
]
},
{
"id": 9,
"videoIndex": 0,
"channel": "央视频道",
"logo": "https://resources.yangshipin.cn/assets/oms/image/202306/aa6157ec65188cd41826e5a2f088c3d6d153205f5f6428258d12c59999e221aa.png?imageMogr2/format/webp",
"pid": "600001805",
"sid": "2000203503",
"programId": "600001805",
"needToken": true,
"mustToken": false,
"title": "CCTV10 科教",
"videoUrl": [
"http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226189/index.m3u8"
]
},
{
"id": 10,
"videoIndex": 0,
"channel": "央视频道",
"logo": "https://resources.yangshipin.cn/assets/oms/image/202306/ed12ed7c7a1034dae4350011fe039284c5d5a836506b28c9e32e3c75299625c0.png?imageMogr2/format/webp",
"pid": "600001806",
"sid": "2000204103",
"programId": "600001806",
"needToken": true,
"mustToken": false,
"title": "CCTV11 戏曲",
"videoUrl": [
"http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226240/index.m3u8"
]
},
{
"id": 11,
"videoIndex": 0,
"channel": "央视频道",
"logo": "https://resources.yangshipin.cn/assets/oms/image/202306/484083cffaa40df7e659565e8cb4d1cc740158a185512114167aa21fa0c59240.png?imageMogr2/format/webp",
"pid": "600001807",
"sid": "2000202603",
"programId": "600001807",
"needToken": true,
"mustToken": false,
"title": "CCTV12 社会与法",
"videoUrl": [
"http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226190/index.m3u8"
]
},
{
"id": 12,
"videoIndex": 0,
"channel": "央视频道",
"logo": "https://resources.yangshipin.cn/assets/oms/image/202306/266da7b43c03e2312186b4a999e0f060e8f15b10d2cc2c9aa32171819254cf1a.png?imageMogr2/format/webp",
"pid": "600001811",
"sid": "2000204603",
"programId": "600001811",
"needToken": false,
"mustToken": false,
"title": "CCTV13 新闻",
"videoUrl": [
"https://live-play.cctvnews.cctv.com/cctv/merge_cctv13.m3u8",
"http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226233/index.m3u8"
]
},
{
"id": 13,
"videoIndex": 0,
"channel": "央视频道",
"logo": "https://resources.yangshipin.cn/assets/oms/image/202306/af6b603896938dc346fbb16abfc63c12cba54b0ec9d18770a15d347d115f12d5.png?imageMogr2/format/webp",
"pid": "600001809",
"sid": "2000204403",
"programId": "600001809",
"needToken": true,
"mustToken": false,
"title": "CCTV14 少儿",
"videoUrl": [
"http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226193/index.m3u8"
]
},
{
"id": 14,
"videoIndex": 0,
"channel": "央视频道",
"logo": "https://resources.yangshipin.cn/assets/oms/image/202306/2ceee92188ef684efe0d8b90839c4f3ad450d179dc64d59beff417059453af47.png?imageMogr2/format/webp",
"pid": "600001815",
"sid": "2000205003",
"programId": "600001815",
"needToken": true,
"mustToken": false,
"title": "CCTV15 音乐",
"videoUrl": [
"http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225785/index.m3u8"
]
},
{
"id": 15,
"videoIndex": 0,
"channel": "央视频道",
"logo": "https://resources.yangshipin.cn/assets/oms/image/202306/53793fa7bacd3a93ff6dc5d2758418985e1f952a316c335d663b572d8bdcd74d.png?imageMogr2/format/webp",
"pid": "600098637",
"sid": "2012375003",
"programId": "600098637",
"needToken": true,
"mustToken": false,
"title": "CCTV16 奥林匹克",
"videoUrl": [
"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"
]
},
{
"id": 16,
"videoIndex": 0,
"channel": "央视频道",
"logo": "https://resources.yangshipin.cn/assets/oms/image/202306/ddef563072f8bad2bea5b9e52674cb7b4ed50efb20c26e61994dfbdf05c1e3c0.png?imageMogr2/format/webp",
"pid": "600001810",
"sid": "2000204203",
"programId": "600001810",
"needToken": true,
"mustToken": false,
"title": "CCTV17 农业农村",
"videoUrl": [
"http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226198/index.m3u8"
]
},
{
"id": 17,
"videoIndex": 0,
"channel": "央视频道",
"logo": "https://resources.yangshipin.cn/assets/oms/image/202306/649ad76a90bfef55b05db9fe52e006487280f619089099d5dc971e387fc6eff0.png?imageMogr2/format/webp",
"pid": "600001817",
"sid": "2000204503",
"programId": "600001817",
"needToken": true,
"mustToken": false,
"title": "CCTV5+ 体育赛事",
"videoUrl": [
"http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226221/index.m3u8"
]
},
{
"id": 18,
"videoIndex": 0,
"channel": "央视频道",
"logo": "https://resources.yangshipin.cn/assets/oms/image/202306/3e9d06fd7244d950df5838750f1c6ac3456e172b51caca2c16d2282125b111e8.png?imageMogr2/format/webp",
"pid": "600002264",
"sid": "2000266303",
"programId": "600002264",
"needToken": true,
"mustToken": false,
"title": "CCTV4K 超高清",
"videoUrl": [
""
]
},
{
"id": 19,
"videoIndex": 0,
"channel": "央视频道",
"logo": "",
"pid": "600156816",
"sid": "2020603421",
"programId": "",
"needToken": false,
"mustToken": false,
"title": "CCTV8K 超高清",
"videoUrl": [
""
]
},
{
"id": 20,
"videoIndex": 0,
"channel": "央视频道",
"logo": "https://resources.yangshipin.cn/assets/oms/image/202306/4d549e53e6d0f632d5a633d1945280797b153e588f919221a07faa869812cc89.png?imageMogr2/format/webp",
"pid": "600099658",
"sid": "2012513603",
"programId": "600099658",
"needToken": true,
"mustToken": true,
"title": "风云剧场",
"videoUrl": [
"http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226950/index.m3u8"
]
},
{
"id": 21,
"videoIndex": 0,
"channel": "央视频道",
"logo": "https://resources.yangshipin.cn/assets/oms/image/202306/a556bd7d93ce65e18f243a8892b5604f4faa994a4897315914216a710a706208.png?imageMogr2/format/webp",
"pid": "600099655",
"sid": "2012514403",
"programId": "600099655",
"needToken": true,
"mustToken": true,
"title": "第一剧场",
"videoUrl": [
"http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226959/index.m3u8"
]
},
{
"id": 22,
"videoIndex": 0,
"channel": "央视频道",
"logo": "https://resources.yangshipin.cn/assets/oms/image/202306/5661bd04fecdb6e899f801147a22ab5d3a475bf2b62e30aec2c0023190ebc9b1.png?imageMogr2/format/webp",
"pid": "600099620",
"sid": "2012511203",
"programId": "600099620",
"needToken": true,
"mustToken": true,
"title": "怀旧剧场",
"videoUrl": [
"http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226972/index.m3u8"
]
},
{
"id": 23,
"videoIndex": 0,
"channel": "央视频道",
"logo": "https://resources.yangshipin.cn/assets/oms/image/202306/bb3c6c9e145d698137f5bb64a582021a01b51344b929003630eb769ea65832a9.png?imageMogr2/format/webp",
"pid": "600099637",
"sid": "2012513303",
"programId": "600099637",
"needToken": true,
"mustToken": true,
"title": "世界地理",
"videoUrl": [
""
]
},
{
"id": 24,
"videoIndex": 0,
"channel": "央视频道",
"logo": "https://resources.yangshipin.cn/assets/oms/image/202306/bbf1d024c5228b8dd128b0e3cb1717d173fab4ee84c3a4c8a57b1a215362ca3b.png?imageMogr2/format/webp",
"pid": "600099660",
"sid": "2012514103",
"programId": "600099660",
"needToken": true,
"mustToken": true,
"title": "风云音乐",
"videoUrl": [
"http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226953/index.m3u8"
]
},
{
"id": 25,
"videoIndex": 0,
"channel": "央视频道",
"logo": "https://resources.yangshipin.cn/assets/oms/image/202306/4c6b6a6d3839889f34d33db3c2f80233b26b74d3489b393487635f8704e70796.png?imageMogr2/format/webp",
"pid": "600099649",
"sid": "2012513403",
"programId": "600099649",
"needToken": true,
"mustToken": true,
"title": "兵器科技",
"videoUrl": [
"http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226975/index.m3u8"
]
},
{
"id": 26,
"videoIndex": 0,
"channel": "央视频道",
"logo": "https://resources.yangshipin.cn/assets/oms/image/202306/cd1e2bb52b06a991de168733e5ff0f1d85adc8042d40c8f393f723543e5dd08a.png?imageMogr2/format/webp",
"pid": "600099636",
"sid": "2012514203",
"programId": "600099636",
"needToken": true,
"mustToken": true,
"title": "风云足球",
"videoUrl": [
"http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226984/index.m3u8"
]
},
{
"id": 27,
"videoIndex": 0,
"channel": "央视频道",
"logo": "https://resources.yangshipin.cn/assets/oms/image/202306/cdd1b31ede7a5ad049ed53d9a072422f829e72dd062ed2c19e077fdd01699071.png?imageMogr2/format/webp",
"pid": "600099659",
"sid": "2012512503",
"programId": "600099659",
"needToken": true,
"mustToken": true,
"title": "高尔夫网球",
"videoUrl": [
"http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226978/index.m3u8"
]
},
{
"id": 28,
"videoIndex": 0,
"channel": "央视频道",
"logo": "https://resources.yangshipin.cn/assets/oms/image/202306/fa28955ce8b2539d728bf4c6a13a46ff57ad76eae46627f7bcfb1ed8a613d3fc.png?imageMogr2/format/webp",
"pid": "600099650",
"sid": "2012513903",
"programId": "600099650",
"needToken": true,
"mustToken": true,
"title": "女性时尚",
"videoUrl": [
"http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226969/index.m3u8"
]
},
{
"id": 29,
"videoIndex": 0,
"channel": "央视频道",
"logo": "https://resources.yangshipin.cn/assets/oms/image/202306/14ac5ce40482cacd3d4b37435222bfe86af2b452a2f04ecbfc1d13d76edd7c57.png?imageMogr2/format/webp",
"pid": "600099653",
"sid": "2012513803",
"programId": "600099653",
"needToken": true,
"mustToken": true,
"title": "央视文化精品",
"videoUrl": [
"http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226981/index.m3u8"
]
},
{
"id": 30,
"videoIndex": 0,
"channel": "央视频道",
"logo": "https://resources.yangshipin.cn/assets/oms/image/202306/10e14a92478011aa6c3c8562e62127f3b1908e29fcd78e4b2b24b9e6d3ec2fbc.png?imageMogr2/format/webp",
"pid": "600099652",
"sid": "2012513703",
"programId": "600099652",
"needToken": true,
"mustToken": true,
"title": "央视台球",
"videoUrl": [
"http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226956/index.m3u8"
]
},
{
"id": 31,
"videoIndex": 0,
"channel": "央视频道",
"logo": "https://resources.yangshipin.cn/assets/oms/image/202306/244d72c0eb1615ed7d51c2f5db5a67f306aa3f58c05bc2d34de3aa7e956dc8c9.png?imageMogr2/format/webp",
"pid": "600099656",
"sid": "2012514003",
"programId": "600099656",
"needToken": true,
"mustToken": true,
"title": "电视指南",
"videoUrl": [
"http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226987/index.m3u8"
]
},
{
"id": 32,
"videoIndex": 0,
"channel": "央视频道",
"logo": "https://resources.yangshipin.cn/assets/oms/image/202306/54a6863656fdfd8f803be193ddf22441c5000a108833889816fd2d8911715ce8.png?imageMogr2/format/webp",
"pid": "600099651",
"sid": "2012513503",
"programId": "600099651",
"needToken": true,
"mustToken": true,
"title": "卫生健康",
"videoUrl": [
""
]
},
{
"id": 33,
"videoIndex": 0,
"channel": "地方频道",
"logo": "https://resources.yangshipin.cn/assets/oms/image/202306/9bd372ca292a82ce3aa08772b07efc4af1f85c21d1f268ea33440c49e9a0a488.png?imageMogr2/format/webp",
"pid": "600002483",
"sid": "2000292403",
"programId": "600002483",
"needToken": true,
"mustToken": false,
"title": "东方卫视",
"videoUrl": [
"http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226217/index.m3u8"
]
},
{
"id": 34,
"videoIndex": 0,
"channel": "地方频道",
"logo": "https://resources.yangshipin.cn/assets/oms/image/202306/4120e89d3079d08aa17d382f69a2308ec70839b278367763c34a34666c75cb88.png?imageMogr2/format/webp",
"pid": "600002475",
"sid": "2000296203",
"programId": "600002475",
"needToken": true,
"mustToken": false,
"title": "湖南卫视",
"videoUrl": [
"http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226307/index.m3u8",
"http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226211/index.m3u8"
]
},
{
"id": 35,
"videoIndex": 0,
"channel": "地方频道",
"logo": "https://resources.yangshipin.cn/assets/oms/image/202306/7a6be5a2bb1dc53a945c016ff1f525dc4a84c51db371c15c89aa55404b0ba784.png?imageMogr2/format/webp",
"pid": "600002508",
"sid": "2000294503",
"programId": "600002508",
"needToken": true,
"mustToken": false,
"title": "湖北卫视",
"videoUrl": [
"http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226477/index.m3u8",
"http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226194/index.m3u8"
]
},
{
"id": 36,
"videoIndex": 0,
"channel": "地方频道",
"logo": "https://resources.yangshipin.cn/assets/oms/image/202306/ac4ed6058a87c101ae7147ebc38905d0cae047fb73fd277ee5049b84f52bda36.png?imageMogr2/format/webp",
"pid": "600002505",
"sid": "2000281303",
"programId": "600002505",
"needToken": true,
"mustToken": false,
"title": "辽宁卫视",
"videoUrl": [
"http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226546/index.m3u8",
"http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226201/index.m3u8"
]
},
{
"id": 37,
"videoIndex": 0,
"channel": "地方频道",
"logo": "https://resources.yangshipin.cn/assets/oms/image/202306/380ad685c0c1d5b2c902246b8d2df6d3f9b45e2837abcfe493075bbded597a31.png?imageMogr2/format/webp",
"pid": "600002521",
"sid": "2000295603",
"programId": "600002521",
"needToken": true,
"mustToken": false,
"title": "江苏卫视",
"videoUrl": [
"http://39.134.24.166/dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226200/index.m3u8",
"http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226200/index.m3u8"
]
},
{
"id": 38,
"videoIndex": 0,
"channel": "地方频道",
"logo": "https://resources.yangshipin.cn/assets/oms/image/202306/3c760d0d00463855890e8a1864ea4a6b6dd66b90c29b4ac714a4b17c16519871.png?imageMogr2/format/webp",
"pid": "600002503",
"sid": "2000294103",
"programId": "600002503",
"needToken": true,
"mustToken": false,
"title": "江西卫视",
"videoUrl": [
"http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226344/index.m3u8",
"http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225764/index.m3u8"
]
},
{
"id": 39,
"videoIndex": 0,
"channel": "地方频道",
"logo": "https://resources.yangshipin.cn/assets/oms/image/202306/22d403f07a7cf5410b3ad3ddb65a11aa229a32475fac213f5344c9f0ec330ca1.png?imageMogr2/format/webp",
"pid": "600002513",
"sid": "2000294803",
"programId": "600002513",
"needToken": true,
"mustToken": false,
"title": "山东卫视",
"videoUrl": [
"http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226209/index.m3u8"
]
},
{
"id": 40,
"videoIndex": 0,
"channel": "地方频道",
"logo": "https://resources.yangshipin.cn/assets/oms/image/202306/28886880a4dc0f06fb7e0a528a1def0591d61a65870e29176ede0cc92033bbfd.png?imageMogr2/format/webp",
"pid": "600002485",
"sid": "2000292703",
"programId": "600002485",
"needToken": true,
"mustToken": false,
"title": "广东卫视",
"videoUrl": [
"http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226216/index.m3u8"
]
},
{
"id": 41,
"videoIndex": 0,
"channel": "地方频道",
"logo": "https://resources.yangshipin.cn/assets/oms/image/202306/54b7e97cb816bb223fe05f3fc44da2c7820eb66e8550c19d23100f2c414ecc38.png?imageMogr2/format/webp",
"pid": "600002509",
"sid": "2000294203",
"programId": "600002509",
"needToken": true,
"mustToken": false,
"title": "广西卫视",
"videoUrl": [
"http://live.gxrb.com.cn/tv/gxtvlive03/index.m3u8",
"http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225770/index.m3u8"
]
},
{
"id": 42,
"videoIndex": 0,
"channel": "地方频道",
"logo": "https://resources.yangshipin.cn/assets/oms/image/202306/657651f411de2673d1770d9a78b44c1265704f7468cc41d4be7f51d630768494.png?imageMogr2/format/webp",
"pid": "600002531",
"sid": "2000297803",
"programId": "600002531",
"needToken": true,
"mustToken": false,
"title": "重庆卫视",
"videoUrl": [
"http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226409/index.m3u8",
"http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226202/index.m3u8"
]
},
{
"id": 43,
"videoIndex": 0,
"channel": "地方频道",
"logo": "https://resources.yangshipin.cn/assets/oms/image/202306/74925962148a6d31c85808b6cd4e444c2a54bab393d2c5fc85e960b50e22fa86.png?imageMogr2/format/webp",
"pid": "600002525",
"sid": "2000296103",
"programId": "600002525",
"needToken": true,
"mustToken": false,
"title": "河南卫视",
"videoUrl": [
"http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226480/index.m3u8",
"http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225767/index.m3u8"
]
},
{
"id": 44,
"videoIndex": 0,
"channel": "地方频道",
"logo": "https://resources.yangshipin.cn/assets/oms/image/202306/d545becdc81c60197b08c7f47380705e4665ed3fe55efc8b855e486f6e655378.png?imageMogr2/format/webp",
"pid": "600002493",
"sid": "2000293403",
"programId": "600002493",
"needToken": true,
"mustToken": false,
"title": "河北卫视",
"videoUrl": [
"http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226406/index.m3u8",
"http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225750/index.m3u8"
]
},
{
"id": 45,
"videoIndex": 0,
"channel": "地方频道",
"logo": "https://resources.yangshipin.cn/assets/oms/image/202306/4eb45f4781d33d872af027dc01c941559aab55667dd99cc5c22bef7037807b13.png?imageMogr2/format/webp",
"pid": "600002490",
"sid": "2000293303",
"programId": "600002490",
"needToken": true,
"mustToken": false,
"title": "贵州卫视",
"videoUrl": [
"http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226474/index.m3u8",
"http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225793/index.m3u8"
]
},
{
"id": 46,
"videoIndex": 0,
"channel": "地方频道",
"logo": "https://resources.yangshipin.cn/assets/oms/image/202306/f4f23633c578beea49a3841d88d3490100f029ee349059fa532869db889872c5.png?imageMogr2/format/webp",
"pid": "600002309",
"sid": "2000272103",
"programId": "600002309",
"needToken": true,
"mustToken": false,
"title": "北京卫视",
"videoUrl": [
"http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221225728/index.m3u8",
"http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226222/index.m3u8"
]
},
{
"id": 47,
"videoIndex": 0,
"channel": "地方频道",
"logo": "https://resources.yangshipin.cn/assets/oms/image/202306/d8273ae9be698ce2db21f5b886ecac95a73429593f93713c60ed8c12c38bf0d3.png?imageMogr2/format/webp",
"pid": "600002498",
"sid": "2000293903",
"programId": "600002498",
"needToken": true,
"mustToken": false,
"title": "黑龙江卫视",
"videoUrl": [
"http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226327/index.m3u8",
"http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226215/index.m3u8"
]
},
{
"id": 48,
"videoIndex": 0,
"channel": "地方频道",
"logo": "https://resources.yangshipin.cn/assets/oms/image/202306/a66c836bd98ba3e41a2e9a570d4b9c50dedc6839e9de333e2e78212ad505f37e.png?imageMogr2/format/webp",
"pid": "600002520",
"sid": "2000295503",
"programId": "600002520",
"needToken": true,
"mustToken": false,
"title": "浙江卫视",
"videoUrl": [
"http://hw-m-l.cztv.com/channels/lantian/channel01/1080p.m3u8",
"http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226199/index.m3u8"
]
},
{
"id": 49,
"videoIndex": 0,
"channel": "地方频道",
"logo": "https://resources.yangshipin.cn/assets/oms/image/202306/f35fa04b51b1ee4984b03578b65403570868ebca03c6c01e11b097f999a58d9b.png?imageMogr2/format/webp",
"pid": "600002532",
"sid": "2000298003",
"programId": "600002532",
"needToken": true,
"mustToken": false,
"title": "安徽卫视",
"videoUrl": [
"http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226391/index.m3u8",
"http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226203/index.m3u8"
]
},
{
"id": 50,
"videoIndex": 0,
"channel": "地方频道",
"logo": "https://resources.yangshipin.cn/assets/oms/image/202306/d59fec04c902e3581c617136d02d4b9b8c4cbe64272781ddd3525e80c823edb7.png?imageMogr2/format/webp",
"pid": "600002481",
"sid": "2000292203",
"programId": "600002481",
"needToken": true,
"mustToken": false,
"title": "深圳卫视",
"videoUrl": [
"http://39.134.24.166/dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226205/index.m3u8",
"http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226205/index.m3u8"
]
},
{
"id": 51,
"videoIndex": 0,
"channel": "地方频道",
"logo": "https://resources.yangshipin.cn/assets/oms/image/202306/3276a414ae0eaa0f116f2045cd913367967d0c7c1e978e8621ac3879436c6ed7.png?imageMogr2/format/webp",
"pid": "600002516",
"sid": "2000295003",
"programId": "600002516",
"needToken": true,
"mustToken": false,
"title": "四川卫视",
"videoUrl": [
"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"
]
},
{
"id": 52,
"videoIndex": 0,
"channel": "地方频道",
"logo": "https://resources.yangshipin.cn/assets/oms/image/202306/3208fe6564a293c21b711333fb3edb05bb5b406cff840573c9a8d839680a1579.png?imageMogr2/format/webp",
"pid": "600002484",
"sid": "2000292503",
"programId": "600002484",
"needToken": true,
"mustToken": false,
"title": "东南卫视",
"videoUrl": [
"http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226341/index.m3u8",
"http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225766/index.m3u8"
]
},
{
"id": 53,
"videoIndex": 0,
"channel": "地方频道",
"logo": "https://resources.yangshipin.cn/assets/oms/image/202306/6e060391fde0469801fc3d84dbf204b4f8d650d251f17d7595a6964c0bb99e81.png?imageMogr2/format/webp",
"pid": "600002506",
"sid": "2000291503",
"programId": "600002506",
"needToken": true,
"mustToken": false,
"title": "海南卫视",
"videoUrl": [
"http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226465/index.m3u8",
"http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225769/index.m3u8"
]
},
{
"id": 54,
"videoIndex": 0,
"channel": "地方频道",
"logo": "",
"pid": "600152137",
"sid": "2019927003",
"programId": "",
"needToken": true,
"mustToken": false,
"title": "天津卫视",
"videoUrl": [
""
]
},
{
"id": 55,
"videoIndex": 0,
"channel": "地方频道",
"logo": "",
"pid": "600152138",
"sid": "2019927403",
"programId": "",
"needToken": true,
"mustToken": false,
"title": "新疆卫视",
"videoUrl": [
""
]
},
{
"id": 56,
"videoIndex": 0,
"channel": "国际频道",
"logo": "https://resources.yangshipin.cn/assets/oms/image/202306/a72dff758ca1c17cd0ecc8cedc11b893d208f409d5e6302faa0e9d298848abc3.png?imageMogr2/format/webp",
"pid": "600014550",
"sid": "2001656803",
"programId": "600014550",
"needToken": false,
"mustToken": false,
"title": "CGTN",
"videoUrl": [
"http://live.cgtn.com/1000/prog_index.m3u8"
]
},
{
"id": 57,
"videoIndex": 0,
"channel": "国际频道",
"logo": "https://resources.yangshipin.cn/assets/oms/image/202306/a8d0046a47433d952bf6ed17062deb8bd2184ba9aec0f7781df6bf9487a3ffcf.png?imageMogr2/format/webp",
"pid": "600084704",
"sid": "2010153503",
"programId": "600084704",
"needToken": false,
"mustToken": false,
"title": "CGTN 法语频道",
"videoUrl": [
"https://livefr.cgtn.com/1000f/prog_index.m3u8"
]
},
{
"id": 58,
"videoIndex": 0,
"channel": "国际频道",
"logo": "https://resources.yangshipin.cn/assets/oms/image/202306/bf0a820893cbaf20dd0333e27042e1ef9c8806e5b602b6a8c95af399db0bc77a.png?imageMogr2/format/webp",
"pid": "600084758",
"sid": "2010152603",
"programId": "600084758",
"needToken": false,
"mustToken": false,
"title": "CGTN 俄语频道",
"videoUrl": [
"http://liveru.cgtn.com/1000r/prog_index.m3u8"
]
},
{
"id": 59,
"videoIndex": 0,
"channel": "国际频道",
"logo": "https://resources.yangshipin.cn/assets/oms/image/202306/2e44e2aa3e7a1cedf07fd0ae59fe69e86a60a2632660a006e3e9e7397b2d107e.png?imageMogr2/format/webp",
"pid": "600084782",
"sid": "2010155203",
"programId": "600084782",
"needToken": false,
"mustToken": false,
"title": "CGTN 阿拉伯语频道",
"videoUrl": [
"http://livear.cgtn.com/1000a/prog_index.m3u8"
]
},
{
"id": 60,
"videoIndex": 0,
"channel": "国际频道",
"logo": "https://resources.yangshipin.cn/assets/oms/image/202309/7c337e3dbe64402ec7e4678a619a4a6d95144e42f35161181ff78e143b7cf67a.png?imageMogr2/format/webp",
"pid": "600084744",
"sid": "2010152503",
"programId": "600084744",
"needToken": false,
"mustToken": false,
"title": "CGTN 西班牙语频道",
"videoUrl": [
"http://livees.cgtn.com/500e/prog_index.m3u8",
"http://livees.cgtn.com/1000e/prog_index.m3u8"
]
},
{
"id": 61,
"videoIndex": 0,
"channel": "国际频道",
"logo": "https://resources.yangshipin.cn/assets/oms/image/202309/74d3ac436a7e374879578de1d87a941fbf566d39d5632b027c5097891ed32bd5.png?imageMogr2/format/webp",
"pid": "600084781",
"sid": "2010155403",
"programId": "600084781",
"needToken": false,
"mustToken": false,
"title": "CGTN 纪录频道",
"videoUrl": [
"https://livedoc.cgtn.com/500d/prog_index.m3u8"
]
}
]
+4
View File
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="server_url">http://localhost:8080</string>
</resources>
+2 -1
View File
@@ -1,4 +1,5 @@
<resources>
<string name="app_name">我的电视</string>
<string name="logo">logo</string>
<string name="title_channel_reversal">换台反转</string>
<string name="title_channel_num">数字选台</string>
</resources>
-1
View File
@@ -1,6 +1,5 @@
<resources>
<style name="CustomImageCardViewStyle" parent="Widget.Leanback.ImageCardViewStyle">
<item name="lbImageCardViewType">Title|Content</item>
</style>
+9
View File
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true">
<trust-anchors>
<certificates src="system" />
<certificates src="user" />
</trust-anchors>
</base-config>
</network-security-config>
+3 -3
View File
@@ -1,6 +1,6 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
id 'com.android.application' version '8.2.0' apply false
id 'com.android.library' version '8.2.0' apply false
id 'org.jetbrains.kotlin.android' version '1.9.21' apply false
id 'com.android.application' version '8.2.2' apply false
id 'com.android.library' version '8.2.2' apply false
id 'org.jetbrains.kotlin.android' version '1.9.22' apply false
}