Compare commits

...
21 Commits
Author SHA1 Message Date
Li ZongYing 526a7c5c40 fix ysp 2024-02-07 02:57:36 +08:00
Li ZongYing 960f890ddd readme 2024-02-06 23:03:36 +08:00
李宗英 0a7837c577 Merge pull request #223 from LeGend-wLw/main
完成服务器有关所有功能,并优化相关代码
2024-02-06 15:59:12 +08:00
LeGend-wLw d8342e8c6a Add:从淘宝服务器获取当前时间 2024-02-05 14:25:18 +08:00
LeGend-wLw 6387f48409 Add:淘宝服务器返回时间戳格式 2024-02-05 14:24:46 +08:00
LeGend-wLw 807300918c Fix:网络请求超时时间修改为500ms 2024-02-05 14:15:52 +08:00
LeGend-wLw 36eacb7a57 Fix:添加1s超时 2024-02-05 14:14:37 +08:00
LeGend-wLw b4abe0ccc6 Merge remote-tracking branch 'origin/main' 2024-02-05 13:24:25 +08:00
LeGend-wLw fd1dc8d3c7 Fix:更改服务器地址为Github地址 2024-02-05 13:21:44 +08:00
LeGend-wLw ea56bf1b80 Merge branch 'main' into main 2024-02-05 13:10:07 +08:00
LeGend-wLw a746fe6c65 Add:添加log信息 2024-02-04 23:58:11 +08:00
LeGend-wLw 594d5e3499 Merge branch 'main' of https://github.com/LeGend-wLw/my-tv 2024-02-04 23:52:44 +08:00
LeGend-wLw 6061187bc8 Merge branch 'lizongying:main' into main 2024-02-04 23:52:04 +08:00
LeGend-wLw cd8b37e63c Fix:优化从服务器获取逻辑 2024-02-04 23:49:19 +08:00
LeGend-wLw aa3ba1d41f Fix:移除ChannelUtils有关代码 2024-02-04 23:48:58 +08:00
LeGend-wLw c778ac07ba Add:添加本地文件版本,服务器版本地址 2024-02-04 23:48:39 +08:00
LeGend-wLw 700c8b4c04 Fix:移除TVList的初始化 2024-02-04 23:48:03 +08:00
LeGend-wLw 825a872a09 Fix:移除TVList的初始化 2024-02-04 23:47:54 +08:00
LeGend-wLw 54b43664ec add:注释 2024-02-04 23:47:10 +08:00
LeGend-wLw a93cfb455c 在应用初始化时初始化TVlist 2024-02-04 23:46:57 +08:00
LeGend-wLw 8535689e95 完成专门的ChannelUtils 2024-02-04 23:41:51 +08:00
13 changed files with 1230 additions and 119 deletions
+1 -1
View File
@@ -112,4 +112,4 @@ adb install my-tv.apk
## 赞赏
![image](./screenshots/appreciate.jpeg)
![image](./screenshots/zfb.jpg)
@@ -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()
}
}
}
@@ -139,7 +139,6 @@ class MainFragment : BrowseSupportFragment() {
val cardPresenter = CardPresenter(viewLifecycleOwner)
var idx: Long = 0
context?.let { TVList.init(it) }
for ((k, v) in TVList.list) {
val listRowAdapter = ArrayObjectAdapter(cardPresenter)
for ((idx2, v1) in v.withIndex()) {
@@ -41,7 +41,7 @@ class Request {
private val regex = Regex("""des_key = "([^"]+).+var des_iv = "([^"]+)""")
private val input =
"""{"mver":"1","subver":"1.2","host":"www.yangshipin.cn/#/tv/home?pid=","referer":"","canvas":"YSPANGLE(Apple,AppleM1Pro,OpenGL4.1)"}""".toByteArray()
"""{"mver":"1","subver":"1.2","host":"www.yangshipin.cn/#/tv/home?pid=","referer":"","canvas":"YSPANGLE(Apple,ANGLEMetalRenderer:AppleM1Pro,UnspecifiedVersion)"}""".toByteArray()
private var mapping = mapOf(
"CCTV4K" to "CCTV4K 超高清",
@@ -97,8 +97,6 @@ class Request {
fun initYSP(context: Context) {
ysp = YSP(context)
//TODO 不确定在哪里初始化
TVList.init(context)
}
var call: Call<LiveInfo>? = null
@@ -120,6 +118,7 @@ class Request {
override fun onResponse(call: Call<LiveInfo>, response: Response<LiveInfo>) {
if (response.isSuccessful) {
val liveInfo = response.body()
if (liveInfo?.data?.playurl != null) {
val chanll = liveInfo.data.chanll
val decodedBytes = Base64.decode(
@@ -135,8 +134,8 @@ class Request {
val url = liveInfo.data.playurl + "&revoi=" + encryptTripleDES(
keyBytes + byteArrayOf(0, 0, 0, 0, 0, 0, 0, 0),
ivBytes
).uppercase()
// Log.d(TAG, "$title url $url")
).uppercase() + liveInfo.data.extended_param
Log.d(TAG, "$title url $url")
tvModel.addVideoUrl(url)
tvModel.allReady()
tvModel.retryTimes = 0
@@ -356,29 +355,21 @@ class Request {
) {
if (response.isSuccessful) {
val body = response.body()
if (body?.data?.feedModuleListCount == 1) {
for (item in body.data?.feedModuleListList!![0]?.dataTvChannelListList!!) {
if (item.isVip && !item.isLimitedFree) {
continue
}
Log.i(
Log.d(
TAG,
"${item.channelName},${item.pid},${item.streamId}"
)
var channelType = "央视频道"
if (item?.channelType === "weishi") {
channelType = "地方频道"
}
if (!mapping.containsKey(item.channelName)) {
continue
}
val tv =
TVList.list[channelType]?.find { it.title == mapping[item.channelName] }
if (tv != null) {
tv.logo = item.tvLogo
tv.pid = item.pid
tv.sid = item.streamId
for ((_, v) in TVList.list) {
for (v2 in v) {
Log.i(TAG, "$v2")
if (v2.title == item.channelName || v2.alias == item.channelName) {
v2.pid = item.pid
v2.sid = item.streamId
}
}
}
}
}
+4 -3
View File
@@ -5,6 +5,7 @@ import java.io.Serializable
data class TV(
var id: Int = 0,
var title: String,
var alias: String = "",
var videoUrl: List<String>,
var videoIndex: Int = 0,
var channel: String = "",
@@ -12,10 +13,10 @@ data class TV(
var pid: String = "",
var sid: String = "",
var programId: String = "",
var needToken:Boolean=false,
var mustToken:Boolean=false,
var needToken: Boolean = false,
var mustToken: Boolean = false,
) : Serializable {
) : Serializable {
override fun toString(): String {
return "TV{" +
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,5 @@
package com.lizongying.mytv
class UpdateManager {
}
+39 -49
View File
@@ -1,12 +1,18 @@
package com.lizongying.mytv
import android.content.Context
import android.content.res.Resources
import android.os.SystemClock
import android.util.Log
import android.util.TypedValue
import java.io.File
import com.google.gson.Gson
import com.lizongying.mytv.api.TimeResponse
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.text.SimpleDateFormat
import java.util.*
import java.io.IOException
import java.util.Date
import java.util.Locale
object Utils {
fun getDateFormat(format: String): String {
@@ -17,6 +23,35 @@ object Utils {
return Date().time / 1000
}
init {
CoroutineScope(Dispatchers.Default).launch {
updateTimestampFromServer()
}
}
/**
* 从服务器获取时间戳
* @return Long 时间戳
*/
private suspend fun updateTimestampFromServer() {
val currentTimeMillis = 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 string = response.body()?.string()
Gson().fromJson(string, TimeResponse::class.java).data.t.toLong()
}
}
SystemClock.setCurrentTimeMillis(currentTimeMillis)
}
fun dpToPx(dp: Float): Int {
return TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, dp, Resources.getSystem().displayMetrics
@@ -28,49 +63,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)
}
}
@@ -9,3 +9,11 @@ data class Info(
data class InfoData(
val token: String,
)
data class TimeResponse(
val data: Time
) {
data class Time(
val t: String
)
}
@@ -13,6 +13,7 @@ data class LiveInfo(
)
data class LiveInfoData(
val extended_param: String,
val chanll: String,
val playurl: String,
val errinfo: String,
@@ -127,7 +127,6 @@ class YSP(var context: Context) {
private fun getSignature(): String {
val e =
"adjust=${adjust}&appVer=${appVer}&app_version=$appVersion&cKey=$cKey&channel=$channel&cmd=$cmd&cnlid=$cnlid&defn=${defn}&devid=${devid}&dtype=${dtype}&encryptVer=${encryptVer}&guid=${guid}&livepid=${livepid}&otype=${otype}&platform=${platform}&rand_str=${randStr}&sphttps=${sphttps}&stream=${stream}".toByteArray()
val hashedData = encryptor?.hash(e) ?: return ""
return hashedData.let { it -> it.joinToString("") { "%02x".format(it) } }
}
+3 -1
View File
@@ -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>
Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB