fix: single stream playback and history saving issues
This commit is contained in:
@@ -27,7 +27,6 @@ object UrlInterceptor {
|
||||
throw NetworkBlockedException("HTTP $httpCode: Network blocked or restricted")
|
||||
}
|
||||
|
||||
val finalUrl = response.request.url.toString()
|
||||
val content = response.body.string()
|
||||
|
||||
if (!content.trim().startsWith("#EXTM3U")) {
|
||||
@@ -35,14 +34,14 @@ object UrlInterceptor {
|
||||
if (contentLower.startsWith("<!doctype") || contentLower.startsWith("<html")) {
|
||||
throw NetworkBlockedException("Network blocked or restricted (HTML response)")
|
||||
}
|
||||
return@withContext finalUrl
|
||||
return@withContext currentUrl
|
||||
}
|
||||
|
||||
val isMasterPlaylist = content.contains("#EXT-X-STREAM-INF")
|
||||
val isMediaPlaylist = content.contains("#EXTINF")
|
||||
|
||||
if (isMediaPlaylist) {
|
||||
return@withContext finalUrl
|
||||
return@withContext currentUrl
|
||||
}
|
||||
|
||||
if (isMasterPlaylist) {
|
||||
@@ -52,7 +51,7 @@ object UrlInterceptor {
|
||||
}
|
||||
|
||||
if (variantUrl != null) {
|
||||
val uri = finalUrl.toUri()
|
||||
val uri = currentUrl.toUri()
|
||||
val baseUrl = "${uri.scheme}://${uri.host}${if (uri.port == -1) "" else ":${uri.port}"}${uri.path?.substringBeforeLast("/") ?: ""}"
|
||||
|
||||
currentUrl = if (variantUrl.startsWith("http")) {
|
||||
@@ -66,7 +65,7 @@ object UrlInterceptor {
|
||||
}
|
||||
}
|
||||
|
||||
return@withContext finalUrl
|
||||
return@withContext currentUrl
|
||||
}
|
||||
|
||||
url
|
||||
|
||||
@@ -247,6 +247,7 @@ fun ChannelItem(
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.defaultMinSize(minHeight = 96.dp)
|
||||
.clickable(onClick = onClick),
|
||||
colors = CardDefaults.cardColors(
|
||||
containerColor = if (isPlaying)
|
||||
@@ -258,6 +259,7 @@ fun ChannelItem(
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.defaultMinSize(minHeight = 96.dp)
|
||||
.padding(16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
|
||||
@@ -380,6 +380,7 @@ private fun PlayerPageContent(
|
||||
}
|
||||
}
|
||||
} else {
|
||||
viewModel.saveHistory()
|
||||
onBack()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,17 +82,46 @@ object M3U8Parser {
|
||||
}
|
||||
}
|
||||
|
||||
private data class FetchResult(
|
||||
val channels: List<M3U8Channel>,
|
||||
val finalUrl: String
|
||||
)
|
||||
|
||||
suspend fun parseM3U8FromUrl(url: String): Result<List<M3U8Channel>> = withContext(Dispatchers.IO) {
|
||||
val httpsUrl = NetworkUtils.convertToHttps(url)
|
||||
|
||||
try {
|
||||
val channels = fetchM3U8Channels(httpsUrl)
|
||||
Result.Success(channels)
|
||||
val result = fetchM3U8Channels(httpsUrl)
|
||||
if (result.channels.isEmpty()) {
|
||||
val singleChannel = listOf(
|
||||
M3U8Channel(
|
||||
id = "${result.finalUrl.hashCode()}_0",
|
||||
name = result.finalUrl.substringAfterLast("/").substringBeforeLast(".").ifEmpty {
|
||||
result.finalUrl.substringAfterLast("/").substringBefore("?").ifEmpty { "Stream" }
|
||||
},
|
||||
url = result.finalUrl
|
||||
)
|
||||
)
|
||||
return@withContext Result.Success(singleChannel)
|
||||
}
|
||||
Result.Success(result.channels)
|
||||
} catch (e: Exception) {
|
||||
if (httpsUrl != url) {
|
||||
try {
|
||||
val channels = fetchM3U8Channels(url)
|
||||
Result.Success(channels)
|
||||
val result = fetchM3U8Channels(url)
|
||||
if (result.channels.isEmpty()) {
|
||||
val singleChannel = listOf(
|
||||
M3U8Channel(
|
||||
id = "${result.finalUrl.hashCode()}_0",
|
||||
name = result.finalUrl.substringAfterLast("/").substringBeforeLast(".").ifEmpty {
|
||||
result.finalUrl.substringAfterLast("/").substringBefore("?").ifEmpty { "Stream" }
|
||||
},
|
||||
url = result.finalUrl
|
||||
)
|
||||
)
|
||||
return@withContext Result.Success(singleChannel)
|
||||
}
|
||||
Result.Success(result.channels)
|
||||
} catch (fallbackException: Exception) {
|
||||
Result.Error(fallbackException, "Failed to fetch M3U8 from URL: ${fallbackException.message}")
|
||||
}
|
||||
@@ -102,7 +131,7 @@ object M3U8Parser {
|
||||
}
|
||||
}
|
||||
|
||||
private fun fetchM3U8Channels(url: String): List<M3U8Channel> {
|
||||
private fun fetchM3U8Channels(url: String): FetchResult {
|
||||
val request = Request.Builder().url(url).build()
|
||||
val response = client.newCall(request).execute()
|
||||
|
||||
@@ -110,6 +139,8 @@ object M3U8Parser {
|
||||
throw Exception("HTTP ${response.code}")
|
||||
}
|
||||
|
||||
val finalUrl = response.request.url.toString()
|
||||
|
||||
val contentLength = response.body.contentLength()
|
||||
if (contentLength > PlayerConstants.M3U8_MAX_SIZE_BYTES) {
|
||||
response.close()
|
||||
@@ -173,7 +204,7 @@ object M3U8Parser {
|
||||
}
|
||||
}
|
||||
|
||||
return channels
|
||||
return FetchResult(channels, finalUrl)
|
||||
}
|
||||
|
||||
private fun extractChannelName(line: String): String {
|
||||
|
||||
@@ -107,7 +107,6 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application)
|
||||
localPlayer?.stop()
|
||||
|
||||
viewModelScope.launch {
|
||||
android.util.Log.d("PlayerViewModel", "loadChannels called: url=$url")
|
||||
_uiState.value = _uiState.value.copy(
|
||||
channels = emptyList(),
|
||||
isLoadingChannels = true,
|
||||
@@ -133,7 +132,7 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application)
|
||||
url.startsWith("rtmp") || url.startsWith("rtsp") -> {
|
||||
repository.parseM3U8FromUrl(url).onSuccess { channels ->
|
||||
parsedChannels = channels
|
||||
isM3U8Playlist = channels.size > 1
|
||||
isM3U8Playlist = channels.size >= 1
|
||||
}.onError { _, _ ->
|
||||
}
|
||||
}
|
||||
@@ -145,7 +144,7 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application)
|
||||
if (content.contains("#EXTM3U")) {
|
||||
repository.parseM3U8FromContent(content).onSuccess { channels ->
|
||||
parsedChannels = channels
|
||||
isM3U8Playlist = channels.size > 1
|
||||
isM3U8Playlist = channels.size >= 1
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -155,7 +154,7 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application)
|
||||
url.contains("#EXTM3U") -> {
|
||||
repository.parseM3U8FromContent(url).onSuccess { channels ->
|
||||
parsedChannels = channels
|
||||
isM3U8Playlist = channels.size > 1
|
||||
isM3U8Playlist = channels.size >= 1
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -360,10 +359,15 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application)
|
||||
|
||||
fun saveHistory() {
|
||||
val state = _uiState.value
|
||||
if (state.playlistUrl.isNotEmpty() && state.videoTitle.isNotEmpty()) {
|
||||
val watchTime = watchTimeTracker.getAccumulatedTime()
|
||||
if (watchTime == 0L) {
|
||||
return
|
||||
}
|
||||
val urlToSave = state.playlistUrl.ifEmpty { state.currentChannelUrl }
|
||||
if (urlToSave.isNotEmpty() && state.videoTitle.isNotEmpty()) {
|
||||
HistoryManager.addOrUpdateHistory(
|
||||
getApplication(),
|
||||
state.playlistUrl,
|
||||
urlToSave,
|
||||
state.videoTitle,
|
||||
state.currentChannelUrl,
|
||||
state.currentChannelId
|
||||
|
||||
Reference in New Issue
Block a user