Compare commits

..
9 Commits
Author SHA1 Message Date
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
22 changed files with 550 additions and 326 deletions
+15
View File
@@ -14,6 +14,21 @@
## 更新日志 ## 更新日志
### v1.4.8(通用版)
* 频道号从1开始,cctv5+为18
* 提高cctv6清晰度
* 增加天津卫视、新疆卫视
### v1.4.7(高版本专用)
* 修复部分用户cctv13播放过程中卡住的问题
* 调整cctv的频道顺序
### v1.4.6(通用版)
* 10以下频道不再需要先按0
### v1.4.5(高版本专用) ### v1.4.5(高版本专用)
* 数字选台配置 * 数字选台配置
+1 -1
View File
@@ -101,7 +101,7 @@ dependencies {
implementation "androidx.media3:media3-exoplayer-hls:$media3_version" implementation "androidx.media3:media3-exoplayer-hls:$media3_version"
// 21:2.9.0 17:2.6.4 // 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.google.protobuf:protobuf-kotlin:3.25.1'
implementation "com.squareup.retrofit2:converter-gson:$retrofit2_version" implementation "com.squareup.retrofit2:converter-gson:$retrofit2_version"
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"
}
}
@@ -31,23 +31,31 @@ class CardPresenter(
cardView.setMainImageDimensions(CARD_WIDTH, CARD_HEIGHT) cardView.setMainImageDimensions(CARD_WIDTH, CARD_HEIGHT)
cardView.tag = tvViewModel.videoUrl.value cardView.tag = tvViewModel.videoUrl.value
if (tvViewModel.logo.value != null) { when (tvViewModel.title.value) {
if (tvViewModel.title.value == "CCTV8K 超高清") { "CCTV8K 超高清" -> Glide.with(viewHolder.view.context)
Glide.with(viewHolder.view.context) .load(R.drawable.cctv8k)
.load(R.drawable.cctv8k) .centerInside()
.centerInside() .into(cardView.mainImageView)
.into(cardView.mainImageView)
} else {
Glide.with(viewHolder.view.context)
.load(tvViewModel.logo.value)
.centerInside()
.into(cardView.mainImageView)
}
cardView.setBackgroundColor(Color.WHITE) "天津卫视" -> Glide.with(viewHolder.view.context)
cardView.setMainImageScaleType(ImageView.ScaleType.CENTER_INSIDE) .load(R.drawable.tianjin)
.centerInside()
.into(cardView.mainImageView)
"新疆卫视" -> Glide.with(viewHolder.view.context)
.load(R.drawable.xinjiang)
.centerInside()
.into(cardView.mainImageView)
else -> Glide.with(viewHolder.view.context)
.load(tvViewModel.logo.value)
.centerInside()
.into(cardView.mainImageView)
} }
cardView.setBackgroundColor(Color.WHITE)
cardView.setMainImageScaleType(ImageView.ScaleType.CENTER_INSIDE)
tvViewModel.program.observe(owner) { _ -> tvViewModel.program.observe(owner) { _ ->
val program = tvViewModel.getProgramOne() val program = tvViewModel.getProgramOne()
if (program != null) { if (program != null) {
@@ -2,7 +2,6 @@ package com.lizongying.mytv
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@@ -16,49 +15,52 @@ class ChannelFragment : Fragment() {
private val handler = Handler() private val handler = Handler()
private val delay: Long = 3000 private val delay: Long = 3000
private var channel = 0
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View { ): View {
_binding = ChannelBinding.inflate(inflater, container, false) _binding = ChannelBinding.inflate(inflater, container, false)
_binding!!.root.visibility = View.GONE
(activity as MainActivity).fragmentReady() (activity as MainActivity).fragmentReady()
return binding.root return binding.root
} }
fun show(tvViewModel: TVViewModel) { fun show(tvViewModel: TVViewModel) {
binding.channelContent.text = tvViewModel.id.value.toString() handler.removeCallbacks(hideRunnable)
handler.removeCallbacks(removeRunnable) handler.removeCallbacks(playRunnable)
binding.channelContent.text = (tvViewModel.id.value?.plus(1)).toString()
view?.visibility = View.VISIBLE view?.visibility = View.VISIBLE
handler.postDelayed(removeRunnable, delay) handler.postDelayed(hideRunnable, delay)
} }
fun show(channel: String) { fun show(channel: String) {
this.channel = "${binding.channelContent.text}$channel".toInt()
handler.removeCallbacks(hideRunnable)
handler.removeCallbacks(playRunnable)
if (binding.channelContent.text == "") { if (binding.channelContent.text == "") {
binding.channelContent.text = channel binding.channelContent.text = channel
handler.removeCallbacks(removeRunnable)
view?.visibility = View.VISIBLE view?.visibility = View.VISIBLE
handler.postDelayed(removeRunnable, delay) handler.postDelayed(playRunnable, delay)
} else { } else {
val ch = "${binding.channelContent.text}$channel".toInt() handler.postDelayed(playRunnable, 0)
Log.i(TAG, "channel $ch")
(activity as MainActivity).play(ch)
binding.channelContent.text = ""
view?.visibility = View.GONE
} }
} }
override fun onResume() {
super.onResume()
handler.postDelayed(removeRunnable, delay)
}
override fun onPause() { override fun onPause() {
super.onPause() super.onPause()
handler.removeCallbacks(removeRunnable) handler.removeCallbacks(hideRunnable)
handler.removeCallbacks(playRunnable)
} }
private val removeRunnable = Runnable { private val hideRunnable = Runnable {
binding.channelContent.text = ""
view?.visibility = View.GONE
}
private val playRunnable = Runnable {
(activity as MainActivity).play(channel - 1)
binding.channelContent.text = "" binding.channelContent.text = ""
view?.visibility = View.GONE view?.visibility = View.GONE
} }
@@ -2,7 +2,6 @@ package com.lizongying.mytv
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@@ -23,29 +22,33 @@ class InfoFragment : Fragment() {
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View { ): View {
_binding = InfoBinding.inflate(inflater, container, false) _binding = InfoBinding.inflate(inflater, container, false)
_binding!!.root.visibility = View.GONE
(activity as MainActivity).fragmentReady() (activity as MainActivity).fragmentReady()
return binding.root return binding.root
} }
override fun onResume() {
super.onResume()
// Use a Handler to delay the fragment transaction
handler.postDelayed(removeRunnable, delay)
}
fun show(tvViewModel: TVViewModel) { fun show(tvViewModel: TVViewModel) {
binding.textView.text = tvViewModel.title.value binding.textView.text = tvViewModel.title.value
if (tvViewModel.title.value == "CCTV8K 超高清") {
Glide.with(this) when (tvViewModel.title.value) {
"CCTV8K 超高清" -> Glide.with(this)
.load(R.drawable.cctv8k) .load(R.drawable.cctv8k)
.into(binding.infoLogo) .into(binding.infoLogo)
} else {
Glide.with(this) "天津卫视" -> Glide.with(this)
.load(R.drawable.tianjin)
.into(binding.infoLogo)
"新疆卫视" -> Glide.with(this)
.load(R.drawable.xinjiang)
.into(binding.infoLogo)
else -> Glide.with(this)
.load(tvViewModel.logo.value) .load(tvViewModel.logo.value)
.into(binding.infoLogo) .into(binding.infoLogo)
} }
val program = tvViewModel.getProgramOne() val program = tvViewModel.getProgramOne()
if (program != null) { if (program != null) {
binding.infoDesc.text = program.name binding.infoDesc.text = program.name
@@ -56,10 +59,13 @@ class InfoFragment : Fragment() {
handler.postDelayed(removeRunnable, delay) handler.postDelayed(removeRunnable, delay)
} }
override fun onResume() {
super.onResume()
handler.postDelayed(removeRunnable, delay)
}
override fun onPause() { override fun onPause() {
super.onPause() super.onPause()
Log.i(TAG, "onPause")
// Cancel the delayed task when the fragment is paused
handler.removeCallbacks(removeRunnable) handler.removeCallbacks(removeRunnable)
} }
@@ -28,6 +28,7 @@ class MainActivity : FragmentActivity() {
private val mainFragment = MainFragment() private val mainFragment = MainFragment()
private val infoFragment = InfoFragment() private val infoFragment = InfoFragment()
private val channelFragment = ChannelFragment() private val channelFragment = ChannelFragment()
private lateinit var settingFragment: SettingFragment
private var doubleBackToExitPressedOnce = false private var doubleBackToExitPressedOnce = false
@@ -42,7 +43,6 @@ class MainActivity : FragmentActivity() {
private var channelNum = true private var channelNum = true
private var versionName = "" private var versionName = ""
private lateinit var dialogFragment: MyDialogFragment
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@@ -58,8 +58,7 @@ class MainActivity : FragmentActivity() {
.add(R.id.main_browse_fragment, infoFragment) .add(R.id.main_browse_fragment, infoFragment)
.add(R.id.main_browse_fragment, channelFragment) .add(R.id.main_browse_fragment, channelFragment)
.add(R.id.main_browse_fragment, mainFragment) .add(R.id.main_browse_fragment, mainFragment)
.hide(infoFragment) .hide(mainFragment)
.hide(channelFragment)
.commit() .commit()
mainFragment.view?.requestFocus() mainFragment.view?.requestFocus()
} }
@@ -70,7 +69,7 @@ class MainActivity : FragmentActivity() {
channelNum = sharedPref.getBoolean(CHANNEL_NUM, channelNum) channelNum = sharedPref.getBoolean(CHANNEL_NUM, channelNum)
versionName = getPackageInfo().versionName versionName = getPackageInfo().versionName
dialogFragment = MyDialogFragment(versionName, channelReversal, channelNum) settingFragment = SettingFragment(versionName, channelReversal, channelNum)
} }
fun showInfoFragment(tvViewModel: TVViewModel) { fun showInfoFragment(tvViewModel: TVViewModel) {
@@ -80,6 +79,20 @@ class MainActivity : FragmentActivity() {
} }
} }
private fun showChannel(channel: String) {
if (!mainFragment.isHidden) {
return
}
if (settingFragment.isVisible) {
return
}
if (channelNum) {
channelFragment.show(channel)
}
}
fun play(tvViewModel: TVViewModel) { fun play(tvViewModel: TVViewModel) {
playerFragment.play(tvViewModel) playerFragment.play(tvViewModel)
mainFragment.view?.requestFocus() mainFragment.view?.requestFocus()
@@ -109,6 +122,7 @@ class MainActivity : FragmentActivity() {
val transaction = supportFragmentManager.beginTransaction() val transaction = supportFragmentManager.beginTransaction()
if (mainFragment.isHidden) { if (mainFragment.isHidden) {
mainFragment.setPosition()
transaction.show(mainFragment) transaction.show(mainFragment)
keepRunnable() keepRunnable()
} else { } else {
@@ -214,20 +228,20 @@ class MainActivity : FragmentActivity() {
return return
} }
Log.i(TAG, "dialogFragment ${dialogFragment.isVisible}") Log.i(TAG, "settingFragment ${settingFragment.isVisible}")
if (!dialogFragment.isVisible) { if (!settingFragment.isVisible) {
dialogFragment.show(supportFragmentManager, "settings_dialog") settingFragment.show(supportFragmentManager, "setting")
handler.removeCallbacks(hideHelp) handler.removeCallbacks(hideHelp)
handler.postDelayed(hideHelp, delayHideHelp) handler.postDelayed(hideHelp, delayHideHelp)
} else { } else {
handler.removeCallbacks(hideHelp) handler.removeCallbacks(hideHelp)
dialogFragment.dismiss() settingFragment.dismiss()
} }
} }
private val hideHelp = Runnable { private val hideHelp = Runnable {
if (dialogFragment.isVisible) { if (settingFragment.isVisible) {
dialogFragment.dismiss() settingFragment.dismiss()
} }
} }
@@ -283,20 +297,6 @@ class MainActivity : FragmentActivity() {
}, 2000) }, 2000)
} }
private fun showChannel(channel: String) {
if (!mainFragment.isHidden) {
return
}
if (dialogFragment.isVisible) {
return
}
if (channelNum) {
channelFragment.show(channel)
}
}
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean { override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
when (keyCode) { when (keyCode) {
KeyEvent.KEYCODE_0 -> { KeyEvent.KEYCODE_0 -> {
@@ -6,120 +6,252 @@ import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.util.Log import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast import android.widget.Toast
import androidx.leanback.app.BrowseSupportFragment import androidx.fragment.app.Fragment
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.lifecycle.lifecycleScope 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.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.TVListViewModel
import com.lizongying.mytv.models.TVViewModel import com.lizongying.mytv.models.TVViewModel
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class MainFragment : Fragment(), CardAdapter.ItemListener {
class MainFragment : BrowseSupportFragment() { private var itemPosition = 0
var itemPosition: Int = 0
private var request: Request = Request() private var rowList: MutableList<View> = mutableListOf()
private var rowsAdapter: ArrayObjectAdapter? = null private var _binding: ShowBinding? = null
private val binding get() = _binding!!
private var request = Request()
var tvListViewModel = TVListViewModel() var tvListViewModel = TVListViewModel()
private var sharedPref: SharedPreferences? = null private var sharedPref: SharedPreferences? = null
private var lastVideoUrl: String = "" private var lastVideoUrl = ""
private val handler = Handler(Looper.getMainLooper()) private val handler = Handler(Looper.getMainLooper())
private lateinit var mUpdateProgramRunnable: UpdateProgramRunnable private lateinit var mUpdateProgramRunnable: UpdateProgramRunnable
private var ready = 0 private var ready = 0
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreateView(
super.onCreate(savedInstanceState) inflater: LayoutInflater, container: ViewGroup?,
headersState = HEADERS_DISABLED savedInstanceState: Bundle?
): View {
_binding = ShowBinding.inflate(inflater, container, false)
return binding.root
} }
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState) super.onActivityCreated(savedInstanceState)
activity?.let { request.initYSP(it) } activity?.let { request.initYSP(it) }
sharedPref = activity?.getPreferences(Context.MODE_PRIVATE) sharedPref = activity?.getPreferences(Context.MODE_PRIVATE)
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() val tvListViewModelCurrent = TVListViewModel()
handler.post(mUpdateProgramRunnable) for ((idx2, v1) in v.withIndex()) {
val tvViewModel = TVViewModel(v1)
tvListViewModel.getTVListViewModel().value?.forEach { tvViewModel -> tvViewModel.setRowPosition(idx.toInt())
tvViewModel.errInfo.observe(viewLifecycleOwner) { _ -> tvViewModel.setItemPosition(idx2)
if (tvViewModel.errInfo.value != null tvListViewModelCurrent.addTVViewModel(tvViewModel)
&& tvViewModel.id.value == itemPosition tvListViewModel.addTVViewModel(tvViewModel)
) {
Toast.makeText(context, tvViewModel.errInfo.value, Toast.LENGTH_SHORT).show()
} }
} tvListViewModel.maxNum.add(v.size)
tvViewModel.ready.observe(viewLifecycleOwner) { _ ->
// not first time && channel not change val adapter = CardAdapter(viewLifecycleOwner, tvListViewModelCurrent)
if (tvViewModel.ready.value != null rowList.add(itemBinding.rowItems)
&& tvViewModel.id.value == itemPosition
&& check(tvViewModel) adapter.setItemListener(this)
) {
Log.i(TAG, "ready ${tvViewModel.title.value}") itemBinding.rowHeader.text = k
(activity as? MainActivity)?.play(tvViewModel) 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) { mUpdateProgramRunnable = UpdateProgramRunnable()
val title = tvViewModel.title.value handler.post(mUpdateProgramRunnable)
Log.i(TAG, "switch $title")
if (tvViewModel.pid.value != "") { itemPosition = sharedPref?.getInt(POSITION, 0)!!
Log.i(TAG, "request $title ${tvViewModel.pid.value}") if (itemPosition >= tvListViewModel.size()) {
lifecycleScope.launch(Dispatchers.IO) { itemPosition = 0
tvViewModel.let { request.fetchData(it) } }
} tvListViewModel.setItemPosition(itemPosition)
(activity as? MainActivity)?.showInfoFragment(tvViewModel) setPosition()
setSelectedPosition( tvListViewModel.getTVListViewModel().value?.forEach { tvViewModel ->
tvViewModel.getRowPosition(), true, tvViewModel.errInfo.observe(viewLifecycleOwner) { _ ->
SelectItemViewHolderTask(tvViewModel.getItemPosition()) if (tvViewModel.errInfo.value != null
) && tvViewModel.id.value == itemPosition
} else { ) {
if (check(tvViewModel)) { Toast.makeText(context, tvViewModel.errInfo.value, Toast.LENGTH_SHORT)
(activity as? MainActivity)?.play(tvViewModel) .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) (activity as? MainActivity)?.showInfoFragment(tvViewModel)
setPosition(
setSelectedPosition( tvViewModel.getRowPosition(), tvViewModel.getItemPosition()
tvViewModel.getRowPosition(), true,
SelectItemViewHolderTask(tvViewModel.getItemPosition())
) )
} else {
if (check(tvViewModel)) {
(activity as? MainActivity)?.play(tvViewModel)
(activity as? MainActivity)?.showInfoFragment(tvViewModel)
setPosition(
tvViewModel.getRowPosition(), tvViewModel.getItemPosition()
)
}
} }
} }
} }
} }
}
fragmentReady() fragmentReady()
}
} }
override fun onDestroy() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onDestroy() super.onViewCreated(view, savedInstanceState)
handler.removeCallbacks(mUpdateProgramRunnable) }
with(sharedPref!!.edit()) {
putInt("position", itemPosition) override fun onItemFocusChange(tvViewModel: TVViewModel, hasFocus: Boolean) {
apply() if (hasFocus) {
tvListViewModel.setItemPositionCurrent(tvViewModel.id.value!!)
(activity as MainActivity).keepRunnable()
}
}
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()
}
fun setPosition() {
val tvViewModel = tvListViewModel.getTVViewModel(itemPosition)
rowList[tvViewModel!!.getRowPosition()].post {
((rowList[tvViewModel.getRowPosition()] as RecyclerView).layoutManager as LinearLayoutManager).findViewByPosition(
tvViewModel.getItemPosition()
)?.requestFocus()
}
}
fun setPosition(rowPosition: Int, itemPosition: Int) {
rowList[rowPosition].post {
((rowList[rowPosition] as RecyclerView).layoutManager as LinearLayoutManager).findViewByPosition(
itemPosition
)?.requestFocus()
}
}
fun check(tvViewModel: TVViewModel): Boolean {
val title = tvViewModel.title.value
val videoUrl = tvViewModel.videoIndex.value?.let { tvViewModel.videoUrl.value?.get(it) }
if (videoUrl == null || videoUrl == "") {
Log.e(TAG, "$title videoUrl is empty")
return false
}
if (videoUrl == lastVideoUrl) {
Log.e(TAG, "$title videoUrl is duplication")
return false
}
return true
}
fun fragmentReady() {
ready++
Log.i(TAG, "ready $ready")
if (ready == 4) {
// request.fetchPage()
tvListViewModel.getTVViewModel(itemPosition)?.changed()
}
}
fun play(itemPosition: Int) {
view?.post {
if (itemPosition > -1 && itemPosition < tvListViewModel.size()) {
this.itemPosition = itemPosition
tvListViewModel.setItemPosition(itemPosition)
tvListViewModel.getTVViewModel(itemPosition)?.changed()
} else {
Toast.makeText(context, "频道不存在", Toast.LENGTH_SHORT).show()
}
}
}
fun prev() {
view?.post {
itemPosition--
if (itemPosition == -1) {
itemPosition = tvListViewModel.size() - 1
}
tvListViewModel.setItemPosition(itemPosition)
tvListViewModel.getTVViewModel(itemPosition)?.changed()
}
}
fun next() {
view?.post {
itemPosition++
if (itemPosition == tvListViewModel.size()) {
itemPosition = 0
}
tvListViewModel.setItemPosition(itemPosition)
tvListViewModel.getTVViewModel(itemPosition)?.changed()
} }
} }
@@ -150,184 +282,35 @@ class MainFragment : BrowseSupportFragment() {
} }
} }
fun check(tvViewModel: TVViewModel): Boolean { override fun onStop() {
val title = tvViewModel.title.value Log.i(TAG, "onStop")
val videoUrl = tvViewModel.videoIndex.value?.let { tvViewModel.videoUrl.value?.get(it) } super.onStop()
if (videoUrl == null || videoUrl == "") { with(sharedPref!!.edit()) {
Log.e(TAG, "$title videoUrl is empty") putInt(POSITION, itemPosition)
return false apply()
} }
Log.i(TAG, "POSITION saved")
if (videoUrl == lastVideoUrl) {
Log.e(TAG, "$title videoUrl is duplication")
return false
}
return true
} }
fun toLastPosition() { override fun onDestroy() {
setSelectedPosition( Log.i(TAG, "onDestroy")
selectedPosition, false, super.onDestroy()
SelectItemViewHolderTask(tvListViewModel.maxNum[selectedPosition] - 1) handler.removeCallbacks(mUpdateProgramRunnable)
)
} }
fun toFirstPosition() { override fun onDestroyView() {
setSelectedPosition( Log.i(TAG, "onDestroyView")
selectedPosition, false, super.onDestroyView()
SelectItemViewHolderTask(0) _binding = null
)
}
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 fragmentReady() {
ready++
Log.i(TAG, "ready $ready")
if (ready == 4) {
// request.fetchPage()
val tvViewModel = tvListViewModel.getTVViewModel(itemPosition)
tvViewModel?.changed()
(activity as? MainActivity)?.switchMainFragment()
}
}
fun play(itemPosition: Int) {
view?.post {
if (itemPosition < tvListViewModel.size()) {
this.itemPosition = itemPosition
tvListViewModel.setItemPosition(itemPosition)
tvListViewModel.getTVViewModel(itemPosition)?.changed()
}
}
}
fun prev() {
view?.post {
itemPosition--
if (itemPosition == -1) {
itemPosition = tvListViewModel.size() - 1
}
tvListViewModel.setItemPosition(itemPosition)
tvListViewModel.getTVViewModel(itemPosition)?.changed()
}
}
fun next() {
view?.post {
itemPosition++
if (itemPosition == tvListViewModel.size()) {
itemPosition = 0
}
tvListViewModel.setItemPosition(itemPosition)
tvListViewModel.getTVViewModel(itemPosition)?.changed()
}
}
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()
}
}
}
}
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()
}
}
}
}
private fun setupEventListeners() {
onItemViewClickedListener = ItemViewClickedListener()
onItemViewSelectedListener = ItemViewSelectedListener()
}
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()
}
}
}
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()
}
}
} }
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
view!!.requestFocus() view?.post { view?.requestFocus() }
} }
companion object { companion object {
private const val TAG = "MainFragment" private const val TAG = "MainFragment"
private const val POSITION = "position"
} }
} }
@@ -74,10 +74,9 @@ class PlayerFragment : Fragment() {
@OptIn(UnstableApi::class) @OptIn(UnstableApi::class)
fun play(tvViewModel: TVViewModel) { fun play(tvViewModel: TVViewModel) {
this.tvViewModel = tvViewModel this.tvViewModel = tvViewModel
val videoUrlCurrent = val videoUrlCurrent = tvViewModel.getVideoUrlCurrent()
tvViewModel.videoIndex.value?.let { tvViewModel.videoUrl.value?.get(it) }
playerView?.player?.run { playerView?.player?.run {
videoUrlCurrent?.let { setMediaItem(MediaItem.fromUri(it)) } setMediaItem(MediaItem.fromUri(videoUrlCurrent))
prepare() prepare()
} }
} }
@@ -81,6 +81,8 @@ class Request {
"四川卫视" to "四川卫视", "四川卫视" to "四川卫视",
"福建东南卫视" to "东南卫视", "福建东南卫视" to "东南卫视",
"海南卫视" to "海南卫视", "海南卫视" to "海南卫视",
"天津卫视" to "天津卫视",
"新疆卫视" to "新疆卫视",
) )
fun initYSP(context: Context) { fun initYSP(context: Context) {
@@ -8,9 +8,9 @@ import androidx.fragment.app.DialogFragment
import com.lizongying.mytv.databinding.DialogBinding import com.lizongying.mytv.databinding.DialogBinding
class MyDialogFragment(private val versionName: String, class SettingFragment(private val versionName: String,
private val channelReversal: Boolean, private val channelReversal: Boolean,
private val channelNum: Boolean, private val channelNum: Boolean,
) : ) :
DialogFragment() { DialogFragment() {
@@ -52,7 +52,7 @@ class MyDialogFragment(private val versionName: String,
} }
companion object { companion object {
const val TAG = "MyDialogFragment" const val TAG = "SettingFragment"
} }
} }
@@ -7,7 +7,7 @@ object TVList {
private var mappingLogo = mapOf( private var mappingLogo = mapOf(
"CCTV4K 超高清" to "https://resources.yangshipin.cn/assets/oms/image/202306/3e9d06fd7244d950df5838750f1c6ac3456e172b51caca2c16d2282125b111e8.png?imageMogr2/format/webp", "CCTV4K 超高清" to "https://resources.yangshipin.cn/assets/oms/image/202306/3e9d06fd7244d950df5838750f1c6ac3456e172b51caca2c16d2282125b111e8.png?imageMogr2/format/webp",
"CCTV8K 超高清" to "https://static.wikia.nocookie.net/logos/images/6/69/CCTV8K.png/revision/latest/scale-to-width-down/120?cb=20230104110835&path-prefix=vi", "CCTV8K 超高清" to "",
"CCTV1 综合" to "https://resources.yangshipin.cn/assets/oms/image/202306/d57905b93540bd15f0c48230dbbbff7ee0d645ff539e38866e2d15c8b9f7dfcd.png?imageMogr2/format/webp", "CCTV1 综合" to "https://resources.yangshipin.cn/assets/oms/image/202306/d57905b93540bd15f0c48230dbbbff7ee0d645ff539e38866e2d15c8b9f7dfcd.png?imageMogr2/format/webp",
"CCTV2 财经" to "https://resources.yangshipin.cn/assets/oms/image/202306/20115388de0207131af17eac86c33049b95d69eaff064e55653a1b941810a006.png?imageMogr2/format/webp", "CCTV2 财经" to "https://resources.yangshipin.cn/assets/oms/image/202306/20115388de0207131af17eac86c33049b95d69eaff064e55653a1b941810a006.png?imageMogr2/format/webp",
"CCTV3 综艺" to "https://resources.yangshipin.cn/assets/oms/image/202306/7b7a65c712450da3deb6ca66fbacf4f9aee00d3f20bd80eafb5ada01ec63eb3a.png?imageMogr2/format/webp", "CCTV3 综艺" to "https://resources.yangshipin.cn/assets/oms/image/202306/7b7a65c712450da3deb6ca66fbacf4f9aee00d3f20bd80eafb5ada01ec63eb3a.png?imageMogr2/format/webp",
@@ -66,6 +66,8 @@ object TVList {
"四川卫视" to "https://resources.yangshipin.cn/assets/oms/image/202306/3276a414ae0eaa0f116f2045cd913367967d0c7c1e978e8621ac3879436c6ed7.png?imageMogr2/format/webp", "四川卫视" to "https://resources.yangshipin.cn/assets/oms/image/202306/3276a414ae0eaa0f116f2045cd913367967d0c7c1e978e8621ac3879436c6ed7.png?imageMogr2/format/webp",
"东南卫视" to "https://resources.yangshipin.cn/assets/oms/image/202306/3208fe6564a293c21b711333fb3edb05bb5b406cff840573c9a8d839680a1579.png?imageMogr2/format/webp", "东南卫视" to "https://resources.yangshipin.cn/assets/oms/image/202306/3208fe6564a293c21b711333fb3edb05bb5b406cff840573c9a8d839680a1579.png?imageMogr2/format/webp",
"海南卫视" to "https://resources.yangshipin.cn/assets/oms/image/202306/6e060391fde0469801fc3d84dbf204b4f8d650d251f17d7595a6964c0bb99e81.png?imageMogr2/format/webp", "海南卫视" to "https://resources.yangshipin.cn/assets/oms/image/202306/6e060391fde0469801fc3d84dbf204b4f8d650d251f17d7595a6964c0bb99e81.png?imageMogr2/format/webp",
"天津卫视" to "",
"新疆卫视" to "",
) )
private var mappingEPG = mapOf( private var mappingEPG = mapOf(
"CCTV4K 超高清" to "600002264", "CCTV4K 超高清" to "600002264",
@@ -128,6 +130,8 @@ object TVList {
"四川卫视" to "600002516", "四川卫视" to "600002516",
"东南卫视" to "600002484", "东南卫视" to "600002484",
"海南卫视" to "600002506", "海南卫视" to "600002506",
"天津卫视" to "600152137",
"新疆卫视" to "600152138",
) )
private var mappingVideo = mapOf( private var mappingVideo = mapOf(
"CCTV4K 超高清" to arrayOf("600002264", "2000266303"), "CCTV4K 超高清" to arrayOf("600002264", "2000266303"),
@@ -138,14 +142,14 @@ object TVList {
"CCTV4 中文国际" to arrayOf("600001814", "2000204803"), "CCTV4 中文国际" to arrayOf("600001814", "2000204803"),
"CCTV5 体育" to arrayOf("600001818", "2000205103"), "CCTV5 体育" to arrayOf("600001818", "2000205103"),
"CCTV5+ 体育赛事" to arrayOf("600001817", "2000204503"), "CCTV5+ 体育赛事" to arrayOf("600001817", "2000204503"),
"CCTV6 电影" to arrayOf("600001802", "2000203303"), "CCTV6 电影" to arrayOf("600001802", "2013693901"),
"CCTV7 国防军事" to arrayOf("600004092", "2000510003"), "CCTV7 国防军事" to arrayOf("600004092", "2000510003"),
"CCTV8 电视剧" to arrayOf("600001803", "2000203903"), "CCTV8 电视剧" to arrayOf("600001803", "2000203903"),
"CCTV9 记录" to arrayOf("600004078", "2000499403"), "CCTV9 记录" to arrayOf("600004078", "2000499403"),
"CCTV10 科教" to arrayOf("600001805", "2000203503"), "CCTV10 科教" to arrayOf("600001805", "2000203503"),
"CCTV11 戏曲" to arrayOf("600001806", "2000204103"), "CCTV11 戏曲" to arrayOf("600001806", "2000204103"),
"CCTV12 社会与法" to arrayOf("600001807", "2000202603"), "CCTV12 社会与法" to arrayOf("600001807", "2000202603"),
// "CCTV13 新闻" to arrayOf("600001811","2000204603"), "CCTV13 新闻" to arrayOf("600001811","2000204603"),
"CCTV14 少儿" to arrayOf("600001809", "2000204403"), "CCTV14 少儿" to arrayOf("600001809", "2000204403"),
"CCTV15 音乐" to arrayOf("600001815", "2000205003"), "CCTV15 音乐" to arrayOf("600001815", "2000205003"),
"CCTV16 奥林匹克" to arrayOf("600098637", "2012375003"), "CCTV16 奥林匹克" to arrayOf("600098637", "2012375003"),
@@ -190,20 +194,19 @@ object TVList {
"四川卫视" to arrayOf("600002516", "2000295003"), "四川卫视" to arrayOf("600002516", "2000295003"),
"东南卫视" to arrayOf("600002484", "2000292503"), "东南卫视" to arrayOf("600002484", "2000292503"),
"海南卫视" to arrayOf("600002506", "2000291503"), "海南卫视" to arrayOf("600002506", "2000291503"),
"天津卫视" to arrayOf("600152137", "2019927003"),
"新疆卫视" to arrayOf("600152138", "2019927403"),
) )
private var count: Int = 0 private var count: Int = 0
private fun setupTV(): Map<String, List<TV>> { private fun setupTV(): Map<String, List<TV>> {
val tvs = """ val tvs = """
央视频道 央视频道
CCTV4K 超高清,
CCTV8K 超高清,
CCTV1 综合,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226231/index.m3u8 CCTV1 综合,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226231/index.m3u8
CCTV2 财经,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226195/index.m3u8 CCTV2 财经,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226195/index.m3u8
CCTV3 综艺,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226397/index.m3u8 CCTV3 综艺,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226397/index.m3u8
CCTV4 中文国际,http://39.134.24.161/dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226191/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226191/index.m3u8 CCTV4 中文国际,http://39.134.24.161/dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226191/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226191/index.m3u8
CCTV5 体育,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226395/index.m3u8 CCTV5 体育,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226395/index.m3u8
CCTV5+ 体育赛事,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226221/index.m3u8
CCTV6 电影,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226393/index.m3u8 CCTV6 电影,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226393/index.m3u8
CCTV7 国防军事,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226192/index.m3u8 CCTV7 国防军事,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226192/index.m3u8
CCTV8 电视剧,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226391/index.m3u8 CCTV8 电视剧,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226391/index.m3u8
@@ -216,6 +219,9 @@ CCTV14 少儿,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226193/inde
CCTV15 音乐,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225785/index.m3u8 CCTV15 音乐,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225785/index.m3u8
CCTV16 奥林匹克,http://39.134.24.162/dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226921/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226921/index.m3u8 CCTV16 奥林匹克,http://39.134.24.162/dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226921/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226921/index.m3u8
CCTV17 农业农村,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226198/index.m3u8 CCTV17 农业农村,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226198/index.m3u8
CCTV5+ 体育赛事,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226221/index.m3u8
CCTV4K 超高清,
CCTV8K 超高清,
风云剧场,http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226950/index.m3u8 风云剧场,http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226950/index.m3u8
第一剧场,http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226959/index.m3u8 第一剧场,http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226959/index.m3u8
怀旧剧场,http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226972/index.m3u8 怀旧剧场,http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226972/index.m3u8
@@ -251,6 +257,8 @@ CCTV17 农业农村,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/322122619
四川卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225768/index.m3u8;http://39.134.24.166/dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225768/index.m3u8 四川卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225768/index.m3u8;http://39.134.24.166/dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225768/index.m3u8
东南卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226341/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225766/index.m3u8 东南卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226341/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225766/index.m3u8
海南卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226465/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225769/index.m3u8 海南卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226465/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225769/index.m3u8
天津卫视,
新疆卫视,
国际频道 国际频道
CGTN,http://live.cgtn.com/1000/prog_index.m3u8 CGTN,http://live.cgtn.com/1000/prog_index.m3u8
CGTN 法语频道,https://livefr.cgtn.com/1000f/prog_index.m3u8 CGTN 法语频道,https://livefr.cgtn.com/1000f/prog_index.m3u8
@@ -1,5 +1,7 @@
package com.lizongying.mytv package com.lizongying.mytv
import android.content.res.Resources
import android.util.TypedValue
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Date import java.util.Date
import java.util.Locale import java.util.Locale
@@ -12,4 +14,20 @@ object Utils {
fun getDateTimestamp(): Long { fun getDateTimestamp(): Long {
return Date().time / 1000 return Date().time / 1000
} }
fun dpToPx(dp: Float): Int {
return TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
dp,
Resources.getSystem().displayMetrics
).toInt()
}
fun dpToPx(dp: Int): Int {
return TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
dp.toFloat(),
Resources.getSystem().displayMetrics
).toInt()
}
} }
@@ -20,7 +20,7 @@ class TVViewModel(private var tv: TV) : ViewModel() {
private var itemPosition: Int = 0 private var itemPosition: Int = 0
var retryTimes: Int = 0 var retryTimes: Int = 0
var retryMaxTimes: Int = 5 var retryMaxTimes: Int = 8
var programUpdateTime: Long = 0 var programUpdateTime: Long = 0
private val _errInfo = MutableLiveData<String>() private val _errInfo = MutableLiveData<String>()
@@ -222,18 +222,25 @@ class TVViewModel(private var tv: TV) : ViewModel() {
mMinimumLoadableRetryCount = minimumLoadableRetryCount mMinimumLoadableRetryCount = minimumLoadableRetryCount
} }
/**
* (playerView?.player as ExoPlayer).setMediaSource(tvViewModel.buildSource())
*/
@OptIn(UnstableApi::class) @OptIn(UnstableApi::class)
fun buildSource(videoUrl: String, mHeaders: Map<String, String>?): HlsMediaSource { fun buildSource(): HlsMediaSource {
val httpDataSource = DefaultHttpDataSource.Factory() val httpDataSource = DefaultHttpDataSource.Factory()
mHeaders?.let { httpDataSource.setDefaultRequestProperties(it) } mHeaders?.let { httpDataSource.setDefaultRequestProperties(it) }
return HlsMediaSource.Factory(httpDataSource).createMediaSource( return HlsMediaSource.Factory(httpDataSource).createMediaSource(
MediaItem.fromUri( MediaItem.fromUri(
Uri.parse(videoUrl) Uri.parse(getVideoUrlCurrent())
) )
) )
} }
fun getVideoUrlCurrent(): String {
return _videoUrl.value!![_videoIndex.value!!]
}
companion object { companion object {
private const val TAG = "TVViewModel" private const val TAG = "TVViewModel"
} }
Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

+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> <resources>
<style name="CustomImageCardViewStyle" parent="Widget.Leanback.ImageCardViewStyle"> <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>
<style name="CustomImageCardTheme" parent="Theme.Leanback"> <style name="CustomImageCardTheme" parent="Theme.Leanback">
<item name="imageCardViewStyle">@style/CustomImageCardViewStyle</item> <item name="imageCardViewStyle">@style/CustomImageCardViewStyle</item>
<item name="imageCardViewImageStyle">@style/CustomImageCardViewImageStyle</item>
</style> </style>
</resources> </resources>
+2 -2
View File
@@ -1,6 +1,6 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules. // Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins { plugins {
id 'com.android.application' version '8.2.0' apply false id 'com.android.application' version '8.2.1' apply false
id 'com.android.library' version '8.2.0' apply false id 'com.android.library' version '8.2.1' apply false
id 'org.jetbrains.kotlin.android' version '1.9.21' apply false id 'org.jetbrains.kotlin.android' version '1.9.21' apply false
} }