Compare commits

...
12 Commits
Author SHA1 Message Date
Li ZongYing 51d9c4c2a7 fix gson crash 2024-05-26 20:16:10 +08:00
Li ZongYing 70da1d8241 fix the settings options are not displayed 2024-05-26 09:36:24 +08:00
Li ZongYing ef17427f64 add reset 2024-05-21 09:55:15 +08:00
Li ZongYing c1143afb4e fix ysp 2024-05-20 22:23:16 +08:00
Li ZongYing 68f158e556 fix ysp 2024-05-20 22:08:37 +08:00
Li ZongYing 4ef95d60c4 fix ysp 2024-05-20 22:02:51 +08:00
Li ZongYing b782d1f841 fix some bug 2024-05-16 15:35:48 +08:00
Li ZongYing 7e57aa8c03 fix error in playing 2024-05-16 14:37:16 +08:00
Li ZongYing c16a4bcb9c optimize time 2024-05-09 22:26:37 +08:00
Li ZongYing 0286d118ed fix update 2024-05-07 17:55:08 +08:00
Li ZongYing fc83defe31 optimize version check 2024-05-07 12:56:34 +08:00
Li ZongYing 6fc73efd47 optimize version check 2024-05-07 12:55:11 +08:00
41 changed files with 817 additions and 232 deletions
+24 -1
View File
@@ -37,6 +37,13 @@ jobs:
# override default build-tools version (29.0.3) -- optional
BUILD_TOOLS_VERSION: "34.0.0"
- name: Get History
id: get_history
run: |
chmod +x history.sh
output=$(./history.sh)
echo "$output" > history.md
- name: Create Release
id: create_release
uses: actions/create-release@v1
@@ -47,6 +54,7 @@ jobs:
release_name: Release ${{ github.ref }}
draft: false
prerelease: false
body_path: history.md
- name: Upload Release Asset
uses: actions/upload-release-asset@v1
@@ -56,4 +64,19 @@ jobs:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ${{ steps.sign_app.outputs.signedReleaseFile }}
asset_name: my-tv-${{ github.ref_name }}.apk
asset_content_type: application/vnd.android.package-archive
asset_content_type: application/vnd.android.package-archive
- name: Gitee Create Release
run: |
latest_commit=$(git rev-parse HEAD)
history=$(cat history.md)
curl -v POST https://gitee.com/api/v5/repos/${{ github.repository }}/releases \
-H "Content-Type: application/json" \
-d '{
"access_token": "${{ secrets.GITEE_ACCESS_TOKEN}}",
"tag_name": "${{ github.ref_name }}",
"name": "Release ${{ github.ref_name }}",
"body": "'"$history"'",
"prerelease": false,
"target_commitish": "'"$latest_commit"'"
}'
+32 -2
View File
@@ -1,5 +1,35 @@
## 更新日志
### v2.0.5
* 设置页增加恢复默认按钮,目前只用于同步系统时间
### v2.0.4
* 修复不能播放的问题
* 包含国际频道
### v2.0.3
* 修复不能播放的问题
* 不包含国际频道
### v2.0.0
* 解决卡顿问题
* 解决网格样式时,点击空白处不退出的问题
* 解决频道列表样式切换可能不生效的问题
### v1.9.8(通用)
* 优化在线升级
* 优化可能状态错误的问题
### v1.9.6(通用)
* 优化在线升级
* 优化可能状态错误的问题
### v1.9.4(通用)
* 修复默认时间不显示问题
@@ -36,6 +66,8 @@
* 修复无法播放问题
### 以下已不可用
### v1.8.0(通用)
* 修复返回键无法退出问题
@@ -171,8 +203,6 @@
* 修复播放失败的问题
### 以下已不可用
### v1.5.3(安卓5及以上专用)
* 修复部分情况下APP切换后无法继续播放的问题
+9 -1
View File
@@ -1,6 +1,6 @@
.PHONY: all
all: info
all: info gen-version
branch := $(shell git rev-parse --abbrev-ref HEAD)
commit := $(shell git rev-parse --short HEAD)
@@ -9,3 +9,11 @@ info:
@echo 'SHELL='$(SHELL)
@echo 'branch='$(branch)
@echo 'commit='$(commit)
gen-version:
git describe --tags --always
git describe --tags --always | sed 's/v/ /g' | sed 's/\./ /g' | sed 's/-/ /g' | awk '{print ($$1*16777216)+($$2*65536)+($$3*256)+$$4}'
#make gen v=v2.0.0
gen:
echo $(v) | sed 's/v/ /g' | sed 's/\./ /g' | sed 's/-/ /g' | awk '{print "{\"version_code\": " ($$1*16777216)+($$2*65536)+($$3*256)+$$4 ", \"version_name\": \"" "$(v)" "\"}"}' > version.json
+2 -2
View File
@@ -26,7 +26,7 @@
## TODO
* 音量不同
* 大湾区卫视、广东4k超高清、广东珠江、三沙卫视
* 大湾区卫视、广东4k超高清、广东珠江、三沙卫视、翡翠
* CHC高清三个电影频道
* 地方频道
* 收藏夹
@@ -35,7 +35,7 @@
* 亮度调节
* 音量调节
* 軟解
* 自動更新
* 网格空白处退出
## 版权说明
+10 -3
View File
@@ -77,10 +77,9 @@ static def VersionCode() {
def replace = [v: "", ".": " ", "-": " "]
def arr = (process.text.trim().replace(replace) + " 0").split(" ")
def versionCode = arr[0].toInteger() * 16777216 + arr[1].toInteger() * 65536 + arr[2].toInteger() * 256 + arr[3].toInteger()
println("VersionCode $versionCode")
return versionCode
} catch (ignored) {
return 0
return 1
}
}
@@ -88,13 +87,19 @@ static def VersionName() {
try {
def process = "git describe --tags --always".execute()
process.waitFor()
return process.text.trim() - "v"
def versionName = process.text.trim() - "v"
if (versionName.isEmpty()) {
versionName = "1.0.0"
}
return versionName
} catch (ignored) {
return "1.0.0"
}
}
dependencies {
// minSdk17
def media3_version = "1.2.1"
implementation "androidx.media3:media3-ui:$media3_version"
@@ -109,6 +114,8 @@ dependencies {
def retrofit2_version = "2.6.4"
implementation 'com.google.protobuf:protobuf-kotlin:3.25.1'
// Gson 2.10.1 and older: API level 19
implementation("com.google.code.gson:gson:2.10.1")
implementation "com.squareup.retrofit2:converter-gson:$retrofit2_version"
implementation "com.squareup.retrofit2:converter-protobuf:$retrofit2_version"
implementation "com.squareup.retrofit2:retrofit:$retrofit2_version"
+11 -1
View File
@@ -12,9 +12,10 @@
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<application
android:name=".MyTvApplication"
android:name=".MyTVApplication"
android:allowBackup="true"
android:banner="@drawable/banner"
android:icon="@drawable/logo"
@@ -63,5 +64,14 @@
android:exported="false" />
<meta-data android:name="android.max_aspect" android:value="2.5" />
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
</application>
</manifest>
Binary file not shown.
Binary file not shown.
@@ -34,7 +34,7 @@ class CardAdapter(
val inflater = LayoutInflater.from(parent.context)
val binding = CardBinding.inflate(inflater, parent, false)
val application = mainFragment.requireActivity().applicationContext as MyTvApplication
val application = mainFragment.requireActivity().applicationContext as MyTVApplication
binding.card.layoutParams.width = application.px2Px(binding.card.layoutParams.width)
binding.card.layoutParams.height = application.px2Px(binding.card.layoutParams.height)
@@ -28,7 +28,7 @@ class ChannelFragment : Fragment() {
_binding = ChannelBinding.inflate(inflater, container, false)
_binding!!.root.visibility = View.GONE
val application = requireActivity().applicationContext as MyTvApplication
val application = requireActivity().applicationContext as MyTVApplication
binding.channel.layoutParams.width = application.px2Px(binding.channel.layoutParams.width)
binding.channel.layoutParams.height = application.px2Px(binding.channel.layoutParams.height)
@@ -7,24 +7,33 @@ import androidx.fragment.app.DialogFragment
class ConfirmationFragment(
private val listener: ConfirmationListener,
private val message: String
private val message: String,
private val update: Boolean
) : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return activity?.let {
val builder = AlertDialog.Builder(it)
builder.setTitle("确定更新吗?")
.setMessage(message)
.setPositiveButton(
"确定"
) { _, _ ->
listener.onConfirm()
}
.setNegativeButton(
"取消"
) { _, _ ->
listener.onCancel()
}
builder.setTitle(message)
if (update) {
builder.setMessage("确定更新吗?")
.setPositiveButton(
"确定"
) { _, _ ->
listener.onConfirm()
}
.setNegativeButton(
"取消"
) { _, _ ->
listener.onCancel()
}
} else {
builder.setMessage("")
.setNegativeButton(
"好的"
) { _, _ ->
}
}
builder.create()
} ?: throw IllegalStateException("Activity cannot be null")
}
@@ -19,7 +19,7 @@ class ErrorFragment : Fragment() {
): View {
_binding = ErrorBinding.inflate(inflater, container, false)
val application = requireActivity().applicationContext as MyTvApplication
val application = requireActivity().applicationContext as MyTVApplication
binding.logo.layoutParams.width = application.px2Px(binding.logo.layoutParams.width)
binding.logo.layoutParams.height = application.px2Px(binding.logo.layoutParams.height)
@@ -29,7 +29,7 @@ class InfoFragment : Fragment() {
): View {
_binding = InfoBinding.inflate(inflater, container, false)
val application = requireActivity().applicationContext as MyTvApplication
val application = requireActivity().applicationContext as MyTVApplication
binding.info.layoutParams.width = application.px2Px(binding.info.layoutParams.width)
binding.info.layoutParams.height = application.px2Px(binding.info.layoutParams.height)
@@ -7,10 +7,10 @@ import androidx.recyclerview.widget.RecyclerView
class ItemDecoration(context: Context) : RecyclerView.ItemDecoration() {
private var application: MyTvApplication
private var application: MyTVApplication
init {
application = context.applicationContext as MyTvApplication
application = context.applicationContext as MyTVApplication
}
override fun getItemOffsets(
@@ -17,7 +17,7 @@ class LoadingFragment : Fragment() {
): View {
_binding = LoadingBinding.inflate(inflater, container, false)
val application = requireActivity().applicationContext as MyTvApplication
val application = requireActivity().applicationContext as MyTVApplication
binding.bar.layoutParams.width = application.px2Px(binding.bar.layoutParams.width)
binding.bar.layoutParams.height = application.px2Px(binding.bar.layoutParams.height)
@@ -8,6 +8,7 @@ import android.util.Log
import android.view.GestureDetector
import android.view.KeyEvent
import android.view.MotionEvent
import android.view.View
import android.view.View.SYSTEM_UI_FLAG_FULLSCREEN
import android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
import android.view.View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
@@ -25,7 +26,7 @@ import kotlinx.coroutines.async
import kotlinx.coroutines.launch
class MainActivity : FragmentActivity(), Request.RequestListener {
class MainActivity : FragmentActivity(), Request.RequestListener, OnSharedPreferenceChangeListener {
private var ready = 0
private val playerFragment = PlayerFragment()
@@ -44,13 +45,13 @@ class MainActivity : FragmentActivity(), Request.RequestListener {
private val handler = Handler()
private val delayHideMain: Long = 10000
private val delayHideSetting: Long = 10000
private val delayHideSetting: Long = 15000
init {
Utils.setRequestListener(this)
}
private fun syncTime() {
fun syncTime() {
lifecycleScope.launch(Dispatchers.IO) {
val utilsJob = async(start = CoroutineStart.LAZY) { Utils.init() }
utilsJob.start()
@@ -59,9 +60,6 @@ class MainActivity : FragmentActivity(), Request.RequestListener {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Request.setRequestListener(this)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
val lp = window.attributes
@@ -70,6 +68,17 @@ class MainActivity : FragmentActivity(), Request.RequestListener {
window.setAttributes(lp)
}
window.decorView.apply {
systemUiVisibility =
SYSTEM_UI_FLAG_FULLSCREEN or
SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
SYSTEM_UI_FLAG_HIDE_NAVIGATION or
SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
View.SYSTEM_UI_FLAG_IMMERSIVE
}
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
window.decorView.systemUiVisibility =
@@ -82,6 +91,10 @@ class MainActivity : FragmentActivity(), Request.RequestListener {
// window.navigationBarColor = Color.TRANSPARENT
// }
setContentView(R.layout.activity_main)
Request.setRequestListener(this)
if (savedInstanceState == null) {
val transaction = supportFragmentManager.beginTransaction()
.add(R.id.main_browse_fragment, playerFragment)
@@ -122,6 +135,8 @@ class MainActivity : FragmentActivity(), Request.RequestListener {
// Log.i(TAG, "net ${Build.VERSION.SDK_INT}")
// ready++
// }
SP.setOnSharedPreferenceChangeListener(this)
}
fun showInfoFragment(tvViewModel: TVViewModel) {
@@ -358,8 +373,6 @@ class MainActivity : FragmentActivity(), Request.RequestListener {
private val hideSetting = Runnable {
if (settingFragment.isVisible) {
settingFragment.dismiss()
showTime()
mainFragment.changeMenu()
}
}
@@ -499,7 +512,7 @@ class MainActivity : FragmentActivity(), Request.RequestListener {
}
KeyEvent.KEYCODE_UNKNOWN -> {
showSetting()
// showSetting()
return true
}
@@ -612,4 +625,12 @@ class MainActivity : FragmentActivity(), Request.RequestListener {
private companion object {
const val TAG = "MainActivity"
}
override fun onSharedPreferenceChanged(key: String) {
Log.i(TAG, "$key changed")
when (key) {
SP.KEY_GRID -> mainFragment.changeMenu()
SP.KEY_TIME -> showTime()
}
}
}
@@ -2,8 +2,10 @@ package com.lizongying.mytv
import android.os.Bundle
import android.util.Log
import android.view.GestureDetector
import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
@@ -40,15 +42,18 @@ class MainFragment : Fragment(), CardAdapter.ItemListener {
private var lastVideoUrl = ""
private lateinit var application: MyTvApplication
private lateinit var application: MyTVApplication
private lateinit var gestureDetector: GestureDetector
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val context = requireContext()
_binding = MenuBinding.inflate(inflater, container, false)
application = requireActivity().applicationContext as MyTvApplication
application = requireActivity().applicationContext as MyTVApplication
binding.menu.layoutParams.width = application.shouldWidthPx()
binding.menu.layoutParams.height = application.shouldHeightPx()
@@ -57,9 +62,19 @@ class MainFragment : Fragment(), CardAdapter.ItemListener {
hideSelf()
}
gestureDetector = GestureDetector(context, GestureListener())
return binding.root
}
private inner class GestureListener : GestureDetector.SimpleOnGestureListener() {
override fun onSingleTapConfirmed(e: MotionEvent): Boolean {
Log.i(TAG, "onSingleTapConfirmed")
hideSelf()
return true
}
}
private fun hideSelf() {
requireActivity().supportFragmentManager.beginTransaction()
.hide(this)
@@ -134,6 +149,21 @@ class MainFragment : Fragment(), CardAdapter.ItemListener {
hideSelf()
}
itemBinding.items.addOnItemTouchListener(object : RecyclerView.OnItemTouchListener {
override fun onInterceptTouchEvent(rv: RecyclerView, e: MotionEvent): Boolean {
gestureDetector.onTouchEvent(e)
return false
}
override fun onTouchEvent(rv: RecyclerView, e: MotionEvent) {
TODO("Not yet implemented")
}
override fun onRequestDisallowInterceptTouchEvent(disallowIntercept: Boolean) {
TODO("Not yet implemented")
}
})
val layoutParamsHeader =
itemBinding.header.layoutParams as ViewGroup.MarginLayoutParams
layoutParamsHeader.topMargin = application.px2Px(itemBinding.header.marginTop)
@@ -8,7 +8,7 @@ import android.view.WindowManager
import androidx.multidex.MultiDex
class MyTvApplication : Application() {
class MyTVApplication : Application() {
private lateinit var displayMetrics: DisplayMetrics
private lateinit var realDisplayMetrics: DisplayMetrics
@@ -0,0 +1,6 @@
package com.lizongying.mytv
interface OnSharedPreferenceChangeListener {
fun onSharedPreferenceChanged(key: String)
}
@@ -15,6 +15,7 @@ import androidx.media3.common.PlaybackException
import androidx.media3.common.Player
import androidx.media3.common.VideoSize
import androidx.media3.common.util.UnstableApi
import androidx.media3.exoplayer.DefaultRenderersFactory
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.ui.PlayerView
import com.google.android.exoplayer2.SimpleExoPlayer
@@ -52,10 +53,16 @@ class PlayerFragment : Fragment(), SurfaceHolder.Callback {
playerView?.viewTreeObserver?.addOnGlobalLayoutListener(object :
ViewTreeObserver.OnGlobalLayoutListener {
@OptIn(UnstableApi::class)
override fun onGlobalLayout() {
playerView!!.viewTreeObserver.removeOnGlobalLayoutListener(this)
// val renderersFactory = context?.let { DefaultRenderersFactory(it) }
// renderersFactory?.setExtensionRendererMode(DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON)
playerView!!.player = activity?.let {
ExoPlayer.Builder(it)
// .setRenderersFactory(renderersFactory!!)
.build()
}
playerView!!.player?.playWhenReady = true
+20 -4
View File
@@ -13,12 +13,12 @@ object SP {
// If use channel num to select channel or not
private const val KEY_CHANNEL_NUM = "channel_num"
private const val KEY_TIME = "time"
const val KEY_TIME = "time"
// If start app on device boot or not
private const val KEY_BOOT_STARTUP = "boot_startup"
private const val KEY_GRID = "grid"
const val KEY_GRID = "grid"
// Position in list of the selected channel item
private const val KEY_POSITION = "position"
@@ -28,6 +28,8 @@ object SP {
private lateinit var sp: SharedPreferences
private var listener: OnSharedPreferenceChangeListener? = null
/**
* The method must be invoked as early as possible(At least before using the keys)
*/
@@ -35,6 +37,10 @@ object SP {
sp = context.getSharedPreferences(SP_FILE_NAME, Context.MODE_PRIVATE)
}
fun setOnSharedPreferenceChangeListener(listener: OnSharedPreferenceChangeListener) {
this.listener = listener
}
var channelReversal: Boolean
get() = sp.getBoolean(KEY_CHANNEL_REVERSAL, false)
set(value) = sp.edit().putBoolean(KEY_CHANNEL_REVERSAL, value).apply()
@@ -45,7 +51,12 @@ object SP {
var time: Boolean
get() = sp.getBoolean(KEY_TIME, true)
set(value) = sp.edit().putBoolean(KEY_TIME, value).apply()
set(value) {
if (value != this.time) {
sp.edit().putBoolean(KEY_TIME, value).apply()
listener?.onSharedPreferenceChanged(KEY_TIME)
}
}
var bootStartup: Boolean
get() = sp.getBoolean(KEY_BOOT_STARTUP, false)
@@ -53,7 +64,12 @@ object SP {
var grid: Boolean
get() = sp.getBoolean(KEY_GRID, false)
set(value) = sp.edit().putBoolean(KEY_GRID, value).apply()
set(value) {
if (value != this.grid) {
sp.edit().putBoolean(KEY_GRID, value).apply()
listener?.onSharedPreferenceChanged(KEY_GRID)
}
}
var itemPosition: Int
get() = sp.getInt(KEY_POSITION, 0)
@@ -1,14 +1,20 @@
package com.lizongying.mytv
import android.Manifest
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.widget.Toast
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.core.view.marginEnd
import androidx.core.view.marginTop
import androidx.fragment.app.DialogFragment
import com.lizongying.mytv.api.YSP
import com.lizongying.mytv.databinding.SettingBinding
@@ -87,15 +93,19 @@ class SettingFragment : DialogFragment() {
}
}
updateManager = UpdateManager(context, this, context.appVersionCode)
binding.checkVersion.setOnClickListener(
OnClickListenerCheckVersion(
activity as MainActivity,
updateManager
)
)
binding.checkVersion.setOnClickListener {
(activity as MainActivity).settingDelayHide()
requestInstallPermissions()
}
val application = requireActivity().applicationContext as MyTvApplication
binding.clear.setOnClickListener {
(requireActivity() as MainActivity).syncTime()
SP.guid = ""
YSP.getGuid()
}
val application = requireActivity().applicationContext as MyTVApplication
val textSize = application.px2PxFont(binding.switchChannelReversal.textSize)
binding.content.layoutParams.width =
application.px2Px(binding.content.layoutParams.width)
@@ -106,51 +116,41 @@ class SettingFragment : DialogFragment() {
application.px2Px(binding.content.paddingBottom)
)
binding.name.textSize = application.px2PxFont(binding.name.textSize)
binding.version.textSize = application.px2PxFont(binding.version.textSize)
binding.version.textSize = textSize
val layoutParamsVersion = binding.version.layoutParams as ViewGroup.MarginLayoutParams
layoutParamsVersion.topMargin = application.px2Px(binding.version.marginTop)
binding.version.layoutParams = layoutParamsVersion
binding.checkVersion.textSize = application.px2PxFont(binding.checkVersion.textSize)
binding.checkVersion.textSize = textSize
val layoutParamsCheckVersion =
binding.checkVersion.layoutParams as ViewGroup.MarginLayoutParams
layoutParamsCheckVersion.marginEnd = application.px2Px(binding.checkVersion.marginEnd)
binding.checkVersion.layoutParams = layoutParamsCheckVersion
binding.versionName.textSize = application.px2PxFont(binding.versionName.textSize)
binding.versionName.textSize = textSize
binding.exit.textSize = application.px2PxFont(binding.exit.textSize)
binding.clear.textSize = textSize
binding.exit.textSize = textSize
binding.switchChannelReversal.textSize =
application.px2PxFont(binding.switchChannelReversal.textSize)
val layoutParamsChannelReversal =
val layoutParamsChannelSwitch =
binding.switchChannelReversal.layoutParams as ViewGroup.MarginLayoutParams
layoutParamsChannelReversal.topMargin =
layoutParamsChannelSwitch.topMargin =
application.px2Px(binding.switchChannelReversal.marginTop)
binding.switchChannelReversal.layoutParams = layoutParamsChannelReversal
binding.switchChannelNum.textSize = application.px2PxFont(binding.switchChannelNum.textSize)
val layoutParamsChannelNum =
binding.switchChannelNum.layoutParams as ViewGroup.MarginLayoutParams
layoutParamsChannelNum.topMargin = application.px2Px(binding.switchChannelNum.marginTop)
binding.switchChannelNum.layoutParams = layoutParamsChannelNum
binding.switchChannelReversal.textSize = textSize
binding.switchChannelReversal.layoutParams = layoutParamsChannelSwitch
binding.switchTime.textSize = application.px2PxFont(binding.switchTime.textSize)
val layoutParamsTime = binding.switchTime.layoutParams as ViewGroup.MarginLayoutParams
layoutParamsTime.topMargin = application.px2Px(binding.switchTime.marginTop)
binding.switchTime.layoutParams = layoutParamsTime
binding.switchChannelNum.textSize = textSize
binding.switchChannelNum.layoutParams = layoutParamsChannelSwitch
binding.switchBootStartup.textSize =
application.px2PxFont(binding.switchBootStartup.textSize)
val layoutParamsBootStartup =
binding.switchBootStartup.layoutParams as ViewGroup.MarginLayoutParams
layoutParamsBootStartup.topMargin = application.px2Px(binding.switchBootStartup.marginTop)
binding.switchBootStartup.layoutParams = layoutParamsBootStartup
binding.switchTime.textSize = textSize
binding.switchTime.layoutParams = layoutParamsChannelSwitch
binding.switchGrid.textSize = application.px2PxFont(binding.switchGrid.textSize)
val layoutParamsGrid = binding.switchGrid.layoutParams as ViewGroup.MarginLayoutParams
layoutParamsGrid.topMargin = application.px2Px(binding.switchGrid.marginTop)
binding.switchGrid.layoutParams = layoutParamsGrid
binding.switchBootStartup.textSize = textSize
binding.switchBootStartup.layoutParams = layoutParamsChannelSwitch
binding.switchGrid.textSize = textSize
binding.switchGrid.layoutParams = layoutParamsChannelSwitch
binding.appreciate.layoutParams.width =
application.px2Px(binding.appreciate.layoutParams.width)
@@ -159,23 +159,67 @@ class SettingFragment : DialogFragment() {
requireActivity().finishAffinity()
}
updateManager = UpdateManager(context, context.appVersionCode)
return binding.root
}
fun setVersionName(versionName: String) {
if (_binding != null) {
binding.versionName.text = versionName
private fun requestInstallPermissions() {
val context = requireContext()
val permissionsList: MutableList<String> = ArrayList()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && !context.packageManager.canRequestPackageInstalls()) {
permissionsList.add(Manifest.permission.REQUEST_INSTALL_PACKAGES)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
ContextCompat.checkSelfPermission(
context,
Manifest.permission.READ_EXTERNAL_STORAGE
) != PackageManager.PERMISSION_GRANTED
) {
permissionsList.add(Manifest.permission.READ_EXTERNAL_STORAGE)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
ContextCompat.checkSelfPermission(
context,
Manifest.permission.WRITE_EXTERNAL_STORAGE
) != PackageManager.PERMISSION_GRANTED
) {
permissionsList.add(Manifest.permission.WRITE_EXTERNAL_STORAGE)
}
if (permissionsList.isNotEmpty()) {
ActivityCompat.requestPermissions(
requireActivity(),
permissionsList.toTypedArray<String>(),
PERMISSIONS_REQUEST_CODE
)
} else {
updateManager.checkAndUpdate()
}
}
internal class OnClickListenerCheckVersion(
private val mainActivity: MainActivity,
private val updateManager: UpdateManager
) :
View.OnClickListener {
override fun onClick(view: View?) {
mainActivity.settingDelayHide()
updateManager.checkAndUpdate()
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == PERMISSIONS_REQUEST_CODE) {
var allPermissionsGranted = true
for (result in grantResults) {
if (result != PackageManager.PERMISSION_GRANTED) {
allPermissionsGranted = false
break
}
}
if (allPermissionsGranted) {
updateManager.checkAndUpdate()
} else {
Toast.makeText(context, "权限授权失败", Toast.LENGTH_LONG).show()
}
}
}
@@ -186,6 +230,7 @@ class SettingFragment : DialogFragment() {
companion object {
const val TAG = "SettingFragment"
const val PERMISSIONS_REQUEST_CODE = 1
}
}
@@ -24,7 +24,7 @@ class TimeFragment : Fragment() {
): View {
_binding = TimeBinding.inflate(inflater, container, false)
val application = requireActivity().applicationContext as MyTvApplication
val application = requireActivity().applicationContext as MyTVApplication
binding.time.layoutParams.width = application.px2Px(binding.time.layoutParams.width)
binding.time.layoutParams.height = application.px2Px(binding.time.layoutParams.height)
@@ -1,6 +1,5 @@
package com.lizongying.mytv
import android.app.Activity
import android.app.DownloadManager
import android.app.DownloadManager.Request
import android.content.BroadcastReceiver
@@ -14,11 +13,10 @@ import android.os.Environment
import android.os.Handler
import android.os.Looper
import android.util.Log
import androidx.core.app.ActivityCompat
import androidx.core.content.PermissionChecker
import androidx.core.content.PermissionChecker.checkSelfPermission
import com.lizongying.mytv.api.ReleaseV2
import androidx.fragment.app.FragmentActivity
import com.lizongying.mytv.api.ApiClient
import com.lizongying.mytv.requests.ReleaseRequest
import com.lizongying.mytv.requests.ReleaseResponse
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -27,81 +25,61 @@ import java.io.File
class UpdateManager(
private var context: Context,
private var settingFragment: SettingFragment,
private var versionCode: Long
) :
ConfirmationFragment.ConfirmationListener {
private var myRequest = ReleaseRequest()
private var release: ReleaseV2? = null
private var releaseRequest = ReleaseRequest()
private var release: ReleaseResponse? = null
private var downloadReceiver: DownloadReceiver? = null
fun checkAndUpdate() {
if (!haveStoragePermission()) {
return
}
CoroutineScope(Dispatchers.Main).launch {
var text = "版本获取失败"
var update = false
try {
release = myRequest.getRelease()
Log.i(TAG, "versionCode $versionCode ${release?.c}")
if (release?.c != null) {
text = if (release?.c!! > versionCode) {
"最新版本:${release?.n}\n${release?.d ?: ""}"
release = releaseRequest.getRelease()
Log.i(TAG, "versionCode $versionCode ${release?.version_code}")
if (release?.version_code != null) {
if (release?.version_code!! > versionCode) {
text = "最新版本:${release?.version_name}"
update = true
} else {
"已是最新版本,不需要更新"
text = "已是最新版本,不需要更新"
}
}
} catch (e: Exception) {
Log.e(TAG, "Error occurred: ${e.message}", e)
}
updateUI(text)
updateUI(text, update)
}
}
private fun updateUI(text: String) {
val dialog = ConfirmationFragment(this@UpdateManager, text)
settingFragment.fragmentManager?.let { dialog.show(it, TAG) }
private fun updateUI(text: String, update: Boolean) {
val dialog = ConfirmationFragment(this@UpdateManager, text, update)
dialog.show((context as FragmentActivity).supportFragmentManager, TAG)
}
private fun haveStoragePermission(): Boolean {
if (Build.VERSION.SDK_INT >= 23) {
if (checkSelfPermission(context, android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
=== PermissionChecker.PERMISSION_GRANTED
) {
Log.e("Permission error", "You have permission")
return true
} else {
Log.e("Permission error", "You have asked for permission")
ActivityCompat.requestPermissions(
context as Activity, arrayOf(
android.Manifest.permission.WRITE_EXTERNAL_STORAGE
), 1
)
return false
}
} else { //you don't need to worry about these stuff below api level 23
Log.e("Permission error", "You already have the permission")
return true
}
}
private fun startDownload(release: ReleaseV2) {
val apkFileName = "my-tv-${release.n}.apk"
Log.i(TAG, "apkFileName $apkFileName")
private fun startDownload(release: ReleaseResponse) {
val apkName = "my-tv"
val apkFileName = "$apkName-${release.version_name}.apk"
val downloadManager =
context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
val request = Request(Uri.parse(release.u))
Log.i(TAG, "url ${Uri.parse(release.u)}")
val request =
Request(Uri.parse("${ApiClient.DOWNLOAD_HOST}${release.version_name}/$apkName-${release.version_name}.apk"))
Log.i(
TAG,
"url ${Uri.parse("${ApiClient.DOWNLOAD_HOST}${release.version_name}/$apkName-${release.version_name}.apk")}"
)
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)?.mkdirs()
Log.i(TAG, "save dir ${Environment.DIRECTORY_DOWNLOADS}")
request.setDestinationInExternalFilesDir(
context,
Environment.DIRECTORY_DOWNLOADS,
apkFileName
)
request.setTitle("${settingFragment.resources.getString(R.string.app_name)} ${release.n}")
request.setTitle("${context.resources.getString(R.string.app_name)} ${release.version_name}")
request.setNotificationVisibility(Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
request.setAllowedOverRoaming(false)
request.setMimeType("application/vnd.android.package-archive")
@@ -179,27 +157,73 @@ class UpdateManager(
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()
val downloadManager =
context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
val query = DownloadManager.Query().setFilterById(downloadReference)
val cursor = downloadManager.query(query)
if (cursor != null && cursor.moveToFirst()) {
val statusIndex = cursor.getColumnIndex(DownloadManager.COLUMN_STATUS)
if (statusIndex < 0) {
Log.i(TAG, "Download failure")
return
}
val status = cursor.getInt(statusIndex)
val progressIndex =
cursor.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR)
if (progressIndex < 0) {
Log.i(TAG, "Download failure")
return
}
val progress = cursor.getInt(progressIndex)
val totalSizeIndex =
cursor.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES)
val totalSize = cursor.getInt(totalSizeIndex)
cursor.close()
when (status) {
DownloadManager.STATUS_SUCCESSFUL -> {
installNewVersion()
}
DownloadManager.STATUS_FAILED -> {
// Handle download failure
Log.i(TAG, "Download failure")
}
else -> {
// Update UI with download progress
val percentage = progress * 100 / totalSize
Log.i(TAG, "Download progress: $percentage%")
}
}
}
}
}
private fun installNewVersion() {
val installIntent = Intent(Intent.ACTION_VIEW)
val apkUri = Uri.fromFile(
File(
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
apkFileName
)
val apkFile = File(
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
apkFileName
)
installIntent.setDataAndType(apkUri, "application/vnd.android.package-archive")
installIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
context.startActivity(installIntent)
Log.i(TAG, "apkFile $apkFile")
if (apkFile.exists()) {
val apkUri = Uri.parse("file://$apkFile")
Log.i(TAG, "apkUri $apkUri")
val installIntent = Intent(Intent.ACTION_VIEW).apply {
setDataAndType(apkUri, "application/vnd.android.package-archive")
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_ACTIVITY_NEW_TASK)
}
context.startActivity(installIntent)
} else {
Log.e(TAG, "APK file does not exist!")
}
}
}
+22 -6
View File
@@ -8,6 +8,8 @@ import com.lizongying.mytv.api.TimeResponse
import com.lizongying.mytv.requests.Request
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import okhttp3.Interceptor
import okhttp3.Response
import java.io.IOException
import java.text.SimpleDateFormat
import java.util.Date
@@ -34,19 +36,34 @@ object Utils {
}
suspend fun init() {
var currentTimeMillis: Long = 0
try {
currentTimeMillis = getTimestampFromServer()
val currentTimeMillis = getTimestampFromServer()
if (currentTimeMillis > 0) {
between = System.currentTimeMillis() - currentTimeMillis
}
} catch (e: Exception) {
println("Failed to retrieve timestamp from server: ${e.message}")
}
between = System.currentTimeMillis() - currentTimeMillis
withContext(Dispatchers.Main) {
listener?.onRequestFinished(null)
}
}
class RetryInterceptor(private val maxRetry: Int) : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
var response = chain.proceed(request)
var tryCount = 0
while (!response.isSuccessful && tryCount < maxRetry) {
tryCount++
response = chain.proceed(request)
}
return response
}
}
/**
* 从服务器获取时间戳
* @return Long 时间戳
@@ -54,8 +71,8 @@ object Utils {
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()
.addInterceptor(RetryInterceptor(3))
.build()
val request = okhttp3.Request.Builder()
.url("https://api.m.taobao.com/rest/api3.do?api=mtop.common.getTimestamp")
.build()
@@ -66,7 +83,6 @@ object Utils {
Gson().fromJson(string, TimeResponse::class.java).data.t.toLong()
}
} catch (e: IOException) {
// Handle network errors
throw IOException("Error during network request", e)
}
}
@@ -4,6 +4,7 @@ package com.lizongying.mytv.api
import android.os.Build
import android.util.Log
import com.lizongying.mytv.jce.JceConverterFactory
import com.lizongying.mytv.requests.ReleaseService
import okhttp3.ConnectionSpec
import okhttp3.OkHttpClient
import okhttp3.TlsVersion
@@ -23,6 +24,7 @@ class ApiClient {
private val protoUrl = "https://capi.yangshipin.cn/"
private val traceUrl = "https://btrace.yangshipin.cn/"
private val trace2Url = "https://aatc-api.yangshipin.cn/"
private val tokenUrl = "https://h5access.yangshipin.cn/"
private val trace3Url = "https://dtrace.ysp.cctv.cn/"
private val jceUrl = "https://jacc.ysp.cctv.cn/"
private val fUrl = "https://m.fengshows.com/"
@@ -47,7 +49,7 @@ class ApiClient {
val releaseService: ReleaseService by lazy {
Retrofit.Builder()
.baseUrl(myUrl)
.baseUrl(HOST)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.build().create(ReleaseService::class.java)
@@ -77,6 +79,14 @@ class ApiClient {
.build().create(YSPBtraceService::class.java)
}
val yspToken2Service: YSPApiService by lazy {
Retrofit.Builder()
.baseUrl(tokenUrl)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.build().create(YSPApiService::class.java)
}
val yspBtraceService3: YSPBtraceService by lazy {
Retrofit.Builder()
.baseUrl(trace3Url)
@@ -156,7 +166,7 @@ class ApiClient {
val sslContext = SSLContext.getInstance("SSL")
sslContext.init(null, trustAllCerts, java.security.SecureRandom())
val proxy = Proxy(Proxy.Type.HTTP, InetSocketAddress("10.0.2.2", 8888))
// val proxy = Proxy(Proxy.Type.HTTP, InetSocketAddress("10.0.2.2", 8888))
val builder = OkHttpClient.Builder()
.sslSocketFactory(sslContext.socketFactory, trustAllCerts[0] as X509TrustManager)
@@ -170,4 +180,9 @@ class ApiClient {
throw RuntimeException(e)
}
}
companion object {
const val HOST = "https://gitee.com/lizongying/my-tv/raw/"
const val DOWNLOAD_HOST = "https://gitee.com/lizongying/my-tv/releases/download/"
}
}
@@ -1,17 +1,17 @@
package com.lizongying.mytv.api
data class KvcollectRequest(
val BossId: Int = 2727,
val Pwd: Int = 1424084450,
val BossId: Int = 99150,
val Pwd: Int = 1999332929,
val cdn: String = "waibao",
val cmd: Int = 263,
val defn: String = "fhd",
val downspeed: Double = 10.0,
val downspeed: Int = 10,
val durl: String = "", //
val errcode: String = "",
val fact1: String = "ysp_pc_live_b",
val firstreport: Int = 0,
val fplayerver: String = "100",
val fplayerver: String = "180",
val ftime: String = "", //
val geturltime: Int = 0,
val guid: String = "", //
@@ -39,17 +39,17 @@ data class KvcollectRequest(
data class KvcollectRequest2(
val BossId: Int = 2727,
val Pwd: Int = 1424084450,
val BossId: Int = 99150,
val Pwd: Int = 1999332929,
val cdn: String = "waibao",
val cmd: Int = 263,
val defn: String = "fhd",
val downspeed: String = "10",
val downspeed: Int = 10,
val durl: String = "", //
val errcode: String = "",
val fact1: String = "ysp_pc_live_b",
val firstreport: Int = 0,
val fplayerver: String = "100",
val fplayerver: String = "180",
val ftime: String = "", //
val geturltime: Int = 0,
val guid: String = "", //
@@ -68,9 +68,9 @@ data class KvcollectRequest2(
val sRef: String = "",
val sUrl: String = "", //
val sdtfrom: String = "ysp_pc_01",
val seq: Int = 9, //
val seq: Int = 0, //
var signature: String = "",
val url: String = "", //
val viewid: String = "", //
val _dc: Double = 0.0,
val _dc: String = "",
)
@@ -1,12 +0,0 @@
package com.lizongying.mytv.api
import retrofit2.Call
import retrofit2.http.GET
import retrofit2.http.Path
interface ReleaseService {
@GET("my-tv/v2/release/{name}")
fun getRelease(
@Path("name") date: String = "2",
): Call<ReleaseV2>
}
@@ -0,0 +1,11 @@
package com.lizongying.mytv.api
data class Token2(
val ret: Int?,
val msg: String?,
val data: Data,
) {
data class Data(
val token: String,
)
}
@@ -4,6 +4,7 @@ import android.content.Context
import com.lizongying.mytv.SP
import com.lizongying.mytv.Utils.getDateTimestamp
import com.lizongying.mytv.models.TVViewModel
import java.security.MessageDigest
import kotlin.math.floor
import kotlin.random.Random
@@ -52,6 +53,13 @@ object YSP {
private var appid = "ysp_pc"
var token = ""
var token2 = ""
var yspsdkinput = ""
var openapi_signature = ""
var nseqId = 1
var nrequest_id = ""
private var encryptor = Encryptor()
@@ -75,10 +83,43 @@ object YSP {
cKey =
encryptor.encrypt(cnlid, timeStr, appVer, guid, platform)
randStr = getRand()
signature = getSignature()
return """{"cnlid":"$cnlid","livepid":"$livepid","stream":"$stream","guid":"$guid","cKey":"$cKey","adjust":$adjust,"sphttps":"$sphttps","platform":"$platform","cmd":"$cmd","encryptVer":"$encryptVer","dtype":"$dtype","devid":"$devid","otype":"$otype","appVer":"$appVer","app_version":"$appVersion","rand_str":"$randStr","channel":"$channel","defn":"$defn","signature":"$signature"}"""
}
fun getTokenData(tvModel: TVViewModel) {
livepid = tvModel.getTV().pid
cnlid = tvModel.getTV().sid
defn = "fhd"
randStr = getRand()
if (tvModel.retryTimes > 0) {
guid = newGuid()
}
cKey =
encryptor.encrypt(cnlid, timeStr, appVer, guid, platform)
yspsdkinput =
md("adjust=$adjust&app_version=$appVersion&appVer=$appVer&channel=$channel&cKey=$cKey&cmd=$cmd&cnlid=$cnlid&defn=$defn&devid=$devid&dtype=$dtype&encryptVer=$encryptVer&guid=$guid&livepid=$livepid&otype=$otype&platform=$platform&sphttps=$sphttps&stream=$stream")
nseqId++
nrequest_id = "999999" + getRand() + getTimeStr()
openapi_signature =
md("yspappid:519748109;host:www.yangshipin.cn;protocol:https:;token:$token2;input:$yspsdkinput-$guid-$nseqId-$nrequest_id;")
}
fun md(str: String): String {
val md = MessageDigest.getInstance("MD5")
md.update(str.toByteArray())
val digest = md.digest()
return digest.let { it -> it.joinToString("") { "%02x".format(it) } }
}
fun getAuthData(tvModel: TVViewModel): String {
livepid = tvModel.getTV().pid
@@ -92,7 +133,7 @@ object YSP {
return """pid=$livepid&guid=$guid&appid=$appid&rand_str=$randStr&signature=$signature"""
}
private fun getTimeStr(): String {
fun getTimeStr(): String {
return getDateTimestamp().toString()
}
@@ -106,6 +147,7 @@ object YSP {
var guid = SP.guid
if (guid.length < 18) {
guid = generateGuid()
this.guid = guid
SP.guid = guid
}
return guid
@@ -2,9 +2,11 @@ package com.lizongying.mytv.api
import retrofit2.Call
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.Header
import retrofit2.http.Headers
import retrofit2.http.POST
import retrofit2.http.Query
interface YSPApiService {
@@ -18,6 +20,11 @@ interface YSPApiService {
fun getLiveInfo(
@Header("cookie") cookie: String,
@Header("Yspplayertoken") token: String,
@Header("Yspsdkinput") Yspsdkinput: String,
@Header("yspsdksign") yspsdksign: String,
@Header("Seqid") Seqid: String,
@Header("Request-Id") RequestId: String,
@Header("Yspappid") Yspappid: String="519748109",
@Body request: LiveInfoRequest,
): Call<LiveInfo>
@@ -32,4 +39,20 @@ interface YSPApiService {
@Header("cookie") cookie: String,
@Body request: AuthRequest,
): Call<Auth>
@GET("web/open/token")
@Headers(
"content-type: application/json",
"referer: https://www.yangshipin.cn/",
"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 getToken(
@Header("cookie") cookie: String,
@Query("yspappid") yspappid: String = "519748109",
@Query("guid") guid: String = "",
@Query("vappid") vappid: String = "59306155",
@Query("vsecret") vsecret: String = "b42702bf7309a179d102f3d51b1add2fda0bc7ada64cb801",
@Query("raw") raw: String = "1",
@Query("ts") ts: String = "",
): Call<Token2>
}
@@ -87,8 +87,8 @@ interface YSPBtraceService {
"referer: https://www.yangshipin.cn/",
)
fun kvcollect3(
@Field("BossId") BossId: String = "9141",
@Field("Pwd") Pwd: String = "1137850982",
@Field("BossId") BossId: String = "9150",
@Field("Pwd") Pwd: String = "1999332929",
@Field("prog") prog: String = "", //
@Field("playno") playno: String = "", //
@Field("guid") guid: String = "", //
@@ -102,7 +102,7 @@ interface YSPBtraceService {
@Field("firstreport") firstreport: String = "-",
@Field("sUrl") sUrl: String = "", //
@Field("sRef") sRef: String = "-",
@Field("fplayerver") fplayerver: String = "100",
@Field("fplayerver") fplayerver: String = "180",
@Field("livepid") livepid: String = "",
@Field("viewid") viewid: String = "",
@Field("seq") seq: String = "",
@@ -118,8 +118,8 @@ interface YSPBtraceService {
@Field("_dc") _dc: String = "",
@Field("live_type") live_type: String = "-",
@Field("ftime") ftime: String = "",
@Field("url") url: String = "",
@Field("rand_str") rand_str: String = "", //
@Field("signature") signature: String = "",
// @Field("url") url: String = "",
// @Field("rand_str") rand_str: String = "", //
// @Field("signature") signature: String = "",
): Call<Void>
}
@@ -13,17 +13,20 @@ import kotlin.coroutines.suspendCoroutine
class ReleaseRequest {
private var releaseService = ApiClient().releaseService
suspend fun getRelease(): ReleaseV2? {
suspend fun getRelease(): ReleaseResponse? {
return withContext(Dispatchers.IO) {
fetchRelease()
}
}
private suspend fun fetchRelease(): ReleaseV2? {
private suspend fun fetchRelease(): ReleaseResponse? {
return suspendCoroutine { continuation ->
releaseService.getRelease()
.enqueue(object : Callback<ReleaseV2> {
override fun onResponse(call: Call<ReleaseV2>, response: Response<ReleaseV2>) {
.enqueue(object : Callback<ReleaseResponse> {
override fun onResponse(
call: Call<ReleaseResponse>,
response: Response<ReleaseResponse>
) {
if (response.isSuccessful) {
continuation.resume(response.body())
} else {
@@ -31,7 +34,7 @@ class ReleaseRequest {
}
}
override fun onFailure(call: Call<ReleaseV2>, t: Throwable) {
override fun onFailure(call: Call<ReleaseResponse>, t: Throwable) {
continuation.resume(null)
}
})
@@ -0,0 +1,7 @@
package com.lizongying.mytv.requests
data class ReleaseResponse(
val version_code: Int?,
val version_name: String?,
)
@@ -0,0 +1,10 @@
package com.lizongying.mytv.requests
import retrofit2.Call
import retrofit2.http.GET
interface ReleaseService {
@GET("JELLY_BEAN_MR1/version.json")
fun getRelease(
): Call<ReleaseResponse>
}
@@ -18,6 +18,7 @@ import com.lizongying.mytv.api.KvcollectRequest
import com.lizongying.mytv.api.KvcollectRequest2
import com.lizongying.mytv.api.LiveInfo
import com.lizongying.mytv.api.LiveInfoRequest
import com.lizongying.mytv.api.Token2
import com.lizongying.mytv.api.YSP
import com.lizongying.mytv.api.YSPApiService
import com.lizongying.mytv.api.YSPBtraceService
@@ -42,6 +43,7 @@ import kotlin.random.Random
object Request {
private const val TAG = "Request"
private var yspTokenService: YSPTokenService = ApiClient().yspTokenService
private var yspToken2Service: YSPApiService = ApiClient().yspToken2Service
private var yspApiService: YSPApiService = ApiClient().yspApiService
private var yspBtraceService: YSPBtraceService = ApiClient().yspBtraceService
private var yspBtraceService2: YSPBtraceService = ApiClient().yspBtraceService2
@@ -56,6 +58,7 @@ object Request {
private var needAuth = true
private var needToken = false
private var needToken2 = true
private val handler = Handler(Looper.getMainLooper())
private lateinit var btraceRunnable: BtraceRunnable
@@ -74,6 +77,7 @@ object Request {
}
private var call: Call<LiveInfo>? = null
private var callToken: Call<Token2>? = null
private var callAuth: Call<Auth>? = null
private var callInfo: Call<Info>? = null
private var callFAuth: Call<FAuth>? = null
@@ -84,11 +88,117 @@ object Request {
private fun cancelCall() {
call?.cancel()
callAuth?.cancel()
callToken?.cancel()
callInfo?.cancel()
callFAuth?.cancel()
callPage?.cancel()
}
private fun fetchToken(tvModel: TVViewModel, cookie: String) {
cancelCall()
val title = tvModel.getTV().title
callToken = yspToken2Service.getToken("guid=${YSP.getGuid()}; $cookie", "519748109", YSP.getGuid(), "59306155", "b42702bf7309a179d102f3d51b1add2fda0bc7ada64cb801" ,"1", YSP.getTimeStr())
callToken?.enqueue(object : Callback<Token2> {
override fun onResponse(call: Call<Token2>, response: Response<Token2>) {
if (response.isSuccessful) {
val liveInfo = response.body()
if (liveInfo?.data?.token != null) {
Log.i(TAG, "token ${liveInfo.data.token}")
YSP.token2 = liveInfo.data.token
YSP.getTokenData(tvModel)
fetchAuth(tvModel, cookie)
} else {
Log.e(TAG, "$title token error")
if (tvModel.retryTimes < tvModel.retryMaxTimes) {
tvModel.retryTimes++
if (tvModel.getTV().needToken) {
if (needToken && tvModel.tokenYSPRetryTimes < tvModel.tokenYSPRetryMaxTimes) {
tvModel.tokenYSPRetryTimes++
tvModel.needGetToken = true
fetchToken(tvModel)
} else {
if (!tvModel.getTV().mustToken) {
fetchToken(tvModel, cookie)
} else {
val err = "错误"
Log.e(TAG, "$title $err")
tvModel.setErrInfo(err)
}
}
} else {
fetchToken(tvModel, cookie)
}
} else {
val err = "认证结果错误"
Log.e(TAG, "$title $err")
tvModel.setErrInfo(err)
}
}
} else {
Log.e(TAG, "$title token status error")
if (tvModel.retryTimes < tvModel.retryMaxTimes) {
tvModel.retryTimes++
if (tvModel.getTV().needToken) {
if (needToken && tvModel.tokenYSPRetryTimes < tvModel.tokenYSPRetryMaxTimes) {
tvModel.tokenYSPRetryTimes++
tvModel.needGetToken = true
fetchToken(tvModel)
} else {
if (!tvModel.getTV().mustToken) {
fetchToken(tvModel, cookie)
} else {
val err = "错误"
Log.e(TAG, "$title $err")
tvModel.setErrInfo(err)
}
}
} else {
fetchToken(tvModel, cookie)
}
} else {
val err = "认证状态错误"
Log.e(TAG, "$title $err")
tvModel.setErrInfo(err)
}
}
}
override fun onFailure(call: Call<Token2>, t: Throwable) {
Log.e(TAG, "$title token request error $t")
if (tvModel.retryTimes < tvModel.retryMaxTimes) {
tvModel.retryTimes++
if (tvModel.getTV().needToken) {
if (needToken && tvModel.tokenYSPRetryTimes < tvModel.tokenYSPRetryMaxTimes) {
tvModel.tokenYSPRetryTimes++
tvModel.needGetToken = true
fetchToken(tvModel)
} else {
if (!tvModel.getTV().mustToken) {
fetchToken(tvModel, cookie)
} else {
val err = "错误"
Log.e(TAG, "$title $err")
tvModel.setErrInfo(err)
}
}
} else {
fetchToken(tvModel, cookie)
}
} else {
val err = "认证请求错误"
Log.e(TAG, "$title $err")
tvModel.setErrInfo(err)
}
}
})
}
private fun fetchAuth(tvModel: TVViewModel, cookie: String) {
cancelCall()
@@ -103,11 +213,11 @@ object Request {
val liveInfo = response.body()
if (liveInfo?.data?.token != null) {
Log.i(TAG, "token ${liveInfo.data.token}")
Log.i(TAG, "auth ${liveInfo.data.token}")
YSP.token = liveInfo.data.token
fetchVideo(tvModel, cookie)
} else {
Log.e(TAG, "$title token error")
Log.e(TAG, "$title auth error")
if (tvModel.retryTimes < tvModel.retryMaxTimes) {
tvModel.retryTimes++
if (tvModel.getTV().needToken) {
@@ -206,8 +316,13 @@ object Request {
val request = LiveInfoRequest(data)
call = request.let {
yspApiService.getLiveInfo(
"guid=${YSP.getGuid()}; $cookie",
"guid=${YSP.getGuid()}; gr_user_id=261eecf2-0ce4-4316-a6a1-7171e3de7389; $cookie; newLogin=1; nseqId=${YSP.nseqId}; nrequest-id=${YSP.nrequest_id}",
YSP.token,
YSP.yspsdkinput,
YSP.openapi_signature,
"${YSP.nseqId}",
YSP.nrequest_id,
"519748109",
it
)
}
@@ -392,6 +507,66 @@ object Request {
})
}
private fun fetchToken(tvModel: TVViewModel) {
cancelCall()
if (tvModel.needGetToken) {
callInfo = yspTokenService.getInfo("")
callInfo?.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")
tvModel.needGetToken = false
tvModel.tokenYSPRetryTimes = 0
val cookie =
"versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; deviceModel=124; appid=1400421205; yspappid=519748109;yspopenid=$openid; vusession=$token"
fetchAuth(tvModel, cookie)
} else if (response.code() == 304) {
tvModel.needGetToken = false
tvModel.tokenYSPRetryTimes = 0
val cookie =
"versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; deviceModel=124; appid=1400421205; yspappid=519748109; yspopenid=$openid; vusession=$token"
fetchVideo(tvModel, cookie)
} else {
Log.e(TAG, "info status error")
if (tvModel.tokenYSPRetryTimes < tvModel.tokenYSPRetryMaxTimes) {
tvModel.tokenYSPRetryTimes++
fetchToken(tvModel)
} else {
if (!tvModel.getTV().mustToken) {
val cookie =
"versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; deviceModel=124; appid=1400421205; yspappid=519748109"
fetchToken(tvModel, cookie)
} else {
// TODO
}
}
}
}
override fun onFailure(call: Call<Info>, t: Throwable) {
Log.e(TAG, "info request error $t")
if (tvModel.tokenYSPRetryTimes < tvModel.tokenYSPRetryMaxTimes) {
tvModel.tokenYSPRetryTimes++
fetchAuth(tvModel)
} else {
if (!tvModel.getTV().mustToken) {
val cookie =
"versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; deviceModel=124; appid=1400421205; yspappid=519748109"
fetchToken(tvModel, cookie)
} else {
// TODO
}
}
}
})
} else {
val cookie =
"versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; deviceModel=124; appid=1400421205; yspappid=519748109;yspopenid=$openid; vusession=$token"
fetchToken(tvModel, cookie)
}
}
private fun fetchAuth(tvModel: TVViewModel) {
cancelCall()
if (tvModel.needGetToken) {
@@ -447,7 +622,7 @@ object Request {
})
} else {
val cookie =
"versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; deviceModel=120; appid=1400421205; yspappid=519748109;yspopenid=$openid; vusession=$token"
"versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; deviceModel=124; yspopenid=$openid; vusession=$token"
fetchAuth(tvModel, cookie)
}
}
@@ -569,19 +744,42 @@ object Request {
if (token == "") {
tvModel.needGetToken = true
}
if (needAuth) {
fetchAuth(tvModel)
if (needToken2) {
if (needAuth) {
fetchToken(tvModel)
} else {
fetchAuth(tvModel)
}
} else {
fetchVideo(tvModel)
if (needAuth) {
fetchAuth(tvModel)
} else {
fetchVideo(tvModel)
}
}
} else {
val cookie =
"versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; updateProtocol=1; deviceModel=120"
if (needAuth) {
fetchAuth(tvModel, cookie)
"versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; deviceModel=124"
if (needToken2) {
if (needAuth) {
fetchToken(tvModel, cookie)
} else {
fetchAuth(tvModel, cookie)
}
} else {
fetchVideo(tvModel, cookie)
if (needAuth) {
fetchAuth(tvModel, cookie)
} else {
fetchVideo(tvModel, cookie)
}
}
//
// if (needAuth) {
// fetchAuth(tvModel, cookie)
// } else {
// fetchVideo(tvModel, cookie)
// }
}
}
@@ -700,7 +898,7 @@ object Request {
seq = tvModel.seq,
durl = url,
url = url,
_dc = randomNumber,
// _dc = "",
)
val e =
@@ -732,7 +930,7 @@ object Request {
val guid = YSP.getGuid()
val pid = tvModel.getTV().pid
val sid = tvModel.getTV().sid
val randomNumber = Random.nextDouble()
// val randomNumber = Random.nextDouble()
val url = tvModel.getTV().videoUrl.first()
val r = KvcollectRequest2(
@@ -747,12 +945,12 @@ object Request {
seq = tvModel.seq,
durl = url,
url = url,
_dc = randomNumber,
_dc = "",
)
val e =
"BossId=${r.BossId}&Pwd=${r.Pwd}&_dc=${r._dc}&cdn=${r.cdn}&cmd=${r.cmd}&defn=${r.defn}&downspeed=${r.downspeed}&durl=${r.durl}&errcode=${r.errcode}&fact1=${r.fact1}&firstreport=${r.firstreport}&fplayerver=${r.fplayerver}&ftime=${r.ftime}&geturltime=${r.geturltime}&guid=${r.guid}&hc_openid=${r.hc_openid}&hh_ua=${r.hh_ua}&live_type=${r.live_type}&livepid=${r.livepid}&login_type=${r.login_type}&open_id=${r.open_id}&openid=${r.openid}&platform=${r.platform}&playno=${r.playno}&prd=${r.prd}&prog=${r.prog}&rand_str=${r.rand_str}&sRef=${r.sRef}&sUrl=${r.sUrl}&sdtfrom=${r.sdtfrom}&seq=${r.seq}&url=${r.url}&viewid=${r.viewid}"
r.signature = YSP.getAuthSignature(e)
// val e =
// "BossId=${r.BossId}&Pwd=${r.Pwd}&_dc=${r._dc}&cdn=${r.cdn}&cmd=${r.cmd}&defn=${r.defn}&downspeed=${r.downspeed}&durl=${r.durl}&errcode=${r.errcode}&fact1=${r.fact1}&firstreport=${r.firstreport}&fplayerver=${r.fplayerver}&ftime=${r.ftime}&geturltime=${r.geturltime}&guid=${r.guid}&hc_openid=${r.hc_openid}&hh_ua=${r.hh_ua}&live_type=${r.live_type}&livepid=${r.livepid}&login_type=${r.login_type}&open_id=${r.open_id}&openid=${r.openid}&platform=${r.platform}&playno=${r.playno}&prd=${r.prd}&prog=${r.prog}&rand_str=${r.rand_str}&sRef=${r.sRef}&sUrl=${r.sUrl}&sdtfrom=${r.sdtfrom}&seq=${r.seq}&url=${r.url}&viewid=${r.viewid}"
// r.signature = YSP.getAuthSignature(e)
callBtracePage = yspBtraceService3.kvcollect3(
guid = r.guid,
@@ -761,18 +959,18 @@ object Request {
livepid = r.livepid,
sUrl = r.sUrl,
playno = r.playno,
rand_str = r.rand_str,
// rand_str = r.rand_str,
ftime = r.ftime,
seq = "${r.seq}",
durl = r.durl,
url = r.url,
// url = r.url,
_dc = "${r._dc}",
signature = r.signature
// signature = r.signature
)
callBtracePage?.enqueue(object : Callback<Void> {
override fun onResponse(call: Call<Void>, response: Response<Void>) {
if (response.isSuccessful) {
Log.d(TAG, "$title kvcollect success")
Log.i(TAG, "$title kvcollect success")
} else {
Log.e(TAG, "$title kvcollect status error")
}
+22 -11
View File
@@ -1,18 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/setting"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<LinearLayout
<ScrollView
android:id="@+id/container"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
android:layout_height="match_parent"
android:fillViewport="true"
>
<LinearLayout
android:id="@+id/content"
android:layout_width="300dp"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingTop="20dp"
android:paddingBottom="20dp"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:background="@drawable/rounded_dark_left"
@@ -55,6 +59,13 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
<Button
android:id="@+id/clear"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="14sp"
android:text="@string/clear"
/>
<Button
android:id="@+id/exit"
android:layout_width="wrap_content"
@@ -98,11 +109,11 @@
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
<ImageView
android:id="@+id/appreciate"
android:layout_width="300dp"
android:layout_height="wrap_content"
android:src="@drawable/appreciate"
android:background="@drawable/rounded_white_right" />
</LinearLayout>
</FrameLayout>
</ScrollView>
<ImageView
android:id="@+id/appreciate"
android:layout_width="300dp"
android:layout_height="wrap_content"
android:src="@drawable/appreciate"
android:background="@drawable/rounded_white_right" />
</LinearLayout>
+1
View File
@@ -6,5 +6,6 @@
<string name="title_boot_startup">开机自启</string>
<string name="title_grid">频道列表采用网格样式</string>
<string name="title_time">显示时间</string>
<string name="clear">恢复默认</string>
<string name="exit">退出应用</string>
</resources>
+3
View File
@@ -0,0 +1,3 @@
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="external_files" path="." />
</paths>
Executable
+20
View File
@@ -0,0 +1,20 @@
#!/bin/bash
in_changelog=false
while IFS= read -r line; do
if [[ "$line" == "## "* ]]; then
continue
fi
if [[ $in_changelog == false ]] && [[ "$line" == "### "* ]]; then
in_changelog=true
continue
fi
if [[ $in_changelog == true ]] && [[ "$line" == "### "* ]]; then
break
fi
echo "$line"
done < HISTORY.md
+1
View File
@@ -0,0 +1 @@
{"version_code": 33555712, "version_name": "v2.0.5"}