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.png)
![image](./screenshots/img_1.png) ![image](./screenshots/img_1.png)
## 更新日志
### v1.2.5
* 美化频道信息显示
* 优化节目单获取
### v1.2.4
* 改变换台滑动方向,上一个频道下滑,下一个频道上滑
* 软件退出时,退出播放器
* 播放相同的频道,不再重复加载
* 暂时移除部分频道
## 其他 ## 其他
小米电视可以使用小米电视助手进行安装 小米电视可以使用小米电视助手进行安装
+4 -1
View File
@@ -13,7 +13,7 @@ android {
defaultConfig { defaultConfig {
applicationId "com.lizongying.mytv" applicationId "com.lizongying.mytv"
minSdk 21 minSdk 17
targetSdk 33 targetSdk 33
versionCode VersionCode() versionCode VersionCode()
versionName VersionName() versionName VersionName()
@@ -38,6 +38,7 @@ android {
// libraries Gradle should build and package with your app. // libraries Gradle should build and package with your app.
abiFilters "armeabi-v7a", "arm64-v8a" abiFilters "armeabi-v7a", "arm64-v8a"
} }
multiDexEnabled true
} }
buildTypes { buildTypes {
@@ -91,6 +92,8 @@ static def VersionName() {
dependencies { dependencies {
def media3_version = "1.1.1" def media3_version = "1.1.1"
implementation "androidx.media3:media3-ui:$media3_version"
// For media playback using ExoPlayer // For media playback using ExoPlayer
implementation "androidx.media3:media3-exoplayer:$media3_version" implementation "androidx.media3:media3-exoplayer:$media3_version"
+4 -3
View File
@@ -12,14 +12,15 @@
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:supportsRtl="true" android:supportsRtl="true"
android:usesCleartextTraffic="true"
android:theme="@style/Theme.MyTV"> android:theme="@style/Theme.MyTV">
<activity <activity
android:keepScreenOn="true" android:keepScreenOn="true"
android:name=".MainActivity" android:name=".MainActivity"
android:banner="@drawable/tv" android:banner="@drawable/logo"
android:exported="true" android:exported="true"
android:icon="@drawable/tv" android:icon="@drawable/logo"
android:logo="@drawable/tv" android:logo="@drawable/logo"
android:screenOrientation="landscape"> android:screenOrientation="landscape">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
Binary file not shown.
Binary file not shown.
@@ -1,47 +1,22 @@
package com.lizongying.mytv package com.lizongying.mytv
import android.content.Context import android.graphics.Color
import android.graphics.Bitmap
import android.media.MediaMetadataRetriever
import android.util.Log
import android.view.ContextThemeWrapper import android.view.ContextThemeWrapper
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView
import androidx.leanback.widget.ImageCardView import androidx.leanback.widget.ImageCardView
import androidx.leanback.widget.Presenter import androidx.leanback.widget.Presenter
import androidx.lifecycle.LifecycleCoroutineScope
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.lizongying.mytv.models.TVViewModel import com.lizongying.mytv.models.TVViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class CardPresenter( class CardPresenter(
private val lifecycleScope: LifecycleCoroutineScope, private val owner: LifecycleOwner,
// private val owner: LifecycleOwner
) : Presenter() { ) : 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 { override fun onCreateViewHolder(parent: ViewGroup): ViewHolder {
val cardView = object : val cardView = object :
ImageCardView(ContextThemeWrapper(parent.context, R.style.CustomImageCardTheme)) { ImageCardView(ContextThemeWrapper(parent.context, R.style.CustomImageCardTheme)) {}
override fun setSelected(selected: Boolean) {
updateCardBackgroundColor(this)
super.setSelected(selected)
}
}
cardView.isFocusable = true cardView.isFocusable = true
cardView.isFocusableInTouchMode = true cardView.isFocusableInTouchMode = true
@@ -49,70 +24,39 @@ class CardPresenter(
} }
override fun onBindViewHolder(viewHolder: ViewHolder, item: Any) { override fun onBindViewHolder(viewHolder: ViewHolder, item: Any) {
val tv = item as TV val tvViewModel = item as TVViewModel
val cardView = viewHolder.view as ImageCardView val cardView = viewHolder.view as ImageCardView
cardView.titleText = tv.title cardView.titleText = tvViewModel.title.value
cardView.setMainImageDimensions(CARD_WIDTH, CARD_HEIGHT) cardView.setMainImageDimensions(CARD_WIDTH, CARD_HEIGHT)
cardView.tag = tv.videoUrl cardView.tag = tvViewModel.videoUrl.value
// lifecycleScope.launch(Dispatchers.IO) { if (tvViewModel.logo.value != null) {
// val videoThumbnail = tv.videoUrl?.let { getVideoThumbnail(it) } Glide.with(viewHolder.view.context)
// .load(tvViewModel.logo.value)
// withContext(Dispatchers.Main) { .centerInside()
// cardView.mainImageView.setImageBitmap(videoThumbnail) .into(cardView.mainImageView)
// }
// }
// tvViewModel?.update(tv) cardView.setBackgroundColor(Color.WHITE)
// tvViewModel?.backgroundImage?.observe(owner) { imageUrl -> cardView.setMainImageScaleType(ImageView.ScaleType.CENTER_INSIDE)
// if (imageUrl != "") { }
// Glide.with(viewHolder.view.context)
// .load(imageUrl)
// .centerCrop()
// .into(cardView.mainImageView)
// }
// }
}
override fun onUnbindViewHolder(viewHolder: ViewHolder) { tvViewModel.program.observe(owner) { _ ->
val cardView = viewHolder.view as ImageCardView val program = tvViewModel.getProgramOne()
cardView.badgeImage = null if (program != null) {
cardView.mainImage = null cardView.contentText = program.name
}
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)
}
}
} }
} }
} }
private fun getVideoThumbnail(url: String): Bitmap? { override fun onUnbindViewHolder(viewHolder: ViewHolder) {
val mediaMetadataRetriever = MediaMetadataRetriever() val cardView = viewHolder.view as ImageCardView
try { cardView.mainImage = null
val map = HashMap<String, String>()
mediaMetadataRetriever.setDataSource(url, map)
return mediaMetadataRetriever.frameAtTime
} catch (e: Exception) {
e.printStackTrace()
} finally {
mediaMetadataRetriever.release()
}
return null
} }
companion object { companion object {
private const val TAG = "CardPresenter" private const val TAG = "CardPresenter"
private const val CARD_WIDTH = 313 private const val CARD_WIDTH = 300
private const val CARD_HEIGHT = 176 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 package com.lizongying.mytv
import android.os.Bundle import android.os.Bundle
import android.os.Handler
import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import com.bumptech.glide.Glide
import com.lizongying.mytv.databinding.InfoBinding import com.lizongying.mytv.databinding.InfoBinding
import com.lizongying.mytv.models.TVViewModel
class InfoFragment : Fragment() { class InfoFragment : Fragment() {
private var _binding: InfoBinding? = null private var _binding: InfoBinding? = null
private val binding get() = _binding!! private val binding get() = _binding!!
private val handler = Handler()
private val delay: Long = 3000
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
@@ -19,8 +26,39 @@ class InfoFragment : Fragment() {
return binding.root return binding.root
} }
fun setInfo(info: TV) { override fun onResume() {
binding.textView.text = info.title 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() { override fun onDestroyView() {
@@ -10,7 +10,10 @@ import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.util.Log import android.util.Log
import android.view.GestureDetector
import android.view.KeyEvent import android.view.KeyEvent
import android.view.MotionEvent
import android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
import android.view.WindowManager import android.view.WindowManager
import android.widget.ImageView import android.widget.ImageView
import android.widget.LinearLayout import android.widget.LinearLayout
@@ -18,57 +21,48 @@ import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import com.lizongying.mytv.models.TVViewModel
import java.security.MessageDigest import java.security.MessageDigest
class MainActivity : FragmentActivity() { class MainActivity : FragmentActivity() {
private val playbackFragment = PlaybackFragment() var playerFragment = PlayerFragment()
private val mainFragment = MainFragment() private val mainFragment = MainFragment()
private val infoFragment = InfoFragment() private val infoFragment = InfoFragment()
private var doubleBackToExitPressedOnce = false private var doubleBackToExitPressedOnce = false
private lateinit var gestureDetector: GestureDetector
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main) 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) { if (savedInstanceState == null) {
supportFragmentManager.beginTransaction() supportFragmentManager.beginTransaction()
.add(R.id.main_browse_fragment, playbackFragment) .add(R.id.main_browse_fragment, playerFragment)
.add(R.id.main_browse_fragment, mainFragment)
.add(R.id.main_browse_fragment, infoFragment) .add(R.id.main_browse_fragment, infoFragment)
.add(R.id.main_browse_fragment, mainFragment)
.hide(infoFragment) .hide(infoFragment)
.commit() .commit()
mainFragment.view?.requestFocus()
} }
gestureDetector = GestureDetector(this, GestureListener())
} }
fun switchInfoFragment(tv: TV) { fun showInfoFragment(tvViewModel: TVViewModel) {
infoFragment.setInfo(tv) infoFragment.show(tvViewModel)
if (infoFragment.isHidden) {
supportFragmentManager.beginTransaction().show(infoFragment).commit()
}
} }
fun showInfoFragment(tv: TV) { fun play(tvViewModel: TVViewModel) {
infoFragment.setInfo(tv) Log.i(TAG, "play: ${tvViewModel.getTV()}")
supportFragmentManager.beginTransaction() playerFragment.play(tvViewModel)
.show(infoFragment) mainFragment.view?.requestFocus()
.commit()
}
fun hideInfoFragment() {
supportFragmentManager.beginTransaction()
.hide(infoFragment)
.commit()
}
fun play(tv: TV) {
Log.i(TAG, "play: $tv")
playbackFragment.play(tv)
} }
fun prev() { fun prev() {
@@ -79,11 +73,11 @@ class MainActivity : FragmentActivity() {
mainFragment.next() mainFragment.next()
} }
fun prevSource() { private fun prevSource() {
mainFragment.prevSource() mainFragment.prevSource()
} }
fun nextSource() { private fun nextSource() {
mainFragment.nextSource() mainFragment.nextSource()
} }
@@ -104,7 +98,7 @@ class MainActivity : FragmentActivity() {
mainFragment.focus() mainFragment.focus()
} }
fun mainFragmentIsHidden(): Boolean { private fun mainFragmentIsHidden(): Boolean {
return mainFragment.isHidden 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 { override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
when (keyCode) { when (keyCode) {
KeyEvent.KEYCODE_BACK -> { KeyEvent.KEYCODE_BACK -> {
@@ -171,31 +213,58 @@ class MainActivity : FragmentActivity() {
} }
KeyEvent.KEYCODE_DPAD_CENTER -> { KeyEvent.KEYCODE_DPAD_CENTER -> {
Log.i(TAG, "KEYCODE_DPAD_CENTER")
switchMainFragment() switchMainFragment()
} }
KeyEvent.KEYCODE_DPAD_UP -> { KeyEvent.KEYCODE_DPAD_UP -> {
if (mainFragment.isHidden) { if (mainFragment.isHidden) {
prev() prev()
} else {
// if (mainFragment.selectedPosition == 0) {
// mainFragment.setSelectedPosition(
// mainFragment.tvListViewModel.maxNum.size - 1,
// false
// )
// }
} }
} }
KeyEvent.KEYCODE_DPAD_DOWN -> { KeyEvent.KEYCODE_DPAD_DOWN -> {
if (mainFragment.isHidden) { if (mainFragment.isHidden) {
next() next()
} else {
// if (mainFragment.selectedPosition == mainFragment.tvListViewModel.maxNum.size - 1) {
//// mainFragment.setSelectedPosition(0, false)
// hideMainFragment()
// return false
// }
} }
} }
KeyEvent.KEYCODE_DPAD_LEFT -> { KeyEvent.KEYCODE_DPAD_LEFT -> {
if (mainFragment.isHidden) { // if (mainFragment.isHidden) {
prevSource() // prevSource()
} // } else {
//// if (mainFragment.tvListViewModel.getTVViewModelCurrent()
//// ?.getItemPosition() == 0
//// ) {
////// mainFragment.toLastPosition()
//// hideMainFragment()
//// }
// }
} }
KeyEvent.KEYCODE_DPAD_RIGHT -> { KeyEvent.KEYCODE_DPAD_RIGHT -> {
if (mainFragment.isHidden) { // if (mainFragment.isHidden) {
nextSource() // 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.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import android.os.Bundle import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.util.Log import android.util.Log
import android.widget.Toast
import androidx.core.content.ContextCompat
import androidx.leanback.app.BrowseSupportFragment import androidx.leanback.app.BrowseSupportFragment
import androidx.leanback.widget.ArrayObjectAdapter import androidx.leanback.widget.ArrayObjectAdapter
import androidx.leanback.widget.HeaderItem import androidx.leanback.widget.HeaderItem
@@ -18,101 +18,173 @@ import androidx.leanback.widget.Presenter
import androidx.leanback.widget.Row import androidx.leanback.widget.Row
import androidx.leanback.widget.RowPresenter import androidx.leanback.widget.RowPresenter
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.lizongying.mytv.Utils.getDateTimestamp
import com.lizongying.mytv.models.TVListViewModel import com.lizongying.mytv.models.TVListViewModel
import com.lizongying.mytv.models.TVViewModel
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class MainFragment : BrowseSupportFragment() { class MainFragment : BrowseSupportFragment() {
var itemPosition: Int = 0 var itemPosition: Int = 0
private val list2: MutableList<Info> = mutableListOf()
private var request: Request? = null private var request: Request? = null
private var rowsAdapter: ArrayObjectAdapter? = null private var rowsAdapter: ArrayObjectAdapter? = null
private var tvListViewModel = TVListViewModel() var tvListViewModel = TVListViewModel()
private var sharedPref: SharedPreferences? = null private var sharedPref: SharedPreferences? = null
// override fun onCreateView( private var lastVideoUrl: String = ""
// inflater: LayoutInflater,
// container: ViewGroup?, private val handler = Handler(Looper.getMainLooper())
// savedInstanceState: Bundle? private lateinit var mUpdateProgramRunnable: UpdateProgramRunnable
// ): View? {
// super.onCreate() override fun onCreate(savedInstanceState: Bundle?) {
// // 使用自定义的布局文件 super.onCreate(savedInstanceState)
// return inflater.inflate(R.layout.custom_browse_fragment, container, false) headersState = HEADERS_DISABLED
// } }
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState) super.onActivityCreated(savedInstanceState)
sharedPref = activity?.getPreferences(Context.MODE_PRIVATE) sharedPref = activity?.getPreferences(Context.MODE_PRIVATE)
setupUIElements()
request = activity?.let { Request(it) } request = activity?.let { Request(it) }
loadRows() loadRows()
mUpdateProgramRunnable = UpdateProgramRunnable()
handler.post(mUpdateProgramRunnable)
setupEventListeners() setupEventListeners()
view?.post { view?.post {
// request?.fetchPage() // request?.fetchPage()
// tvListViewModel.getTVViewModel(0)?.let { request?.fetchProgram(it) }
} }
tvListViewModel.getTVListViewModel().value?.forEach { tvViewModel ->
tvViewModel.ready.observe(viewLifecycleOwner) { _ ->
tvListViewModel.getListLiveData().value?.forEach { tvViewModel -> // not first time && channel not change
tvViewModel.videoIndex.observe(viewLifecycleOwner) { videoIndex -> if (tvViewModel.ready.value != null
if (tvViewModel.getIsFirstTime()) { && tvViewModel.id.value == itemPosition
tvViewModel.isFirstTime(false) && check(tvViewModel)
} else { ) {
val tv = tvViewModel.getTV() Log.i(TAG, "ready ${tvViewModel.title.value}")
if (tvViewModel.id.value == itemPosition) { (activity as? MainActivity)?.play(tvViewModel)
(activity as? MainActivity)?.play(tv) }
// (activity as? MainActivity)?.switchInfoFragment(tv) }
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() { override fun onDestroy() {
brandColor = ContextCompat.getColor(context!!, R.color.fastlane_background) super.onDestroy()
handler.removeCallbacks(mUpdateProgramRunnable)
} }
private fun updateRows(tv: TV) { fun updateProgram(tvViewModel: TVViewModel) {
// 获取适配器中的数据 val timestamp = getDateTimestamp()
val dataList = rowsAdapter?.replace(tv.id, tv) if (timestamp - tvViewModel.programUpdateTime > 60) {
// if (tvViewModel.program.value!!.isEmpty()) {
//// 修改数据 tvViewModel.programUpdateTime = timestamp
//// 这里假设 dataList 是一个可变的列表 request?.fetchProgram(tvViewModel)
// dataList[position] = updatedData } else {
// if (timestamp - tvViewModel.program.value!!.last().et < 600) {
//// 刷新适配器 tvViewModel.programUpdateTime = timestamp
// rowsAdapter.notifyItemChanged(position) request?.fetchProgram(tvViewModel)
// rowsAdapter.notifyItemRangeChanged() }
}
}
}
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() { private fun loadRows() {
val list = TVList.list
rowsAdapter = ArrayObjectAdapter(ListRowPresenter()) rowsAdapter = ArrayObjectAdapter(ListRowPresenter())
val cardPresenter = CardPresenter(lifecycleScope) val cardPresenter = CardPresenter(viewLifecycleOwner)
var idx: Long = 0 var idx: Long = 0
for ((k, v) in list) { for ((k, v) in TVList.list) {
val listRowAdapter = ArrayObjectAdapter(cardPresenter) val listRowAdapter = ArrayObjectAdapter(cardPresenter)
var idx2 = 0 for ((idx2, v1) in v.withIndex()) {
for ((_, v1) in v) { val tvViewModel = TVViewModel(v1)
list2.add( tvViewModel.setRowPosition(idx.toInt())
Info( tvViewModel.setItemPosition(idx2)
idx.toInt(), idx2, v1 tvListViewModel.addTVViewModel(tvViewModel)
) listRowAdapter.add(tvViewModel)
)
listRowAdapter.add(v1)
idx2++
tvListViewModel.addTV(v1)
} }
tvListViewModel.maxNum.add(v.size)
val header = HeaderItem(idx, k) val header = HeaderItem(idx, k)
rowsAdapter!!.add(ListRow(header, listRowAdapter)) rowsAdapter!!.add(ListRow(header, listRowAdapter))
idx++ idx++
@@ -126,23 +198,9 @@ class MainFragment : BrowseSupportFragment() {
savePosition(0) 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() (activity as? MainActivity)?.switchMainFragment()
} }
@@ -153,6 +211,7 @@ class MainFragment : BrowseSupportFragment() {
} }
fun savePosition(position: Int) { fun savePosition(position: Int) {
tvListViewModel.setItemPosition(position)
with(sharedPref!!.edit()) { with(sharedPref!!.edit()) {
putInt("position", position) putInt("position", position)
apply() apply()
@@ -163,97 +222,62 @@ class MainFragment : BrowseSupportFragment() {
view?.post { view?.post {
itemPosition-- itemPosition--
if (itemPosition == -1) { if (itemPosition == -1) {
itemPosition = list2.size - 1 itemPosition = tvListViewModel.size() - 1
} }
savePosition(itemPosition) savePosition(itemPosition)
val l = list2[itemPosition] val tvViewModel = tvListViewModel.getTVViewModel(itemPosition)
tvViewModel?.changed()
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()
} }
} }
fun next() { fun next() {
view?.post { view?.post {
itemPosition++ itemPosition++
if (itemPosition == list2.size) { if (itemPosition == tvListViewModel.size()) {
itemPosition = 0 itemPosition = 0
} }
savePosition(itemPosition) savePosition(itemPosition)
val l = list2[itemPosition] val tvViewModel = tvListViewModel.getTVViewModel(itemPosition)
tvViewModel?.changed()
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()
} }
} }
fun prevSource() { fun prevSource() {
view?.post { view?.post {
val tv = list2[itemPosition].item val tvViewModel = tvListViewModel.getTVViewModel(itemPosition)
if (tv.videoUrl.size > 1) { if (tvViewModel != null) {
tv.videoIndex-- if (tvViewModel.videoUrl.value!!.size > 1) {
if (tv.videoIndex == -1) { val videoIndex = tvViewModel.videoIndex.value?.minus(1)
tv.videoIndex = tv.videoUrl.size - 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() { fun nextSource() {
view?.post { view?.post {
val tv = list2[itemPosition].item val tvViewModel = tvListViewModel.getTVViewModel(itemPosition)
if (tvViewModel != null) {
if (tv.videoUrl.size > 1) { if (tvViewModel.videoUrl.value!!.size > 1) {
tv.videoIndex++ val videoIndex = tvViewModel.videoIndex.value?.plus(1)
if (tv.videoIndex == tv.videoUrl.size) { if (videoIndex == tvViewModel.videoUrl.value!!.size) {
tv.videoIndex = 0 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() { private fun setupEventListeners() {
onItemViewClickedListener = ItemViewClickedListener() onItemViewClickedListener = ItemViewClickedListener()
onItemViewSelectedListener = ItemViewSelectedListener() onItemViewSelectedListener = ItemViewSelectedListener()
@@ -266,21 +290,14 @@ class MainFragment : BrowseSupportFragment() {
rowViewHolder: RowPresenter.ViewHolder, rowViewHolder: RowPresenter.ViewHolder,
row: Row row: Row
) { ) {
if (item is TV) { if (item is TVViewModel) {
itemPosition = item.id if (itemPosition != item.id.value!!) {
savePosition(itemPosition) itemPosition = item.id.value!!
savePosition(itemPosition)
val tvModel = tvListViewModel.getTVModel(itemPosition) val tvViewModel = tvListViewModel.getTVViewModel(itemPosition)
if (tvModel?.ysp() != null) { tvViewModel?.changed()
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)
} }
(activity as? MainActivity)?.switchMainFragment() (activity as? MainActivity)?.switchMainFragment()
} }
} }
@@ -291,18 +308,17 @@ class MainFragment : BrowseSupportFragment() {
itemViewHolder: Presenter.ViewHolder?, item: Any?, itemViewHolder: Presenter.ViewHolder?, item: Any?,
rowViewHolder: RowPresenter.ViewHolder, row: Row rowViewHolder: RowPresenter.ViewHolder, row: Row
) { ) {
if (itemViewHolder == null) { if (item is TVViewModel) {
view?.post { tvListViewModel.setItemPositionCurrent(item.id.value!!)
val it = list2[itemPosition]
setSelectedPosition(
it.rowPosition, false,
SelectItemViewHolderTask(it.itemPosition)
)
}
} }
} }
} }
override fun onResume() {
super.onResume()
view!!.requestFocus()
}
companion object { companion object {
private const val TAG = "MainFragment" 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 package com.lizongying.mytv
import android.content.Context import android.content.Context
import android.os.Handler
import android.os.Looper
import android.util.Base64 import android.util.Base64
import android.util.Log import android.util.Log
import com.lizongying.mytv.Utils.getDateFormat
import com.lizongying.mytv.api.ApiClient import com.lizongying.mytv.api.ApiClient
import com.lizongying.mytv.api.BtraceClient
import com.lizongying.mytv.api.LiveInfo import com.lizongying.mytv.api.LiveInfo
import com.lizongying.mytv.api.LiveInfoRequest import com.lizongying.mytv.api.LiveInfoRequest
import com.lizongying.mytv.api.ProtoClient import com.lizongying.mytv.api.ProtoClient
import com.lizongying.mytv.api.YSP import com.lizongying.mytv.api.YSP
import com.lizongying.mytv.api.YSPApiService import com.lizongying.mytv.api.YSPApiService
import com.lizongying.mytv.api.YSPBtraceService
import com.lizongying.mytv.api.YSPProtoService import com.lizongying.mytv.api.YSPProtoService
import com.lizongying.mytv.models.TVViewModel 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.oms.common.proto.pageModel
import com.lizongying.mytv.proto.Ysp.cn.yangshipin.omstv.common.proto.epgProgramModel
import retrofit2.Call import retrofit2.Call
import retrofit2.Callback import retrofit2.Callback
import retrofit2.Response import retrofit2.Response
@@ -23,10 +29,15 @@ import javax.crypto.spec.SecretKeySpec
class Request(var context: Context) { class Request(var context: Context) {
private var ysp: YSP? = null private var ysp: YSP? = null
private var yspApiService: YSPApiService? = null private var yspApiService: YSPApiService? = null
private var yspBtraceService: YSPBtraceService? = null
private var yspProtoService: YSPProtoService? = null private var yspProtoService: YSPProtoService? = null
// TODO onDestroy
private val handler = Handler(Looper.getMainLooper())
private lateinit var myRunnable: MyRunnable
private var mapping = mapOf( private var mapping = mapOf(
"CCTV4K" to "CCTV4K", "CCTV4K" to "CCTV4K 超高清",
"CCTV1" to "CCTV1 综合", "CCTV1" to "CCTV1 综合",
"CCTV2" to "CCTV2 财经", "CCTV2" to "CCTV2 财经",
"CCTV4" to "CCTV4 中文国际", "CCTV4" to "CCTV4 中文国际",
@@ -43,11 +54,11 @@ class Request(var context: Context) {
"CCTV16-HD" to "CCTV16 奥林匹克", "CCTV16-HD" to "CCTV16 奥林匹克",
"CCTV17" to "CCTV17 农业农村", "CCTV17" to "CCTV17 农业农村",
"CGTN" to "CGTN", "CGTN" to "CGTN",
"CGTN外语纪录频道" to "CGTN 纪录频道",
"CGTN法语频道" to "CGTN 法语频道", "CGTN法语频道" to "CGTN 法语频道",
"CGTN俄语频道" to "CGTN 俄语频道", "CGTN俄语频道" to "CGTN 俄语频道",
"CGTN阿拉伯语频道" to "CGTN 阿拉伯语频道", "CGTN阿拉伯语频道" to "CGTN 阿拉伯语频道",
"CGTN西班牙语频道" to "CGTN 西班牙语频道", "CGTN西班牙语频道" to "CGTN 西班牙语频道",
"CGTN外语纪录频道" to "CGTN 纪录频道",
"东方卫视" to "东方卫视", "东方卫视" to "东方卫视",
"湖南卫视" to "湖南卫视", "湖南卫视" to "湖南卫视",
@@ -77,12 +88,18 @@ class Request(var context: Context) {
ysp = YSP(context) ysp = YSP(context)
} }
yspApiService = ApiClient().yspApiService yspApiService = ApiClient().yspApiService
yspBtraceService = BtraceClient().yspBtraceService
yspProtoService = ProtoClient().yspProtoService yspProtoService = ProtoClient().yspProtoService
} }
fun fetchData(tvModel: TVViewModel) { fun fetchData(tvModel: TVViewModel) {
if (::myRunnable.isInitialized) {
handler.removeCallbacks(myRunnable)
}
tvModel.seq = 0
val data = ysp?.switch(tvModel) val data = ysp?.switch(tvModel)
val title = tvModel.getTV().title val title = tvModel.title.value
val request = data?.let { LiveInfoRequest(it) } val request = data?.let { LiveInfoRequest(it) }
request?.let { yspApiService?.getLiveInfo(it) } request?.let { yspApiService?.getLiveInfo(it) }
@@ -109,6 +126,10 @@ class Request(var context: Context) {
).uppercase() ).uppercase()
Log.i(TAG, "$title url $url") Log.i(TAG, "$title url $url")
tvModel.addVideoUrl(url) tvModel.addVideoUrl(url)
tvModel.allReady()
myRunnable = MyRunnable(tvModel)
handler.post(myRunnable)
} else { } else {
Log.e(TAG, "$title key error") Log.e(TAG, "$title key error")
tvModel.firstSource() tvModel.firstSource()
@@ -124,12 +145,55 @@ class Request(var context: Context) {
} }
override fun onFailure(call: Call<LiveInfo>, t: Throwable) { override fun onFailure(call: Call<LiveInfo>, t: Throwable) {
Log.e(TAG, "${tvModel.getTV().title} request error") Log.e(TAG, "$title request error")
tvModel.firstSource() 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() { fun fetchPage() {
yspProtoService?.getPage()?.enqueue(object : Callback<pageModel.Response> { yspProtoService?.getPage()?.enqueue(object : Callback<pageModel.Response> {
override fun onResponse( override fun onResponse(
@@ -155,7 +219,8 @@ class Request(var context: Context) {
if (!mapping.containsKey(item.channelName)) { if (!mapping.containsKey(item.channelName)) {
continue continue
} }
val tv = TVList.list[channelType]?.get(mapping[item.channelName]) val tv =
TVList.list[channelType]?.find { it.title == mapping[item.channelName] }
if (tv != null) { if (tv != null) {
tv.logo = item.tvLogo tv.logo = item.tvLogo
tv.pid = item.pid tv.pid = item.pid
@@ -172,22 +237,31 @@ class Request(var context: Context) {
}) })
} }
fun fetchProgram(tvModel: TVViewModel) { fun fetchProgram(tvViewModel: TVViewModel) {
yspProtoService?.getProgram("", "")?.enqueue(object : Callback<LiveInfo> { val title = tvViewModel.title.value
override fun onResponse(call: Call<LiveInfo>, response: Response<LiveInfo>) { yspProtoService?.getProgram(tvViewModel.programId.value!!, getDateFormat("yyyyMMdd"))
if (response.isSuccessful) { ?.enqueue(object : Callback<epgProgramModel.Response> {
val liveInfo = response.body() override fun onResponse(
Log.i(TAG, "${liveInfo?.data?.playurl}") 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 { 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)"}""" """{"mver":"1","subver":"1.2","host":"www.yangshipin.cn/#/tv/home?pid=","referer":"","canvas":"YSPANGLE(Apple,AppleM1Pro,OpenGL4.1)"}"""
return try { return try {
val keySpec = SecretKeySpec(key, "DESede") val keySpec = SecretKeySpec(key, "DESede")
@@ -10,6 +10,7 @@ data class TV(
var logo: String = "", var logo: String = "",
var pid: String = "", var pid: String = "",
var sid: String = "", var sid: String = "",
var programId: String = "",
) : Serializable { ) : Serializable {
override fun toString(): String { override fun toString(): String {
@@ -21,6 +22,7 @@ data class TV(
", logo='" + logo + '\'' + ", logo='" + logo + '\'' +
", pid='" + pid + '\'' + ", pid='" + pid + '\'' +
", sid='" + sid + '\'' + ", sid='" + sid + '\'' +
", programId='" + programId + '\'' +
'}' '}'
} }
+309 -193
View File
@@ -1,149 +1,283 @@
package com.lizongying.mytv package com.lizongying.mytv
object TVList { object TVList {
val list: Map<String, Map<String, TV>> by lazy { val list: Map<String, List<TV>> by lazy {
setupTV() 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 var count: Int = 0
private fun setupTV(): Map<String, Map<String, TV>> { private fun setupTV(): Map<String, List<TV>> {
val tvs = """ val tvs = """
央视频道 央视频道
CCTV4K,,https://resources.yangshipin.cn/assets/oms/image/202306/3e9d06fd7244d950df5838750f1c6ac3456e172b51caca2c16d2282125b111e8.png?imageMogr2/format/webp,600002264,2000266303 CCTV4K 超高清,
CCTV1 综合,http://tvpull.dxhmt.cn/tv/11481-4.m3u8,https://resources.yangshipin.cn/assets/oms/image/202306/d57905b93540bd15f0c48230dbbbff7ee0d645ff539e38866e2d15c8b9f7dfcd.png?imageMogr2/format/webp,600001859,2000210103 CCTV1 综合,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226231/index.m3u8
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 CCTV2 财经,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226195/index.m3u8
CCTV3 综艺,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226397/index.m3u8 CCTV3 综艺,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226397/index.m3u8
CCTV4 中文国际,http://39.134.24.161/dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226191/index.m3u8;http://hlsbkmgsplive.miguvideo.com/wd_r2/cctv/cctv4hd/1500/index.m3u8?&encrypt=,https://resources.yangshipin.cn/assets/oms/image/202306/f357e58fdbcc076a3d65e1f958c942b2e14f14342c60736ceed98b092d35356a.png?imageMogr2/format/webp,600001814,2000204803 CCTV4 中文国际,http://39.134.24.161/dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226191/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226191/index.m3u8
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/3221226395/index.m3u8
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 CCTV5+ 体育赛事,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226221/index.m3u8
CCTV6 电影,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226393/index.m3u8 CCTV6 电影,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226393/index.m3u8
CCTV7 国防军事,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226192/index.m3u8,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 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 CCTV9 记录,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226197/index.m3u8
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 CCTV10 科教,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226189/index.m3u8
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 CCTV11 戏曲,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226240/index.m3u8
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 CCTV12 社会与法,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226190/index.m3u8
CCTV13 新闻,https://live-play.cctvnews.cctv.com/cctv/merge_cctv13.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,https://resources.yangshipin.cn/assets/oms/image/202306/af6b603896938dc346fbb16abfc63c12cba54b0ec9d18770a15d347d115f12d5.png?imageMogr2/format/webp,600001809,2000204403 CCTV14 少儿,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226193/index.m3u8
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 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,https://resources.yangshipin.cn/assets/oms/image/202306/53793fa7bacd3a93ff6dc5d2758418985e1f952a316c335d663b572d8bdcd74d.png?imageMogr2/format/webp,600098637,2012375003 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,https://resources.yangshipin.cn/assets/oms/image/202306/ddef563072f8bad2bea5b9e52674cb7b4ed50efb20c26e61994dfbdf05c1e3c0.png?imageMogr2/format/webp,600001810,2000204203 CCTV17 农业农村,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226198/index.m3u8
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
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://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/3221226987/index.m3u8
风云足球,http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226984/index.m3u8 风云足球,http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226984/index.m3u8
风云剧场,http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226950/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/3221226975/index.m3u8
高尔夫网球,http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226978/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/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 东方卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226217/index.m3u8
CHC家庭影院,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226462/index.m3u8 湖南卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226307/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226211/index.m3u8
CHC动作电影,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226465/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://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225821/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://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226229/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://dbiptv.sn.chinamobile.com/PLTV/88888888/224/3221226357/1.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/88888888/224/3221226358/1.m3u8 山东卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226209/index.m3u8
陕西三套,http://dbiptv.sn.chinamobile.com/PLTV/88888888/224/3221226359/1.m3u8 广东卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226216/index.m3u8
陕西四套,http://dbiptv.sn.chinamobile.com/PLTV/88888888/224/3221226360/1.m3u8 广西卫视,http://live.gxrb.com.cn/tv/gxtvlive03/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225770/index.m3u8
陕西五套,http://dbiptv.sn.chinamobile.com/PLTV/88888888/224/3221226361/1.m3u8 重庆卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226409/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226202/index.m3u8
陕西六套,http://dbiptv.sn.chinamobile.com/PLTV/88888888/224/3221226362/1.m3u8 河南卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226480/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225767/index.m3u8
陕西七套,http://dbiptv.sn.chinamobile.com/PLTV/88888888/224/3221226363/1.m3u8 河北卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226406/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225750/index.m3u8
陕西八套,http://dbiptv.sn.chinamobile.com/PLTV/88888888/224/3221226364/1.m3u8 贵州卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226474/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225793/index.m3u8
西安新闻综合,http://dbiptv.sn.chinamobile.com/PLTV/88888888/224/3221226366/1.m3u8 北京卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221225728/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226222/index.m3u8
西安都市,http://dbiptv.sn.chinamobile.com/PLTV/88888888/224/3221226367/1.m3u8 黑龙江卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226327/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226215/index.m3u8
西安商务资讯,http://dbiptv.sn.chinamobile.com/PLTV/88888888/224/3221226368/1.m3u8 浙江卫视,http://hw-m-l.cztv.com/channels/lantian/channel01/1080p.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226199/index.m3u8
西安影视,http://dbiptv.sn.chinamobile.com/PLTV/88888888/224/3221226369/1.m3u8 安徽卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226391/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226203/index.m3u8
西安丝路,http://dbiptv.sn.chinamobile.com/PLTV/88888888/224/3221226370/1.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/3221226371/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
咸阳-1,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226372/index.m3u8 东南卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226341/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225766/index.m3u8
杨凌-1,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226373/index.m3u8 海南卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226465/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225769/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
""".trimIndent() """.trimIndent()
val map: MutableMap<String, MutableMap<String, TV>> = mutableMapOf() val map: MutableMap<String, MutableList<TV>> = mutableMapOf()
var channel = "" var channel = ""
for (i in tvs.split("\n")) { for (i in tvs.split("\n")) {
@@ -215,30 +323,38 @@ CHC动作电影,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226465/in
} }
if (!i.contains(",")) { if (!i.contains(",")) {
channel = i.trim() channel = i.trim()
if (channel == "移动专区") {
break
}
continue continue
} }
val p = i.split(",") val p = i.split(",")
val titleMap = map[channel] ?: mutableMapOf() val titleMap = map[channel] ?: mutableListOf()
val tv = titleMap[p[0]] ?: TV( val tv = TV(
count, count,
p[0], p[0],
p[1].split(";").map { it.trim() } p[1].split(";").map { it.trim() }
) )
count++
if (p.size > 2) { val logo = mappingLogo[tv.title]
tv.logo = p[2] if (logo != null && logo != "") {
tv.logo = logo
} }
if (p.size > 3) { val programId = mappingEPG[tv.title]
tv.pid = p[3] if (programId != null && programId != "") {
} tv.programId = programId
if (p.size > 4) {
tv.sid = p[4]
} }
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 map[channel] = titleMap
count++
} }
return map 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 android.content.SharedPreferences
import com.lizongying.mytv.Encryptor import com.lizongying.mytv.Encryptor
import com.lizongying.mytv.MainActivity import com.lizongying.mytv.MainActivity
import com.lizongying.mytv.Utils.getDateTimestamp
import com.lizongying.mytv.models.TVViewModel import com.lizongying.mytv.models.TVViewModel
import java.util.Date
import kotlin.math.floor import kotlin.math.floor
import kotlin.random.Random
class YSP(var context: Context) { class YSP(var context: Context) {
private var cnlid = "" private var cnlid = ""
@@ -60,15 +61,14 @@ class YSP(var context: Context) {
sharedPref = (context as MainActivity).getPreferences(Context.MODE_PRIVATE) sharedPref = (context as MainActivity).getPreferences(Context.MODE_PRIVATE)
} }
// guid = getGuid() guid = getGuid()
} }
fun switch(tvModel: TVViewModel): String { fun switch(tvModel: TVViewModel): String {
livepid = tvModel.getTV().pid livepid = tvModel.pid.value!!
cnlid = tvModel.getTV().sid cnlid = tvModel.sid.value!!
defn = "fhd" defn = "fhd"
guid = getGuid()
randStr = getRand() randStr = getRand()
timeStr = getTimeStr() timeStr = getTimeStr()
@@ -83,34 +83,28 @@ class YSP(var context: Context) {
} }
private fun getTimeStr(): String { 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 timestamp = (System.currentTimeMillis()).toString(36)
val originalString = Math.random().toString() val randomPart = Random.nextLong().toString(36).take(11)
val resultString = if (originalString.startsWith("0.")) { return timestamp + "_" + "0".repeat(11 - randomPart.length) + randomPart
originalString.substring(2)
} else {
originalString
}
val randomPart = resultString.toLong().toString(36)
return timestamp + "_" + randomPart
} }
private fun getGuid(): String { fun getGuid(): String {
// var guid = sharedPref?.getString("guid", "") var guid = sharedPref?.getString("guid", "")
if (guid == "") { if (guid == null || guid.length < 18) {
guid = generateGuid() guid = generateGuid()
// with(sharedPref!!.edit()) { with(sharedPref!!.edit()) {
// putString("guid", guid) putString("guid", guid)
// apply() apply()
// } }
} }
return guid return guid
} }
private fun getRand(): String { fun getRand(): String {
var n = "" var n = ""
val e = "ABCDEFGHIJKlMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" val e = "ABCDEFGHIJKlMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
val r = e.length 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 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.oms.common.proto.pageModel
import com.lizongying.mytv.proto.Ysp.cn.yangshipin.omstv.common.proto.epgProgramModel
import retrofit2.Call import retrofit2.Call
import retrofit2.http.GET import retrofit2.http.GET
import retrofit2.http.Headers import retrofit2.http.Headers
@@ -15,13 +16,13 @@ interface YSPProtoService {
fun getPage( fun getPage(
): Call<pageModel.Response> ): Call<pageModel.Response>
@GET("api/yspepg/program/{livepid}/{cnlid}") @GET("api/yspepg/program/{livepid}/{date}")
@Headers( @Headers(
"yspappid: 519748109", "yspappid: 519748109",
"platform: 109", "platform: 109",
) )
fun getProgram( fun getProgram(
@Path("livepid") livepid: String, @Path("livepid") livepid: String,
@Path("cnlid") cnlid: String, @Path("date") date: String,
): Call<LiveInfo> ): Call<epgProgramModel.Response>
} }
@@ -1,47 +1,61 @@
package com.lizongying.mytv.models package com.lizongying.mytv.models
import android.content.SharedPreferences
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import com.lizongying.mytv.TV
class TVListViewModel : ViewModel() { class TVListViewModel : ViewModel() {
private val tvListLiveData = MutableLiveData<MutableList<TV>>()
private val tvModelListLiveData = MutableLiveData<MutableList<TVViewModel>>() var maxNum = mutableListOf<Int>()
fun getListLiveData(): MutableLiveData<MutableList<TVViewModel>> { private var sharedPref: SharedPreferences? = null
return tvModelListLiveData
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) { fun addTVViewModel(tvViewModel: TVViewModel) {
val currentList = tvListLiveData.value ?: mutableListOf() val currentTVModelList = tvListViewModel.value ?: mutableListOf()
currentList.add(tv) currentTVModelList.add(tvViewModel)
tvListLiveData.value = currentList tvListViewModel.value = currentTVModelList
val currentModelList = tvModelListLiveData.value ?: mutableListOf()
currentModelList.add(TVViewModel(tv))
tvModelListLiveData.value = currentModelList
} }
fun updateTV(tv: TV) { fun getTVViewModel(id: Int): TVViewModel? {
val currentList = tvListLiveData.value ?: mutableListOf() return tvListViewModel.value?.get(id)
currentList[tv.id] = tv
tvListLiveData.value = currentList
val currentModelList = tvModelListLiveData.value ?: mutableListOf()
currentModelList[tv.id].update(tv)
tvModelListLiveData.value = currentModelList
} }
fun getTV(id: Int): TV? { fun getTVViewModelCurrent(): TVViewModel? {
return tvListLiveData.value?.get(id) return _itemPositionCurrent.value?.let { tvListViewModel.value?.get(it) }
} }
fun getTVModel(id: Int): TVViewModel? { fun setItemPosition(position: Int) {
return tvModelListLiveData.value?.get(id) _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 { fun size(): Int {
return tvListLiveData.value!!.size return tvListViewModel.value!!.size
} }
} }
@@ -1,60 +1,33 @@
package com.lizongying.mytv.models package com.lizongying.mytv.models
import android.net.Uri
import android.util.Log import android.util.Log
import androidx.annotation.OptIn
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel 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.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() { 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 "东方卫视", private var rowPosition: Int = 0
"湖南卫视" to "湖南卫视", private var itemPosition: Int = 0
"湖北卫视" 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 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>() private val _id = MutableLiveData<Int>()
val id: LiveData<Int> val id: LiveData<Int>
@@ -72,6 +45,10 @@ class TVViewModel(private var tv: TV) : ViewModel() {
val videoIndex: LiveData<Int> val videoIndex: LiveData<Int>
get() = _videoIndex get() = _videoIndex
private val _logo = MutableLiveData<String>()
val logo: LiveData<String>
get() = _logo
private val _pid = MutableLiveData<String>() private val _pid = MutableLiveData<String>()
val pid: LiveData<String> val pid: LiveData<String>
get() = _pid get() = _pid
@@ -80,49 +57,84 @@ class TVViewModel(private var tv: TV) : ViewModel() {
val sid: LiveData<String> val sid: LiveData<String>
get() = _sid get() = _sid
private val _backgroundImage = MutableLiveData<String>() private val _change = MutableLiveData<Boolean>()
val backgroundImage: LiveData<String> val change: LiveData<Boolean>
get() = _backgroundImage get() = _change
fun getBackgroundImage(): String { private val _ready = MutableLiveData<Boolean>()
return tv.logo ?: "" val ready: LiveData<Boolean>
} get() = _ready
fun updateBackgroundImage(url: String) { private var mMinimumLoadableRetryCount = 3
_backgroundImage.value = url
} var seq = 0
fun addVideoUrl(url: String) { 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 tv.videoIndex = tv.videoUrl.lastIndex
_videoUrl.value = tv.videoUrl _videoUrl.value = tv.videoUrl
_videoIndex.value = tv.videoIndex _videoIndex.value = tv.videoIndex
} }
fun firstSource() { fun firstSource() {
if (tv.videoUrl.isNotEmpty()) { if (_videoUrl.value!!.isNotEmpty()) {
tv.videoIndex = 0 setVideoIndex(0)
_videoIndex.value = 0 allReady()
} else { } else {
Log.e(TAG, "no first") 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 { init {
_id.value = tv.id _id.value = tv.id
_title.value = tv.title _title.value = tv.title
_videoUrl.value = tv.videoUrl _videoUrl.value = tv.videoUrl
_videoIndex.value = tv.videoIndex _videoIndex.value = tv.videoIndex
_logo.value = tv.logo
_programId.value = tv.programId
_pid.value = tv.pid _pid.value = tv.pid
_sid.value = tv.sid _sid.value = tv.sid
_program.value = mutableListOf()
} }
fun getIsFirstTime(): Boolean { fun getRowPosition(): Int {
return isFirstTime return rowPosition
} }
fun isFirstTime(firstTime: Boolean) { fun getItemPosition(): Int {
isFirstTime = firstTime return itemPosition
}
fun setRowPosition(position: Int) {
rowPosition = position
}
fun setItemPosition(position: Int) {
itemPosition = position
} }
fun update(t: TV) { fun update(t: TV) {
@@ -133,11 +145,58 @@ class TVViewModel(private var tv: TV) : ViewModel() {
return tv return tv
} }
fun ysp(): String? { fun getProgramOne(): Program? {
if (tv.pid == "") { 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 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 { 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_height="80dp"
android:layout_gravity="center_horizontal|bottom" android:layout_gravity="center_horizontal|bottom"
android:layout_marginBottom="20dp" android:layout_marginBottom="20dp"
android:background="@color/fastlane_background" android:background="@drawable/rounded_background">
>
<TextView <ImageView
android:id="@+id/textView" android:id="@+id/info_logo"
android:layout_width="wrap_content" android:layout_width="100dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_horizontal|top" android:layout_marginTop="8dp"
android:layout_marginTop="10dp" android:contentDescription="@string/logo"
android:textSize="40sp" 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> </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> <resources>
<color name="fastlane_background">#30000000</color> <color name="fastlane_background">#30000000</color>
<color name="black">#000</color>
<color name="white">#FFF</color>
</resources> </resources>
+2 -1
View File
@@ -1,3 +1,4 @@
<resources> <resources>
<string name="app_name">My TV</string> <string name="app_name">我的电视</string>
<string name="logo">logo</string>
</resources> </resources>
+2 -1
View File
@@ -2,7 +2,8 @@
<style name="CustomImageCardViewStyle" parent="Widget.Leanback.ImageCardViewStyle"> <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>
<style name="CustomImageCardTheme" parent="Theme.Leanback"> <style name="CustomImageCardTheme" parent="Theme.Leanback">
+5 -1
View File
@@ -1,4 +1,8 @@
<resources> <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> </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, # resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library # thereby reducing the size of the R class for that library
android.nonTransitiveRClass=true android.nonTransitiveRClass=true
android.defaults.buildfeatures.buildconfig=true #android.defaults.buildfeatures.buildconfig=true
android.nonFinalResIds=false android.nonFinalResIds=false
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

After

Width:  |  Height:  |  Size: 1.6 MiB