Compare commits

...
15 Commits
Author SHA1 Message Date
Li ZongYing 534c2ef759 support 4.2 2023-12-29 13:02:14 +08:00
Li ZongYing 286cec465a some info style 2023-12-29 10:44:58 +08:00
Li ZongYing 3eb6757543 change channel info style 2023-12-28 20:30:12 +08:00
Li ZongYing 72e893b73a show info 2023-12-27 23:53:03 +08:00
Li ZongYing e6530b4e9e fix still playing after exit 2023-12-27 18:17:13 +08:00
Li ZongYing c759ada907 fix still playing after exit 2023-12-27 16:26:05 +08:00
Li ZongYing bb4fd56845 fix video size on phone 2023-12-26 11:21:03 +08:00
Li ZongYing 657b8714a0 support phone 2023-12-25 19:23:45 +08:00
Li ZongYing 3b62d5e00b remove some channel 2023-12-24 12:00:16 +08:00
Li ZongYing 262c92e0ad fix 5mins 2023-12-23 23:34:53 +08:00
Li ZongYing 5ea45fdc02 support fullscreen 2023-12-22 00:02:57 +08:00
Li ZongYing b2c2847ae0 add epg 2023-12-21 17:51:26 +08:00
Li ZongYing 0285f1b3b1 remove left headers 2023-12-20 14:55:39 +08:00
Li ZongYing e6fa9a50db change sign 2023-12-18 13:37:27 +08:00
Li ZongYing 7b7db88422 test sign 2023-12-18 13:23:55 +08:00
38 changed files with 1446 additions and 1018 deletions
+16 -2
View File
@@ -4,13 +4,27 @@
## 使用
下载安装[releases](https://github.com/lizongying/my-tv/releases)
下载安装 [releases](https://github.com/lizongying/my-tv/releases/)
[my-tv](https://lyrics.run/my-tv.html)
其他地址 [my-tv](https://lyrics.run/my-tv.html)
![image](./screenshots/img.png)
![image](./screenshots/img_1.png)
## 更新日志
### v1.2.5
* 美化频道信息显示
* 优化节目单获取
### v1.2.4
* 改变换台滑动方向,上一个频道下滑,下一个频道上滑
* 软件退出时,退出播放器
* 播放相同的频道,不再重复加载
* 暂时移除部分频道
## 其他
小米电视可以使用小米电视助手进行安装
+4 -1
View File
@@ -13,7 +13,7 @@ android {
defaultConfig {
applicationId "com.lizongying.mytv"
minSdk 21
minSdk 17
targetSdk 33
versionCode VersionCode()
versionName VersionName()
@@ -38,6 +38,7 @@ android {
// libraries Gradle should build and package with your app.
abiFilters "armeabi-v7a", "arm64-v8a"
}
multiDexEnabled true
}
buildTypes {
@@ -91,6 +92,8 @@ static def VersionName() {
dependencies {
def media3_version = "1.1.1"
implementation "androidx.media3:media3-ui:$media3_version"
// For media playback using ExoPlayer
implementation "androidx.media3:media3-exoplayer:$media3_version"
+4 -3
View File
@@ -12,14 +12,15 @@
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:usesCleartextTraffic="true"
android:theme="@style/Theme.MyTV">
<activity
android:keepScreenOn="true"
android:name=".MainActivity"
android:banner="@drawable/tv"
android:banner="@drawable/logo"
android:exported="true"
android:icon="@drawable/tv"
android:logo="@drawable/tv"
android:icon="@drawable/logo"
android:logo="@drawable/logo"
android:screenOrientation="landscape">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
Binary file not shown.
Binary file not shown.
@@ -1,47 +1,22 @@
package com.lizongying.mytv
import android.content.Context
import android.graphics.Bitmap
import android.media.MediaMetadataRetriever
import android.util.Log
import android.graphics.Color
import android.view.ContextThemeWrapper
import android.view.ViewGroup
import android.widget.ImageView
import androidx.leanback.widget.ImageCardView
import androidx.leanback.widget.Presenter
import androidx.lifecycle.LifecycleCoroutineScope
import androidx.lifecycle.LifecycleOwner
import com.bumptech.glide.Glide
import com.lizongying.mytv.models.TVViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class CardPresenter(
private val lifecycleScope: LifecycleCoroutineScope,
// private val owner: LifecycleOwner
private val owner: LifecycleOwner,
) : Presenter() {
private var tvViewModel: TVViewModel? = null
fun bindData(viewModel: TVViewModel, fragment: Context?, viewHolder: ViewHolder) {
}
fun setViewModel(model: TVViewModel) {
tvViewModel = model
}
fun unbind() {
// viewModel?.backgroundImage?.removeObservers(fragment)
}
override fun onCreateViewHolder(parent: ViewGroup): ViewHolder {
val cardView = object :
ImageCardView(ContextThemeWrapper(parent.context, R.style.CustomImageCardTheme)) {
override fun setSelected(selected: Boolean) {
updateCardBackgroundColor(this)
super.setSelected(selected)
}
}
ImageCardView(ContextThemeWrapper(parent.context, R.style.CustomImageCardTheme)) {}
cardView.isFocusable = true
cardView.isFocusableInTouchMode = true
@@ -49,70 +24,39 @@ 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) }
//
// withContext(Dispatchers.Main) {
// cardView.mainImageView.setImageBitmap(videoThumbnail)
// }
// }
if (tvViewModel.logo.value != null) {
Glide.with(viewHolder.view.context)
.load(tvViewModel.logo.value)
.centerInside()
.into(cardView.mainImageView)
// tvViewModel?.update(tv)
// tvViewModel?.backgroundImage?.observe(owner) { imageUrl ->
// if (imageUrl != "") {
// Glide.with(viewHolder.view.context)
// .load(imageUrl)
// .centerCrop()
// .into(cardView.mainImageView)
// }
// }
}
cardView.setBackgroundColor(Color.WHITE)
cardView.setMainImageScaleType(ImageView.ScaleType.CENTER_INSIDE)
}
override fun onUnbindViewHolder(viewHolder: ViewHolder) {
val cardView = viewHolder.view as ImageCardView
cardView.badgeImage = null
cardView.mainImage = null
}
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)
}
}
tvViewModel.program.observe(owner) { _ ->
val program = tvViewModel.getProgramOne()
if (program != null) {
cardView.contentText = program.name
}
}
}
private fun getVideoThumbnail(url: String): Bitmap? {
val mediaMetadataRetriever = MediaMetadataRetriever()
try {
val map = HashMap<String, String>()
mediaMetadataRetriever.setDataSource(url, map)
return mediaMetadataRetriever.frameAtTime
} catch (e: Exception) {
e.printStackTrace()
} finally {
mediaMetadataRetriever.release()
}
return null
override fun onUnbindViewHolder(viewHolder: ViewHolder) {
val cardView = viewHolder.view as ImageCardView
cardView.mainImage = null
}
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 = 101
}
}
@@ -1,251 +0,0 @@
package com.lizongying.mytv
import android.content.Context
import android.media.MediaPlayer
import android.net.Uri
import android.os.Handler
import android.view.SurfaceHolder
import androidx.annotation.OptIn
import androidx.leanback.media.PlaybackGlueHost
import androidx.leanback.media.PlayerAdapter
import androidx.leanback.media.SurfaceHolderGlueHost
import androidx.media3.common.MediaItem
import androidx.media3.common.util.UnstableApi
import androidx.media3.datasource.DefaultHttpDataSource
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.exoplayer.hls.HlsMediaSource
import java.io.IOException
open class ExoPlayerAdapter(private var mContext: Context?) : PlayerAdapter() {
val mPlayer = mContext?.let { ExoPlayer.Builder(it).build() }
var mSurfaceHolderGlueHost: SurfaceHolderGlueHost? = null
val mRunnable: Runnable = object : Runnable {
override fun run() {
callback.onCurrentPositionChanged(this@ExoPlayerAdapter)
mHandler.postDelayed(this, getProgressUpdatingInterval().toLong())
}
};
val mHandler = Handler()
var mInitialized = false // true when the MediaPlayer is prepared/initialized
var mMediaSourceUri: Uri? = null
var mHasDisplay = false
var mBufferedProgress: Long = 0
var mBufferingStart = false
open fun notifyBufferingStartEnd() {
callback.onBufferingStateChanged(
this@ExoPlayerAdapter,
mBufferingStart || !mInitialized
)
}
override fun onAttachedToHost(host: PlaybackGlueHost?) {
if (host is SurfaceHolderGlueHost) {
mSurfaceHolderGlueHost = host
mSurfaceHolderGlueHost!!.setSurfaceHolderCallback(VideoPlayerSurfaceHolderCallback(this))
}
}
/**
* Will reset the [MediaPlayer] and the glue such that a new file can be played. You are
* not required to call this method before playing the first file. However you have to call it
* before playing a second one.
*/
open fun reset() {
changeToUnitialized()
// mPlayer.reset()
}
open fun changeToUnitialized() {
if (mInitialized) {
mInitialized = false
notifyBufferingStartEnd()
if (mHasDisplay) {
callback.onPreparedStateChanged(this@ExoPlayerAdapter)
}
}
}
/**
* Release internal MediaPlayer. Should not use the object after call release().
*/
open fun release() {
changeToUnitialized()
mHasDisplay = false
mPlayer?.release()
}
override fun onDetachedFromHost() {
if (mSurfaceHolderGlueHost != null) {
mSurfaceHolderGlueHost!!.setSurfaceHolderCallback(null)
mSurfaceHolderGlueHost = null
}
reset()
release()
}
/**
* @see MediaPlayer.setDisplay
*/
fun setDisplay(surfaceHolder: SurfaceHolder?) {
val hadDisplay = mHasDisplay
mHasDisplay = surfaceHolder != null
if (hadDisplay == mHasDisplay) {
return
}
mPlayer?.setVideoSurfaceHolder(surfaceHolder)
if (mHasDisplay) {
if (mInitialized) {
callback.onPreparedStateChanged(this@ExoPlayerAdapter)
}
} else {
if (mInitialized) {
callback.onPreparedStateChanged(this@ExoPlayerAdapter)
}
}
}
override fun setProgressUpdatingEnabled(enabled: Boolean) {
mHandler.removeCallbacks(mRunnable)
if (!enabled) {
return
}
mHandler.postDelayed(mRunnable, getProgressUpdatingInterval().toLong())
}
/**
* Return updating interval of progress UI in milliseconds. Subclass may override.
* @return Update interval of progress UI in milliseconds.
*/
open fun getProgressUpdatingInterval(): Int {
return 16
}
override fun isPlaying(): Boolean {
return mInitialized && mPlayer?.isPlaying ?: false
}
override fun getDuration(): Long {
if (mInitialized) {
val duration = mPlayer?.duration
if (duration != null) {
return duration.toLong()
}
}
return -1
}
override fun getCurrentPosition(): Long {
if (mInitialized) {
val currentPosition = mPlayer?.currentPosition
if (currentPosition != null) {
return currentPosition.toLong()
}
}
return -1
}
override fun play() {
if (!mInitialized || mPlayer?.isPlaying == true) {
return
}
mPlayer?.play()
callback.onPlayStateChanged(this@ExoPlayerAdapter)
callback.onCurrentPositionChanged(this@ExoPlayerAdapter)
}
override fun pause() {
if (isPlaying) {
mPlayer?.pause()
callback.onPlayStateChanged(this@ExoPlayerAdapter)
}
}
override fun seekTo(newPosition: Long) {
if (!mInitialized) {
return
}
mPlayer?.seekTo(newPosition.toInt().toLong())
}
override fun getBufferedPosition(): Long {
return mBufferedProgress
}
/**
* Sets the media source of the player witha given URI.
*
* @return Returns `true` if uri represents a new media; `false`
* otherwise.
* @see MediaPlayer.setDataSource
*/
open fun setDataSource(uri: Uri?): Boolean {
if (if (mMediaSourceUri != null) mMediaSourceUri == uri else uri == null) {
return false
}
mMediaSourceUri = uri
prepareMediaForPlaying()
return true
}
private var mHeaders: Map<String, String>? = mapOf()
fun setHeaders(headers: Map<String, String>) {
mHeaders = headers
}
@OptIn(UnstableApi::class)
private fun prepareMediaForPlaying() {
reset()
try {
if (mMediaSourceUri != null) {
val httpDataSource = DefaultHttpDataSource.Factory()
mHeaders?.let { httpDataSource.setDefaultRequestProperties(it) }
val hlsMediaSource =
HlsMediaSource.Factory(httpDataSource).createMediaSource(
MediaItem.fromUri(
mMediaSourceUri!!
)
)
mPlayer?.setMediaSource(hlsMediaSource)
} else {
return
}
} catch (e: IOException) {
e.printStackTrace()
throw RuntimeException(e)
}
mPlayer?.prepare()
mPlayer?.playWhenReady = true
callback.onPlayStateChanged(this@ExoPlayerAdapter)
}
/**
* @return True if MediaPlayer OnPreparedListener is invoked and got a SurfaceHolder if
* [PlaybackGlueHost] provides SurfaceHolder.
*/
override fun isPrepared(): Boolean {
return mInitialized && (mSurfaceHolderGlueHost == null || mHasDisplay)
}
}
internal class VideoPlayerSurfaceHolderCallback(private val playerAdapter: ExoPlayerAdapter) :
SurfaceHolder.Callback {
override fun surfaceCreated(surfaceHolder: SurfaceHolder) {
playerAdapter.setDisplay(surfaceHolder)
}
override fun surfaceChanged(surfaceHolder: SurfaceHolder, i: Int, i1: Int, i2: Int) {
// Handle surface changes if needed
}
override fun surfaceDestroyed(surfaceHolder: SurfaceHolder) {
playerAdapter.setDisplay(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
@@ -1,16 +1,23 @@
package com.lizongying.mytv
import android.os.Bundle
import android.os.Handler
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.bumptech.glide.Glide
import com.lizongying.mytv.databinding.InfoBinding
import com.lizongying.mytv.models.TVViewModel
class InfoFragment : Fragment() {
private var _binding: InfoBinding? = null
private val binding get() = _binding!!
private val handler = Handler()
private val delay: Long = 3000
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
@@ -19,8 +26,39 @@ class InfoFragment : Fragment() {
return binding.root
}
fun setInfo(info: TV) {
binding.textView.text = info.title
override fun onResume() {
super.onResume()
// Use a Handler to delay the fragment transaction
handler.postDelayed(removeRunnable, delay)
}
fun show(tvViewModel: TVViewModel) {
Log.i(TAG, "show")
binding.textView.text = tvViewModel.title.value
Glide.with(this)
.load(tvViewModel.logo.value)
.into(binding.infoLogo)
val program = tvViewModel.getProgramOne()
if (program != null) {
binding.infoDesc.text = program.name
}
handler.removeCallbacks(removeRunnable)
view?.visibility = View.VISIBLE
handler.postDelayed(removeRunnable, delay)
}
override fun onPause() {
super.onPause()
Log.i(TAG, "onPause")
// Cancel the delayed task when the fragment is paused
handler.removeCallbacks(removeRunnable)
}
private val removeRunnable = Runnable {
Log.i(TAG, "hide")
view?.visibility = View.GONE
}
override fun onDestroyView() {
@@ -10,7 +10,10 @@ import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.util.Log
import android.view.GestureDetector
import android.view.KeyEvent
import android.view.MotionEvent
import android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
import android.view.WindowManager
import android.widget.ImageView
import android.widget.LinearLayout
@@ -18,57 +21,48 @@ 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
class MainActivity : FragmentActivity() {
private val playbackFragment = PlaybackFragment()
var playerFragment = PlayerFragment()
private val mainFragment = MainFragment()
private val infoFragment = InfoFragment()
private var doubleBackToExitPressedOnce = false
private lateinit var gestureDetector: GestureDetector
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
window.decorView.systemUiVisibility = SYSTEM_UI_FLAG_HIDE_NAVIGATION
if (savedInstanceState == null) {
supportFragmentManager.beginTransaction()
.add(R.id.main_browse_fragment, playbackFragment)
.add(R.id.main_browse_fragment, mainFragment)
.add(R.id.main_browse_fragment, playerFragment)
.add(R.id.main_browse_fragment, infoFragment)
.add(R.id.main_browse_fragment, mainFragment)
.hide(infoFragment)
.commit()
mainFragment.view?.requestFocus()
}
gestureDetector = GestureDetector(this, GestureListener())
}
fun switchInfoFragment(tv: TV) {
infoFragment.setInfo(tv)
if (infoFragment.isHidden) {
supportFragmentManager.beginTransaction().show(infoFragment).commit()
}
fun showInfoFragment(tvViewModel: TVViewModel) {
infoFragment.show(tvViewModel)
}
fun showInfoFragment(tv: TV) {
infoFragment.setInfo(tv)
supportFragmentManager.beginTransaction()
.show(infoFragment)
.commit()
}
fun hideInfoFragment() {
supportFragmentManager.beginTransaction()
.hide(infoFragment)
.commit()
}
fun play(tv: TV) {
Log.i(TAG, "play: $tv")
playbackFragment.play(tv)
fun play(tvViewModel: TVViewModel) {
Log.i(TAG, "play: ${tvViewModel.getTV()}")
playerFragment.play(tvViewModel)
mainFragment.view?.requestFocus()
}
fun prev() {
@@ -79,11 +73,11 @@ class MainActivity : FragmentActivity() {
mainFragment.next()
}
fun prevSource() {
private fun prevSource() {
mainFragment.prevSource()
}
fun nextSource() {
private fun nextSource() {
mainFragment.nextSource()
}
@@ -104,7 +98,7 @@ class MainActivity : FragmentActivity() {
mainFragment.focus()
}
fun mainFragmentIsHidden(): Boolean {
private fun mainFragmentIsHidden(): Boolean {
return mainFragment.isHidden
}
@@ -116,6 +110,54 @@ class MainActivity : FragmentActivity() {
}
}
override fun onTouchEvent(event: MotionEvent?): Boolean {
if (event != null) {
gestureDetector.onTouchEvent(event)
}
return super.onTouchEvent(event)
}
private inner class GestureListener : GestureDetector.SimpleOnGestureListener() {
override fun onSingleTapConfirmed(e: MotionEvent): Boolean {
Log.i(TAG, "onSingleTapConfirmed")
switchMainFragment()
return true
}
override fun onFling(
e1: MotionEvent,
e2: MotionEvent,
velocityX: Float,
velocityY: Float
): Boolean {
if (velocityY > 0) {
if (mainFragment.isHidden) {
prev()
} else {
// if (mainFragment.selectedPosition == 0) {
// mainFragment.setSelectedPosition(
// mainFragment.tvListViewModel.maxNum.size - 1,
// false
// )
// }
}
}
if (velocityY < 0) {
if (mainFragment.isHidden) {
next()
} else {
// if (mainFragment.selectedPosition == mainFragment.tvListViewModel.maxNum.size - 1) {
//// mainFragment.setSelectedPosition(0, false)
// hideMainFragment()
// return false
// }
}
}
return super.onFling(e1, e2, velocityX, velocityY)
}
}
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
when (keyCode) {
KeyEvent.KEYCODE_BACK -> {
@@ -171,31 +213,58 @@ 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()
}
// if (mainFragment.isHidden) {
// prevSource()
// } else {
//// if (mainFragment.tvListViewModel.getTVViewModelCurrent()
//// ?.getItemPosition() == 0
//// ) {
////// mainFragment.toLastPosition()
//// hideMainFragment()
//// }
// }
}
KeyEvent.KEYCODE_DPAD_RIGHT -> {
if (mainFragment.isHidden) {
nextSource()
}
// if (mainFragment.isHidden) {
// nextSource()
// } else {
//// if (mainFragment.tvListViewModel.getTVViewModelCurrent()
//// ?.getItemPosition() == mainFragment.tvListViewModel.maxNum[mainFragment.selectedPosition] - 1
//// ) {
//// mainFragment.toFirstPosition()
//// }
// }
}
}
@@ -3,9 +3,9 @@ package com.lizongying.mytv
import android.content.Context
import android.content.SharedPreferences
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.util.Log
import android.widget.Toast
import androidx.core.content.ContextCompat
import androidx.leanback.app.BrowseSupportFragment
import androidx.leanback.widget.ArrayObjectAdapter
import androidx.leanback.widget.HeaderItem
@@ -18,101 +18,173 @@ import androidx.leanback.widget.Presenter
import androidx.leanback.widget.Row
import androidx.leanback.widget.RowPresenter
import androidx.lifecycle.lifecycleScope
import com.lizongying.mytv.Utils.getDateTimestamp
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<Info> = 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)
// }
private var lastVideoUrl: String = ""
private val handler = Handler(Looper.getMainLooper())
private lateinit var mUpdateProgramRunnable: UpdateProgramRunnable
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
headersState = HEADERS_DISABLED
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
sharedPref = activity?.getPreferences(Context.MODE_PRIVATE)
setupUIElements()
request = activity?.let { Request(it) }
loadRows()
mUpdateProgramRunnable = UpdateProgramRunnable()
handler.post(mUpdateProgramRunnable)
setupEventListeners()
view?.post {
// request?.fetchPage()
// tvListViewModel.getTVViewModel(0)?.let { request?.fetchProgram(it) }
}
tvListViewModel.getTVListViewModel().value?.forEach { tvViewModel ->
tvViewModel.ready.observe(viewLifecycleOwner) { _ ->
tvListViewModel.getListLiveData().value?.forEach { tvViewModel ->
tvViewModel.videoIndex.observe(viewLifecycleOwner) { videoIndex ->
if (tvViewModel.getIsFirstTime()) {
tvViewModel.isFirstTime(false)
} else {
val tv = tvViewModel.getTV()
if (tvViewModel.id.value == itemPosition) {
(activity as? MainActivity)?.play(tv)
// (activity as? MainActivity)?.switchInfoFragment(tv)
// 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 != null) {
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)
setSelectedPosition(
tvViewModel.getRowPosition(), true,
SelectItemViewHolderTask(tvViewModel.getItemPosition())
)
}
}
}
}
}
}
private fun setupUIElements() {
brandColor = ContextCompat.getColor(context!!, R.color.fastlane_background)
override fun onDestroy() {
super.onDestroy()
handler.removeCallbacks(mUpdateProgramRunnable)
}
private fun updateRows(tv: TV) {
// 获取适配器中的数据
val dataList = rowsAdapter?.replace(tv.id, tv)
//
//// 修改数据
//// 这里假设 dataList 是一个可变的列表
// dataList[position] = updatedData
//
//// 刷新适配器
// rowsAdapter.notifyItemChanged(position)
// rowsAdapter.notifyItemRangeChanged()
fun updateProgram(tvViewModel: TVViewModel) {
val timestamp = getDateTimestamp()
if (timestamp - tvViewModel.programUpdateTime > 60) {
if (tvViewModel.program.value!!.isEmpty()) {
tvViewModel.programUpdateTime = timestamp
request?.fetchProgram(tvViewModel)
} else {
if (timestamp - tvViewModel.program.value!!.last().et < 600) {
tvViewModel.programUpdateTime = timestamp
request?.fetchProgram(tvViewModel)
}
}
}
}
inner class UpdateProgramRunnable : Runnable {
override fun run() {
tvListViewModel.getTVListViewModel().value?.filter { it.programId.value != null }
?.forEach { tvViewModel ->
updateProgram(
tvViewModel
)
}
handler.postDelayed(this, 60000)
}
}
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 toLastPosition() {
setSelectedPosition(
selectedPosition, false,
SelectItemViewHolderTask(tvListViewModel.maxNum[selectedPosition] - 1)
)
}
fun toFirstPosition() {
setSelectedPosition(
selectedPosition, false,
SelectItemViewHolderTask(0)
)
}
override fun startHeadersTransition(withHeaders: Boolean) {
}
private fun loadRows() {
val list = TVList.list
rowsAdapter = ArrayObjectAdapter(ListRowPresenter())
val cardPresenter = CardPresenter(lifecycleScope)
val cardPresenter = CardPresenter(viewLifecycleOwner)
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 +198,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 +211,7 @@ class MainFragment : BrowseSupportFragment() {
}
fun savePosition(position: Int) {
tvListViewModel.setItemPosition(position)
with(sharedPref!!.edit()) {
putInt("position", position)
apply()
@@ -163,97 +222,62 @@ 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
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()
}
(activity as? MainActivity)?.play(tv)
// (activity as? MainActivity)?.switchInfoFragment(tv)
}
}
}
fun tvViewModel(): TVViewModel? {
return tvListViewModel.getTVViewModel(itemPosition)
}
private fun setupEventListeners() {
onItemViewClickedListener = ItemViewClickedListener()
onItemViewSelectedListener = ItemViewSelectedListener()
@@ -266,21 +290,14 @@ class MainFragment : BrowseSupportFragment() {
rowViewHolder: RowPresenter.ViewHolder,
row: Row
) {
if (item is TV) {
itemPosition = item.id
savePosition(itemPosition)
if (item is TVViewModel) {
if (itemPosition != item.id.value!!) {
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()
}
}
@@ -291,18 +308,17 @@ class MainFragment : BrowseSupportFragment() {
itemViewHolder: Presenter.ViewHolder?, item: Any?,
rowViewHolder: RowPresenter.ViewHolder, row: Row
) {
if (itemViewHolder == null) {
view?.post {
val it = list2[itemPosition]
setSelectedPosition(
it.rowPosition, false,
SelectItemViewHolderTask(it.itemPosition)
)
}
if (item is TVViewModel) {
tvListViewModel.setItemPositionCurrent(item.id.value!!)
}
}
}
override fun onResume() {
super.onResume()
view!!.requestFocus()
}
companion object {
private const val TAG = "MainFragment"
}
@@ -1,55 +0,0 @@
package com.lizongying.mytv
import android.content.Context
import android.view.KeyEvent
import android.view.View
import androidx.leanback.media.MediaPlayerAdapter
import androidx.leanback.media.PlaybackTransportControlGlue
import androidx.leanback.media.PlayerAdapter
class PlaybackControlGlue(
context: Context?,
playerAdapter: PlayerAdapter?,
) :
PlaybackTransportControlGlue<PlayerAdapter>(context, playerAdapter) {
override fun onKey(v: View?, keyCode: Int, event: KeyEvent?): Boolean {
if (event!!.action == KeyEvent.ACTION_DOWN) {
when (keyCode) {
KeyEvent.KEYCODE_DPAD_CENTER -> {
(context as? MainActivity)?.switchMainFragment()
}
KeyEvent.KEYCODE_DPAD_UP -> {
if ((context as? MainActivity)?.mainFragmentIsHidden() == true) {
(context as? MainActivity)?.prev()
}
}
KeyEvent.KEYCODE_DPAD_DOWN -> {
if ((context as? MainActivity)?.mainFragmentIsHidden() == true) {
(context as? MainActivity)?.next()
}
}
KeyEvent.KEYCODE_DPAD_LEFT -> {
if ((context as? MainActivity)?.mainFragmentIsHidden() == true) {
(context as? MainActivity)?.prevSource()
}
}
KeyEvent.KEYCODE_DPAD_RIGHT -> {
if ((context as? MainActivity)?.mainFragmentIsHidden() == true) {
(context as? MainActivity)?.nextSource()
}
}
}
}
return super.onKey(v, keyCode, event)
}
companion object {
private const val TAG = "CustomPlaybackControlGlue"
}
}
@@ -1,66 +0,0 @@
package com.lizongying.mytv
import android.net.Uri
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.widget.PlaybackControlsRow
import java.io.IOException
class PlaybackFragment : VideoSupportFragment() {
private lateinit var mTransportControlGlue: PlaybackTransportControlGlue<PlayerAdapter>
private var playerAdapter: ExoPlayerAdapter? = null
private var lastVideoUrl: String = ""
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
playerAdapter = ExoPlayerAdapter(context)
playerAdapter?.setRepeatAction(PlaybackControlsRow.RepeatAction.INDEX_NONE)
view?.isFocusable = false
view?.isFocusableInTouchMode = false
}
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")
return
}
if (videoUrl == lastVideoUrl) {
Log.e(TAG, "videoUrl is duplication")
return
}
lastVideoUrl = videoUrl
playerAdapter?.reset()
val glueHost = VideoSupportFragmentGlueHost(this@PlaybackFragment)
mTransportControlGlue = PlaybackControlGlue(activity, playerAdapter)
mTransportControlGlue.host = glueHost
mTransportControlGlue.playWhenPrepared()
try {
playerAdapter?.setDataSource(Uri.parse(videoUrl))
} catch (e: IOException) {
return
}
hideControlsOverlay(false)
}
companion object {
private const val TAG = "PlaybackVideoFragment"
}
}
@@ -0,0 +1,115 @@
package com.lizongying.mytv
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.ViewTreeObserver
import androidx.annotation.OptIn
import androidx.fragment.app.Fragment
import androidx.media3.common.MediaItem
import androidx.media3.common.PlaybackException
import androidx.media3.common.Player
import androidx.media3.common.VideoSize
import androidx.media3.common.util.UnstableApi
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.ui.PlayerView
import com.lizongying.mytv.databinding.PlayerBinding
import com.lizongying.mytv.models.TVViewModel
class PlayerFragment : Fragment() {
private var _binding: PlayerBinding? = null
private var playerView: PlayerView? = null
private var videoUrl: String? = null
private var tvViewModel: TVViewModel? = null
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = PlayerBinding.inflate(inflater, container, false)
playerView = _binding!!.playerView
(activity as MainActivity).playerFragment = this
playerView?.viewTreeObserver?.addOnGlobalLayoutListener(object :
ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
playerView!!.viewTreeObserver.removeOnGlobalLayoutListener(this)
playerView!!.player = activity?.let {
ExoPlayer.Builder(it)
.build()
}
playerView!!.player?.playWhenReady = true
playerView!!.player?.addListener(object : Player.Listener {
override fun onVideoSizeChanged(videoSize: VideoSize) {
val aspectRatio = 16f / 9f
val layoutParams = playerView?.layoutParams
layoutParams?.width =
(playerView?.measuredHeight?.times(aspectRatio))?.toInt()
playerView?.layoutParams = layoutParams
}
//
// override fun onPlayerError(error: PlaybackException) {
// super.onPlayerError(error)
// }
})
if (videoUrl !== null) {
playerView!!.player?.run {
videoUrl?.let { MediaItem.fromUri(it) }?.let { setMediaItem(it) }
prepare()
}
videoUrl = null
}
}
})
Log.i(TAG, "PlayerFragment onCreateView")
return _binding!!.root
}
@OptIn(UnstableApi::class)
fun play(tvViewModel: TVViewModel) {
this.tvViewModel = tvViewModel
val videoUrlCurrent =
tvViewModel.videoIndex.value?.let { tvViewModel.videoUrl.value?.get(it) }
if (playerView == null || playerView?.player == null) {
Log.i(TAG, "playerView not ready $view}")
videoUrl = videoUrlCurrent
} else {
Log.i(TAG, "playerView ok")
playerView?.player?.run {
val mediaItem = MediaItem.Builder()
tvViewModel.id.value?.let { mediaItem.setMediaId(it.toString()) }
videoUrlCurrent?.let { mediaItem.setUri(it) }
setMediaItem(mediaItem.build())
prepare()
}
}
}
override fun onStart() {
super.onStart()
if (playerView != null) {
playerView!!.player?.play()
}
}
override fun onStop() {
super.onStop()
if (playerView != null) {
playerView!!.player?.stop()
}
}
override fun onDestroy() {
super.onDestroy()
if (playerView != null) {
playerView!!.player?.release()
}
}
companion object {
private const val TAG = "PlaybackVideoFragment"
}
}
@@ -1,17 +1,23 @@
package com.lizongying.mytv
import android.content.Context
import android.os.Handler
import android.os.Looper
import android.util.Base64
import android.util.Log
import com.lizongying.mytv.Utils.getDateFormat
import com.lizongying.mytv.api.ApiClient
import com.lizongying.mytv.api.BtraceClient
import com.lizongying.mytv.api.LiveInfo
import com.lizongying.mytv.api.LiveInfoRequest
import com.lizongying.mytv.api.ProtoClient
import com.lizongying.mytv.api.YSP
import com.lizongying.mytv.api.YSPApiService
import com.lizongying.mytv.api.YSPBtraceService
import com.lizongying.mytv.api.YSPProtoService
import com.lizongying.mytv.models.TVViewModel
import com.lizongying.mytv.proto.Ysp.cn.yangshipin.oms.common.proto.pageModel
import com.lizongying.mytv.proto.Ysp.cn.yangshipin.omstv.common.proto.epgProgramModel
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
@@ -23,10 +29,15 @@ import javax.crypto.spec.SecretKeySpec
class Request(var context: Context) {
private var ysp: YSP? = null
private var yspApiService: YSPApiService? = null
private var yspBtraceService: YSPBtraceService? = null
private var yspProtoService: YSPProtoService? = null
// TODO onDestroy
private val handler = Handler(Looper.getMainLooper())
private lateinit var myRunnable: MyRunnable
private var mapping = mapOf(
"CCTV4K" to "CCTV4K",
"CCTV4K" to "CCTV4K 超高清",
"CCTV1" to "CCTV1 综合",
"CCTV2" to "CCTV2 财经",
"CCTV4" to "CCTV4 中文国际",
@@ -43,11 +54,11 @@ class Request(var context: Context) {
"CCTV16-HD" to "CCTV16 奥林匹克",
"CCTV17" to "CCTV17 农业农村",
"CGTN" to "CGTN",
"CGTN外语纪录频道" to "CGTN 纪录频道",
"CGTN法语频道" to "CGTN 法语频道",
"CGTN俄语频道" to "CGTN 俄语频道",
"CGTN阿拉伯语频道" to "CGTN 阿拉伯语频道",
"CGTN西班牙语频道" to "CGTN 西班牙语频道",
"CGTN外语纪录频道" to "CGTN 纪录频道",
"东方卫视" to "东方卫视",
"湖南卫视" to "湖南卫视",
@@ -77,12 +88,18 @@ class Request(var context: Context) {
ysp = YSP(context)
}
yspApiService = ApiClient().yspApiService
yspBtraceService = BtraceClient().yspBtraceService
yspProtoService = ProtoClient().yspProtoService
}
fun fetchData(tvModel: TVViewModel) {
if (::myRunnable.isInitialized) {
handler.removeCallbacks(myRunnable)
}
tvModel.seq = 0
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 +126,10 @@ class Request(var context: Context) {
).uppercase()
Log.i(TAG, "$title url $url")
tvModel.addVideoUrl(url)
tvModel.allReady()
myRunnable = MyRunnable(tvModel)
handler.post(myRunnable)
} else {
Log.e(TAG, "$title key error")
tvModel.firstSource()
@@ -124,12 +145,55 @@ class Request(var context: Context) {
}
override fun onFailure(call: Call<LiveInfo>, t: Throwable) {
Log.e(TAG, "${tvModel.getTV().title} request error")
Log.e(TAG, "$title request error")
tvModel.firstSource()
}
})
}
inner class MyRunnable(private val tvModel: TVViewModel) : Runnable {
override fun run() {
fetchBtrace(tvModel)
handler.postDelayed(this, 60000)
}
}
fun fetchBtrace(tvModel: TVViewModel) {
val title = tvModel.title.value
val guid = ysp?.getGuid()!!
val pid = tvModel.pid.value!!
val sid = tvModel.sid.value!!
yspBtraceService?.kvcollect(
c_timestamp = ysp?.generateGuid()!!,
guid = guid,
c_guid = guid,
prog = sid,
viewid = sid,
fpid = pid,
livepid = pid,
sUrl = "https://www.yangshipin.cn/#/tv/home?pid=$pid",
playno = ysp?.getRand()!!,
ftime = getDateFormat("yyyy-MM-dd HH:mm:ss"),
seq = tvModel.seq.toString(),
)
?.enqueue(object : Callback<Void> {
override fun onResponse(call: Call<Void>, response: Response<Void>) {
if (response.isSuccessful) {
Log.d(TAG, "$title kvcollect success")
} else {
Log.e(TAG, "$title kvcollect status error")
tvModel.firstSource()
}
}
override fun onFailure(call: Call<Void>, t: Throwable) {
Log.e(TAG, "$title kvcollect request error")
}
})
tvModel.seq++
}
fun fetchPage() {
yspProtoService?.getPage()?.enqueue(object : Callback<pageModel.Response> {
override fun onResponse(
@@ -155,7 +219,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
@@ -172,22 +237,31 @@ class Request(var context: Context) {
})
}
fun fetchProgram(tvModel: TVViewModel) {
yspProtoService?.getProgram("", "")?.enqueue(object : Callback<LiveInfo> {
override fun onResponse(call: Call<LiveInfo>, response: Response<LiveInfo>) {
if (response.isSuccessful) {
val liveInfo = response.body()
Log.i(TAG, "${liveInfo?.data?.playurl}")
fun fetchProgram(tvViewModel: TVViewModel) {
val title = tvViewModel.title.value
yspProtoService?.getProgram(tvViewModel.programId.value!!, getDateFormat("yyyyMMdd"))
?.enqueue(object : Callback<epgProgramModel.Response> {
override fun onResponse(
call: Call<epgProgramModel.Response>,
response: Response<epgProgramModel.Response>
) {
if (response.isSuccessful) {
val program = response.body()
if (program != null) {
tvViewModel.addProgram(program.dataListList)
Log.i(TAG, "$title program ${program.dataListList.size}")
}
}
}
}
override fun onFailure(call: Call<LiveInfo>, t: Throwable) {
}
})
override fun onFailure(call: Call<epgProgramModel.Response>, t: Throwable) {
Log.e(TAG, "$title program request failed $t")
}
})
}
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")
@@ -10,6 +10,7 @@ data class TV(
var logo: String = "",
var pid: String = "",
var sid: String = "",
var programId: String = "",
) : Serializable {
override fun toString(): String {
@@ -21,6 +22,7 @@ data class TV(
", logo='" + logo + '\'' +
", pid='" + pid + '\'' +
", sid='" + sid + '\'' +
", programId='" + programId + '\'' +
'}'
}
+309 -193
View File
@@ -1,149 +1,283 @@
package com.lizongying.mytv
object TVList {
val list: Map<String, Map<String, TV>> by lazy {
val list: Map<String, List<TV>> by lazy {
setupTV()
}
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",
"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",
"CCTV4" to "https://resources.yangshipin.cn/assets/oms/image/202306/f357e58fdbcc076a3d65e1f958c942b2e14f14342c60736ceed98b092d35356a.png?imageMogr2/format/webp",
"CCTV4 中文国际" to "https://resources.yangshipin.cn/assets/oms/image/202306/f357e58fdbcc076a3d65e1f958c942b2e14f14342c60736ceed98b092d35356a.png?imageMogr2/format/webp",
"CCTV5" to "https://resources.yangshipin.cn/assets/oms/image/202306/0a6a7138952675983a3d854df7688557b286d59aa06166edae51506f9204d655.png?imageMogr2/format/webp",
"CCTV5 体育" to "https://resources.yangshipin.cn/assets/oms/image/202306/0a6a7138952675983a3d854df7688557b286d59aa06166edae51506f9204d655.png?imageMogr2/format/webp",
"CCTV5+" to "https://resources.yangshipin.cn/assets/oms/image/202306/649ad76a90bfef55b05db9fe52e006487280f619089099d5dc971e387fc6eff0.png?imageMogr2/format/webp",
"CCTV5+ 体育赛事" to "https://resources.yangshipin.cn/assets/oms/image/202306/649ad76a90bfef55b05db9fe52e006487280f619089099d5dc971e387fc6eff0.png?imageMogr2/format/webp",
"CCTV6" to "https://resources.yangshipin.cn/assets/oms/image/202306/741515efda91f03f455df8a7da4ee11fa9329139c276435cf0a9e2af398d5bf2.png?imageMogr2/format/webp",
"CCTV6 电影" to "https://resources.yangshipin.cn/assets/oms/image/202306/741515efda91f03f455df8a7da4ee11fa9329139c276435cf0a9e2af398d5bf2.png?imageMogr2/format/webp",
"CCTV7" to "https://resources.yangshipin.cn/assets/oms/image/202306/b29af94e295ebdf646cefb68122c429b9cd921f498ca20d2d8070252536f9ff9.png?imageMogr2/format/webp",
"CCTV7 国防军事" to "https://resources.yangshipin.cn/assets/oms/image/202306/b29af94e295ebdf646cefb68122c429b9cd921f498ca20d2d8070252536f9ff9.png?imageMogr2/format/webp",
"CCTV8" to "https://resources.yangshipin.cn/assets/oms/image/202306/ad51de94426a0ba039e6dd6a8534ea98ecc813a6176bde87b4f18cc34d6d7590.png?imageMogr2/format/webp",
"CCTV8 电视剧" to "https://resources.yangshipin.cn/assets/oms/image/202306/ad51de94426a0ba039e6dd6a8534ea98ecc813a6176bde87b4f18cc34d6d7590.png?imageMogr2/format/webp",
"CCTV9" to "https://resources.yangshipin.cn/assets/oms/image/202306/2ed1b4deeca179d5db806bb941790f82eb92a1b7299c1c38fe027f95a5caee5e.png?imageMogr2/format/webp",
"CCTV9 记录" to "https://resources.yangshipin.cn/assets/oms/image/202306/2ed1b4deeca179d5db806bb941790f82eb92a1b7299c1c38fe027f95a5caee5e.png?imageMogr2/format/webp",
"CCTV10" to "https://resources.yangshipin.cn/assets/oms/image/202306/aa6157ec65188cd41826e5a2f088c3d6d153205f5f6428258d12c59999e221aa.png?imageMogr2/format/webp",
"CCTV10 科教" to "https://resources.yangshipin.cn/assets/oms/image/202306/aa6157ec65188cd41826e5a2f088c3d6d153205f5f6428258d12c59999e221aa.png?imageMogr2/format/webp",
"CCTV11" to "https://resources.yangshipin.cn/assets/oms/image/202306/ed12ed7c7a1034dae4350011fe039284c5d5a836506b28c9e32e3c75299625c0.png?imageMogr2/format/webp",
"CCTV11 戏曲" to "https://resources.yangshipin.cn/assets/oms/image/202306/ed12ed7c7a1034dae4350011fe039284c5d5a836506b28c9e32e3c75299625c0.png?imageMogr2/format/webp",
"CCTV12" to "https://resources.yangshipin.cn/assets/oms/image/202306/484083cffaa40df7e659565e8cb4d1cc740158a185512114167aa21fa0c59240.png?imageMogr2/format/webp",
"CCTV12 社会与法" to "https://resources.yangshipin.cn/assets/oms/image/202306/484083cffaa40df7e659565e8cb4d1cc740158a185512114167aa21fa0c59240.png?imageMogr2/format/webp",
"CCTV13" to "https://resources.yangshipin.cn/assets/oms/image/202306/266da7b43c03e2312186b4a999e0f060e8f15b10d2cc2c9aa32171819254cf1a.png?imageMogr2/format/webp",
"CCTV13 新闻" to "https://resources.yangshipin.cn/assets/oms/image/202306/266da7b43c03e2312186b4a999e0f060e8f15b10d2cc2c9aa32171819254cf1a.png?imageMogr2/format/webp",
"CCTV14" to "https://resources.yangshipin.cn/assets/oms/image/202306/af6b603896938dc346fbb16abfc63c12cba54b0ec9d18770a15d347d115f12d5.png?imageMogr2/format/webp",
"CCTV14 少儿" to "https://resources.yangshipin.cn/assets/oms/image/202306/af6b603896938dc346fbb16abfc63c12cba54b0ec9d18770a15d347d115f12d5.png?imageMogr2/format/webp",
"CCTV15" to "https://resources.yangshipin.cn/assets/oms/image/202306/2ceee92188ef684efe0d8b90839c4f3ad450d179dc64d59beff417059453af47.png?imageMogr2/format/webp",
"CCTV15 音乐" to "https://resources.yangshipin.cn/assets/oms/image/202306/2ceee92188ef684efe0d8b90839c4f3ad450d179dc64d59beff417059453af47.png?imageMogr2/format/webp",
"CCTV16" to "https://resources.yangshipin.cn/assets/oms/image/202306/53793fa7bacd3a93ff6dc5d2758418985e1f952a316c335d663b572d8bdcd74d.png?imageMogr2/format/webp",
"CCTV16 奥林匹克" to "https://resources.yangshipin.cn/assets/oms/image/202306/53793fa7bacd3a93ff6dc5d2758418985e1f952a316c335d663b572d8bdcd74d.png?imageMogr2/format/webp",
"CCTV17" to "https://resources.yangshipin.cn/assets/oms/image/202306/ddef563072f8bad2bea5b9e52674cb7b4ed50efb20c26e61994dfbdf05c1e3c0.png?imageMogr2/format/webp",
"CCTV17 农业农村" to "https://resources.yangshipin.cn/assets/oms/image/202306/ddef563072f8bad2bea5b9e52674cb7b4ed50efb20c26e61994dfbdf05c1e3c0.png?imageMogr2/format/webp",
"CGTN" to "https://resources.yangshipin.cn/assets/oms/image/202306/a72dff758ca1c17cd0ecc8cedc11b893d208f409d5e6302faa0e9d298848abc3.png?imageMogr2/format/webp",
"CGTN 法语频道" to "https://resources.yangshipin.cn/assets/oms/image/202306/a8d0046a47433d952bf6ed17062deb8bd2184ba9aec0f7781df6bf9487a3ffcf.png?imageMogr2/format/webp",
"CGTN 俄语频道" to "https://resources.yangshipin.cn/assets/oms/image/202306/bf0a820893cbaf20dd0333e27042e1ef9c8806e5b602b6a8c95af399db0bc77a.png?imageMogr2/format/webp",
"CGTN 阿拉伯语频道" to "https://resources.yangshipin.cn/assets/oms/image/202306/2e44e2aa3e7a1cedf07fd0ae59fe69e86a60a2632660a006e3e9e7397b2d107e.png?imageMogr2/format/webp",
"CGTN 西班牙语频道" to "https://resources.yangshipin.cn/assets/oms/image/202309/7c337e3dbe64402ec7e4678a619a4a6d95144e42f35161181ff78e143b7cf67a.png?imageMogr2/format/webp",
"CGTN 纪录频道" to "https://resources.yangshipin.cn/assets/oms/image/202309/74d3ac436a7e374879578de1d87a941fbf566d39d5632b027c5097891ed32bd5.png?imageMogr2/format/webp",
"风云剧场" to "https://resources.yangshipin.cn/assets/oms/image/202306/4d549e53e6d0f632d5a633d1945280797b153e588f919221a07faa869812cc89.png?imageMogr2/format/webp",
"第一剧场" to "https://resources.yangshipin.cn/assets/oms/image/202306/a556bd7d93ce65e18f243a8892b5604f4faa994a4897315914216a710a706208.png?imageMogr2/format/webp",
"怀旧剧场" to "https://resources.yangshipin.cn/assets/oms/image/202306/5661bd04fecdb6e899f801147a22ab5d3a475bf2b62e30aec2c0023190ebc9b1.png?imageMogr2/format/webp",
"风云音乐" to "https://resources.yangshipin.cn/assets/oms/image/202306/bbf1d024c5228b8dd128b0e3cb1717d173fab4ee84c3a4c8a57b1a215362ca3b.png?imageMogr2/format/webp",
"兵器科技" to "https://resources.yangshipin.cn/assets/oms/image/202306/4c6b6a6d3839889f34d33db3c2f80233b26b74d3489b393487635f8704e70796.png?imageMogr2/format/webp",
"风云足球" to "https://resources.yangshipin.cn/assets/oms/image/202306/cd1e2bb52b06a991de168733e5ff0f1d85adc8042d40c8f393f723543e5dd08a.png?imageMogr2/format/webp",
"高尔夫网球" to "https://resources.yangshipin.cn/assets/oms/image/202306/cdd1b31ede7a5ad049ed53d9a072422f829e72dd062ed2c19e077fdd01699071.png?imageMogr2/format/webp",
"女性时尚" to "https://resources.yangshipin.cn/assets/oms/image/202306/fa28955ce8b2539d728bf4c6a13a46ff57ad76eae46627f7bcfb1ed8a613d3fc.png?imageMogr2/format/webp",
"央视文化精品" to "https://resources.yangshipin.cn/assets/oms/image/202306/14ac5ce40482cacd3d4b37435222bfe86af2b452a2f04ecbfc1d13d76edd7c57.png?imageMogr2/format/webp",
"央视台球" to "https://resources.yangshipin.cn/assets/oms/image/202306/10e14a92478011aa6c3c8562e62127f3b1908e29fcd78e4b2b24b9e6d3ec2fbc.png?imageMogr2/format/webp",
"电视指南" to "https://resources.yangshipin.cn/assets/oms/image/202306/244d72c0eb1615ed7d51c2f5db5a67f306aa3f58c05bc2d34de3aa7e956dc8c9.png?imageMogr2/format/webp",
"卫生健康" to "https://resources.yangshipin.cn/assets/oms/image/202306/54a6863656fdfd8f803be193ddf22441c5000a108833889816fd2d8911715ce8.png?imageMogr2/format/webp",
"东方卫视" to "https://resources.yangshipin.cn/assets/oms/image/202306/9bd372ca292a82ce3aa08772b07efc4af1f85c21d1f268ea33440c49e9a0a488.png?imageMogr2/format/webp",
"湖南卫视" to "https://resources.yangshipin.cn/assets/oms/image/202306/4120e89d3079d08aa17d382f69a2308ec70839b278367763c34a34666c75cb88.png?imageMogr2/format/webp",
"湖北卫视" to "https://resources.yangshipin.cn/assets/oms/image/202306/7a6be5a2bb1dc53a945c016ff1f525dc4a84c51db371c15c89aa55404b0ba784.png?imageMogr2/format/webp",
"辽宁卫视" to "https://resources.yangshipin.cn/assets/oms/image/202306/ac4ed6058a87c101ae7147ebc38905d0cae047fb73fd277ee5049b84f52bda36.png?imageMogr2/format/webp",
"江苏卫视" to "https://resources.yangshipin.cn/assets/oms/image/202306/380ad685c0c1d5b2c902246b8d2df6d3f9b45e2837abcfe493075bbded597a31.png?imageMogr2/format/webp",
"江西卫视" to "https://resources.yangshipin.cn/assets/oms/image/202306/3c760d0d00463855890e8a1864ea4a6b6dd66b90c29b4ac714a4b17c16519871.png?imageMogr2/format/webp",
"山东卫视" to "https://resources.yangshipin.cn/assets/oms/image/202306/22d403f07a7cf5410b3ad3ddb65a11aa229a32475fac213f5344c9f0ec330ca1.png?imageMogr2/format/webp",
"广东卫视" to "https://resources.yangshipin.cn/assets/oms/image/202306/28886880a4dc0f06fb7e0a528a1def0591d61a65870e29176ede0cc92033bbfd.png?imageMogr2/format/webp",
"广西卫视" to "https://resources.yangshipin.cn/assets/oms/image/202306/54b7e97cb816bb223fe05f3fc44da2c7820eb66e8550c19d23100f2c414ecc38.png?imageMogr2/format/webp",
"重庆卫视" to "https://resources.yangshipin.cn/assets/oms/image/202306/657651f411de2673d1770d9a78b44c1265704f7468cc41d4be7f51d630768494.png?imageMogr2/format/webp",
"河南卫视" to "https://resources.yangshipin.cn/assets/oms/image/202306/74925962148a6d31c85808b6cd4e444c2a54bab393d2c5fc85e960b50e22fa86.png?imageMogr2/format/webp",
"河北卫视" to "https://resources.yangshipin.cn/assets/oms/image/202306/d545becdc81c60197b08c7f47380705e4665ed3fe55efc8b855e486f6e655378.png?imageMogr2/format/webp",
"贵州卫视" to "https://resources.yangshipin.cn/assets/oms/image/202306/4eb45f4781d33d872af027dc01c941559aab55667dd99cc5c22bef7037807b13.png?imageMogr2/format/webp",
"北京卫视" to "https://resources.yangshipin.cn/assets/oms/image/202306/f4f23633c578beea49a3841d88d3490100f029ee349059fa532869db889872c5.png?imageMogr2/format/webp",
"黑龙江卫视" to "https://resources.yangshipin.cn/assets/oms/image/202306/d8273ae9be698ce2db21f5b886ecac95a73429593f93713c60ed8c12c38bf0d3.png?imageMogr2/format/webp",
"浙江卫视" to "https://resources.yangshipin.cn/assets/oms/image/202306/a66c836bd98ba3e41a2e9a570d4b9c50dedc6839e9de333e2e78212ad505f37e.png?imageMogr2/format/webp",
"安徽卫视" to "https://resources.yangshipin.cn/assets/oms/image/202306/f35fa04b51b1ee4984b03578b65403570868ebca03c6c01e11b097f999a58d9b.png?imageMogr2/format/webp",
"深圳卫视" to "https://resources.yangshipin.cn/assets/oms/image/202306/d59fec04c902e3581c617136d02d4b9b8c4cbe64272781ddd3525e80c823edb7.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/6e060391fde0469801fc3d84dbf204b4f8d650d251f17d7595a6964c0bb99e81.png?imageMogr2/format/webp",
)
private var mappingEPG = mapOf(
"CCTV4K" to "600002264",
"CCTV4K 超高清" to "600002264",
"CCTV1" to "600001859",
"CCTV1 综合" to "600001859",
"CCTV2" to "600001800",
"CCTV2 财经" to "600001800",
"CCTV3" to "600001801",
"CCTV3 综艺" to "600001801",
"CCTV4" to "600001814",
"CCTV4 中文国际" to "600001814",
"CCTV5" to "600001818",
"CCTV5 体育" to "600001818",
"CCTV5+" to "600001817",
"CCTV5+ 体育赛事" to "600001817",
"CCTV6" to "600001802",
"CCTV6 电影" to "600001802",
"CCTV7" to "600004092",
"CCTV7 国防军事" to "600004092",
"CCTV8" to "600001803",
"CCTV8 电视剧" to "600001803",
"CCTV9" to "600004078",
"CCTV9 记录" to "600004078",
"CCTV10" to "600001805",
"CCTV10 科教" to "600001805",
"CCTV11" to "600001806",
"CCTV11 戏曲" to "600001806",
"CCTV12" to "600001807",
"CCTV12 社会与法" to "600001807",
"CCTV13" to "600001811",
"CCTV13 新闻" to "600001811",
"CCTV14" to "600001809",
"CCTV14 少儿" to "600001809",
"CCTV15" to "600001815",
"CCTV15 音乐" to "600001815",
"CCTV16" to "600098637",
"CCTV16 奥林匹克" to "600098637",
"CCTV17" to "600001810",
"CCTV17 农业农村" to "600001810",
"CGTN" to "600014550",
"CGTN 法语频道" to "600084704",
"CGTN 俄语频道" to "600084758",
"CGTN 阿拉伯语频道" to "600084782",
"CGTN 西班牙语频道" to "600084744",
"CGTN 记录频道" to "600084781",
"风云剧场" to "600099658",
"第一剧场" to "600099655",
"怀旧剧场" to "600099620",
"风云音乐" to "600099660",
"兵器科技" to "600099649",
"风云足球" to "600099636",
"高尔夫网球" to "600099659",
"女性时尚" to "600099650",
"央视文化精品" to "600099653",
"央视台球" to "600099652",
"电视指南" to "600099656",
"卫生健康" to "600099651",
"东方卫视" to "600002483",
"湖南卫视" to "600002475",
"湖北卫视" to "600002508",
"辽宁卫视" to "600002505",
"江苏卫视" to "600002521",
"江西卫视" to "600002503",
"山东卫视" to "600002513",
"广东卫视" to "600002485",
"广西卫视" to "600002509",
"重庆卫视" to "600002531",
"河南卫视" to "600002525",
"河北卫视" to "600002493",
"贵州卫视" to "600002490",
"北京卫视" to "600002309",
"黑龙江卫视" to "600002498",
"浙江卫视" to "600002520",
"安徽卫视" to "600002532",
"深圳卫视" to "600002481",
"四川卫视" to "600002516",
"东南卫视" to "600002484",
"海南卫视" to "600002506",
)
private var mappingVideo = mapOf(
"CCTV4K" to arrayOf("600002264", "2000266303"),
"CCTV4K 超高清" to arrayOf("600002264", "2000266303"),
"CCTV1" to arrayOf("600001859", "2000210103"),
"CCTV1 综合" to arrayOf("600001859", "2000210103"),
"CCTV2" to arrayOf("600001800", "2000203603"),
"CCTV2 财经" to arrayOf("600001800", "2000203603"),
// "CCTV3" to arrayOf("600001801",""),
// "CCTV3 综艺" to arrayOf("600001801",""),
"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",""),
// "CCTV6 电影" to arrayOf("600001802",""),
"CCTV7" to arrayOf("600004092", "2000510003"),
"CCTV7 国防军事" to arrayOf("600004092", "2000510003"),
// "CCTV8" to arrayOf("600001803",""),
// "CCTV8 电视剧" to arrayOf("600001803",""),
"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",""),
// "CCTV13 新闻" to arrayOf("600001811",""),
"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"),
"CCTV17" to arrayOf("600001810", "2000204203"),
"CCTV17 农业农村" to arrayOf("600001810", "2000204203"),
"CGTN" to arrayOf("600014550", "2001656803"),
"CGTN 法语频道" to arrayOf("600084704", "2010153503"),
"CGTN 俄语频道" to arrayOf("600084758", "2010152603"),
"CGTN 阿拉伯语频道" to arrayOf("600084782", "2010155203"),
"CGTN 西班牙语频道" to arrayOf("600084744", "2010152503"),
"CGTN 记录频道" to arrayOf("600084781", "2010155403"),
// "风云剧场" to arrayOf("600099658",""),
// "第一剧场" to arrayOf("600099655",""),
// "怀旧剧场" to arrayOf("600099620",""),
// "风云音乐" to arrayOf("600099660",""),
// "兵器科技" to arrayOf("600099649",""),
// "风云足球" to arrayOf("600099636",""),
// "高尔夫网球" to arrayOf("600099659",""),
// "女性时尚" to arrayOf("600099650",""),
// "央视文化精品" to arrayOf("600099653",""),
// "央视台球" to arrayOf("600099652",""),
// "电视指南" to arrayOf("600099656",""),
// "卫生健康" to arrayOf("600099651",""),
"东方卫视" to arrayOf("600002483", "2000292403"),
"湖南卫视" to arrayOf("600002475", "2000296203"),
"湖北卫视" to arrayOf("600002508", "2000294503"),
"辽宁卫视" to arrayOf("600002505", "2000281303"),
"江苏卫视" to arrayOf("600002521", "2000295603"),
"江西卫视" to arrayOf("600002503", "2000294103"),
"山东卫视" to arrayOf("600002513", "2000294803"),
"广东卫视" to arrayOf("600002485", "2000292703"),
"广西卫视" to arrayOf("600002509", "2000294203"),
"重庆卫视" to arrayOf("600002531", "2000297803"),
"河南卫视" to arrayOf("600002525", "2000296103"),
"河北卫视" to arrayOf("600002493", "2000293403"),
"贵州卫视" to arrayOf("600002490", "2000293303"),
"北京卫视" to arrayOf("600002309", "2000272103"),
"黑龙江卫视" to arrayOf("600002498", "2000293903"),
"浙江卫视" to arrayOf("600002520", "2000295503"),
"安徽卫视" to arrayOf("600002532", "2000298003"),
"深圳卫视" to arrayOf("600002481", "2000292203"),
"四川卫视" to arrayOf("600002516", "2000295003"),
"东南卫视" to arrayOf("600002484", "2000292503"),
"海南卫视" to arrayOf("600002506", "2000291503"),
)
private var count: Int = 0
private fun setupTV(): Map<String, Map<String, TV>> {
private fun setupTV(): Map<String, List<TV>> {
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
CCTV4K 超高清,
CCTV1 综合,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226231/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
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
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
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/3221226221/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,https://resources.yangshipin.cn/assets/oms/image/202306/b29af94e295ebdf646cefb68122c429b9cd921f498ca20d2d8070252536f9ff9.png?imageMogr2/format/webp,600004092,2000510003
CCTV7 国防军事,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226192/index.m3u8
CCTV8 电视剧,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226391/index.m3u8
CCTV9 记录,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226197/index.m3u8,https://resources.yangshipin.cn/assets/oms/image/202306/2ed1b4deeca179d5db806bb941790f82eb92a1b7299c1c38fe027f95a5caee5e.png?imageMogr2/format/webp,600004078,2000499403
CCTV10 科教,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226189/index.m3u8,https://resources.yangshipin.cn/assets/oms/image/202306/aa6157ec65188cd41826e5a2f088c3d6d153205f5f6428258d12c59999e221aa.png?imageMogr2/format/webp,600001805,2000203503
CCTV11 戏曲,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226240/index.m3u8,https://resources.yangshipin.cn/assets/oms/image/202306/ed12ed7c7a1034dae4350011fe039284c5d5a836506b28c9e32e3c75299625c0.png?imageMogr2/format/webp,600001806,2000204103
CCTV12 社会与法,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226190/index.m3u8, https://resources.yangshipin.cn/assets/oms/image/202306/484083cffaa40df7e659565e8cb4d1cc740158a185512114167aa21fa0c59240.png?imageMogr2/format/webp,600001807,2000202603
CCTV13 新闻,https://live-play.cctvnews.cctv.com/cctv/merge_cctv13.m3u8
CCTV14 少儿,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226193/index.m3u8,https://resources.yangshipin.cn/assets/oms/image/202306/af6b603896938dc346fbb16abfc63c12cba54b0ec9d18770a15d347d115f12d5.png?imageMogr2/format/webp,600001809,2000204403
CCTV15 音乐,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225785/index.m3u8,https://resources.yangshipin.cn/assets/oms/image/202306/2ceee92188ef684efe0d8b90839c4f3ad450d179dc64d59beff417059453af47.png?imageMogr2/format/webp,600001815,2000205003
CCTV16 奥林匹克,http://39.134.24.162/dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226921/index.m3u8,https://resources.yangshipin.cn/assets/oms/image/202306/53793fa7bacd3a93ff6dc5d2758418985e1f952a316c335d663b572d8bdcd74d.png?imageMogr2/format/webp,600098637,2012375003
CCTV17 农业农村,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226198/index.m3u8,https://resources.yangshipin.cn/assets/oms/image/202306/ddef563072f8bad2bea5b9e52674cb7b4ed50efb20c26e61994dfbdf05c1e3c0.png?imageMogr2/format/webp,600001810,2000204203
CGTN,,https://resources.yangshipin.cn/assets/oms/image/202306/a72dff758ca1c17cd0ecc8cedc11b893d208f409d5e6302faa0e9d298848abc3.png?imageMogr2/format/webp,600014550,2001656803
CGTN 新闻频道,http://live.cgtn.com/1000/prog_index.m3u8
CCTV9 记录,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226197/index.m3u8
CCTV10 科教,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226189/index.m3u8
CCTV11 戏曲,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226240/index.m3u8
CCTV12 社会与法,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226190/index.m3u8
CCTV13 新闻,https://live-play.cctvnews.cctv.com/cctv/merge_cctv13.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226233/index.m3u8
CCTV14 少儿,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226193/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
CCTV17 农业农村,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226198/index.m3u8
CGTN,http://live.cgtn.com/1000/prog_index.m3u8
CGTN 法语频道,https://livefr.cgtn.com/1000f/prog_index.m3u8
CGTN 俄语频道,http://liveru.cgtn.com/1000r/prog_index.m3u8
CGTN 阿拉伯语频道,http://livear.cgtn.com/1000a/prog_index.m3u8
CGTN 西班牙语频道,http://livees.cgtn.com/500e/prog_index.m3u8;http://livees.cgtn.com/1000e/prog_index.m3u8
CGTN 纪录频道,https://livedoc.cgtn.com/500d/prog_index.m3u8
CGTN 法语频道,https://livefr.cgtn.com/1000f/prog_index.m3u8,https://resources.yangshipin.cn/assets/oms/image/202306/a8d0046a47433d952bf6ed17062deb8bd2184ba9aec0f7781df6bf9487a3ffcf.png?imageMogr2/format/webp,600084704,2010153503
CGTN 俄语频道,http://liveru.cgtn.com/1000r/prog_index.m3u8,https://resources.yangshipin.cn/assets/oms/image/202306/bf0a820893cbaf20dd0333e27042e1ef9c8806e5b602b6a8c95af399db0bc77a.png?imageMogr2/format/webp,600084758,2010152603
CGTN 西班牙语频道,http://livees.cgtn.com/500e/prog_index.m3u8;http://livees.cgtn.com/1000e/prog_index.m3u8,https://resources.yangshipin.cn/assets/oms/image/202309/7c337e3dbe64402ec7e4678a619a4a6d95144e42f35161181ff78e143b7cf67a.png?imageMogr2/format/webp,600084744,2010152503
CGTN 阿拉伯语频道,http://livear.cgtn.com/1000a/prog_index.m3u8,https://resources.yangshipin.cn/assets/oms/image/202306/2e44e2aa3e7a1cedf07fd0ae59fe69e86a60a2632660a006e3e9e7397b2d107e.png?imageMogr2/format/webp,600084782,2010155203
书画频道,http://211.103.180.178:8234/live_hls/hdmi.m3u8
地方频道
东方卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226217/index.m3u8,https://resources.yangshipin.cn/assets/oms/image/202306/9bd372ca292a82ce3aa08772b07efc4af1f85c21d1f268ea33440c49e9a0a488.png?imageMogr2/format/webp,600002483,2000292403
内蒙古卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226389/index.m3u8
湖南卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226307/index.m3u8,https://resources.yangshipin.cn/assets/oms/image/202306/4120e89d3079d08aa17d382f69a2308ec70839b278367763c34a34666c75cb88.png?imageMogr2/format/webp,600002475,2000296203
湖北卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226477/index.m3u8,https://resources.yangshipin.cn/assets/oms/image/202306/7a6be5a2bb1dc53a945c016ff1f525dc4a84c51db371c15c89aa55404b0ba784.png?imageMogr2/format/webp,600002508,2000294503
辽宁卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226546/index.m3u8,https://resources.yangshipin.cn/assets/oms/image/202306/ac4ed6058a87c101ae7147ebc38905d0cae047fb73fd277ee5049b84f52bda36.png?imageMogr2/format/webp,600002505,2000281303
江苏卫视,http://39.134.24.166/dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226200/index.m3u8,https://resources.yangshipin.cn/assets/oms/image/202306/380ad685c0c1d5b2c902246b8d2df6d3f9b45e2837abcfe493075bbded597a31.png?imageMogr2/format/webp,600002521,2000295603
江西卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226344/index.m3u8,https://resources.yangshipin.cn/assets/oms/image/202306/3c760d0d00463855890e8a1864ea4a6b6dd66b90c29b4ac714a4b17c16519871.png?imageMogr2/format/webp,600002503,2000294103
西藏卫视,http://39.134.24.161/dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226212/index.m3u8
山东卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226209/index.m3u8,https://resources.yangshipin.cn/assets/oms/image/202306/22d403f07a7cf5410b3ad3ddb65a11aa229a32475fac213f5344c9f0ec330ca1.png?imageMogr2/format/webp,600002513,2000294803
山西卫视,http://39.134.24.161/dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225763/index.m3u8
广东卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226216/index.m3u8,https://resources.yangshipin.cn/assets/oms/image/202306/28886880a4dc0f06fb7e0a528a1def0591d61a65870e29176ede0cc92033bbfd.png?imageMogr2/format/webp,600002485,2000292703
广西卫视,http://live.gxrb.com.cn/tv/gxtvlive03/index.m3u8,https://resources.yangshipin.cn/assets/oms/image/202306/54b7e97cb816bb223fe05f3fc44da2c7820eb66e8550c19d23100f2c414ecc38.png?imageMogr2/format/webp,600002509,2000294203
重庆卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226409/index.m3u8,https://resources.yangshipin.cn/assets/oms/image/202306/657651f411de2673d1770d9a78b44c1265704f7468cc41d4be7f51d630768494.png?imageMogr2/format/webp,600002531,2000297803
甘肃卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221225633/index.m3u8
青海卫视,http://stream.qhbtv.com/qhws/sd/live.m3u8
陕西卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226457/index.m3u8
河南卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226480/index.m3u8,https://resources.yangshipin.cn/assets/oms/image/202306/74925962148a6d31c85808b6cd4e444c2a54bab393d2c5fc85e960b50e22fa86.png?imageMogr2/format/webp,600002525,2000296103
河北卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226406/index.m3u8,https://resources.yangshipin.cn/assets/oms/image/202306/d545becdc81c60197b08c7f47380705e4665ed3fe55efc8b855e486f6e655378.png?imageMogr2/format/webp,600002493,2000293403
云南卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226444/index.m3u8
贵州卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226474/index.m3u8,https://resources.yangshipin.cn/assets/oms/image/202306/4eb45f4781d33d872af027dc01c941559aab55667dd99cc5c22bef7037807b13.png?imageMogr2/format/webp,600002490,2000293303
新疆卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226460/index.m3u8
北京卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221225728/index.m3u8,https://resources.yangshipin.cn/assets/oms/image/202306/f4f23633c578beea49a3841d88d3490100f029ee349059fa532869db889872c5.png?imageMogr2/format/webp,600002309,2000272103
天津卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221225740/index.m3u8
黑龙江卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226327/index.m3u8,https://resources.yangshipin.cn/assets/oms/image/202306/d8273ae9be698ce2db21f5b886ecac95a73429593f93713c60ed8c12c38bf0d3.png?imageMogr2/format/webp,600002498,2000293903
吉林卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226397/index.m3u8
浙江卫视,http://hw-m-l.cztv.com/channels/lantian/channel01/1080p.m3u8,https://resources.yangshipin.cn/assets/oms/image/202306/a66c836bd98ba3e41a2e9a570d4b9c50dedc6839e9de333e2e78212ad505f37e.png?imageMogr2/format/webp,600002520,2000295503
安徽卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226391/index.m3u8,https://resources.yangshipin.cn/assets/oms/image/202306/f35fa04b51b1ee4984b03578b65403570868ebca03c6c01e11b097f999a58d9b.png?imageMogr2/format/webp,600002532,2000298003
深圳卫视,http://39.134.24.166/dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226205/index.m3u8,https://resources.yangshipin.cn/assets/oms/image/202306/d59fec04c902e3581c617136d02d4b9b8c4cbe64272781ddd3525e80c823edb7.png?imageMogr2/format/webp,600002481,2000292203
四川卫视,http://39.134.24.166/dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225768/index.m3u8,https://resources.yangshipin.cn/assets/oms/image/202306/3276a414ae0eaa0f116f2045cd913367967d0c7c1e978e8621ac3879436c6ed7.png?imageMogr2/format/webp,600002516,2000295003
东南卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226341/index.m3u8,https://resources.yangshipin.cn/assets/oms/image/202306/3208fe6564a293c21b711333fb3edb05bb5b406cff840573c9a8d839680a1579.png?imageMogr2/format/webp,600002484,2000292503
海南卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226465/index.m3u8,https://resources.yangshipin.cn/assets/oms/image/202306/6e060391fde0469801fc3d84dbf204b4f8d650d251f17d7595a6964c0bb99e81.png?imageMogr2/format/webp,600002506,2000291503
宁夏卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225748/index.m3u8
安多卫视,http://stream.qhbtv.com/adws/sd/live.m3u8
三沙卫视,https://pullsstv90080111.ssws.tv/live/SSTV20220729.m3u8
延边卫视,http://live.ybtvyun.com/video/s10006-44f040627ca1/index.m3u8
浙江少儿,http://hw-m-l.cztv.com/channels/lantian/channel008/1080p.m3u8
南京少儿,http://live.nbs.cn/channels/njtv/sepd/500k.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
CCTV3,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226397/index.m3u8
CCTV4,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/3221226221/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
CCTV8,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226391/index.m3u8
CCTV9,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226197/index.m3u8
CCTV10,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226189/index.m3u8
CCTV11,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226240/index.m3u8
CCTV12,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226190/index.m3u8
CCTV13,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226233/index.m3u8
CCTV14,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226193/index.m3u8
CCTV15,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225785/index.m3u8
CCTV16,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226921/index.m3u8
CCTV17,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226198/index.m3u8
CETV1,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225753/index.m3u8
CETV2,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225756/index.m3u8
CETV3,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226226/index.m3u8
CETV4,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226225/index.m3u8
浙江卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226199/index.m3u8
四川卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225768/index.m3u8
重庆卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226202/index.m3u8
安徽卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226203/index.m3u8
天津卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226204/index.m3u8
山西卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225763/index.m3u8
山东卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226209/index.m3u8
山东教育,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226238/index.m3u8
东南卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225766/index.m3u8
海南卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225769/index.m3u8
厦门卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226219/index.m3u8
河南卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225767/index.m3u8
湖北卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226194/index.m3u8
河北卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225750/index.m3u8
湖南卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226211/index.m3u8
金鹰卡通,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225744/index.m3u8
东方卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226217/index.m3u8
哈哈炫动,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226213/index.m3u8
辽宁卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226201/index.m3u8
黑龙江卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226215/index.m3u8
吉林卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225792/index.m3u8
广西卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225770/index.m3u8
江西卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225764/index.m3u8
江苏卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226200/index.m3u8
优漫卡通,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225795/index.m3u8
深圳卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226205/index.m3u8
广东卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226216/index.m3u8
大湾区卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226218/index.m3u8
北京卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226222/index.m3u8
北京卡酷,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225743/index.m3u8
冬奥纪实,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226232/index.m3u8
嘉佳卡通,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226227/index.m3u8
云南卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225751/index.m3u8
贵州卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225793/index.m3u8
宁夏卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225748/index.m3u8
甘肃卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225754/index.m3u8
西藏卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226212/index.m3u8
安多卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226228/index.m3u8
康巴卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226234/index.m3u8
新疆卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225747/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
@@ -155,58 +289,32 @@ CETV4,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226225/index.m3u8
兵器科技,http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226975/index.m3u8
高尔夫网球,http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226978/index.m3u8
央视文化精品,http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226981/index.m3u8
哒啵电竞,http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226951/index.m3u8
哒啵赛事,http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226954/index.m3u8
CHC高清电影,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226463/index.m3u8
CHC家庭影院,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226462/index.m3u8
CHC动作电影,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226465/index.m3u8
陕西卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225821/index.m3u8
农林卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226229/index.m3u8
陕西一套,http://dbiptv.sn.chinamobile.com/PLTV/88888888/224/3221226357/1.m3u8
陕西二套,http://dbiptv.sn.chinamobile.com/PLTV/88888888/224/3221226358/1.m3u8
陕西三套,http://dbiptv.sn.chinamobile.com/PLTV/88888888/224/3221226359/1.m3u8
陕西四套,http://dbiptv.sn.chinamobile.com/PLTV/88888888/224/3221226360/1.m3u8
陕西五套,http://dbiptv.sn.chinamobile.com/PLTV/88888888/224/3221226361/1.m3u8
陕西六套,http://dbiptv.sn.chinamobile.com/PLTV/88888888/224/3221226362/1.m3u8
陕西七套,http://dbiptv.sn.chinamobile.com/PLTV/88888888/224/3221226363/1.m3u8
陕西八套,http://dbiptv.sn.chinamobile.com/PLTV/88888888/224/3221226364/1.m3u8
西安新闻综合,http://dbiptv.sn.chinamobile.com/PLTV/88888888/224/3221226366/1.m3u8
西安都市,http://dbiptv.sn.chinamobile.com/PLTV/88888888/224/3221226367/1.m3u8
西安商务资讯,http://dbiptv.sn.chinamobile.com/PLTV/88888888/224/3221226368/1.m3u8
西安影视,http://dbiptv.sn.chinamobile.com/PLTV/88888888/224/3221226369/1.m3u8
西安丝路,http://dbiptv.sn.chinamobile.com/PLTV/88888888/224/3221226370/1.m3u8
西安教育,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226371/index.m3u8
咸阳-1,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226372/index.m3u8
杨凌-1,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226373/index.m3u8
延安-1,http://dbiptv.sn.chinamobile.com/PLTV/88888888/224/3221226374/1.m3u8
延安-2,http://dbiptv.sn.chinamobile.com/PLTV/88888888/224/3221226375/1.m3u8
铜川-1,http://dbiptv.sn.chinamobile.com/PLTV/88888888/224/3221226379/1.m3u8
铜川-2,http://dbiptv.sn.chinamobile.com/PLTV/88888888/224/3221226380/1.m3u8
宝鸡-1,http://dbiptv.sn.chinamobile.com/PLTV/88888888/224/3221226383/1.m3u8
宝鸡-2,http://dbiptv.sn.chinamobile.com/PLTV/88888888/224/3221226384/1.m3u8
宁强-1,http://dbiptv.sn.chinamobile.com/PLTV/88888888/224/3221226390/1.m3u8
宁强-2,http://dbiptv.sn.chinamobile.com/PLTV/88888888/224/3221226389/1.m3u8
汉中-1,http://dbiptv.sn.chinamobile.com/PLTV/88888888/224/3221226331/1.m3u8
汉中-3,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225991/index.m3u8
佛坪-1,http://dbiptv.sn.chinamobile.com/PLTV/88888888/224/3221226454/1.m3u8
镇巴-1,http://dbiptv.sn.chinamobile.com/PLTV/88888888/224/3221226457/1.m3u8
略阳-1,http://dbiptv.sn.chinamobile.com/PLTV/88888888/224/3221226322/1.m3u8
西乡-1,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226556/index.m3u8
榆林-1,http://dbiptv.sn.chinamobile.com/PLTV/88888888/224/3221226377/1.m3u8
商洛-1,http://dbiptv.sn.chinamobile.com/PLTV/88888888/224/3221226378/1.m3u8
渭南-1,http://dbiptv.sn.chinamobile.com/PLTV/88888888/224/3221226376/1.m3u8
安康-1,http://dbiptv.sn.chinamobile.com/PLTV/88888888/224/3221226385/1.m3u8
纪实人文,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226230/index.m3u8
山东教育卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226238/index.m3u8
置业频道,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226241/index.m3u8
京视剧场,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226242/index.m3u8
家庭理财,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226244/index.m3u8
奕坦春秋,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226245/index.m3u8
发现之旅,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226235/index.m3u8
老故事,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226236/index.m3u8
地方频道
东方卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226217/index.m3u8
湖南卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226307/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226211/index.m3u8
湖北卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226477/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226194/index.m3u8
辽宁卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226546/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226201/index.m3u8
江苏卫视,http://39.134.24.166/dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226200/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226200/index.m3u8
江西卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226344/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225764/index.m3u8
山东卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226209/index.m3u8
广东卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226216/index.m3u8
广西卫视,http://live.gxrb.com.cn/tv/gxtvlive03/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225770/index.m3u8
重庆卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226409/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226202/index.m3u8
河南卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226480/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225767/index.m3u8
河北卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226406/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225750/index.m3u8
贵州卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226474/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225793/index.m3u8
北京卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221225728/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226222/index.m3u8
黑龙江卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226327/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226215/index.m3u8
浙江卫视,http://hw-m-l.cztv.com/channels/lantian/channel01/1080p.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226199/index.m3u8
安徽卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226391/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226203/index.m3u8
深圳卫视,http://39.134.24.166/dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226205/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226205/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/3221226465/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225769/index.m3u8
""".trimIndent()
val map: MutableMap<String, MutableMap<String, TV>> = mutableMapOf()
val map: MutableMap<String, MutableList<TV>> = mutableMapOf()
var channel = ""
for (i in tvs.split("\n")) {
@@ -215,30 +323,38 @@ CHC动作电影,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226465/in
}
if (!i.contains(",")) {
channel = i.trim()
if (channel == "移动专区") {
break
}
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]
val logo = mappingLogo[tv.title]
if (logo != null && logo != "") {
tv.logo = logo
}
if (p.size > 3) {
tv.pid = p[3]
}
if (p.size > 4) {
tv.sid = p[4]
val programId = mappingEPG[tv.title]
if (programId != null && programId != "") {
tv.programId = programId
}
titleMap[p[0]] = tv
val video = mappingVideo[tv.title]
if (video != null) {
tv.pid = video[0]
tv.sid = video[1]
}
titleMap.add(tv)
map[channel] = titleMap
count++
}
return map
@@ -0,0 +1,15 @@
package com.lizongying.mytv
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
object Utils {
fun getDateFormat(format: String): String {
return SimpleDateFormat(format, Locale.CHINA).format(Date())
}
fun getDateTimestamp(): Long {
return Date().time / 1000
}
}
@@ -0,0 +1,17 @@
package com.lizongying.mytv.api
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
class BtraceClient {
private val yspUrl = "https://btrace.yangshipin.cn/"
val yspBtraceService: YSPBtraceService by lazy {
Retrofit.Builder()
.baseUrl(yspUrl)
.addConverterFactory(GsonConverterFactory.create())
.build().create(YSPBtraceService::class.java)
}
}
@@ -1,24 +0,0 @@
package com.lizongying.mytv.api
import okhttp3.MediaType
import okhttp3.RequestBody
import okio.BufferedSink
import java.io.IOException
data class Page(
val code: Int,
val msg: String,
val data: Data,
)
data class Data(
val feedModuleList: List<Module>
)
data class Module(
val dataTvChannelList: List<TvChannelListData>
)
data class TvChannelListData(
val dataTvChannelList: List<TvChannelListData>
)
@@ -4,9 +4,10 @@ import android.content.Context
import android.content.SharedPreferences
import com.lizongying.mytv.Encryptor
import com.lizongying.mytv.MainActivity
import com.lizongying.mytv.Utils.getDateTimestamp
import com.lizongying.mytv.models.TVViewModel
import java.util.Date
import kotlin.math.floor
import kotlin.random.Random
class YSP(var context: Context) {
private var cnlid = ""
@@ -60,15 +61,14 @@ class YSP(var context: Context) {
sharedPref = (context as MainActivity).getPreferences(Context.MODE_PRIVATE)
}
// guid = getGuid()
guid = getGuid()
}
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()
randStr = getRand()
timeStr = getTimeStr()
@@ -83,34 +83,28 @@ class YSP(var context: Context) {
}
private fun getTimeStr(): String {
return (Date().time / 1000).toString()
return getDateTimestamp().toString()
}
private fun generateGuid(): String {
fun generateGuid(): String {
val timestamp = (System.currentTimeMillis()).toString(36)
val originalString = Math.random().toString()
val resultString = if (originalString.startsWith("0.")) {
originalString.substring(2)
} else {
originalString
}
val randomPart = resultString.toLong().toString(36)
return timestamp + "_" + randomPart
val randomPart = Random.nextLong().toString(36).take(11)
return timestamp + "_" + "0".repeat(11 - randomPart.length) + randomPart
}
private fun getGuid(): String {
// var guid = sharedPref?.getString("guid", "")
if (guid == "") {
fun getGuid(): String {
var guid = sharedPref?.getString("guid", "")
if (guid == null || guid.length < 18) {
guid = generateGuid()
// with(sharedPref!!.edit()) {
// putString("guid", guid)
// apply()
// }
with(sharedPref!!.edit()) {
putString("guid", guid)
apply()
}
}
return guid
}
private fun getRand(): String {
fun getRand(): String {
var n = ""
val e = "ABCDEFGHIJKlMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
val r = e.length
@@ -0,0 +1,71 @@
package com.lizongying.mytv.api
import retrofit2.Call
import retrofit2.http.Field
import retrofit2.http.FormUrlEncoded
import retrofit2.http.Headers
import retrofit2.http.POST
import retrofit2.http.Path
import retrofit2.http.Query
interface YSPBtraceService {
@FormUrlEncoded
@POST("kvcollect")
@Headers(
"content-type: application/x-www-form-urlencoded",
"referer: https://www.yangshipin.cn/",
)
fun kvcollect(
@Query("BossId") BossId: String = "2727",
@Query("c_timestamp") c_timestamp: String = "",
@Field("Pwd") Pwd: String = "1424084450",
@Field("fpid") fpid: String = "",
@Field("livepid") livepid: String = "",
@Field("prd") prd: String = "60000",
@Field("ftime") ftime: String = "",
@Field("prog") prog: String = "",
@Field("playno") playno: String = "",
@Field("guid") guid: String = "",
@Field("hh_ua") hh_ua: String = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36",
@Field("cdn") cdn: String = "waibao",
@Field("platform") platform: String = "5910204",
@Field("errcode") errcode: String = "-",
@Field("sUrl") sUrl: String = "",
@Field("seq") seq: String = "",
@Field("login_type") login_type: String = "undefined",
@Field("open_id") open_id: String = "undefined",
@Field("openid") openid: String = "undefined",
@Field("defn") defn: String = "fhd",
@Field("durl") durl: String = "-",
@Field("sdtfrom") sdtfrom: String = "ysp_pc_01",
@Field("firstreport") firstreport: String = "0",
@Field("fplayerver") fplayerver: String = "89",
@Field("cmd") cmd: String = "263",
@Field("fact1") fact1: String = "ysp_pc_live_b",
@Field("sRef") sRef: String = "-",
@Field("viewid") viewid: String = "",
@Field("geturltime") geturltime: String = "0",
@Field("hc_openid") hc_openid: String = "undefined",
@Field("downspeed") downspeed: String = "10",
@Field("c_host") c_host: String = "www.yangshipin.cn",
@Field("c_pathname") c_pathname: String = "www.yangshipin.cn/",
@Field("c_url") c_url: String = "www.yangshipin.cn/",
@Field("c_channel") c_channel: String = "-",
@Field("c_referrer") c_referrer: String = "-",
@Field("c_ssize") c_ssize: String = "618",
@Field("c_ua") c_ua: String = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36",
@Field("c_os") c_os: String = "mac os",
@Field("c_osv") c_osv: String = "os10.15.7",
@Field("c_browser") c_browser: String = "chrome",
@Field("c_browserv") c_browserv: String = "chrome119",
@Field("c_dvendor") c_dvendor: String = "apple",
@Field("c_dmodel") c_dmodel: String = "macintosh",
@Field("c_dtype") c_dtype: String = "unkown",
@Field("c_city") c_city: String = "disabled",
@Field("c_nation") c_nation: String = "disabled",
@Field("c_province") c_province: String = "disabled",
@Field("c_guid") c_guid: String = "",
@Field("c_vuid") c_vuid: String = "-",
): Call<Void>
}
@@ -1,6 +1,7 @@
package com.lizongying.mytv.api
import com.lizongying.mytv.proto.Ysp.cn.yangshipin.oms.common.proto.pageModel
import com.lizongying.mytv.proto.Ysp.cn.yangshipin.omstv.common.proto.epgProgramModel
import retrofit2.Call
import retrofit2.http.GET
import retrofit2.http.Headers
@@ -15,13 +16,13 @@ interface YSPProtoService {
fun getPage(
): Call<pageModel.Response>
@GET("api/yspepg/program/{livepid}/{cnlid}")
@GET("api/yspepg/program/{livepid}/{date}")
@Headers(
"yspappid: 519748109",
"platform: 109",
)
fun getProgram(
@Path("livepid") livepid: String,
@Path("cnlid") cnlid: String,
): Call<LiveInfo>
@Path("date") date: String,
): Call<epgProgramModel.Response>
}
@@ -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<MutableList<TV>>()
private val tvModelListLiveData = MutableLiveData<MutableList<TVViewModel>>()
var maxNum = mutableListOf<Int>()
fun getListLiveData(): MutableLiveData<MutableList<TVViewModel>> {
return tvModelListLiveData
private var sharedPref: SharedPreferences? = null
private val tvListViewModel = MutableLiveData<MutableList<TVViewModel>>()
private val _itemPosition = MutableLiveData<Int>()
val itemPosition: LiveData<Int>
get() = _itemPosition
private val _itemPositionCurrent = MutableLiveData<Int>()
val itemPositionCurrent: LiveData<Int>
get() = _itemPositionCurrent
fun getTVListViewModel(): MutableLiveData<MutableList<TVViewModel>> {
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
}
}
@@ -1,60 +1,33 @@
package com.lizongying.mytv.models
import android.net.Uri
import android.util.Log
import androidx.annotation.OptIn
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.media3.common.MediaItem
import androidx.media3.common.util.UnstableApi
import androidx.media3.datasource.DefaultHttpDataSource
import androidx.media3.exoplayer.hls.HlsMediaSource
import com.lizongying.mytv.TV
import com.lizongying.mytv.Utils.getDateTimestamp
import com.lizongying.mytv.proto.Ysp.cn.yangshipin.omstv.common.proto.programModel.Program
class TVViewModel(private var tv: TV) : ViewModel() {
private var mapping = mapOf(
"CCTV4K" to "CCTV4K",
"CCTV1" to "CCTV1 综合",
"CCTV2" to "CCTV2 财经",
"CCTV4" to "CCTV4 中文国际",
"CCTV5" to "CCTV5 体育",
"CCTV5+" to "CCTV5+ 体育赛事",
"CCTV7" to "CCTV7 国防军事",
"CCTV9" to "CCTV9 记录",
"CCTV10" to "CCTV10 科教",
"CCTV11" to "CCTV11 戏曲",
"CCTV12" to "CCTV12 社会与法",
"CCTV13" to "CCTV13 新闻",
"CCTV14" to "CCTV14 少儿",
"CCTV15" to "CCTV15 音乐",
"CCTV16-HD" to "CCTV16 奥林匹克",
"CCTV17" to "CCTV17 农业农村",
"CGTN" to "CGTN",
"CGTN外语纪录频道" to "CGTN 纪录频道",
"CGTN法语频道" to "CGTN 法语频道",
"CGTN俄语频道" to "CGTN 俄语频道",
"CGTN阿拉伯语频道" to "CGTN 阿拉伯语频道",
"CGTN西班牙语频道" to "CGTN 西班牙语频道",
"东方卫视" to "东方卫视",
"湖南卫视" to "湖南卫视",
"湖北卫视" to "湖北卫视",
"辽宁卫视" to "辽宁卫视",
"江苏卫视" to "江苏卫视",
"江西卫视" to "江西卫视",
"山东卫视" to "山东卫视",
"广东卫视" to "广东卫视",
"广西卫视" to "广西卫视",
"重庆卫视" to "重庆卫视",
"河南卫视" to "河南卫视",
"河北卫视" to "河北卫视",
"贵州卫视" to "贵州卫视",
"北京卫视" to "北京卫视",
"黑龙江卫视" to "黑龙江卫视",
"浙江卫视" to "浙江卫视",
"安徽卫视" to "安徽卫视",
"深圳卫视" to "深圳卫视",
"四川卫视" to "四川卫视",
"福建东南卫视" to "东南卫视",
"海南卫视" to "海南卫视",
).entries.associate { (key, value) -> value to key }
private var rowPosition: Int = 0
private var itemPosition: Int = 0
private var isFirstTime = true
var programUpdateTime: Long = 0
private val _programId = MutableLiveData<String>()
val programId: LiveData<String>
get() = _programId
private var _program = MutableLiveData<MutableList<Program>>()
val program: LiveData<MutableList<Program>>
get() = _program
private val _id = MutableLiveData<Int>()
val id: LiveData<Int>
@@ -72,6 +45,10 @@ class TVViewModel(private var tv: TV) : ViewModel() {
val videoIndex: LiveData<Int>
get() = _videoIndex
private val _logo = MutableLiveData<String>()
val logo: LiveData<String>
get() = _logo
private val _pid = MutableLiveData<String>()
val pid: LiveData<String>
get() = _pid
@@ -80,49 +57,84 @@ class TVViewModel(private var tv: TV) : ViewModel() {
val sid: LiveData<String>
get() = _sid
private val _backgroundImage = MutableLiveData<String>()
val backgroundImage: LiveData<String>
get() = _backgroundImage
private val _change = MutableLiveData<Boolean>()
val change: LiveData<Boolean>
get() = _change
fun getBackgroundImage(): String {
return tv.logo ?: ""
}
private val _ready = MutableLiveData<Boolean>()
val ready: LiveData<Boolean>
get() = _ready
fun updateBackgroundImage(url: String) {
_backgroundImage.value = url
}
private var mMinimumLoadableRetryCount = 3
var seq = 0
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
_videoUrl.value = tv.videoUrl
_videoIndex.value = tv.videoIndex
_logo.value = tv.logo
_programId.value = tv.programId
_pid.value = tv.pid
_sid.value = tv.sid
_program.value = mutableListOf()
}
fun getIsFirstTime(): Boolean {
return isFirstTime
fun getRowPosition(): Int {
return rowPosition
}
fun isFirstTime(firstTime: Boolean) {
isFirstTime = firstTime
fun getItemPosition(): Int {
return itemPosition
}
fun setRowPosition(position: Int) {
rowPosition = position
}
fun setItemPosition(position: Int) {
itemPosition = position
}
fun update(t: TV) {
@@ -133,11 +145,58 @@ class TVViewModel(private var tv: TV) : ViewModel() {
return tv
}
fun ysp(): String? {
if (tv.pid == "") {
fun getProgramOne(): Program? {
val programNew =
(_program.value?.filter { it.et > getDateTimestamp() })?.toMutableList()
if (programNew != null && _program.value != programNew) {
_program.value = programNew
}
if (_program.value!!.isEmpty()) {
return null
}
return mapping[tv.title]
return _program.value?.first()
}
fun addProgram(p: MutableList<Program>) {
val timestamp = getDateTimestamp()
// after now & not empty & different
val p1 = (p.filter { it.et > timestamp }).toMutableList()
if (p1.isEmpty() || _program.value == p1) {
return
}
if (_program.value!!.isEmpty()) {
_program.value = p1
} else {
_program.value =
((_program.value?.filter { it.et > timestamp && it.st < p1.first().st })?.plus(
p1
))?.toMutableList()
}
}
private var mHeaders: Map<String, String>? = mapOf()
fun setHeaders(headers: Map<String, String>) {
mHeaders = headers
}
fun setMinimumLoadableRetryCount(minimumLoadableRetryCount: Int) {
mMinimumLoadableRetryCount = minimumLoadableRetryCount
}
@OptIn(UnstableApi::class)
fun buildSource(videoUrl: String, mHeaders: Map<String, String>?): HlsMediaSource {
val httpDataSource = DefaultHttpDataSource.Factory()
mHeaders?.let { httpDataSource.setDefaultRequestProperties(it) }
return HlsMediaSource.Factory(httpDataSource).createMediaSource(
MediaItem.fromUri(
Uri.parse(videoUrl)
)
)
}
companion object {
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

@@ -0,0 +1,4 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/white" />
<corners android:radius="4dp" /> <!-- Adjust the radius as needed -->
</shape>
@@ -0,0 +1,4 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#FF263238" />
<corners android:radius="4dp" /> <!-- Adjust the radius as needed -->
</shape>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

+44 -9
View File
@@ -5,15 +5,50 @@
android:layout_height="80dp"
android:layout_gravity="center_horizontal|bottom"
android:layout_marginBottom="20dp"
android:background="@color/fastlane_background"
>
android:background="@drawable/rounded_background">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
<ImageView
android:id="@+id/info_logo"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal|top"
android:layout_marginTop="10dp"
android:textSize="40sp"
/>
android:layout_marginTop="8dp"
android:contentDescription="@string/logo"
android:padding="10dp" />
<LinearLayout
android:layout_width="216dp"
android:layout_height="match_parent"
android:layout_marginStart="100dp"
android:background="#FF263238"
android:gravity="start|center_vertical"
android:orientation="vertical"
android:padding="7dp">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:layout_marginTop="0dp"
android:gravity="start"
android:textColor="#FFEEEEEE"
android:textSize="22sp" />
<TextView
android:id="@+id/info_desc"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:layout_marginTop="8dp"
android:gravity="start"
android:singleLine="true"
android:textColor="#B3EEEEEE"
android:textSize="16sp" />
</LinearLayout>
<View
android:layout_width="8dp"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:background="@drawable/rounded_background2" />
</FrameLayout>
+17
View File
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/player_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/black"
android:keepScreenOn="true">
<androidx.media3.ui.PlayerView
android:id="@+id/player_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
app:resize_mode="fill"
app:use_controller="false" />
</FrameLayout>
+202
View File
@@ -0,0 +1,202 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Must be kept in sync with AspectRatioFrameLayout -->
<attr name="resize_mode" format="enum">
<enum name="fit" value="0"/>
<enum name="fixed_width" value="1"/>
<enum name="fixed_height" value="2"/>
<enum name="fill" value="3"/>
<enum name="zoom" value="4"/>
</attr>
<!-- Must be kept in sync with LegacyPlayerView and PlayerView -->
<attr name="surface_type" format="enum">
<enum name="none" value="0"/>
<enum name="surface_view" value="1"/>
<enum name="texture_view" value="2"/>
<enum name="spherical_gl_surface_view" value="3"/>
<enum name="video_decoder_gl_surface_view" value="4"/>
</attr>
<!-- Must be kept in sync with RepeatModeUtil -->
<attr name="repeat_toggle_modes">
<flag name="none" value="0"/>
<flag name="one" value="1"/>
<flag name="all" value="2"/>
</attr>
<!-- LegacyPlayerView and PlayerView attributes -->
<attr name="use_artwork" format="boolean"/>
<attr name="artwork_display_mode" format="enum">
<enum name="off" value="0"/>
<enum name="fit" value="1"/>
<enum name="fill" value="2"/>
</attr>
<attr name="shutter_background_color" format="color"/>
<attr name="default_artwork" format="reference"/>
<attr name="use_controller" format="boolean"/>
<attr name="hide_on_touch" format="boolean"/>
<attr name="hide_during_ads" format="boolean"/>
<attr name="auto_show" format="boolean"/>
<attr name="show_buffering" format="enum">
<enum name="never" value="0"/>
<enum name="when_playing" value="1"/>
<enum name="always" value="2"/>
</attr>
<attr name="keep_content_on_player_reset" format="boolean"/>
<attr name="player_layout_id" format="reference"/>
<!-- LegacyPlayerControlView and PlayerControlView attributes -->
<attr name="show_timeout" format="integer"/>
<attr name="show_rewind_button" format="boolean"/>
<attr name="show_fastforward_button" format="boolean"/>
<attr name="show_previous_button" format="boolean"/>
<attr name="show_next_button" format="boolean"/>
<attr name="show_shuffle_button" format="boolean"/>
<attr name="show_subtitle_button" format="boolean"/>
<attr name="show_vr_button" format="boolean"/>
<attr name="time_bar_min_update_interval" format="integer"/>
<attr name="controller_layout_id" format="reference"/>
<attr name="animation_enabled" format="boolean"/>
<!-- Needed for https://github.com/google/ExoPlayer/issues/7898 -->
<attr name="backgroundTint" format="color"/>
<!-- DefaultTimeBar attributes -->
<attr name="bar_height" format="dimension"/>
<attr name="bar_gravity" format="enum">
<enum name="center" value="0"/>
<enum name="bottom" value="1"/>
</attr>
<attr name="touch_target_height" format="dimension"/>
<attr name="ad_marker_width" format="dimension"/>
<attr name="scrubber_enabled_size" format="dimension"/>
<attr name="scrubber_disabled_size" format="dimension"/>
<attr name="scrubber_dragged_size" format="dimension"/>
<attr name="scrubber_drawable" format="reference"/>
<attr name="played_color" format="color"/>
<attr name="scrubber_color" format="color"/>
<attr name="buffered_color" format="color"/>
<attr name="unplayed_color" format="color"/>
<attr name="ad_marker_color" format="color"/>
<attr name="played_ad_marker_color" format="color"/>
<declare-styleable name="PlayerView">
<attr name="use_artwork"/>
<attr name="artwork_display_mode"/>
<attr name="shutter_background_color"/>
<attr name="default_artwork"/>
<attr name="use_controller"/>
<attr name="hide_on_touch"/>
<attr name="hide_during_ads"/>
<attr name="auto_show"/>
<attr name="show_buffering"/>
<attr name="keep_content_on_player_reset"/>
<attr name="player_layout_id"/>
<attr name="surface_type"/>
<!-- AspectRatioFrameLayout attributes -->
<attr name="resize_mode"/>
<!-- PlayerControlView attributes -->
<attr name="show_timeout"/>
<attr name="repeat_toggle_modes"/>
<attr name="show_shuffle_button"/>
<attr name="show_subtitle_button"/>
<attr name="show_vr_button"/>
<attr name="time_bar_min_update_interval"/>
<attr name="controller_layout_id"/>
<attr name="animation_enabled"/>
<!-- DefaultTimeBar attributes -->
<attr name="bar_height"/>
<attr name="bar_gravity"/>
<attr name="touch_target_height"/>
<attr name="ad_marker_width"/>
<attr name="scrubber_enabled_size"/>
<attr name="scrubber_disabled_size"/>
<attr name="scrubber_dragged_size"/>
<attr name="scrubber_drawable"/>
<attr name="played_color"/>
<attr name="scrubber_color"/>
<attr name="buffered_color" />
<attr name="unplayed_color"/>
<attr name="ad_marker_color"/>
<attr name="played_ad_marker_color"/>
</declare-styleable>
<declare-styleable name="AspectRatioFrameLayout">
<attr name="resize_mode"/>
</declare-styleable>
<declare-styleable name="LegacyPlayerControlView">
<attr name="show_timeout"/>
<attr name="repeat_toggle_modes"/>
<attr name="show_rewind_button"/>
<attr name="show_fastforward_button"/>
<attr name="show_previous_button"/>
<attr name="show_next_button"/>
<attr name="show_shuffle_button"/>
<attr name="time_bar_min_update_interval"/>
<attr name="controller_layout_id"/>
<!-- DefaultTimeBar attributes -->
<attr name="bar_height"/>
<attr name="bar_gravity"/>
<attr name="touch_target_height"/>
<attr name="ad_marker_width"/>
<attr name="scrubber_enabled_size"/>
<attr name="scrubber_disabled_size"/>
<attr name="scrubber_dragged_size"/>
<attr name="scrubber_drawable"/>
<attr name="played_color"/>
<attr name="scrubber_color"/>
<attr name="buffered_color" />
<attr name="unplayed_color"/>
<attr name="ad_marker_color"/>
<attr name="played_ad_marker_color"/>
</declare-styleable>
<declare-styleable name="PlayerControlView">
<attr name="show_timeout"/>
<attr name="repeat_toggle_modes"/>
<attr name="show_rewind_button"/>
<attr name="show_fastforward_button"/>
<attr name="show_previous_button"/>
<attr name="show_next_button"/>
<attr name="show_shuffle_button"/>
<attr name="show_subtitle_button"/>
<attr name="show_vr_button"/>
<attr name="time_bar_min_update_interval"/>
<attr name="controller_layout_id"/>
<attr name="animation_enabled"/>
<!-- DefaultTimeBar attributes -->
<attr name="bar_height"/>
<attr name="bar_gravity"/>
<attr name="touch_target_height"/>
<attr name="ad_marker_width"/>
<attr name="scrubber_enabled_size"/>
<attr name="scrubber_disabled_size"/>
<attr name="scrubber_dragged_size"/>
<attr name="scrubber_drawable"/>
<attr name="played_color"/>
<attr name="scrubber_color"/>
<attr name="buffered_color" />
<attr name="unplayed_color"/>
<attr name="ad_marker_color"/>
<attr name="played_ad_marker_color"/>
</declare-styleable>
<declare-styleable name="DefaultTimeBar">
<attr name="bar_height"/>
<attr name="bar_gravity"/>
<attr name="touch_target_height"/>
<attr name="ad_marker_width"/>
<attr name="scrubber_enabled_size"/>
<attr name="scrubber_disabled_size"/>
<attr name="scrubber_dragged_size"/>
<attr name="scrubber_drawable"/>
<attr name="played_color"/>
<attr name="scrubber_color"/>
<attr name="buffered_color" />
<attr name="unplayed_color"/>
<attr name="ad_marker_color"/>
<attr name="played_ad_marker_color"/>
</declare-styleable>
</resources>
+2
View File
@@ -1,3 +1,5 @@
<resources>
<color name="fastlane_background">#30000000</color>
<color name="black">#000</color>
<color name="white">#FFF</color>
</resources>
+2 -1
View File
@@ -1,3 +1,4 @@
<resources>
<string name="app_name">My TV</string>
<string name="app_name">我的电视</string>
<string name="logo">logo</string>
</resources>
+2 -1
View File
@@ -2,7 +2,8 @@
<style name="CustomImageCardViewStyle" parent="Widget.Leanback.ImageCardViewStyle">
<item name="lbImageCardViewType">Title</item>
<!-- <item name="infoVisibility">activated</item>-->
<item name="lbImageCardViewType">Title|Content</item>
</style>
<style name="CustomImageCardTheme" parent="Theme.Leanback">
+5 -1
View File
@@ -1,4 +1,8 @@
<resources>
<style name="Theme.MyTV" parent="@style/Theme.Leanback" />
<style name="Theme.MyTV" parent="@style/Theme.Leanback">
<item name="browsePaddingStart">11dp</item>
<item name="browsePaddingEnd">11dp</item>
<item name="browseRowsMarginTop">54dp</item>
</style>
</resources>
+1 -1
View File
@@ -21,5 +21,5 @@ kotlin.code.style=official
# resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library
android.nonTransitiveRClass=true
android.defaults.buildfeatures.buildconfig=true
#android.defaults.buildfeatures.buildconfig=true
android.nonFinalResIds=false
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

After

Width:  |  Height:  |  Size: 1.6 MiB