Compare commits

..
10 Commits
Author SHA1 Message Date
Li ZongYing dd4550cf4f replay on resume 2024-02-01 18:10:58 +08:00
Li ZongYing 4bdc625718 add channel 2024-01-26 18:30:31 +08:00
Li ZongYing cda19995b2 optimize channel switch 2024-01-25 15:12:47 +08:00
Li ZongYing 1eb2f257b5 change channel order 2024-01-25 11:04:06 +08:00
Li ZongYing 9feb0dc4bc channel num setting 2024-01-19 12:18:17 +08:00
Li ZongYing 46311036d6 number selection 2024-01-17 16:27:25 +08:00
Li ZongYing 1aade3a48d 17ok 2024-01-14 18:58:37 +08:00
Li ZongYing 2528cf33db test 17 2024-01-14 10:09:59 +08:00
Li ZongYing 4364f66ec5 17 2024-01-14 02:53:09 +08:00
Li ZongYing 49cd7b3af2 help page 2024-01-11 16:32:08 +08:00
17 changed files with 531 additions and 245 deletions
+9 -1
View File
@@ -1,6 +1,6 @@
# 我的电视
安卓电视直播软件,内置直播源
电视直播软件,安装即可使用
## 使用
@@ -14,6 +14,14 @@
## 更新日志
### v1.5.1(高版本专用)
* 性能优化
### v1.5.0(通用版)
* 修复部分情况下APP切换后无法继续播放的问题
### v1.4.9(高版本专用)
* 同步v1.4.8
+1 -1
View File
@@ -101,7 +101,7 @@ dependencies {
implementation "androidx.media3:media3-exoplayer-hls:$media3_version"
// 21:2.9.0 17:2.6.4
def retrofit2_version = "2.9.0"
def retrofit2_version = "2.6.4"
implementation 'com.google.protobuf:protobuf-kotlin:3.25.1'
implementation "com.squareup.retrofit2:converter-gson:$retrofit2_version"
+5 -3
View File
@@ -9,17 +9,19 @@
<uses-permission android:name="android.permission.INTERNET" />
<application
android:allowBackup="true"
android:largeHeap="true"
android:icon="@drawable/logo"
android:logo="@drawable/logo"
android:banner="@drawable/banner"
android:label="@string/app_name"
android:supportsRtl="true"
android:networkSecurityConfig="@xml/network"
android:usesCleartextTraffic="true"
android:theme="@style/Theme.MyTV">
<activity
android:keepScreenOn="true"
android:name=".MainActivity"
android:banner="@drawable/banner"
android:exported="true"
android:icon="@drawable/logo"
android:logo="@drawable/logo"
android:screenOrientation="landscape">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,119 @@
package com.lizongying.mytv
import android.graphics.Color
import android.view.ContextThemeWrapper
import android.view.View
import android.view.ViewGroup
import android.view.animation.ScaleAnimation
import android.widget.ImageView
import androidx.leanback.widget.ImageCardView
import androidx.lifecycle.LifecycleOwner
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.lizongying.mytv.models.TVListViewModel
import com.lizongying.mytv.models.TVViewModel
class CardAdapter(private val owner: LifecycleOwner, private var tvListViewModel: TVListViewModel) :
RecyclerView.Adapter<CardAdapter.ViewHolder>() {
private var listener: ItemListener? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val cardView = object :
ImageCardView(ContextThemeWrapper(parent.context, R.style.CustomImageCardTheme)) {}
startScaleAnimation(cardView, 1.0f, 0.9f)
cardView.isFocusable = true
cardView.isFocusableInTouchMode = true
return ViewHolder(cardView)
}
private fun startScaleAnimation(view: View, fromScale: Float, toScale: Float) {
val scaleAnimation = ScaleAnimation(
fromScale, toScale,
fromScale, toScale,
ScaleAnimation.RELATIVE_TO_SELF, 0.5f,
ScaleAnimation.RELATIVE_TO_SELF, 0.5f
)
scaleAnimation.duration = 200
scaleAnimation.fillAfter = true
view.startAnimation(scaleAnimation)
}
override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
val item = tvListViewModel.getTVViewModel(position)
val tvViewModel = item as TVViewModel
val cardView = viewHolder.view as ImageCardView
cardView.setOnFocusChangeListener { _, hasFocus ->
listener?.onItemFocusChange(item, hasFocus)
if (hasFocus) {
startScaleAnimation(cardView, 0.9f, 1.0f)
} else {
startScaleAnimation(cardView, 1.0f, 0.9f)
}
}
cardView.setOnClickListener { _ ->
listener?.onItemClicked(item)
}
cardView.titleText = tvViewModel.title.value
cardView.tag = tvViewModel.videoUrl.value
when (tvViewModel.title.value) {
"CCTV8K 超高清" -> Glide.with(viewHolder.view.context)
.load(R.drawable.cctv8k)
.centerInside()
.into(cardView.mainImageView)
"天津卫视" -> Glide.with(viewHolder.view.context)
.load(R.drawable.tianjin)
.centerInside()
.into(cardView.mainImageView)
"新疆卫视" -> Glide.with(viewHolder.view.context)
.load(R.drawable.xinjiang)
.centerInside()
.into(cardView.mainImageView)
else -> Glide.with(viewHolder.view.context)
.load(tvViewModel.logo.value)
.centerInside()
.into(cardView.mainImageView)
}
cardView.mainImageView.setBackgroundColor(Color.WHITE)
cardView.setMainImageScaleType(ImageView.ScaleType.CENTER_INSIDE)
tvViewModel.program.observe(owner) { _ ->
val program = tvViewModel.getProgramOne()
if (program != null) {
cardView.contentText = program.name
}
}
}
override fun getItemCount() = tvListViewModel.size()
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val view = itemView
}
interface ItemListener {
fun onItemFocusChange(tvViewModel: TVViewModel, hasFocus: Boolean)
fun onItemClicked(tvViewModel: TVViewModel)
}
fun setItemListener(listener: ItemListener) {
this.listener = listener
}
companion object {
private const val TAG = "CardAdapter"
}
}
@@ -35,16 +35,17 @@ class MainActivity : FragmentActivity() {
private lateinit var gestureDetector: GestureDetector
private val handler = Handler()
private val delay: Long = 4000
private val delayHideHelp: Long = 10000
private val delayHideMain: Long = 5000
private val delayHideSetting: Long = 10000
private lateinit var sharedPref: SharedPreferences
lateinit var sharedPref: SharedPreferences
private var channelReversal = false
private var channelNum = true
private var versionName = ""
override fun onCreate(savedInstanceState: Bundle?) {
Log.i(TAG, "onCreate")
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
@@ -60,7 +61,6 @@ class MainActivity : FragmentActivity() {
.add(R.id.main_browse_fragment, mainFragment)
.hide(mainFragment)
.commit()
mainFragment.view?.requestFocus()
}
gestureDetector = GestureDetector(this, GestureListener())
@@ -122,6 +122,7 @@ class MainActivity : FragmentActivity() {
val transaction = supportFragmentManager.beginTransaction()
if (mainFragment.isHidden) {
mainFragment.setPosition()
transaction.show(mainFragment)
keepRunnable()
} else {
@@ -132,11 +133,11 @@ class MainActivity : FragmentActivity() {
}
fun keepRunnable() {
handler.removeCallbacks(hideRunnable)
handler.postDelayed(hideRunnable, delay)
handler.removeCallbacks(hideMain)
handler.postDelayed(hideMain, delayHideMain)
}
private val hideRunnable = Runnable {
private val hideMain = Runnable {
if (!mainFragment.isHidden) {
supportFragmentManager.beginTransaction().hide(mainFragment).commit()
}
@@ -222,7 +223,7 @@ class MainActivity : FragmentActivity() {
this.channelNum = channelNum
}
private fun showHelp() {
private fun showSetting() {
if (!mainFragment.isHidden) {
return
}
@@ -230,15 +231,15 @@ class MainActivity : FragmentActivity() {
Log.i(TAG, "settingFragment ${settingFragment.isVisible}")
if (!settingFragment.isVisible) {
settingFragment.show(supportFragmentManager, "setting")
handler.removeCallbacks(hideHelp)
handler.postDelayed(hideHelp, delayHideHelp)
handler.removeCallbacks(hideSetting)
handler.postDelayed(hideSetting, delayHideSetting)
} else {
handler.removeCallbacks(hideHelp)
handler.removeCallbacks(hideSetting)
settingFragment.dismiss()
}
}
private val hideHelp = Runnable {
private val hideSetting = Runnable {
if (settingFragment.isVisible) {
settingFragment.dismiss()
}
@@ -359,27 +360,27 @@ class MainActivity : FragmentActivity() {
}
KeyEvent.KEYCODE_BOOKMARK -> {
showHelp()
showSetting()
return true
}
KeyEvent.KEYCODE_UNKNOWN -> {
showHelp()
showSetting()
return true
}
KeyEvent.KEYCODE_HELP -> {
showHelp()
showSetting()
return true
}
KeyEvent.KEYCODE_SETTINGS -> {
showHelp()
showSetting()
return true
}
KeyEvent.KEYCODE_MENU -> {
showHelp()
showSetting()
return true
}
@@ -477,7 +478,7 @@ class MainActivity : FragmentActivity() {
private fun hashSignature(signature: Signature): String {
return try {
val md = MessageDigest.getInstance("SHA-256")
val md = MessageDigest.getInstance("MD5")
md.update(signature.toByteArray())
val digest = md.digest()
digest.let { it -> it.joinToString("") { "%02x".format(it) } }
@@ -487,6 +488,22 @@ class MainActivity : FragmentActivity() {
}
}
override fun onStart() {
Log.i(TAG, "onStart")
super.onStart()
}
override fun onResume() {
Log.i(TAG, "onResume")
super.onResume()
}
override fun onPause() {
Log.i(TAG, "onPause")
super.onPause()
handler.removeCallbacks(hideMain)
}
companion object {
private const val TAG = "MainActivity"
private const val CHANNEL_REVERSAL = "channel_reversal"
@@ -1,41 +1,41 @@
package com.lizongying.mytv
import android.content.Context
import android.content.SharedPreferences
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.leanback.app.BrowseSupportFragment
import androidx.leanback.widget.ArrayObjectAdapter
import androidx.leanback.widget.HeaderItem
import androidx.leanback.widget.ListRow
import androidx.leanback.widget.ListRowPresenter
import androidx.leanback.widget.ListRowPresenter.SelectItemViewHolderTask
import androidx.leanback.widget.OnItemViewClickedListener
import androidx.leanback.widget.OnItemViewSelectedListener
import androidx.leanback.widget.Presenter
import androidx.leanback.widget.Row
import androidx.leanback.widget.RowPresenter
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.lizongying.mytv.Utils.dpToPx
import com.lizongying.mytv.Utils.getDateTimestamp
import com.lizongying.mytv.databinding.RowBinding
import com.lizongying.mytv.databinding.ShowBinding
import com.lizongying.mytv.models.TVListViewModel
import com.lizongying.mytv.models.TVViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class MainFragment : BrowseSupportFragment() {
class MainFragment : Fragment(), CardAdapter.ItemListener {
private var itemPosition = 0
private var rowsAdapter: ArrayObjectAdapter? = null
private var rowList: MutableList<View> = mutableListOf()
private var _binding: ShowBinding? = null
private val binding get() = _binding!!
private var request = Request()
var tvListViewModel = TVListViewModel()
private var sharedPref: SharedPreferences? = null
private lateinit var sharedPref: SharedPreferences
private var lastVideoUrl = ""
@@ -44,183 +44,154 @@ class MainFragment : BrowseSupportFragment() {
private var ready = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
headersState = HEADERS_DISABLED
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = ShowBinding.inflate(inflater, container, false)
return binding.root
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
activity?.let { request.initYSP(it) }
sharedPref = activity?.getPreferences(Context.MODE_PRIVATE)
sharedPref = (activity as? MainActivity)?.sharedPref!!
loadRows()
view?.post {
val content = binding.content
setupEventListeners()
var idx: Long = 0
for ((k, v) in TVList.list) {
val itemBinding: RowBinding =
RowBinding.inflate(layoutInflater, content, false)
mUpdateProgramRunnable = UpdateProgramRunnable()
handler.post(mUpdateProgramRunnable)
tvListViewModel.tvListViewModel.value?.forEach { tvViewModel ->
tvViewModel.errInfo.observe(viewLifecycleOwner) { _ ->
if (tvViewModel.errInfo.value != null
&& tvViewModel.id.value == itemPosition
) {
Toast.makeText(context, tvViewModel.errInfo.value, Toast.LENGTH_SHORT).show()
val tvListViewModelCurrent = TVListViewModel()
for ((idx2, v1) in v.withIndex()) {
val tvViewModel = TVViewModel(v1)
tvViewModel.setRowPosition(idx.toInt())
tvViewModel.setItemPosition(idx2)
tvListViewModelCurrent.addTVViewModel(tvViewModel)
tvListViewModel.addTVViewModel(tvViewModel)
}
}
tvViewModel.ready.observe(viewLifecycleOwner) { _ ->
tvListViewModel.maxNum.add(v.size)
// not first time && channel not change
if (tvViewModel.ready.value != null
&& tvViewModel.id.value == itemPosition
&& check(tvViewModel)
) {
Log.i(TAG, "ready ${tvViewModel.title.value}")
(activity as? MainActivity)?.play(tvViewModel)
}
val adapter = CardAdapter(viewLifecycleOwner, tvListViewModelCurrent)
rowList.add(itemBinding.rowItems)
adapter.setItemListener(this)
itemBinding.rowHeader.text = k
itemBinding.rowItems.adapter = adapter
itemBinding.rowItems.layoutManager =
LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
val layoutParams = itemBinding.row.layoutParams as ViewGroup.MarginLayoutParams
layoutParams.topMargin = dpToPx(11F)
itemBinding.row.layoutParams = layoutParams
content.addView(itemBinding.row)
idx++
}
tvViewModel.change.observe(viewLifecycleOwner) { _ ->
if (tvViewModel.change.value != null) {
val title = tvViewModel.title.value
Log.i(TAG, "switch $title")
if (tvViewModel.pid.value != "") {
Log.i(TAG, "request $title ${tvViewModel.pid.value}")
lifecycleScope.launch(Dispatchers.IO) {
tvViewModel.let { request.fetchData(it) }
}
(activity as? MainActivity)?.showInfoFragment(tvViewModel)
setSelectedPosition(
tvViewModel.getRowPosition(), true,
SelectItemViewHolderTask(tvViewModel.getItemPosition())
)
} else {
if (check(tvViewModel)) {
(activity as? MainActivity)?.play(tvViewModel)
mUpdateProgramRunnable = UpdateProgramRunnable()
handler.post(mUpdateProgramRunnable)
itemPosition = sharedPref.getInt(POSITION, 0)
if (itemPosition >= tvListViewModel.size()) {
itemPosition = 0
}
tvListViewModel.setItemPosition(itemPosition)
setPosition()
tvListViewModel.tvListViewModel.value?.forEach { tvViewModel ->
tvViewModel.errInfo.observe(viewLifecycleOwner) { _ ->
if (tvViewModel.errInfo.value != null
&& tvViewModel.id.value == itemPosition
) {
Toast.makeText(context, tvViewModel.errInfo.value, Toast.LENGTH_SHORT)
.show()
}
}
tvViewModel.ready.observe(viewLifecycleOwner) { _ ->
// not first time && channel not change
if (tvViewModel.ready.value != null
&& tvViewModel.id.value == itemPosition
&& check(tvViewModel)
) {
Log.i(TAG, "ready ${tvViewModel.title.value}")
(activity as? MainActivity)?.play(tvViewModel)
}
}
tvViewModel.change.observe(viewLifecycleOwner) { _ ->
if (tvViewModel.change.value != null) {
val title = tvViewModel.title.value
Log.i(TAG, "switch $title")
if (tvViewModel.pid.value != "") {
Log.i(TAG, "request $title ${tvViewModel.pid.value}")
lifecycleScope.launch(Dispatchers.IO) {
tvViewModel.let { request.fetchData(it) }
}
(activity as? MainActivity)?.showInfoFragment(tvViewModel)
setSelectedPosition(
tvViewModel.getRowPosition(), true,
SelectItemViewHolderTask(tvViewModel.getItemPosition())
setPosition(
tvViewModel.getRowPosition(), tvViewModel.getItemPosition()
)
} else {
if (check(tvViewModel)) {
(activity as? MainActivity)?.play(tvViewModel)
(activity as? MainActivity)?.showInfoFragment(tvViewModel)
setPosition(
tvViewModel.getRowPosition(), tvViewModel.getItemPosition()
)
}
}
}
}
}
}
fragmentReady()
}
fun toLastPosition() {
setSelectedPosition(
selectedPosition, false,
SelectItemViewHolderTask(tvListViewModel.maxNum[selectedPosition] - 1)
)
}
fun toFirstPosition() {
setSelectedPosition(
selectedPosition, false,
SelectItemViewHolderTask(0)
)
}
override fun startHeadersTransition(withHeaders: Boolean) {
}
private fun loadRows() {
rowsAdapter = ArrayObjectAdapter(ListRowPresenter())
val cardPresenter = CardPresenter(viewLifecycleOwner)
var idx: Long = 0
for ((k, v) in TVList.list) {
val listRowAdapter = ArrayObjectAdapter(cardPresenter)
for ((idx2, v1) in v.withIndex()) {
val tvViewModel = TVViewModel(v1)
tvViewModel.setRowPosition(idx.toInt())
tvViewModel.setItemPosition(idx2)
tvListViewModel.addTVViewModel(tvViewModel)
listRowAdapter.add(tvViewModel)
}
tvListViewModel.maxNum.add(v.size)
val header = HeaderItem(idx, k)
rowsAdapter!!.add(ListRow(header, listRowAdapter))
idx++
}
adapter = rowsAdapter
itemPosition = sharedPref?.getInt(POSITION, 0)!!
if (itemPosition >= tvListViewModel.size()) {
itemPosition = 0
}
tvListViewModel.setItemPosition(itemPosition)
}
fun prevSource() {
view?.post {
val tvViewModel = tvListViewModel.getTVViewModel(itemPosition)
if (tvViewModel != null) {
if (tvViewModel.videoUrl.value!!.size > 1) {
val videoIndex = tvViewModel.videoIndex.value?.minus(1)
if (videoIndex == -1) {
tvViewModel.setVideoIndex(tvViewModel.videoUrl.value!!.size - 1)
}
tvViewModel.changed()
}
}
fragmentReady()
}
}
fun nextSource() {
view?.post {
val tvViewModel = tvListViewModel.getTVViewModel(itemPosition)
if (tvViewModel != null) {
if (tvViewModel.videoUrl.value!!.size > 1) {
val videoIndex = tvViewModel.videoIndex.value?.plus(1)
if (videoIndex == tvViewModel.videoUrl.value!!.size) {
tvViewModel.setVideoIndex(0)
}
tvViewModel.changed()
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
}
override fun onItemFocusChange(tvViewModel: TVViewModel, hasFocus: Boolean) {
if (hasFocus) {
tvListViewModel.setItemPositionCurrent(tvViewModel.id.value!!)
(activity as MainActivity).keepRunnable()
}
}
private fun setupEventListeners() {
onItemViewClickedListener = ItemViewClickedListener()
onItemViewSelectedListener = ItemViewSelectedListener()
override fun onItemClicked(tvViewModel: TVViewModel) {
if (this.isHidden) {
(activity as? MainActivity)?.switchMainFragment()
return
}
if (itemPosition != tvViewModel.id.value!!) {
itemPosition = tvViewModel.id.value!!
tvListViewModel.setItemPosition(itemPosition)
tvListViewModel.getTVViewModel(itemPosition)?.changed()
}
(activity as? MainActivity)?.switchMainFragment()
}
private inner class ItemViewClickedListener : OnItemViewClickedListener {
override fun onItemClicked(
itemViewHolder: Presenter.ViewHolder,
item: Any,
rowViewHolder: RowPresenter.ViewHolder,
row: Row
) {
if (item is TVViewModel) {
if (itemPosition != item.id.value!!) {
itemPosition = item.id.value!!
tvListViewModel.setItemPosition(itemPosition)
tvListViewModel.getTVViewModel(itemPosition)?.changed()
}
(activity as? MainActivity)?.switchMainFragment()
}
fun setPosition() {
val tvViewModel = tvListViewModel.getTVViewModel(itemPosition)
rowList[tvViewModel!!.getRowPosition()].post {
((rowList[tvViewModel.getRowPosition()] as RecyclerView).layoutManager as LinearLayoutManager).findViewByPosition(
tvViewModel.getItemPosition()
)?.requestFocus()
}
}
private inner class ItemViewSelectedListener : OnItemViewSelectedListener {
override fun onItemSelected(
itemViewHolder: Presenter.ViewHolder?, item: Any?,
rowViewHolder: RowPresenter.ViewHolder, row: Row
) {
if (item is TVViewModel) {
tvListViewModel.setItemPositionCurrent(item.id.value!!)
(activity as MainActivity).keepRunnable()
}
fun setPosition(rowPosition: Int, itemPosition: Int) {
rowList[rowPosition].post {
((rowList[rowPosition] as RecyclerView).layoutManager as LinearLayoutManager).findViewByPosition(
itemPosition
)?.requestFocus()
}
}
@@ -310,14 +281,19 @@ class MainFragment : BrowseSupportFragment() {
}
}
override fun onResume() {
Log.i(TAG, "onResume")
super.onResume()
}
override fun onStop() {
Log.i(TAG, "onStop")
super.onStop()
with(sharedPref!!.edit()) {
with(sharedPref.edit()) {
putInt(POSITION, itemPosition)
apply()
}
Log.i(TAG, "POSITION saved")
Log.i(TAG, "$POSITION saved")
}
override fun onDestroy() {
@@ -326,9 +302,10 @@ class MainFragment : BrowseSupportFragment() {
handler.removeCallbacks(mUpdateProgramRunnable)
}
override fun onResume() {
super.onResume()
view?.post { view?.requestFocus() }
override fun onDestroyView() {
Log.i(TAG, "onDestroyView")
super.onDestroyView()
_binding = null
}
companion object {
@@ -74,23 +74,31 @@ class PlayerFragment : Fragment() {
@OptIn(UnstableApi::class)
fun play(tvViewModel: TVViewModel) {
this.tvViewModel = tvViewModel
val videoUrlCurrent = tvViewModel.getVideoUrlCurrent()
playerView?.player?.run {
setMediaItem(MediaItem.fromUri(videoUrlCurrent))
setMediaItem(MediaItem.fromUri(tvViewModel.getVideoUrlCurrent()))
prepare()
}
}
override fun onStart() {
Log.i(TAG, "onStart")
super.onStart()
if (playerView != null) {
playerView!!.player?.play()
if (playerView != null && playerView!!.player?.isPlaying == false) {
Log.i(TAG, "replay")
playerView!!.player?.prepare()
} else {
Log.i(TAG, "playing")
}
}
override fun onStop() {
super.onStop()
if (playerView != null) {
override fun onResume() {
Log.i(TAG, "onResume")
super.onResume()
}
override fun onPause() {
super.onPause()
if (playerView != null && playerView!!.player?.isPlaying == true) {
playerView!!.player?.stop()
}
}
@@ -32,10 +32,12 @@ class Request {
private var yspBtraceService: YSPBtraceService = ApiClient().yspBtraceService
private var yspProtoService: YSPProtoService = ApiClient().yspProtoService
private var ysp: YSP? = null
private var token = ""
// TODO onDestroy
private val handler = Handler(Looper.getMainLooper())
private lateinit var myRunnable: MyRunnable
private lateinit var btraceRunnable: BtraceRunnable
private var tokenRunnable: TokenRunnable = TokenRunnable()
private var mapping = mapOf(
"CCTV4K" to "CCTV4K 超高清",
@@ -85,6 +87,10 @@ class Request {
"新疆卫视" to "新疆卫视",
)
init {
handler.post(tokenRunnable)
}
fun initYSP(context: Context) {
ysp = YSP(context)
}
@@ -93,8 +99,8 @@ class Request {
fun fetchVideo(tvModel: TVViewModel, cookie: String) {
call?.cancel()
if (::myRunnable.isInitialized) {
handler.removeCallbacks(myRunnable)
if (::btraceRunnable.isInitialized) {
handler.removeCallbacks(btraceRunnable)
}
val title = tvModel.title.value
@@ -125,17 +131,22 @@ class Request {
keyBytes + byteArrayOf(0, 0, 0, 0, 0, 0, 0, 0),
ivBytes
).uppercase()
Log.d(TAG, "$title url $url")
// Log.d(TAG, "$title url $url")
tvModel.addVideoUrl(url)
tvModel.allReady()
tvModel.retryTimes = 0
myRunnable = MyRunnable(tvModel)
handler.post(myRunnable)
btraceRunnable = BtraceRunnable(tvModel)
handler.post(btraceRunnable)
} else {
Log.e(TAG, "$title key error")
if (tvModel.retryTimes < tvModel.retryMaxTimes) {
tvModel.retryTimes++
fetchVideo(tvModel, cookie)
if (tvModel.needToken) {
token = ""
fetchVideo(tvModel)
} else {
fetchVideo(tvModel, cookie)
}
}
}
} else {
@@ -146,7 +157,12 @@ class Request {
Log.e(TAG, "$title url error $request $liveInfo")
if (tvModel.retryTimes < tvModel.retryMaxTimes) {
tvModel.retryTimes++
fetchVideo(tvModel, cookie)
if (tvModel.needToken) {
token = ""
fetchVideo(tvModel)
} else {
fetchVideo(tvModel, cookie)
}
}
}
}
@@ -154,7 +170,12 @@ class Request {
Log.e(TAG, "$title status error")
if (tvModel.retryTimes < tvModel.retryMaxTimes) {
tvModel.retryTimes++
fetchVideo(tvModel, cookie)
if (tvModel.needToken) {
token = ""
fetchVideo(tvModel)
} else {
fetchVideo(tvModel, cookie)
}
}
}
}
@@ -163,39 +184,50 @@ class Request {
Log.e(TAG, "$title request error")
if (tvModel.retryTimes < tvModel.retryMaxTimes) {
tvModel.retryTimes++
fetchVideo(tvModel, cookie)
if (tvModel.needToken) {
token = ""
fetchVideo(tvModel)
} else {
fetchVideo(tvModel, cookie)
}
}
}
})
}
fun fetchVideo(tvModel: TVViewModel) {
yspTokenService.getInfo()
.enqueue(object : Callback<Info> {
override fun onResponse(call: Call<Info>, response: Response<Info>) {
if (response.isSuccessful) {
val token = response.body()?.data?.token
Log.i(TAG, "info success $token")
val cookie =
"vplatform=109; yspopenid=vu0-8lgGV2LW9QjDeuBFsX8yMnzs37Q3_HZF6XyVDpGR_I; vusession=$token"
fetchVideo(tvModel, cookie)
} else {
Log.e(TAG, "info status error")
if (token == "") {
yspTokenService.getInfo()
.enqueue(object : Callback<Info> {
override fun onResponse(call: Call<Info>, response: Response<Info>) {
if (response.isSuccessful) {
token = response.body()?.data?.token!!
Log.i(TAG, "info success $token")
val cookie =
"vplatform=109; yspopenid=vu0-8lgGV2LW9QjDeuBFsX8yMnzs37Q3_HZF6XyVDpGR_I; vusession=$token"
fetchVideo(tvModel, cookie)
} else {
Log.e(TAG, "info status error")
if (tvModel.retryTimes < tvModel.retryMaxTimes) {
tvModel.retryTimes++
fetchVideo(tvModel)
}
}
}
override fun onFailure(call: Call<Info>, t: Throwable) {
Log.e(TAG, "info request error $t")
if (tvModel.retryTimes < tvModel.retryMaxTimes) {
tvModel.retryTimes++
fetchVideo(tvModel)
}
}
}
override fun onFailure(call: Call<Info>, t: Throwable) {
Log.e(TAG, "info request error $t")
if (tvModel.retryTimes < tvModel.retryMaxTimes) {
tvModel.retryTimes++
fetchVideo(tvModel)
}
}
})
})
} else {
val cookie =
"vplatform=109; yspopenid=vu0-8lgGV2LW9QjDeuBFsX8yMnzs37Q3_HZF6XyVDpGR_I; vusession=$token"
fetchVideo(tvModel, cookie)
}
}
fun fetchData(tvModel: TVViewModel) {
@@ -207,7 +239,32 @@ class Request {
}
}
inner class MyRunnable(private val tvModel: TVViewModel) : Runnable {
inner class TokenRunnable : Runnable {
override fun run() {
fetchToken()
handler.postDelayed(this, 600000)
}
}
fun fetchToken() {
yspTokenService.getInfo()
.enqueue(object : Callback<Info> {
override fun onResponse(call: Call<Info>, response: Response<Info>) {
if (response.isSuccessful) {
token = response.body()?.data?.token!!
Log.i(TAG, "info success $token")
} else {
Log.e(TAG, "token status error")
}
}
override fun onFailure(call: Call<Info>, t: Throwable) {
Log.e(TAG, "token request error $t")
}
})
}
inner class BtraceRunnable(private val tvModel: TVViewModel) : Runnable {
override fun run() {
fetchBtrace(tvModel)
handler.postDelayed(this, 60000)
@@ -304,8 +361,10 @@ class Request {
val program = response.body()
if (program != null) {
tvViewModel.addProgram(program.dataListList)
Log.i(TAG, "$title program ${program.dataListList.size}")
Log.d(TAG, "$title program ${program.dataListList.size}")
}
} else {
Log.w(TAG, "$title program error")
}
}
@@ -277,9 +277,6 @@ CGTN 纪录频道,https://livedoc.cgtn.com/500d/prog_index.m3u8
}
if (!i.contains(",")) {
channel = i.trim()
if (channel == "移动专区") {
break
}
continue
}
val p = i.split(",")
@@ -2,6 +2,7 @@ package com.lizongying.mytv.api
import android.content.Context
import android.content.SharedPreferences
import android.util.Log
import com.lizongying.mytv.Encryptor
import com.lizongying.mytv.MainActivity
import com.lizongying.mytv.Utils.getDateTimestamp
@@ -51,14 +52,14 @@ class YSP(var context: Context) {
private var signature = ""
private var encryptor: Encryptor? = null
private var sharedPref: SharedPreferences? = null
private lateinit var sharedPref: SharedPreferences
init {
if (context is MainActivity) {
encryptor = Encryptor()
encryptor!!.init(context)
sharedPref = (context as MainActivity).getPreferences(Context.MODE_PRIVATE)
sharedPref = (context as MainActivity).sharedPref
}
guid = getGuid()
@@ -77,15 +78,10 @@ class YSP(var context: Context) {
timeStr = getTimeStr()
// guid = "lq3oqitm_1e15dnzgjnb"
// randStr = "BfcCPQp8Hq"
// timeStr = "1702166501"
cKey =
encryptor!!.encrypt(cnlid, timeStr, appVer, guid, platform)
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"}"""
// return """{"cnlid":"2000203803","livepid":"600001801","stream":"2","guid":"lq1y36mb_ccfwmja9zan","cKey":"--01A9F5E89BB86A0C61F4025BEDE15309B6913A79FD1AF1EE7F5EC9C7605F377D1D2281488385C32DEB9E7D0DD3559CB700BD7AF44DD5C9DE0AE14D94B8214027B5D664C108AEE23532348DCC61B86F7C8FBB6CF14D588E6093A25E97DF6D66F4882AB28F17016472DD43D45EF076B7F505176A5E8DEDF2662E5F9AB12B69CB20BCE1579BE724091F3AF6826AE34B713906F3FE139C3783F80EECBD08416DC525E1","adjust":1,"sphttps":"1","platform":"5910204","cmd":"2","encryptVer":"8.1","dtype":"1","devid":"devid","otype":"ojson","appVer":"V1.0.0","app_version":"V1.0.0","rand_str":"IOS2soOw44","channel":"ysp_tx","defn":"fhd","signature":"6ca945f651817de8c6e6910457ceafd6"}"""
}
private fun getTimeStr(): String {
@@ -99,10 +95,10 @@ class YSP(var context: Context) {
}
fun getGuid(): String {
var guid = sharedPref?.getString("guid", "")
var guid = sharedPref.getString("guid", "")
if (guid == null || guid.length < 18) {
guid = generateGuid()
with(sharedPref!!.edit()) {
with(sharedPref.edit()) {
putString("guid", guid)
apply()
}
@@ -112,7 +108,7 @@ class YSP(var context: Context) {
private fun newGuid(): String {
guid = generateGuid()
with(sharedPref!!.edit()) {
with(sharedPref.edit()) {
putString("guid", guid)
apply()
}
@@ -78,7 +78,20 @@ class TVViewModel(private var tv: TV) : ViewModel() {
var needToken = false
private val channelsNeedToken = arrayOf(
// "CCTV4K 超高清",
"CCTV4K 超高清",
"CCTV2 财经",
"CCTV5 体育",
"CCTV5+ 体育赛事",
"CCTV7 国防军事",
"CCTV9 记录",
"CCTV10 科教",
"CCTV11 戏曲",
"CCTV12 社会与法",
"CCTV14 少儿",
"CCTV15 音乐",
"CCTV16 奥林匹克",
"CCTV17 农业农村",
"CCTV3 综艺",
"CCTV6 电影",
"CCTV8 电视剧",
@@ -95,6 +108,30 @@ class TVViewModel(private var tv: TV) : ViewModel() {
"央视台球",
"电视指南",
"卫生健康",
"东方卫视",
"湖南卫视",
"湖北卫视",
"辽宁卫视",
"江苏卫视",
"江西卫视",
"山东卫视",
"广东卫视",
"广西卫视",
"重庆卫视",
"河南卫视",
"河北卫视",
"贵州卫视",
"北京卫视",
"黑龙江卫视",
"浙江卫视",
"安徽卫视",
"深圳卫视",
"四川卫视",
"东南卫视",
"海南卫视",
"天津卫视",
"新疆卫视",
)
fun addVideoUrl(url: String) {
+25
View File
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/row"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/row_header"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:layout_marginTop="0dp"
android:layout_marginStart="10dp"
android:layout_marginBottom="5dp"
android:gravity="start"
android:textColor="#FFEEEEEE"
android:textSize="22sp" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/row_items"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</androidx.recyclerview.widget.RecyclerView>
</LinearLayout>
+17
View File
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/show_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ScrollView
android:id="@+id/scroll_view"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
</LinearLayout>
</ScrollView>
</FrameLayout>
+16 -1
View File
@@ -1,10 +1,25 @@
<resources>
<style name="CustomImageCardViewStyle" parent="Widget.Leanback.ImageCardViewStyle">
<item name="lbImageCardViewType">Title|Content</item>
<item name="cardType">infoUnder</item>
<item name="infoVisibility">always</item>
<!-- In order to keep backward compatibility we have to create an icon on right. -->
<item name="lbImageCardViewType">Title|Content|IconOnRight</item>
<!-- Deprecated. Use 'Widget.Leanback.ImageCardView.InfoAreaStyle' instead. -->
<item name="infoAreaBackground">@null</item>
</style>
<style name="CustomImageCardViewImageStyle" parent="Widget.Leanback.ImageCardView.ImageStyle">
<item name="android:layout_width">143dp</item>
<item name="android:layout_height">50dp</item>
<item name="android:adjustViewBounds">true</item>
<item name="android:contentDescription">@null</item>
<item name="android:scaleType">centerCrop</item>
<item name="layout_viewType">main</item>
</style>
<style name="CustomImageCardTheme" parent="Theme.Leanback">
<item name="imageCardViewStyle">@style/CustomImageCardViewStyle</item>
<item name="imageCardViewImageStyle">@style/CustomImageCardViewImageStyle</item>
</style>
</resources>
+9
View File
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true">
<trust-anchors>
<certificates src="system" />
<certificates src="user" />
</trust-anchors>
</base-config>
</network-security-config>