fix still playing after exit

This commit is contained in:
Li ZongYing 2023-12-27 16:26:05 +08:00
parent bb4fd56845
commit c759ada907
6 changed files with 153 additions and 69 deletions

View File

@ -4,13 +4,22 @@
## 使用 ## 使用
下载安装[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.4
* 改变换台滑动方向,上一个频道下滑,下一个频道上滑
* 软件退出时,退出播放器
* 播放相同的频道,不再重复加载
* 暂时移除部分频道
## 其他 ## 其他
小米电视可以使用小米电视助手进行安装 小米电视可以使用小米电视助手进行安装

View File

@ -27,7 +27,7 @@ import java.security.MessageDigest
class MainActivity : FragmentActivity() { class MainActivity : FragmentActivity() {
private val playerFragment = PlayerFragment() var playerFragment = PlayerFragment()
private val mainFragment = MainFragment() private val mainFragment = MainFragment()
private val infoFragment = InfoFragment() private val infoFragment = InfoFragment()
@ -88,11 +88,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()
} }
@ -100,7 +100,7 @@ class MainActivity : FragmentActivity() {
val transaction = supportFragmentManager.beginTransaction() val transaction = supportFragmentManager.beginTransaction()
if (mainFragment.isHidden) { if (mainFragment.isHidden) {
focusMainFragment() // focusMainFragment()
transaction.show(mainFragment) transaction.show(mainFragment)
} else { } else {
transaction.hide(mainFragment) transaction.hide(mainFragment)
@ -113,7 +113,7 @@ class MainActivity : FragmentActivity() {
mainFragment.focus() mainFragment.focus()
} }
fun mainFragmentIsHidden(): Boolean { private fun mainFragmentIsHidden(): Boolean {
return mainFragment.isHidden return mainFragment.isHidden
} }
@ -126,7 +126,6 @@ class MainActivity : FragmentActivity() {
} }
override fun onTouchEvent(event: MotionEvent?): Boolean { override fun onTouchEvent(event: MotionEvent?): Boolean {
// 在触摸事件中将事件传递给 GestureDetector 处理手势
if (event != null) { if (event != null) {
gestureDetector.onTouchEvent(event) gestureDetector.onTouchEvent(event)
} }
@ -147,30 +146,27 @@ class MainActivity : FragmentActivity() {
velocityX: Float, velocityX: Float,
velocityY: Float velocityY: Float
): Boolean { ): Boolean {
// 如果 Y 方向的速度为负值,表示向上滑动 if (velocityY > 0) {
if (velocityY < 0) {
// 在这里执行上滑时的操作
if (mainFragment.isHidden) { if (mainFragment.isHidden) {
prev() prev()
} else { } else {
if (mainFragment.selectedPosition == 0) { // if (mainFragment.selectedPosition == 0) {
mainFragment.setSelectedPosition( // mainFragment.setSelectedPosition(
mainFragment.tvListViewModel.maxNum.size - 1, // mainFragment.tvListViewModel.maxNum.size - 1,
false // false
) // )
} // }
} }
} }
if (velocityY > 0) { if (velocityY < 0) {
// 在这里执行上滑时的操作
if (mainFragment.isHidden) { if (mainFragment.isHidden) {
next() next()
} else { } else {
if (mainFragment.selectedPosition == mainFragment.tvListViewModel.maxNum.size - 1) { // if (mainFragment.selectedPosition == mainFragment.tvListViewModel.maxNum.size - 1) {
// mainFragment.setSelectedPosition(0, false) //// mainFragment.setSelectedPosition(0, false)
hideMainFragment() // hideMainFragment()
return false // return false
} // }
} }
} }
return super.onFling(e1, e2, velocityX, velocityY) return super.onFling(e1, e2, velocityX, velocityY)

View File

@ -4,9 +4,6 @@ import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast import android.widget.Toast
import androidx.leanback.app.BrowseSupportFragment import androidx.leanback.app.BrowseSupportFragment
import androidx.leanback.widget.ArrayObjectAdapter import androidx.leanback.widget.ArrayObjectAdapter
@ -36,6 +33,9 @@ class MainFragment : BrowseSupportFragment() {
var tvListViewModel = TVListViewModel() var tvListViewModel = TVListViewModel()
private var sharedPref: SharedPreferences? = null private var sharedPref: SharedPreferences? = null
private var lastVideoUrl: String = ""
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
headersState = HEADERS_DISABLED headersState = HEADERS_DISABLED
@ -56,19 +56,23 @@ class MainFragment : BrowseSupportFragment() {
} }
tvListViewModel.getTVListViewModel().value?.forEach { tvViewModel -> tvListViewModel.getTVListViewModel().value?.forEach { tvViewModel ->
tvViewModel.ready.observe(viewLifecycleOwner) { _ -> tvViewModel.ready.observe(viewLifecycleOwner) { _ ->
if (tvViewModel.ready.value != null) {
// not first time && channel not change
if (tvViewModel.ready.value != null
&& tvViewModel.id.value == itemPosition
&& check(tvViewModel)
) {
Log.i(TAG, "ready ${tvViewModel.title.value}") Log.i(TAG, "ready ${tvViewModel.title.value}")
if (tvViewModel.id.value == itemPosition) { (activity as? MainActivity)?.play(tvViewModel)
(activity as? MainActivity)?.play(tvViewModel) // (activity as? MainActivity)?.switchInfoFragment(item)
// (activity as? MainActivity)?.switchInfoFragment(tv)
}
} }
} }
tvViewModel.change.observe(viewLifecycleOwner) { _ -> tvViewModel.change.observe(viewLifecycleOwner) { _ ->
if (tvViewModel.change.value != null) { if (tvViewModel.change.value != null && check(tvViewModel)) {
Log.i(TAG, "switch to ${tvViewModel.title.value}") val title = tvViewModel.title.value
Log.i(TAG, "switch $title")
if (tvViewModel.ysp() != null) { if (tvViewModel.ysp() != null) {
Log.i(TAG, "${tvViewModel.title.value} to get ysp") Log.i(TAG, "request $title")
lifecycleScope.launch(Dispatchers.IO) { lifecycleScope.launch(Dispatchers.IO) {
tvViewModel.let { request?.fetchData(it) } tvViewModel.let { request?.fetchData(it) }
} }
@ -82,7 +86,7 @@ class MainFragment : BrowseSupportFragment() {
) )
Toast.makeText( Toast.makeText(
activity, activity,
tvViewModel.title.value, title,
Toast.LENGTH_SHORT Toast.LENGTH_SHORT
).show() ).show()
} }
@ -98,6 +102,22 @@ 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 toLastPosition() { fun toLastPosition() {
setSelectedPosition( setSelectedPosition(
selectedPosition, false, selectedPosition, false,

View File

@ -5,9 +5,11 @@ 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 android.view.ViewTreeObserver
import androidx.annotation.OptIn import androidx.annotation.OptIn
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.media3.common.MediaItem import androidx.media3.common.MediaItem
import androidx.media3.common.PlaybackException
import androidx.media3.common.Player import androidx.media3.common.Player
import androidx.media3.common.VideoSize import androidx.media3.common.VideoSize
import androidx.media3.common.util.UnstableApi import androidx.media3.common.util.UnstableApi
@ -19,10 +21,10 @@ import com.lizongying.mytv.models.TVViewModel
class PlayerFragment : Fragment() { class PlayerFragment : Fragment() {
private var lastVideoUrl: String = ""
private var _binding: PlayerBinding? = null private var _binding: PlayerBinding? = null
private var playerView: PlayerView? = null private var playerView: PlayerView? = null
private var videoUrl: String? = null
private var tvViewModel: TVViewModel? = null
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, inflater: LayoutInflater, container: ViewGroup?,
@ -30,43 +32,80 @@ class PlayerFragment : Fragment() {
): View { ): View {
_binding = PlayerBinding.inflate(inflater, container, false) _binding = PlayerBinding.inflate(inflater, container, false)
playerView = _binding!!.playerView 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 return _binding!!.root
} }
@OptIn(UnstableApi::class) @OptIn(UnstableApi::class)
fun play(tvModel: TVViewModel) { fun play(tvViewModel: TVViewModel) {
val videoUrl = tvModel.videoIndex.value?.let { tvModel.videoUrl.value?.get(it) } this.tvViewModel = tvViewModel
if (videoUrl == null || videoUrl == "") { val videoUrlCurrent =
Log.e(TAG, "${tvModel.title.value} videoUrl is empty") tvViewModel.videoIndex.value?.let { tvViewModel.videoUrl.value?.get(it) }
return if (playerView == null || playerView?.player == null) {
} Log.i(TAG, "playerView not ready $view}")
videoUrl = videoUrlCurrent
if (videoUrl == lastVideoUrl) { } else {
Log.e(TAG, "videoUrl is duplication") Log.i(TAG, "playerView ok")
return playerView?.player?.run {
} val mediaItem = MediaItem.Builder()
tvViewModel.id.value?.let { mediaItem.setMediaId(it.toString()) }
lastVideoUrl = videoUrl videoUrlCurrent?.let { mediaItem.setUri(it) }
setMediaItem(mediaItem.build())
if (playerView!!.player == null) { prepare()
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
}
})
} }
}
playerView!!.player?.run { override fun onPause() {
setMediaItem(MediaItem.fromUri(videoUrl)) super.onPause()
prepare() if (playerView != null) {
playerView!!.player?.stop()
}
}
override fun onResume() {
super.onResume()
if (playerView != null) {
playerView!!.player?.play()
}
}
override fun onDestroy() {
super.onDestroy()
if (playerView != null) {
playerView!!.player?.release()
} }
} }

View File

@ -86,6 +86,26 @@ CGTN 纪录频道,https://livedoc.cgtn.com/500d/prog_index.m3u8,https://resource
浙江少儿,http://hw-m-l.cztv.com/channels/lantian/channel008/1080p.m3u8 浙江少儿,http://hw-m-l.cztv.com/channels/lantian/channel008/1080p.m3u8
移动专区 移动专区
天津卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221225740/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226204/index.m3u8
吉林卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226397/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225792/index.m3u8
云南卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226444/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225751/index.m3u8
内蒙古卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226389/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225786/index.m3u8
新疆卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226460/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225747/index.m3u8
甘肃卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221225633/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225754/index.m3u8
青海卫视,http://stream.qhbtv.com/qhws/sd/live.m3u8
陕西卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226457/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225821/index.m3u8
西藏卫视,http://39.134.24.161/dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226212/index.m3u8;http://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/3221225763/index.m3u8
宁夏卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225748/index.m3u8
安多卫视,http://stream.qhbtv.com/adws/sd/live.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226228/index.m3u8
三沙卫视,https://pullsstv90080111.ssws.tv/live/SSTV20220729.m3u8
延边卫视,http://live.ybtvyun.com/video/s10006-44f040627ca1/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226220/index.m3u8
浙江少儿,http://hw-m-l.cztv.com/channels/lantian/channel008/1080p.m3u8
CETV1,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225753/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 CETV2,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225756/index.m3u8
CETV3,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226226/index.m3u8 CETV3,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226226/index.m3u8

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