Compare commits

...
5 Commits
Author SHA1 Message Date
Li ZongYing 0f4a33a3fc change channel order 2024-01-25 11:37:06 +08:00
Li ZongYing c17d62c698 rename to setting 2024-01-19 13:49:43 +08:00
Li ZongYing 33d3da97e5 fix 13 2024-01-18 22:19:04 +08:00
Li ZongYing ba95ce5374 number selection 2024-01-18 21:42:06 +08:00
Li ZongYing e1ee909071 optimize memu hiding 2024-01-17 10:19:50 +08:00
15 changed files with 405 additions and 138 deletions
+14
View File
@@ -14,6 +14,20 @@
## 更新日志 ## 更新日志
### v1.4.7(高版本专用)
* 修复部分用户cctv13播放过程中卡住的问题
* 调整cctv的频道顺序
### v1.4.5(高版本专用)
* 数字选台配置
### v1.4.4(通用版)
* 优化图标显示
* 增加换台反转
### v1.3.3 ### v1.3.3
* 部分错误会提示用户 * 部分错误会提示用户
+1 -1
View File
@@ -9,7 +9,7 @@
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<application <application
android:allowBackup="true" android:allowBackup="true"
android:icon="@mipmap/ic_launcher" android:icon="@drawable/logo"
android:label="@string/app_name" android:label="@string/app_name"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/Theme.MyTV"> android:theme="@style/Theme.MyTV">
@@ -0,0 +1,74 @@
package com.lizongying.mytv
import android.os.Bundle
import android.os.Handler
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.lizongying.mytv.databinding.ChannelBinding
import com.lizongying.mytv.models.TVViewModel
class ChannelFragment : Fragment() {
private var _binding: ChannelBinding? = null
private val binding get() = _binding!!
private val handler = Handler()
private val delay: Long = 3000
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = ChannelBinding.inflate(inflater, container, false)
_binding!!.root.visibility = View.GONE
(activity as MainActivity).fragmentReady()
return binding.root
}
fun show(tvViewModel: TVViewModel) {
binding.channelContent.text = tvViewModel.id.value.toString()
handler.removeCallbacks(removeRunnable)
view?.visibility = View.VISIBLE
handler.postDelayed(removeRunnable, delay)
}
fun show(channel: String) {
if (binding.channelContent.text == "") {
binding.channelContent.text = channel
handler.removeCallbacks(removeRunnable)
view?.visibility = View.VISIBLE
handler.postDelayed(removeRunnable, delay)
} else {
val ch = "${binding.channelContent.text}$channel".toInt()
(activity as MainActivity).play(ch)
binding.channelContent.text = ""
view?.visibility = View.GONE
}
}
override fun onResume() {
super.onResume()
handler.postDelayed(removeRunnable, delay)
}
override fun onPause() {
super.onPause()
handler.removeCallbacks(removeRunnable)
}
private val removeRunnable = Runnable {
binding.channelContent.text = ""
view?.visibility = View.GONE
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
companion object {
private const val TAG = "ChannelFragment"
}
}
@@ -23,17 +23,11 @@ class InfoFragment : Fragment() {
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View { ): View {
_binding = InfoBinding.inflate(inflater, container, false) _binding = InfoBinding.inflate(inflater, container, false)
_binding!!.root.visibility = View.GONE
(activity as MainActivity).fragmentReady() (activity as MainActivity).fragmentReady()
return binding.root return binding.root
} }
override fun onResume() {
super.onResume()
// Use a Handler to delay the fragment transaction
handler.postDelayed(removeRunnable, delay)
}
fun show(tvViewModel: TVViewModel) { fun show(tvViewModel: TVViewModel) {
binding.textView.text = tvViewModel.title.value binding.textView.text = tvViewModel.title.value
if (tvViewModel.title.value == "CCTV8K 超高清") { if (tvViewModel.title.value == "CCTV8K 超高清") {
@@ -56,10 +50,13 @@ class InfoFragment : Fragment() {
handler.postDelayed(removeRunnable, delay) handler.postDelayed(removeRunnable, delay)
} }
override fun onResume() {
super.onResume()
handler.postDelayed(removeRunnable, delay)
}
override fun onPause() { override fun onPause() {
super.onPause() super.onPause()
Log.i(TAG, "onPause")
// Cancel the delayed task when the fragment is paused
handler.removeCallbacks(removeRunnable) handler.removeCallbacks(removeRunnable)
} }
@@ -27,6 +27,8 @@ class MainActivity : FragmentActivity() {
var playerFragment = PlayerFragment() var playerFragment = PlayerFragment()
private val mainFragment = MainFragment() private val mainFragment = MainFragment()
private val infoFragment = InfoFragment() private val infoFragment = InfoFragment()
private val channelFragment = ChannelFragment()
private lateinit var settingFragment: SettingFragment
private var doubleBackToExitPressedOnce = false private var doubleBackToExitPressedOnce = false
@@ -34,9 +36,13 @@ class MainActivity : FragmentActivity() {
private val handler = Handler() private val handler = Handler()
private val delay: Long = 4000 private val delay: Long = 4000
private val delayHideHelp: Long = 10000
private lateinit var sharedPref: SharedPreferences private lateinit var sharedPref: SharedPreferences
private var channelReversal = false private var channelReversal = false
private var channelNum = true
private var versionName = ""
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@@ -50,19 +56,41 @@ class MainActivity : FragmentActivity() {
supportFragmentManager.beginTransaction() supportFragmentManager.beginTransaction()
.add(R.id.main_browse_fragment, playerFragment) .add(R.id.main_browse_fragment, playerFragment)
.add(R.id.main_browse_fragment, infoFragment) .add(R.id.main_browse_fragment, infoFragment)
.add(R.id.main_browse_fragment, channelFragment)
.add(R.id.main_browse_fragment, mainFragment) .add(R.id.main_browse_fragment, mainFragment)
.hide(infoFragment) .hide(mainFragment)
.commit() .commit()
mainFragment.view?.requestFocus() mainFragment.view?.requestFocus()
} }
gestureDetector = GestureDetector(this, GestureListener()) gestureDetector = GestureDetector(this, GestureListener())
sharedPref = getPreferences(Context.MODE_PRIVATE) sharedPref = getPreferences(Context.MODE_PRIVATE)
channelReversal = sharedPref.getBoolean(CHANNEL_REVERSAL, false) channelReversal = sharedPref.getBoolean(CHANNEL_REVERSAL, channelReversal)
channelNum = sharedPref.getBoolean(CHANNEL_NUM, channelNum)
versionName = getPackageInfo().versionName
settingFragment = SettingFragment(versionName, channelReversal, channelNum)
} }
fun showInfoFragment(tvViewModel: TVViewModel) { fun showInfoFragment(tvViewModel: TVViewModel) {
infoFragment.show(tvViewModel) infoFragment.show(tvViewModel)
if (channelNum) {
channelFragment.show(tvViewModel)
}
}
private fun showChannel(channel: String) {
if (!mainFragment.isHidden) {
return
}
if (settingFragment.isVisible) {
return
}
if (channelNum) {
channelFragment.show(channel)
}
} }
fun play(tvViewModel: TVViewModel) { fun play(tvViewModel: TVViewModel) {
@@ -70,6 +98,10 @@ class MainActivity : FragmentActivity() {
mainFragment.view?.requestFocus() mainFragment.view?.requestFocus()
} }
fun play(itemPosition: Int) {
mainFragment.play(itemPosition)
}
fun prev() { fun prev() {
mainFragment.prev() mainFragment.prev()
} }
@@ -79,11 +111,11 @@ class MainActivity : FragmentActivity() {
} }
private fun prevSource() { private fun prevSource() {
mainFragment.prevSource() // mainFragment.prevSource()
} }
private fun nextSource() { private fun nextSource() {
mainFragment.nextSource() // mainFragment.nextSource()
} }
fun switchMainFragment() { fun switchMainFragment() {
@@ -105,7 +137,9 @@ class MainActivity : FragmentActivity() {
} }
private val hideRunnable = Runnable { private val hideRunnable = Runnable {
supportFragmentManager.beginTransaction().hide(mainFragment).commit() if (!mainFragment.isHidden) {
supportFragmentManager.beginTransaction().hide(mainFragment).commit()
}
} }
private fun mainFragmentIsHidden(): Boolean { private fun mainFragmentIsHidden(): Boolean {
@@ -180,14 +214,34 @@ class MainActivity : FragmentActivity() {
this.channelReversal = channelReversal this.channelReversal = channelReversal
} }
fun saveChannelNum(channelNum: Boolean) {
with(sharedPref.edit()) {
putBoolean(CHANNEL_NUM, channelNum)
apply()
}
this.channelNum = channelNum
}
private fun showHelp() { private fun showHelp() {
if (!mainFragment.isHidden) { if (!mainFragment.isHidden) {
return return
} }
val versionName = getPackageInfo().versionName Log.i(TAG, "settingFragment ${settingFragment.isVisible}")
val dialogFragment = MyDialogFragment(versionName, channelReversal) if (!settingFragment.isVisible) {
dialogFragment.show(supportFragmentManager, "settings_dialog") settingFragment.show(supportFragmentManager, "setting")
handler.removeCallbacks(hideHelp)
handler.postDelayed(hideHelp, delayHideHelp)
} else {
handler.removeCallbacks(hideHelp)
settingFragment.dismiss()
}
}
private val hideHelp = Runnable {
if (settingFragment.isVisible) {
settingFragment.dismiss()
}
} }
private fun channelUp() { private fun channelUp() {
@@ -244,6 +298,56 @@ class MainActivity : FragmentActivity() {
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean { override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
when (keyCode) { when (keyCode) {
KeyEvent.KEYCODE_0 -> {
showChannel("0")
return true
}
KeyEvent.KEYCODE_1 -> {
showChannel("1")
return true
}
KeyEvent.KEYCODE_2 -> {
showChannel("2")
return true
}
KeyEvent.KEYCODE_3 -> {
showChannel("3")
return true
}
KeyEvent.KEYCODE_4 -> {
showChannel("4")
return true
}
KeyEvent.KEYCODE_5 -> {
showChannel("5")
return true
}
KeyEvent.KEYCODE_6 -> {
showChannel("6")
return true
}
KeyEvent.KEYCODE_7 -> {
showChannel("7")
return true
}
KeyEvent.KEYCODE_8 -> {
showChannel("8")
return true
}
KeyEvent.KEYCODE_9 -> {
showChannel("9")
return true
}
KeyEvent.KEYCODE_ESCAPE -> { KeyEvent.KEYCODE_ESCAPE -> {
back() back()
return true return true
@@ -386,5 +490,6 @@ class MainActivity : FragmentActivity() {
companion object { companion object {
private const val TAG = "MainActivity" private const val TAG = "MainActivity"
private const val CHANNEL_REVERSAL = "channel_reversal" private const val CHANNEL_REVERSAL = "channel_reversal"
private const val CHANNEL_NUM = "channel_num"
} }
} }
@@ -25,19 +25,19 @@ 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
private var request: Request = Request() private var itemPosition = 0
private var rowsAdapter: ArrayObjectAdapter? = null private var rowsAdapter: ArrayObjectAdapter? = null
private var request = Request()
var tvListViewModel = TVListViewModel() var tvListViewModel = TVListViewModel()
private var sharedPref: SharedPreferences? = null private var sharedPref: SharedPreferences? = null
private var lastVideoUrl: String = "" private var lastVideoUrl = ""
private val handler = Handler(Looper.getMainLooper()) private val handler = Handler(Looper.getMainLooper())
private lateinit var mUpdateProgramRunnable: UpdateProgramRunnable private lateinit var mUpdateProgramRunnable: UpdateProgramRunnable
@@ -53,7 +53,6 @@ class MainFragment : BrowseSupportFragment() {
super.onActivityCreated(savedInstanceState) super.onActivityCreated(savedInstanceState)
activity?.let { request.initYSP(it) } activity?.let { request.initYSP(it) }
sharedPref = activity?.getPreferences(Context.MODE_PRIVATE) sharedPref = activity?.getPreferences(Context.MODE_PRIVATE)
loadRows() loadRows()
@@ -100,7 +99,6 @@ class MainFragment : BrowseSupportFragment() {
if (check(tvViewModel)) { if (check(tvViewModel)) {
(activity as? MainActivity)?.play(tvViewModel) (activity as? MainActivity)?.play(tvViewModel)
(activity as? MainActivity)?.showInfoFragment(tvViewModel) (activity as? MainActivity)?.showInfoFragment(tvViewModel)
setSelectedPosition( setSelectedPosition(
tvViewModel.getRowPosition(), true, tvViewModel.getRowPosition(), true,
SelectItemViewHolderTask(tvViewModel.getItemPosition()) SelectItemViewHolderTask(tvViewModel.getItemPosition())
@@ -114,58 +112,6 @@ class MainFragment : BrowseSupportFragment() {
fragmentReady() fragmentReady()
} }
override fun onDestroy() {
super.onDestroy()
handler.removeCallbacks(mUpdateProgramRunnable)
with(sharedPref!!.edit()) {
putInt("position", itemPosition)
apply()
}
}
fun updateProgram(tvViewModel: TVViewModel) {
val timestamp = getDateTimestamp()
if (timestamp - tvViewModel.programUpdateTime > 60) {
if (tvViewModel.program.value!!.isEmpty()) {
tvViewModel.programUpdateTime = timestamp
request.fetchProgram(tvViewModel)
} else {
if (timestamp - tvViewModel.program.value!!.last().et < 600) {
tvViewModel.programUpdateTime = timestamp
request.fetchProgram(tvViewModel)
}
}
}
}
inner class UpdateProgramRunnable : Runnable {
override fun run() {
tvListViewModel.getTVListViewModel().value?.filter { it.programId.value != null }
?.forEach { tvViewModel ->
updateProgram(
tvViewModel
)
}
handler.postDelayed(this, 60000)
}
}
fun check(tvViewModel: TVViewModel): Boolean {
val title = tvViewModel.title.value
val videoUrl = tvViewModel.videoIndex.value?.let { tvViewModel.videoUrl.value?.get(it) }
if (videoUrl == null || videoUrl == "") {
Log.e(TAG, "$title videoUrl is empty")
return false
}
if (videoUrl == lastVideoUrl) {
Log.e(TAG, "$title videoUrl is duplication")
return false
}
return true
}
fun toLastPosition() { fun toLastPosition() {
setSelectedPosition( setSelectedPosition(
selectedPosition, false, selectedPosition, false,
@@ -206,51 +152,13 @@ class MainFragment : BrowseSupportFragment() {
adapter = rowsAdapter adapter = rowsAdapter
itemPosition = sharedPref?.getInt("position", 0)!! itemPosition = sharedPref?.getInt(POSITION, 0)!!
if (itemPosition >= tvListViewModel.size()) { if (itemPosition >= tvListViewModel.size()) {
itemPosition = 0 itemPosition = 0
} }
tvListViewModel.setItemPosition(itemPosition) tvListViewModel.setItemPosition(itemPosition)
} }
fun fragmentReady() {
ready++
Log.i(TAG, "ready $ready")
if (ready == 3) {
// request.fetchPage()
val tvViewModel = tvListViewModel.getTVViewModel(itemPosition)
tvViewModel?.changed()
(activity as? MainActivity)?.switchMainFragment()
}
}
fun prev() {
view?.post {
itemPosition--
if (itemPosition == -1) {
itemPosition = tvListViewModel.size() - 1
}
tvListViewModel.setItemPosition(itemPosition)
val tvViewModel = tvListViewModel.getTVViewModel(itemPosition)
tvViewModel?.changed()
}
}
fun next() {
view?.post {
itemPosition++
if (itemPosition == tvListViewModel.size()) {
itemPosition = 0
}
tvListViewModel.setItemPosition(itemPosition)
val tvViewModel = tvListViewModel.getTVViewModel(itemPosition)
tvViewModel?.changed()
}
}
fun prevSource() { fun prevSource() {
view?.post { view?.post {
val tvViewModel = tvListViewModel.getTVViewModel(itemPosition) val tvViewModel = tvListViewModel.getTVViewModel(itemPosition)
@@ -297,9 +205,7 @@ class MainFragment : BrowseSupportFragment() {
if (itemPosition != item.id.value!!) { if (itemPosition != item.id.value!!) {
itemPosition = item.id.value!! itemPosition = item.id.value!!
tvListViewModel.setItemPosition(itemPosition) tvListViewModel.setItemPosition(itemPosition)
tvListViewModel.getTVViewModel(itemPosition)?.changed()
val tvViewModel = tvListViewModel.getTVViewModel(itemPosition)
tvViewModel?.changed()
} }
(activity as? MainActivity)?.switchMainFragment() (activity as? MainActivity)?.switchMainFragment()
} }
@@ -318,12 +224,113 @@ class MainFragment : BrowseSupportFragment() {
} }
} }
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 fragmentReady() {
ready++
Log.i(TAG, "ready $ready")
if (ready == 4) {
// request.fetchPage()
tvListViewModel.getTVViewModel(itemPosition)?.changed()
}
}
fun play(itemPosition: Int) {
view?.post {
if (itemPosition < tvListViewModel.size()) {
this.itemPosition = itemPosition
tvListViewModel.setItemPosition(itemPosition)
tvListViewModel.getTVViewModel(itemPosition)?.changed()
}
}
}
fun prev() {
view?.post {
itemPosition--
if (itemPosition == -1) {
itemPosition = tvListViewModel.size() - 1
}
tvListViewModel.setItemPosition(itemPosition)
tvListViewModel.getTVViewModel(itemPosition)?.changed()
}
}
fun next() {
view?.post {
itemPosition++
if (itemPosition == tvListViewModel.size()) {
itemPosition = 0
}
tvListViewModel.setItemPosition(itemPosition)
tvListViewModel.getTVViewModel(itemPosition)?.changed()
}
}
fun updateProgram(tvViewModel: TVViewModel) {
val timestamp = getDateTimestamp()
if (timestamp - tvViewModel.programUpdateTime > 60) {
if (tvViewModel.program.value!!.isEmpty()) {
tvViewModel.programUpdateTime = timestamp
request.fetchProgram(tvViewModel)
} else {
if (timestamp - tvViewModel.program.value!!.last().et < 600) {
tvViewModel.programUpdateTime = timestamp
request.fetchProgram(tvViewModel)
}
}
}
}
inner class UpdateProgramRunnable : Runnable {
override fun run() {
tvListViewModel.getTVListViewModel().value?.filter { it.programId.value != null }
?.forEach { tvViewModel ->
updateProgram(
tvViewModel
)
}
handler.postDelayed(this, 60000)
}
}
override fun onStop() {
Log.i(TAG, "onStop")
super.onStop()
with(sharedPref!!.edit()) {
putInt(POSITION, itemPosition)
apply()
}
Log.i(TAG, "POSITION saved")
}
override fun onDestroy() {
Log.i(TAG, "onDestroy")
super.onDestroy()
handler.removeCallbacks(mUpdateProgramRunnable)
}
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
view!!.requestFocus() view?.post { view?.requestFocus() }
} }
companion object { companion object {
private const val TAG = "MainFragment" private const val TAG = "MainFragment"
private const val POSITION = "position"
} }
} }
@@ -74,10 +74,9 @@ class PlayerFragment : Fragment() {
@OptIn(UnstableApi::class) @OptIn(UnstableApi::class)
fun play(tvViewModel: TVViewModel) { fun play(tvViewModel: TVViewModel) {
this.tvViewModel = tvViewModel this.tvViewModel = tvViewModel
val videoUrlCurrent = val videoUrlCurrent = tvViewModel.getVideoUrlCurrent()
tvViewModel.videoIndex.value?.let { tvViewModel.videoUrl.value?.get(it) }
playerView?.player?.run { playerView?.player?.run {
videoUrlCurrent?.let { setMediaItem(MediaItem.fromUri(it)) } setMediaItem(MediaItem.fromUri(videoUrlCurrent))
prepare() prepare()
} }
} }
@@ -103,6 +102,11 @@ class PlayerFragment : Fragment() {
} }
} }
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
companion object { companion object {
private const val TAG = "PlaybackVideoFragment" private const val TAG = "PlaybackVideoFragment"
} }
@@ -8,12 +8,20 @@ import androidx.fragment.app.DialogFragment
import com.lizongying.mytv.databinding.DialogBinding import com.lizongying.mytv.databinding.DialogBinding
class MyDialogFragment(private val versionName: String, private val channelReversal: Boolean) : class SettingFragment(private val versionName: String,
private val channelReversal: Boolean,
private val channelNum: Boolean,
) :
DialogFragment() { DialogFragment() {
private var _binding: DialogBinding? = null private var _binding: DialogBinding? = null
private val binding get() = _binding!! private val binding get() = _binding!!
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(STYLE_NO_TITLE, 0)
}
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
@@ -23,12 +31,18 @@ class MyDialogFragment(private val versionName: String, private val channelRever
_binding?.version?.text = _binding?.version?.text =
"当前版本: $versionName\n获取最新: https://github.com/lizongying/my-tv/releases/" "当前版本: $versionName\n获取最新: https://github.com/lizongying/my-tv/releases/"
val switchView = _binding?.switchView val switchChannelReversal = _binding?.switchChannelReversal
switchView?.isChecked = channelReversal switchChannelReversal?.isChecked = channelReversal
switchView?.setOnCheckedChangeListener { _, isChecked -> switchChannelReversal?.setOnCheckedChangeListener { _, isChecked ->
(activity as MainActivity).saveChannelReversal(isChecked) (activity as MainActivity).saveChannelReversal(isChecked)
} }
val switchChannelNum = _binding?.switchChannelNum
switchChannelNum?.isChecked = channelNum
switchChannelNum?.setOnCheckedChangeListener { _, isChecked ->
(activity as MainActivity).saveChannelNum(isChecked)
}
return binding.root return binding.root
} }
@@ -38,7 +52,7 @@ class MyDialogFragment(private val versionName: String, private val channelRever
} }
companion object { companion object {
const val TAG = "MyDialogFragment" const val TAG = "SettingFragment"
} }
} }
@@ -145,7 +145,7 @@ object TVList {
"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","2000204603"), "CCTV13 新闻" to arrayOf("600001811","2000204603"),
"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"),
@@ -196,8 +196,6 @@ object TVList {
private fun setupTV(): Map<String, List<TV>> { private fun setupTV(): Map<String, List<TV>> {
val tvs = """ val tvs = """
央视频道 央视频道
CCTV4K 超高清,
CCTV8K 超高清,
CCTV1 综合,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226231/index.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 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
@@ -216,6 +214,8 @@ CCTV14 少儿,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226193/inde
CCTV15 音乐,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225785/index.m3u8 CCTV15 音乐,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225785/index.m3u8
CCTV16 奥林匹克,http://39.134.24.162/dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226921/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226921/index.m3u8 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 CCTV17 农业农村,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226198/index.m3u8
CCTV4K 超高清,
CCTV8K 超高清,
风云剧场,http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226950/index.m3u8 风云剧场,http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226950/index.m3u8
第一剧场,http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226959/index.m3u8 第一剧场,http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226959/index.m3u8
怀旧剧场,http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226972/index.m3u8 怀旧剧场,http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226972/index.m3u8
@@ -1,5 +1,7 @@
package com.lizongying.mytv package com.lizongying.mytv
import android.content.res.Resources
import android.util.TypedValue
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Date import java.util.Date
import java.util.Locale import java.util.Locale
@@ -12,4 +14,20 @@ object Utils {
fun getDateTimestamp(): Long { fun getDateTimestamp(): Long {
return Date().time / 1000 return Date().time / 1000
} }
fun dpToPx(dp: Float): Int {
return TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
dp,
Resources.getSystem().displayMetrics
).toInt()
}
fun dpToPx(dp: Int): Int {
return TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
dp.toFloat(),
Resources.getSystem().displayMetrics
).toInt()
}
} }
@@ -20,7 +20,7 @@ class TVViewModel(private var tv: TV) : ViewModel() {
private var itemPosition: Int = 0 private var itemPosition: Int = 0
var retryTimes: Int = 0 var retryTimes: Int = 0
var retryMaxTimes: Int = 5 var retryMaxTimes: Int = 8
var programUpdateTime: Long = 0 var programUpdateTime: Long = 0
private val _errInfo = MutableLiveData<String>() private val _errInfo = MutableLiveData<String>()
@@ -222,18 +222,25 @@ class TVViewModel(private var tv: TV) : ViewModel() {
mMinimumLoadableRetryCount = minimumLoadableRetryCount mMinimumLoadableRetryCount = minimumLoadableRetryCount
} }
/**
* (playerView?.player as ExoPlayer).setMediaSource(tvViewModel.buildSource())
*/
@OptIn(UnstableApi::class) @OptIn(UnstableApi::class)
fun buildSource(videoUrl: String, mHeaders: Map<String, String>?): HlsMediaSource { fun buildSource(): HlsMediaSource {
val httpDataSource = DefaultHttpDataSource.Factory() val httpDataSource = DefaultHttpDataSource.Factory()
mHeaders?.let { httpDataSource.setDefaultRequestProperties(it) } mHeaders?.let { httpDataSource.setDefaultRequestProperties(it) }
return HlsMediaSource.Factory(httpDataSource).createMediaSource( return HlsMediaSource.Factory(httpDataSource).createMediaSource(
MediaItem.fromUri( MediaItem.fromUri(
Uri.parse(videoUrl) Uri.parse(getVideoUrlCurrent())
) )
) )
} }
fun getVideoUrlCurrent(): String {
return _videoUrl.value!![_videoIndex.value!!]
}
companion object { companion object {
private const val TAG = "TVViewModel" private const val TAG = "TVViewModel"
} }
+20
View File
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/channel_fragment"
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_gravity="end|top"
android:layout_marginTop="20dp"
android:layout_marginEnd="50dp">
<TextView
android:id="@+id/channel_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_marginTop="0dp"
android:text="99"
android:textColor="#FFEEEEEE"
android:textStyle="bold"
android:textSize="50sp" />
</FrameLayout>
+8 -2
View File
@@ -26,12 +26,18 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="10dp" android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:text="当前版本: $versionName\n获取最新: https://github.com/lizongying/my-tv/releases/" android:text="当前版本: $versionName\n获取最新: https://github.com/lizongying/my-tv/releases/"
></TextView> ></TextView>
<Switch <Switch
android:id="@+id/switch_view" android:id="@+id/switch_channel_reversal"
android:text="@string/title_channel_reversal" android:text="@string/title_channel_reversal"
android:layout_marginTop="10dp"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Switch
android:id="@+id/switch_channel_num"
android:text="@string/title_channel_num"
android:layout_marginTop="10dp"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" /> android:layout_height="wrap_content" />
</LinearLayout> </LinearLayout>
+2 -1
View File
@@ -1,4 +1,5 @@
<resources> <resources>
<string name="app_name">我的电视</string> <string name="app_name">我的电视</string>
<string name="title_channel_reversal">频道反转</string> <string name="title_channel_reversal">换台反转</string>
<string name="title_channel_num">数字选台</string>
</resources> </resources>
+2 -2
View File
@@ -1,6 +1,6 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules. // Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins { plugins {
id 'com.android.application' version '8.2.0' apply false id 'com.android.application' version '8.2.1' apply false
id 'com.android.library' version '8.2.0' apply false id 'com.android.library' version '8.2.1' apply false
id 'org.jetbrains.kotlin.android' version '1.9.21' apply false id 'org.jetbrains.kotlin.android' version '1.9.21' apply false
} }