Compare commits

...
3 Commits
Author SHA1 Message Date
Li ZongYing d7a60531ab fix cctv6 2024-02-08 00:22:24 +08:00
Li ZongYing 764f2a1549 fix time error 2024-02-07 19:21:17 +08:00
Li ZongYing aa4d52681f fix ysp 2024-02-07 03:05:11 +08:00
23 changed files with 1416 additions and 157 deletions
+10
View File
@@ -14,6 +14,16 @@
## 更新日志 ## 更新日志
### v1.5.8(通用版)
* 修复央视6画质差的问题
* 增加兵团卫视
* 播放失败重试
### v1.5.6(通用版)
* 解决部分设备系统时间不对导致播放失败的问题
### v1.5.2(通用版) ### v1.5.2(通用版)
* 修复APP恢复后频道号、频道列表不自动消失的问题 * 修复APP恢复后频道号、频道列表不自动消失的问题
@@ -80,6 +80,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()
@@ -47,6 +47,11 @@ class CardPresenter(
.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()
@@ -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()
}
}
@@ -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
@@ -44,6 +50,16 @@ class MainActivity : FragmentActivity() {
private var versionName = "" 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")
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@@ -68,8 +84,14 @@ class MainActivity : FragmentActivity() {
channelReversal = sharedPref.getBoolean(CHANNEL_REVERSAL, channelReversal) channelReversal = sharedPref.getBoolean(CHANNEL_REVERSAL, channelReversal)
channelNum = sharedPref.getBoolean(CHANNEL_NUM, channelNum) channelNum = sharedPref.getBoolean(CHANNEL_NUM, channelNum)
versionName = getPackageInfo().versionName val packageInfo = getPackageInfo()
settingFragment = SettingFragment(versionName, channelReversal, channelNum) 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)
} }
fun showInfoFragment(tvViewModel: TVViewModel) { fun showInfoFragment(tvViewModel: TVViewModel) {
@@ -156,7 +178,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 {
@@ -496,7 +522,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)
} }
} }
@@ -42,8 +42,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?
@@ -62,7 +60,6 @@ class MainFragment : Fragment(), CardAdapter.ItemListener {
val content = binding.content val content = binding.content
var idx: Long = 0 var idx: Long = 0
context?.let { TVList.init(it) }
for ((k, v) in TVList.list) { for ((k, v) in TVList.list) {
val itemBinding: RowBinding = val itemBinding: RowBinding =
RowBinding.inflate(layoutInflater, content, false) RowBinding.inflate(layoutInflater, content, false)
@@ -150,7 +147,7 @@ class MainFragment : Fragment(), CardAdapter.ItemListener {
} }
} }
fragmentReady() (activity as MainActivity).fragmentReady()
} }
} }
@@ -181,6 +178,8 @@ class MainFragment : Fragment(), CardAdapter.ItemListener {
fun setPosition() { fun setPosition() {
val tvViewModel = tvListViewModel.getTVViewModel(itemPosition) val tvViewModel = tvListViewModel.getTVViewModel(itemPosition)
Log.i(TAG, "tvViewModel $tvViewModel")
Log.i(TAG, "rowList ${rowList.size}")
rowList[tvViewModel!!.getRowPosition()].post { rowList[tvViewModel!!.getRowPosition()].post {
((rowList[tvViewModel.getRowPosition()] as RecyclerView).layoutManager as LinearLayoutManager).findViewByPosition( ((rowList[tvViewModel.getRowPosition()] as RecyclerView).layoutManager as LinearLayoutManager).findViewByPosition(
tvViewModel.getItemPosition() tvViewModel.getItemPosition()
@@ -213,12 +212,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) {
@@ -300,7 +295,9 @@ class MainFragment : Fragment(), CardAdapter.ItemListener {
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() {
@@ -32,7 +32,6 @@ class PlayerFragment : Fragment() {
): View { ): View {
_binding = PlayerBinding.inflate(inflater, container, false) _binding = PlayerBinding.inflate(inflater, container, false)
playerView = _binding!!.playerView playerView = _binding!!.playerView
(activity as MainActivity).playerFragment = this
playerView?.viewTreeObserver?.addOnGlobalLayoutListener(object : playerView?.viewTreeObserver?.addOnGlobalLayoutListener(object :
ViewTreeObserver.OnGlobalLayoutListener { ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() { override fun onGlobalLayout() {
@@ -63,6 +62,7 @@ class PlayerFragment : Fragment() {
super.onPlayerError(error) super.onPlayerError(error)
Log.e(TAG, "PlaybackException $error") Log.e(TAG, "PlaybackException $error")
tvViewModel?.changed()
} }
}) })
} }
@@ -86,6 +86,7 @@ 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()
playerView!!.player?.play()
} else { } else {
Log.i(TAG, "playing") Log.i(TAG, "playing")
} }
@@ -41,55 +41,7 @@ class Request {
private val regex = Regex("""des_key = "([^"]+).+var des_iv = "([^"]+)""") private val regex = Regex("""des_key = "([^"]+).+var des_iv = "([^"]+)""")
private val input = private val input =
"""{"mver":"1","subver":"1.2","host":"www.yangshipin.cn/#/tv/home?pid=","referer":"","canvas":"YSPANGLE(Apple,AppleM1Pro,OpenGL4.1)"}""".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)
@@ -118,6 +70,7 @@ class Request {
override fun onResponse(call: Call<LiveInfo>, response: Response<LiveInfo>) { override fun onResponse(call: Call<LiveInfo>, response: Response<LiveInfo>) {
if (response.isSuccessful) { if (response.isSuccessful) {
val liveInfo = response.body() val liveInfo = response.body()
if (liveInfo?.data?.playurl != null) { if (liveInfo?.data?.playurl != null) {
val chanll = liveInfo.data.chanll val chanll = liveInfo.data.chanll
val decodedBytes = Base64.decode( val decodedBytes = Base64.decode(
@@ -133,8 +86,8 @@ class Request {
val url = liveInfo.data.playurl + "&revoi=" + encryptTripleDES( val url = liveInfo.data.playurl + "&revoi=" + encryptTripleDES(
keyBytes + byteArrayOf(0, 0, 0, 0, 0, 0, 0, 0), keyBytes + byteArrayOf(0, 0, 0, 0, 0, 0, 0, 0),
ivBytes ivBytes
).uppercase() ).uppercase() + liveInfo.data.extended_param
// Log.d(TAG, "$title url $url") Log.d(TAG, "$title url $url")
tvModel.addVideoUrl(url) tvModel.addVideoUrl(url)
tvModel.allReady() tvModel.allReady()
tvModel.retryTimes = 0 tvModel.retryTimes = 0
@@ -202,7 +155,7 @@ class Request {
} }
override fun onFailure(call: Call<LiveInfo>, t: Throwable) { override fun onFailure(call: Call<LiveInfo>, t: Throwable) {
Log.e(TAG, "$title request error") Log.e(TAG, "$title 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) {
@@ -354,29 +307,20 @@ class Request {
) { ) {
if (response.isSuccessful) { if (response.isSuccessful) {
val body = response.body() val body = response.body()
if (body?.data?.feedModuleListCount == 1) { if (body?.data?.feedModuleListCount == 1) {
for (item in body.data?.feedModuleListList!![0]?.dataTvChannelListList!!) { for (item in body.data?.feedModuleListList!![0]?.dataTvChannelListList!!) {
if (item.isVip && !item.isLimitedFree) { Log.d(
continue
}
Log.i(
TAG, TAG,
"${item.channelName},${item.pid},${item.streamId}" "${item.channelName},${item.pid},${item.streamId}"
) )
var channelType = "央视频道"
if (item?.channelType === "weishi") { for ((_, v) in TVList.list) {
channelType = "地方频道" for (v2 in v) {
} if (v2.title == item.channelName || v2.alias == item.channelName) {
if (!mapping.containsKey(item.channelName)) { v2.pid = item.pid
continue v2.sid = item.streamId
} }
val tv = }
TVList.list[channelType]?.find { it.title == mapping[item.channelName] }
if (tv != null) {
tv.logo = item.tvLogo
tv.pid = item.pid
tv.sid = item.streamId
} }
} }
} }
@@ -1,22 +1,28 @@
package com.lizongying.mytv package com.lizongying.mytv
import android.os.Build
import android.os.Bundle import android.os.Bundle
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 androidx.annotation.RequiresApi
import androidx.fragment.app.DialogFragment 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,
) :
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)
@@ -43,14 +49,33 @@ class SettingFragment(private val versionName: String,
(activity as MainActivity).saveChannelNum(isChecked) (activity as MainActivity).saveChannelNum(isChecked)
} }
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"
} }
+4 -3
View File
@@ -5,6 +5,7 @@ import java.io.Serializable
data class TV( data class TV(
var id: Int = 0, var id: Int = 0,
var title: String, var title: String,
var alias: String = "",
var videoUrl: List<String>, var videoUrl: List<String>,
var videoIndex: Int = 0, var videoIndex: Int = 0,
var channel: String = "", var channel: String = "",
@@ -12,10 +13,10 @@ data class TV(
var pid: String = "", var pid: String = "",
var sid: String = "", var sid: String = "",
var programId: String = "", var programId: String = "",
var needToken:Boolean=false, var needToken: Boolean = false,
var mustToken:Boolean=false, var mustToken: Boolean = false,
) : Serializable { ) : Serializable {
override fun toString(): String { override fun toString(): String {
return "TV{" + return "TV{" +
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,143 @@
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.net.Uri
import android.os.Build
import android.os.Environment
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))
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)
)
}
}
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")
}
}
}
+49 -4
View File
@@ -3,18 +3,63 @@ package com.lizongying.mytv
import android.content.Context import android.content.Context
import android.content.res.Resources import android.content.res.Resources
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 {
@@ -13,9 +13,11 @@ 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/"
private var okHttpClient = getUnsafeOkHttpClient() private var okHttpClient = getUnsafeOkHttpClient()
val yspApiService: YSPApiService by lazy { val yspApiService: YSPApiService by lazy {
@@ -34,6 +36,14 @@ class ApiClient {
.build().create(YSPTokenService::class.java) .build().create(YSPTokenService::class.java)
} }
val releaseService: ReleaseService by lazy {
Retrofit.Builder()
.baseUrl(devUrl)
.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)
@@ -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
)
}
@@ -13,6 +13,7 @@ data class LiveInfo(
) )
data class LiveInfoData( data class LiveInfoData(
val extended_param: String,
val chanll: String, val chanll: String,
val playurl: String, val playurl: String,
val errinfo: String, val errinfo: 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>
}
@@ -127,7 +127,6 @@ class YSP(var context: Context) {
private fun getSignature(): String { private fun getSignature(): String {
val e = val e =
"adjust=${adjust}&appVer=${appVer}&app_version=$appVersion&cKey=$cKey&channel=$channel&cmd=$cmd&cnlid=$cnlid&defn=${defn}&devid=${devid}&dtype=${dtype}&encryptVer=${encryptVer}&guid=${guid}&livepid=${livepid}&otype=${otype}&platform=${platform}&rand_str=${randStr}&sphttps=${sphttps}&stream=${stream}".toByteArray() "adjust=${adjust}&appVer=${appVer}&app_version=$appVersion&cKey=$cKey&channel=$channel&cmd=$cmd&cnlid=$cnlid&defn=${defn}&devid=${devid}&dtype=${dtype}&encryptVer=${encryptVer}&guid=${guid}&livepid=${livepid}&otype=${otype}&platform=${platform}&rand_str=${randStr}&sphttps=${sphttps}&stream=${stream}".toByteArray()
val hashedData = encryptor?.hash(e) ?: return "" val hashedData = encryptor?.hash(e) ?: return ""
return hashedData.let { it -> it.joinToString("") { "%02x".format(it) } } return hashedData.let { it -> it.joinToString("") { "%02x".format(it) } }
} }
@@ -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

+25 -1
View File
@@ -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"
+1
View File
@@ -2,4 +2,5 @@
<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>
</resources> </resources>