diff --git a/app/src/main/cpp/arm64-v8a/libnative.so b/app/src/main/cpp/arm64-v8a/libnative.so index 10ddfa6..88d9f87 100755 Binary files a/app/src/main/cpp/arm64-v8a/libnative.so and b/app/src/main/cpp/arm64-v8a/libnative.so differ diff --git a/app/src/main/cpp/armeabi-v7a/libnative.so b/app/src/main/cpp/armeabi-v7a/libnative.so index 748fe66..21148df 100755 Binary files a/app/src/main/cpp/armeabi-v7a/libnative.so and b/app/src/main/cpp/armeabi-v7a/libnative.so differ diff --git a/app/src/main/java/com/lizongying/mytv/CardPresenter.kt b/app/src/main/java/com/lizongying/mytv/CardPresenter.kt index aac0880..843d816 100644 --- a/app/src/main/java/com/lizongying/mytv/CardPresenter.kt +++ b/app/src/main/java/com/lizongying/mytv/CardPresenter.kt @@ -49,12 +49,12 @@ class CardPresenter( } override fun onBindViewHolder(viewHolder: ViewHolder, item: Any) { - val tv = item as TV + val tvViewModel = item as TVViewModel val cardView = viewHolder.view as ImageCardView - cardView.titleText = tv.title + cardView.titleText = tvViewModel.title.value cardView.setMainImageDimensions(CARD_WIDTH, CARD_HEIGHT) - cardView.tag = tv.videoUrl + cardView.tag = tvViewModel.videoUrl.value // lifecycleScope.launch(Dispatchers.IO) { // val videoThumbnail = tv.videoUrl?.let { getVideoThumbnail(it) } @@ -83,17 +83,17 @@ class CardPresenter( private fun updateCardBackgroundColor(view: ImageCardView) { val currentTag = view.tag - lifecycleScope.launch(Dispatchers.IO) { - delay(1000) - if (view.isSelected && view.tag != null && currentTag == view.tag) { - val videoThumbnail = view.tag.toString().let { getVideoThumbnail(it) } - withContext(Dispatchers.Main) { - if (view.isSelected && currentTag == view.tag) { - view.mainImageView.setImageBitmap(videoThumbnail) - } - } - } - } +// lifecycleScope.launch(Dispatchers.IO) { +// delay(1000) +// if (view.isSelected && view.tag != null && currentTag == view.tag) { +// val videoThumbnail = view.tag.toString().let { getVideoThumbnail(it) } +// withContext(Dispatchers.Main) { +// if (view.isSelected && currentTag == view.tag) { +// view.mainImageView.setImageBitmap(videoThumbnail) +// } +// } +// } +// } } private fun getVideoThumbnail(url: String): Bitmap? { @@ -112,7 +112,7 @@ class CardPresenter( companion object { private const val TAG = "CardPresenter" - private const val CARD_WIDTH = 313 - private const val CARD_HEIGHT = 176 + private const val CARD_WIDTH = 300 + private const val CARD_HEIGHT = 169 } } \ No newline at end of file diff --git a/app/src/main/java/com/lizongying/mytv/ExoPlayerAdapter.kt b/app/src/main/java/com/lizongying/mytv/ExoPlayerAdapter.kt index 1df9bfb..efbca9e 100644 --- a/app/src/main/java/com/lizongying/mytv/ExoPlayerAdapter.kt +++ b/app/src/main/java/com/lizongying/mytv/ExoPlayerAdapter.kt @@ -10,6 +10,8 @@ import androidx.leanback.media.PlaybackGlueHost import androidx.leanback.media.PlayerAdapter import androidx.leanback.media.SurfaceHolderGlueHost import androidx.media3.common.MediaItem +import androidx.media3.common.PlaybackException +import androidx.media3.common.Player import androidx.media3.common.util.UnstableApi import androidx.media3.datasource.DefaultHttpDataSource import androidx.media3.exoplayer.ExoPlayer @@ -176,6 +178,14 @@ open class ExoPlayerAdapter(private var mContext: Context?) : PlayerAdapter() { return mBufferedProgress } + private var mPlayerErrorListener: PlayerErrorListener? = null + + private inner class PlayerErrorListener : Player.Listener { + override fun onPlayerError(error: PlaybackException) { + callback.onError(this@ExoPlayerAdapter, error.errorCode, error.message) + } + } + /** * Sets the media source of the player witha given URI. * @@ -189,6 +199,13 @@ open class ExoPlayerAdapter(private var mContext: Context?) : PlayerAdapter() { } mMediaSourceUri = uri prepareMediaForPlaying() + + mPlayer?.playWhenReady = true + + if (mPlayerErrorListener == null) { + mPlayerErrorListener = PlayerErrorListener() + mPlayer?.addListener(mPlayerErrorListener!!) + } return true } @@ -221,7 +238,7 @@ open class ExoPlayerAdapter(private var mContext: Context?) : PlayerAdapter() { throw RuntimeException(e) } mPlayer?.prepare() - mPlayer?.playWhenReady = true + callback.onPlayStateChanged(this@ExoPlayerAdapter) } @@ -232,6 +249,10 @@ open class ExoPlayerAdapter(private var mContext: Context?) : PlayerAdapter() { override fun isPrepared(): Boolean { return mInitialized && (mSurfaceHolderGlueHost == null || mHasDisplay) } + + companion object { + private const val TAG = "ExoPlayerAdapter" + } } internal class VideoPlayerSurfaceHolderCallback(private val playerAdapter: ExoPlayerAdapter) : diff --git a/app/src/main/java/com/lizongying/mytv/Info.kt b/app/src/main/java/com/lizongying/mytv/Info.kt deleted file mode 100644 index d7855a5..0000000 --- a/app/src/main/java/com/lizongying/mytv/Info.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.lizongying.mytv - -import java.io.Serializable - -data class Info( - var rowPosition: Int = 0, - var itemPosition: Int = 0, - var item: TV, -) : Serializable \ No newline at end of file diff --git a/app/src/main/java/com/lizongying/mytv/MainActivity.kt b/app/src/main/java/com/lizongying/mytv/MainActivity.kt index a2ba521..0684c1a 100644 --- a/app/src/main/java/com/lizongying/mytv/MainActivity.kt +++ b/app/src/main/java/com/lizongying/mytv/MainActivity.kt @@ -18,6 +18,7 @@ import android.widget.TextView import android.widget.Toast import androidx.core.content.ContextCompat import androidx.fragment.app.FragmentActivity +import com.lizongying.mytv.models.TVViewModel import java.security.MessageDigest @@ -43,8 +44,6 @@ class MainActivity : FragmentActivity() { .hide(infoFragment) .commit() } - - Log.i(TAG, "Signature ${getAppSignature()}") } fun switchInfoFragment(tv: TV) { @@ -68,9 +67,9 @@ class MainActivity : FragmentActivity() { .commit() } - fun play(tv: TV) { - Log.i(TAG, "play: $tv") - playbackFragment.play(tv) + fun play(tvViewModel: TVViewModel) { + Log.i(TAG, "play: ${tvViewModel.getTV()}") + playbackFragment.play(tvViewModel) } fun prev() { @@ -173,30 +172,57 @@ class MainActivity : FragmentActivity() { } KeyEvent.KEYCODE_DPAD_CENTER -> { + Log.i(TAG, "KEYCODE_DPAD_CENTER") switchMainFragment() } KeyEvent.KEYCODE_DPAD_UP -> { if (mainFragment.isHidden) { prev() + } else { + if (mainFragment.selectedPosition == 0) { + mainFragment.setSelectedPosition( + mainFragment.tvListViewModel.maxNum.size - 1, + false + ) + } } } KeyEvent.KEYCODE_DPAD_DOWN -> { if (mainFragment.isHidden) { next() + } else { + if (mainFragment.selectedPosition == mainFragment.tvListViewModel.maxNum.size - 1) { +// mainFragment.setSelectedPosition(0, false) + hideMainFragment() + return false + } } } KeyEvent.KEYCODE_DPAD_LEFT -> { if (mainFragment.isHidden) { prevSource() + } else { + if (mainFragment.tvListViewModel.getTVViewModelCurrent() + ?.getItemPosition() == 0 + ) { +// mainFragment.toLastPosition() + hideMainFragment() + } } } KeyEvent.KEYCODE_DPAD_RIGHT -> { if (mainFragment.isHidden) { nextSource() + } else { + if (mainFragment.tvListViewModel.getTVViewModelCurrent() + ?.getItemPosition() == mainFragment.tvListViewModel.maxNum[mainFragment.selectedPosition] - 1 + ) { + mainFragment.toFirstPosition() + } } } } diff --git a/app/src/main/java/com/lizongying/mytv/MainFragment.kt b/app/src/main/java/com/lizongying/mytv/MainFragment.kt index 5bb1e60..17c3f64 100644 --- a/app/src/main/java/com/lizongying/mytv/MainFragment.kt +++ b/app/src/main/java/com/lizongying/mytv/MainFragment.kt @@ -19,32 +19,25 @@ import androidx.leanback.widget.Row import androidx.leanback.widget.RowPresenter import androidx.lifecycle.lifecycleScope import com.lizongying.mytv.models.TVListViewModel +import com.lizongying.mytv.models.TVViewModel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch + class MainFragment : BrowseSupportFragment() { - var itemPosition: Int = 0 - private val list2: MutableList = mutableListOf() - private var request: Request? = null private var rowsAdapter: ArrayObjectAdapter? = null - private var tvListViewModel = TVListViewModel() + var tvListViewModel = TVListViewModel() private var sharedPref: SharedPreferences? = null - -// override fun onCreateView( -// inflater: LayoutInflater, -// container: ViewGroup?, -// savedInstanceState: Bundle? -// ): View? { -// super.onCreate() -// // 使用自定义的布局文件 -// return inflater.inflate(R.layout.custom_browse_fragment, container, false) -// } + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + headersState = HEADERS_DISABLED + } override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) @@ -60,59 +53,109 @@ class MainFragment : BrowseSupportFragment() { // request?.fetchPage() } - tvListViewModel.getListLiveData().value?.forEach { tvViewModel -> - tvViewModel.videoIndex.observe(viewLifecycleOwner) { videoIndex -> - if (tvViewModel.getIsFirstTime()) { - tvViewModel.isFirstTime(false) - } else { - val tv = tvViewModel.getTV() + tvListViewModel.getTVListViewModel().value?.forEach { tvViewModel -> + tvViewModel.ready.observe(viewLifecycleOwner) { _ -> + if (tvViewModel.ready.value != null) { + Log.i(TAG, "ready ${tvViewModel.title.value}") if (tvViewModel.id.value == itemPosition) { - (activity as? MainActivity)?.play(tv) + (activity as? MainActivity)?.play(tvViewModel) // (activity as? MainActivity)?.switchInfoFragment(tv) } } } + tvViewModel.change.observe(viewLifecycleOwner) { _ -> + if (tvViewModel.change.value != null) { + Log.i(TAG, "switch to ${tvViewModel.title.value}") + if (tvViewModel.ysp() != null) { + Log.i(TAG, "${tvViewModel.title.value} to get ysp") + lifecycleScope.launch(Dispatchers.IO) { + tvViewModel.let { request?.fetchData(it) } + } + } else { + (activity as? MainActivity)?.play(tvViewModel) +// (activity as? MainActivity)?.switchInfoFragment(item) + } + setSelectedPosition( + tvViewModel.getRowPosition(), true, + SelectItemViewHolderTask(tvViewModel.getItemPosition()) + ) + Toast.makeText( + activity, + tvViewModel.title.value, + Toast.LENGTH_SHORT + ).show() + } + } } } + fun toLastPosition() { + setSelectedPosition( + selectedPosition, false, + SelectItemViewHolderTask(tvListViewModel.maxNum[selectedPosition] - 1) + ) + } + + fun toFirstPosition() { + setSelectedPosition( + selectedPosition, false, + SelectItemViewHolderTask(0) + ) + } + + override fun startHeadersTransition(withHeaders: Boolean) { +// check(mCanShowHeaders) { "Cannot start headers transition" } +// if (isInHeadersTransition || mShowingHeaders == withHeaders) { +// return +// } +// startHeadersTransitionInternal(withHeaders) + } + private fun setupUIElements() { brandColor = ContextCompat.getColor(context!!, R.color.fastlane_background) +// var headers = headersSupportFragment +// headers.setMenuVisibility(false) +// Log.i(TAG, "headers $headers") + +// setHeadersState(HEADERS_DISABLED); +// +// setHeaderPresenterSelector(object : PresenterSelector() { +// override fun getPresenter(o: Any): Presenter { +// return IconHeaderItemPresenter() +// } +// }) +// showHeaders(true) } - private fun updateRows(tv: TV) { -// 获取适配器中的数据 - val dataList = rowsAdapter?.replace(tv.id, tv) -// -//// 修改数据 -//// 这里假设 dataList 是一个可变的列表 -// dataList[position] = updatedData -// -//// 刷新适配器 -// rowsAdapter.notifyItemChanged(position) -// rowsAdapter.notifyItemRangeChanged() - } +// private fun updateRows(tv: TV) { +//// 获取适配器中的数据 +// val dataList = rowsAdapter?.replace(tv.id, tv) +//// +////// 修改数据 +////// 这里假设 dataList 是一个可变的列表 +//// dataList[position] = updatedData +//// +////// 刷新适配器 +//// rowsAdapter.notifyItemChanged(position) +//// rowsAdapter.notifyItemRangeChanged() +// } private fun loadRows() { - val list = TVList.list - rowsAdapter = ArrayObjectAdapter(ListRowPresenter()) val cardPresenter = CardPresenter(lifecycleScope) var idx: Long = 0 - for ((k, v) in list) { + for ((k, v) in TVList.list) { val listRowAdapter = ArrayObjectAdapter(cardPresenter) - var idx2 = 0 - for ((_, v1) in v) { - list2.add( - Info( - idx.toInt(), idx2, v1 - ) - ) - listRowAdapter.add(v1) - idx2++ - tvListViewModel.addTV(v1) + 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++ @@ -126,23 +169,9 @@ class MainFragment : BrowseSupportFragment() { savePosition(0) } - val tv = list2[itemPosition].item + val tvViewModel = tvListViewModel.getTVViewModel(itemPosition) + tvViewModel?.changed() - val tvModel = tvListViewModel.getTVModel(itemPosition) - if (tvModel?.ysp() != null) { - lifecycleScope.launch(Dispatchers.IO) { - tvModel.let { request?.fetchData(it) } - } - } else { - (activity as? MainActivity)?.play(tv) -// (activity as? MainActivity)?.switchInfoFragment(tv) - } - - Toast.makeText( - activity, - tv.title, - Toast.LENGTH_SHORT - ).show() (activity as? MainActivity)?.switchMainFragment() } @@ -153,6 +182,7 @@ class MainFragment : BrowseSupportFragment() { } fun savePosition(position: Int) { + tvListViewModel.setItemPosition(position) with(sharedPref!!.edit()) { putInt("position", position) apply() @@ -163,97 +193,63 @@ class MainFragment : BrowseSupportFragment() { view?.post { itemPosition-- if (itemPosition == -1) { - itemPosition = list2.size - 1 + itemPosition = tvListViewModel.size() - 1 } savePosition(itemPosition) - val l = list2[itemPosition] - - val tvModel = tvListViewModel.getTVModel(itemPosition) - if (tvModel?.ysp() != null) { - Log.i(TAG, "${tvModel.title} to get ysp") - lifecycleScope.launch(Dispatchers.IO) { - tvModel.let { request?.fetchData(it) } - } - } else { - l.item.let { (activity as? MainActivity)?.play(it) } - } - - setSelectedPosition( - l.rowPosition, false, - SelectItemViewHolderTask(l.itemPosition) - ) - Toast.makeText( - activity, - l.item.title, - Toast.LENGTH_SHORT - ).show() + val tvViewModel = tvListViewModel.getTVViewModel(itemPosition) + tvViewModel?.changed() } } fun next() { view?.post { itemPosition++ - if (itemPosition == list2.size) { + if (itemPosition == tvListViewModel.size()) { itemPosition = 0 } savePosition(itemPosition) - val l = list2[itemPosition] - - val tvModel = tvListViewModel.getTVModel(itemPosition) - if (tvModel?.ysp() != null) { - Log.i(TAG, "${tvModel.title} to get ysp") - lifecycleScope.launch(Dispatchers.IO) { - tvModel.let { request?.fetchData(it) } - } - } else { - l.item.let { (activity as? MainActivity)?.play(it) } - } - - setSelectedPosition( - l.rowPosition, false, - SelectItemViewHolderTask(l.itemPosition) - ) - Toast.makeText( - activity, - l.item.title, - Toast.LENGTH_SHORT - ).show() + val tvViewModel = tvListViewModel.getTVViewModel(itemPosition) + tvViewModel?.changed() } } fun prevSource() { view?.post { - val tv = list2[itemPosition].item - if (tv.videoUrl.size > 1) { - tv.videoIndex-- - if (tv.videoIndex == -1) { - tv.videoIndex = tv.videoUrl.size - 1 + 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() } - - (activity as? MainActivity)?.play(tv) -// (activity as? MainActivity)?.switchInfoFragment(tv) } } } fun nextSource() { view?.post { - val tv = list2[itemPosition].item - - if (tv.videoUrl.size > 1) { - tv.videoIndex++ - if (tv.videoIndex == tv.videoUrl.size) { - tv.videoIndex = 0 - } - - (activity as? MainActivity)?.play(tv) -// (activity as? MainActivity)?.switchInfoFragment(tv) + val tvViewModel = tvListViewModel.getTVViewModel(itemPosition) + if (tvViewModel != null) { + tvViewModel.changed() +// if (tvViewModel.videoUrl.value!!.size > 1) { +// val videoIndex = tvViewModel.videoIndex.value?.plus(1) +// if (videoIndex == tvViewModel.videoUrl.value!!.size) { +// tvViewModel.setVideoIndex(0) +// } +// tvViewModel.changed() +// } } } } + fun tvViewModel(): TVViewModel? { + return tvListViewModel.getTVViewModel(itemPosition) + } + private fun setupEventListeners() { onItemViewClickedListener = ItemViewClickedListener() onItemViewSelectedListener = ItemViewSelectedListener() @@ -266,20 +262,12 @@ class MainFragment : BrowseSupportFragment() { rowViewHolder: RowPresenter.ViewHolder, row: Row ) { - if (item is TV) { - itemPosition = item.id + if (item is TVViewModel) { + itemPosition = item.id.value!! savePosition(itemPosition) - val tvModel = tvListViewModel.getTVModel(itemPosition) - if (tvModel?.ysp() != null) { - Log.i(TAG, "${tvModel.title} to get ysp") - lifecycleScope.launch(Dispatchers.IO) { - tvModel.let { request?.fetchData(it) } - } - } else { - (activity as? MainActivity)?.play(item) -// (activity as? MainActivity)?.switchInfoFragment(item) - } + val tvViewModel = tvListViewModel.getTVViewModel(itemPosition) + tvViewModel?.changed() (activity as? MainActivity)?.switchMainFragment() } @@ -293,13 +281,18 @@ class MainFragment : BrowseSupportFragment() { ) { if (itemViewHolder == null) { view?.post { - val it = list2[itemPosition] - setSelectedPosition( - it.rowPosition, false, - SelectItemViewHolderTask(it.itemPosition) - ) + val tvViewModel = tvListViewModel.getTVViewModel(itemPosition) + if (tvViewModel != null) { + setSelectedPosition( + tvViewModel.getRowPosition(), false, + SelectItemViewHolderTask(tvViewModel.getItemPosition()) + ) + } } } + if (item is TVViewModel) { + tvListViewModel.setItemPositionCurrent(item.id.value!!) + } } } diff --git a/app/src/main/java/com/lizongying/mytv/PlaybackControlGlue.kt b/app/src/main/java/com/lizongying/mytv/PlaybackControlGlue.kt index b329d50..ba85436 100644 --- a/app/src/main/java/com/lizongying/mytv/PlaybackControlGlue.kt +++ b/app/src/main/java/com/lizongying/mytv/PlaybackControlGlue.kt @@ -1,6 +1,7 @@ package com.lizongying.mytv import android.content.Context +import android.util.Log import android.view.KeyEvent import android.view.View import androidx.leanback.media.MediaPlayerAdapter @@ -17,6 +18,7 @@ class PlaybackControlGlue( if (event!!.action == KeyEvent.ACTION_DOWN) { when (keyCode) { KeyEvent.KEYCODE_DPAD_CENTER -> { + Log.i(TAG, "KEYCODE_DPAD_CENTER") (context as? MainActivity)?.switchMainFragment() } @@ -50,6 +52,6 @@ class PlaybackControlGlue( } companion object { - private const val TAG = "CustomPlaybackControlGlue" + private const val TAG = "PlaybackControlGlue" } } diff --git a/app/src/main/java/com/lizongying/mytv/PlaybackFragment.kt b/app/src/main/java/com/lizongying/mytv/PlaybackFragment.kt index 97d1220..2f4b3ca 100644 --- a/app/src/main/java/com/lizongying/mytv/PlaybackFragment.kt +++ b/app/src/main/java/com/lizongying/mytv/PlaybackFragment.kt @@ -5,9 +5,10 @@ import android.os.Bundle import android.util.Log import androidx.leanback.app.VideoSupportFragment import androidx.leanback.app.VideoSupportFragmentGlueHost -import androidx.leanback.media.PlayerAdapter import androidx.leanback.media.PlaybackTransportControlGlue +import androidx.leanback.media.PlayerAdapter import androidx.leanback.widget.PlaybackControlsRow +import com.lizongying.mytv.models.TVViewModel import java.io.IOException class PlaybackFragment : VideoSupportFragment() { @@ -29,12 +30,10 @@ class PlaybackFragment : VideoSupportFragment() { override fun showControlsOverlay(runAnimation: Boolean) { } - fun play(tv: TV) { - val videoUrl = tv.videoUrl[tv.videoIndex] - - if (videoUrl == "") { - Log.e(TAG, "tv $tv") - Log.e(TAG, "videoUrl is empty") + fun play(tvModel: TVViewModel) { + val videoUrl = tvModel.videoIndex.value?.let { tvModel.videoUrl.value?.get(it) } + if (videoUrl == null || videoUrl == "") { + Log.e(TAG, "${tvModel.title.value} videoUrl is empty") return } @@ -45,21 +44,31 @@ class PlaybackFragment : VideoSupportFragment() { lastVideoUrl = videoUrl - playerAdapter?.reset() - val glueHost = VideoSupportFragmentGlueHost(this@PlaybackFragment) mTransportControlGlue = PlaybackControlGlue(activity, playerAdapter) mTransportControlGlue.host = glueHost mTransportControlGlue.playWhenPrepared() + playerAdapter?.callback = PlayerCallback(tvModel) try { playerAdapter?.setDataSource(Uri.parse(videoUrl)) } catch (e: IOException) { + Log.e(TAG, "error $e") return } hideControlsOverlay(false) } + private inner class PlayerCallback(private var tvModel: TVViewModel) : + PlayerAdapter.Callback() { + override fun onError(adapter: PlayerAdapter?, errorCode: Int, errorMessage: String?) { + Log.e(TAG, "on error: $errorMessage") + if (tvModel.ysp() != null && tvModel.videoIndex.value!! > 0 && errorMessage == "Source error") { + tvModel.changed() + } + } + } + companion object { private const val TAG = "PlaybackVideoFragment" } diff --git a/app/src/main/java/com/lizongying/mytv/Request.kt b/app/src/main/java/com/lizongying/mytv/Request.kt index 2680665..34a8fa5 100644 --- a/app/src/main/java/com/lizongying/mytv/Request.kt +++ b/app/src/main/java/com/lizongying/mytv/Request.kt @@ -82,7 +82,7 @@ class Request(var context: Context) { fun fetchData(tvModel: TVViewModel) { val data = ysp?.switch(tvModel) - val title = tvModel.getTV().title + val title = tvModel.title.value val request = data?.let { LiveInfoRequest(it) } request?.let { yspApiService?.getLiveInfo(it) } @@ -109,6 +109,7 @@ class Request(var context: Context) { ).uppercase() Log.i(TAG, "$title url $url") tvModel.addVideoUrl(url) + tvModel.allReady() } else { Log.e(TAG, "$title key error") tvModel.firstSource() @@ -124,7 +125,7 @@ class Request(var context: Context) { } override fun onFailure(call: Call, t: Throwable) { - Log.e(TAG, "${tvModel.getTV().title} request error") + Log.e(TAG, "$title request error") tvModel.firstSource() } }) @@ -155,7 +156,8 @@ class Request(var context: Context) { if (!mapping.containsKey(item.channelName)) { continue } - val tv = TVList.list[channelType]?.get(mapping[item.channelName]) + val tv = + TVList.list[channelType]?.find { it.title == mapping[item.channelName] } if (tv != null) { tv.logo = item.tvLogo tv.pid = item.pid @@ -187,7 +189,7 @@ class Request(var context: Context) { } private fun encryptTripleDES(key: ByteArray, iv: ByteArray): String { - var plaintext = + val plaintext = """{"mver":"1","subver":"1.2","host":"www.yangshipin.cn/#/tv/home?pid=","referer":"","canvas":"YSPANGLE(Apple,AppleM1Pro,OpenGL4.1)"}""" return try { val keySpec = SecretKeySpec(key, "DESede") diff --git a/app/src/main/java/com/lizongying/mytv/TVList.kt b/app/src/main/java/com/lizongying/mytv/TVList.kt index 0ba2fce..6dcbcd1 100644 --- a/app/src/main/java/com/lizongying/mytv/TVList.kt +++ b/app/src/main/java/com/lizongying/mytv/TVList.kt @@ -1,20 +1,20 @@ package com.lizongying.mytv object TVList { - val list: Map> by lazy { + val list: Map> by lazy { setupTV() } private var count: Int = 0 - private fun setupTV(): Map> { + private fun setupTV(): Map> { val tvs = """ 央视频道 CCTV4K,,https://resources.yangshipin.cn/assets/oms/image/202306/3e9d06fd7244d950df5838750f1c6ac3456e172b51caca2c16d2282125b111e8.png?imageMogr2/format/webp,600002264,2000266303 CCTV1 综合,http://tvpull.dxhmt.cn/tv/11481-4.m3u8,https://resources.yangshipin.cn/assets/oms/image/202306/d57905b93540bd15f0c48230dbbbff7ee0d645ff539e38866e2d15c8b9f7dfcd.png?imageMogr2/format/webp,600001859,2000210103 CCTV2 财经,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226195/index.m3u8,https://resources.yangshipin.cn/assets/oms/image/202306/20115388de0207131af17eac86c33049b95d69eaff064e55653a1b941810a006.png?imageMogr2/format/webp,600001800,2000203603 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://hlsbkmgsplive.miguvideo.com/wd_r2/cctv/cctv4hd/1500/index.m3u8?&encrypt=,https://resources.yangshipin.cn/assets/oms/image/202306/f357e58fdbcc076a3d65e1f958c942b2e14f14342c60736ceed98b092d35356a.png?imageMogr2/format/webp,600001814,2000204803 +CCTV4 中文国际,http://39.134.24.161/dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226191/index.m3u8,https://resources.yangshipin.cn/assets/oms/image/202306/f357e58fdbcc076a3d65e1f958c942b2e14f14342c60736ceed98b092d35356a.png?imageMogr2/format/webp,600001814,2000204803 CCTV5 体育,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226395/index.m3u8,https://resources.yangshipin.cn/assets/oms/image/202306/0a6a7138952675983a3d854df7688557b286d59aa06166edae51506f9204d655.png?imageMogr2/format/webp,600001818,2000205103 CCTV5+ 体育赛事,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226221/index.m3u8,https://resources.yangshipin.cn/assets/oms/image/202306/649ad76a90bfef55b05db9fe52e006487280f619089099d5dc971e387fc6eff0.png?imageMogr2/format/webp,600001817,2000204503 CCTV6 电影,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226393/index.m3u8 @@ -143,7 +143,6 @@ CETV4,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226225/index.m3u8 兵团卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226214/index.m3u8 延边卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226220/index.m3u8 内蒙古卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225786/index.m3u8 -康巴卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226234/index.m3u8 电视指南,http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226987/index.m3u8 风云足球,http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226984/index.m3u8 风云剧场,http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226950/index.m3u8 @@ -206,7 +205,7 @@ CHC动作电影,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226465/in 老故事,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226236/index.m3u8 """.trimIndent() - val map: MutableMap> = mutableMapOf() + val map: MutableMap> = mutableMapOf() var channel = "" for (i in tvs.split("\n")) { @@ -218,14 +217,13 @@ CHC动作电影,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226465/in continue } val p = i.split(",") - val titleMap = map[channel] ?: mutableMapOf() + val titleMap = map[channel] ?: mutableListOf() - val tv = titleMap[p[0]] ?: TV( + val tv = TV( count, p[0], p[1].split(";").map { it.trim() } ) - count++ if (p.size > 2) { tv.logo = p[2] @@ -237,8 +235,9 @@ CHC动作电影,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226465/in tv.sid = p[4] } - titleMap[p[0]] = tv + titleMap.add(tv) map[channel] = titleMap + count++ } return map diff --git a/app/src/main/java/com/lizongying/mytv/api/YSP.kt b/app/src/main/java/com/lizongying/mytv/api/YSP.kt index 288e697..85a63e2 100644 --- a/app/src/main/java/com/lizongying/mytv/api/YSP.kt +++ b/app/src/main/java/com/lizongying/mytv/api/YSP.kt @@ -64,8 +64,8 @@ class YSP(var context: Context) { } fun switch(tvModel: TVViewModel): String { - livepid = tvModel.getTV().pid - cnlid = tvModel.getTV().sid + livepid = tvModel.pid.value!! + cnlid = tvModel.sid.value!! defn = "fhd" // guid = getGuid() diff --git a/app/src/main/java/com/lizongying/mytv/models/TVListViewModel.kt b/app/src/main/java/com/lizongying/mytv/models/TVListViewModel.kt index dff5038..5e1ab2c 100644 --- a/app/src/main/java/com/lizongying/mytv/models/TVListViewModel.kt +++ b/app/src/main/java/com/lizongying/mytv/models/TVListViewModel.kt @@ -1,47 +1,61 @@ package com.lizongying.mytv.models +import android.content.SharedPreferences +import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel -import com.lizongying.mytv.TV class TVListViewModel : ViewModel() { - private val tvListLiveData = MutableLiveData>() - private val tvModelListLiveData = MutableLiveData>() + var maxNum = mutableListOf() - fun getListLiveData(): MutableLiveData> { - return tvModelListLiveData + private var sharedPref: SharedPreferences? = null + + private val tvListViewModel = MutableLiveData>() + + private val _itemPosition = MutableLiveData() + val itemPosition: LiveData + get() = _itemPosition + + private val _itemPositionCurrent = MutableLiveData() + val itemPositionCurrent: LiveData + get() = _itemPositionCurrent + + fun getTVListViewModel(): MutableLiveData> { + return tvListViewModel } - fun addTV(tv: TV) { - val currentList = tvListLiveData.value ?: mutableListOf() - currentList.add(tv) - tvListLiveData.value = currentList - - val currentModelList = tvModelListLiveData.value ?: mutableListOf() - currentModelList.add(TVViewModel(tv)) - tvModelListLiveData.value = currentModelList + fun addTVViewModel(tvViewModel: TVViewModel) { + val currentTVModelList = tvListViewModel.value ?: mutableListOf() + currentTVModelList.add(tvViewModel) + tvListViewModel.value = currentTVModelList } - fun updateTV(tv: TV) { - val currentList = tvListLiveData.value ?: mutableListOf() - currentList[tv.id] = tv - tvListLiveData.value = currentList - - val currentModelList = tvModelListLiveData.value ?: mutableListOf() - currentModelList[tv.id].update(tv) - tvModelListLiveData.value = currentModelList + fun getTVViewModel(id: Int): TVViewModel? { + return tvListViewModel.value?.get(id) } - fun getTV(id: Int): TV? { - return tvListLiveData.value?.get(id) + fun getTVViewModelCurrent(): TVViewModel? { + return _itemPositionCurrent.value?.let { tvListViewModel.value?.get(it) } } - fun getTVModel(id: Int): TVViewModel? { - return tvModelListLiveData.value?.get(id) + fun setItemPosition(position: Int) { + _itemPosition.value = position + _itemPositionCurrent.value = position + } + + fun setItemPositionCurrent(position: Int) { + _itemPositionCurrent.value = position + } + + fun savePosition(position: Int) { + with(sharedPref!!.edit()) { + putInt("position", position) + apply() + } } fun size(): Int { - return tvListLiveData.value!!.size + return tvListViewModel.value!!.size } } \ No newline at end of file diff --git a/app/src/main/java/com/lizongying/mytv/models/TVViewModel.kt b/app/src/main/java/com/lizongying/mytv/models/TVViewModel.kt index 8b0a7dd..655f99c 100644 --- a/app/src/main/java/com/lizongying/mytv/models/TVViewModel.kt +++ b/app/src/main/java/com/lizongying/mytv/models/TVViewModel.kt @@ -56,6 +56,9 @@ class TVViewModel(private var tv: TV) : ViewModel() { private var isFirstTime = true + private var rowPosition: Int = 0 + private var itemPosition: Int = 0 + private val _id = MutableLiveData() val id: LiveData get() = _id @@ -72,6 +75,10 @@ class TVViewModel(private var tv: TV) : ViewModel() { val videoIndex: LiveData get() = _videoIndex + private val _logo = MutableLiveData() + val logo: LiveData + get() = _logo + private val _pid = MutableLiveData() val pid: LiveData get() = _pid @@ -80,34 +87,54 @@ class TVViewModel(private var tv: TV) : ViewModel() { val sid: LiveData get() = _sid - private val _backgroundImage = MutableLiveData() - val backgroundImage: LiveData - get() = _backgroundImage + private val _change = MutableLiveData() + val change: LiveData + get() = _change - fun getBackgroundImage(): String { - return tv.logo ?: "" - } - - fun updateBackgroundImage(url: String) { - _backgroundImage.value = url - } + private val _ready = MutableLiveData() + val ready: LiveData + get() = _ready fun addVideoUrl(url: String) { - tv.videoUrl = tv.videoUrl + listOf(url) + if (_videoUrl.value?.isNotEmpty() == true) { + if (_videoUrl.value!!.last().contains("cctv.cn")) { + tv.videoUrl = tv.videoUrl.subList(0, tv.videoUrl.lastIndex) + listOf(url) + } else { + tv.videoUrl = tv.videoUrl + listOf(url) + } + } else { + tv.videoUrl = tv.videoUrl + listOf(url) + } tv.videoIndex = tv.videoUrl.lastIndex _videoUrl.value = tv.videoUrl _videoIndex.value = tv.videoIndex } fun firstSource() { - if (tv.videoUrl.isNotEmpty()) { - tv.videoIndex = 0 - _videoIndex.value = 0 + if (_videoUrl.value!!.isNotEmpty()) { + setVideoIndex(0) + allReady() } else { Log.e(TAG, "no first") } } + fun changed() { + _change.value = true + } + + fun allReady() { + _ready.value = true + } + + fun setVideoIndex(videoIndex: Int) { + _videoIndex.value = videoIndex + } + + fun setLogo(url: String) { + _logo.value = url + } + init { _id.value = tv.id _title.value = tv.title @@ -125,6 +152,22 @@ class TVViewModel(private var tv: TV) : ViewModel() { isFirstTime = firstTime } + fun getRowPosition(): Int { + return rowPosition + } + + fun getItemPosition(): Int { + return itemPosition + } + + fun setRowPosition(position: Int) { + rowPosition = position + } + + fun setItemPosition(position: Int) { + itemPosition = position + } + fun update(t: TV) { tv = t } diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index eece25f..92f58ce 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -1,4 +1,7 @@ - + \ No newline at end of file