mirror of
https://github.com/lizongying/my-tv.git
synced 2026-10-31 16:46:30 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
10f261be8b | ||
|
|
52d356817a | ||
|
|
6ede7f76ef | ||
|
|
a4da01adf4 | ||
|
|
8b33c7c94c | ||
|
|
84a6c0f78e | ||
|
|
1417bbf57b | ||
|
|
ce678b8741 | ||
|
|
353dc0570f | ||
|
|
d7a60531ab | ||
|
|
764f2a1549 |
@@ -0,0 +1,83 @@
|
|||||||
|
name: Bug 反馈
|
||||||
|
description: 反馈一个 Bug
|
||||||
|
labels: [ "bug" ]
|
||||||
|
title: "[BUG] "
|
||||||
|
body:
|
||||||
|
- type: checkboxes
|
||||||
|
id: checklist
|
||||||
|
attributes:
|
||||||
|
label: 检查清单
|
||||||
|
description: 确保我们的错误报告表单适合您。
|
||||||
|
options:
|
||||||
|
- label: 之前没有人提交过类似或相同的 bug report。
|
||||||
|
required: true
|
||||||
|
- label: 我正在使用本软件的最新版本。
|
||||||
|
required: true
|
||||||
|
- type: dropdown
|
||||||
|
id: version
|
||||||
|
attributes:
|
||||||
|
label: my-ty 版本
|
||||||
|
description: 请选择正在使用的版本
|
||||||
|
options:
|
||||||
|
- 最新稳定版
|
||||||
|
- 最新 CI 版
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: bug
|
||||||
|
attributes:
|
||||||
|
label: Bug 描述
|
||||||
|
description: 请描述 bug 详情
|
||||||
|
placeholder: |
|
||||||
|
e.g. Crashed when generating snapshot.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: expected
|
||||||
|
attributes:
|
||||||
|
label: 预期行为
|
||||||
|
description: 你预期会发生什么?
|
||||||
|
placeholder: |
|
||||||
|
e.g. A New snapshot!
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: actual
|
||||||
|
attributes:
|
||||||
|
label: 实际行为
|
||||||
|
description: 反而发生了什么?
|
||||||
|
placeholder: |
|
||||||
|
e.g. Crashed.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: steps
|
||||||
|
attributes:
|
||||||
|
label: 复现步骤
|
||||||
|
description: 如何复现这个 bug。
|
||||||
|
placeholder: |
|
||||||
|
1. Open the app
|
||||||
|
2. Crashed
|
||||||
|
|
||||||
|
What an app.
|
||||||
|
- type: input
|
||||||
|
id: ui
|
||||||
|
attributes:
|
||||||
|
label: UI / OS
|
||||||
|
description: 你的电视系统 UI 或 OS 或 品牌
|
||||||
|
placeholder: TCL / XIAOMI / PHONE / etc.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: input
|
||||||
|
id: android
|
||||||
|
attributes:
|
||||||
|
label: Android 版本
|
||||||
|
description: 你的 Android 版本
|
||||||
|
placeholder: "12"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: additional
|
||||||
|
attributes:
|
||||||
|
label: 额外信息
|
||||||
|
description: 任何你觉得值得说的。
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
name: 功能(新频道)请求
|
||||||
|
description: 提出一个建议
|
||||||
|
labels: [ "enhancement" ]
|
||||||
|
title: "[FR] "
|
||||||
|
body:
|
||||||
|
- type: checkboxes
|
||||||
|
id: checklist
|
||||||
|
attributes:
|
||||||
|
label: 检查清单
|
||||||
|
description: 确保我们的错误报告表单适合您。
|
||||||
|
options:
|
||||||
|
- label: 之前没有人提交过类似或相同的功能请求。
|
||||||
|
required: true
|
||||||
|
- label: 这个建议不会背离 LibChecker 的初衷。
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: propose
|
||||||
|
attributes:
|
||||||
|
label: 改进目的
|
||||||
|
description: 改进有什么用
|
||||||
|
placeholder: |
|
||||||
|
Show your idea here.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: solution
|
||||||
|
attributes:
|
||||||
|
label: 解决方案
|
||||||
|
description: 你会怎么完成这个改进?
|
||||||
|
placeholder: |
|
||||||
|
How to do it on your opinion? Or left this blank
|
||||||
|
- type: textarea
|
||||||
|
id: addition
|
||||||
|
attributes:
|
||||||
|
label: 额外信息
|
||||||
|
description: 任何你觉得值得说的。
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
下载安装 [releases](https://github.com/lizongying/my-tv/releases/)
|
下载安装 [releases](https://github.com/lizongying/my-tv/releases/)
|
||||||
|
|
||||||
其他地址 [my-tv](https://lyrics.run/my-tv.html)
|
更多地址 [my-tv](https://lyrics.run/my-tv.html)
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||
@@ -14,46 +14,114 @@
|
|||||||
|
|
||||||
## 更新日志
|
## 更新日志
|
||||||
|
|
||||||
### v1.5.2(通用版)
|
### v1.6.2(通用)
|
||||||
|
|
||||||
|
* 新的频道列表样式
|
||||||
|
|
||||||
|
### 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恢复后频道号、频道列表不自动消失的问题
|
* 修复APP恢复后频道号、频道列表不自动消失的问题
|
||||||
|
|
||||||
### v1.5.1(高版本专用)
|
### v1.5.1(安卓5及以上专用)
|
||||||
|
|
||||||
* 性能优化
|
* 性能优化
|
||||||
|
|
||||||
### v1.5.0(通用版)
|
### v1.5.0(通用)
|
||||||
|
|
||||||
* 修复部分情况下APP切换后无法继续播放的问题
|
* 修复部分情况下APP切换后无法继续播放的问题
|
||||||
|
|
||||||
### v1.4.9(高版本专用)
|
### v1.4.9(安卓5及以上专用)
|
||||||
|
|
||||||
* 同步v1.4.8
|
* 同步v1.4.8
|
||||||
|
|
||||||
### v1.4.8(通用版)
|
### v1.4.8(通用)
|
||||||
|
|
||||||
* 频道号从1开始,CCTV5+为18
|
* 频道号从1开始,CCTV5+为18
|
||||||
* 提高CCTV6清晰度
|
* 提高CCTV6清晰度
|
||||||
* 增加天津卫视、新疆卫视
|
* 增加天津卫视、新疆卫视
|
||||||
|
|
||||||
### v1.4.7(高版本专用)
|
### v1.4.7(安卓5及以上专用)
|
||||||
|
|
||||||
* 修复部分用户CCTV13播放过程中卡住的问题
|
* 修复部分用户CCTV13播放过程中卡住的问题
|
||||||
* 调整CCTV的频道顺序
|
* 调整CCTV的频道顺序
|
||||||
|
|
||||||
### v1.4.6(通用版)
|
### v1.4.6(通用)
|
||||||
|
|
||||||
* 10以下频道不再需要先按0
|
* 10以下频道不再需要先按0
|
||||||
|
|
||||||
### v1.4.5(高版本专用)
|
### v1.4.5(安卓5及以上专用)
|
||||||
|
|
||||||
* 数字选台配置
|
* 数字选台配置
|
||||||
|
|
||||||
### v1.4.4(通用版)
|
### 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
|
### v1.3.3
|
||||||
|
|
||||||
* 部分错误会提示用户
|
* 部分错误会提示用户
|
||||||
@@ -64,6 +132,27 @@
|
|||||||
* 增加重试,减少因网络问题导致的播放失败
|
* 增加重试,减少因网络问题导致的播放失败
|
||||||
* 优化横幅banner
|
* 优化横幅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
|
### v1.2.6
|
||||||
|
|
||||||
* 支持安卓4.2
|
* 支持安卓4.2
|
||||||
@@ -84,6 +173,14 @@
|
|||||||
* 播放相同的频道,不再重复加载
|
* 播放相同的频道,不再重复加载
|
||||||
* 暂时移除部分频道
|
* 暂时移除部分频道
|
||||||
|
|
||||||
|
### v1.2.3
|
||||||
|
|
||||||
|
* 固定视频比列为16:9
|
||||||
|
* 隐藏全面屏底部的小横条
|
||||||
|
* 移除移动专区
|
||||||
|
* 修复一个闪退问题
|
||||||
|
* 更换图标和应用名
|
||||||
|
|
||||||
## 其他
|
## 其他
|
||||||
|
|
||||||
小米电视可以使用小米电视助手进行安装
|
小米电视可以使用小米电视助手进行安装
|
||||||
@@ -106,9 +203,9 @@ adb install my-tv.apk
|
|||||||
* 凤凰卫视、凤凰资讯台
|
* 凤凰卫视、凤凰资讯台
|
||||||
* 海外
|
* 海外
|
||||||
* 1.5.0 无法安装,1.5.1 可以安装
|
* 1.5.0 无法安装,1.5.1 可以安装
|
||||||
* 获取系统时间
|
|
||||||
* 选中的图标比例能否相差更大
|
* 选中的图标比例能否相差更大
|
||||||
* 自动重连
|
* 节目增加预告
|
||||||
|
* 频道列表优化
|
||||||
|
|
||||||
## 赞赏
|
## 赞赏
|
||||||
|
|
||||||
|
|||||||
@@ -113,4 +113,8 @@ dependencies {
|
|||||||
implementation 'com.github.bumptech.glide:glide:4.11.0'
|
implementation 'com.github.bumptech.glide:glide:4.11.0'
|
||||||
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.6.2"
|
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.6.2"
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.0-RC")
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.0-RC")
|
||||||
|
|
||||||
|
implementation 'com.google.android.exoplayer:exoplayer-ui:2.13.3'
|
||||||
|
implementation 'com.google.android.exoplayer:exoplayer-core:2.13.3'
|
||||||
|
implementation 'com.google.android.exoplayer:exoplayer-hls:2.13.3'
|
||||||
}
|
}
|
||||||
@@ -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.RECEIVE_BOOT_COMPLETED" />
|
||||||
<application
|
<application
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:largeHeap="true"
|
android:largeHeap="true"
|
||||||
@@ -29,5 +30,13 @@
|
|||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
<receiver
|
||||||
|
android:name=".BootReceiver"
|
||||||
|
android:exported="true">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
</application>
|
</application>
|
||||||
</manifest>
|
</manifest>
|
||||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,19 @@
|
|||||||
|
package com.lizongying.mytv
|
||||||
|
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
|
||||||
|
class BootReceiver : BroadcastReceiver() {
|
||||||
|
|
||||||
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
|
val sp = context.getSharedPreferences("MainActivity", Context.MODE_PRIVATE)
|
||||||
|
if (sp.getBoolean(MainActivity.BOOT_STARTUP, true)) {
|
||||||
|
context.startActivity(
|
||||||
|
Intent(context, MainActivity::class.java)
|
||||||
|
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ package com.lizongying.mytv
|
|||||||
|
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.view.ContextThemeWrapper
|
import android.view.ContextThemeWrapper
|
||||||
|
import android.view.KeyEvent
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.animation.ScaleAnimation
|
import android.view.animation.ScaleAnimation
|
||||||
@@ -14,31 +15,40 @@ import com.lizongying.mytv.models.TVListViewModel
|
|||||||
import com.lizongying.mytv.models.TVViewModel
|
import com.lizongying.mytv.models.TVViewModel
|
||||||
|
|
||||||
|
|
||||||
class CardAdapter(private val owner: LifecycleOwner, private var tvListViewModel: TVListViewModel) :
|
class CardAdapter(
|
||||||
|
private val recyclerView: RecyclerView,
|
||||||
|
private val owner: LifecycleOwner,
|
||||||
|
private var tvListViewModel: TVListViewModel,
|
||||||
|
) :
|
||||||
RecyclerView.Adapter<CardAdapter.ViewHolder>() {
|
RecyclerView.Adapter<CardAdapter.ViewHolder>() {
|
||||||
|
|
||||||
private var listener: ItemListener? = null
|
private var listener: ItemListener? = null
|
||||||
|
private var focused: View? = null
|
||||||
|
private var defaultFocused = false
|
||||||
|
var defaultFocus: Int = -1
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
val cardView = object :
|
val cardView = object :
|
||||||
ImageCardView(ContextThemeWrapper(parent.context, R.style.CustomImageCardTheme)) {}
|
ImageCardView(ContextThemeWrapper(parent.context, R.style.CustomImageCardTheme)) {}
|
||||||
startScaleAnimation(cardView, 1.0f, 0.9f)
|
|
||||||
cardView.isFocusable = true
|
cardView.isFocusable = true
|
||||||
cardView.isFocusableInTouchMode = true
|
cardView.isFocusableInTouchMode = true
|
||||||
|
|
||||||
return ViewHolder(cardView)
|
return ViewHolder(cardView)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun startScaleAnimation(view: View, fromScale: Float, toScale: Float) {
|
fun clear() {
|
||||||
|
focused?.clearFocus()
|
||||||
|
recyclerView.invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun startScaleAnimation(view: View, fromScale: Float, toScale: Float, duration: Long) {
|
||||||
val scaleAnimation = ScaleAnimation(
|
val scaleAnimation = ScaleAnimation(
|
||||||
fromScale, toScale,
|
fromScale, toScale,
|
||||||
fromScale, toScale,
|
fromScale, toScale,
|
||||||
ScaleAnimation.RELATIVE_TO_SELF, 0.5f,
|
ScaleAnimation.RELATIVE_TO_SELF, 0.5f,
|
||||||
ScaleAnimation.RELATIVE_TO_SELF, 0.5f
|
ScaleAnimation.RELATIVE_TO_SELF, 0.5f
|
||||||
)
|
)
|
||||||
scaleAnimation.duration = 200
|
scaleAnimation.duration = duration
|
||||||
scaleAnimation.fillAfter = true
|
scaleAnimation.fillAfter = false
|
||||||
|
|
||||||
view.startAnimation(scaleAnimation)
|
view.startAnimation(scaleAnimation)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,21 +58,35 @@ class CardAdapter(private val owner: LifecycleOwner, private var tvListViewModel
|
|||||||
val tvViewModel = item as TVViewModel
|
val tvViewModel = item as TVViewModel
|
||||||
val cardView = viewHolder.view as ImageCardView
|
val cardView = viewHolder.view as ImageCardView
|
||||||
|
|
||||||
cardView.setOnFocusChangeListener { _, hasFocus ->
|
if (!defaultFocused && item.id.value == defaultFocus) {
|
||||||
|
cardView.requestFocus()
|
||||||
|
defaultFocused = true
|
||||||
|
}
|
||||||
|
|
||||||
|
val onFocusChangeListener = View.OnFocusChangeListener { _, hasFocus ->
|
||||||
listener?.onItemFocusChange(item, hasFocus)
|
listener?.onItemFocusChange(item, hasFocus)
|
||||||
|
|
||||||
|
// if (hasFocus && defaultFocus == item.id.value) {
|
||||||
if (hasFocus) {
|
if (hasFocus) {
|
||||||
startScaleAnimation(cardView, 0.9f, 1.0f)
|
focused = cardView
|
||||||
} else {
|
startScaleAnimation(cardView, 0.9f, 1.0f, 200)
|
||||||
startScaleAnimation(cardView, 1.0f, 0.9f)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cardView.onFocusChangeListener = onFocusChangeListener
|
||||||
|
|
||||||
cardView.setOnClickListener { _ ->
|
cardView.setOnClickListener { _ ->
|
||||||
listener?.onItemClicked(item)
|
listener?.onItemClicked(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cardView.setOnKeyListener { _, keyCode, event: KeyEvent? ->
|
||||||
|
if (event?.action == KeyEvent.ACTION_DOWN) {
|
||||||
|
return@setOnKeyListener listener?.onKey(keyCode) ?: false
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
cardView.titleText = tvViewModel.title.value
|
cardView.titleText = tvViewModel.title.value
|
||||||
cardView.tag = tvViewModel.videoUrl.value
|
|
||||||
|
|
||||||
when (tvViewModel.title.value) {
|
when (tvViewModel.title.value) {
|
||||||
"CCTV8K 超高清" -> Glide.with(viewHolder.view.context)
|
"CCTV8K 超高清" -> Glide.with(viewHolder.view.context)
|
||||||
@@ -80,6 +104,11 @@ class CardAdapter(private val owner: LifecycleOwner, private var tvListViewModel
|
|||||||
.centerInside()
|
.centerInside()
|
||||||
.into(cardView.mainImageView)
|
.into(cardView.mainImageView)
|
||||||
|
|
||||||
|
"兵团卫视" -> Glide.with(viewHolder.view.context)
|
||||||
|
.load(R.drawable.bingtuan)
|
||||||
|
.centerInside()
|
||||||
|
.into(cardView.mainImageView)
|
||||||
|
|
||||||
else -> Glide.with(viewHolder.view.context)
|
else -> Glide.with(viewHolder.view.context)
|
||||||
.load(tvViewModel.logo.value)
|
.load(tvViewModel.logo.value)
|
||||||
.centerInside()
|
.centerInside()
|
||||||
@@ -106,6 +135,7 @@ class CardAdapter(private val owner: LifecycleOwner, private var tvListViewModel
|
|||||||
interface ItemListener {
|
interface ItemListener {
|
||||||
fun onItemFocusChange(tvViewModel: TVViewModel, hasFocus: Boolean)
|
fun onItemFocusChange(tvViewModel: TVViewModel, hasFocus: Boolean)
|
||||||
fun onItemClicked(tvViewModel: TVViewModel)
|
fun onItemClicked(tvViewModel: TVViewModel)
|
||||||
|
fun onKey(keyCode: Int): Boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setItemListener(listener: ItemListener) {
|
fun setItemListener(listener: ItemListener) {
|
||||||
|
|||||||
@@ -1,77 +0,0 @@
|
|||||||
package com.lizongying.mytv
|
|
||||||
|
|
||||||
import android.graphics.Color
|
|
||||||
import android.view.ContextThemeWrapper
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import android.widget.ImageView
|
|
||||||
import androidx.leanback.widget.ImageCardView
|
|
||||||
import androidx.leanback.widget.Presenter
|
|
||||||
import androidx.lifecycle.LifecycleOwner
|
|
||||||
import com.bumptech.glide.Glide
|
|
||||||
import com.lizongying.mytv.models.TVViewModel
|
|
||||||
|
|
||||||
class CardPresenter(
|
|
||||||
private val owner: LifecycleOwner,
|
|
||||||
) : Presenter() {
|
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup): ViewHolder {
|
|
||||||
val cardView = object :
|
|
||||||
ImageCardView(ContextThemeWrapper(parent.context, R.style.CustomImageCardTheme)) {}
|
|
||||||
|
|
||||||
cardView.isFocusable = true
|
|
||||||
cardView.isFocusableInTouchMode = true
|
|
||||||
return ViewHolder(cardView)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onBindViewHolder(viewHolder: ViewHolder, item: Any) {
|
|
||||||
val tvViewModel = item as TVViewModel
|
|
||||||
val cardView = viewHolder.view as ImageCardView
|
|
||||||
|
|
||||||
cardView.titleText = tvViewModel.title.value
|
|
||||||
cardView.setMainImageDimensions(CARD_WIDTH, CARD_HEIGHT)
|
|
||||||
cardView.tag = tvViewModel.videoUrl.value
|
|
||||||
|
|
||||||
when (tvViewModel.title.value) {
|
|
||||||
"CCTV8K 超高清" -> Glide.with(viewHolder.view.context)
|
|
||||||
.load(R.drawable.cctv8k)
|
|
||||||
.centerInside()
|
|
||||||
.into(cardView.mainImageView)
|
|
||||||
|
|
||||||
"天津卫视" -> 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) {
|
|
||||||
cardView.contentText = program.name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onUnbindViewHolder(viewHolder: ViewHolder) {
|
|
||||||
val cardView = viewHolder.view as ImageCardView
|
|
||||||
cardView.mainImage = null
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val TAG = "CardPresenter"
|
|
||||||
private const val CARD_WIDTH = 300
|
|
||||||
private const val CARD_HEIGHT = 101
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package com.lizongying.mytv
|
||||||
|
|
||||||
|
import android.app.AlertDialog
|
||||||
|
import android.app.Dialog
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.fragment.app.DialogFragment
|
||||||
|
|
||||||
|
class ConfirmationDialogFragment(private val listener: ConfirmationDialogListener) :
|
||||||
|
DialogFragment() {
|
||||||
|
|
||||||
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
|
return activity?.let {
|
||||||
|
val builder = AlertDialog.Builder(it)
|
||||||
|
builder.setTitle("确认")
|
||||||
|
.setMessage("确认更新吗?")
|
||||||
|
.setPositiveButton(
|
||||||
|
"确定"
|
||||||
|
) { _, _ ->
|
||||||
|
listener.onConfirm()
|
||||||
|
}
|
||||||
|
.setNegativeButton(
|
||||||
|
"取消"
|
||||||
|
) { _, _ ->
|
||||||
|
listener.onCancel()
|
||||||
|
}
|
||||||
|
// 创建并返回 AlertDialog 对象
|
||||||
|
builder.create()
|
||||||
|
} ?: throw IllegalStateException("Activity cannot be null")
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ConfirmationDialogListener {
|
||||||
|
fun onConfirm()
|
||||||
|
fun onCancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -9,6 +9,8 @@ class Encryptor {
|
|||||||
|
|
||||||
external fun hash(data: ByteArray): ByteArray?
|
external fun hash(data: ByteArray): ByteArray?
|
||||||
|
|
||||||
|
external fun hash2(data: ByteArray): ByteArray?
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
init {
|
init {
|
||||||
System.loadLibrary("native")
|
System.loadLibrary("native")
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package com.lizongying.mytv
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Canvas
|
||||||
|
import android.graphics.Paint
|
||||||
|
import android.graphics.Rect
|
||||||
|
import android.view.View
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
|
||||||
|
class GrayOverlayItemDecoration(private val context: Context) : RecyclerView.ItemDecoration() {
|
||||||
|
|
||||||
|
private val grayOverlayPaint = Paint().apply {
|
||||||
|
color = ContextCompat.getColor(context, R.color.gray_overlay)
|
||||||
|
style = Paint.Style.FILL
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDrawOver(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
|
||||||
|
super.onDrawOver(c, parent, state)
|
||||||
|
|
||||||
|
val childCount = parent.childCount
|
||||||
|
for (i in 0 until childCount) {
|
||||||
|
val child = parent.getChildAt(i)
|
||||||
|
if (!child.hasFocus()) {
|
||||||
|
// 计算遮罩层的大小
|
||||||
|
val overlayRect = Rect(
|
||||||
|
child.left,
|
||||||
|
child.top,
|
||||||
|
child.right,
|
||||||
|
child.bottom
|
||||||
|
)
|
||||||
|
// 绘制灰色遮罩层
|
||||||
|
c.drawRect(overlayRect, grayOverlayPaint)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemOffsets(
|
||||||
|
outRect: Rect,
|
||||||
|
view: View,
|
||||||
|
parent: RecyclerView,
|
||||||
|
state: RecyclerView.State
|
||||||
|
) {
|
||||||
|
super.getItemOffsets(outRect, view, parent, state)
|
||||||
|
// 在此处设置偏移量为0,以防止遮罩层影响项的布局
|
||||||
|
outRect.setEmpty()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -43,6 +43,10 @@ class InfoFragment : Fragment() {
|
|||||||
.load(R.drawable.xinjiang)
|
.load(R.drawable.xinjiang)
|
||||||
.into(binding.infoLogo)
|
.into(binding.infoLogo)
|
||||||
|
|
||||||
|
"兵团卫视" -> Glide.with(this)
|
||||||
|
.load(R.drawable.bingtuan)
|
||||||
|
.into(binding.infoLogo)
|
||||||
|
|
||||||
else -> Glide.with(this)
|
else -> Glide.with(this)
|
||||||
.load(tvViewModel.logo.value)
|
.load(tvViewModel.logo.value)
|
||||||
.into(binding.infoLogo)
|
.into(binding.infoLogo)
|
||||||
|
|||||||
@@ -18,16 +18,22 @@ 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
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
|
|
||||||
|
|
||||||
class MainActivity : FragmentActivity() {
|
class MainActivity : FragmentActivity() {
|
||||||
|
|
||||||
var playerFragment = PlayerFragment()
|
private var ready = 0
|
||||||
private val mainFragment = MainFragment()
|
private var playerFragment = PlayerFragment()
|
||||||
private val infoFragment = InfoFragment()
|
private var mainFragment = MainFragment()
|
||||||
private val channelFragment = ChannelFragment()
|
private var infoFragment = InfoFragment()
|
||||||
|
private var channelFragment = ChannelFragment()
|
||||||
private lateinit var settingFragment: SettingFragment
|
private lateinit var settingFragment: SettingFragment
|
||||||
|
|
||||||
private var doubleBackToExitPressedOnce = false
|
private var doubleBackToExitPressedOnce = false
|
||||||
@@ -41,8 +47,17 @@ class MainActivity : FragmentActivity() {
|
|||||||
lateinit var sharedPref: SharedPreferences
|
lateinit var sharedPref: SharedPreferences
|
||||||
private var channelReversal = false
|
private var channelReversal = false
|
||||||
private var channelNum = true
|
private var channelNum = true
|
||||||
|
private var bootStartup = true
|
||||||
|
|
||||||
private var versionName = ""
|
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")
|
||||||
@@ -67,9 +82,17 @@ class MainActivity : FragmentActivity() {
|
|||||||
sharedPref = getPreferences(Context.MODE_PRIVATE)
|
sharedPref = getPreferences(Context.MODE_PRIVATE)
|
||||||
channelReversal = sharedPref.getBoolean(CHANNEL_REVERSAL, channelReversal)
|
channelReversal = sharedPref.getBoolean(CHANNEL_REVERSAL, channelReversal)
|
||||||
channelNum = sharedPref.getBoolean(CHANNEL_NUM, channelNum)
|
channelNum = sharedPref.getBoolean(CHANNEL_NUM, channelNum)
|
||||||
|
bootStartup = sharedPref.getBoolean(BOOT_STARTUP, bootStartup)
|
||||||
|
|
||||||
versionName = getPackageInfo().versionName
|
val packageInfo = getPackageInfo()
|
||||||
settingFragment = SettingFragment(versionName, channelReversal, channelNum)
|
val versionName = packageInfo.versionName
|
||||||
|
val versionCode = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||||
|
packageInfo.longVersionCode
|
||||||
|
} else {
|
||||||
|
packageInfo.versionCode.toLong()
|
||||||
|
}
|
||||||
|
settingFragment =
|
||||||
|
SettingFragment(versionName, versionCode, channelReversal, channelNum, bootStartup)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun showInfoFragment(tvViewModel: TVViewModel) {
|
fun showInfoFragment(tvViewModel: TVViewModel) {
|
||||||
@@ -124,7 +147,7 @@ class MainActivity : FragmentActivity() {
|
|||||||
if (mainFragment.isHidden) {
|
if (mainFragment.isHidden) {
|
||||||
mainFragment.setPosition()
|
mainFragment.setPosition()
|
||||||
transaction.show(mainFragment)
|
transaction.show(mainFragment)
|
||||||
keepRunnable()
|
mainActive()
|
||||||
} else {
|
} else {
|
||||||
transaction.hide(mainFragment)
|
transaction.hide(mainFragment)
|
||||||
}
|
}
|
||||||
@@ -132,11 +155,16 @@ class MainActivity : FragmentActivity() {
|
|||||||
transaction.commit()
|
transaction.commit()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun keepRunnable() {
|
fun mainActive() {
|
||||||
handler.removeCallbacks(hideMain)
|
handler.removeCallbacks(hideMain)
|
||||||
handler.postDelayed(hideMain, delayHideMain)
|
handler.postDelayed(hideMain, delayHideMain)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun settingActive() {
|
||||||
|
handler.removeCallbacks(hideSetting)
|
||||||
|
handler.postDelayed(hideSetting, delayHideSetting)
|
||||||
|
}
|
||||||
|
|
||||||
private val hideMain = Runnable {
|
private val hideMain = Runnable {
|
||||||
if (!mainFragment.isHidden) {
|
if (!mainFragment.isHidden) {
|
||||||
supportFragmentManager.beginTransaction().hide(mainFragment).commit()
|
supportFragmentManager.beginTransaction().hide(mainFragment).commit()
|
||||||
@@ -156,7 +184,11 @@ class MainActivity : FragmentActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun fragmentReady() {
|
fun fragmentReady() {
|
||||||
mainFragment.fragmentReady()
|
ready++
|
||||||
|
Log.i(TAG, "ready $ready")
|
||||||
|
if (ready == 4) {
|
||||||
|
mainFragment.fragmentReady()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onTouchEvent(event: MotionEvent?): Boolean {
|
override fun onTouchEvent(event: MotionEvent?): Boolean {
|
||||||
@@ -223,6 +255,14 @@ class MainActivity : FragmentActivity() {
|
|||||||
this.channelNum = channelNum
|
this.channelNum = channelNum
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun saveBootStartup(bootStartup: Boolean) {
|
||||||
|
with(sharedPref.edit()) {
|
||||||
|
putBoolean(BOOT_STARTUP, bootStartup)
|
||||||
|
apply()
|
||||||
|
}
|
||||||
|
this.bootStartup = bootStartup
|
||||||
|
}
|
||||||
|
|
||||||
private fun showSetting() {
|
private fun showSetting() {
|
||||||
if (!mainFragment.isHidden) {
|
if (!mainFragment.isHidden) {
|
||||||
return
|
return
|
||||||
@@ -231,8 +271,7 @@ class MainActivity : FragmentActivity() {
|
|||||||
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")
|
||||||
handler.removeCallbacks(hideSetting)
|
settingActive()
|
||||||
handler.postDelayed(hideSetting, delayHideSetting)
|
|
||||||
} else {
|
} else {
|
||||||
handler.removeCallbacks(hideSetting)
|
handler.removeCallbacks(hideSetting)
|
||||||
settingFragment.dismiss()
|
settingFragment.dismiss()
|
||||||
@@ -297,7 +336,9 @@ class MainActivity : FragmentActivity() {
|
|||||||
}, 2000)
|
}, 2000)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
|
|
||||||
|
fun onKey(keyCode: Int): Boolean {
|
||||||
|
Log.i(TAG, "keyCode $keyCode")
|
||||||
when (keyCode) {
|
when (keyCode) {
|
||||||
KeyEvent.KEYCODE_0 -> {
|
KeyEvent.KEYCODE_0 -> {
|
||||||
showChannel("0")
|
showChannel("0")
|
||||||
@@ -409,6 +450,7 @@ class MainActivity : FragmentActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
KeyEvent.KEYCODE_DPAD_LEFT -> {
|
KeyEvent.KEYCODE_DPAD_LEFT -> {
|
||||||
|
channelUp()
|
||||||
// if (mainFragment.isHidden) {
|
// if (mainFragment.isHidden) {
|
||||||
// prevSource()
|
// prevSource()
|
||||||
// } else {
|
// } else {
|
||||||
@@ -422,6 +464,7 @@ class MainActivity : FragmentActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
KeyEvent.KEYCODE_DPAD_RIGHT -> {
|
KeyEvent.KEYCODE_DPAD_RIGHT -> {
|
||||||
|
channelDown()
|
||||||
// if (mainFragment.isHidden) {
|
// if (mainFragment.isHidden) {
|
||||||
// nextSource()
|
// nextSource()
|
||||||
// } else {
|
// } else {
|
||||||
@@ -433,6 +476,13 @@ class MainActivity : FragmentActivity() {
|
|||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
|
||||||
|
if (onKey(keyCode)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
return super.onKeyDown(keyCode, event)
|
return super.onKeyDown(keyCode, event)
|
||||||
}
|
}
|
||||||
@@ -496,7 +546,7 @@ class MainActivity : FragmentActivity() {
|
|||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
Log.i(TAG, "onResume")
|
Log.i(TAG, "onResume")
|
||||||
super.onResume()
|
super.onResume()
|
||||||
if (!mainFragment.isHidden){
|
if (!mainFragment.isHidden) {
|
||||||
handler.postDelayed(hideMain, delayHideMain)
|
handler.postDelayed(hideMain, delayHideMain)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -511,5 +561,6 @@ class MainActivity : FragmentActivity() {
|
|||||||
private const val TAG = "MainActivity"
|
private const val TAG = "MainActivity"
|
||||||
private const val CHANNEL_REVERSAL = "channel_reversal"
|
private const val CHANNEL_REVERSAL = "channel_reversal"
|
||||||
private const val CHANNEL_NUM = "channel_num"
|
private const val CHANNEL_NUM = "channel_num"
|
||||||
|
const val BOOT_STARTUP = "boot_startup"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,13 +5,14 @@ import android.os.Bundle
|
|||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import android.view.KeyEvent
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.lizongying.mytv.Utils.dpToPx
|
import com.lizongying.mytv.Utils.dpToPx
|
||||||
import com.lizongying.mytv.Utils.getDateTimestamp
|
import com.lizongying.mytv.Utils.getDateTimestamp
|
||||||
@@ -22,6 +23,7 @@ import com.lizongying.mytv.models.TVViewModel
|
|||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
|
||||||
class MainFragment : Fragment(), CardAdapter.ItemListener {
|
class MainFragment : Fragment(), CardAdapter.ItemListener {
|
||||||
|
|
||||||
private var itemPosition = 0
|
private var itemPosition = 0
|
||||||
@@ -42,8 +44,6 @@ class MainFragment : Fragment(), CardAdapter.ItemListener {
|
|||||||
private val handler = Handler(Looper.getMainLooper())
|
private val handler = Handler(Looper.getMainLooper())
|
||||||
private lateinit var mUpdateProgramRunnable: UpdateProgramRunnable
|
private lateinit var mUpdateProgramRunnable: UpdateProgramRunnable
|
||||||
|
|
||||||
private var ready = 0
|
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater, container: ViewGroup?,
|
inflater: LayoutInflater, container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
@@ -58,6 +58,8 @@ class MainFragment : Fragment(), CardAdapter.ItemListener {
|
|||||||
activity?.let { request.initYSP(it) }
|
activity?.let { request.initYSP(it) }
|
||||||
sharedPref = (activity as? MainActivity)?.sharedPref!!
|
sharedPref = (activity as? MainActivity)?.sharedPref!!
|
||||||
|
|
||||||
|
itemPosition = sharedPref.getInt(POSITION, 0)
|
||||||
|
|
||||||
view?.post {
|
view?.post {
|
||||||
val content = binding.content
|
val content = binding.content
|
||||||
|
|
||||||
@@ -76,15 +78,35 @@ class MainFragment : Fragment(), CardAdapter.ItemListener {
|
|||||||
}
|
}
|
||||||
tvListViewModel.maxNum.add(v.size)
|
tvListViewModel.maxNum.add(v.size)
|
||||||
|
|
||||||
val adapter = CardAdapter(viewLifecycleOwner, tvListViewModelCurrent)
|
val adapter =
|
||||||
|
CardAdapter(
|
||||||
|
itemBinding.rowItems,
|
||||||
|
viewLifecycleOwner,
|
||||||
|
tvListViewModelCurrent,
|
||||||
|
)
|
||||||
rowList.add(itemBinding.rowItems)
|
rowList.add(itemBinding.rowItems)
|
||||||
|
|
||||||
adapter.setItemListener(this)
|
adapter.setItemListener(this)
|
||||||
|
|
||||||
itemBinding.rowHeader.text = k
|
itemBinding.rowHeader.text = k
|
||||||
|
itemBinding.rowItems.tag = idx.toInt()
|
||||||
itemBinding.rowItems.adapter = adapter
|
itemBinding.rowItems.adapter = adapter
|
||||||
itemBinding.rowItems.layoutManager =
|
itemBinding.rowItems.layoutManager =
|
||||||
LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
|
GridLayoutManager(context, 6)
|
||||||
|
itemBinding.rowItems.layoutParams.height =
|
||||||
|
dpToPx(92 * ((tvListViewModelCurrent.size() + 6 - 1) / 6))
|
||||||
|
|
||||||
|
itemBinding.rowItems.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||||
|
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||||
|
super.onScrolled(recyclerView, dx, dy)
|
||||||
|
(activity as MainActivity).mainActive()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
val itemDecoration = context?.let { GrayOverlayItemDecoration(it) }
|
||||||
|
if (itemDecoration != null) {
|
||||||
|
itemBinding.rowItems.addItemDecoration(itemDecoration)
|
||||||
|
}
|
||||||
|
|
||||||
val layoutParams = itemBinding.row.layoutParams as ViewGroup.MarginLayoutParams
|
val layoutParams = itemBinding.row.layoutParams as ViewGroup.MarginLayoutParams
|
||||||
layoutParams.topMargin = dpToPx(11F)
|
layoutParams.topMargin = dpToPx(11F)
|
||||||
@@ -97,12 +119,19 @@ class MainFragment : Fragment(), CardAdapter.ItemListener {
|
|||||||
mUpdateProgramRunnable = UpdateProgramRunnable()
|
mUpdateProgramRunnable = UpdateProgramRunnable()
|
||||||
handler.post(mUpdateProgramRunnable)
|
handler.post(mUpdateProgramRunnable)
|
||||||
|
|
||||||
itemPosition = sharedPref.getInt(POSITION, 0)
|
|
||||||
if (itemPosition >= tvListViewModel.size()) {
|
if (itemPosition >= tvListViewModel.size()) {
|
||||||
itemPosition = 0
|
itemPosition = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val tv = tvListViewModel.getTVViewModel(itemPosition)
|
||||||
|
for (i in rowList) {
|
||||||
|
if (i.tag as Int == tv?.getRowPosition()) {
|
||||||
|
((i as RecyclerView).adapter as CardAdapter).defaultFocus = itemPosition
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
tvListViewModel.setItemPosition(itemPosition)
|
tvListViewModel.setItemPosition(itemPosition)
|
||||||
setPosition()
|
|
||||||
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
|
||||||
@@ -148,8 +177,7 @@ class MainFragment : Fragment(), CardAdapter.ItemListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
(activity as MainActivity).fragmentReady()
|
||||||
fragmentReady()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,14 +185,41 @@ class MainFragment : Fragment(), CardAdapter.ItemListener {
|
|||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onKey(keyCode: Int): Boolean {
|
||||||
|
if (this.isHidden) {
|
||||||
|
when (keyCode) {
|
||||||
|
KeyEvent.KEYCODE_DPAD_UP -> {
|
||||||
|
(activity as MainActivity).onKey(keyCode)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyEvent.KEYCODE_DPAD_DOWN -> {
|
||||||
|
(activity as MainActivity).onKey(keyCode)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
override fun onItemFocusChange(tvViewModel: TVViewModel, hasFocus: Boolean) {
|
override fun onItemFocusChange(tvViewModel: TVViewModel, hasFocus: Boolean) {
|
||||||
if (hasFocus) {
|
if (hasFocus) {
|
||||||
tvListViewModel.setItemPositionCurrent(tvViewModel.id.value!!)
|
tvListViewModel.setItemPositionCurrent(tvViewModel.id.value!!)
|
||||||
(activity as MainActivity).keepRunnable()
|
|
||||||
|
val row = tvViewModel.getRowPosition()
|
||||||
|
|
||||||
|
for (i in rowList) {
|
||||||
|
if (i.tag as Int != row) {
|
||||||
|
((i as RecyclerView).adapter as CardAdapter).clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(activity as MainActivity).mainActive()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onItemClicked(tvViewModel: TVViewModel) {
|
override fun onItemClicked(tvViewModel: TVViewModel) {
|
||||||
|
Log.i(TAG, "onItemClicked")
|
||||||
if (this.isHidden) {
|
if (this.isHidden) {
|
||||||
(activity as? MainActivity)?.switchMainFragment()
|
(activity as? MainActivity)?.switchMainFragment()
|
||||||
return
|
return
|
||||||
@@ -181,7 +236,7 @@ class MainFragment : Fragment(), CardAdapter.ItemListener {
|
|||||||
fun setPosition() {
|
fun setPosition() {
|
||||||
val tvViewModel = tvListViewModel.getTVViewModel(itemPosition)
|
val tvViewModel = tvListViewModel.getTVViewModel(itemPosition)
|
||||||
rowList[tvViewModel!!.getRowPosition()].post {
|
rowList[tvViewModel!!.getRowPosition()].post {
|
||||||
((rowList[tvViewModel.getRowPosition()] as RecyclerView).layoutManager as LinearLayoutManager).findViewByPosition(
|
((rowList[tvViewModel.getRowPosition()] as RecyclerView).layoutManager as GridLayoutManager).findViewByPosition(
|
||||||
tvViewModel.getItemPosition()
|
tvViewModel.getItemPosition()
|
||||||
)?.requestFocus()
|
)?.requestFocus()
|
||||||
}
|
}
|
||||||
@@ -189,10 +244,12 @@ class MainFragment : Fragment(), CardAdapter.ItemListener {
|
|||||||
|
|
||||||
fun setPosition(rowPosition: Int, itemPosition: Int) {
|
fun setPosition(rowPosition: Int, itemPosition: Int) {
|
||||||
rowList[rowPosition].post {
|
rowList[rowPosition].post {
|
||||||
((rowList[rowPosition] as RecyclerView).layoutManager as LinearLayoutManager).findViewByPosition(
|
((rowList[rowPosition] as RecyclerView).layoutManager as GridLayoutManager).findViewByPosition(
|
||||||
itemPosition
|
itemPosition
|
||||||
)?.requestFocus()
|
)?.requestFocus()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
((rowList[rowPosition] as RecyclerView).adapter as CardAdapter).defaultFocus = itemPosition
|
||||||
}
|
}
|
||||||
|
|
||||||
fun check(tvViewModel: TVViewModel): Boolean {
|
fun check(tvViewModel: TVViewModel): Boolean {
|
||||||
@@ -212,12 +269,8 @@ class MainFragment : Fragment(), CardAdapter.ItemListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun fragmentReady() {
|
fun fragmentReady() {
|
||||||
ready++
|
|
||||||
Log.i(TAG, "ready $ready")
|
|
||||||
if (ready == 4) {
|
|
||||||
// request.fetchPage()
|
// request.fetchPage()
|
||||||
tvListViewModel.getTVViewModel(itemPosition)?.changed()
|
tvListViewModel.getTVViewModel(itemPosition)?.changed()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun play(itemPosition: Int) {
|
fun play(itemPosition: Int) {
|
||||||
@@ -293,13 +346,15 @@ class MainFragment : Fragment(), CardAdapter.ItemListener {
|
|||||||
putInt(POSITION, itemPosition)
|
putInt(POSITION, itemPosition)
|
||||||
apply()
|
apply()
|
||||||
}
|
}
|
||||||
Log.i(TAG, "$POSITION saved")
|
Log.i(TAG, "$POSITION $itemPosition saved")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
Log.i(TAG, "onDestroy")
|
Log.i(TAG, "onDestroy")
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
handler.removeCallbacks(mUpdateProgramRunnable)
|
if (::mUpdateProgramRunnable.isInitialized) {
|
||||||
|
handler.removeCallbacks(mUpdateProgramRunnable)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package com.lizongying.mytv
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
|
import android.view.SurfaceHolder
|
||||||
|
import android.view.SurfaceView
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.ViewTreeObserver
|
import android.view.ViewTreeObserver
|
||||||
@@ -15,24 +17,39 @@ import androidx.media3.common.VideoSize
|
|||||||
import androidx.media3.common.util.UnstableApi
|
import androidx.media3.common.util.UnstableApi
|
||||||
import androidx.media3.exoplayer.ExoPlayer
|
import androidx.media3.exoplayer.ExoPlayer
|
||||||
import androidx.media3.ui.PlayerView
|
import androidx.media3.ui.PlayerView
|
||||||
|
import com.google.android.exoplayer2.SimpleExoPlayer
|
||||||
import com.lizongying.mytv.databinding.PlayerBinding
|
import com.lizongying.mytv.databinding.PlayerBinding
|
||||||
import com.lizongying.mytv.models.TVViewModel
|
import com.lizongying.mytv.models.TVViewModel
|
||||||
|
|
||||||
|
|
||||||
class PlayerFragment : Fragment() {
|
class PlayerFragment : Fragment(), SurfaceHolder.Callback {
|
||||||
|
|
||||||
private var _binding: PlayerBinding? = null
|
private var _binding: PlayerBinding? = null
|
||||||
private var playerView: PlayerView? = null
|
private var playerView: PlayerView? = null
|
||||||
private var tvViewModel: TVViewModel? = null
|
private var tvViewModel: TVViewModel? = null
|
||||||
private val aspectRatio = 16f / 9f
|
private val aspectRatio = 16f / 9f
|
||||||
|
|
||||||
|
|
||||||
|
private lateinit var surfaceView: SurfaceView
|
||||||
|
private lateinit var surfaceHolder: SurfaceHolder
|
||||||
|
private var exoPlayer: SimpleExoPlayer? = null
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater, container: ViewGroup?,
|
inflater: LayoutInflater, container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
): View {
|
): View {
|
||||||
_binding = PlayerBinding.inflate(inflater, container, false)
|
_binding = PlayerBinding.inflate(inflater, container, false)
|
||||||
playerView = _binding!!.playerView
|
|
||||||
(activity as MainActivity).playerFragment = this
|
if (Utils.isTmallDevice()) {
|
||||||
|
_binding!!.playerView.visibility = View.GONE
|
||||||
|
surfaceView = _binding!!.surfaceView
|
||||||
|
surfaceHolder = surfaceView.holder
|
||||||
|
surfaceHolder.addCallback(this)
|
||||||
|
} else {
|
||||||
|
_binding!!.surfaceView.visibility = View.GONE
|
||||||
|
playerView = _binding!!.playerView
|
||||||
|
}
|
||||||
|
|
||||||
playerView?.viewTreeObserver?.addOnGlobalLayoutListener(object :
|
playerView?.viewTreeObserver?.addOnGlobalLayoutListener(object :
|
||||||
ViewTreeObserver.OnGlobalLayoutListener {
|
ViewTreeObserver.OnGlobalLayoutListener {
|
||||||
override fun onGlobalLayout() {
|
override fun onGlobalLayout() {
|
||||||
@@ -63,6 +80,7 @@ class PlayerFragment : Fragment() {
|
|||||||
super.onPlayerError(error)
|
super.onPlayerError(error)
|
||||||
|
|
||||||
Log.e(TAG, "PlaybackException $error")
|
Log.e(TAG, "PlaybackException $error")
|
||||||
|
tvViewModel?.changed()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -78,6 +96,10 @@ class PlayerFragment : Fragment() {
|
|||||||
setMediaItem(MediaItem.fromUri(tvViewModel.getVideoUrlCurrent()))
|
setMediaItem(MediaItem.fromUri(tvViewModel.getVideoUrlCurrent()))
|
||||||
prepare()
|
prepare()
|
||||||
}
|
}
|
||||||
|
exoPlayer?.run {
|
||||||
|
setMediaItem(com.google.android.exoplayer2.MediaItem.fromUri(tvViewModel.getVideoUrlCurrent()))
|
||||||
|
prepare()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStart() {
|
override fun onStart() {
|
||||||
@@ -86,8 +108,12 @@ class PlayerFragment : Fragment() {
|
|||||||
if (playerView != null && playerView!!.player?.isPlaying == false) {
|
if (playerView != null && playerView!!.player?.isPlaying == false) {
|
||||||
Log.i(TAG, "replay")
|
Log.i(TAG, "replay")
|
||||||
playerView!!.player?.prepare()
|
playerView!!.player?.prepare()
|
||||||
} else {
|
playerView!!.player?.play()
|
||||||
Log.i(TAG, "playing")
|
}
|
||||||
|
if (exoPlayer?.isPlaying == false) {
|
||||||
|
Log.i(TAG, "replay")
|
||||||
|
exoPlayer?.prepare()
|
||||||
|
exoPlayer?.play()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,6 +127,9 @@ class PlayerFragment : Fragment() {
|
|||||||
if (playerView != null && playerView!!.player?.isPlaying == true) {
|
if (playerView != null && playerView!!.player?.isPlaying == true) {
|
||||||
playerView!!.player?.stop()
|
playerView!!.player?.stop()
|
||||||
}
|
}
|
||||||
|
if (exoPlayer?.isPlaying == true) {
|
||||||
|
exoPlayer?.stop()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
@@ -108,6 +137,7 @@ class PlayerFragment : Fragment() {
|
|||||||
if (playerView != null) {
|
if (playerView != null) {
|
||||||
playerView!!.player?.release()
|
playerView!!.player?.release()
|
||||||
}
|
}
|
||||||
|
exoPlayer?.release()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
@@ -118,4 +148,16 @@ class PlayerFragment : Fragment() {
|
|||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "PlaybackVideoFragment"
|
private const val TAG = "PlaybackVideoFragment"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun surfaceCreated(holder: SurfaceHolder) {
|
||||||
|
exoPlayer = SimpleExoPlayer.Builder(requireContext()).build()
|
||||||
|
exoPlayer?.setVideoSurfaceHolder(surfaceHolder)
|
||||||
|
exoPlayer?.playWhenReady = true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun surfaceDestroyed(holder: SurfaceHolder) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -7,6 +7,8 @@ import android.util.Base64
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.lizongying.mytv.Utils.getDateFormat
|
import com.lizongying.mytv.Utils.getDateFormat
|
||||||
import com.lizongying.mytv.api.ApiClient
|
import com.lizongying.mytv.api.ApiClient
|
||||||
|
import com.lizongying.mytv.api.Auth
|
||||||
|
import com.lizongying.mytv.api.AuthRequest
|
||||||
import com.lizongying.mytv.api.Info
|
import com.lizongying.mytv.api.Info
|
||||||
import com.lizongying.mytv.api.LiveInfo
|
import com.lizongying.mytv.api.LiveInfo
|
||||||
import com.lizongying.mytv.api.LiveInfoRequest
|
import com.lizongying.mytv.api.LiveInfoRequest
|
||||||
@@ -34,6 +36,8 @@ class Request {
|
|||||||
private var ysp: YSP? = null
|
private var ysp: YSP? = null
|
||||||
private var token = ""
|
private var token = ""
|
||||||
|
|
||||||
|
private var needAuth = false
|
||||||
|
|
||||||
// TODO onDestroy
|
// TODO onDestroy
|
||||||
private val handler = Handler(Looper.getMainLooper())
|
private val handler = Handler(Looper.getMainLooper())
|
||||||
private lateinit var btraceRunnable: BtraceRunnable
|
private lateinit var btraceRunnable: BtraceRunnable
|
||||||
@@ -43,54 +47,6 @@ class Request {
|
|||||||
private val input =
|
private val input =
|
||||||
"""{"mver":"1","subver":"1.2","host":"www.yangshipin.cn/#/tv/home?pid=","referer":"","canvas":"YSPANGLE(Apple,ANGLEMetalRenderer:AppleM1Pro,UnspecifiedVersion)"}""".toByteArray()
|
"""{"mver":"1","subver":"1.2","host":"www.yangshipin.cn/#/tv/home?pid=","referer":"","canvas":"YSPANGLE(Apple,ANGLEMetalRenderer:AppleM1Pro,UnspecifiedVersion)"}""".toByteArray()
|
||||||
|
|
||||||
private var mapping = mapOf(
|
|
||||||
"CCTV4K" to "CCTV4K 超高清",
|
|
||||||
"CCTV1" to "CCTV1 综合",
|
|
||||||
"CCTV2" to "CCTV2 财经",
|
|
||||||
"CCTV4" to "CCTV4 中文国际",
|
|
||||||
"CCTV5" to "CCTV5 体育",
|
|
||||||
"CCTV5+" to "CCTV5+ 体育赛事",
|
|
||||||
"CCTV7" to "CCTV7 国防军事",
|
|
||||||
"CCTV9" to "CCTV9 记录",
|
|
||||||
"CCTV10" to "CCTV10 科教",
|
|
||||||
"CCTV11" to "CCTV11 戏曲",
|
|
||||||
"CCTV12" to "CCTV12 社会与法",
|
|
||||||
"CCTV13" to "CCTV13 新闻",
|
|
||||||
"CCTV14" to "CCTV14 少儿",
|
|
||||||
"CCTV15" to "CCTV15 音乐",
|
|
||||||
"CCTV16-HD" to "CCTV16 奥林匹克",
|
|
||||||
"CCTV17" to "CCTV17 农业农村",
|
|
||||||
"CGTN" to "CGTN",
|
|
||||||
"CGTN法语频道" to "CGTN 法语频道",
|
|
||||||
"CGTN俄语频道" to "CGTN 俄语频道",
|
|
||||||
"CGTN阿拉伯语频道" to "CGTN 阿拉伯语频道",
|
|
||||||
"CGTN西班牙语频道" to "CGTN 西班牙语频道",
|
|
||||||
"CGTN外语纪录频道" to "CGTN 纪录频道",
|
|
||||||
"东方卫视" to "东方卫视",
|
|
||||||
"湖南卫视" to "湖南卫视",
|
|
||||||
"湖北卫视" to "湖北卫视",
|
|
||||||
"辽宁卫视" to "辽宁卫视",
|
|
||||||
"江苏卫视" to "江苏卫视",
|
|
||||||
"江西卫视" to "江西卫视",
|
|
||||||
"山东卫视" to "山东卫视",
|
|
||||||
"广东卫视" to "广东卫视",
|
|
||||||
"广西卫视" to "广西卫视",
|
|
||||||
"重庆卫视" to "重庆卫视",
|
|
||||||
"河南卫视" to "河南卫视",
|
|
||||||
"河北卫视" to "河北卫视",
|
|
||||||
"贵州卫视" to "贵州卫视",
|
|
||||||
"北京卫视" to "北京卫视",
|
|
||||||
"黑龙江卫视" to "黑龙江卫视",
|
|
||||||
"浙江卫视" to "浙江卫视",
|
|
||||||
"安徽卫视" to "安徽卫视",
|
|
||||||
"深圳卫视" to "深圳卫视",
|
|
||||||
"四川卫视" to "四川卫视",
|
|
||||||
"福建东南卫视" to "东南卫视",
|
|
||||||
"海南卫视" to "海南卫视",
|
|
||||||
"天津卫视" to "天津卫视",
|
|
||||||
"新疆卫视" to "新疆卫视",
|
|
||||||
)
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
handler.post(tokenRunnable)
|
handler.post(tokenRunnable)
|
||||||
}
|
}
|
||||||
@@ -100,6 +56,84 @@ class Request {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var call: Call<LiveInfo>? = null
|
var call: Call<LiveInfo>? = null
|
||||||
|
private var callAuth: Call<Auth>? = null
|
||||||
|
|
||||||
|
private fun fetchAuth(tvModel: TVViewModel, cookie: String) {
|
||||||
|
callAuth?.cancel()
|
||||||
|
|
||||||
|
val title = tvModel.title.value
|
||||||
|
|
||||||
|
val data = ysp?.getAuthData(tvModel)
|
||||||
|
val request = data?.let { AuthRequest(it) }
|
||||||
|
callAuth = request?.let { yspApiService.getAuth("guid=${ysp?.getGuid()}; $cookie", it) }
|
||||||
|
|
||||||
|
callAuth?.enqueue(object : Callback<Auth> {
|
||||||
|
override fun onResponse(call: Call<Auth>, response: Response<Auth>) {
|
||||||
|
if (response.isSuccessful) {
|
||||||
|
val liveInfo = response.body()
|
||||||
|
|
||||||
|
if (liveInfo?.data?.token != null) {
|
||||||
|
Log.i(TAG, "token ${liveInfo.data.token}")
|
||||||
|
ysp?.token = liveInfo.data.token
|
||||||
|
fetchVideo(tvModel, cookie)
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "$title token error")
|
||||||
|
if (tvModel.retryTimes < tvModel.retryMaxTimes) {
|
||||||
|
tvModel.retryTimes++
|
||||||
|
if (tvModel.getTV().needToken) {
|
||||||
|
if (tvModel.tokenRetryTimes == tvModel.tokenRetryMaxTimes) {
|
||||||
|
if (!tvModel.getTV().mustToken) {
|
||||||
|
fetchAuth(tvModel, cookie)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
token = ""
|
||||||
|
fetchAuth(tvModel)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fetchAuth(tvModel, cookie)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "$title auth status error")
|
||||||
|
if (tvModel.retryTimes < tvModel.retryMaxTimes) {
|
||||||
|
tvModel.retryTimes++
|
||||||
|
if (tvModel.getTV().needToken) {
|
||||||
|
if (tvModel.tokenRetryTimes == tvModel.tokenRetryMaxTimes) {
|
||||||
|
if (!tvModel.getTV().mustToken) {
|
||||||
|
fetchAuth(tvModel, cookie)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
token = ""
|
||||||
|
fetchAuth(tvModel)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fetchAuth(tvModel, cookie)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(call: Call<Auth>, t: Throwable) {
|
||||||
|
Log.e(TAG, "$title auth request error $t")
|
||||||
|
if (tvModel.retryTimes < tvModel.retryMaxTimes) {
|
||||||
|
tvModel.retryTimes++
|
||||||
|
if (tvModel.getTV().needToken) {
|
||||||
|
if (tvModel.tokenRetryTimes == tvModel.tokenRetryMaxTimes) {
|
||||||
|
if (!tvModel.getTV().mustToken) {
|
||||||
|
fetchAuth(tvModel, cookie)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
token = ""
|
||||||
|
fetchAuth(tvModel)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fetchAuth(tvModel, cookie)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fun fetchVideo(tvModel: TVViewModel, cookie: String) {
|
fun fetchVideo(tvModel: TVViewModel, cookie: String) {
|
||||||
call?.cancel()
|
call?.cancel()
|
||||||
@@ -112,7 +146,13 @@ class Request {
|
|||||||
tvModel.seq = 0
|
tvModel.seq = 0
|
||||||
val data = ysp?.switch(tvModel)
|
val data = ysp?.switch(tvModel)
|
||||||
val request = data?.let { LiveInfoRequest(it) }
|
val request = data?.let { LiveInfoRequest(it) }
|
||||||
call = request?.let { yspApiService.getLiveInfo("guid=${ysp?.getGuid()}; $cookie", it) }
|
call = request?.let {
|
||||||
|
yspApiService.getLiveInfo(
|
||||||
|
"guid=${ysp?.getGuid()}; $cookie",
|
||||||
|
ysp!!.token,
|
||||||
|
it
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
call?.enqueue(object : Callback<LiveInfo> {
|
call?.enqueue(object : Callback<LiveInfo> {
|
||||||
override fun onResponse(call: Call<LiveInfo>, response: Response<LiveInfo>) {
|
override fun onResponse(call: Call<LiveInfo>, response: Response<LiveInfo>) {
|
||||||
@@ -149,13 +189,16 @@ class Request {
|
|||||||
if (tvModel.tokenRetryTimes == tvModel.tokenRetryMaxTimes) {
|
if (tvModel.tokenRetryTimes == tvModel.tokenRetryMaxTimes) {
|
||||||
if (!tvModel.getTV().mustToken) {
|
if (!tvModel.getTV().mustToken) {
|
||||||
fetchVideo(tvModel, cookie)
|
fetchVideo(tvModel, cookie)
|
||||||
|
// fetchAuth(tvModel, cookie)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
token = ""
|
token = ""
|
||||||
fetchVideo(tvModel)
|
fetchVideo(tvModel)
|
||||||
|
// fetchAuth(tvModel)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fetchVideo(tvModel, cookie)
|
fetchVideo(tvModel, cookie)
|
||||||
|
// fetchAuth(tvModel, cookie)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -171,13 +214,16 @@ class Request {
|
|||||||
if (tvModel.tokenRetryTimes == tvModel.tokenRetryMaxTimes) {
|
if (tvModel.tokenRetryTimes == tvModel.tokenRetryMaxTimes) {
|
||||||
if (!tvModel.getTV().mustToken) {
|
if (!tvModel.getTV().mustToken) {
|
||||||
fetchVideo(tvModel, cookie)
|
fetchVideo(tvModel, cookie)
|
||||||
|
// fetchAuth(tvModel, cookie)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
token = ""
|
token = ""
|
||||||
fetchVideo(tvModel)
|
fetchVideo(tvModel)
|
||||||
|
// fetchAuth(tvModel)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fetchVideo(tvModel, cookie)
|
fetchVideo(tvModel, cookie)
|
||||||
|
// fetchAuth(tvModel, cookie)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -190,20 +236,23 @@ class Request {
|
|||||||
if (tvModel.tokenRetryTimes == tvModel.tokenRetryMaxTimes) {
|
if (tvModel.tokenRetryTimes == tvModel.tokenRetryMaxTimes) {
|
||||||
if (!tvModel.getTV().mustToken) {
|
if (!tvModel.getTV().mustToken) {
|
||||||
fetchVideo(tvModel, cookie)
|
fetchVideo(tvModel, cookie)
|
||||||
|
// fetchAuth(tvModel, cookie)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
token = ""
|
token = ""
|
||||||
fetchVideo(tvModel)
|
fetchVideo(tvModel)
|
||||||
|
// fetchAuth(tvModel)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fetchVideo(tvModel, cookie)
|
fetchVideo(tvModel, cookie)
|
||||||
|
// fetchAuth(tvModel, cookie)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onFailure(call: Call<LiveInfo>, t: Throwable) {
|
override fun onFailure(call: Call<LiveInfo>, t: Throwable) {
|
||||||
Log.e(TAG, "$title request error $t")
|
Log.e(TAG, "$title fetchVideo request error $t")
|
||||||
if (tvModel.retryTimes < tvModel.retryMaxTimes) {
|
if (tvModel.retryTimes < tvModel.retryMaxTimes) {
|
||||||
tvModel.retryTimes++
|
tvModel.retryTimes++
|
||||||
if (tvModel.getTV().needToken) {
|
if (tvModel.getTV().needToken) {
|
||||||
@@ -223,16 +272,63 @@ class Request {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fun fetchVideo(tvModel: TVViewModel) {
|
fun fetchAuth(tvModel: TVViewModel) {
|
||||||
if (token == "") {
|
if (token == "") {
|
||||||
yspTokenService.getInfo()
|
yspTokenService.getInfo("")
|
||||||
.enqueue(object : Callback<Info> {
|
.enqueue(object : Callback<Info> {
|
||||||
override fun onResponse(call: Call<Info>, response: Response<Info>) {
|
override fun onResponse(call: Call<Info>, response: Response<Info>) {
|
||||||
if (response.isSuccessful) {
|
if (response.isSuccessful) {
|
||||||
token = response.body()?.data?.token!!
|
token = response.body()?.data?.token!!
|
||||||
Log.i(TAG, "info success $token")
|
Log.i(TAG, "info success $token")
|
||||||
val cookie =
|
val cookie =
|
||||||
"versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; deviceModel=120; 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=vu0-8lgGV2LW9QjDeuBFsX8yMnzs37Q3_HZF6XyVDpGR_I; vusession=$token"
|
||||||
|
fetchAuth(tvModel, cookie)
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "info status error")
|
||||||
|
if (tvModel.tokenRetryTimes < tvModel.tokenRetryMaxTimes) {
|
||||||
|
tvModel.tokenRetryTimes++
|
||||||
|
fetchAuth(tvModel)
|
||||||
|
} else {
|
||||||
|
if (!tvModel.getTV().mustToken) {
|
||||||
|
val cookie =
|
||||||
|
"versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; deviceModel=120; appid=1400421205; yspappid=519748109"
|
||||||
|
fetchAuth(tvModel, cookie)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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; appid=1400421205; yspappid=519748109"
|
||||||
|
fetchAuth(tvModel, cookie)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
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"
|
||||||
|
fetchAuth(tvModel, cookie)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun fetchVideo(tvModel: TVViewModel) {
|
||||||
|
if (token == "") {
|
||||||
|
yspTokenService.getInfo("")
|
||||||
|
.enqueue(object : Callback<Info> {
|
||||||
|
override fun onResponse(call: Call<Info>, response: Response<Info>) {
|
||||||
|
if (response.isSuccessful && response.body()?.data?.token != null) {
|
||||||
|
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; appid=1400421205; yspappid=519748109;yspopenid=vu0-8lgGV2LW9QjDeuBFsX8yMnzs37Q3_HZF6XyVDpGR_I; vusession=$token"
|
||||||
fetchVideo(tvModel, cookie)
|
fetchVideo(tvModel, cookie)
|
||||||
} else {
|
} else {
|
||||||
Log.e(TAG, "info status error")
|
Log.e(TAG, "info status error")
|
||||||
@@ -242,7 +338,7 @@ class Request {
|
|||||||
} else {
|
} else {
|
||||||
if (!tvModel.getTV().mustToken) {
|
if (!tvModel.getTV().mustToken) {
|
||||||
val cookie =
|
val cookie =
|
||||||
"versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; deviceModel=120; yspappid=519748109"
|
"versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; deviceModel=120; appid=1400421205; yspappid=519748109"
|
||||||
fetchVideo(tvModel, cookie)
|
fetchVideo(tvModel, cookie)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -257,7 +353,7 @@ class Request {
|
|||||||
} else {
|
} else {
|
||||||
if (!tvModel.getTV().mustToken) {
|
if (!tvModel.getTV().mustToken) {
|
||||||
val cookie =
|
val cookie =
|
||||||
"versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; deviceModel=120; yspappid=519748109"
|
"versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; deviceModel=120; appid=1400421205; yspappid=519748109"
|
||||||
fetchVideo(tvModel, cookie)
|
fetchVideo(tvModel, cookie)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -265,18 +361,26 @@ class Request {
|
|||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
val cookie =
|
val cookie =
|
||||||
"versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; deviceModel=120; 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=vu0-8lgGV2LW9QjDeuBFsX8yMnzs37Q3_HZF6XyVDpGR_I; vusession=$token"
|
||||||
fetchVideo(tvModel, cookie)
|
fetchVideo(tvModel, cookie)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun fetchData(tvModel: TVViewModel) {
|
fun fetchData(tvModel: TVViewModel) {
|
||||||
if (tvModel.getTV().needToken) {
|
if (tvModel.getTV().needToken) {
|
||||||
fetchVideo(tvModel)
|
if (needAuth) {
|
||||||
|
fetchAuth(tvModel)
|
||||||
|
} else {
|
||||||
|
fetchVideo(tvModel)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
val cookie =
|
val cookie =
|
||||||
"versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; deviceModel=120; yspappid=519748109"
|
"versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; deviceModel=120; appid=1400421205"
|
||||||
fetchVideo(tvModel, cookie)
|
if (needAuth) {
|
||||||
|
fetchAuth(tvModel, cookie)
|
||||||
|
} else {
|
||||||
|
fetchVideo(tvModel, cookie)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -288,7 +392,7 @@ class Request {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun fetchToken() {
|
fun fetchToken() {
|
||||||
yspTokenService.getInfo()
|
yspTokenService.getInfo(token)
|
||||||
.enqueue(object : Callback<Info> {
|
.enqueue(object : Callback<Info> {
|
||||||
override fun onResponse(call: Call<Info>, response: Response<Info>) {
|
override fun onResponse(call: Call<Info>, response: Response<Info>) {
|
||||||
if (response.isSuccessful) {
|
if (response.isSuccessful) {
|
||||||
@@ -364,7 +468,6 @@ class Request {
|
|||||||
|
|
||||||
for ((_, v) in TVList.list) {
|
for ((_, v) in TVList.list) {
|
||||||
for (v2 in v) {
|
for (v2 in v) {
|
||||||
Log.i(TAG, "$v2")
|
|
||||||
if (v2.title == item.channelName || v2.alias == item.channelName) {
|
if (v2.title == item.channelName || v2.alias == item.channelName) {
|
||||||
v2.pid = item.pid
|
v2.pid = item.pid
|
||||||
v2.sid = item.streamId
|
v2.sid = item.streamId
|
||||||
|
|||||||
@@ -8,15 +8,20 @@ import androidx.fragment.app.DialogFragment
|
|||||||
import com.lizongying.mytv.databinding.DialogBinding
|
import com.lizongying.mytv.databinding.DialogBinding
|
||||||
|
|
||||||
|
|
||||||
class SettingFragment(private val versionName: String,
|
class SettingFragment(
|
||||||
private val channelReversal: Boolean,
|
private val versionName: String,
|
||||||
private val channelNum: Boolean,
|
private val versionCode: Long,
|
||||||
) :
|
private val channelReversal: Boolean,
|
||||||
|
private val channelNum: Boolean,
|
||||||
|
private val bootStartup: Boolean,
|
||||||
|
) :
|
||||||
DialogFragment() {
|
DialogFragment() {
|
||||||
|
|
||||||
private var _binding: DialogBinding? = null
|
private var _binding: DialogBinding? = null
|
||||||
private val binding get() = _binding!!
|
private val binding get() = _binding!!
|
||||||
|
|
||||||
|
private lateinit var updateManager: UpdateManager
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setStyle(STYLE_NO_TITLE, 0)
|
setStyle(STYLE_NO_TITLE, 0)
|
||||||
@@ -35,22 +40,50 @@ class SettingFragment(private val versionName: String,
|
|||||||
switchChannelReversal?.isChecked = channelReversal
|
switchChannelReversal?.isChecked = channelReversal
|
||||||
switchChannelReversal?.setOnCheckedChangeListener { _, isChecked ->
|
switchChannelReversal?.setOnCheckedChangeListener { _, isChecked ->
|
||||||
(activity as MainActivity).saveChannelReversal(isChecked)
|
(activity as MainActivity).saveChannelReversal(isChecked)
|
||||||
|
(activity as MainActivity).settingActive()
|
||||||
}
|
}
|
||||||
|
|
||||||
val switchChannelNum = _binding?.switchChannelNum
|
val switchChannelNum = _binding?.switchChannelNum
|
||||||
switchChannelNum?.isChecked = channelNum
|
switchChannelNum?.isChecked = channelNum
|
||||||
switchChannelNum?.setOnCheckedChangeListener { _, isChecked ->
|
switchChannelNum?.setOnCheckedChangeListener { _, isChecked ->
|
||||||
(activity as MainActivity).saveChannelNum(isChecked)
|
(activity as MainActivity).saveChannelNum(isChecked)
|
||||||
|
(activity as MainActivity).settingActive()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val switchBootStartup = _binding?.switchBootStartup
|
||||||
|
switchBootStartup?.isChecked = bootStartup
|
||||||
|
switchBootStartup?.setOnCheckedChangeListener { _, isChecked ->
|
||||||
|
(activity as MainActivity).saveBootStartup(isChecked)
|
||||||
|
(activity as MainActivity).settingActive()
|
||||||
|
}
|
||||||
|
|
||||||
|
updateManager = UpdateManager(context, this, versionCode)
|
||||||
|
_binding?.checkVersion?.setOnClickListener(OnClickListenerCheckVersion(updateManager))
|
||||||
|
|
||||||
return binding.root
|
return binding.root
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setVersionName(versionName: String) {
|
||||||
|
binding.versionName.text = versionName
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class OnClickListenerCheckVersion(private val updateManager: UpdateManager) :
|
||||||
|
View.OnClickListener {
|
||||||
|
override fun onClick(view: View?) {
|
||||||
|
updateManager.checkAndUpdate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
_binding = null
|
_binding = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
super.onDestroy()
|
||||||
|
// updateManager.destroy()
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val TAG = "SettingFragment"
|
const val TAG = "SettingFragment"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,8 +84,8 @@ object TVList {
|
|||||||
0,
|
0,
|
||||||
"央视频道",
|
"央视频道",
|
||||||
"https://resources.yangshipin.cn/assets/oms/image/202306/741515efda91f03f455df8a7da4ee11fa9329139c276435cf0a9e2af398d5bf2.png?imageMogr2/format/webp",
|
"https://resources.yangshipin.cn/assets/oms/image/202306/741515efda91f03f455df8a7da4ee11fa9329139c276435cf0a9e2af398d5bf2.png?imageMogr2/format/webp",
|
||||||
"600001802",
|
"600108442",
|
||||||
"2022574301",
|
"2013693901",
|
||||||
"600001802",
|
"600001802",
|
||||||
true,
|
true,
|
||||||
mustToken = true
|
mustToken = true
|
||||||
@@ -852,10 +852,24 @@ object TVList {
|
|||||||
true,
|
true,
|
||||||
mustToken = false
|
mustToken = false
|
||||||
),
|
),
|
||||||
|
TV(
|
||||||
|
56,
|
||||||
|
"兵团卫视",
|
||||||
|
"兵团卫视",
|
||||||
|
listOf(),
|
||||||
|
0,
|
||||||
|
"地方频道",
|
||||||
|
"",
|
||||||
|
"600170344",
|
||||||
|
"2022606701",
|
||||||
|
"",
|
||||||
|
true,
|
||||||
|
mustToken = false
|
||||||
|
),
|
||||||
),
|
),
|
||||||
"国际频道" to listOf(
|
"国际频道" to listOf(
|
||||||
TV(
|
TV(
|
||||||
56,
|
57,
|
||||||
"CGTN",
|
"CGTN",
|
||||||
"CGTN",
|
"CGTN",
|
||||||
listOf("http://live.cgtn.com/1000/prog_index.m3u8"),
|
listOf("http://live.cgtn.com/1000/prog_index.m3u8"),
|
||||||
@@ -869,7 +883,7 @@ object TVList {
|
|||||||
mustToken = false
|
mustToken = false
|
||||||
),
|
),
|
||||||
TV(
|
TV(
|
||||||
57,
|
58,
|
||||||
"CGTN 法语频道",
|
"CGTN 法语频道",
|
||||||
"CGTN法语频道",
|
"CGTN法语频道",
|
||||||
listOf("https://livefr.cgtn.com/1000f/prog_index.m3u8"),
|
listOf("https://livefr.cgtn.com/1000f/prog_index.m3u8"),
|
||||||
@@ -883,7 +897,7 @@ object TVList {
|
|||||||
mustToken = false
|
mustToken = false
|
||||||
),
|
),
|
||||||
TV(
|
TV(
|
||||||
58,
|
59,
|
||||||
"CGTN 俄语频道",
|
"CGTN 俄语频道",
|
||||||
"CGTN俄语频道",
|
"CGTN俄语频道",
|
||||||
listOf("http://liveru.cgtn.com/1000r/prog_index.m3u8"),
|
listOf("http://liveru.cgtn.com/1000r/prog_index.m3u8"),
|
||||||
@@ -897,7 +911,7 @@ object TVList {
|
|||||||
mustToken = false
|
mustToken = false
|
||||||
),
|
),
|
||||||
TV(
|
TV(
|
||||||
59,
|
60,
|
||||||
"CGTN 阿拉伯语频道",
|
"CGTN 阿拉伯语频道",
|
||||||
"CGTN阿拉伯语频道",
|
"CGTN阿拉伯语频道",
|
||||||
listOf("http://livear.cgtn.com/1000a/prog_index.m3u8"),
|
listOf("http://livear.cgtn.com/1000a/prog_index.m3u8"),
|
||||||
@@ -911,7 +925,7 @@ object TVList {
|
|||||||
mustToken = false
|
mustToken = false
|
||||||
),
|
),
|
||||||
TV(
|
TV(
|
||||||
60,
|
61,
|
||||||
"CGTN 西班牙语频道",
|
"CGTN 西班牙语频道",
|
||||||
"CGTN西班牙语频道",
|
"CGTN西班牙语频道",
|
||||||
listOf(
|
listOf(
|
||||||
@@ -928,7 +942,7 @@ object TVList {
|
|||||||
mustToken = false
|
mustToken = false
|
||||||
),
|
),
|
||||||
TV(
|
TV(
|
||||||
61,
|
62,
|
||||||
"CGTN 纪录频道",
|
"CGTN 纪录频道",
|
||||||
"CGTN外语纪录频道",
|
"CGTN外语纪录频道",
|
||||||
listOf("https://livedoc.cgtn.com/500d/prog_index.m3u8"),
|
listOf("https://livedoc.cgtn.com/500d/prog_index.m3u8"),
|
||||||
|
|||||||
@@ -0,0 +1,189 @@
|
|||||||
|
package com.lizongying.mytv
|
||||||
|
|
||||||
|
import android.app.DownloadManager
|
||||||
|
import android.app.DownloadManager.Request
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.IntentFilter
|
||||||
|
import android.database.Cursor
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Environment
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
|
import android.util.Log
|
||||||
|
import android.widget.Toast
|
||||||
|
import com.lizongying.mytv.api.Release
|
||||||
|
import com.lizongying.mytv.requests.MyRequest
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
|
||||||
|
class UpdateManager(
|
||||||
|
private var context: Context?,
|
||||||
|
private var settingFragment: SettingFragment,
|
||||||
|
private var versionCode: Long
|
||||||
|
) :
|
||||||
|
ConfirmationDialogFragment.ConfirmationDialogListener {
|
||||||
|
|
||||||
|
private var myRequest = MyRequest()
|
||||||
|
private var release: Release? = null
|
||||||
|
|
||||||
|
private var downloadReceiver: DownloadReceiver? = null
|
||||||
|
|
||||||
|
fun checkAndUpdate() {
|
||||||
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
|
try {
|
||||||
|
release = myRequest.getRelease()
|
||||||
|
updateUI(release)
|
||||||
|
Log.i(TAG, "versionCode $versionCode ${release?.data?.versionCode}")
|
||||||
|
if (release != null) {
|
||||||
|
if (release?.data?.versionCode!! >= versionCode) {
|
||||||
|
val dialog = ConfirmationDialogFragment(this@UpdateManager)
|
||||||
|
dialog.show(settingFragment.fragmentManager, "ConfirmationDialogFragment")
|
||||||
|
} else {
|
||||||
|
Toast.makeText(context, "不需要更新", Toast.LENGTH_LONG)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Error occurred: ${e.message}", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateUI(release: Release?) {
|
||||||
|
if (release?.data?.versionName.isNullOrEmpty()) {
|
||||||
|
settingFragment.setVersionName("版本获取失败")
|
||||||
|
} else {
|
||||||
|
settingFragment.setVersionName("最新版本:${release?.data?.versionName!!}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun startDownload(release: Release) {
|
||||||
|
val apkFileName = "my-tv-${release.data.versionName}.apk"
|
||||||
|
Log.i(TAG, "apkFileName $apkFileName")
|
||||||
|
val downloadManager =
|
||||||
|
context!!.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
||||||
|
val request = Request(Uri.parse(release.data.downloadUrl))
|
||||||
|
Log.i(TAG, "url ${Uri.parse(release.data.downloadUrl)}")
|
||||||
|
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, apkFileName)
|
||||||
|
request.setTitle("New Version Download")
|
||||||
|
request.setNotificationVisibility(Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
|
||||||
|
|
||||||
|
// 获取下载任务的引用
|
||||||
|
val downloadReference = downloadManager.enqueue(request)
|
||||||
|
|
||||||
|
downloadReceiver = DownloadReceiver(context!!, apkFileName, downloadReference)
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
context!!.registerReceiver(
|
||||||
|
downloadReceiver,
|
||||||
|
IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE),
|
||||||
|
Context.RECEIVER_NOT_EXPORTED,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
context!!.registerReceiver(
|
||||||
|
downloadReceiver,
|
||||||
|
IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
getDownloadProgress(context!!, downloadReference) { progress ->
|
||||||
|
println("Download progress: $progress%")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getDownloadProgress(context: Context, downloadId: Long, progressListener: (Int) -> Unit) {
|
||||||
|
val downloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
||||||
|
val handler = Handler(Looper.getMainLooper())
|
||||||
|
val intervalMillis: Long = 1000
|
||||||
|
|
||||||
|
handler.post(object : Runnable {
|
||||||
|
override fun run() {
|
||||||
|
Log.i(TAG, "search")
|
||||||
|
val query = DownloadManager.Query().setFilterById(downloadId)
|
||||||
|
val cursor: Cursor = downloadManager.query(query)
|
||||||
|
cursor.use {
|
||||||
|
if (it.moveToFirst()) {
|
||||||
|
val bytesDownloadedIndex =
|
||||||
|
it.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR)
|
||||||
|
val bytesTotalIndex =
|
||||||
|
it.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES)
|
||||||
|
|
||||||
|
// 检查列名是否存在
|
||||||
|
if (bytesDownloadedIndex != -1 && bytesTotalIndex != -1) {
|
||||||
|
val bytesDownloaded = it.getInt(bytesDownloadedIndex)
|
||||||
|
val bytesTotal = it.getInt(bytesTotalIndex)
|
||||||
|
|
||||||
|
if (bytesTotal != -1) {
|
||||||
|
val progress = (bytesDownloaded * 100L / bytesTotal).toInt()
|
||||||
|
progressListener(progress)
|
||||||
|
if (progress == 100) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handler.postDelayed(this, intervalMillis)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private class DownloadReceiver(
|
||||||
|
private val context: Context,
|
||||||
|
private val apkFileName: String,
|
||||||
|
private val downloadReference: Long
|
||||||
|
) : BroadcastReceiver() {
|
||||||
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
|
val reference = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1)
|
||||||
|
Log.i(TAG, "reference $reference")
|
||||||
|
val progress = intent.getIntExtra("progress", 0)
|
||||||
|
Log.i(TAG, "progress $progress")
|
||||||
|
|
||||||
|
// 检查是否是我们发起的下载
|
||||||
|
if (reference == downloadReference) {
|
||||||
|
// 下载完成,触发安装
|
||||||
|
installNewVersion()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun installNewVersion() {
|
||||||
|
val installIntent = Intent(Intent.ACTION_VIEW)
|
||||||
|
val apkUri = Uri.fromFile(
|
||||||
|
File(
|
||||||
|
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
|
||||||
|
apkFileName
|
||||||
|
)
|
||||||
|
)
|
||||||
|
installIntent.setDataAndType(apkUri, "application/vnd.android.package-archive")
|
||||||
|
installIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
context.startActivity(installIntent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "UpdateManager"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onConfirm() {
|
||||||
|
Log.i(TAG, "onConfirm $release")
|
||||||
|
release?.let { startDownload(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCancel() {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun destroy() {
|
||||||
|
if (downloadReceiver != null) {
|
||||||
|
context!!.unregisterReceiver(downloadReceiver)
|
||||||
|
Log.i(TAG, "destroy downloadReceiver")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,19 +2,65 @@ package com.lizongying.mytv
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
|
import android.os.Build
|
||||||
import android.util.TypedValue
|
import android.util.TypedValue
|
||||||
|
import com.google.gson.Gson
|
||||||
|
import com.lizongying.mytv.api.TimeResponse
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.text.SimpleDateFormat
|
|
||||||
import java.util.*
|
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Date
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
object Utils {
|
object Utils {
|
||||||
|
private var between: Long = 0
|
||||||
|
|
||||||
fun getDateFormat(format: String): String {
|
fun getDateFormat(format: String): String {
|
||||||
return SimpleDateFormat(format, Locale.CHINA).format(Date())
|
return SimpleDateFormat(
|
||||||
|
format,
|
||||||
|
Locale.CHINA
|
||||||
|
).format(Date(System.currentTimeMillis() - between))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getDateTimestamp(): Long {
|
fun getDateTimestamp(): Long {
|
||||||
return Date().time / 1000
|
return (System.currentTimeMillis() - between) / 1000
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun init() {
|
||||||
|
var currentTimeMillis: Long = 0
|
||||||
|
try {
|
||||||
|
currentTimeMillis = getTimestampFromServer()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
println("Failed to retrieve timestamp from server: ${e.message}")
|
||||||
|
}
|
||||||
|
between = System.currentTimeMillis() - currentTimeMillis
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从服务器获取时间戳
|
||||||
|
* @return Long 时间戳
|
||||||
|
*/
|
||||||
|
private suspend fun getTimestampFromServer(): Long {
|
||||||
|
return 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("https://api.m.taobao.com/rest/api3.do?api=mtop.common.getTimestamp")
|
||||||
|
.build()
|
||||||
|
try {
|
||||||
|
client.newCall(request).execute().use { response ->
|
||||||
|
if (!response.isSuccessful) throw IOException("Unexpected code $response")
|
||||||
|
val string = response.body()?.string()
|
||||||
|
Gson().fromJson(string, TimeResponse::class.java).data.t.toLong()
|
||||||
|
}
|
||||||
|
} catch (e: IOException) {
|
||||||
|
// Handle network errors
|
||||||
|
throw IOException("Error during network request", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun dpToPx(dp: Float): Int {
|
fun dpToPx(dp: Float): Int {
|
||||||
@@ -29,6 +75,8 @@ object Utils {
|
|||||||
).toInt()
|
).toInt()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun isTmallDevice() = Build.MANUFACTURER.equals("Tmall", ignoreCase = true)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取可读写的目录
|
* 获取可读写的目录
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import javax.net.ssl.X509TrustManager
|
|||||||
class ApiClient {
|
class ApiClient {
|
||||||
private val yspUrl = "https://player-api.yangshipin.cn/"
|
private val yspUrl = "https://player-api.yangshipin.cn/"
|
||||||
private val myUrl = "https://lyrics.run/"
|
private val myUrl = "https://lyrics.run/"
|
||||||
|
private val devUrl = "http://10.0.2.2:8081/"
|
||||||
private val protoUrl = "https://capi.yangshipin.cn/"
|
private val protoUrl = "https://capi.yangshipin.cn/"
|
||||||
private val traceUrl = "https://btrace.yangshipin.cn/"
|
private val traceUrl = "https://btrace.yangshipin.cn/"
|
||||||
|
|
||||||
@@ -34,6 +35,14 @@ class ApiClient {
|
|||||||
.build().create(YSPTokenService::class.java)
|
.build().create(YSPTokenService::class.java)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val releaseService: ReleaseService by lazy {
|
||||||
|
Retrofit.Builder()
|
||||||
|
.baseUrl(myUrl)
|
||||||
|
.client(okHttpClient)
|
||||||
|
.addConverterFactory(GsonConverterFactory.create())
|
||||||
|
.build().create(ReleaseService::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
val yspProtoService: YSPProtoService by lazy {
|
val yspProtoService: YSPProtoService by lazy {
|
||||||
Retrofit.Builder()
|
Retrofit.Builder()
|
||||||
.baseUrl(protoUrl)
|
.baseUrl(protoUrl)
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package com.lizongying.mytv.api
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import okhttp3.MediaType
|
||||||
|
import okhttp3.RequestBody
|
||||||
|
import okio.BufferedSink
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
|
data class Auth(
|
||||||
|
val code: Int,
|
||||||
|
val msg: String,
|
||||||
|
val data: AuthData,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class AuthData(
|
||||||
|
val token: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class AuthRequest(
|
||||||
|
var data: String,
|
||||||
|
) : RequestBody() {
|
||||||
|
override fun contentType(): MediaType? {
|
||||||
|
return MediaType.parse("application/x-www-form-urlencoded;charset=UTF-8")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun writeTo(sink: BufferedSink) {
|
||||||
|
try {
|
||||||
|
sink.writeUtf8(data)
|
||||||
|
} catch (e: IOException) {
|
||||||
|
Log.e(TAG, "$e")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "AuthRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,9 +3,30 @@ package com.lizongying.mytv.api
|
|||||||
data class Info(
|
data class Info(
|
||||||
val code: Int?,
|
val code: Int?,
|
||||||
val msg: String?,
|
val msg: String?,
|
||||||
val data: InfoData,
|
val data: Data,
|
||||||
)
|
) {
|
||||||
|
data class Data(
|
||||||
|
val token: String,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
data class InfoData(
|
data class Release(
|
||||||
val token: String,
|
val code: Int?,
|
||||||
)
|
val msg: String?,
|
||||||
|
val data: Data,
|
||||||
|
) {
|
||||||
|
data class Data(
|
||||||
|
val versionName: String,
|
||||||
|
val versionCode: Int,
|
||||||
|
val downloadUrl: String,
|
||||||
|
val updateTime: Int,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
data class TimeResponse(
|
||||||
|
val data: Time
|
||||||
|
) {
|
||||||
|
data class Time(
|
||||||
|
val t: String
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package com.lizongying.mytv.api
|
||||||
|
|
||||||
|
import retrofit2.Call
|
||||||
|
import retrofit2.http.GET
|
||||||
|
|
||||||
|
|
||||||
|
interface ReleaseService {
|
||||||
|
@GET("my-tv/v1/release")
|
||||||
|
fun getRelease(
|
||||||
|
): Call<Release>
|
||||||
|
}
|
||||||
@@ -50,6 +50,9 @@ class YSP(var context: Context) {
|
|||||||
|
|
||||||
private var signature = ""
|
private var signature = ""
|
||||||
|
|
||||||
|
private var appid = "ysp_pc"
|
||||||
|
var token = ""
|
||||||
|
|
||||||
private var encryptor: Encryptor? = null
|
private var encryptor: Encryptor? = null
|
||||||
private lateinit var sharedPref: SharedPreferences
|
private lateinit var sharedPref: SharedPreferences
|
||||||
|
|
||||||
@@ -83,6 +86,19 @@ class YSP(var context: Context) {
|
|||||||
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":"$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"}"""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getAuthData(tvModel: TVViewModel): String {
|
||||||
|
livepid = tvModel.pid.value!!
|
||||||
|
|
||||||
|
randStr = getRand()
|
||||||
|
|
||||||
|
if (tvModel.retryTimes > 0) {
|
||||||
|
guid = newGuid()
|
||||||
|
}
|
||||||
|
|
||||||
|
signature = getAuthSignature()
|
||||||
|
return """pid=$livepid&guid=$guid&appid=$appid&rand_str=$randStr&signature=$signature"""
|
||||||
|
}
|
||||||
|
|
||||||
private fun getTimeStr(): String {
|
private fun getTimeStr(): String {
|
||||||
return getDateTimestamp().toString()
|
return getDateTimestamp().toString()
|
||||||
}
|
}
|
||||||
@@ -131,6 +147,13 @@ class YSP(var context: Context) {
|
|||||||
return hashedData.let { it -> it.joinToString("") { "%02x".format(it) } }
|
return hashedData.let { it -> it.joinToString("") { "%02x".format(it) } }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun getAuthSignature(): String {
|
||||||
|
val e =
|
||||||
|
"appid=${appid}&guid=${guid}&pid=${livepid}&rand_str=${randStr}".toByteArray()
|
||||||
|
val hashedData = encryptor?.hash2(e) ?: return ""
|
||||||
|
return hashedData.let { it -> it.joinToString("") { "%02x".format(it) } }
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "YSP"
|
private const val TAG = "YSP"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,9 +13,23 @@ interface YSPApiService {
|
|||||||
"content-type: application/json;charset=UTF-8",
|
"content-type: application/json;charset=UTF-8",
|
||||||
"referer: https://www.yangshipin.cn/",
|
"referer: https://www.yangshipin.cn/",
|
||||||
"yspappid: 519748109",
|
"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 getLiveInfo(
|
fun getLiveInfo(
|
||||||
@Header("cookie") cookie: String,
|
@Header("cookie") cookie: String,
|
||||||
|
@Header("Yspplayertoken") token: String,
|
||||||
@Body request: LiveInfoRequest,
|
@Body request: LiveInfoRequest,
|
||||||
): Call<LiveInfo>
|
): Call<LiveInfo>
|
||||||
|
|
||||||
|
@POST("v1/player/auth")
|
||||||
|
@Headers(
|
||||||
|
"content-type: application/x-www-form-urlencoded;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 getAuth(
|
||||||
|
@Header("cookie") cookie: String,
|
||||||
|
@Body request: AuthRequest,
|
||||||
|
): Call<Auth>
|
||||||
}
|
}
|
||||||
@@ -2,10 +2,12 @@ package com.lizongying.mytv.api
|
|||||||
|
|
||||||
import retrofit2.Call
|
import retrofit2.Call
|
||||||
import retrofit2.http.GET
|
import retrofit2.http.GET
|
||||||
|
import retrofit2.http.Query
|
||||||
|
|
||||||
|
|
||||||
interface YSPTokenService {
|
interface YSPTokenService {
|
||||||
@GET("my-tv/v1/info")
|
@GET("my-tv/v1/info")
|
||||||
fun getInfo(
|
fun getInfo(
|
||||||
|
@Query("token") token: String = "",
|
||||||
): Call<Info>
|
): Call<Info>
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package com.lizongying.mytv.requests
|
||||||
|
|
||||||
|
import com.lizongying.mytv.api.ApiClient
|
||||||
|
import com.lizongying.mytv.api.Release
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import retrofit2.Call
|
||||||
|
import retrofit2.Callback
|
||||||
|
import retrofit2.Response
|
||||||
|
import kotlin.coroutines.resume
|
||||||
|
import kotlin.coroutines.suspendCoroutine
|
||||||
|
|
||||||
|
class MyRequest {
|
||||||
|
private var releaseService = ApiClient().releaseService
|
||||||
|
|
||||||
|
suspend fun getRelease(): Release? {
|
||||||
|
return withContext(Dispatchers.IO) {
|
||||||
|
fetchRelease()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun fetchRelease(): Release? {
|
||||||
|
return suspendCoroutine { continuation ->
|
||||||
|
releaseService.getRelease()
|
||||||
|
.enqueue(object : Callback<Release> {
|
||||||
|
override fun onResponse(call: Call<Release>, response: Response<Release>) {
|
||||||
|
if (response.isSuccessful) {
|
||||||
|
continuation.resume(response.body())
|
||||||
|
} else {
|
||||||
|
continuation.resume(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(call: Call<Release>, t: Throwable) {
|
||||||
|
continuation.resume(null)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "MyRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
@@ -13,7 +13,6 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="end"
|
android:layout_gravity="end"
|
||||||
android:layout_marginTop="0dp"
|
android:layout_marginTop="0dp"
|
||||||
android:text="99"
|
|
||||||
android:textColor="#FFEEEEEE"
|
android:textColor="#FFEEEEEE"
|
||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
android:textSize="50sp" />
|
android:textSize="50sp" />
|
||||||
|
|||||||
@@ -27,7 +27,31 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="10dp"
|
android:layout_marginTop="10dp"
|
||||||
android:text="当前版本: $versionName\n获取最新: https://github.com/lizongying/my-tv/releases/"
|
android:text="当前版本: $versionName\n获取最新: https://github.com/lizongying/my-tv/releases/"
|
||||||
></TextView>
|
/>
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="300dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
android:id="@+id/check_version"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="10dp"
|
||||||
|
android:text="@string/check_version"
|
||||||
|
/>
|
||||||
|
<ProgressBar
|
||||||
|
android:visibility="gone"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
/>
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/version_name"
|
||||||
|
android:text=""
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
<Switch
|
<Switch
|
||||||
android:id="@+id/switch_channel_reversal"
|
android:id="@+id/switch_channel_reversal"
|
||||||
android:text="@string/title_channel_reversal"
|
android:text="@string/title_channel_reversal"
|
||||||
@@ -40,6 +64,12 @@
|
|||||||
android:layout_marginTop="10dp"
|
android:layout_marginTop="10dp"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content" />
|
android:layout_height="wrap_content" />
|
||||||
|
<Switch
|
||||||
|
android:id="@+id/switch_boot_startup"
|
||||||
|
android:text="@string/title_boot_startup"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
<ImageView
|
<ImageView
|
||||||
android:layout_width="300dp"
|
android:layout_width="300dp"
|
||||||
|
|||||||
@@ -7,6 +7,14 @@
|
|||||||
android:background="@color/black"
|
android:background="@color/black"
|
||||||
android:keepScreenOn="true">
|
android:keepScreenOn="true">
|
||||||
|
|
||||||
|
<SurfaceView
|
||||||
|
android:id="@+id/surface_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
app:resize_mode="fill"
|
||||||
|
app:use_controller="false" />
|
||||||
|
|
||||||
<androidx.media3.ui.PlayerView
|
<androidx.media3.ui.PlayerView
|
||||||
android:id="@+id/player_view"
|
android:id="@+id/player_view"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:id="@+id/row"
|
android:id="@+id/row"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
|||||||
@@ -2,4 +2,5 @@
|
|||||||
<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="gray_overlay">#7F000000</color>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -2,4 +2,6 @@
|
|||||||
<string name="app_name">我的电视</string>
|
<string name="app_name">我的电视</string>
|
||||||
<string name="title_channel_reversal">换台反转</string>
|
<string name="title_channel_reversal">换台反转</string>
|
||||||
<string name="title_channel_num">数字选台</string>
|
<string name="title_channel_num">数字选台</string>
|
||||||
|
<string name="check_version">检查更新</string>
|
||||||
|
<string name="title_boot_startup">开机自启</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -10,8 +10,8 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="CustomImageCardViewImageStyle" parent="Widget.Leanback.ImageCardView.ImageStyle">
|
<style name="CustomImageCardViewImageStyle" parent="Widget.Leanback.ImageCardView.ImageStyle">
|
||||||
<item name="android:layout_width">143dp</item>
|
<item name="android:layout_width">160dp</item>
|
||||||
<item name="android:layout_height">50dp</item>
|
<item name="android:layout_height">40dp</item>
|
||||||
<item name="android:adjustViewBounds">true</item>
|
<item name="android:adjustViewBounds">true</item>
|
||||||
<item name="android:contentDescription">@null</item>
|
<item name="android:contentDescription">@null</item>
|
||||||
<item name="android:scaleType">centerCrop</item>
|
<item name="android:scaleType">centerCrop</item>
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 73 KiB |
Reference in New Issue
Block a user