diff --git a/README.md b/README.md
index ca1289c..795eef8 100644
--- a/README.md
+++ b/README.md
@@ -41,6 +41,12 @@
## 更新日志
+### v1.0.7
+
+* 支持rtsp直播
+* 支持循环播放
+* 支持txt/m3u视频源
+
### v1.0.6
* 修复视频可能无声音的问题
@@ -88,11 +94,8 @@ adb install my-tv-0.apk
* 音量不同
* 收藏夹
-* 自定义源
* 节目增加预告
* 频道列表优化
-* 更新的视频源不会覆盖已有的节目源
-* 更新后自动播放
* 节目列表焦点消失的问题
## 赞赏
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 68115f7..d400d2f 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -74,6 +74,8 @@ dependencies {
// For HLS playback support with ExoPlayer
implementation("androidx.media3:media3-exoplayer-hls:$media3Version")
+ implementation("androidx.media3:media3-exoplayer-rtsp:$media3Version")
+
implementation("androidx.core:core-ktx:1.12.0")
implementation("androidx.leanback:leanback:1.0.0")
implementation("com.github.bumptech.glide:glide:4.11.0")
@@ -94,4 +96,6 @@ dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.0-RC")
implementation(files("libs/lib-decoder-ffmpeg-release.aar"))
+
+ implementation("io.github.lizongying:gua64:1.4.3")
}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 880e77c..2d29dcc 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -48,6 +48,7 @@
+
diff --git a/app/src/main/java/com/lizongying/mytv0/MainActivity.kt b/app/src/main/java/com/lizongying/mytv0/MainActivity.kt
index 8884559..ec67d72 100644
--- a/app/src/main/java/com/lizongying/mytv0/MainActivity.kt
+++ b/app/src/main/java/com/lizongying/mytv0/MainActivity.kt
@@ -17,7 +17,6 @@ import android.view.WindowManager
import android.widget.Toast
import androidx.fragment.app.FragmentActivity
import com.lizongying.mytv0.models.TVList
-import kotlin.math.abs
class MainActivity : FragmentActivity() {
@@ -147,25 +146,23 @@ class MainActivity : FragmentActivity() {
return super.onFling(e1, e2, velocityX, velocityY)
}
- override fun onScroll(
- e1: MotionEvent?,
- e2: MotionEvent,
- distanceX: Float,
- distanceY: Float
- ): Boolean {
- // 计算手势滑动的方向和距离
- val deltaY = e1?.y?.let { e2.y.minus(it) } ?: 0f
- val deltaX = e1?.x?.let { e2.x.minus(it) } ?: 0f
-
- // 如果是垂直滑动
- if (abs(deltaY) > abs(deltaX)) {
- if ((e1?.x ?: 0f) > windowManager.defaultDisplay.width * 2 / 3) {
- adjustVolume(deltaY) // 调整音量
- }
- }
-
- return super.onScroll(e1, e2, distanceX, distanceY)
- }
+// override fun onScroll(
+// e1: MotionEvent?,
+// e2: MotionEvent,
+// distanceX: Float,
+// distanceY: Float
+// ): Boolean {
+// val deltaY = e1?.y?.let { e2.y.minus(it) } ?: 0f
+// val deltaX = e1?.x?.let { e2.x.minus(it) } ?: 0f
+//
+// if (abs(deltaY) > abs(deltaX)) {
+// if ((e1?.x ?: 0f) > windowManager.defaultDisplay.width * 2 / 3) {
+// adjustVolume(deltaY)
+// }
+// }
+//
+// return super.onScroll(e1, e2, distanceX, distanceY)
+// }
private fun adjustVolume(deltaY: Float) {
val maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)
@@ -200,7 +197,16 @@ class MainActivity : FragmentActivity() {
// // 可以添加一个toast来显示当前亮度
// Toast.makeText(context, "Brightness: $brightness", Toast.LENGTH_SHORT).show()
// }
+ }
+ fun onPlayEnd() {
+ val tvModel = TVList.getTVModelCurrent()
+ if (SP.repeatInfo) {
+ infoFragment.show(tvModel)
+ if (SP.channelNum) {
+ channelFragment.show(tvModel)
+ }
+ }
}
fun play(position: Int) {
diff --git a/app/src/main/java/com/lizongying/mytv0/PlayerFragment.kt b/app/src/main/java/com/lizongying/mytv0/PlayerFragment.kt
index 50eeebe..b90f028 100644
--- a/app/src/main/java/com/lizongying/mytv0/PlayerFragment.kt
+++ b/app/src/main/java/com/lizongying/mytv0/PlayerFragment.kt
@@ -14,6 +14,7 @@ import androidx.media3.common.MediaItem
import androidx.media3.common.MimeTypes
import androidx.media3.common.PlaybackException
import androidx.media3.common.Player
+import androidx.media3.common.Player.REPEAT_MODE_ALL
import androidx.media3.common.VideoSize
import androidx.media3.common.util.UnstableApi
import androidx.media3.datasource.DataSource
@@ -24,6 +25,7 @@ import androidx.media3.exoplayer.DefaultRenderersFactory
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.exoplayer.mediacodec.MediaCodecSelector
import androidx.media3.exoplayer.mediacodec.MediaCodecUtil
+import com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_PERIOD_TRANSITION
import com.google.android.exoplayer2.SimpleExoPlayer
import com.lizongying.mytv0.databinding.PlayerBinding
import com.lizongying.mytv0.models.TVModel
@@ -41,6 +43,13 @@ class PlayerFragment : Fragment(), SurfaceHolder.Callback {
private lateinit var surfaceView: SurfaceView
+ private lateinit var mainActivity: MainActivity
+
+ override fun onActivityCreated(savedInstanceState: Bundle?) {
+ mainActivity = activity as MainActivity
+ super.onActivityCreated(savedInstanceState)
+ }
+
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
@@ -71,6 +80,7 @@ class PlayerFragment : Fragment(), SurfaceHolder.Callback {
.build()
}
playerView.player = player
+ player?.repeatMode = REPEAT_MODE_ALL
player?.playWhenReady = true
player?.addListener(object : Player.Listener {
override fun onVideoSizeChanged(videoSize: VideoSize) {
@@ -87,6 +97,28 @@ class PlayerFragment : Fragment(), SurfaceHolder.Callback {
}
}
+ override fun onIsPlayingChanged(isPlaying: Boolean) {
+ Log.i(TAG, "isPlaying $isPlaying")
+ super.onIsPlayingChanged(isPlaying)
+ }
+
+ override fun onPlaybackStateChanged(playbackState: Int) {
+ Log.i(TAG, "playbackState $playbackState")
+ super.onPlaybackStateChanged(playbackState)
+ }
+
+
+ override fun onPositionDiscontinuity(
+ oldPosition: Player.PositionInfo,
+ newPosition: Player.PositionInfo,
+ reason: Int
+ ) {
+ if (reason == DISCONTINUITY_REASON_PERIOD_TRANSITION) {
+ mainActivity.onPlayEnd()
+ }
+ super.onPositionDiscontinuity(oldPosition, newPosition, reason)
+ }
+
override fun onPlayerError(error: PlaybackException) {
super.onPlayerError(error)
tvModel?.setReady()
diff --git a/app/src/main/java/com/lizongying/mytv0/SP.kt b/app/src/main/java/com/lizongying/mytv0/SP.kt
index 694cd5d..e42d10c 100644
--- a/app/src/main/java/com/lizongying/mytv0/SP.kt
+++ b/app/src/main/java/com/lizongying/mytv0/SP.kt
@@ -21,6 +21,8 @@ object SP {
private const val KEY_POSITION_SUB = "position_sub"
+ private const val KEY_REPEAT_INFO = "repeat_info"
+
private lateinit var sp: SharedPreferences
/**
@@ -56,4 +58,8 @@ object SP {
var positionSub: Int
get() = sp.getInt(KEY_POSITION_SUB, 0)
set(value) = sp.edit().putInt(KEY_POSITION_SUB, value).apply()
+
+ var repeatInfo: Boolean
+ get() = sp.getBoolean(KEY_REPEAT_INFO, true)
+ set(value) = sp.edit().putBoolean(KEY_REPEAT_INFO, value).apply()
}
\ No newline at end of file
diff --git a/app/src/main/java/com/lizongying/mytv0/SettingFragment.kt b/app/src/main/java/com/lizongying/mytv0/SettingFragment.kt
index 6222950..527ab9d 100644
--- a/app/src/main/java/com/lizongying/mytv0/SettingFragment.kt
+++ b/app/src/main/java/com/lizongying/mytv0/SettingFragment.kt
@@ -49,6 +49,13 @@ class SettingFragment : Fragment() {
(activity as MainActivity).settingActive()
}
+ val switchRepeatInfo = _binding?.switchRepeatInfo
+ switchRepeatInfo?.isChecked = SP.repeatInfo
+ switchRepeatInfo?.setOnCheckedChangeListener { _, isChecked ->
+ SP.repeatInfo = isChecked
+ (activity as MainActivity).settingActive()
+ }
+
val updateManager = UpdateManager(context, this, context.appVersionCode)
binding.checkVersion.setOnClickListener {
OnClickListenerCheckVersion(updateManager)
diff --git a/app/src/main/java/com/lizongying/mytv0/models/TVList.kt b/app/src/main/java/com/lizongying/mytv0/models/TVList.kt
index 1887337..f7be32e 100644
--- a/app/src/main/java/com/lizongying/mytv0/models/TVList.kt
+++ b/app/src/main/java/com/lizongying/mytv0/models/TVList.kt
@@ -8,6 +8,7 @@ import androidx.lifecycle.MutableLiveData
import com.google.gson.JsonSyntaxException
import com.lizongying.mytv0.R
import com.lizongying.mytv0.showToast
+import io.github.lizongying.Gua
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -98,8 +99,98 @@ object TVList {
}
private fun str2List(str: String) {
- val type = object : com.google.gson.reflect.TypeToken>() {}.type
- list = com.google.gson.Gson().fromJson(str, type)
+ var string = str
+ val g = Gua()
+ if (g.verify(str)) {
+ string = g.decode(str)
+ }
+ if (string.isBlank()) {
+ return
+ }
+ Log.i("string", string)
+ Log.i("string first", "${string[0]}")
+ when (string[0]) {
+ '[' -> {
+ Log.i("", "1111111")
+ val type = object : com.google.gson.reflect.TypeToken>() {}.type
+ list = com.google.gson.Gson().fromJson(string, type)
+ }
+
+ '#' -> {
+ Log.i("", "2222222")
+ val lines = string.lines()
+
+ Log.i("lines", "$lines")
+ val nameRegex = Regex("""tvg-name="([^"]+)"""")
+ val logRegex = Regex("""tvg-logo="([^"]+)"""")
+ val groupRegex = Regex("""group-title="([^"]+)"""")
+
+ val l = mutableListOf()
+ for ((index, line) in lines.withIndex()) {
+ val trimmedLine = line.trim()
+ if (trimmedLine.startsWith("#EXTINF")) {
+ val info = trimmedLine.split(",")
+ Log.i("info", "$info")
+
+ val title = info.last()
+ val name = nameRegex.find(info.first())?.groupValues?.get(1)
+ val group = groupRegex.find(info.first())?.groupValues?.get(1)
+ val logo = logRegex.find(info.first())?.groupValues?.get(1)
+ val uris =
+ if (index + 1 < lines.size) listOf(lines[index + 1]) else emptyList()
+ Log.i("info", "$title $name $group $logo $uris")
+ val tv = TV(
+ 0,
+ name!!,
+ title,
+ "",
+ logo!!,
+ "",
+ uris,
+ mapOf(),
+ group!!,
+ listOf(),
+ )
+
+ l.add(tv)
+ }
+ }
+ list = l
+ }
+
+ else -> {
+ Log.i("", "333333")
+ val lines = string.lines()
+ var group = ""
+ val l = mutableListOf()
+ for (line in lines) {
+ val trimmedLine = line.trim()
+ if (trimmedLine.isNotEmpty()) {
+ if (trimmedLine.contains("#genre#")) {
+ group = trimmedLine.split(',', limit = 2)[0].trim()
+ } else {
+ val arr = trimmedLine.split(',').map { it.trim() }
+ val tv = TV(
+ 0,
+ "",
+ arr.first(),
+ "",
+ "",
+ "",
+ arr.drop(1),
+ mapOf(),
+ group,
+ listOf(),
+ )
+
+ l.add(tv)
+ }
+ }
+ }
+ list = l
+ }
+ }
+
Log.i("TVList", "$list")
listModel = list.map { tv ->
@@ -130,6 +221,10 @@ object TVList {
}
}
+ fun getTVModelCurrent(): TVModel {
+ return getTVModel(position.value!!)
+ }
+
fun getTVModel(idx: Int): TVModel {
return listModel[idx]
}
diff --git a/app/src/main/res/layout/setting.xml b/app/src/main/res/layout/setting.xml
index 5eddd40..48bddc6 100644
--- a/app/src/main/res/layout/setting.xml
+++ b/app/src/main/res/layout/setting.xml
@@ -98,5 +98,11 @@
android:layout_marginTop="5dp"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 97f558d..122ddbf 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -15,4 +15,5 @@
赞赏
配置地址
确认
+ 循环播放时显示信息
\ No newline at end of file