diff --git a/app/src/main/java/com/lizongying/mytv/ChannelUtils.kt b/app/src/main/java/com/lizongying/mytv/ChannelUtils.kt new file mode 100644 index 0000000..2767416 --- /dev/null +++ b/app/src/main/java/com/lizongying/mytv/ChannelUtils.kt @@ -0,0 +1,157 @@ +package com.lizongying.mytv + +import android.content.Context +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import java.io.File + +/** + *@author LeGend + *@date 2024/2/4 22:42 + */ +object ChannelUtils { + /** + * 获取服务器channel版本 + * + * @param context Context + * + * @return 服务器channel版本 + */ + suspend fun getServerVersion(context: Context): Int { + return withContext(Dispatchers.IO) { + val client = okhttp3.OkHttpClient.Builder().connectTimeout(500, java.util.concurrent.TimeUnit.MILLISECONDS) + .readTimeout(1, java.util.concurrent.TimeUnit.SECONDS).build() + client.newCall(okhttp3.Request.Builder().url(getServerVersionUrl(context)).build()).execute() + .use { response -> + if (!response.isSuccessful) throw java.io.IOException("Unexpected code $response") + val body = response.body() + body?.string()?.toInt() ?: 0 + } + } + } + + /** + * 获取服务器channel + * + * @param url String 服务器地址 + * + * @return Array 服务器channel + * + * @throws java.io.IOException 网络请求失败 + */ + suspend fun getServerChannel(url: String): List { + val result = withContext(Dispatchers.IO) { + val client = okhttp3.OkHttpClient.Builder().connectTimeout(500, java.util.concurrent.TimeUnit.MILLISECONDS) + .readTimeout(1, java.util.concurrent.TimeUnit.SECONDS).build() + val request = okhttp3.Request.Builder().url(url).build() + client.newCall(request).execute().use { response -> + if (!response.isSuccessful) throw java.io.IOException("Unexpected code $response") + val body = response.body() + body?.string() ?: "" + } + } + return withContext(Dispatchers.Default) { + val type = object : com.google.gson.reflect.TypeToken>() {}.type + com.google.gson.Gson().fromJson(result, type) + } + } + + /** + * 获取服务器地址 + * + * @param context Context + * + * @return 服务器地址 + */ + fun getServerUrl(context: Context): String { + return context.resources.getString(R.string.server_url) + } + + /** + * 获取serverVersion的URL + * + * @param context Context + * + * @return serverVersionURL 服务器版本地址 + */ + suspend fun getServerVersionUrl(context: Context): String { + return withContext(Dispatchers.IO) { + context.resources.getString(R.string.server_version_url) + } + } + + + /** + * 获取本地channel版本 + * + * @param context Context + * + * @return 本地channel + */ + suspend fun getLocalVersion(context: Context): Int { + return withContext(Dispatchers.IO) { + val file = File(getAppDirectory(context), "channels") + //检查本地是否已经有保存的channels.json,若无保存的Channel.json则从读取assert中文件 + val savedVersion = + context.getSharedPreferences("saved_version", Context.MODE_PRIVATE).getInt("version", Integer.MIN_VALUE) + if (!file.exists() || savedVersion == Integer.MIN_VALUE) { + context.resources.getInteger(R.integer.local_channel_version) + } else { + savedVersion + } + } + } + + /** + * 获取本地可读取的目录 + * @param context Context + * + * @return 可读取的目录 + */ + private fun getAppDirectory(context: Context): File { + return context.filesDir + } + + /** + * 获取本地channel + * + * @param context Context + * + * @return Array 本地channel + */ + suspend fun getLocalChannel(context: Context): List { + val str = withContext(Dispatchers.IO) { + if (File(getAppDirectory(context), "channels").exists()) { + File(getAppDirectory(context), "channels").readText() + } else { + context.resources.openRawResource(R.raw.channels).bufferedReader().use { it.readText() } + } + } + return withContext(Dispatchers.Default) { + val type = object : com.google.gson.reflect.TypeToken>() {}.type + com.google.gson.Gson().fromJson(str, type) + } + } + + /** + * 更新channels.json + * + * @param context Context + * + * @return 无 + * + * @throws java.io.IOException 写入失败 + */ + suspend fun updateLocalChannel(context: Context, version: Int, channels: List) { + withContext(Dispatchers.IO) { + val file = File(getAppDirectory(context), "channels") + if (!file.exists()) { + file.createNewFile() + } + file.writeText(com.google.gson.Gson().toJson(channels)) + context.getSharedPreferences("saved_version", Context.MODE_PRIVATE).edit().putInt( + "version", version + ).apply() + } + } +} diff --git a/app/src/main/java/com/lizongying/mytv/MainActivity.kt b/app/src/main/java/com/lizongying/mytv/MainActivity.kt index f9ed1e4..d240129 100644 --- a/app/src/main/java/com/lizongying/mytv/MainActivity.kt +++ b/app/src/main/java/com/lizongying/mytv/MainActivity.kt @@ -46,6 +46,7 @@ class MainActivity : FragmentActivity() { override fun onCreate(savedInstanceState: Bundle?) { Log.i(TAG, "onCreate") + TVList.init(this) super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) diff --git a/app/src/main/java/com/lizongying/mytv/MainFragment.kt b/app/src/main/java/com/lizongying/mytv/MainFragment.kt index 4122393..208fa69 100644 --- a/app/src/main/java/com/lizongying/mytv/MainFragment.kt +++ b/app/src/main/java/com/lizongying/mytv/MainFragment.kt @@ -139,8 +139,7 @@ class MainFragment : BrowseSupportFragment() { val cardPresenter = CardPresenter(viewLifecycleOwner) var idx: Long = 0 - context?.let { TVList.init(it) } - for ((k, v) in TVList.list) { + for ((k, v) in TVList.list!!) { val listRowAdapter = ArrayObjectAdapter(cardPresenter) for ((idx2, v1) in v.withIndex()) { val tvViewModel = TVViewModel(v1) diff --git a/app/src/main/java/com/lizongying/mytv/Request.kt b/app/src/main/java/com/lizongying/mytv/Request.kt index ef52fb5..acd62e8 100644 --- a/app/src/main/java/com/lizongying/mytv/Request.kt +++ b/app/src/main/java/com/lizongying/mytv/Request.kt @@ -97,8 +97,6 @@ class Request { fun initYSP(context: Context) { ysp = YSP(context) - //TODO 不确定在哪里初始化 - TVList.init(context) } var call: Call? = null @@ -374,7 +372,7 @@ class Request { continue } val tv = - TVList.list[channelType]?.find { it.title == mapping[item.channelName] } + TVList.list?.get(channelType)?.find { it.title == mapping[item.channelName] } if (tv != null) { tv.logo = item.tvLogo tv.pid = item.pid diff --git a/app/src/main/java/com/lizongying/mytv/TVList.kt b/app/src/main/java/com/lizongying/mytv/TVList.kt index cdbb295..39e131d 100644 --- a/app/src/main/java/com/lizongying/mytv/TVList.kt +++ b/app/src/main/java/com/lizongying/mytv/TVList.kt @@ -1,54 +1,81 @@ package com.lizongying.mytv import android.content.Context -import com.google.gson.Gson -import com.google.gson.reflect.TypeToken -import java.io.File +import android.util.Log +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import java.io.IOException +import kotlin.math.log object TVList { - lateinit var list: Map> - private val channels = "channels.json" - - fun init(context: Context) { - if (::list.isInitialized) { - return - } - synchronized(this) { - if (::list.isInitialized) { - return + @Volatile + var list: Map>? = null + get():Map>? { + //等待初始化完成 + while (this.list === null) { + Thread.sleep(10) } - list = setupTV(context) + return this.list } - } - private fun setupTV(context: Context): Map> { - val map: MutableMap> = mutableMapOf() - val appDirectory = Utils.getAppDirectory(context) - - //检查当前目录下是否存在channels.json - val file = File(appDirectory, channels) - if (!file.exists()) { - //不存在则从assets中拷贝 - file.createNewFile() - context.resources.openRawResource(R.raw.channels).use { input -> - file.outputStream().use { output -> - input.copyTo(output) + /** + * 初始化 + * + * @param context Context + */ + fun init(context: Context) { + CoroutineScope(Dispatchers.Default).launch { + //获取本地版本号 + val localVersion = ChannelUtils.getLocalVersion(context) + //获取服务器版本号 + val serverVersion = try { + ChannelUtils.getServerVersion(context) + } catch (e: IOException) { + Log.e("TVList", "无法从服务器获取版本信息", e) + Integer.MIN_VALUE + } + //频道列表 + val channelTVMap: MutableMap> = mutableMapOf() + //是否从服务器更新 + var updateFromServer = false + //获取频道列表 + val tvList: List = if (localVersion < serverVersion) { + //获取服务器地址 + val url = ChannelUtils.getServerUrl(context) + //是否从服务器更新 + updateFromServer = true + Log.i("TVList", "从服务器获取频道信息") + try { + ChannelUtils.getServerChannel(url) + } catch (e: IOException) { + Log.e("TVList", "无法从服务器获取频道信息", e) + updateFromServer = false + ChannelUtils.getLocalChannel(context) + } + } else { + Log.i("TVList", "从本地获取频道信息") + //获取本地频道 + ChannelUtils.getLocalChannel(context) + } + //按频道分类 + for (tv in tvList) { + val key = tv.channel + if (channelTVMap.containsKey(key)) { + val list = channelTVMap[key]!! + list.add(tv) + channelTVMap[key] = list + } else { + channelTVMap[key] = mutableListOf(tv) } } - } - - //读取channels.json,并转换为Map> - val json = file.readText() - //防止类型擦除 - val type = object : TypeToken>() {}.type - Gson().fromJson>(json, type)?.forEach { - if (map.containsKey(it.channel)) { - map[it.channel]?.add(it) - } else { - map[it.channel] = mutableListOf(it) + //保存频道列表 + list = channelTVMap + //保存版本号 + if (updateFromServer) { + ChannelUtils.updateLocalChannel(context, serverVersion, tvList) } } - return map } } \ No newline at end of file diff --git a/app/src/main/java/com/lizongying/mytv/Utils.kt b/app/src/main/java/com/lizongying/mytv/Utils.kt index 4eeb39b..206a407 100644 --- a/app/src/main/java/com/lizongying/mytv/Utils.kt +++ b/app/src/main/java/com/lizongying/mytv/Utils.kt @@ -1,20 +1,41 @@ package com.lizongying.mytv -import android.content.Context import android.content.res.Resources import android.util.TypedValue -import java.io.File +import com.google.gson.Gson +import com.lizongying.mytv.api.TimeResponse +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext import java.text.SimpleDateFormat import java.util.* -import java.io.IOException object Utils { fun getDateFormat(format: String): String { return SimpleDateFormat(format, Locale.CHINA).format(Date()) } - fun getDateTimestamp(): Long { - return Date().time / 1000 + suspend fun getDateTimestamp(): Long { + return getTimestampFromServer() / 1000 + } + + /** + * 从服务器获取时间戳 + * @return Long 时间戳 + */ + private suspend fun getTimestampFromServer(): Long { + return withContext(Dispatchers.IO) { + val client = okhttp3.OkHttpClient.Builder().connectTimeout(500, java.util.concurrent.TimeUnit.MILLISECONDS) + .readTimeout(1, java.util.concurrent.TimeUnit.SECONDS).build() + client.newCall( + okhttp3.Request.Builder().url("https://api.m.taobao.com/rest/api3.do?api=mtop.common.getTimestamp") + .build() + ).execute().use { response -> + if (!response.isSuccessful) throw java.io.IOException("Unexpected code $response") + val body = response.body() + val string = body?.toString() + Gson().fromJson(string, TimeResponse::class.java).data.t.toLong() + } + } } fun dpToPx(dp: Float): Int { @@ -28,49 +49,4 @@ object Utils { TypedValue.COMPLEX_UNIT_DIP, dp.toFloat(), Resources.getSystem().displayMetrics ).toInt() } - - /** - * 获取可读写的目录 - * - * @param context 应用环境信息 - * - * @return 可读写的目录 - * - */ - fun getAppDirectory(context: Context): File { - return context.filesDir - } - - /** - * 更新channels.json - * - * @param context 应用环境信息 - * - * @return 无 - * - * @throws IOException 网络请求失败 - */ - fun updateChannel(context: Context) { - val client = okhttp3.OkHttpClient() - val request = okhttp3.Request.Builder().url(getServerUrl(context)).build() - client.newCall(request).execute().use { response -> - if (!response.isSuccessful) throw IOException("Unexpected code $response") - val body = response.body() - //覆盖channels.json - val file = File(getAppDirectory(context), "channels.json") - if (!file.exists()) { - file.createNewFile() - } - file.writeText(body!!.string()) - } - } - - /** - * 从res/values/server.xml获取服务器地址 - * @param context 应用环境信息 - * @return 服务器地址 - */ - private fun getServerUrl(context: Context): String { - return context.resources.getString(R.string.server_url) - } } \ No newline at end of file diff --git a/app/src/main/java/com/lizongying/mytv/api/Info.kt b/app/src/main/java/com/lizongying/mytv/api/Info.kt index 7708ac8..867acaa 100644 --- a/app/src/main/java/com/lizongying/mytv/api/Info.kt +++ b/app/src/main/java/com/lizongying/mytv/api/Info.kt @@ -9,3 +9,11 @@ data class Info( data class InfoData( val token: String, ) + +data class TimeResponse( + val api: String, val v: String, val ret: List, val data: Time +) { + data class Time( + val t: String + ) +} \ No newline at end of file diff --git a/app/src/main/res/values/server.xml b/app/src/main/res/values/server.xml index 394661b..16407dc 100644 --- a/app/src/main/res/values/server.xml +++ b/app/src/main/res/values/server.xml @@ -1,4 +1,6 @@ - http://localhost:8080 + 1 + https://raw.githubusercontent.com/LeGend-wLw/my-tv-json-utils/main/version.txt + https://github.com/LeGend-wLw/my-tv-json-utils/raw/main/channels.json \ No newline at end of file