fix: xtream cannot be parse.

This commit is contained in:
oxy
2024-03-15 02:40:59 +08:00
parent 09147be2f6
commit e146d6b9d0
9 changed files with 122 additions and 102 deletions

View File

@ -87,65 +87,63 @@ data class XtreamLive(
@Serializable
data class XtreamVod(
@SerialName("added")
val added: String?,
val added: String? = null,
@SerialName("category_id")
val categoryId: Int?,
val categoryId: Int? = null,
@SerialName("container_extension")
val containerExtension: String?,
val containerExtension: String? = null,
@SerialName("custom_sid")
val customSid: String?,
val customSid: String? = null,
@SerialName("direct_source")
val directSource: String?,
val directSource: String? = null,
@SerialName("name")
val name: String?,
val name: String? = null,
@SerialName("num")
val num: Int?,
val num: String? = null,
@SerialName("rating")
val rating: String?,
val rating: String? = null,
@SerialName("rating_5based")
val rating5based: Double?,
val rating5based: String? = null,
@SerialName("stream_icon")
val streamIcon: String?,
val streamIcon: String? = null,
@SerialName("stream_id")
val streamId: Int?,
val streamId: Int? = null,
@SerialName("stream_type")
val streamType: String?
val streamType: String? = null
)
@Serializable
data class XtreamSerial(
@SerialName("backdrop_path")
val backdropPath: List<String>,
@SerialName("cast")
val cast: String?,
val cast: String? = null,
@SerialName("category_id")
val categoryId: Int?,
val categoryId: Int? = null,
@SerialName("cover")
val cover: String?,
val cover: String? = null,
@SerialName("director")
val director: String?,
val director: String? = null,
@SerialName("episode_run_time")
val episodeRunTime: String?,
val episodeRunTime: String? = null,
@SerialName("genre")
val genre: String?,
val genre: String? = null,
@SerialName("last_modified")
val lastModified: String?,
val lastModified: String? = null,
@SerialName("name")
val name: String?,
val name: String? = null,
@SerialName("num")
val num: Int?,
val num: String? = null,
@SerialName("plot")
val plot: String?,
val plot: String? = null,
@SerialName("rating")
val rating: String?,
val rating: String? = null,
@SerialName("rating_5based")
val rating5based: Int?,
val rating5based: String? = null,
@SerialName("releaseDate")
val releaseDate: String?,
val releaseDate: String? = null,
@SerialName("series_id")
val seriesId: Int?,
val seriesId: Int? = null,
@SerialName("youtube_trailer")
val youtubeTrailer: String?
val youtubeTrailer: String? = null
)
fun XtreamLive.toStream(

View File

@ -6,11 +6,11 @@ import kotlinx.serialization.Serializable
@Serializable
data class XtreamStreamInfo(
@SerialName("episodes")
val episodes: Map<Int, List<Episode>> = emptyMap(),
val episodes: Map<String, List<Episode>> = emptyMap(),
@SerialName("info")
val info: Info?,
@SerialName("seasons")
val seasons: List<String> = emptyList()
// @SerialName("seasons")
// val seasons: List<String?> = emptyList()
) {
@Serializable
data class Episode(
@ -23,13 +23,13 @@ data class XtreamStreamInfo(
@SerialName("direct_source")
val directSource: String?,
@SerialName("episode_num")
val episodeNum: Int?,
val episodeNum: String?,
@SerialName("id")
val id: String?,
@SerialName("info")
val info: Info?,
@SerialName("season")
val season: Int?,
val season: String?,
@SerialName("title")
val title: String?
) {
@ -38,11 +38,11 @@ data class XtreamStreamInfo(
@SerialName("audio")
val audio: Audio?,
@SerialName("bitrate")
val bitrate: Int?,
val bitrate: String?,
@SerialName("duration")
val duration: String?,
@SerialName("duration_secs")
val durationSecs: Int?,
val durationSecs: String?,
@SerialName("video")
val video: Video?
) {
@ -51,9 +51,9 @@ data class XtreamStreamInfo(
@SerialName("avg_frame_rate")
val avgFrameRate: String?,
@SerialName("bits_per_sample")
val bitsPerSample: Int?,
val bitsPerSample: String?,
@SerialName("channels")
val channels: Int?,
val channels: String?,
@SerialName("codec_long_name")
val codecLongName: String?,
@SerialName("codec_name")
@ -71,7 +71,7 @@ data class XtreamStreamInfo(
@SerialName("dmix_mode")
val dmixMode: String?,
@SerialName("index")
val index: Int?,
val index: String?,
@SerialName("loro_cmixlev")
val loroCmixlev: String?,
@SerialName("loro_surmixlev")
@ -87,7 +87,7 @@ data class XtreamStreamInfo(
@SerialName("sample_rate")
val sampleRate: String?,
@SerialName("start_pts")
val startPts: Int?,
val startPts: String?,
@SerialName("start_time")
val startTime: String?,
@SerialName("tags")
@ -117,9 +117,9 @@ data class XtreamStreamInfo(
@SerialName("codec_type")
val codecType: String?,
@SerialName("coded_height")
val codedHeight: Int?,
val codedHeight: String?,
@SerialName("coded_width")
val codedWidth: Int?,
val codedWidth: String?,
@SerialName("display_aspect_ratio")
val displayAspectRatio: String?,
@SerialName("disposition")
@ -127,15 +127,15 @@ data class XtreamStreamInfo(
@SerialName("field_order")
val fieldOrder: String?,
@SerialName("has_b_frames")
val hasBFrames: Int?,
val hasBFrames: String?,
@SerialName("height")
val height: Int?,
val height: String?,
@SerialName("index")
val index: Int?,
val index: String?,
@SerialName("is_avc")
val isAvc: Boolean = false,
@SerialName("level")
val level: Int?,
val level: String?,
@SerialName("nal_length_size")
val nalLengthSize: String?,
@SerialName("pix_fmt")
@ -145,11 +145,11 @@ data class XtreamStreamInfo(
@SerialName("r_frame_rate")
val rFrameRate: String?,
@SerialName("refs")
val refs: Int?,
val refs: String?,
@SerialName("sample_aspect_ratio")
val sampleAspectRatio: String?,
@SerialName("start_pts")
val startPts: Int?,
val startPts: String?,
@SerialName("start_time")
val startTime: String?,
@SerialName("tags")
@ -157,35 +157,35 @@ data class XtreamStreamInfo(
@SerialName("time_base")
val timeBase: String?,
@SerialName("width")
val width: Int?
val width: String?
)
@Serializable
data class Disposition(
@SerialName("attached_pic")
val attachedPic: Int?,
val attachedPic: String?,
@SerialName("clean_effects")
val cleanEffects: Int?,
val cleanEffects: String?,
@SerialName("comment")
val comment: Int?,
val comment: String?,
@SerialName("default")
val default: Int?,
val default: String?,
@SerialName("dub")
val dub: Int?,
val dub: String?,
@SerialName("forced")
val forced: Int?,
val forced: String?,
@SerialName("hearing_impaired")
val hearingImpaired: Int?,
val hearingImpaired: String?,
@SerialName("karaoke")
val karaoke: Int?,
val karaoke: String?,
@SerialName("lyrics")
val lyrics: Int?,
val lyrics: String?,
@SerialName("original")
val original: Int?,
val original: String?,
@SerialName("timed_thumbnails")
val timedThumbnails: Int?,
val timedThumbnails: String?,
@SerialName("visual_impaired")
val visualImpaired: Int?
val visualImpaired: String?
)
}
}
@ -215,7 +215,7 @@ data class XtreamStreamInfo(
@SerialName("rating")
val rating: String?,
@SerialName("rating_5based")
val rating5based: Int?,
val rating5based: String?,
@SerialName("releaseDate")
val releaseDate: String?,
@SerialName("youtube_trailer")

View File

@ -13,6 +13,9 @@ import kotlinx.coroutines.flow.Flow
@Dao
interface StreamDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertOrReplace(stream: Stream)
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertOrReplaceAll(vararg streams: Stream)

View File

@ -32,6 +32,7 @@ internal class XtreamParserImpl @Inject constructor(
private val json = Json {
ignoreUnknownKeys = true
explicitNulls = false
isLenient = true
}
override suspend fun execute(
@ -87,19 +88,25 @@ internal class XtreamParserImpl @Inject constructor(
val port = info.serverInfo.port?.toIntOrNull()
val httpsPort = info.serverInfo.httpsPort?.toIntOrNull()
val lives: List<XtreamLive> = if (requiredLives) newCall(liveStreamsUrl) ?: emptyList() else emptyList()
val lives: List<XtreamLive> =
if (requiredLives) newCall(liveStreamsUrl) ?: emptyList() else emptyList()
currentCount += lives.size
callback(currentCount, -1)
val vods: List<XtreamVod> = if (requiredVods) newCall(vodStreamsUrl) ?: emptyList() else emptyList()
val vods: List<XtreamVod> =
if (requiredVods) newCall(vodStreamsUrl) ?: emptyList() else emptyList()
currentCount += vods.size
callback(currentCount, -1)
val series: List<XtreamSerial> = if (requiredSeries) newCall(seriesStreamsUrl) ?: emptyList() else emptyList()
val series: List<XtreamSerial> =
if (requiredSeries) newCall(seriesStreamsUrl) ?: emptyList() else emptyList()
currentCount += series.size
callback(currentCount, -1)
val liveCategories: List<XtreamCategory> = if (requiredLives) newCall(liveCategoriesUrl) ?: emptyList() else emptyList()
val vodCategories: List<XtreamCategory> = if (requiredVods) newCall(vodCategoriesUrl) ?: emptyList() else emptyList()
val serialCategories: List<XtreamCategory> = if (requiredSeries) newCall(serialCategoriesUrl) ?: emptyList() else emptyList()
val liveCategories: List<XtreamCategory> =
if (requiredLives) newCall(liveCategoriesUrl) ?: emptyList() else emptyList()
val vodCategories: List<XtreamCategory> =
if (requiredVods) newCall(vodCategoriesUrl) ?: emptyList() else emptyList()
val serialCategories: List<XtreamCategory> =
if (requiredSeries) newCall(serialCategoriesUrl) ?: emptyList() else emptyList()
return XtreamOutput(
lives = lives,

View File

@ -169,7 +169,7 @@ class PlaylistRepositoryImpl @Inject constructor(
source = DataSource.Xtream
)
playlistDao.insertOrReplace(playlist)
val streams = lives.map { current ->
lives.forEach { current ->
current.toStream(
basicUrl = basicUrl,
username = username,
@ -177,17 +177,13 @@ class PlaylistRepositoryImpl @Inject constructor(
playlistUrl = playlist.url,
category = liveCategories.find { it.categoryId == current.categoryId }?.categoryName.orEmpty(),
containerExtension = allowedOutputFormats.first()
).also {
).also { stream ->
currentCount += 1
callback(currentCount, total)
streamDao.insertOrReplace(stream)
}
}
streamDao.compareAndUpdate(
strategy = pref.playlistStrategy,
url = playlist.url,
update = streams
)
logger.log("xtream: lives +[${streams.size}]")
logger.log("xtream: lives +[${lives.size}]")
}
if (requiredVods) {
val playlist = Playlist(
@ -200,23 +196,19 @@ class PlaylistRepositoryImpl @Inject constructor(
source = DataSource.Xtream
)
playlistDao.insertOrReplace(playlist)
val streams = vods.map { current ->
vods.forEach { current ->
current.toStream(
basicUrl = basicUrl,
username = username,
password = password,
playlistUrl = playlist.url,
category = vodCategories.find { it.categoryId == current.categoryId }?.categoryName.orEmpty()
).also {
).also { stream ->
currentCount += 1
callback(currentCount, total)
streamDao.insertOrReplace(stream)
}
}
streamDao.compareAndUpdate(
strategy = pref.playlistStrategy,
url = playlist.url,
update = streams
)
logger.log("xtream: vods +[${vods.size}]")
}
@ -231,12 +223,12 @@ class PlaylistRepositoryImpl @Inject constructor(
source = DataSource.Xtream
)
playlistDao.insertOrReplace(playlist)
val streams = series.flatMap { current ->
series.forEach { current ->
ensureActive()
val seriesInfo = xtreamParser.getSeriesInfo(
input = input.copy(type = DataSource.Xtream.TYPE_SERIES),
seriesId = current.seriesId ?: return@flatMap emptyList()
) ?: return@flatMap emptyList()
seriesId = current.seriesId ?: return@forEach
) ?: return@forEach
seriesInfo.episodes.flatMap { (_, episodes) ->
episodes.map { episode ->
Stream(
@ -245,19 +237,15 @@ class PlaylistRepositoryImpl @Inject constructor(
title = current.name.orEmpty() + " " + episode.title.orEmpty(),
cover = current.cover,
playlistUrl = playlist.url,
)
).also { stream ->
currentCount += 1
callback(currentCount, total)
streamDao.insertOrReplace(stream)
}
}
}.also {
currentCount += 1
callback(currentCount, total)
}
}
streamDao.compareAndUpdate(
strategy = pref.playlistStrategy,
url = playlist.url,
update = streams
)
logger.log("xtream: series +[${streams.size}]")
logger.log("xtream: series +[${series.size}]")
}
}

View File

@ -27,6 +27,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.InternalComposeApi
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@ -45,6 +46,7 @@ import com.google.accompanist.permissions.shouldShowRationale
import com.m3u.core.architecture.pref.LocalPref
import com.m3u.core.util.basic.title
import com.m3u.core.wrapper.Event
import com.m3u.data.database.model.DataSource
import com.m3u.data.database.model.Stream
import com.m3u.features.playlist.internal.PlaylistScreenImpl
import com.m3u.features.playlist.internal.TvPlaylistScreenImpl
@ -87,6 +89,14 @@ internal fun PlaylistRoute(
val pinnedCategories by viewModel.pinnedCategories.collectAsStateWithLifecycle()
val refreshing by viewModel.subscribingOrRefreshing.collectAsStateWithLifecycle()
val singleLineTitle by remember {
derivedStateOf {
when (playlist?.type) {
DataSource.Xtream.TYPE_SERIES -> false
else -> true
}
}
}
val sorts = viewModel.sorts
val sort by viewModel.sort.collectAsStateWithLifecycle()
@ -179,6 +189,7 @@ internal fun PlaylistRoute(
viewModel.onEvent(PlaylistEvent.SavePicture(it))
},
createShortcut = { viewModel.onEvent(PlaylistEvent.CreateShortcut(context, it)) },
singleLineTitle = singleLineTitle,
modifier = Modifier
.fillMaxSize()
.thenIf(!tv && pref.godMode) {
@ -219,6 +230,7 @@ private fun PlaylistScreen(
savePicture: (streamId: Int) -> Unit,
createShortcut: (streamId: Int) -> Unit,
contentPadding: PaddingValues,
singleLineTitle: Boolean,
modifier: Modifier = Modifier
) {
val helper = LocalHelper.current
@ -270,6 +282,7 @@ private fun PlaylistScreen(
hide = hide,
onSavePicture = savePicture,
createShortcut = createShortcut,
singleLineTitle = singleLineTitle,
modifier = modifier
)
} else {

View File

@ -25,6 +25,7 @@ internal fun StreamGallery(
streams: ImmutableList<Stream>,
zapping: Stream?,
recently: Boolean,
singleLineTitle: Boolean,
onClick: (Stream) -> Unit,
onMenu: (Stream) -> Unit,
modifier: Modifier = Modifier,
@ -41,7 +42,8 @@ internal fun StreamGallery(
onClick = onClick,
onMenu = onMenu,
modifier = modifier,
contentPadding = contentPadding
contentPadding = contentPadding,
singleLineTitle = singleLineTitle
)
}
@ -70,6 +72,7 @@ private fun StreamGalleryImpl(
streams: ImmutableList<Stream>,
zapping: Stream?,
recently: Boolean,
singleLineTitle: Boolean,
onClick: (Stream) -> Unit,
onMenu: (Stream) -> Unit,
modifier: Modifier = Modifier,
@ -97,6 +100,7 @@ private fun StreamGalleryImpl(
onClick = { onClick(stream) },
onLongClick = { onMenu(stream) },
modifier = Modifier.fillMaxWidth(),
singleLineTitle = singleLineTitle
)
}
}

View File

@ -55,7 +55,8 @@ internal fun StreamItem(
zapping: Boolean,
onClick: () -> Unit,
onLongClick: () -> Unit,
modifier: Modifier = Modifier
modifier: Modifier = Modifier,
singleLineTitle: Boolean = true
) {
when (currentUiMode()) {
UiMode.Default -> {
@ -65,7 +66,8 @@ internal fun StreamItem(
zapping = zapping,
onClick = onClick,
onLongClick = onLongClick,
modifier = modifier
modifier = modifier,
singleLineTitle = singleLineTitle
)
}
@ -76,7 +78,8 @@ internal fun StreamItem(
zapping = zapping,
onClick = onClick,
onLongClick = onLongClick,
modifier = modifier
modifier = modifier,
singleLineTitle = singleLineTitle
)
}
@ -92,6 +95,7 @@ private fun StreamItemImpl(
onLongClick: () -> Unit,
modifier: Modifier = Modifier,
zapping: Boolean = false,
singleLineTitle: Boolean = true
) {
val context = LocalContext.current
val spacing = LocalSpacing.current
@ -153,7 +157,7 @@ private fun StreamItemImpl(
text = stream.title.trim(),
style = MaterialTheme.typography.titleSmall,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
maxLines = if (singleLineTitle) 1 else Int.MAX_VALUE,
fontWeight = FontWeight.Bold,
)
if (recently) {
@ -218,6 +222,7 @@ private fun CompactStreamItem(
onClick: () -> Unit,
onLongClick: () -> Unit,
modifier: Modifier = Modifier,
singleLineTitle: Boolean = true
) {
val spacing = LocalSpacing.current
val favourite = stream.favourite
@ -242,7 +247,7 @@ private fun CompactStreamItem(
style = MaterialTheme.typography.titleSmall,
fontSize = MaterialTheme.typography.titleSmall.fontSize,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
maxLines = if (singleLineTitle) 1 else Int.MAX_VALUE,
fontWeight = FontWeight.Bold
)
},

View File

@ -95,6 +95,7 @@ internal fun PlaylistScreenImpl(
onSavePicture: (streamId: Int) -> Unit,
createShortcut: (streamId: Int) -> Unit,
isAtTopState: MutableState<Boolean>,
singleLineTitle: Boolean,
modifier: Modifier = Modifier,
contentPadding: PaddingValues = PaddingValues()
) {
@ -219,6 +220,7 @@ internal fun PlaylistScreenImpl(
streams = categories[currentPage].streams,
zapping = zapping,
recently = sort == Sort.RECENTLY,
singleLineTitle = singleLineTitle,
onClick = onStream,
contentPadding = inner,
onMenu = { dialogStatus = DialogStatus.Selections(it) },