Skip to content

Commit

Permalink
optimize video source selection
Browse files Browse the repository at this point in the history
  • Loading branch information
lizongying committed Dec 6, 2024
1 parent 119fdaf commit a056ddb
Show file tree
Hide file tree
Showing 15 changed files with 268 additions and 293 deletions.
15 changes: 15 additions & 0 deletions HISTORY.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
## 更新日誌

### v1.3.8.3

* 修復視頻源導入成功時卻顯示錯誤的問題
* 擴寬視頻源選擇界面
* 視頻源按添加時間倒序排列
* 加速GitHub視頻源導入

### v1.3.8.2-kitkat

* 支持多源選擇

### v1.3.8.2

* 支持多源選擇
Expand All @@ -8,6 +19,10 @@

* 支持rtmp

### v1.3.8.0-kitkat

* 優化遠程配置

### v1.3.8.0

* 優化遠程配置
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

注意:

* 遇到問題可以先考慮重啟/恢復默認/清除數據/重新安裝等方式自助解決
* 視頻源可以設置為本地文件,格式如:file:///mnt/sdcard/tmp/channels.m3u
/channels.m3u

Expand Down
2 changes: 1 addition & 1 deletion app/src/main/java/com/lizongying/mytv0/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ class MainActivity : AppCompatActivity() {
.use { i ->
val channels = i.readText()
if (channels.isNotEmpty()) {
viewModel.tryStr2List(channels, null, "")
viewModel.tryStr2Channels(channels, null, "")
} else {
Log.w(TAG, "$it is empty")
}
Expand Down
125 changes: 79 additions & 46 deletions app/src/main/java/com/lizongying/mytv0/MainViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import com.lizongying.mytv0.models.TVModel
import com.lizongying.mytv0.requests.HttpClient
import com.lizongying.mytv0.showToast
import io.github.lizongying.Gua
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
Expand All @@ -35,7 +34,7 @@ class MainViewModel : ViewModel() {
var listModel: List<TVModel> = listOf()
val groupModel = TVGroupModel()
private var cacheFile: File? = null
private var cacheConfig = ""
private var cacheChannels = ""
private var initialized = false

val sources = Sources()
Expand Down Expand Up @@ -67,7 +66,7 @@ class MainViewModel : ViewModel() {
if (it.startsWith("http")) {
viewModelScope.launch {
Log.i(TAG, "updateConfig $it")
update(it)
importFromUrl(it)
SP.epg?.let { i ->
updateEPG(i)
}
Expand Down Expand Up @@ -95,17 +94,17 @@ class MainViewModel : ViewModel() {
cacheFile!!.createNewFile()
}

cacheConfig = getCache()
cacheChannels = getCache()

if (cacheConfig.isEmpty()) {
cacheConfig = context.resources.openRawResource(R.raw.channels).bufferedReader()
if (cacheChannels.isEmpty()) {
cacheChannels = context.resources.openRawResource(R.raw.channels).bufferedReader()
.use { it.readText() }
}

Log.i(TAG, "cacheConfig $cacheConfig")
Log.i(TAG, "cacheChannels $cacheChannels")

try {
str2List(cacheConfig)
str2Channels(cacheChannels)
} catch (e: Exception) {
e.printStackTrace()
cacheFile!!.deleteOnExit()
Expand Down Expand Up @@ -142,35 +141,68 @@ class MainViewModel : ViewModel() {
}
}

suspend fun update(serverUrl: String) {
private suspend fun importFromUrl(serverUrl: String) {
Log.i(TAG, "request $serverUrl")
try {
withContext(Dispatchers.IO) {
val request = okhttp3.Request.Builder().url(serverUrl).build()
val response = HttpClient.okHttpClient.newCall(request).execute()
val urls =
if (serverUrl.startsWith("https://raw.githubusercontent.com") || serverUrl.startsWith("https://github.com")) {
listOf(
"https://ghp.ci/",
"https://gh.llkk.cc/",
"https://github.moeyy.xyz/",
"https://mirror.ghproxy.com/",
"https://ghproxy.cn/",
"https://ghproxy.net/",
"https://ghproxy.click/",
"https://ghproxy.com/",
"https://github.moeyy.cn/",
"https://gh-proxy.llyke.com/",
).map {
Pair("$it$serverUrl", serverUrl)
}
} else {
listOf(Pair(serverUrl, serverUrl))
}

if (response.isSuccessful) {
val str = response.body?.string() ?: ""
withContext(Dispatchers.Main) {
tryStr2List(str, null, serverUrl)
var err = 0
var shouldBreak = false
for ((a, b) in urls) {
try {
withContext(Dispatchers.IO) {
val request = okhttp3.Request.Builder().url(a).build()
val response = HttpClient.okHttpClient.newCall(request).execute()

if (response.isSuccessful) {
val str = response.body?.string() ?: ""
withContext(Dispatchers.Main) {
tryStr2Channels(str, null, b)
}
err = 0
shouldBreak = true
} else {
Log.e(TAG, "Request status ${response.code}")
err = R.string.channel_status_error
}
} else {
Log.e(TAG, "Request status ${response.code}")
R.string.channel_status_error.showToast()
}
} catch (e: JsonSyntaxException) {
e.printStackTrace()
Log.e(TAG, "JSON Parse Error", e)
err = R.string.channel_format_error
shouldBreak = true
} catch (e: NullPointerException) {
e.printStackTrace()
Log.e(TAG, "Null Pointer Error", e)
err = R.string.channel_read_error
} catch (e: Exception) {
e.printStackTrace()
Log.e(TAG, "Request error $e")
err = R.string.channel_request_error
}
} catch (e: JsonSyntaxException) {
e.printStackTrace()
Log.e("JSON Parse Error", e.toString())
R.string.channel_format_error.showToast()
} catch (e: NullPointerException) {
e.printStackTrace()
Log.e("Null Pointer Error", e.toString())
R.string.channel_read_error.showToast()
} catch (e: Exception) {
e.printStackTrace()
Log.e(TAG, "Request error $e")
R.string.channel_request_error.showToast()

if (shouldBreak) break
}

if (err != 0) {
err.showToast()
}
}

Expand All @@ -179,14 +211,14 @@ class MainViewModel : ViewModel() {
.use { it.readText() }

try {
str2List(str)
str2Channels(str)
} catch (e: Exception) {
e.printStackTrace()
R.string.channel_read_error.showToast()
}
}

fun parseUri(uri: Uri) {
fun importFromUri(uri: Uri) {
if (uri.scheme == "file") {
val file = uri.toFile()
Log.i(TAG, "file $file")
Expand All @@ -197,19 +229,19 @@ class MainViewModel : ViewModel() {
return
}

tryStr2List(str, file, uri.toString())
tryStr2Channels(str, file, uri.toString())
} else {
CoroutineScope(Dispatchers.IO).launch {
update(uri.toString())
viewModelScope.launch {
importFromUrl(uri.toString())
}
}
}

fun tryStr2List(str: String, file: File?, url: String) {
fun tryStr2Channels(str: String, file: File?, url: String) {
try {
if (str2List(str)) {
if (str2Channels(str)) {
cacheFile!!.writeText(str)
cacheConfig = str
cacheChannels = str
if (url.isNotEmpty()) {
SP.config = url
sources.addSource(
Expand All @@ -230,24 +262,25 @@ class MainViewModel : ViewModel() {
}
}

private fun str2List(str: String): Boolean {
private fun str2Channels(str: String): Boolean {
var string = str
if (initialized && string == cacheConfig) {
Log.w(TAG, "same config")
if (initialized && string == cacheChannels) {
Log.w(TAG, "same channels")
return false
}

val g = Gua()
if (g.verify(str)) {
string = g.decode(str)
}

if (string.isEmpty()) {
Log.w(TAG, "config is empty")
Log.w(TAG, "channels is empty")
return false
}

if (initialized && string == cacheConfig) {
Log.w(TAG, "same config")
if (initialized && string == cacheChannels) {
Log.w(TAG, "same channels")
return false
}

Expand Down
65 changes: 57 additions & 8 deletions app/src/main/java/com/lizongying/mytv0/SP.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ package com.lizongying.mytv0
import android.content.Context
import android.content.SharedPreferences
import android.util.Log
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import com.lizongying.mytv0.data.Source

object SP {
private const val TAG = "SP"
Expand Down Expand Up @@ -54,18 +57,68 @@ object SP {

private const val KEY_SOURCES = "sources"

private const val KEY_SOURCE_ID = "source_id"

const val DEFAULT_CONFIG_URL = ""
const val DEFAULT_CHANNEL_NUM = false
const val DEFAULT_EPG = "https://live.fanmingming.com/e.xml"
const val DEFAULT_CHANNEL = 0
const val DEFAULT_SHOW_ALL_CHANNELS = false
const val DEFAULT_SHOW_ALL_CHANNELS = true
const val DEFAULT_COMPACT_MENU = true
const val DEFAULT_DISPLAY_SECONDS = false
const val DEFAULT_LOG_TIMES = 10
const val DEFAULT_POSITION_GROUP = 1
const val DEFAULT_POSITION = 0
val DEFAULT_SOURCES = Gson().toJson(listOf(
"https://live.fanmingming.com/tv/m3u/ipv6.m3u",
"https://live.fanmingming.com/tv/m3u/itv.m3u",
"https://live.fanmingming.com/tv/m3u/index.m3u",

"https://iptv-org.github.io/iptv/index.m3u",

// https://github.com/Guovin/iptv-api
"https://raw.githubusercontent.com/Guovin/iptv-api/gd/output/result.m3u",

// https://github.com/joevess/IPTV
"https://raw.githubusercontent.com/joevess/IPTV/main/sources/iptv_sources.m3u",
"https://raw.githubusercontent.com/joevess/IPTV/main/sources/home_sources.m3u",
"https://raw.githubusercontent.com/joevess/IPTV/main/iptv.m3u8",
"https://raw.githubusercontent.com/joevess/IPTV/main/home.m3u8",

// https://github.com/zbefine/iptv
"https://raw.githubusercontent.com/zbefine/iptv/main/iptv.m3u",

// https://github.com/YanG-1989/m3u
"https://raw.githubusercontent.com/YanG-1989/m3u/main/Gather.m3u",

// https://github.com/YueChan/Live
"https://raw.githubusercontent.com/YueChan/Live/main/APTV.m3u",
"https://raw.githubusercontent.com/YueChan/Live/main/Global.m3u",
"https://raw.githubusercontent.com/YueChan/Live/main/IPTV.m3u",

"https://freetv.fun/test_channels_new.m3u",

// https://github.com/SPX372928/MyIPTV
"https://raw.githubusercontent.com/SPX372928/MyIPTV/master/%E9%BB%91%E9%BE%99%E6%B1%9FPLTV%E7%A7%BB%E5%8A%A8CDN%E7%89%88.txt",

// https://github.com/vbskycn/iptv
"https://live.zbds.top/tv/iptv6.m3u",
"https://ghp.ci/raw.githubusercontent.com/vbskycn/iptv/refs/heads/master/tv/iptv4.m3u",

// https://github.com/yuanzl77/IPTV
"http://175.178.251.183:6689/live.m3u",

// https://github.com/BurningC4/Chinese-IPTV
"https://raw.githubusercontent.com/BurningC4/Chinese-IPTV/master/TV-IPV4.m3u",

// https://github.com/Moexin/IPTV
"https://raw.githubusercontent.com/Moexin/IPTV/Files/CCTV.m3u",
"https://raw.githubusercontent.com/Moexin/IPTV/Files/CNTV.m3u",
"https://raw.githubusercontent.com/Moexin/IPTV/Files/IPTV.m3u",
).map {
Source(
uri = it
)
}, object : TypeToken<List<Source>>() {}.type
) ?: ""

private lateinit var sp: SharedPreferences

Expand Down Expand Up @@ -181,10 +234,6 @@ object SP {
set(value) = sp.edit().putInt(KEY_LOG_TIMES, value).apply()

var sources: String?
get() = sp.getString(KEY_SOURCES, "")
get() = sp.getString(KEY_SOURCES, DEFAULT_SOURCES)
set(value) = sp.edit().putString(KEY_SOURCES, value).apply()

var sourceId: String?
get() = sp.getString(KEY_SOURCE_ID, "")
set(value) = sp.edit().putString(KEY_SOURCE_ID, value).apply()
}
8 changes: 5 additions & 3 deletions app/src/main/java/com/lizongying/mytv0/SettingFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,8 @@ class SettingFragment : Fragment() {
viewModel.groupModel.setPosition(SP.DEFAULT_POSITION_GROUP)
viewModel.groupModel.setPositionPlaying(SP.DEFAULT_POSITION_GROUP)

SP.sources = SP.DEFAULT_SOURCES

SP.config = SP.DEFAULT_CONFIG_URL
Log.i(TAG, "config url: ${SP.config}")
context.deleteFile(CACHE_FILE_NAME)
Expand Down Expand Up @@ -351,7 +353,7 @@ class SettingFragment : Fragment() {
if (uri.scheme == "file") {
requestReadPermissions()
} else {
viewModel.parseUri(uri)
viewModel.importFromUri(uri)
}
} else {
R.string.invalid_config_address.showToast()
Expand Down Expand Up @@ -437,7 +439,7 @@ class SettingFragment : Fragment() {
PERMISSIONS_REQUEST_CODE
)
} else {
viewModel.parseUri(uri)
viewModel.importFromUri(uri)
}
}

Expand All @@ -449,7 +451,7 @@ class SettingFragment : Fragment() {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == PERMISSION_READ_EXTERNAL_STORAGE_REQUEST_CODE) {
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
viewModel.parseUri(uri)
viewModel.importFromUri(uri)
} else {
R.string.authorization_failed.showToast()
}
Expand Down
Loading

0 comments on commit a056ddb

Please sign in to comment.