Compare commits

..
26 Commits
Author SHA1 Message Date
Li ZongYing 4fb27ba502 fix channel number did not disappear 2024-02-05 06:12:01 +08:00
李宗英 1714633cb7 Merge pull request #205 from LeGend-wLw/main
移除硬编码,更改为json格式,并支持从服务器上动态更新channels。
2024-02-04 22:46:08 +08:00
LeGend-wLw f7b024f6da 防止多次初始化 2024-02-04 16:42:56 +08:00
LeGend-wLw dd8df63b01 添加初始化TVList行为 2024-02-04 16:42:38 +08:00
LeGend-wLw a4c95efd71 添加从服务器上获取channel.json逻辑,但未启用 2024-02-04 16:37:59 +08:00
LeGend-wLw 9f32fa3673 移除MustToken和needToken,移动到TV类中 2024-02-04 16:37:20 +08:00
LeGend-wLw b3d0093ab0 修改为从json文件中读取channel信息 2024-02-04 16:36:34 +08:00
LeGend-wLw 5798d3a5fe 更改TV类中成员,使能和json对应 2024-02-04 16:36:05 +08:00
LeGend-wLw 4eb90de79d 当前转换成json的channel信息 2024-02-04 16:35:38 +08:00
LeGend-wLw b8232971e2 服务器地址,未更新 2024-02-04 16:35:20 +08:00
LeGend-wLw bc3681b0a6 将从tvModel中获取needToken和MustToken改成从TV类中获取needToken和MustToken 2024-02-04 16:34:59 +08:00
Li ZongYing 088808c3fd fix retry bug 2024-02-03 20:07:08 +08:00
Li ZongYing 73239a6677 replay 2024-02-01 17:04:02 +08:00
Li ZongYing cc2514a81a performance optimization 2024-01-28 16:00:33 +08:00
Li ZongYing bcf9bab25f add channel 2024-01-27 19:52:36 +08:00
Li ZongYing f7eebb3f7a channel allow one num 2024-01-25 12:21:16 +08:00
Li ZongYing 0f4a33a3fc change channel order 2024-01-25 11:37:06 +08:00
Li ZongYing c17d62c698 rename to setting 2024-01-19 13:49:43 +08:00
Li ZongYing 33d3da97e5 fix 13 2024-01-18 22:19:04 +08:00
Li ZongYing ba95ce5374 number selection 2024-01-18 21:42:06 +08:00
Li ZongYing e1ee909071 optimize memu hiding 2024-01-17 10:19:50 +08:00
Li ZongYing 1fd0951f92 channel reversal 2024-01-16 19:04:43 +08:00
Li ZongYing 42b072f5ec support KEYCODE_CHANNEL_UP & KEYCODE_CHANNEL_DOWN 2024-01-14 12:09:12 +08:00
Li ZongYing a23b1e11ce fix the menu did not appear 2024-01-14 10:15:45 +08:00
Li ZongYing 1ac6d87c3c clean log 2024-01-12 10:13:29 +08:00
Li ZongYing b075a7db81 trust all certs 2024-01-11 19:33:31 +08:00
10 changed files with 169 additions and 317 deletions
+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.6.4"
def retrofit2_version = "2.9.0"
implementation 'com.google.protobuf:protobuf-kotlin:3.25.1'
implementation "com.squareup.retrofit2:converter-gson:$retrofit2_version"
Binary file not shown.
Binary file not shown.
@@ -1,119 +0,0 @@
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"
}
}
@@ -122,7 +122,6 @@ class MainActivity : FragmentActivity() {
val transaction = supportFragmentManager.beginTransaction()
if (mainFragment.isHidden) {
mainFragment.setPosition()
transaction.show(mainFragment)
keepRunnable()
} else {
@@ -5,31 +5,30 @@ 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.fragment.app.Fragment
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.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 : Fragment(), CardAdapter.ItemListener {
class MainFragment : BrowseSupportFragment() {
private var itemPosition = 0
private var rowList: MutableList<View> = mutableListOf()
private var _binding: ShowBinding? = null
private val binding get() = _binding!!
private var rowsAdapter: ArrayObjectAdapter? = null
private var request = Request()
@@ -44,12 +43,15 @@ class MainFragment : Fragment(), CardAdapter.ItemListener {
private var ready = 0
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = ShowBinding.inflate(inflater, container, false)
return binding.root
override fun onCreate(savedInstanceState: Bundle?) {
Log.i(TAG, "onCreate")
super.onCreate(savedInstanceState)
headersState = HEADERS_DISABLED
}
override fun onStart() {
Log.i(TAG, "onStart")
super.onStart()
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
@@ -58,141 +60,172 @@ class MainFragment : Fragment(), CardAdapter.ItemListener {
activity?.let { request.initYSP(it) }
sharedPref = (activity as? MainActivity)?.sharedPref!!
view?.post {
val content = binding.content
loadRows()
var idx: Long = 0
context?.let { TVList.init(it) }
for ((k, v) in TVList.list) {
val itemBinding: RowBinding =
RowBinding.inflate(layoutInflater, content, false)
setupEventListeners()
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)
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()
}
tvListViewModel.maxNum.add(v.size)
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.ready.observe(viewLifecycleOwner) { _ ->
mUpdateProgramRunnable = UpdateProgramRunnable()
handler.post(mUpdateProgramRunnable)
itemPosition = sharedPref.getInt(POSITION, 0)
if (itemPosition >= tvListViewModel.size()) {
itemPosition = 0
// 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)
}
}
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")
lifecycleScope.launch(Dispatchers.IO) {
tvViewModel.let { request.fetchData(it) }
}
tvViewModel.change.observe(viewLifecycleOwner) { _ ->
if (tvViewModel.change.value != null) {
val title = tvViewModel.title.value
if (tvViewModel.pid.value != "") {
Log.i(TAG, "request $title")
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)
(activity as? MainActivity)?.showInfoFragment(tvViewModel)
setPosition(
tvViewModel.getRowPosition(), tvViewModel.getItemPosition()
setSelectedPosition(
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()
}
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
context?.let { TVList.init(it) }
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()
}
}
}
}
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()
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 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 fun setupEventListeners() {
onItemViewClickedListener = ItemViewClickedListener()
onItemViewSelectedListener = ItemViewSelectedListener()
}
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 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(rowPosition: Int, itemPosition: Int) {
rowList[rowPosition].post {
((rowList[rowPosition] as RecyclerView).layoutManager as LinearLayoutManager).findViewByPosition(
itemPosition
)?.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()
}
}
}
@@ -303,12 +336,6 @@ class MainFragment : Fragment(), CardAdapter.ItemListener {
handler.removeCallbacks(mUpdateProgramRunnable)
}
override fun onDestroyView() {
Log.i(TAG, "onDestroyView")
super.onDestroyView()
_binding = null
}
companion object {
private const val TAG = "MainFragment"
private const val POSITION = "position"
@@ -97,6 +97,8 @@ class Request {
fun initYSP(context: Context) {
ysp = YSP(context)
//TODO 不确定在哪里初始化
TVList.init(context)
}
var call: Call<LiveInfo>? = null
@@ -202,7 +204,7 @@ class Request {
}
override fun onFailure(call: Call<LiveInfo>, t: Throwable) {
Log.e(TAG, "$title request error")
Log.e(TAG, "$title request error $t")
if (tvModel.retryTimes < tvModel.retryMaxTimes) {
tvModel.retryTimes++
if (tvModel.getTV().needToken) {
-25
View File
@@ -1,25 +0,0 @@
<?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
@@ -1,17 +0,0 @@
<?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>
+1 -16
View File
@@ -1,25 +1,10 @@
<resources>
<style name="CustomImageCardViewStyle" parent="Widget.Leanback.ImageCardViewStyle">
<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>
<item name="lbImageCardViewType">Title|Content</item>
</style>
<style name="CustomImageCardTheme" parent="Theme.Leanback">
<item name="imageCardViewStyle">@style/CustomImageCardViewStyle</item>
<item name="imageCardViewImageStyle">@style/CustomImageCardViewImageStyle</item>
</style>
</resources>