Compare commits

..
7 Commits
Author SHA1 Message Date
Li ZongYing fd897f68c0 fix crash on setting 2024-03-29 16:19:27 +08:00
Li ZongYing 65c4b0cf52 channel filter 2024-03-26 13:36:26 +08:00
Li ZongYing 8290a86909 readme 2024-03-18 19:39:04 +08:00
Li ZongYing 4e05fe91d7 readme 2024-03-18 19:36:46 +08:00
Li ZongYing d5de8a8d2a readme 2024-03-18 19:35:39 +08:00
Li ZongYing c232099d29 readme 2024-03-18 19:32:53 +08:00
Li ZongYing 3fb4b1e585 readme 2024-03-18 18:34:58 +08:00
30 changed files with 562 additions and 1475 deletions
+229
View File
@@ -0,0 +1,229 @@
## 更新日志
### v1.7.2(通用)
* 支持节目列表网格样式和行样式切换,软件重启后生效
* 节目列表样式变更
### v1.7.1(安卓5及以上专用)
* 解决设置页更新闪退的问题
* 凤凰卫视回归
* 解决368问题
### v1.7.0(通用)
* 网络请求优化
### v1.6.9(安卓5及以上专用)
* 去掉港澳台和国际频道
* 解决部分情况下3、6、8等频道无法播放的问题
* 解决部分情况下启动后黑屏问题
### v1.6.8(通用)
* 修复部分设备崩溃的问题
* 修复部分设备凤凰卫视无法播放的问题
### v1.6.7(安卓5及以上专用)
* 手机双击打开配置
* 自动更新
### v1.6.6(通用)
* 更新重庆卫视图标
* 凤凰卫视增强画质
* 凤凰卫视增加EPG
### v1.6.5(安卓5及以上专用)
* 增加CETV1图标
* 稳定性提升
### v1.6.4(通用)
* 增加CETV1
* 增加凤凰卫视
* 默认关闭开机启动
### v1.6.3(安卓5及以上专用)
* 增加CETV1
* 凤凰卫视增强画质
* 默认关闭开机启动
* 延迟菜单自动关闭时间
* 解决一些可能导致首次打开时黑屏的问题
### v1.6.2(通用)
* 修复按键无效的问题
* 新的频道列表样式
### v1.6.1(安卓5及以上专用)
* 增加凤凰卫视
### v1.6.0(通用)
* 通用(春晚緊急修復)
### v1.5.9(安卓5及以上专用)
* 解决天猫魔盒闪退问题
### v1.5.8(通用)
* 修复央视6画质差的问题
* 增加兵团卫视
* 播放失败重试
### v1.5.7(安卓5及以上专用)
* 修复播放失败的问题
### v1.5.6(通用)
* 解决部分设备系统时间不对导致播放失败的问题
### v1.5.5(安卓5及以上专用)
* 修复播放失败的问题
* 修复APP恢复后频道号、频道列表不自动消失的问题
### v1.5.4(通用)
* 修复播放失败的问题
### 以下已不可用
### v1.5.3(安卓5及以上专用)
* 修复部分情况下APP切换后无法继续播放的问题
* 优化重试逻辑
### v1.5.2(通用)
* 修复APP恢复后频道号、频道列表不自动消失的问题
### v1.5.1(安卓5及以上专用)
* 性能优化
### v1.5.0(通用)
* 修复部分情况下APP切换后无法继续播放的问题
### v1.4.9(安卓5及以上专用)
* 同步v1.4.8
### v1.4.8(通用)
* 频道号从1开始,CCTV5+为18
* 提高CCTV6清晰度
* 增加天津卫视、新疆卫视
### v1.4.7(安卓5及以上专用)
* 修复部分用户CCTV13播放过程中卡住的问题
* 调整CCTV的频道顺序
### v1.4.6(通用)
* 10以下频道不再需要先按0
### v1.4.5(安卓5及以上专用)
* 数字选台配置
### v1.4.4(通用)
* 优化图标显示
* 增加换台反转
### v1.4.3(安卓5及以上专用)
* 支持频道反转配置
### v1.4.2(通用)
* 支持安卓4
### v1.4.1
* 解决部分用户无法打开菜单的问题
### v1.4.0
* 解决极个别高版本机型黑屏问题
### v1.3.9
* 提高稳定性
* 提高连接速度
### v1.3.4
* 部分错误会提示用户
* 菜单3秒钟后自动关闭
### v1.3.3
* 部分错误会提示用户
* 菜单3秒钟后自动关闭
### v1.3.2
* 增加重试,减少因网络问题导致的播放失败
* 优化横幅banner
### v1.3.1
* 增加CCTV 8K 超高清频道
* 增加国际频道栏目
### v1.3.0
* 处理368可能失败的情形
### v1.2.9
* 支持3、6、8
### v1.2.8
* 兼容16:10等分辨率
### v1.2.7
* 此版本是为了测试安卓6.0以下版本,如之前可以正常运行,请勿安装。
### v1.2.6
* 支持安卓4.2
* 解决部分频道无法播放的问题
* 修复切换时有时没有恢复播放的问题
* 左右键不再切换源
* 增大频道信息标题尺寸,缩小与节目信息的间隔
### v1.2.5
* 美化频道信息显示
* 优化节目单获取
### v1.2.4
* 改变换台滑动方向,上一个频道下滑,下一个频道上滑
* 软件退出时,退出播放器
* 播放相同的频道,不再重复加载
* 暂时移除部分频道
### v1.2.3
* 固定视频比列为16:9
* 隐藏全面屏底部的小横条
* 移除移动专区
* 修复一个闪退问题
* 更换图标和应用名
+13 -209
View File
@@ -4,214 +4,9 @@
## 使用 ## 使用
下载安装 [releases](https://github.com/lizongying/my-tv/releases/) 下载安装:
[github](https://github.com/lizongying/my-tv/releases/)
更多地址 [my-tv](https://lyrics.run/my-tv.html) [gitee](https://gitee.com/lizongying/my-tv/releases/)
![image](./screenshots/img_3.png)
![image](./screenshots/img_2.png)
![image](./screenshots/img_1.png)
## 更新日志
### v1.6.6(通用)
* 更新重庆卫视图标
* 凤凰卫视增强画质
* 凤凰卫视增加EPG
### v1.6.5(安卓5及以上专用)
* 增加CETV1图标
* 稳定性提升
### v1.6.4(通用)
* 增加CETV1
* 增加凤凰卫视
* 默认关闭开机启动
### v1.6.3(安卓5及以上专用)
* 增加CETV1
* 凤凰卫视增强画质
* 默认关闭开机启动
* 延迟菜单自动关闭时间
* 解决一些可能导致首次打开时黑屏的问题
### v1.6.2(通用)
* 修复按键无效的问题
* 新的频道列表样式
### v1.6.1(安卓5及以上专用)
* 增加凤凰卫视
### v1.6.0(通用)
* 通用(春晚緊急修復)
### v1.5.9(安卓5及以上专用)
* 解决天猫魔盒闪退问题
### v1.5.8(通用)
* 修复央视6画质差的问题
* 增加兵团卫视
* 播放失败重试
### v1.5.7(安卓5及以上专用)
* 修复播放失败的问题
### v1.5.6(通用)
* 解决部分设备系统时间不对导致播放失败的问题
### v1.5.5(安卓5及以上专用)
* 修复播放失败的问题
* 修复APP恢复后频道号、频道列表不自动消失的问题
### v1.5.4(通用)
* 修复播放失败的问题
### 以下已不可用
### v1.5.3(安卓5及以上专用)
* 修复部分情况下APP切换后无法继续播放的问题
* 优化重试逻辑
### v1.5.2(通用)
* 修复APP恢复后频道号、频道列表不自动消失的问题
### v1.5.1(安卓5及以上专用)
* 性能优化
### v1.5.0(通用)
* 修复部分情况下APP切换后无法继续播放的问题
### v1.4.9(安卓5及以上专用)
* 同步v1.4.8
### v1.4.8(通用)
* 频道号从1开始,CCTV5+为18
* 提高CCTV6清晰度
* 增加天津卫视、新疆卫视
### v1.4.7(安卓5及以上专用)
* 修复部分用户CCTV13播放过程中卡住的问题
* 调整CCTV的频道顺序
### v1.4.6(通用)
* 10以下频道不再需要先按0
### v1.4.5(安卓5及以上专用)
* 数字选台配置
### v1.4.4(通用)
* 优化图标显示
* 增加换台反转
### v1.4.3(安卓5及以上专用)
* 支持频道反转配置
### v1.4.2(通用)
* 支持安卓4
### v1.4.1
* 解决部分用户无法打开菜单的问题
### v1.4.0
* 解决极个别高版本机型黑屏问题
### v1.3.9
* 提高稳定性
* 提高连接速度
### v1.3.4
* 部分错误会提示用户
* 菜单3秒钟后自动关闭
### v1.3.3
* 部分错误会提示用户
* 菜单3秒钟后自动关闭
### v1.3.2
* 增加重试,减少因网络问题导致的播放失败
* 优化横幅banner
### v1.3.1
* 增加CCTV 8K 超高清频道
* 增加国际频道栏目
### v1.3.0
* 处理368可能失败的情形
### v1.2.9
* 支持3、6、8
### v1.2.8
* 兼容16:10等分辨率
### v1.2.7
* 此版本是为了测试安卓6.0以下版本,如之前可以正常运行,请勿安装。
### v1.2.6
* 支持安卓4.2
* 解决部分频道无法播放的问题
* 修复切换时有时没有恢复播放的问题
* 左右键不再切换源
* 增大频道信息标题尺寸,缩小与节目信息的间隔
### v1.2.5
* 美化频道信息显示
* 优化节目单获取
### v1.2.4
* 改变换台滑动方向,上一个频道下滑,下一个频道上滑
* 软件退出时,退出播放器
* 播放相同的频道,不再重复加载
* 暂时移除部分频道
### v1.2.3
* 固定视频比列为16:9
* 隐藏全面屏底部的小横条
* 移除移动专区
* 修复一个闪退问题
* 更换图标和应用名
## 其他
小米电视可以使用小米电视助手进行安装 小米电视可以使用小米电视助手进行安装
@@ -221,6 +16,14 @@
adb install my-tv.apk adb install my-tv.apk
``` ```
![image](./screenshots/img_3.png)
![image](./screenshots/img_2.png)
![image](./screenshots/img_1.png)
## 更新日志
[更新日志](./HISTORY.md)
## TODO ## TODO
* 音量不同 * 音量不同
@@ -233,7 +36,8 @@ adb install my-tv.apk
* 节目增加预告 * 节目增加预告
* 频道列表优化 * 频道列表优化
* 自动更新 * 自动更新
* 時間 * 显示时间
* 软解
## 赞赏 ## 赞赏
+7
View File
@@ -7,6 +7,7 @@
android:name="android.software.leanback" android:name="android.software.leanback"
android:required="true" /> android:required="true" />
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<application <application
@@ -39,6 +40,12 @@
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
</intent-filter> </intent-filter>
</receiver> </receiver>
<receiver android:name=".NetworkChangeReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
</intent-filter>
</receiver>
<provider <provider
android:name=".InitializerProvider" android:name=".InitializerProvider"
android:authorities="${applicationId}.InitializerProvider" android:authorities="${applicationId}.InitializerProvider"
Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

@@ -1,17 +1,17 @@
package com.lizongying.mytv package com.lizongying.mytv
import android.content.Context
import android.graphics.Color import android.graphics.Color
import android.view.ContextThemeWrapper import android.view.ContextThemeWrapper
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView import android.widget.ImageView
import androidx.leanback.widget.ImageCardView import androidx.leanback.widget.ImageCardView
import androidx.leanback.widget.Presenter import androidx.leanback.widget.Presenter
import androidx.lifecycle.LifecycleOwner
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.lizongying.mytv.models.TVViewModel import com.lizongying.mytv.models.TVViewModel
class CardPresenter( class CardPresenter(
private val owner: LifecycleOwner, private val context: Context,
) : Presenter() { ) : Presenter() {
override fun onCreateViewHolder(parent: ViewGroup): ViewHolder { override fun onCreateViewHolder(parent: ViewGroup): ViewHolder {
@@ -27,18 +27,32 @@ class CardPresenter(
val tvViewModel = item as TVViewModel val tvViewModel = item as TVViewModel
val cardView = viewHolder.view as ImageCardView val cardView = viewHolder.view as ImageCardView
cardView.titleText = tvViewModel.title.value cardView.titleText = tvViewModel.getTV().title
cardView.setMainImageDimensions(CARD_WIDTH, CARD_HEIGHT) cardView.setMainImageDimensions(CARD_WIDTH, CARD_HEIGHT)
cardView.tag = tvViewModel.videoUrl.value cardView.tag = tvViewModel.videoUrl.value
Glide.with(viewHolder.view.context) Glide.with(viewHolder.view.context)
.load(tvViewModel.logo.value) .load(tvViewModel.getTV().logo)
.centerInside() .centerInside()
.into(cardView.mainImageView) .into(cardView.mainImageView)
cardView.setBackgroundColor(Color.WHITE) cardView.setBackgroundColor(Color.WHITE)
cardView.setMainImageScaleType(ImageView.ScaleType.CENTER_INSIDE) cardView.setMainImageScaleType(ImageView.ScaleType.CENTER_INSIDE)
// cardView.setOnFocusChangeListener { v, hasFocus ->
// run {
// if (hasFocus) {
// if (v != null) {
// (v as ImageCardView).setInfoAreaBackgroundColor(context.resources.getColor(R.color.focus))
// }
// } else {
// if (v != null) {
// (v as ImageCardView).setInfoAreaBackgroundColor(context.resources.getColor(R.color.ic_launcher_background))
// }
// }
// }
// }
val epg = tvViewModel.epg.value?.filter { it.beginTime < Utils.getDateTimestamp() } val epg = tvViewModel.epg.value?.filter { it.beginTime < Utils.getDateTimestamp() }
if (!epg.isNullOrEmpty()) { if (!epg.isNullOrEmpty()) {
cardView.contentText = epg.last().title cardView.contentText = epg.last().title
@@ -30,7 +30,7 @@ class ChannelFragment : Fragment() {
fun show(tvViewModel: TVViewModel) { fun show(tvViewModel: TVViewModel) {
handler.removeCallbacks(hideRunnable) handler.removeCallbacks(hideRunnable)
handler.removeCallbacks(playRunnable) handler.removeCallbacks(playRunnable)
binding.channelContent.text = (tvViewModel.id.value?.plus(1)).toString() binding.channelContent.text = (tvViewModel.getTV().id.plus(1)).toString()
view?.visibility = View.VISIBLE view?.visibility = View.VISIBLE
handler.postDelayed(hideRunnable, delay) handler.postDelayed(hideRunnable, delay)
} }
@@ -1,157 +0,0 @@
package com.lizongying.mytv
import android.content.Context
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.File
/**
*@author LeGend
*@date 2024/2/4 22:42
*/
object ChannelUtils {
/**
* 获取服务器channel版本
*
* @param context Context
*
* @return 服务器channel版本
*/
suspend fun getServerVersion(context: Context): Int {
return withContext(Dispatchers.IO) {
val client = okhttp3.OkHttpClient.Builder().connectTimeout(500, java.util.concurrent.TimeUnit.MILLISECONDS)
.readTimeout(1, java.util.concurrent.TimeUnit.SECONDS).build()
client.newCall(okhttp3.Request.Builder().url(getServerVersionUrl(context)).build()).execute()
.use { response ->
if (!response.isSuccessful) throw java.io.IOException("Unexpected code $response")
val body = response.body()
body?.string()?.toInt() ?: 0
}
}
}
/**
* 获取服务器channel
*
* @param url String 服务器地址
*
* @return Array<TV> 服务器channel
*
* @throws java.io.IOException 网络请求失败
*/
suspend fun getServerChannel(url: String): List<TV> {
val result = withContext(Dispatchers.IO) {
val client = okhttp3.OkHttpClient.Builder().connectTimeout(500, java.util.concurrent.TimeUnit.MILLISECONDS)
.readTimeout(1, java.util.concurrent.TimeUnit.SECONDS).build()
val request = okhttp3.Request.Builder().url(url).build()
client.newCall(request).execute().use { response ->
if (!response.isSuccessful) throw java.io.IOException("Unexpected code $response")
val body = response.body()
body?.string() ?: ""
}
}
return withContext(Dispatchers.Default) {
val type = object : com.google.gson.reflect.TypeToken<List<TV>>() {}.type
com.google.gson.Gson().fromJson(result, type)
}
}
/**
* 获取服务器地址
*
* @param context Context
*
* @return 服务器地址
*/
fun getServerUrl(context: Context): String {
return context.resources.getString(R.string.server_url)
}
/**
* 获取serverVersion的URL
*
* @param context Context
*
* @return serverVersionURL 服务器版本地址
*/
suspend fun getServerVersionUrl(context: Context): String {
return withContext(Dispatchers.IO) {
context.resources.getString(R.string.server_version_url)
}
}
/**
* 获取本地channel版本
*
* @param context Context
*
* @return 本地channel
*/
suspend fun getLocalVersion(context: Context): Int {
return withContext(Dispatchers.IO) {
val file = File(getAppDirectory(context), "channels")
//检查本地是否已经有保存的channels.json,若无保存的Channel.json则从读取assert中文件
val savedVersion =
context.getSharedPreferences("saved_version", Context.MODE_PRIVATE).getInt("version", Integer.MIN_VALUE)
if (!file.exists() || savedVersion == Integer.MIN_VALUE) {
context.resources.getInteger(R.integer.local_channel_version)
} else {
savedVersion
}
}
}
/**
* 获取本地可读取的目录
* @param context Context
*
* @return 可读取的目录
*/
private fun getAppDirectory(context: Context): File {
return context.filesDir
}
/**
* 获取本地channel
*
* @param context Context
*
* @return Array<TV> 本地channel
*/
suspend fun getLocalChannel(context: Context): List<TV> {
val str = withContext(Dispatchers.IO) {
if (File(getAppDirectory(context), "channels").exists()) {
File(getAppDirectory(context), "channels").readText()
} else {
context.resources.openRawResource(R.raw.channels).bufferedReader().use { it.readText() }
}
}
return withContext(Dispatchers.Default) {
val type = object : com.google.gson.reflect.TypeToken<List<TV>>() {}.type
com.google.gson.Gson().fromJson(str, type)
}
}
/**
* 更新channels.json
*
* @param context Context
*
* @return 无
*
* @throws java.io.IOException 写入失败
*/
suspend fun updateLocalChannel(context: Context, version: Int, channels: List<TV>) {
withContext(Dispatchers.IO) {
val file = File(getAppDirectory(context), "channels")
if (!file.exists()) {
file.createNewFile()
}
file.writeText(com.google.gson.Gson().toJson(channels))
context.getSharedPreferences("saved_version", Context.MODE_PRIVATE).edit().putInt(
"version", version
).apply()
}
}
}
@@ -5,14 +5,16 @@ import android.app.Dialog
import android.os.Bundle import android.os.Bundle
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
class ConfirmationDialogFragment(private val listener: ConfirmationDialogListener) : class ConfirmationFragment(
DialogFragment() { private val listener: ConfirmationListener,
private val message: String
) : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return activity?.let { return activity?.let {
val builder = AlertDialog.Builder(it) val builder = AlertDialog.Builder(it)
builder.setTitle("确认") builder.setTitle("确定更新吗?")
.setMessage("确认更新吗?") .setMessage(message)
.setPositiveButton( .setPositiveButton(
"确定" "确定"
) { _, _ -> ) { _, _ ->
@@ -23,12 +25,11 @@ class ConfirmationDialogFragment(private val listener: ConfirmationDialogListene
) { _, _ -> ) { _, _ ->
listener.onCancel() listener.onCancel()
} }
// 创建并返回 AlertDialog 对象
builder.create() builder.create()
} ?: throw IllegalStateException("Activity cannot be null") } ?: throw IllegalStateException("Activity cannot be null")
} }
interface ConfirmationDialogListener { interface ConfirmationListener {
fun onConfirm() fun onConfirm()
fun onCancel() fun onCancel()
} }
@@ -0,0 +1,28 @@
package com.lizongying.mytv
import android.view.View
import androidx.core.content.ContextCompat
import androidx.leanback.app.ErrorSupportFragment
class ErrorFragment : ErrorSupportFragment() {
internal fun setErrorContent(message: String) {
imageDrawable =
ContextCompat.getDrawable(context!!, androidx.leanback.R.drawable.lb_ic_sad_cloud)
this.message = message
setDefaultBackground(TRANSLUCENT)
backgroundDrawable = ContextCompat.getDrawable(
context!!,
R.color.black
)
buttonText = resources.getString(R.string.dismiss_error)
buttonClickListener = View.OnClickListener {
fragmentManager!!.beginTransaction().remove(this@ErrorFragment).commit()
}
}
companion object {
private const val TRANSLUCENT = false
}
}
@@ -7,6 +7,8 @@ import android.content.pm.PackageInfo
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.content.pm.Signature import android.content.pm.Signature
import android.content.pm.SigningInfo import android.content.pm.SigningInfo
import android.net.ConnectivityManager
import android.net.NetworkCapabilities
import android.os.Build import android.os.Build
import android.util.Log import android.util.Log
import java.security.MessageDigest import java.security.MessageDigest
@@ -71,6 +73,25 @@ val Context.appSignature: String
return hashSignature(sign) return hashSignature(sign)
} }
val Context.isNetworkConnected: Boolean
get() {
val connectivityManager =
this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val network = connectivityManager.activeNetwork
val networkCapabilities = connectivityManager.getNetworkCapabilities(network)
return networkCapabilities != null &&
(networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) ||
networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) ||
networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET))
} else {
val networkInfo = connectivityManager.activeNetworkInfo
return networkInfo != null && networkInfo.isConnectedOrConnecting
}
}
private fun hashSignature(signature: Signature): String { private fun hashSignature(signature: Signature): String {
return try { return try {
val md = MessageDigest.getInstance("MD5") val md = MessageDigest.getInstance("MD5")
@@ -29,13 +29,13 @@ class InfoFragment : Fragment() {
} }
fun show(tvViewModel: TVViewModel) { fun show(tvViewModel: TVViewModel) {
binding.textView.text = tvViewModel.title.value binding.textView.text = tvViewModel.getTV().title
Glide.with(this) Glide.with(this)
.load(tvViewModel.logo.value) .load(tvViewModel.getTV().logo)
.into(binding.infoLogo) .into(binding.infoLogo)
Log.i(TAG, "${tvViewModel.title.value} ${tvViewModel.epg.value}") Log.i(TAG, "${tvViewModel.getTV().title} ${tvViewModel.epg.value}")
val epg = tvViewModel.epg.value?.filter { it.beginTime < Utils.getDateTimestamp() } val epg = tvViewModel.epg.value?.filter { it.beginTime < Utils.getDateTimestamp() }
if (!epg.isNullOrEmpty()) { if (!epg.isNullOrEmpty()) {
binding.infoDesc.text = epg.last().title binding.infoDesc.text = epg.last().title
@@ -1,5 +1,9 @@
package com.lizongying.mytv package com.lizongying.mytv
import android.content.Context
import android.net.ConnectivityManager
import android.net.Network
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
@@ -7,11 +11,17 @@ import android.util.Log
import android.view.GestureDetector import android.view.GestureDetector
import android.view.KeyEvent import android.view.KeyEvent
import android.view.MotionEvent import android.view.MotionEvent
import android.view.View
import android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION import android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
import android.view.WindowManager import android.view.WindowManager
import android.widget.Toast import android.widget.Toast
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.lifecycleScope
import com.lizongying.mytv.models.TVViewModel import com.lizongying.mytv.models.TVViewModel
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
class MainActivity : FragmentActivity(), Request.RequestListener { class MainActivity : FragmentActivity(), Request.RequestListener {
@@ -22,6 +32,7 @@ class MainActivity : FragmentActivity(), Request.RequestListener {
private val infoFragment = InfoFragment() private val infoFragment = InfoFragment()
private val channelFragment = ChannelFragment() private val channelFragment = ChannelFragment()
private val settingFragment = SettingFragment() private val settingFragment = SettingFragment()
private val errorFragment = ErrorFragment()
private var doubleBackToExitPressedOnce = false private var doubleBackToExitPressedOnce = false
@@ -31,6 +42,16 @@ class MainActivity : FragmentActivity(), Request.RequestListener {
private val delayHideMain: Long = 10000 private val delayHideMain: Long = 10000
private val delayHideSetting: Long = 10000 private val delayHideSetting: Long = 10000
init {
lifecycleScope.launch(Dispatchers.IO) {
val utilsJob = async(start = CoroutineStart.LAZY) { Utils.init() }
utilsJob.start()
// utilsJob.await()
}
}
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
Log.i(TAG, "onCreate") Log.i(TAG, "onCreate")
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@@ -54,6 +75,32 @@ class MainActivity : FragmentActivity(), Request.RequestListener {
.commit() .commit()
} }
gestureDetector = GestureDetector(this, GestureListener()) gestureDetector = GestureDetector(this, GestureListener())
errorFragment.buttonClickListener = View.OnClickListener {
supportFragmentManager.beginTransaction()
.remove(errorFragment)
.commit()
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
val connectivityManager =
getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
connectivityManager.registerDefaultNetworkCallback(object :
ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
super.onAvailable(network)
Log.i(TAG, "net ${Build.VERSION.SDK_INT}")
if (this@MainActivity.isNetworkConnected) {
Log.i(TAG, "net isNetworkConnected")
ready++
}
}
})
} else {
Log.i(TAG, "net ${Build.VERSION.SDK_INT}")
ready++
}
} }
fun showInfoFragment(tvViewModel: TVViewModel) { fun showInfoFragment(tvViewModel: TVViewModel) {
@@ -120,11 +167,15 @@ class MainActivity : FragmentActivity(), Request.RequestListener {
handler.postDelayed(hideMain, delayHideMain) handler.postDelayed(hideMain, delayHideMain)
} }
fun settingActive() { fun settingDelayHide() {
handler.removeCallbacks(hideSetting) handler.removeCallbacks(hideSetting)
handler.postDelayed(hideSetting, delayHideSetting) handler.postDelayed(hideSetting, delayHideSetting)
} }
fun settingNeverHide() {
handler.removeCallbacks(hideSetting)
}
private val hideMain = Runnable { private val hideMain = Runnable {
if (!mainFragment.isHidden) { if (!mainFragment.isHidden) {
supportFragmentManager.beginTransaction().hide(mainFragment).commit() supportFragmentManager.beginTransaction().hide(mainFragment).commit()
@@ -146,11 +197,19 @@ class MainActivity : FragmentActivity(), Request.RequestListener {
fun fragmentReady() { fun fragmentReady() {
ready++ ready++
Log.i(TAG, "ready $ready") Log.i(TAG, "ready $ready")
if (ready == 5) { if (ready == 6) {
mainFragment.fragmentReady() mainFragment.fragmentReady()
} }
} }
fun isPlaying() {
if (errorFragment.isVisible) {
supportFragmentManager.beginTransaction()
.remove(errorFragment)
.commit()
}
}
override fun onTouchEvent(event: MotionEvent?): Boolean { override fun onTouchEvent(event: MotionEvent?): Boolean {
if (event != null) { if (event != null) {
gestureDetector.onTouchEvent(event) gestureDetector.onTouchEvent(event)
@@ -211,7 +270,7 @@ class MainActivity : FragmentActivity(), Request.RequestListener {
Log.i(TAG, "settingFragment ${settingFragment.isVisible}") Log.i(TAG, "settingFragment ${settingFragment.isVisible}")
if (!settingFragment.isVisible) { if (!settingFragment.isVisible) {
settingFragment.show(supportFragmentManager, "setting") settingFragment.show(supportFragmentManager, "setting")
settingActive() settingDelayHide()
} else { } else {
handler.removeCallbacks(hideSetting) handler.removeCallbacks(hideSetting)
settingFragment.dismiss() settingFragment.dismiss()
@@ -277,6 +336,7 @@ class MainActivity : FragmentActivity(), Request.RequestListener {
} }
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean { override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
Log.i(TAG, "keyCode $keyCode, event $event")
when (keyCode) { when (keyCode) {
KeyEvent.KEYCODE_0 -> { KeyEvent.KEYCODE_0 -> {
showChannel("0") showChannel("0")
@@ -442,7 +502,13 @@ class MainActivity : FragmentActivity(), Request.RequestListener {
Request.onDestroy() Request.onDestroy()
} }
override fun onRequestFinished() { override fun onRequestFinished(message: String?) {
if (message != null && !errorFragment.isVisible) {
supportFragmentManager.beginTransaction()
.add(R.id.main_browse_fragment, errorFragment)
.commitNow()
errorFragment.setErrorContent(message)
}
fragmentReady() fragmentReady()
} }
@@ -1,9 +1,10 @@
package com.lizongying.mytv package com.lizongying.mytv
import android.os.Bundle import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.util.Log import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast import android.widget.Toast
import androidx.leanback.app.BrowseSupportFragment import androidx.leanback.app.BrowseSupportFragment
import androidx.leanback.widget.ArrayObjectAdapter import androidx.leanback.widget.ArrayObjectAdapter
@@ -17,7 +18,6 @@ import androidx.leanback.widget.Presenter
import androidx.leanback.widget.Row import androidx.leanback.widget.Row
import androidx.leanback.widget.RowPresenter import androidx.leanback.widget.RowPresenter
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.lizongying.mytv.Utils.getDateTimestamp
import com.lizongying.mytv.api.YSP import com.lizongying.mytv.api.YSP
import com.lizongying.mytv.models.TVListViewModel import com.lizongying.mytv.models.TVListViewModel
import com.lizongying.mytv.models.TVViewModel import com.lizongying.mytv.models.TVViewModel
@@ -40,6 +40,29 @@ class MainFragment : BrowseSupportFragment() {
headersState = HEADERS_DISABLED headersState = HEADERS_DISABLED
} }
// override fun onCreateView(
// inflater: LayoutInflater,
// container: ViewGroup?,
// savedInstanceState: Bundle?
// ): View? {
// val rootView = super.onCreateView(inflater, container, savedInstanceState)
// rootView?.setOnClickListener {
// Log.i(TAG, "main on click")
// fragmentManager!!.beginTransaction().hide(this).commit()
// }
// mainFragment.view?.setOnClickListener {
// Log.i(TAG, "mainFragment on click")
// fragmentManager!!.beginTransaction().hide(this).commit()
// }
// getRowsSupportFragment().view?.setOnClickListener {
// Log.i(TAG, "getRowsSupportFragment on click")
// fragmentManager!!.beginTransaction().hide(this).commit()
// }
//
//
// return rootView
// }
override fun onStart() { override fun onStart() {
Log.i(TAG, "onStart") Log.i(TAG, "onStart")
super.onStart() super.onStart()
@@ -57,7 +80,7 @@ class MainFragment : BrowseSupportFragment() {
tvListViewModel.tvListViewModel.value?.forEach { tvViewModel -> tvListViewModel.tvListViewModel.value?.forEach { tvViewModel ->
tvViewModel.errInfo.observe(viewLifecycleOwner) { _ -> tvViewModel.errInfo.observe(viewLifecycleOwner) { _ ->
if (tvViewModel.errInfo.value != null if (tvViewModel.errInfo.value != null
&& tvViewModel.id.value == itemPosition && tvViewModel.getTV().id == itemPosition
) { ) {
Toast.makeText(context, tvViewModel.errInfo.value, Toast.LENGTH_SHORT).show() Toast.makeText(context, tvViewModel.errInfo.value, Toast.LENGTH_SHORT).show()
} }
@@ -66,18 +89,18 @@ class MainFragment : BrowseSupportFragment() {
// not first time && channel not change // not first time && channel not change
if (tvViewModel.ready.value != null if (tvViewModel.ready.value != null
&& tvViewModel.id.value == itemPosition && tvViewModel.getTV().id == itemPosition
&& check(tvViewModel) && check(tvViewModel)
) { ) {
Log.i(TAG, "ready ${tvViewModel.title.value}") Log.i(TAG, "ready ${tvViewModel.getTV().title}")
(activity as? MainActivity)?.play(tvViewModel) (activity as? MainActivity)?.play(tvViewModel)
} }
} }
tvViewModel.change.observe(viewLifecycleOwner) { _ -> tvViewModel.change.observe(viewLifecycleOwner) { _ ->
if (tvViewModel.change.value != null) { if (tvViewModel.change.value != null) {
val title = tvViewModel.title.value val title = tvViewModel.getTV().title
Log.i(TAG, "switch $title") Log.i(TAG, "switch $title")
if (tvViewModel.pid.value != "") { if (tvViewModel.getTV().pid != "") {
Log.i(TAG, "request $title") Log.i(TAG, "request $title")
lifecycleScope.launch(Dispatchers.IO) { lifecycleScope.launch(Dispatchers.IO) {
tvViewModel.let { Request.fetchData(it) } tvViewModel.let { Request.fetchData(it) }
@@ -124,7 +147,7 @@ class MainFragment : BrowseSupportFragment() {
private fun loadRows() { private fun loadRows() {
rowsAdapter = ArrayObjectAdapter(ListRowPresenter()) rowsAdapter = ArrayObjectAdapter(ListRowPresenter())
val cardPresenter = CardPresenter(viewLifecycleOwner) val cardPresenter = CardPresenter(context!!)
var idx: Long = 0 var idx: Long = 0
for ((k, v) in TVList.list) { for ((k, v) in TVList.list) {
@@ -135,8 +158,6 @@ class MainFragment : BrowseSupportFragment() {
tvViewModel.setItemPosition(idx2) tvViewModel.setItemPosition(idx2)
tvListViewModel.addTVViewModel(tvViewModel) tvListViewModel.addTVViewModel(tvViewModel)
listRowAdapter.add(tvViewModel) listRowAdapter.add(tvViewModel)
updateEPG(tvViewModel)
} }
tvListViewModel.maxNum.add(v.size) tvListViewModel.maxNum.add(v.size)
val header = HeaderItem(idx, k) val header = HeaderItem(idx, k)
@@ -196,8 +217,8 @@ class MainFragment : BrowseSupportFragment() {
row: Row row: Row
) { ) {
if (item is TVViewModel) { if (item is TVViewModel) {
if (itemPosition != item.id.value!!) { if (itemPosition != item.getTV().id) {
itemPosition = item.id.value!! itemPosition = item.getTV().id
tvListViewModel.setItemPosition(itemPosition) tvListViewModel.setItemPosition(itemPosition)
tvListViewModel.getTVViewModel(itemPosition)?.changed() tvListViewModel.getTVViewModel(itemPosition)?.changed()
} }
@@ -212,14 +233,14 @@ class MainFragment : BrowseSupportFragment() {
rowViewHolder: RowPresenter.ViewHolder, row: Row rowViewHolder: RowPresenter.ViewHolder, row: Row
) { ) {
if (item is TVViewModel) { if (item is TVViewModel) {
tvListViewModel.setItemPositionCurrent(item.id.value!!) tvListViewModel.setItemPositionCurrent(item.getTV().id)
(activity as MainActivity).mainActive() (activity as MainActivity).mainActive()
} }
} }
} }
fun check(tvViewModel: TVViewModel): Boolean { fun check(tvViewModel: TVViewModel): Boolean {
val title = tvViewModel.title.value val title = tvViewModel.getTV().title
val videoUrl = tvViewModel.videoIndex.value?.let { tvViewModel.videoUrl.value?.get(it) } val videoUrl = tvViewModel.videoIndex.value?.let { tvViewModel.videoUrl.value?.get(it) }
if (videoUrl == null || videoUrl == "") { if (videoUrl == null || videoUrl == "") {
Log.e(TAG, "$title videoUrl is empty") Log.e(TAG, "$title videoUrl is empty")
@@ -235,8 +256,11 @@ class MainFragment : BrowseSupportFragment() {
} }
fun fragmentReady() { fun fragmentReady() {
// request.fetchPage()
tvListViewModel.getTVViewModel(itemPosition)?.changed() tvListViewModel.getTVViewModel(itemPosition)?.changed()
tvListViewModel.tvListViewModel.value?.forEach { tvViewModel ->
updateEPG(tvViewModel)
}
} }
fun play(itemPosition: Int) { fun play(itemPosition: Int) {
@@ -0,0 +1,16 @@
package com.lizongying.mytv
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.net.ConnectivityManager
class NetworkChangeReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
if (intent?.action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
val isConnected = context?.isNetworkConnected
if (isConnected == true) {
}
}
}
}
@@ -82,6 +82,13 @@ class PlayerFragment : Fragment(), SurfaceHolder.Callback {
Log.e(TAG, "PlaybackException $error") Log.e(TAG, "PlaybackException $error")
tvViewModel?.changed() tvViewModel?.changed()
} }
override fun onIsPlayingChanged(isPlaying: Boolean) {
super.onIsPlayingChanged(isPlaying)
if (isPlaying) {
(activity as MainActivity).isPlaying()
}
}
}) })
} }
}) })
@@ -24,6 +24,9 @@ import com.lizongying.mytv.api.YSPTokenService
import com.lizongying.mytv.models.TVViewModel import com.lizongying.mytv.models.TVViewModel
import com.lizongying.mytv.proto.Ysp.cn.yangshipin.oms.common.proto.pageModel import com.lizongying.mytv.proto.Ysp.cn.yangshipin.oms.common.proto.pageModel
import com.lizongying.mytv.proto.Ysp.cn.yangshipin.omstv.common.proto.epgProgramModel import com.lizongying.mytv.proto.Ysp.cn.yangshipin.omstv.common.proto.epgProgramModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import retrofit2.Call import retrofit2.Call
import retrofit2.Callback import retrofit2.Callback
import retrofit2.Response import retrofit2.Response
@@ -57,7 +60,9 @@ object Request {
private var listener: RequestListener? = null private var listener: RequestListener? = null
private var initRetryTimes = 0 private var initRetryTimes = 0
private var initRetryMaxTimes = 1 private var initRetryMaxTimes = 0
private var openid = "vu0-8lgGV2LW9QjDeucB9H12d_6HQWmTFgqCWg5o-VBQN4"
fun onCreate() { fun onCreate() {
Log.i(TAG, "onCreate") Log.i(TAG, "onCreate")
@@ -86,7 +91,7 @@ object Request {
private fun fetchAuth(tvModel: TVViewModel, cookie: String) { private fun fetchAuth(tvModel: TVViewModel, cookie: String) {
cancelCall() cancelCall()
val title = tvModel.title.value val title = tvModel.getTV().title
val data = YSP.getAuthData(tvModel) val data = YSP.getAuthData(tvModel)
val request = AuthRequest(data) val request = AuthRequest(data)
@@ -166,17 +171,24 @@ object Request {
handler.removeCallbacks(btraceRunnable) handler.removeCallbacks(btraceRunnable)
} }
val title = tvModel.title.value val title = tvModel.getTV().title
tvModel.seq = 0 tvModel.seq = 0
val data = YSP.switch(tvModel) val data = YSP.switch(tvModel)
val request = LiveInfoRequest(data) val request = LiveInfoRequest(data)
call = request.let { call = request.let {
yspApiService.getLiveInfo( if (token == "") {
"guid=${YSP.getGuid()}; $cookie", yspApiService.getLiveInfoV2(
YSP.token, "guid=${YSP.getGuid()}; $cookie",
it it,
) )
} else {
yspApiService.getLiveInfo(
"guid=${YSP.getGuid()}; $cookie",
YSP.token,
it
)
}
} }
call?.enqueue(object : Callback<LiveInfo> { call?.enqueue(object : Callback<LiveInfo> {
@@ -254,7 +266,7 @@ object Request {
} }
} }
} else { } else {
Log.e(TAG, "$title status error") Log.e(TAG, "$title status error $data")
if (tvModel.retryTimes < tvModel.retryMaxTimes) { if (tvModel.retryTimes < tvModel.retryMaxTimes) {
tvModel.retryTimes++ tvModel.retryTimes++
if (tvModel.getTV().needToken) { if (tvModel.getTV().needToken) {
@@ -309,13 +321,13 @@ object Request {
tvModel.needGetToken = false tvModel.needGetToken = false
tvModel.tokenYSPRetryTimes = 0 tvModel.tokenYSPRetryTimes = 0
val cookie = val cookie =
"versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; deviceModel=120; appid=1400421205; yspappid=519748109;yspopenid=vu0-8lgGV2LW9QjDeuBFsX8yMnzs37Q3_HZF6XyVDpGR_I; vusession=$token" "versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; deviceModel=120; appid=1400421205; yspappid=519748109;yspopenid=$openid; vusession=$token"
fetchAuth(tvModel, cookie) fetchAuth(tvModel, cookie)
} else if (response.code() == 304) { } else if (response.code() == 304) {
tvModel.needGetToken = false tvModel.needGetToken = false
tvModel.tokenYSPRetryTimes = 0 tvModel.tokenYSPRetryTimes = 0
val cookie = val cookie =
"versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; deviceModel=120; appid=1400421205; yspappid=519748109; yspopenid=vu0-8lgGV2LW9QjDeuBFsX8yMnzs37Q3_HZF6XyVDpGR_I; vusession=$token" "versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; deviceModel=120; appid=1400421205; yspappid=519748109; yspopenid=$openid; vusession=$token"
fetchVideo(tvModel, cookie) fetchVideo(tvModel, cookie)
} else { } else {
Log.e(TAG, "info status error") Log.e(TAG, "info status error")
@@ -348,7 +360,7 @@ object Request {
}) })
} else { } else {
val cookie = val cookie =
"versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; deviceModel=120; appid=1400421205; yspappid=519748109;yspopenid=vu0-8lgGV2LW9QjDeuBFsX8yMnzs37Q3_HZF6XyVDpGR_I; vusession=$token" "versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; deviceModel=120; appid=1400421205; yspappid=519748109;yspopenid=$openid; vusession=$token"
fetchAuth(tvModel, cookie) fetchAuth(tvModel, cookie)
} }
} }
@@ -366,13 +378,13 @@ object Request {
tvModel.needGetToken = false tvModel.needGetToken = false
tvModel.tokenYSPRetryTimes = 0 tvModel.tokenYSPRetryTimes = 0
val cookie = val cookie =
"versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; deviceModel=120; appid=1400421205; yspappid=519748109; yspopenid=vu0-8lgGV2LW9QjDeuBFsX8yMnzs37Q3_HZF6XyVDpGR_I; vusession=$token" "versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; deviceModel=120; appid=1400421205; yspappid=519748109; yspopenid=$openid; vusession=$token"
fetchVideo(tvModel, cookie) fetchVideo(tvModel, cookie)
} else if (response.code() == 304) { } else if (response.code() == 304) {
tvModel.needGetToken = false tvModel.needGetToken = false
tvModel.tokenYSPRetryTimes = 0 tvModel.tokenYSPRetryTimes = 0
val cookie = val cookie =
"versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; deviceModel=120; appid=1400421205; yspappid=519748109; yspopenid=vu0-8lgGV2LW9QjDeuBFsX8yMnzs37Q3_HZF6XyVDpGR_I; vusession=$token" "versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; deviceModel=120; appid=1400421205; yspappid=519748109; yspopenid=$openid; vusession=$token"
fetchVideo(tvModel, cookie) fetchVideo(tvModel, cookie)
} else { } else {
Log.e(TAG, "info status error") Log.e(TAG, "info status error")
@@ -405,7 +417,7 @@ object Request {
}) })
} else { } else {
val cookie = val cookie =
"versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; deviceModel=120; appid=1400421205; yspappid=519748109; yspopenid=vu0-8lgGV2LW9QjDeuBFsX8yMnzs37Q3_HZF6XyVDpGR_I; vusession=$token" "versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; deviceModel=120; appid=1400421205; yspappid=519748109; yspopenid=$openid; vusession=$token"
fetchVideo(tvModel, cookie) fetchVideo(tvModel, cookie)
} }
} }
@@ -413,7 +425,7 @@ object Request {
private fun fetchFAuth(tvModel: TVViewModel) { private fun fetchFAuth(tvModel: TVViewModel) {
cancelCall() cancelCall()
val title = tvModel.title.value val title = tvModel.getTV().title
var qa = "HD" var qa = "HD"
if (tokenFH != "") { if (tokenFH != "") {
@@ -455,6 +467,9 @@ object Request {
} }
if (tvModel.getTV().needToken) { if (tvModel.getTV().needToken) {
if (token == "") {
tvModel.needGetToken = true
}
if (needAuth) { if (needAuth) {
fetchAuth(tvModel) fetchAuth(tvModel)
} else { } else {
@@ -462,7 +477,7 @@ object Request {
} }
} else { } else {
val cookie = val cookie =
"versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; deviceModel=120; appid=1400421205" "versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; updateProtocol=1; deviceModel=120"
if (needAuth) { if (needAuth) {
fetchAuth(tvModel, cookie) fetchAuth(tvModel, cookie)
} else { } else {
@@ -482,10 +497,14 @@ object Request {
.enqueue(object : Callback<InfoV2> { .enqueue(object : Callback<InfoV2> {
override fun onResponse(call: Call<InfoV2>, response: Response<InfoV2>) { override fun onResponse(call: Call<InfoV2>, response: Response<InfoV2>) {
if (response.isSuccessful) { if (response.isSuccessful) {
val o = response.body()?.o
val t = response.body()?.t val t = response.body()?.t
val f = response.body()?.f val f = response.body()?.f
val e = response.body()?.e val e = response.body()?.e
val c = response.body()?.c val c = response.body()?.c
if (!o.isNullOrEmpty()) {
openid = o
}
if (!t.isNullOrEmpty()) { if (!t.isNullOrEmpty()) {
token = t token = t
Log.i(TAG, "token success $token") Log.i(TAG, "token success $token")
@@ -516,7 +535,7 @@ object Request {
Utils.setBetween(c * 1000L) Utils.setBetween(c * 1000L)
Log.i(TAG, "current time $c") Log.i(TAG, "current time $c")
} }
listener?.onRequestFinished() listener?.onRequestFinished(null)
} else { } else {
Log.e(TAG, "token status error") Log.e(TAG, "token status error")
handler.postDelayed( handler.postDelayed(
@@ -527,7 +546,7 @@ object Request {
initRetryTimes++ initRetryTimes++
fetchInfoV2() fetchInfoV2()
} else { } else {
listener?.onRequestFinished() listener?.onRequestFinished("状态错误")
} }
} }
} }
@@ -543,7 +562,7 @@ object Request {
initRetryTimes++ initRetryTimes++
fetchInfoV2() fetchInfoV2()
} else { } else {
listener?.onRequestFinished() listener?.onRequestFinished("网络错误")
} }
} }
}) })
@@ -605,11 +624,11 @@ object Request {
} }
fun fetchBtrace(tvModel: TVViewModel) { fun fetchBtrace(tvModel: TVViewModel) {
val title = tvModel.title.value val title = tvModel.getTV().title
val guid = YSP.getGuid() val guid = YSP.getGuid()
val pid = tvModel.pid.value!! val pid = tvModel.getTV().pid
val sid = tvModel.sid.value!! val sid = tvModel.getTV().sid
yspBtraceService.kvcollect( yspBtraceService.kvcollect(
c_timestamp = YSP.generateGuid(), c_timestamp = YSP.generateGuid(),
guid = guid, guid = guid,
@@ -675,8 +694,8 @@ object Request {
} }
fun fetchYEPG(tvViewModel: TVViewModel) { fun fetchYEPG(tvViewModel: TVViewModel) {
val title = tvViewModel.title.value val title = tvViewModel.getTV().title
yspProtoService.getProgram(tvViewModel.programId.value!!, getDateFormat("yyyyMMdd")) yspProtoService.getProgram(tvViewModel.getTV().programId, getDateFormat("yyyyMMdd"))
.enqueue(object : Callback<epgProgramModel.Response> { .enqueue(object : Callback<epgProgramModel.Response> {
override fun onResponse( override fun onResponse(
call: Call<epgProgramModel.Response>, call: Call<epgProgramModel.Response>,
@@ -700,8 +719,8 @@ object Request {
} }
fun fetchFEPG(tvViewModel: TVViewModel) { fun fetchFEPG(tvViewModel: TVViewModel) {
val title = tvViewModel.title.value val title = tvViewModel.getTV().title
fAuthService.getEPG(tvViewModel.pid.value!!, getDateFormat("yyyyMMdd")) fAuthService.getEPG(tvViewModel.getTV().pid, getDateFormat("yyyyMMdd"))
.enqueue(object : Callback<List<FEPG>> { .enqueue(object : Callback<List<FEPG>> {
override fun onResponse( override fun onResponse(
call: Call<List<FEPG>>, call: Call<List<FEPG>>,
@@ -738,7 +757,7 @@ object Request {
} }
interface RequestListener { interface RequestListener {
fun onRequestFinished() fun onRequestFinished(message: String?)
} }
fun setRequestListener(listener: RequestListener) { fun setRequestListener(listener: RequestListener) {
@@ -5,12 +5,12 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import com.lizongying.mytv.databinding.DialogBinding import com.lizongying.mytv.databinding.SettingBinding
class SettingFragment : DialogFragment() { class SettingFragment : DialogFragment() {
private var _binding: DialogBinding? = null private var _binding: SettingBinding? = null
private val binding get() = _binding!! private val binding get() = _binding!!
private lateinit var updateManager: UpdateManager private lateinit var updateManager: UpdateManager
@@ -26,7 +26,7 @@ class SettingFragment : DialogFragment() {
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View { ): View {
val context = requireContext() // It‘s safe to get context here. val context = requireContext() // It‘s safe to get context here.
_binding = DialogBinding.inflate(inflater, container, false) _binding = SettingBinding.inflate(inflater, container, false)
binding.versionName.text = "当前版本: v${context.appVersionName}" binding.versionName.text = "当前版本: v${context.appVersionName}"
binding.version.text = "https://github.com/lizongying/my-tv" binding.version.text = "https://github.com/lizongying/my-tv"
@@ -34,7 +34,7 @@ class SettingFragment : DialogFragment() {
isChecked = SP.channelReversal isChecked = SP.channelReversal
setOnCheckedChangeListener { _, isChecked -> setOnCheckedChangeListener { _, isChecked ->
SP.channelReversal = isChecked SP.channelReversal = isChecked
(activity as MainActivity).settingActive() (activity as MainActivity).settingDelayHide()
} }
} }
@@ -42,7 +42,7 @@ class SettingFragment : DialogFragment() {
isChecked = SP.channelNum isChecked = SP.channelNum
setOnCheckedChangeListener { _, isChecked -> setOnCheckedChangeListener { _, isChecked ->
SP.channelNum = isChecked SP.channelNum = isChecked
(activity as MainActivity).settingActive() (activity as MainActivity).settingDelayHide()
} }
} }
@@ -50,23 +50,34 @@ class SettingFragment : DialogFragment() {
isChecked = SP.bootStartup isChecked = SP.bootStartup
setOnCheckedChangeListener { _, isChecked -> setOnCheckedChangeListener { _, isChecked ->
SP.bootStartup = isChecked SP.bootStartup = isChecked
(activity as MainActivity).settingActive() (activity as MainActivity).settingDelayHide()
} }
} }
updateManager = UpdateManager(context, this, context.appVersionCode) updateManager = UpdateManager(context, this, context.appVersionCode)
binding.checkVersion.setOnClickListener(OnClickListenerCheckVersion(updateManager)) binding.checkVersion.setOnClickListener(
OnClickListenerCheckVersion(
activity as MainActivity,
updateManager
)
)
return binding.root return binding.root
} }
fun setVersionName(versionName: String) { fun setVersionName(versionName: String) {
binding.versionName.text = versionName if (_binding != null) {
binding.versionName.text = versionName
}
} }
internal class OnClickListenerCheckVersion(private val updateManager: UpdateManager) : internal class OnClickListenerCheckVersion(
private val mainActivity: MainActivity,
private val updateManager: UpdateManager
) :
View.OnClickListener { View.OnClickListener {
override fun onClick(view: View?) { override fun onClick(view: View?) {
mainActivity.settingDelayHide()
updateManager.checkAndUpdate() updateManager.checkAndUpdate()
} }
} }
@@ -76,11 +87,6 @@ class SettingFragment : DialogFragment() {
_binding = null _binding = null
} }
override fun onDestroy() {
super.onDestroy()
// updateManager.destroy()
}
companion object { companion object {
const val TAG = "SettingFragment" const val TAG = "SettingFragment"
} }
@@ -1,12 +1,5 @@
package com.lizongying.mytv package com.lizongying.mytv
import android.content.Context
import android.util.Log
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.io.IOException
object TVList { object TVList {
val list: Map<String, List<TV>> by lazy { val list: Map<String, List<TV>> by lazy {
setup() setup()
@@ -970,63 +963,4 @@ object TVList {
// list = list.filterKeys { it in array } // list = list.filterKeys { it in array }
return list return list
} }
/**
* 初始化
*
* @param context Context
*/
fun init(context: Context) {
CoroutineScope(Dispatchers.Default).launch {
//获取本地版本号
val localVersion = ChannelUtils.getLocalVersion(context)
//获取服务器版本号
val serverVersion = try {
ChannelUtils.getServerVersion(context)
} catch (e: IOException) {
Log.e("TVList", "无法从服务器获取版本信息", e)
Integer.MIN_VALUE
}
//频道列表
val channelTVMap: MutableMap<String, MutableList<TV>> = mutableMapOf()
//是否从服务器更新
var updateFromServer = false
//获取频道列表
val tvList: List<TV> = if (localVersion < serverVersion) {
//获取服务器地址
val url = ChannelUtils.getServerUrl(context)
//是否从服务器更新
updateFromServer = true
Log.i("TVList", "从服务器获取频道信息")
try {
ChannelUtils.getServerChannel(url)
} catch (e: IOException) {
Log.e("TVList", "无法从服务器获取频道信息", e)
updateFromServer = false
ChannelUtils.getLocalChannel(context)
}
} else {
Log.i("TVList", "从本地获取频道信息")
//获取本地频道
ChannelUtils.getLocalChannel(context)
}
//按频道分类
for (tv in tvList) {
val key = tv.channel
if (channelTVMap.containsKey(key)) {
val list = channelTVMap[key]!!
list.add(tv)
channelTVMap[key] = list
} else {
channelTVMap[key] = mutableListOf(tv)
}
}
//保存频道列表
// list = channelTVMap
//保存版本号
if (updateFromServer) {
ChannelUtils.updateLocalChannel(context, serverVersion, tvList)
}
}
}
} }
@@ -30,7 +30,7 @@ class UpdateManager(
private var settingFragment: SettingFragment, private var settingFragment: SettingFragment,
private var versionCode: Long private var versionCode: Long
) : ) :
ConfirmationDialogFragment.ConfirmationDialogListener { ConfirmationFragment.ConfirmationListener {
private var myRequest = MyRequest() private var myRequest = MyRequest()
private var release: ReleaseV2? = null private var release: ReleaseV2? = null
@@ -47,12 +47,10 @@ class UpdateManager(
release = myRequest.getRelease() release = myRequest.getRelease()
Log.i(TAG, "versionCode $versionCode ${release?.c}") Log.i(TAG, "versionCode $versionCode ${release?.c}")
if (release?.c != null) { if (release?.c != null) {
if (release?.c!! >= versionCode) { text = if (release?.c!! > versionCode) {
text = "最新版本:${release?.n}" "最新版本:${release?.n}\n${release?.d ?: ""}"
val dialog = ConfirmationDialogFragment(this@UpdateManager)
dialog.show(settingFragment.fragmentManager, "ConfirmationDialogFragment")
} else { } else {
text = "已是最新版本,不需要更新" "已是最新版本,不需要更新"
} }
} }
} catch (e: Exception) { } catch (e: Exception) {
@@ -63,10 +61,11 @@ class UpdateManager(
} }
private fun updateUI(text: String) { private fun updateUI(text: String) {
settingFragment.setVersionName(text) val dialog = ConfirmationFragment(this@UpdateManager, text)
dialog.show(settingFragment.fragmentManager, TAG)
} }
fun haveStoragePermission(): Boolean { private fun haveStoragePermission(): Boolean {
if (Build.VERSION.SDK_INT >= 23) { if (Build.VERSION.SDK_INT >= 23) {
if (checkSelfPermission(context, android.Manifest.permission.WRITE_EXTERNAL_STORAGE) if (checkSelfPermission(context, android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
=== PermissionChecker.PERMISSION_GRANTED === PermissionChecker.PERMISSION_GRANTED
@@ -102,7 +101,7 @@ class UpdateManager(
Environment.DIRECTORY_DOWNLOADS, Environment.DIRECTORY_DOWNLOADS,
apkFileName apkFileName
) )
request.setTitle("New Version Download") request.setTitle("${settingFragment.resources.getString(R.string.app_name)} ${release.n}")
request.setNotificationVisibility(Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) request.setNotificationVisibility(Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
request.setAllowedOverRoaming(false) request.setAllowedOverRoaming(false)
request.setMimeType("application/vnd.android.package-archive") request.setMimeType("application/vnd.android.package-archive")
@@ -9,6 +9,8 @@ import okhttp3.TlsVersion
import retrofit2.Retrofit import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.converter.protobuf.ProtoConverterFactory import retrofit2.converter.protobuf.ProtoConverterFactory
import java.net.InetSocketAddress
import java.net.Proxy
import javax.net.ssl.SSLContext import javax.net.ssl.SSLContext
import javax.net.ssl.TrustManager import javax.net.ssl.TrustManager
import javax.net.ssl.X509TrustManager import javax.net.ssl.X509TrustManager
@@ -126,9 +128,12 @@ class ApiClient {
val sslContext = SSLContext.getInstance("SSL") val sslContext = SSLContext.getInstance("SSL")
sslContext.init(null, trustAllCerts, java.security.SecureRandom()) sslContext.init(null, trustAllCerts, java.security.SecureRandom())
val proxy = Proxy(Proxy.Type.HTTP, InetSocketAddress("10.0.2.2", 8888))
val builder = OkHttpClient.Builder() val builder = OkHttpClient.Builder()
.sslSocketFactory(sslContext.socketFactory, trustAllCerts[0] as X509TrustManager) .sslSocketFactory(sslContext.socketFactory, trustAllCerts[0] as X509TrustManager)
.hostnameVerifier { _, _ -> true } .hostnameVerifier { _, _ -> true }
// .proxy(proxy)
.dns(DnsCache()) .dns(DnsCache())
return enableTls12OnPreLollipop(builder).build() return enableTls12OnPreLollipop(builder).build()
@@ -16,6 +16,7 @@ data class Token(
) )
data class InfoV2( data class InfoV2(
val o: String?,
val f: String?, val f: String?,
val t: String?, val t: String?,
val e: Int?, val e: Int?,
@@ -25,6 +26,7 @@ data class InfoV2(
data class ReleaseV2( data class ReleaseV2(
val n: String?, val n: String?,
val u: String?, val u: String?,
val d: String?,
val c: Int?, val c: Int?,
) )
@@ -1,6 +1,7 @@
package com.lizongying.mytv.api package com.lizongying.mytv.api
import android.content.Context import android.content.Context
import android.util.Log
import com.lizongying.mytv.Encryptor import com.lizongying.mytv.Encryptor
import com.lizongying.mytv.MainActivity import com.lizongying.mytv.MainActivity
import com.lizongying.mytv.SP import com.lizongying.mytv.SP
@@ -63,8 +64,8 @@ object YSP {
} }
fun switch(tvModel: TVViewModel): String { fun switch(tvModel: TVViewModel): String {
livepid = tvModel.pid.value!! livepid = tvModel.getTV().pid
cnlid = tvModel.sid.value!! cnlid = tvModel.getTV().sid
defn = "fhd" defn = "fhd"
randStr = getRand() randStr = getRand()
@@ -82,7 +83,7 @@ object YSP {
} }
fun getAuthData(tvModel: TVViewModel): String { fun getAuthData(tvModel: TVViewModel): String {
livepid = tvModel.pid.value!! livepid = tvModel.getTV().pid
randStr = getRand() randStr = getRand()
@@ -21,6 +21,18 @@ interface YSPApiService {
@Body request: LiveInfoRequest, @Body request: LiveInfoRequest,
): Call<LiveInfo> ): Call<LiveInfo>
@POST("v1/player/get_live_info")
@Headers(
"content-type: application/json;charset=UTF-8",
"referer: https://www.yangshipin.cn/",
"yspappid: 519748109",
"user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
)
fun getLiveInfoV2(
@Header("cookie") cookie: String,
@Body request: LiveInfoRequest,
): Call<LiveInfo>
@POST("v1/player/auth") @POST("v1/player/auth")
@Headers( @Headers(
"content-type: application/x-www-form-urlencoded;charset=UTF-8", "content-type: application/x-www-form-urlencoded;charset=UTF-8",
@@ -1,15 +1,9 @@
package com.lizongying.mytv.models package com.lizongying.mytv.models
import android.net.Uri
import android.util.Log import android.util.Log
import androidx.annotation.OptIn
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.media3.common.MediaItem
import androidx.media3.common.util.UnstableApi
import androidx.media3.datasource.DefaultHttpDataSource
import androidx.media3.exoplayer.hls.HlsMediaSource
import com.lizongying.mytv.TV import com.lizongying.mytv.TV
import com.lizongying.mytv.api.FEPG import com.lizongying.mytv.api.FEPG
import com.lizongying.mytv.proto.Ysp.cn.yangshipin.omstv.common.proto.programModel.Program import com.lizongying.mytv.proto.Ysp.cn.yangshipin.omstv.common.proto.programModel.Program
@@ -26,7 +20,7 @@ class TVViewModel(private var tv: TV) : ViewModel() {
var tokenYSPRetryTimes = 0 var tokenYSPRetryTimes = 0
var tokenYSPRetryMaxTimes = 0 var tokenYSPRetryMaxTimes = 0
var tokenFHRetryTimes = 0 var tokenFHRetryTimes = 0
var tokenFHRetryMaxTimes = 4 var tokenFHRetryMaxTimes = 8
var needGetToken = false var needGetToken = false
@@ -34,22 +28,10 @@ class TVViewModel(private var tv: TV) : ViewModel() {
val errInfo: LiveData<String> val errInfo: LiveData<String>
get() = _errInfo get() = _errInfo
private val _programId = MutableLiveData<String>()
val programId: LiveData<String>
get() = _programId
private var _epg = MutableLiveData<MutableList<EPG>>() private var _epg = MutableLiveData<MutableList<EPG>>()
val epg: LiveData<MutableList<EPG>> val epg: LiveData<MutableList<EPG>>
get() = _epg get() = _epg
private val _id = MutableLiveData<Int>()
val id: LiveData<Int>
get() = _id
private val _title = MutableLiveData<String>()
val title: LiveData<String>
get() = _title
private val _videoUrl = MutableLiveData<List<String>>() private val _videoUrl = MutableLiveData<List<String>>()
val videoUrl: LiveData<List<String>> val videoUrl: LiveData<List<String>>
get() = _videoUrl get() = _videoUrl
@@ -58,18 +40,6 @@ class TVViewModel(private var tv: TV) : ViewModel() {
val videoIndex: LiveData<Int> val videoIndex: LiveData<Int>
get() = _videoIndex get() = _videoIndex
private val _logo = MutableLiveData<Any>()
val logo: LiveData<Any>
get() = _logo
private val _pid = MutableLiveData<String>()
val pid: LiveData<String>
get() = _pid
private val _sid = MutableLiveData<String>()
val sid: LiveData<String>
get() = _sid
private val _change = MutableLiveData<Boolean>() private val _change = MutableLiveData<Boolean>()
val change: LiveData<Boolean> val change: LiveData<Boolean>
get() = _change get() = _change
@@ -116,14 +86,8 @@ class TVViewModel(private var tv: TV) : ViewModel() {
} }
init { init {
_id.value = tv.id
_title.value = tv.title
_videoUrl.value = tv.videoUrl _videoUrl.value = tv.videoUrl
_videoIndex.value = tv.videoUrl.lastIndex _videoIndex.value = tv.videoUrl.lastIndex
_logo.value = tv.logo
_programId.value = tv.programId
_pid.value = tv.pid
_sid.value = tv.sid
} }
fun getRowPosition(): Int { fun getRowPosition(): Int {
@@ -146,10 +110,6 @@ class TVViewModel(private var tv: TV) : ViewModel() {
_errInfo.value = info _errInfo.value = info
} }
fun update(t: TV) {
tv = t
}
fun getTV(): TV { fun getTV(): TV {
return tv return tv
} }
@@ -172,21 +132,6 @@ class TVViewModel(private var tv: TV) : ViewModel() {
_epg.value = p.map { EPG(it.title, formatFTime(it.event_time)) }.toMutableList() _epg.value = p.map { EPG(it.title, formatFTime(it.event_time)) }.toMutableList()
} }
/**
* (playerView?.player as ExoPlayer).setMediaSource(tvViewModel.buildSource())
*/
@OptIn(UnstableApi::class)
fun buildSource(): HlsMediaSource {
val httpDataSource = DefaultHttpDataSource.Factory()
// mHeaders?.let { httpDataSource.setDefaultRequestProperties(it) }
return HlsMediaSource.Factory(httpDataSource).createMediaSource(
MediaItem.fromUri(
Uri.parse(getVideoUrlCurrent())
)
)
}
fun getVideoUrlCurrent(): String { fun getVideoUrlCurrent(): String {
return _videoUrl.value!![_videoIndex.value!!] return _videoUrl.value!![_videoIndex.value!!]
} }
-892
View File
@@ -1,892 +0,0 @@
[
{
"id": 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,
"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,
"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,
"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,
"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,
"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,
"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,
"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,
"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,
"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,
"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,
"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,
"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,
"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,
"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,
"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,
"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,
"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,
"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,
"channel": "央视",
"logo": "",
"pid": "600156816",
"sid": "2020603421",
"programId": "",
"needToken": false,
"mustToken": false,
"title": "CCTV8K 超高清",
"videoUrl": [
""
]
},
{
"id": 20,
"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,
"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,
"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,
"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,
"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,
"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,
"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,
"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,
"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,
"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,
"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,
"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,
"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,
"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,
"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,
"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,
"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,
"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,
"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,
"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,
"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,
"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,
"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,
"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,
"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,
"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,
"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,
"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,
"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,
"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,
"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,
"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,
"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,
"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,
"channel": "地方",
"logo": "",
"pid": "600152137",
"sid": "2019927003",
"programId": "",
"needToken": true,
"mustToken": false,
"title": "天津卫视",
"videoUrl": [
""
]
},
{
"id": 55,
"channel": "地方",
"logo": "",
"pid": "600152138",
"sid": "2019927403",
"programId": "",
"needToken": true,
"mustToken": false,
"title": "新疆卫视",
"videoUrl": [
""
]
},
{
"id": 56,
"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,
"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,
"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,
"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,
"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,
"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"
]
}
]
+2
View File
@@ -1,5 +1,7 @@
<resources> <resources>
<color name="focus">#0096a6</color>
<color name="fastlane_background">#30000000</color> <color name="fastlane_background">#30000000</color>
<color name="black">#000</color> <color name="black">#000</color>
<color name="white">#FFF</color> <color name="white">#FFF</color>
<color name="ic_launcher_background">#223239</color>
</resources> </resources>
@@ -1,4 +1,3 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<color name="ic_launcher_background">#223239</color>
</resources> </resources>
-6
View File
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<integer name="local_channel_version">1</integer>
<string name="server_version_url">https://fastly.jsdelivr.net/gh/LeGend-wLw/my-tv-json-utils@main/version.txt</string>
<string name="server_url">https://fastly.jsdelivr.net/gh/LeGend-wLw/my-tv-json-utils@main/channels.json</string>
</resources>
+1
View File
@@ -4,4 +4,5 @@
<string name="title_channel_num">数字选台</string> <string name="title_channel_num">数字选台</string>
<string name="check_version">检查更新</string> <string name="check_version">检查更新</string>
<string name="title_boot_startup">开机自启</string> <string name="title_boot_startup">开机自启</string>
<string name="dismiss_error">好的</string>
</resources> </resources>