Skip to content

Commit

Permalink
update
Browse files Browse the repository at this point in the history
  • Loading branch information
lsongdev committed Sep 5, 2024
1 parent 8486ea4 commit a48acc6
Show file tree
Hide file tree
Showing 11 changed files with 229 additions and 144 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<h1 align="center">
<img src="./icon.png" alt="DuckTV Icon" width="200">
<img src="./app/src/main/res/drawable-xhdpi/ic_launcher.png" alt="DuckTV Icon" width="200">
<br>DuckTV<br>
</h1>

Expand Down
6 changes: 3 additions & 3 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ android {

defaultConfig {
applicationId = "me.lsong.mytv"
minSdk = 21
minSdk = 31
targetSdk = 34
versionCode = 2
versionName = "1.0.0"
versionCode = 4
versionName = "1.0.$versionCode"

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
Expand Down
4 changes: 2 additions & 2 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@
<application
android:name=".MyTVApplication"
android:allowBackup="true"
android:banner="@drawable/ic_banner"
android:banner="@drawable/tv_banner"
android:icon="@drawable/ic_launcher"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:networkSecurityConfig="@xml/network_security_config"
android:supportsRtl="true"
Expand Down
159 changes: 82 additions & 77 deletions app/src/main/java/me/lsong/mytv/epg/EpgRepository.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,97 +8,102 @@ import okhttp3.OkHttpClient
import okhttp3.Request
import org.xmlpull.v1.XmlPullParser
import me.lsong.mytv.epg.fetcher.EpgFetcher
import java.io.BufferedReader
import java.io.ByteArrayInputStream
import java.io.InputStreamReader
import java.io.StringReader
import java.text.SimpleDateFormat
import java.util.Locale
import java.util.zip.GZIPInputStream

/**
* 节目单获取
*/
class EpgRepository {

/**
* 解析节目单xml
*/
private fun parseFromXml(xmlString: String): EpgList {
val parser: XmlPullParser = Xml.newPullParser()
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false)
parser.setInput(StringReader(xmlString))
val epgMap = mutableMapOf<String, EpgChannel>()
var eventType = parser.eventType
while (eventType != XmlPullParser.END_DOCUMENT) {
when (eventType) {
XmlPullParser.START_TAG -> {
if (parser.name == "channel") {
val channelId = parser.getAttributeValue(null, "id")
parser.nextTag()
val channelDisplayName = parser.nextText()
val channel = EpgChannel(
id = channelId,
title = channelDisplayName,
)
// Log.d("epg", "${channel.id}, ${channel.title}")
epgMap[channelId] = channel
} else if (parser.name == "programme") {
val channelId = parser.getAttributeValue(null, "channel")
val startTime = parser.getAttributeValue(null, "start")
val stopTime = parser.getAttributeValue(null, "stop")
parser.nextTag()
val title = parser.nextText()
fun parseTime(time: String): Long {
if (time.length < 14) return 0
return SimpleDateFormat("yyyyMMddHHmmss Z", Locale.getDefault()).parse(time)?.time ?: 0
}
val programme = EpgProgramme(
channelId = channelId,
startAt = parseTime(startTime),
endAt = parseTime(stopTime),
title = title,
)
if (epgMap.containsKey(channelId)) {
// Log.d("epg", "${programme.channelId}, ${programme.title}")
epgMap[channelId] = epgMap[channelId]!!.copy(
programmes = epgMap[channelId]!!.programmes + listOf(programme)
)
}
}
}
}
eventType = parser.next()
suspend fun getEpgList(url: String): EpgList = withContext(Dispatchers.Default) {
try {
return@withContext parseEpgXml(request(url))
} catch (ex: Exception) {
Log.e("epg", "获取节目单失败", ex)
throw Exception(ex)
}

Log.i("epg","解析节目单完成,共${epgMap.size}个频道")
return EpgList(epgMap.values.toList())
}
}

private suspend fun fetchXml(url: String): String = withContext(Dispatchers.IO) {
Log.d("epg", "获取远程节目单xml: $url")

val client = OkHttpClient()
val request = Request.Builder().url(url).build()
fun request(url: String): String{
val client = OkHttpClient()
val request = Request.Builder().url(url).build()
client.newCall(request).execute().use { response ->
if (!response.isSuccessful) {
throw Exception("request failed: $response.code")
}
val contentType = response.header("content-type")
if (contentType?.startsWith("text/")!!) {
return response.body!!.string()
}
val gzData = response.body!!.bytes()
val stringBuilder = StringBuilder()
val gzipInputStream = GZIPInputStream(ByteArrayInputStream(gzData));
val reader = BufferedReader(InputStreamReader(gzipInputStream));
var line: String?
while (reader.readLine().also { line = it } != null) {
stringBuilder.append(line).append("\n")
}
response.close()
return stringBuilder.toString()
}
}

try {
with(client.newCall(request).execute()) {
if (!isSuccessful) {
throw Exception("获取远程节目单xml失败: $code")
/**
* 解析节目单xml
*/
private fun parseEpgXml(xmlString: String): EpgList {
val parser: XmlPullParser = Xml.newPullParser()
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false)
parser.setInput(StringReader(xmlString))
val epgMap = mutableMapOf<String, EpgChannel>()
var eventType = parser.eventType
while (eventType != XmlPullParser.END_DOCUMENT) {
when (eventType) {
XmlPullParser.START_TAG -> {
if (parser.name == "channel") {
val channelId = parser.getAttributeValue(null, "id")
parser.nextTag()
val channelDisplayName = parser.nextText()
val channel = EpgChannel(
id = channelId,
title = channelDisplayName,
)
// Log.d("epg", "${channel.id}, ${channel.title}")
epgMap[channelId] = channel
} else if (parser.name == "programme") {
val channelId = parser.getAttributeValue(null, "channel")
val startTime = parser.getAttributeValue(null, "start")
val stopTime = parser.getAttributeValue(null, "stop")
parser.nextTag()
val title = parser.nextText()
fun parseTime(time: String): Long {
if (time.length < 14) return 0
return SimpleDateFormat("yyyyMMddHHmmss Z", Locale.getDefault()).parse(time)?.time ?: 0
}
val programme = EpgProgramme(
channelId = channelId,
startAt = parseTime(startTime),
endAt = parseTime(stopTime),
title = title,
)
if (epgMap.containsKey(channelId)) {
// Log.d("epg", "${programme.channelId}, ${programme.title}")
epgMap[channelId] = epgMap[channelId]!!.copy(
programmes = epgMap[channelId]!!.programmes + listOf(programme)
)
}
}

val fetcher = EpgFetcher.instances.first { it.isSupport(url) }

return@with fetcher.fetch(this)
}
} catch (ex: Exception) {
throw Exception("获取远程节目单xml失败,请检查网络连接", ex)
}
eventType = parser.next()
}

suspend fun getEpgList(xmlUrl: String): EpgList = withContext(Dispatchers.Default) {
try {
val xmlString = fetchXml(xmlUrl)
return@withContext parseFromXml(xmlString)
} catch (ex: Exception) {
Log.e("epg", "获取节目单失败", ex)
throw Exception(ex)
}
}
}
Log.i("epg","解析节目单完成,共${epgMap.size}个频道")
return EpgList(epgMap.values.toList())
}
Loading

0 comments on commit a48acc6

Please sign in to comment.