Compare commits

..
3 Commits
Author SHA1 Message Date
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
17 changed files with 644 additions and 211 deletions
@@ -1,47 +1,22 @@
package com.lizongying.mytv
import android.content.Context
import android.graphics.Bitmap
import android.media.MediaMetadataRetriever
import android.util.Log
import android.graphics.Color
import android.view.ContextThemeWrapper
import android.view.ViewGroup
import android.widget.ImageView
import androidx.leanback.widget.ImageCardView
import androidx.leanback.widget.Presenter
import androidx.lifecycle.LifecycleCoroutineScope
import androidx.lifecycle.LifecycleOwner
import com.bumptech.glide.Glide
import com.lizongying.mytv.models.TVViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class CardPresenter(
private val lifecycleScope: LifecycleCoroutineScope,
// private val owner: LifecycleOwner
private val owner: LifecycleOwner,
) : Presenter() {
private var tvViewModel: TVViewModel? = null
fun bindData(viewModel: TVViewModel, fragment: Context?, viewHolder: ViewHolder) {
}
fun setViewModel(model: TVViewModel) {
tvViewModel = model
}
fun unbind() {
// viewModel?.backgroundImage?.removeObservers(fragment)
}
override fun onCreateViewHolder(parent: ViewGroup): ViewHolder {
val cardView = object :
ImageCardView(ContextThemeWrapper(parent.context, R.style.CustomImageCardTheme)) {
override fun setSelected(selected: Boolean) {
updateCardBackgroundColor(this)
super.setSelected(selected)
}
}
ImageCardView(ContextThemeWrapper(parent.context, R.style.CustomImageCardTheme)) {}
cardView.isFocusable = true
cardView.isFocusableInTouchMode = true
@@ -56,63 +31,32 @@ class CardPresenter(
cardView.setMainImageDimensions(CARD_WIDTH, CARD_HEIGHT)
cardView.tag = tvViewModel.videoUrl.value
// lifecycleScope.launch(Dispatchers.IO) {
// val videoThumbnail = tv.videoUrl?.let { getVideoThumbnail(it) }
//
// withContext(Dispatchers.Main) {
// cardView.mainImageView.setImageBitmap(videoThumbnail)
// }
// }
if (tvViewModel.logo.value != null) {
Glide.with(viewHolder.view.context)
.load(tvViewModel.logo.value)
.centerInside()
.into(cardView.mainImageView)
// tvViewModel?.update(tv)
// tvViewModel?.backgroundImage?.observe(owner) { imageUrl ->
// if (imageUrl != "") {
// Glide.with(viewHolder.view.context)
// .load(imageUrl)
// .centerCrop()
// .into(cardView.mainImageView)
// }
// }
cardView.setBackgroundColor(Color.WHITE)
cardView.setMainImageScaleType(ImageView.ScaleType.CENTER_INSIDE)
}
tvViewModel.program.observe(owner) { _ ->
val program = tvViewModel.getProgramOne()
if (program != null) {
cardView.contentText = program.name
}
}
}
override fun onUnbindViewHolder(viewHolder: ViewHolder) {
val cardView = viewHolder.view as ImageCardView
cardView.badgeImage = null
cardView.mainImage = null
}
private fun updateCardBackgroundColor(view: ImageCardView) {
val currentTag = view.tag
// lifecycleScope.launch(Dispatchers.IO) {
// delay(1000)
// if (view.isSelected && view.tag != null && currentTag == view.tag) {
// val videoThumbnail = view.tag.toString().let { getVideoThumbnail(it) }
// withContext(Dispatchers.Main) {
// if (view.isSelected && currentTag == view.tag) {
// view.mainImageView.setImageBitmap(videoThumbnail)
// }
// }
// }
// }
}
private fun getVideoThumbnail(url: String): Bitmap? {
val mediaMetadataRetriever = MediaMetadataRetriever()
try {
val map = HashMap<String, String>()
mediaMetadataRetriever.setDataSource(url, map)
return mediaMetadataRetriever.frameAtTime
} catch (e: Exception) {
e.printStackTrace()
} finally {
mediaMetadataRetriever.release()
}
return null
}
companion object {
private const val TAG = "CardPresenter"
private const val CARD_WIDTH = 300
private const val CARD_HEIGHT = 169
private const val CARD_HEIGHT = 101
}
}
@@ -0,0 +1,23 @@
package com.lizongying.mytv
import androidx.media3.common.C
import androidx.media3.common.util.UnstableApi
import androidx.media3.exoplayer.upstream.DefaultLoadErrorHandlingPolicy
@UnstableApi
class CustomLoadErrorHandlingPolicy(private val minimumLoadableRetryCount: Int) :
DefaultLoadErrorHandlingPolicy(minimumLoadableRetryCount) {
override fun getMinimumLoadableRetryCount(dataType: Int): Int {
return if (minimumLoadableRetryCount == -1) {
if (dataType == C.DATA_TYPE_MEDIA_PROGRESSIVE_LIVE) {
DEFAULT_MIN_LOADABLE_RETRY_COUNT_PROGRESSIVE_LIVE
} else {
DEFAULT_MIN_LOADABLE_RETRY_COUNT
}
} else {
minimumLoadableRetryCount
}
}
}
@@ -38,6 +38,22 @@ open class ExoPlayerAdapter(private var mContext: Context?) : PlayerAdapter() {
var mBufferingStart = false
private var mMinimumLoadableRetryCount = 3
private var mPlayerErrorListener: PlayerErrorListener? = null
init {
mPlayer?.playWhenReady = true
if (mPlayerErrorListener == null) {
mPlayerErrorListener = PlayerErrorListener()
mPlayer?.addListener(mPlayerErrorListener!!)
}
}
open fun notifyBufferingStartEnd() {
callback.onBufferingStateChanged(
this@ExoPlayerAdapter,
@@ -178,14 +194,19 @@ open class ExoPlayerAdapter(private var mContext: Context?) : PlayerAdapter() {
return mBufferedProgress
}
private var mPlayerErrorListener: PlayerErrorListener? = null
private inner class PlayerErrorListener : Player.Listener {
override fun onPlayerError(error: PlaybackException) {
callback.onError(this@ExoPlayerAdapter, error.errorCode, error.message)
}
}
private var mHeaders: Map<String, String>? = mapOf()
fun setHeaders(headers: Map<String, String>) {
mHeaders = headers
}
/**
* Sets the media source of the player witha given URI.
*
@@ -193,46 +214,43 @@ open class ExoPlayerAdapter(private var mContext: Context?) : PlayerAdapter() {
* otherwise.
* @see MediaPlayer.setDataSource
*/
@OptIn(UnstableApi::class)
open fun setDataSource(uri: Uri?): Boolean {
if (if (mMediaSourceUri != null) mMediaSourceUri == uri else uri == null) {
return false
}
mMediaSourceUri = uri
prepareMediaForPlaying()
mPlayer?.playWhenReady = true
val httpDataSource = DefaultHttpDataSource.Factory()
mHeaders?.let { httpDataSource.setDefaultRequestProperties(it) }
if (mPlayerErrorListener == null) {
mPlayerErrorListener = PlayerErrorListener()
mPlayer?.addListener(mPlayerErrorListener!!)
}
val hlsMediaSource =
HlsMediaSource.Factory(httpDataSource).setLoadErrorHandlingPolicy(
CustomLoadErrorHandlingPolicy(mMinimumLoadableRetryCount)
).createMediaSource(
MediaItem.fromUri(
mMediaSourceUri!!
)
)
prepareMediaForPlaying(hlsMediaSource)
return true
}
private var mHeaders: Map<String, String>? = mapOf()
@OptIn(UnstableApi::class)
open fun setDataSource(hlsMediaSource: HlsMediaSource): Boolean {
prepareMediaForPlaying(hlsMediaSource)
return true
}
fun setHeaders(headers: Map<String, String>) {
mHeaders = headers
fun setMinimumLoadableRetryCount(minimumLoadableRetryCount: Int) {
mMinimumLoadableRetryCount = minimumLoadableRetryCount
}
@OptIn(UnstableApi::class)
private fun prepareMediaForPlaying() {
reset()
private fun prepareMediaForPlaying(hlsMediaSource: HlsMediaSource) {
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
}
mPlayer?.setMediaSource(hlsMediaSource)
} catch (e: IOException) {
e.printStackTrace()
throw RuntimeException(e)
@@ -10,7 +10,9 @@ import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.util.Log
import android.view.GestureDetector
import android.view.KeyEvent
import android.view.MotionEvent
import android.view.WindowManager
import android.widget.ImageView
import android.widget.LinearLayout
@@ -30,11 +32,14 @@ class MainActivity : FragmentActivity() {
private var doubleBackToExitPressedOnce = false
private lateinit var gestureDetector: GestureDetector
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
if (savedInstanceState == null) {
supportFragmentManager.beginTransaction()
@@ -44,6 +49,7 @@ class MainActivity : FragmentActivity() {
.hide(infoFragment)
.commit()
}
gestureDetector = GestureDetector(this, GestureListener())
}
fun switchInfoFragment(tv: TV) {
@@ -117,6 +123,85 @@ class MainActivity : FragmentActivity() {
}
}
override fun onTouchEvent(event: MotionEvent?): Boolean {
// 在触摸事件中将事件传递给 GestureDetector 处理手势
if (event != null) {
gestureDetector.onTouchEvent(event)
}
return super.onTouchEvent(event)
}
private inner class GestureListener : GestureDetector.SimpleOnGestureListener() {
override fun onSingleTapConfirmed(e: MotionEvent): Boolean {
// 处理单击事件
val versionName = getPackageInfo().versionName
val textView = TextView(this@MainActivity)
textView.text =
"当前版本: $versionName\n获取最新: https://github.com/lizongying/my-tv/releases/"
val imageView = ImageView(this@MainActivity)
val drawable = ContextCompat.getDrawable(this@MainActivity, R.drawable.appreciate)
imageView.setImageDrawable(drawable)
val linearLayout = LinearLayout(this@MainActivity)
linearLayout.orientation = LinearLayout.VERTICAL
linearLayout.addView(textView)
linearLayout.addView(imageView)
val layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
)
imageView.layoutParams = layoutParams
textView.layoutParams = layoutParams
val builder: AlertDialog.Builder = AlertDialog.Builder(this@MainActivity)
builder
.setView(linearLayout)
val dialog: AlertDialog = builder.create()
dialog.show()
return true
}
override fun onFling(
e1: MotionEvent,
e2: MotionEvent,
velocityX: Float,
velocityY: Float
): Boolean {
// 如果 Y 方向的速度为负值,表示向上滑动
if (velocityY < 0) {
// 在这里执行上滑时的操作
if (mainFragment.isHidden) {
prev()
} else {
if (mainFragment.selectedPosition == 0) {
mainFragment.setSelectedPosition(
mainFragment.tvListViewModel.maxNum.size - 1,
false
)
}
}
}
if (velocityY > 0) {
// 在这里执行上滑时的操作
if (mainFragment.isHidden) {
next()
} else {
if (mainFragment.selectedPosition == mainFragment.tvListViewModel.maxNum.size - 1) {
// mainFragment.setSelectedPosition(0, false)
hideMainFragment()
return false
}
}
}
return super.onFling(e1, e2, velocityX, velocityY)
}
}
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
when (keyCode) {
KeyEvent.KEYCODE_BACK -> {
@@ -173,6 +258,9 @@ class MainActivity : FragmentActivity() {
KeyEvent.KEYCODE_DPAD_CENTER -> {
Log.i(TAG, "KEYCODE_DPAD_CENTER")
// if (mainFragment.isHidden) {
// mainFragment.checkProgram()
// }
switchMainFragment()
}
@@ -5,7 +5,6 @@ import android.content.SharedPreferences
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.core.content.ContextCompat
import androidx.leanback.app.BrowseSupportFragment
import androidx.leanback.widget.ArrayObjectAdapter
import androidx.leanback.widget.HeaderItem
@@ -44,13 +43,13 @@ class MainFragment : BrowseSupportFragment() {
sharedPref = activity?.getPreferences(Context.MODE_PRIVATE)
setupUIElements()
request = activity?.let { Request(it) }
loadRows()
setupEventListeners()
view?.post {
// request?.fetchPage()
// tvListViewModel.getTVViewModel(0)?.let { request?.fetchProgram(it) }
}
tvListViewModel.getTVListViewModel().value?.forEach { tvViewModel ->
@@ -86,6 +85,25 @@ class MainFragment : BrowseSupportFragment() {
).show()
}
}
tvViewModel.program.observe(viewLifecycleOwner) { _ ->
if (tvViewModel.program.value!!.isEmpty()) {
if (tvViewModel.programId.value != null) {
Log.i(TAG, "get program ${tvViewModel.title.value}")
request?.fetchProgram(tvViewModel)
}
}
}
}
}
fun checkProgram() {
tvListViewModel.getTVListViewModel().value?.forEach { tvViewModel ->
if (tvViewModel.program.value!!.isEmpty()) {
if (tvViewModel.programId.value != null) {
Log.i(TAG, "get program ${tvViewModel.title.value}")
request?.fetchProgram(tvViewModel)
}
}
}
}
@@ -104,46 +122,12 @@ class MainFragment : BrowseSupportFragment() {
}
override fun startHeadersTransition(withHeaders: Boolean) {
// check(mCanShowHeaders) { "Cannot start headers transition" }
// if (isInHeadersTransition || mShowingHeaders == withHeaders) {
// return
// }
// startHeadersTransitionInternal(withHeaders)
}
private fun setupUIElements() {
brandColor = ContextCompat.getColor(context!!, R.color.fastlane_background)
// var headers = headersSupportFragment
// headers.setMenuVisibility(false)
// Log.i(TAG, "headers $headers")
// setHeadersState(HEADERS_DISABLED);
//
// setHeaderPresenterSelector(object : PresenterSelector() {
// override fun getPresenter(o: Any): Presenter {
// return IconHeaderItemPresenter()
// }
// })
// showHeaders(true)
}
// private fun updateRows(tv: TV) {
//// 获取适配器中的数据
// val dataList = rowsAdapter?.replace(tv.id, tv)
////
////// 修改数据
////// 这里假设 dataList 是一个可变的列表
//// dataList[position] = updatedData
////
////// 刷新适配器
//// rowsAdapter.notifyItemChanged(position)
//// rowsAdapter.notifyItemRangeChanged()
// }
private fun loadRows() {
rowsAdapter = ArrayObjectAdapter(ListRowPresenter())
val cardPresenter = CardPresenter(lifecycleScope)
val cardPresenter = CardPresenter(viewLifecycleOwner)
var idx: Long = 0
for ((k, v) in TVList.list) {
@@ -234,14 +218,13 @@ class MainFragment : BrowseSupportFragment() {
view?.post {
val tvViewModel = tvListViewModel.getTVViewModel(itemPosition)
if (tvViewModel != null) {
tvViewModel.changed()
// if (tvViewModel.videoUrl.value!!.size > 1) {
// val videoIndex = tvViewModel.videoIndex.value?.plus(1)
// if (videoIndex == tvViewModel.videoUrl.value!!.size) {
// tvViewModel.setVideoIndex(0)
// }
// tvViewModel.changed()
// }
if (tvViewModel.videoUrl.value!!.size > 1) {
val videoIndex = tvViewModel.videoIndex.value?.plus(1)
if (videoIndex == tvViewModel.videoUrl.value!!.size) {
tvViewModel.setVideoIndex(0)
}
tvViewModel.changed()
}
}
}
}
@@ -25,6 +25,11 @@ class PlaybackFragment : VideoSupportFragment() {
view?.isFocusable = false
view?.isFocusableInTouchMode = false
val glueHost = VideoSupportFragmentGlueHost(this@PlaybackFragment)
mTransportControlGlue = PlaybackControlGlue(activity, playerAdapter)
mTransportControlGlue.host = glueHost
mTransportControlGlue.playWhenPrepared()
}
override fun showControlsOverlay(runAnimation: Boolean) {
@@ -44,12 +49,10 @@ class PlaybackFragment : VideoSupportFragment() {
lastVideoUrl = videoUrl
val glueHost = VideoSupportFragmentGlueHost(this@PlaybackFragment)
mTransportControlGlue = PlaybackControlGlue(activity, playerAdapter)
mTransportControlGlue.host = glueHost
mTransportControlGlue.playWhenPrepared()
playerAdapter?.callback = PlayerCallback(tvModel)
if (tvModel.ysp() != null) {
playerAdapter?.setMinimumLoadableRetryCount(0)
}
try {
playerAdapter?.setDataSource(Uri.parse(videoUrl))
} catch (e: IOException) {
@@ -1,20 +1,28 @@
package com.lizongying.mytv
import android.content.Context
import android.os.Handler
import android.os.Looper
import android.util.Base64
import android.util.Log
import com.lizongying.mytv.api.ApiClient
import com.lizongying.mytv.api.BtraceClient
import com.lizongying.mytv.api.LiveInfo
import com.lizongying.mytv.api.LiveInfoRequest
import com.lizongying.mytv.api.ProtoClient
import com.lizongying.mytv.api.YSP
import com.lizongying.mytv.api.YSPApiService
import com.lizongying.mytv.api.YSPBtraceService
import com.lizongying.mytv.api.YSPProtoService
import com.lizongying.mytv.models.TVViewModel
import com.lizongying.mytv.proto.Ysp.cn.yangshipin.oms.common.proto.pageModel
import com.lizongying.mytv.proto.Ysp.cn.yangshipin.omstv.common.proto.epgProgramModel
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import javax.crypto.Cipher
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec
@@ -23,10 +31,14 @@ import javax.crypto.spec.SecretKeySpec
class Request(var context: Context) {
private var ysp: YSP? = null
private var yspApiService: YSPApiService? = null
private var yspBtraceService: YSPBtraceService? = null
private var yspProtoService: YSPProtoService? = null
private val handler = Handler(Looper.getMainLooper())
private lateinit var myRunnable: MyRunnable
private var mapping = mapOf(
"CCTV4K" to "CCTV4K",
"CCTV4K" to "CCTV4K 超高清",
"CCTV1" to "CCTV1 综合",
"CCTV2" to "CCTV2 财经",
"CCTV4" to "CCTV4 中文国际",
@@ -77,10 +89,16 @@ class Request(var context: Context) {
ysp = YSP(context)
}
yspApiService = ApiClient().yspApiService
yspBtraceService = BtraceClient().yspBtraceService
yspProtoService = ProtoClient().yspProtoService
}
fun fetchData(tvModel: TVViewModel) {
if (::myRunnable.isInitialized) {
handler.removeCallbacks(myRunnable)
}
tvModel.seq = 0
val data = ysp?.switch(tvModel)
val title = tvModel.title.value
@@ -110,6 +128,9 @@ class Request(var context: Context) {
Log.i(TAG, "$title url $url")
tvModel.addVideoUrl(url)
tvModel.allReady()
myRunnable = MyRunnable(tvModel)
handler.post(myRunnable)
} else {
Log.e(TAG, "$title key error")
tvModel.firstSource()
@@ -131,6 +152,49 @@ class Request(var context: Context) {
})
}
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 = getCurrentDate2(),
seq = tvModel.seq.toString(),
)
?.enqueue(object : Callback<Void> {
override fun onResponse(call: Call<Void>, response: Response<Void>) {
if (response.isSuccessful) {
Log.i(TAG, "$title kvcollect success")
} else {
Log.e(TAG, "$title status error")
tvModel.firstSource()
}
}
override fun onFailure(call: Call<Void>, t: Throwable) {
Log.e(TAG, "$title btrace error")
}
})
tvModel.seq++
}
fun fetchPage() {
yspProtoService?.getPage()?.enqueue(object : Callback<pageModel.Response> {
override fun onResponse(
@@ -174,18 +238,37 @@ class Request(var context: Context) {
})
}
fun fetchProgram(tvModel: TVViewModel) {
yspProtoService?.getProgram("", "")?.enqueue(object : Callback<LiveInfo> {
override fun onResponse(call: Call<LiveInfo>, response: Response<LiveInfo>) {
if (response.isSuccessful) {
val liveInfo = response.body()
Log.i(TAG, "${liveInfo?.data?.playurl}")
}
}
private fun getCurrentDate(): String {
val currentDate = Date()
val formatter = SimpleDateFormat("yyyyMMdd", Locale.CHINA)
return formatter.format(currentDate)
}
override fun onFailure(call: Call<LiveInfo>, t: Throwable) {
}
})
private fun getCurrentDate2(): String {
val currentDate = Date()
val formatter = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.CHINA)
return formatter.format(currentDate)
}
fun fetchProgram(tvModel: TVViewModel) {
yspProtoService?.getProgram(tvModel.programId.value!!, getCurrentDate())
?.enqueue(object : Callback<epgProgramModel.Response> {
override fun onResponse(
call: Call<epgProgramModel.Response>,
response: Response<epgProgramModel.Response>
) {
if (response.isSuccessful) {
val program = response.body()
if (program != null) {
tvModel.addProgram(program.dataListList)
}
}
}
override fun onFailure(call: Call<epgProgramModel.Response>, t: Throwable) {
Log.e(TAG, "Program request failed", t)
}
})
}
private fun encryptTripleDES(key: ByteArray, iv: ByteArray): String {
+11 -11
View File
@@ -10,7 +10,7 @@ object TVList {
private fun setupTV(): Map<String, List<TV>> {
val tvs = """
央视频道
CCTV4K,,https://resources.yangshipin.cn/assets/oms/image/202306/3e9d06fd7244d950df5838750f1c6ac3456e172b51caca2c16d2282125b111e8.png?imageMogr2/format/webp,600002264,2000266303
CCTV4K 超高清,,https://resources.yangshipin.cn/assets/oms/image/202306/3e9d06fd7244d950df5838750f1c6ac3456e172b51caca2c16d2282125b111e8.png?imageMogr2/format/webp,600002264,2000266303
CCTV1 综合,http://tvpull.dxhmt.cn/tv/11481-4.m3u8,https://resources.yangshipin.cn/assets/oms/image/202306/d57905b93540bd15f0c48230dbbbff7ee0d645ff539e38866e2d15c8b9f7dfcd.png?imageMogr2/format/webp,600001859,2000210103
CCTV2 财经,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226195/index.m3u8,https://resources.yangshipin.cn/assets/oms/image/202306/20115388de0207131af17eac86c33049b95d69eaff064e55653a1b941810a006.png?imageMogr2/format/webp,600001800,2000203603
CCTV3 综艺,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226397/index.m3u8
@@ -40,36 +40,36 @@ CGTN 阿拉伯语频道,http://livear.cgtn.com/1000a/prog_index.m3u8,https://res
地方频道
东方卫视,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://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221225740/index.m3u8
吉林卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226397/index.m3u8
云南卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226444/index.m3u8
内蒙古卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226389/index.m3u8
新疆卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226460/index.m3u8
甘肃卫视,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://39.134.24.161/dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226212/index.m3u8
山西卫视,http://39.134.24.161/dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225763/index.m3u8
宁夏卫视,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
@@ -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>
)
@@ -68,7 +68,6 @@ class YSP(var context: Context) {
cnlid = tvModel.sid.value!!
defn = "fhd"
// guid = getGuid()
randStr = getRand()
timeStr = getTimeStr()
@@ -86,7 +85,7 @@ class YSP(var context: Context) {
return (Date().time / 1000).toString()
}
private fun generateGuid(): String {
fun generateGuid(): String {
val timestamp = (System.currentTimeMillis()).toString(36)
val originalString = Math.random().toString()
val resultString = if (originalString.startsWith("0.")) {
@@ -98,7 +97,7 @@ class YSP(var context: Context) {
return timestamp + "_" + randomPart
}
private fun getGuid(): String {
fun getGuid(): String {
var guid = sharedPref?.getString("guid", "")
if (guid == null || guid.length < 18) {
guid = generateGuid()
@@ -110,7 +109,7 @@ class YSP(var context: Context) {
return guid
}
private fun getRand(): String {
fun getRand(): String {
var n = ""
val e = "ABCDEFGHIJKlMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
val r = e.length
@@ -0,0 +1,71 @@
package com.lizongying.mytv.api
import retrofit2.Call
import retrofit2.http.Field
import retrofit2.http.FormUrlEncoded
import retrofit2.http.Headers
import retrofit2.http.POST
import retrofit2.http.Path
import retrofit2.http.Query
interface YSPBtraceService {
@FormUrlEncoded
@POST("kvcollect")
@Headers(
"content-type: application/x-www-form-urlencoded",
"referer: https://www.yangshipin.cn/",
)
fun kvcollect(
@Query("BossId") BossId: String = "2727",
@Query("c_timestamp") c_timestamp: String = "",
@Field("Pwd") Pwd: String = "1424084450",
@Field("fpid") fpid: String = "",
@Field("livepid") livepid: String = "",
@Field("prd") prd: String = "60000",
@Field("ftime") ftime: String = "",
@Field("prog") prog: String = "",
@Field("playno") playno: String = "",
@Field("guid") guid: String = "",
@Field("hh_ua") hh_ua: String = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36",
@Field("cdn") cdn: String = "waibao",
@Field("platform") platform: String = "5910204",
@Field("errcode") errcode: String = "-",
@Field("sUrl") sUrl: String = "",
@Field("seq") seq: String = "",
@Field("login_type") login_type: String = "undefined",
@Field("open_id") open_id: String = "undefined",
@Field("openid") openid: String = "undefined",
@Field("defn") defn: String = "fhd",
@Field("durl") durl: String = "-",
@Field("sdtfrom") sdtfrom: String = "ysp_pc_01",
@Field("firstreport") firstreport: String = "0",
@Field("fplayerver") fplayerver: String = "89",
@Field("cmd") cmd: String = "263",
@Field("fact1") fact1: String = "ysp_pc_live_b",
@Field("sRef") sRef: String = "-",
@Field("viewid") viewid: String = "",
@Field("geturltime") geturltime: String = "0",
@Field("hc_openid") hc_openid: String = "undefined",
@Field("downspeed") downspeed: String = "10",
@Field("c_host") c_host: String = "www.yangshipin.cn",
@Field("c_pathname") c_pathname: String = "www.yangshipin.cn/",
@Field("c_url") c_url: String = "www.yangshipin.cn/",
@Field("c_channel") c_channel: String = "-",
@Field("c_referrer") c_referrer: String = "-",
@Field("c_ssize") c_ssize: String = "618",
@Field("c_ua") c_ua: String = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36",
@Field("c_os") c_os: String = "mac os",
@Field("c_osv") c_osv: String = "os10.15.7",
@Field("c_browser") c_browser: String = "chrome",
@Field("c_browserv") c_browserv: String = "chrome119",
@Field("c_dvendor") c_dvendor: String = "apple",
@Field("c_dmodel") c_dmodel: String = "macintosh",
@Field("c_dtype") c_dtype: String = "unkown",
@Field("c_city") c_city: String = "disabled",
@Field("c_nation") c_nation: String = "disabled",
@Field("c_province") c_province: String = "disabled",
@Field("c_guid") c_guid: String = "",
@Field("c_vuid") c_vuid: String = "-",
): Call<Void>
}
@@ -1,6 +1,7 @@
package com.lizongying.mytv.api
import com.lizongying.mytv.proto.Ysp.cn.yangshipin.oms.common.proto.pageModel
import com.lizongying.mytv.proto.Ysp.cn.yangshipin.omstv.common.proto.epgProgramModel
import retrofit2.Call
import retrofit2.http.GET
import retrofit2.http.Headers
@@ -15,13 +16,13 @@ interface YSPProtoService {
fun getPage(
): Call<pageModel.Response>
@GET("api/yspepg/program/{livepid}/{cnlid}")
@GET("api/yspepg/program/{livepid}/{date}")
@Headers(
"yspappid: 519748109",
"platform: 109",
)
fun getProgram(
@Path("livepid") livepid: String,
@Path("cnlid") cnlid: String,
): Call<LiveInfo>
@Path("date") date: String,
): Call<epgProgramModel.Response>
}
@@ -1,14 +1,23 @@
package com.lizongying.mytv.models
import android.net.Uri
import android.util.Log
import androidx.annotation.OptIn
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.media3.common.MediaItem
import androidx.media3.common.util.UnstableApi
import androidx.media3.datasource.DefaultHttpDataSource
import androidx.media3.exoplayer.hls.HlsMediaSource
import com.lizongying.mytv.CustomLoadErrorHandlingPolicy
import com.lizongying.mytv.TV
import com.lizongying.mytv.proto.Ysp.cn.yangshipin.omstv.common.proto.programModel.Program
import java.util.Date
class TVViewModel(private var tv: TV) : ViewModel() {
private var mapping = mapOf(
"CCTV4K" to "CCTV4K",
"CCTV4K" to "CCTV4K 超高清",
"CCTV1" to "CCTV1 综合",
"CCTV2" to "CCTV2 财经",
"CCTV4" to "CCTV4 中文国际",
@@ -54,11 +63,156 @@ class TVViewModel(private var tv: TV) : ViewModel() {
"海南卫视" to "海南卫视",
).entries.associate { (key, value) -> value to key }
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/202309/74d3ac436a7e374879578de1d87a941fbf566d39d5632b027c5097891ed32bd5.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/202309/7c337e3dbe64402ec7e4678a619a4a6d95144e42f35161181ff78e143b7cf67a.png?imageMogr2/format/webp",
"CGTN 阿拉伯语频道" to "https://resources.yangshipin.cn/assets/oms/image/202306/2e44e2aa3e7a1cedf07fd0ae59fe69e86a60a2632660a006e3e9e7397b2d107e.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 "600084744",
"CGTN 阿拉伯语频道" to "600084782",
"东方卫视" 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 isFirstTime = true
private var rowPosition: Int = 0
private var itemPosition: Int = 0
private val _programId = MutableLiveData<String>()
val programId: LiveData<String>
get() = _programId
private var _program = MutableLiveData<MutableList<Program>>()
val program: LiveData<MutableList<Program>>
get() = _program
private val _id = MutableLiveData<Int>()
val id: LiveData<Int>
get() = _id
@@ -95,6 +249,10 @@ class TVViewModel(private var tv: TV) : ViewModel() {
val ready: LiveData<Boolean>
get() = _ready
private var mMinimumLoadableRetryCount = 3
var seq = 0
fun addVideoUrl(url: String) {
if (_videoUrl.value?.isNotEmpty() == true) {
if (_videoUrl.value!!.last().contains("cctv.cn")) {
@@ -140,8 +298,16 @@ class TVViewModel(private var tv: TV) : ViewModel() {
_title.value = tv.title
_videoUrl.value = tv.videoUrl
_videoIndex.value = tv.videoIndex
if (mappingLogo[tv.title] != null) {
_logo.value = mappingLogo[tv.title]
}
_pid.value = tv.pid
_sid.value = tv.sid
_program.value = mutableListOf()
if (mappingEPG[tv.title] != null) {
_programId.value = mappingEPG[tv.title]
}
}
fun getIsFirstTime(): Boolean {
@@ -183,6 +349,64 @@ class TVViewModel(private var tv: TV) : ViewModel() {
return mapping[tv.title]
}
fun getProgram(): MutableList<Program>? {
_program.value = (_program.value?.filter { it.et > (Date().time / 1000) })?.toMutableList()
return _program.value?.subList(0, 2)
}
fun getProgramOne(): Program? {
val programNew = (_program.value?.filter { it.et > (Date().time / 1000) })?.toMutableList()
if (programNew != null && _program.value != programNew) {
_program.value = programNew
}
if (_program.value!!.isEmpty()) {
return null
}
return _program.value?.first()
}
fun addProgram(p: MutableList<Program>) {
val p1 = (p.filter { it.et > (Date().time / 1000) }).toMutableList()
if (p1.isEmpty() || _program.value == p1) {
return
}
if (_program.value!!.isEmpty()) {
_program.value = p1
} else {
_program.value =
((_program.value?.filter { it.et > (Date().time / 1000) && 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).setLoadErrorHandlingPolicy(
CustomLoadErrorHandlingPolicy(mMinimumLoadableRetryCount)
).createMediaSource(
MediaItem.fromUri(
Uri.parse(videoUrl)
)
)
}
companion object {
private const val TAG = "TVViewModel"
}
+2 -1
View File
@@ -2,7 +2,8 @@
<style name="CustomImageCardViewStyle" parent="Widget.Leanback.ImageCardViewStyle">
<item name="lbImageCardViewType">Title</item>
<!-- <item name="infoVisibility">activated</item>-->
<item name="lbImageCardViewType">Title|Content</item>
</style>
<style name="CustomImageCardTheme" parent="Theme.Leanback">
+3 -1
View File
@@ -1,7 +1,9 @@
<resources>
<style name="Theme.MyTV" parent="@style/Theme.Leanback">
<item name="browseRowsMarginTop">40dp</item>
<item name="browsePaddingStart">11dp</item>
<item name="browsePaddingEnd">11dp</item>
<item name="browseRowsMarginTop">54dp</item>
</style>
</resources>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

After

Width:  |  Height:  |  Size: 1.6 MiB