diff --git a/README.md b/README.md index 5549223..3185bf3 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,11 @@ * 凤凰卫视增强画质 * 凤凰卫视增加EPG +### v1.6.5(安卓5及以上专用) + +* 增加CETV1图标 +* 稳定性提升 + ### v1.6.4(通用) * 增加CETV1 diff --git a/app/src/main/java/com/lizongying/mytv/Ext.kt b/app/src/main/java/com/lizongying/mytv/Ext.kt new file mode 100644 index 0000000..1ba934d --- /dev/null +++ b/app/src/main/java/com/lizongying/mytv/Ext.kt @@ -0,0 +1,84 @@ +@file:Suppress("DEPRECATION") + +package com.lizongying.mytv + +import android.content.Context +import android.content.pm.PackageInfo +import android.content.pm.PackageManager +import android.content.pm.Signature +import android.content.pm.SigningInfo +import android.os.Build +import android.util.Log +import java.security.MessageDigest + +private const val TAG = "Extensions" + +private val Context.packageInfo: PackageInfo + get() { + val flag = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) { + PackageManager.GET_SIGNATURES + } else { + PackageManager.GET_SIGNING_CERTIFICATES + } + return if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { + packageManager.getPackageInfo(packageName, flag) + } else { + packageManager.getPackageInfo( + packageName, + PackageManager.PackageInfoFlags.of(PackageManager.GET_SIGNING_CERTIFICATES.toLong()) + ) + } + } + +/** + * Return the version code of the app which is defined in build.gradle. + * eg:100 + */ +val Context.appVersionCode: Long + get() { + val packageInfo = this.packageInfo + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + packageInfo.longVersionCode + } else { + packageInfo.versionCode.toLong() + } + } + +/** + * Return the version name of the app which is defined in build.gradle. + * eg:1.0.0 + */ +val Context.appVersionName: String get() = packageInfo.versionName + +val Context.appSignature: String + get() { + val packageInfo = this.packageInfo + var sign: Signature? = null + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) { + val signatures: Array? = packageInfo.signatures + if (signatures != null) { + sign = signatures[0] + } + } else { + val signingInfo: SigningInfo? = packageInfo.signingInfo + if (signingInfo != null) { + sign = signingInfo.apkContentsSigners[0] + } + } + if (sign == null) { + return "" + } + return hashSignature(sign) + } + +private fun hashSignature(signature: Signature): String { + return try { + val md = MessageDigest.getInstance("MD5") + md.update(signature.toByteArray()) + val digest = md.digest() + digest.let { it -> it.joinToString("") { "%02x".format(it) } } + } catch (e: Exception) { + Log.e(TAG, "Error hashing signature", e) + "" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lizongying/mytv/MainActivity.kt b/app/src/main/java/com/lizongying/mytv/MainActivity.kt index bcd5dff..a0f38e6 100644 --- a/app/src/main/java/com/lizongying/mytv/MainActivity.kt +++ b/app/src/main/java/com/lizongying/mytv/MainActivity.kt @@ -1,10 +1,5 @@ package com.lizongying.mytv -import android.content.pm.PackageInfo -import android.content.pm.PackageManager -import android.content.pm.Signature -import android.content.pm.SigningInfo -import android.os.Build import android.os.Bundle import android.os.Handler import android.os.Looper @@ -16,23 +11,17 @@ import android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION import android.view.WindowManager import android.widget.Toast import androidx.fragment.app.FragmentActivity -import androidx.lifecycle.lifecycleScope 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 class MainActivity : FragmentActivity(), Request.RequestListener { private var ready = 0 - private var playerFragment = PlayerFragment() - private var mainFragment = MainFragment() - private var infoFragment = InfoFragment() - private var channelFragment = ChannelFragment() - private lateinit var settingFragment: SettingFragment + private val playerFragment = PlayerFragment() + private val mainFragment = MainFragment() + private val infoFragment = InfoFragment() + private val channelFragment = ChannelFragment() + private val settingFragment = SettingFragment() private var doubleBackToExitPressedOnce = false @@ -64,20 +53,6 @@ class MainActivity : FragmentActivity(), Request.RequestListener { .commit() } gestureDetector = GestureDetector(this, GestureListener()) - val packageInfo = getPackageInfo() - 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, - SP.channelReversal, - SP.channelNum, - SP.bootStartup - ) } fun showInfoFragment(tvViewModel: TVViewModel) { @@ -448,56 +423,7 @@ class MainActivity : FragmentActivity(), Request.RequestListener { return super.onKeyDown(keyCode, event) } - private fun getPackageInfo(): PackageInfo { - val flag = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) { - PackageManager.GET_SIGNATURES - } else { - PackageManager.GET_SIGNING_CERTIFICATES - } - - return if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { - packageManager.getPackageInfo(packageName, flag) - } else { - packageManager.getPackageInfo( - packageName, - PackageManager.PackageInfoFlags.of(PackageManager.GET_SIGNING_CERTIFICATES.toLong()) - ) - } - } - - private fun getAppSignature(): String { - val packageInfo = getPackageInfo() - - var sign: Signature? = null - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) { - val signatures: Array? = packageInfo.signatures - if (signatures != null) { - sign = signatures[0] - } - } else { - val signingInfo: SigningInfo? = packageInfo.signingInfo - if (signingInfo != null) { - sign = signingInfo.apkContentsSigners[0] - } - } - if (sign == null) { - return "" - } - - return hashSignature(sign) - } - - private fun hashSignature(signature: Signature): String { - return try { - val md = MessageDigest.getInstance("MD5") - md.update(signature.toByteArray()) - val digest = md.digest() - digest.let { it -> it.joinToString("") { "%02x".format(it) } } - } catch (e: Exception) { - Log.e(TAG, "Error hashing signature", e) - "" - } - } + private fun getAppSignature() = this.appSignature override fun onStart() { Log.i(TAG, "onStart") diff --git a/app/src/main/java/com/lizongying/mytv/Request.kt b/app/src/main/java/com/lizongying/mytv/Request.kt index febd4ce..89198eb 100644 --- a/app/src/main/java/com/lizongying/mytv/Request.kt +++ b/app/src/main/java/com/lizongying/mytv/Request.kt @@ -56,6 +56,9 @@ object Request { private var listener: RequestListener? = null + private var initRetryTimes = 0 + private var initRetryMaxTimes = 1 + fun onCreate() { Log.i(TAG, "onCreate") fetchInfoV2() @@ -66,11 +69,22 @@ object Request { handler.removeCallbacks(tokenRunnable) } - var call: Call? = null + private var call: Call? = null private var callAuth: Call? = null + private var callInfo: Call? = null + private var fAuth: Call? = null + private var callPage: Call? = null - private fun fetchAuth(tvModel: TVViewModel, cookie: String) { + private fun cancelCall() { + call?.cancel() callAuth?.cancel() + callInfo?.cancel() + fAuth?.cancel() + callPage?.cancel() + } + + private fun fetchAuth(tvModel: TVViewModel, cookie: String) { + cancelCall() val title = tvModel.title.value @@ -146,9 +160,9 @@ object Request { }) } - fun fetchVideo(tvModel: TVViewModel, cookie: String) { - Log.i(TAG, "fetchVideo with cookie") - call?.cancel() + private fun fetchVideo(tvModel: TVViewModel, cookie: String) { + cancelCall() + if (::btraceRunnable.isInitialized) { handler.removeCallbacks(btraceRunnable) } @@ -284,37 +298,23 @@ object Request { }) } - fun fetchAuth(tvModel: TVViewModel) { + private fun fetchAuth(tvModel: TVViewModel) { + cancelCall() if (token == "") { - yspTokenService.getInfo("") - .enqueue(object : Callback { - override fun onResponse(call: Call, response: Response) { - if (response.isSuccessful) { - token = response.body()?.data?.token!! - Log.i(TAG, "info success $token") - val cookie = - "versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; deviceModel=120; 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, t: Throwable) { - Log.e(TAG, "info request error $t") + callInfo = yspTokenService.getInfo("") + callInfo?.enqueue(object : Callback { + override fun onResponse(call: Call, response: Response) { + if (response.isSuccessful) { + token = response.body()?.data?.token!! + Log.i(TAG, "info success $token") + val cookie = + "versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; deviceModel=120; 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++ - fetchVideo(tvModel) + fetchAuth(tvModel) } else { if (!tvModel.getTV().mustToken) { val cookie = @@ -323,7 +323,22 @@ object Request { } } } - }) + } + + override fun onFailure(call: Call, 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" @@ -331,35 +346,21 @@ object Request { } } - fun fetchVideo(tvModel: TVViewModel) { + private fun fetchVideo(tvModel: TVViewModel) { + cancelCall() Log.d(TAG, "fetchVideo") if (token == "") { - yspTokenService.getInfo("") - .enqueue(object : Callback { - override fun onResponse(call: Call, response: Response) { - 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) - } else { - Log.e(TAG, "info status error") - if (tvModel.tokenRetryTimes < tvModel.tokenRetryMaxTimes) { - tvModel.tokenRetryTimes++ - fetchVideo(tvModel) - } else { - if (!tvModel.getTV().mustToken) { - val cookie = - "versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; deviceModel=120; appid=1400421205; yspappid=519748109" - fetchVideo(tvModel, cookie) - } - } - } - } - - override fun onFailure(call: Call, t: Throwable) { - Log.e(TAG, "info request error $t") + callInfo = yspTokenService.getInfo("") + callInfo?.enqueue(object : Callback { + override fun onResponse(call: Call, response: Response) { + 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) + } else { + Log.e(TAG, "info status error") if (tvModel.tokenRetryTimes < tvModel.tokenRetryMaxTimes) { tvModel.tokenRetryTimes++ fetchVideo(tvModel) @@ -371,7 +372,22 @@ object Request { } } } - }) + } + + override fun onFailure(call: Call, 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" + fetchVideo(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" @@ -379,11 +395,8 @@ object Request { } } - private var fAuth: Call? = null - fun fetchFAuth(tvModel: TVViewModel) { - call?.cancel() - callAuth?.cancel() - fAuth?.cancel() + private fun fetchFAuth(tvModel: TVViewModel) { + cancelCall() val title = tvModel.title.value @@ -400,11 +413,11 @@ object Request { // Log.d(TAG, "$title url $url") tvModel.addVideoUrl(url) tvModel.allReady() - tvModel.tokenRetryTimes = 0 + tvModel.tokenFHRetryTimes = 0 } else { Log.e(TAG, "auth status error ${response.code()}") - if (tvModel.tokenRetryTimes < tvModel.tokenRetryMaxTimes) { - tvModel.tokenRetryTimes++ + if (tvModel.tokenFHRetryTimes < tvModel.tokenFHRetryMaxTimes) { + tvModel.tokenFHRetryTimes++ fetchFAuth(tvModel) } } @@ -412,8 +425,8 @@ object Request { override fun onFailure(call: Call, t: Throwable) { Log.e(TAG, "auth request error $t") - if (tvModel.tokenRetryTimes < tvModel.tokenRetryMaxTimes) { - tvModel.tokenRetryTimes++ + if (tvModel.tokenFHRetryTimes < tvModel.tokenFHRetryMaxTimes) { + tvModel.tokenFHRetryTimes++ fetchFAuth(tvModel) } } @@ -421,7 +434,6 @@ object Request { } fun fetchData(tvModel: TVViewModel) { - Log.i(TAG, "fetchData") if (tvModel.getTV().channel == "港澳台") { fetchFAuth(tvModel) return @@ -446,7 +458,7 @@ object Request { class TokenRunnable : Runnable { override fun run() { - fetchToken() +// fetchToken() } } @@ -489,14 +501,20 @@ object Request { Utils.setBetween(c * 1000L) Log.i(TAG, "current time $c") } + listener?.onRequestFinished() } else { Log.e(TAG, "token status error") handler.postDelayed( tokenRunnable, 30 * 60 * 1000 ) + if (initRetryTimes < initRetryMaxTimes) { + initRetryTimes++ + fetchInfoV2() + } else { + listener?.onRequestFinished() + } } - listener?.onRequestFinished() } override fun onFailure(call: Call, t: Throwable) { @@ -505,7 +523,13 @@ object Request { tokenRunnable, 30 * 60 * 1000 ) - listener?.onRequestFinished() + + if (initRetryTimes < initRetryMaxTimes) { + initRetryTimes++ + fetchInfoV2() + } else { + listener?.onRequestFinished() + } } }) } @@ -601,7 +625,8 @@ object Request { } fun fetchPage() { - yspProtoService.getPage().enqueue(object : Callback { + callPage = yspProtoService.getPage() + callPage?.enqueue(object : Callback { override fun onResponse( call: Call, response: Response diff --git a/app/src/main/java/com/lizongying/mytv/SettingFragment.kt b/app/src/main/java/com/lizongying/mytv/SettingFragment.kt index 801f279..dbf63b5 100644 --- a/app/src/main/java/com/lizongying/mytv/SettingFragment.kt +++ b/app/src/main/java/com/lizongying/mytv/SettingFragment.kt @@ -8,14 +8,7 @@ import androidx.fragment.app.DialogFragment import com.lizongying.mytv.databinding.DialogBinding -class SettingFragment( - private val versionName: String, - private val versionCode: Long, - private val channelReversal: Boolean, - private val channelNum: Boolean, - private val bootStartup: Boolean, -) : - DialogFragment() { +class SettingFragment : DialogFragment() { private var _binding: DialogBinding? = null private val binding get() = _binding!! @@ -32,33 +25,37 @@ class SettingFragment( container: ViewGroup?, savedInstanceState: Bundle? ): View { + val context = requireContext() // It‘s safe to get context here. _binding = DialogBinding.inflate(inflater, container, false) - _binding?.version?.text = - "当前版本: $versionName\n获取最新: https://github.com/lizongying/my-tv/releases/" - - val switchChannelReversal = _binding?.switchChannelReversal - switchChannelReversal?.isChecked = channelReversal - switchChannelReversal?.setOnCheckedChangeListener { _, isChecked -> - SP.channelReversal = isChecked - (activity as MainActivity).settingActive() + binding.version.text = + "当前版本: ${context.appVersionName}\n获取最新: https://github.com/lizongying/my-tv/releases/" + + binding.switchChannelReversal.run { + isChecked = SP.channelReversal + setOnCheckedChangeListener { _, isChecked -> + SP.channelReversal = isChecked + (activity as MainActivity).settingActive() + } } - val switchChannelNum = _binding?.switchChannelNum - switchChannelNum?.isChecked = channelNum - switchChannelNum?.setOnCheckedChangeListener { _, isChecked -> - SP.channelNum = isChecked - (activity as MainActivity).settingActive() + binding.switchChannelNum.run { + isChecked = SP.channelNum + setOnCheckedChangeListener { _, isChecked -> + SP.channelNum = isChecked + (activity as MainActivity).settingActive() + } } - val switchBootStartup = _binding?.switchBootStartup - switchBootStartup?.isChecked = bootStartup - switchBootStartup?.setOnCheckedChangeListener { _, isChecked -> - SP.bootStartup = isChecked - (activity as MainActivity).settingActive() + binding.switchBootStartup.run { + isChecked = SP.bootStartup + setOnCheckedChangeListener { _, isChecked -> + SP.bootStartup = isChecked + (activity as MainActivity).settingActive() + } } - updateManager = UpdateManager(context, this, versionCode) - _binding?.checkVersion?.setOnClickListener(OnClickListenerCheckVersion(updateManager)) + updateManager = UpdateManager(context, this, context.appVersionCode) + binding.checkVersion.setOnClickListener(OnClickListenerCheckVersion(updateManager)) return binding.root } diff --git a/app/src/main/java/com/lizongying/mytv/UpdateManager.kt b/app/src/main/java/com/lizongying/mytv/UpdateManager.kt index ac1b852..5616bdd 100644 --- a/app/src/main/java/com/lizongying/mytv/UpdateManager.kt +++ b/app/src/main/java/com/lizongying/mytv/UpdateManager.kt @@ -23,7 +23,7 @@ import java.io.File class UpdateManager( - private var context: Context?, + private var context: Context, private var settingFragment: SettingFragment, private var versionCode: Long ) : @@ -67,7 +67,7 @@ class UpdateManager( val apkFileName = "my-tv-${release.data.versionName}.apk" Log.i(TAG, "apkFileName $apkFileName") val downloadManager = - context!!.getSystemService(Context.DOWNLOAD_SERVICE) as 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) @@ -77,22 +77,22 @@ class UpdateManager( // 获取下载任务的引用 val downloadReference = downloadManager.enqueue(request) - downloadReceiver = DownloadReceiver(context!!, apkFileName, downloadReference) + downloadReceiver = DownloadReceiver(context, apkFileName, downloadReference) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - context!!.registerReceiver( + context.registerReceiver( downloadReceiver, IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE), Context.RECEIVER_NOT_EXPORTED, ) } else { - context!!.registerReceiver( + context.registerReceiver( downloadReceiver, IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE) ) } - getDownloadProgress(context!!, downloadReference) { progress -> + getDownloadProgress(context, downloadReference) { progress -> println("Download progress: $progress%") } } @@ -182,7 +182,7 @@ class UpdateManager( fun destroy() { if (downloadReceiver != null) { - context!!.unregisterReceiver(downloadReceiver) + context.unregisterReceiver(downloadReceiver) Log.i(TAG, "destroy downloadReceiver") } } diff --git a/app/src/main/java/com/lizongying/mytv/models/TVViewModel.kt b/app/src/main/java/com/lizongying/mytv/models/TVViewModel.kt index 00c8648..cb8eb20 100644 --- a/app/src/main/java/com/lizongying/mytv/models/TVViewModel.kt +++ b/app/src/main/java/com/lizongying/mytv/models/TVViewModel.kt @@ -22,10 +22,11 @@ class TVViewModel(private var tv: TV) : ViewModel() { private var itemPosition: Int = 0 var retryTimes = 0 - var tokenRetryTimes = 0 var retryMaxTimes = 8 - var tokenRetryMaxTimes = 2 - var programUpdateTime: Long = 0 + var tokenRetryTimes = 0 + var tokenRetryMaxTimes = 0 + var tokenFHRetryTimes = 0 + var tokenFHRetryMaxTimes = 2 private val _errInfo = MutableLiveData() val errInfo: LiveData diff --git a/app/src/main/res/raw/channels.json b/app/src/main/res/raw/channels.json index a19fc49..7b9f469 100644 --- a/app/src/main/res/raw/channels.json +++ b/app/src/main/res/raw/channels.json @@ -130,7 +130,7 @@ "programId": "600004078", "needToken": true, "mustToken": false, - "title": "CCTV9 记录", + "title": "CCTV9 纪录", "videoUrl": [ "http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226197/index.m3u8" ]