Skip to content

Commit

Permalink
support m3u headers
Browse files Browse the repository at this point in the history
  • Loading branch information
lizongying committed Feb 9, 2025
1 parent 769b2e7 commit 1a45f38
Show file tree
Hide file tree
Showing 12 changed files with 197 additions and 140 deletions.
9 changes: 9 additions & 0 deletions HISTORY.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
## 更新日誌

### v1.3.9.0

* 支持socks代理
* 支持m3u設置headers

### v1.3.8.17-kitkat

* 優化LOGO緩存

### v1.3.8.17

* 優化LOGO緩存
Expand Down
91 changes: 52 additions & 39 deletions app/src/main/java/com/lizongying/mytv0/MainViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class MainViewModel : ViewModel() {
private var timeFormat = if (SP.displaySeconds) "HH:mm:ss" else "HH:mm"

private lateinit var appDirectory: File
var listModel: List<TVModel> = listOf()
var listModel: List<TVModel> = emptyList()
val groupModel = TVGroupModel()
private var cacheFile: File? = null
private var cacheChannels = ""
Expand Down Expand Up @@ -396,10 +396,9 @@ class MainViewModel : ViewModel() {
'[' -> {
try {
list = gson.fromJson(string, typeTvList)
Log.i(TAG, "导入频道 ${list.size}")
Log.i(TAG, "导入频道 ${list.size} $list")
} catch (e: Exception) {
Log.i(TAG, "parse error $string")
Log.i(TAG, e.message, e)
Log.e(TAG, "str2Channels", e)
return false
}
}
Expand All @@ -413,41 +412,55 @@ class MainViewModel : ViewModel() {

val l = mutableListOf<TV>()
val tvMap = mutableMapOf<String, List<TV>>()
for ((index, line) in lines.withIndex()) {

var tv = TV()
for (line in lines) {
val trimmedLine = line.trim()
if (trimmedLine.isEmpty()) {
continue
}
if (trimmedLine.startsWith("#EXTM3U")) {
epgUrl = epgRegex.find(trimmedLine)?.groupValues?.get(1)?.trim()
} else if (trimmedLine.startsWith("#EXTINF")) {
Log.i(TAG, "TV $tv")
val key = tv.group + tv.name
if (key.isNotEmpty()) {
tvMap[key] =
if (!tvMap.containsKey(key)) listOf(tv) else tvMap[key]!! + tv
}
tv = TV()
val info = trimmedLine.split(",")
val title = info.last().trim()
tv.title = info.last().trim()
var name = nameRegex.find(info.first())?.groupValues?.get(1)?.trim()
name = name ?: title
var group = groupRegex.find(info.first())?.groupValues?.get(1)?.trim()
group = group ?: ""
val logo = logRegex.find(info.first())?.groupValues?.get(1)?.trim()
val uris =
if (index + 1 < lines.size) listOf(lines[index + 1].trim()) else emptyList()
val tv = TV(
-1,
name,
title,
"",
logo ?: "",
"",
uris,
0,
mapOf(),
group,
SourceType.UNKNOWN,
listOf(),
)

if (!tvMap.containsKey(group + name)) {
tvMap[group + name] = listOf()
tv.name = if (name.isNullOrEmpty()) tv.title else name
tv.logo = logRegex.find(info.first())?.groupValues?.get(1)?.trim() ?: ""
tv.group = groupRegex.find(info.first())?.groupValues?.get(1)?.trim() ?: ""
} else if (trimmedLine.startsWith("#EXTVLCOPT:http-")) {
val keyValue =
trimmedLine.substringAfter("#EXTVLCOPT:http-").split("=", limit = 2)
if (keyValue.size == 2) {
tv.headers = if (tv.headers == null) {
mapOf<String, String>(keyValue[0] to keyValue[1])
} else {
tv.headers!!.toMutableMap().apply {
this[keyValue[0]] = keyValue[1]
}
}
}
} else if (!trimmedLine.startsWith("#")) {
tv.uris = if (tv.uris.isEmpty()) {
listOf(trimmedLine)
} else {
tv.uris.toMutableList().apply {
this.add(trimmedLine)
}
}
tvMap[group + name] = tvMap[group + name]!! + tv
}
}
val key = tv.group + tv.name
if (key.isNotEmpty()) {
tvMap[key] = if (!tvMap.containsKey(key)) listOf(tv) else tvMap[key]!! + tv
}
for ((_, tv) in tvMap) {
val uris = tv.map { t -> t.uris }.flatten()
val t0 = tv[0]
Expand All @@ -460,15 +473,15 @@ class MainViewModel : ViewModel() {
"",
uris,
0,
mapOf(),
t0.headers,
t0.group,
SourceType.UNKNOWN,
listOf(),
emptyList(),
)
l.add(t1)
}
list = l
Log.i(TAG, "导入频道 ${list.size}")
Log.i(TAG, "导入频道 ${list.size} $list")
}

else -> {
Expand All @@ -489,11 +502,11 @@ class MainViewModel : ViewModel() {
val title = arr.first().trim()
val uris = arr.drop(1)

if (!tvMap.containsKey(group + title)) {
tvMap[group + title] = listOf()
tvMap[group + title] = tvMap[group + title]!! + group
val key = group + title
if (!tvMap.containsKey(key)) {
tvMap[key] = listOf(group)
}
tvMap[group + title] = tvMap[group + title]!! + uris
tvMap[key] = tvMap[key]!! + uris
}
}
}
Expand All @@ -509,10 +522,10 @@ class MainViewModel : ViewModel() {
"",
uris,
0,
mapOf(),
emptyMap(),
channelGroup,
SourceType.UNKNOWN,
listOf(),
emptyList(),
)

l.add(tv)
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/java/com/lizongying/mytv0/SimpleServer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -191,8 +191,10 @@ class SimpleServer(private val context: Context, private val viewModel: MainView
if (req.proxy != null) {
SP.proxy = req.proxy
R.string.default_proxy_set_success.showToast()
Log.i(TAG, "set proxy success")
} else {
R.string.default_proxy_set_failure.showToast()
Log.i(TAG, "set proxy failure")
}
}
}
Expand Down
9 changes: 3 additions & 6 deletions app/src/main/java/com/lizongying/mytv0/Utils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -141,18 +141,15 @@ object Utils {
}

fun formatUrl(url: String): String {
// Check if the URL already starts with "http://" or "https://"
if (url.startsWith("http://") || url.startsWith("https://") || url.startsWith("file://")) {
if (url.startsWith("http://") || url.startsWith("https://") || url.startsWith("file://") || url.startsWith("socks://") || url.startsWith("socks5://")) {
return url
}

// Check if the URL starts with "//"
if (url.startsWith("//")) {
return "http://$url"
return "http:$url"
}

// Otherwise, add "http://" to the beginning of the URL
return "http://${url}"
return "http://$url"
}

fun getUrls(url: String): List<String> {
Expand Down
8 changes: 4 additions & 4 deletions app/src/main/java/com/lizongying/mytv0/data/TV.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ data class TV(
var description: String? = null,
var logo: String = "",
var image: String? = null,
var uris: List<String>,
var uris: List<String> = emptyList(),
var videoIndex: Int = 0,
var headers: Map<String, String>? = null,
var group: String = "",
var sourceType: SourceType = SourceType.UNKNOWN,
var child: List<TV>,
var child: List<TV> = emptyList(),
) : Serializable {

override fun toString(): String {
Expand All @@ -25,8 +25,8 @@ data class TV(
", description='" + description + '\'' +
", logo='" + logo + '\'' +
", image='" + image + '\'' +
", uris='" + uris + '\'' +
", headers='" + headers + '\'' +
", uris=" + uris +
", headers=" + headers +
", group='" + group + '\'' +
", sourceType='" + sourceType + '\'' +
'}'
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/java/com/lizongying/mytv0/models/Sources.kt
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class Sources {
val sources: LiveData<List<Source>>
get() = _sources
private val sourcesValue: List<Source>
get() = _sources.value ?: listOf()
get() = _sources.value ?: emptyList()

private val _checked = MutableLiveData<Int>()
val checked: LiveData<Int>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class TVGroupModel : ViewModel() {
val tvGroup: LiveData<List<TVListModel>>
get() = _tvGroup
val tvGroupValue: List<TVListModel>
get() = _tvGroup.value ?: listOf()
get() = _tvGroup.value ?: emptyList()

private val _position = MutableLiveData<Int>()
val position: LiveData<Int>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class TVListModel(private val name: String, private val groupIndex: Int) : ViewM
val tvList: LiveData<List<TVModel>>
get() = _tvList
private val tvListValue: List<TVModel>
get() = _tvList.value ?: listOf()
get() = _tvList.value ?: emptyList()

private val _position = MutableLiveData<Int>()
val position: LiveData<Int>
Expand Down
16 changes: 5 additions & 11 deletions app/src/main/java/com/lizongying/mytv0/models/TVModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,19 @@ import androidx.lifecycle.ViewModel
import androidx.media3.common.MediaItem
import androidx.media3.common.util.UnstableApi
import androidx.media3.datasource.DataSource
import androidx.media3.datasource.DefaultHttpDataSource
import androidx.media3.datasource.okhttp.OkHttpDataSource
import androidx.media3.datasource.rtmp.RtmpDataSource
import androidx.media3.exoplayer.dash.DashMediaSource
import androidx.media3.exoplayer.hls.HlsMediaSource
import androidx.media3.exoplayer.rtsp.RtspMediaSource
import androidx.media3.exoplayer.source.MediaSource
import androidx.media3.exoplayer.source.ProgressiveMediaSource
import com.lizongying.mytv0.IgnoreSSLCertificate
import com.lizongying.mytv0.SP
import com.lizongying.mytv0.data.EPG
import com.lizongying.mytv0.data.Program
import com.lizongying.mytv0.data.SourceType
import com.lizongying.mytv0.data.TV
import com.lizongying.mytv0.requests.HttpClient
import kotlin.math.max
import kotlin.math.min

Expand Down Expand Up @@ -119,15 +119,9 @@ class TVModel(var tv: TV) : ViewModel() {
val path = uri.path ?: return@let null
val scheme = uri.scheme ?: return@let null

// val okHttpDataSource = OkHttpDataSource.Factory(HttpClient.okHttpClient)
// httpDataSource = okHttpDataSource

IgnoreSSLCertificate.ignore()
val defaultHttpDataSource = DefaultHttpDataSource.Factory()
defaultHttpDataSource.setKeepPostFor302Redirects(true)
defaultHttpDataSource.setAllowCrossProtocolRedirects(true)
val okHttpDataSource = OkHttpDataSource.Factory(HttpClient.okHttpClient)
tv.headers?.let { i ->
defaultHttpDataSource.setDefaultRequestProperties(i)
okHttpDataSource.setDefaultRequestProperties(i)
i.forEach { (key, value) ->
if (key.equals("user-agent", ignoreCase = true)) {
userAgent = value
Expand All @@ -136,7 +130,7 @@ class TVModel(var tv: TV) : ViewModel() {
}
}

_httpDataSource = defaultHttpDataSource
_httpDataSource = okHttpDataSource

sourceTypeList = if (path.lowercase().endsWith(".m3u8")) {
listOf(SourceType.HLS)
Expand Down
Loading

0 comments on commit 1a45f38

Please sign in to comment.