mirror of https://github.com/lizongying/my-tv.git
commit
0a7837c577
@ -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<TV> 服务器channel |
||||
* |
||||
* @throws java.io.IOException 网络请求失败 |
||||
*/ |
||||
suspend fun getServerChannel(url: String): List<TV> { |
||||
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<List<TV>>() {}.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<TV> 本地channel |
||||
*/ |
||||
suspend fun getLocalChannel(context: Context): List<TV> { |
||||
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<List<TV>>() {}.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<TV>) { |
||||
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() |
||||
} |
||||
} |
||||
} |
||||
@ -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<String, List<TV>> |
||||
private val channels = "channels.json" |
||||
|
||||
fun init(context: Context) { |
||||
if (::list.isInitialized) { |
||||
return |
||||
} |
||||
synchronized(this) { |
||||
if (::list.isInitialized) { |
||||
return |
||||
@Volatile |
||||
var list: Map<String, List<TV>>? = null |
||||
get():Map<String, List<TV>>? { |
||||
//等待初始化完成 |
||||
while (this.list === null) { |
||||
Thread.sleep(10) |
||||
} |
||||
list = setupTV(context) |
||||
return this.list |
||||
} |
||||
} |
||||
|
||||
|
||||
private fun setupTV(context: Context): Map<String, List<TV>> { |
||||
val map: MutableMap<String, MutableList<TV>> = 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 |
||||
} |
||||
} |
||||
|
||||
//读取channels.json,并转换为Map<String,LIst<TV>> |
||||
val json = file.readText() |
||||
//防止类型擦除 |
||||
val type = object : TypeToken<Array<TV>>() {}.type |
||||
Gson().fromJson<Array<TV>>(json, type)?.forEach { |
||||
if (map.containsKey(it.channel)) { |
||||
map[it.channel]?.add(it) |
||||
//频道列表 |
||||
val channelTVMap: MutableMap<String, MutableList<TV>> = mutableMapOf() |
||||
//是否从服务器更新 |
||||
var updateFromServer = false |
||||
//获取频道列表 |
||||
val tvList: List<TV> = 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 { |
||||
map[it.channel] = mutableListOf(it) |
||||
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) |
||||
} |
||||
} |
||||
//保存频道列表 |
||||
list = channelTVMap |
||||
//保存版本号 |
||||
if (updateFromServer) { |
||||
ChannelUtils.updateLocalChannel(context, serverVersion, tvList) |
||||
} |
||||
} |
||||
return map |
||||
} |
||||
} |
||||
@ -1,4 +1,6 @@ |
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<resources> |
||||
<string name="server_url">http://localhost:8080</string> |
||||
<integer name="local_channel_version">1</integer> |
||||
<string name="server_version_url">https://raw.githubusercontent.com/LeGend-wLw/my-tv-json-utils/main/version.txt</string> |
||||
<string name="server_url">https://github.com/LeGend-wLw/my-tv-json-utils/raw/main/channels.json</string> |
||||
</resources> |
||||
Loading…
Reference in new issue