mirror of
https://github.com/oxyroid/M3UAndroid.git
synced 2025-08-06 14:59:48 +08:00
fix: xtream cannot be parse.
This commit is contained in:
@ -87,65 +87,63 @@ data class XtreamLive(
|
|||||||
@Serializable
|
@Serializable
|
||||||
data class XtreamVod(
|
data class XtreamVod(
|
||||||
@SerialName("added")
|
@SerialName("added")
|
||||||
val added: String?,
|
val added: String? = null,
|
||||||
@SerialName("category_id")
|
@SerialName("category_id")
|
||||||
val categoryId: Int?,
|
val categoryId: Int? = null,
|
||||||
@SerialName("container_extension")
|
@SerialName("container_extension")
|
||||||
val containerExtension: String?,
|
val containerExtension: String? = null,
|
||||||
@SerialName("custom_sid")
|
@SerialName("custom_sid")
|
||||||
val customSid: String?,
|
val customSid: String? = null,
|
||||||
@SerialName("direct_source")
|
@SerialName("direct_source")
|
||||||
val directSource: String?,
|
val directSource: String? = null,
|
||||||
@SerialName("name")
|
@SerialName("name")
|
||||||
val name: String?,
|
val name: String? = null,
|
||||||
@SerialName("num")
|
@SerialName("num")
|
||||||
val num: Int?,
|
val num: String? = null,
|
||||||
@SerialName("rating")
|
@SerialName("rating")
|
||||||
val rating: String?,
|
val rating: String? = null,
|
||||||
@SerialName("rating_5based")
|
@SerialName("rating_5based")
|
||||||
val rating5based: Double?,
|
val rating5based: String? = null,
|
||||||
@SerialName("stream_icon")
|
@SerialName("stream_icon")
|
||||||
val streamIcon: String?,
|
val streamIcon: String? = null,
|
||||||
@SerialName("stream_id")
|
@SerialName("stream_id")
|
||||||
val streamId: Int?,
|
val streamId: Int? = null,
|
||||||
@SerialName("stream_type")
|
@SerialName("stream_type")
|
||||||
val streamType: String?
|
val streamType: String? = null
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class XtreamSerial(
|
data class XtreamSerial(
|
||||||
@SerialName("backdrop_path")
|
|
||||||
val backdropPath: List<String>,
|
|
||||||
@SerialName("cast")
|
@SerialName("cast")
|
||||||
val cast: String?,
|
val cast: String? = null,
|
||||||
@SerialName("category_id")
|
@SerialName("category_id")
|
||||||
val categoryId: Int?,
|
val categoryId: Int? = null,
|
||||||
@SerialName("cover")
|
@SerialName("cover")
|
||||||
val cover: String?,
|
val cover: String? = null,
|
||||||
@SerialName("director")
|
@SerialName("director")
|
||||||
val director: String?,
|
val director: String? = null,
|
||||||
@SerialName("episode_run_time")
|
@SerialName("episode_run_time")
|
||||||
val episodeRunTime: String?,
|
val episodeRunTime: String? = null,
|
||||||
@SerialName("genre")
|
@SerialName("genre")
|
||||||
val genre: String?,
|
val genre: String? = null,
|
||||||
@SerialName("last_modified")
|
@SerialName("last_modified")
|
||||||
val lastModified: String?,
|
val lastModified: String? = null,
|
||||||
@SerialName("name")
|
@SerialName("name")
|
||||||
val name: String?,
|
val name: String? = null,
|
||||||
@SerialName("num")
|
@SerialName("num")
|
||||||
val num: Int?,
|
val num: String? = null,
|
||||||
@SerialName("plot")
|
@SerialName("plot")
|
||||||
val plot: String?,
|
val plot: String? = null,
|
||||||
@SerialName("rating")
|
@SerialName("rating")
|
||||||
val rating: String?,
|
val rating: String? = null,
|
||||||
@SerialName("rating_5based")
|
@SerialName("rating_5based")
|
||||||
val rating5based: Int?,
|
val rating5based: String? = null,
|
||||||
@SerialName("releaseDate")
|
@SerialName("releaseDate")
|
||||||
val releaseDate: String?,
|
val releaseDate: String? = null,
|
||||||
@SerialName("series_id")
|
@SerialName("series_id")
|
||||||
val seriesId: Int?,
|
val seriesId: Int? = null,
|
||||||
@SerialName("youtube_trailer")
|
@SerialName("youtube_trailer")
|
||||||
val youtubeTrailer: String?
|
val youtubeTrailer: String? = null
|
||||||
)
|
)
|
||||||
|
|
||||||
fun XtreamLive.toStream(
|
fun XtreamLive.toStream(
|
||||||
|
@ -6,11 +6,11 @@ import kotlinx.serialization.Serializable
|
|||||||
@Serializable
|
@Serializable
|
||||||
data class XtreamStreamInfo(
|
data class XtreamStreamInfo(
|
||||||
@SerialName("episodes")
|
@SerialName("episodes")
|
||||||
val episodes: Map<Int, List<Episode>> = emptyMap(),
|
val episodes: Map<String, List<Episode>> = emptyMap(),
|
||||||
@SerialName("info")
|
@SerialName("info")
|
||||||
val info: Info?,
|
val info: Info?,
|
||||||
@SerialName("seasons")
|
// @SerialName("seasons")
|
||||||
val seasons: List<String> = emptyList()
|
// val seasons: List<String?> = emptyList()
|
||||||
) {
|
) {
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Episode(
|
data class Episode(
|
||||||
@ -23,13 +23,13 @@ data class XtreamStreamInfo(
|
|||||||
@SerialName("direct_source")
|
@SerialName("direct_source")
|
||||||
val directSource: String?,
|
val directSource: String?,
|
||||||
@SerialName("episode_num")
|
@SerialName("episode_num")
|
||||||
val episodeNum: Int?,
|
val episodeNum: String?,
|
||||||
@SerialName("id")
|
@SerialName("id")
|
||||||
val id: String?,
|
val id: String?,
|
||||||
@SerialName("info")
|
@SerialName("info")
|
||||||
val info: Info?,
|
val info: Info?,
|
||||||
@SerialName("season")
|
@SerialName("season")
|
||||||
val season: Int?,
|
val season: String?,
|
||||||
@SerialName("title")
|
@SerialName("title")
|
||||||
val title: String?
|
val title: String?
|
||||||
) {
|
) {
|
||||||
@ -38,11 +38,11 @@ data class XtreamStreamInfo(
|
|||||||
@SerialName("audio")
|
@SerialName("audio")
|
||||||
val audio: Audio?,
|
val audio: Audio?,
|
||||||
@SerialName("bitrate")
|
@SerialName("bitrate")
|
||||||
val bitrate: Int?,
|
val bitrate: String?,
|
||||||
@SerialName("duration")
|
@SerialName("duration")
|
||||||
val duration: String?,
|
val duration: String?,
|
||||||
@SerialName("duration_secs")
|
@SerialName("duration_secs")
|
||||||
val durationSecs: Int?,
|
val durationSecs: String?,
|
||||||
@SerialName("video")
|
@SerialName("video")
|
||||||
val video: Video?
|
val video: Video?
|
||||||
) {
|
) {
|
||||||
@ -51,9 +51,9 @@ data class XtreamStreamInfo(
|
|||||||
@SerialName("avg_frame_rate")
|
@SerialName("avg_frame_rate")
|
||||||
val avgFrameRate: String?,
|
val avgFrameRate: String?,
|
||||||
@SerialName("bits_per_sample")
|
@SerialName("bits_per_sample")
|
||||||
val bitsPerSample: Int?,
|
val bitsPerSample: String?,
|
||||||
@SerialName("channels")
|
@SerialName("channels")
|
||||||
val channels: Int?,
|
val channels: String?,
|
||||||
@SerialName("codec_long_name")
|
@SerialName("codec_long_name")
|
||||||
val codecLongName: String?,
|
val codecLongName: String?,
|
||||||
@SerialName("codec_name")
|
@SerialName("codec_name")
|
||||||
@ -71,7 +71,7 @@ data class XtreamStreamInfo(
|
|||||||
@SerialName("dmix_mode")
|
@SerialName("dmix_mode")
|
||||||
val dmixMode: String?,
|
val dmixMode: String?,
|
||||||
@SerialName("index")
|
@SerialName("index")
|
||||||
val index: Int?,
|
val index: String?,
|
||||||
@SerialName("loro_cmixlev")
|
@SerialName("loro_cmixlev")
|
||||||
val loroCmixlev: String?,
|
val loroCmixlev: String?,
|
||||||
@SerialName("loro_surmixlev")
|
@SerialName("loro_surmixlev")
|
||||||
@ -87,7 +87,7 @@ data class XtreamStreamInfo(
|
|||||||
@SerialName("sample_rate")
|
@SerialName("sample_rate")
|
||||||
val sampleRate: String?,
|
val sampleRate: String?,
|
||||||
@SerialName("start_pts")
|
@SerialName("start_pts")
|
||||||
val startPts: Int?,
|
val startPts: String?,
|
||||||
@SerialName("start_time")
|
@SerialName("start_time")
|
||||||
val startTime: String?,
|
val startTime: String?,
|
||||||
@SerialName("tags")
|
@SerialName("tags")
|
||||||
@ -117,9 +117,9 @@ data class XtreamStreamInfo(
|
|||||||
@SerialName("codec_type")
|
@SerialName("codec_type")
|
||||||
val codecType: String?,
|
val codecType: String?,
|
||||||
@SerialName("coded_height")
|
@SerialName("coded_height")
|
||||||
val codedHeight: Int?,
|
val codedHeight: String?,
|
||||||
@SerialName("coded_width")
|
@SerialName("coded_width")
|
||||||
val codedWidth: Int?,
|
val codedWidth: String?,
|
||||||
@SerialName("display_aspect_ratio")
|
@SerialName("display_aspect_ratio")
|
||||||
val displayAspectRatio: String?,
|
val displayAspectRatio: String?,
|
||||||
@SerialName("disposition")
|
@SerialName("disposition")
|
||||||
@ -127,15 +127,15 @@ data class XtreamStreamInfo(
|
|||||||
@SerialName("field_order")
|
@SerialName("field_order")
|
||||||
val fieldOrder: String?,
|
val fieldOrder: String?,
|
||||||
@SerialName("has_b_frames")
|
@SerialName("has_b_frames")
|
||||||
val hasBFrames: Int?,
|
val hasBFrames: String?,
|
||||||
@SerialName("height")
|
@SerialName("height")
|
||||||
val height: Int?,
|
val height: String?,
|
||||||
@SerialName("index")
|
@SerialName("index")
|
||||||
val index: Int?,
|
val index: String?,
|
||||||
@SerialName("is_avc")
|
@SerialName("is_avc")
|
||||||
val isAvc: Boolean = false,
|
val isAvc: Boolean = false,
|
||||||
@SerialName("level")
|
@SerialName("level")
|
||||||
val level: Int?,
|
val level: String?,
|
||||||
@SerialName("nal_length_size")
|
@SerialName("nal_length_size")
|
||||||
val nalLengthSize: String?,
|
val nalLengthSize: String?,
|
||||||
@SerialName("pix_fmt")
|
@SerialName("pix_fmt")
|
||||||
@ -145,11 +145,11 @@ data class XtreamStreamInfo(
|
|||||||
@SerialName("r_frame_rate")
|
@SerialName("r_frame_rate")
|
||||||
val rFrameRate: String?,
|
val rFrameRate: String?,
|
||||||
@SerialName("refs")
|
@SerialName("refs")
|
||||||
val refs: Int?,
|
val refs: String?,
|
||||||
@SerialName("sample_aspect_ratio")
|
@SerialName("sample_aspect_ratio")
|
||||||
val sampleAspectRatio: String?,
|
val sampleAspectRatio: String?,
|
||||||
@SerialName("start_pts")
|
@SerialName("start_pts")
|
||||||
val startPts: Int?,
|
val startPts: String?,
|
||||||
@SerialName("start_time")
|
@SerialName("start_time")
|
||||||
val startTime: String?,
|
val startTime: String?,
|
||||||
@SerialName("tags")
|
@SerialName("tags")
|
||||||
@ -157,35 +157,35 @@ data class XtreamStreamInfo(
|
|||||||
@SerialName("time_base")
|
@SerialName("time_base")
|
||||||
val timeBase: String?,
|
val timeBase: String?,
|
||||||
@SerialName("width")
|
@SerialName("width")
|
||||||
val width: Int?
|
val width: String?
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Disposition(
|
data class Disposition(
|
||||||
@SerialName("attached_pic")
|
@SerialName("attached_pic")
|
||||||
val attachedPic: Int?,
|
val attachedPic: String?,
|
||||||
@SerialName("clean_effects")
|
@SerialName("clean_effects")
|
||||||
val cleanEffects: Int?,
|
val cleanEffects: String?,
|
||||||
@SerialName("comment")
|
@SerialName("comment")
|
||||||
val comment: Int?,
|
val comment: String?,
|
||||||
@SerialName("default")
|
@SerialName("default")
|
||||||
val default: Int?,
|
val default: String?,
|
||||||
@SerialName("dub")
|
@SerialName("dub")
|
||||||
val dub: Int?,
|
val dub: String?,
|
||||||
@SerialName("forced")
|
@SerialName("forced")
|
||||||
val forced: Int?,
|
val forced: String?,
|
||||||
@SerialName("hearing_impaired")
|
@SerialName("hearing_impaired")
|
||||||
val hearingImpaired: Int?,
|
val hearingImpaired: String?,
|
||||||
@SerialName("karaoke")
|
@SerialName("karaoke")
|
||||||
val karaoke: Int?,
|
val karaoke: String?,
|
||||||
@SerialName("lyrics")
|
@SerialName("lyrics")
|
||||||
val lyrics: Int?,
|
val lyrics: String?,
|
||||||
@SerialName("original")
|
@SerialName("original")
|
||||||
val original: Int?,
|
val original: String?,
|
||||||
@SerialName("timed_thumbnails")
|
@SerialName("timed_thumbnails")
|
||||||
val timedThumbnails: Int?,
|
val timedThumbnails: String?,
|
||||||
@SerialName("visual_impaired")
|
@SerialName("visual_impaired")
|
||||||
val visualImpaired: Int?
|
val visualImpaired: String?
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -215,7 +215,7 @@ data class XtreamStreamInfo(
|
|||||||
@SerialName("rating")
|
@SerialName("rating")
|
||||||
val rating: String?,
|
val rating: String?,
|
||||||
@SerialName("rating_5based")
|
@SerialName("rating_5based")
|
||||||
val rating5based: Int?,
|
val rating5based: String?,
|
||||||
@SerialName("releaseDate")
|
@SerialName("releaseDate")
|
||||||
val releaseDate: String?,
|
val releaseDate: String?,
|
||||||
@SerialName("youtube_trailer")
|
@SerialName("youtube_trailer")
|
||||||
|
@ -13,6 +13,9 @@ import kotlinx.coroutines.flow.Flow
|
|||||||
|
|
||||||
@Dao
|
@Dao
|
||||||
interface StreamDao {
|
interface StreamDao {
|
||||||
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
|
suspend fun insertOrReplace(stream: Stream)
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
suspend fun insertOrReplaceAll(vararg streams: Stream)
|
suspend fun insertOrReplaceAll(vararg streams: Stream)
|
||||||
|
|
||||||
|
@ -32,6 +32,7 @@ internal class XtreamParserImpl @Inject constructor(
|
|||||||
private val json = Json {
|
private val json = Json {
|
||||||
ignoreUnknownKeys = true
|
ignoreUnknownKeys = true
|
||||||
explicitNulls = false
|
explicitNulls = false
|
||||||
|
isLenient = true
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun execute(
|
override suspend fun execute(
|
||||||
@ -87,19 +88,25 @@ internal class XtreamParserImpl @Inject constructor(
|
|||||||
val port = info.serverInfo.port?.toIntOrNull()
|
val port = info.serverInfo.port?.toIntOrNull()
|
||||||
val httpsPort = info.serverInfo.httpsPort?.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
|
currentCount += lives.size
|
||||||
callback(currentCount, -1)
|
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
|
currentCount += vods.size
|
||||||
callback(currentCount, -1)
|
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
|
currentCount += series.size
|
||||||
callback(currentCount, -1)
|
callback(currentCount, -1)
|
||||||
|
|
||||||
val liveCategories: List<XtreamCategory> = if (requiredLives) newCall(liveCategoriesUrl) ?: emptyList() else emptyList()
|
val liveCategories: List<XtreamCategory> =
|
||||||
val vodCategories: List<XtreamCategory> = if (requiredVods) newCall(vodCategoriesUrl) ?: emptyList() else emptyList()
|
if (requiredLives) newCall(liveCategoriesUrl) ?: emptyList() else emptyList()
|
||||||
val serialCategories: List<XtreamCategory> = if (requiredSeries) newCall(serialCategoriesUrl) ?: 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(
|
return XtreamOutput(
|
||||||
lives = lives,
|
lives = lives,
|
||||||
|
@ -169,7 +169,7 @@ class PlaylistRepositoryImpl @Inject constructor(
|
|||||||
source = DataSource.Xtream
|
source = DataSource.Xtream
|
||||||
)
|
)
|
||||||
playlistDao.insertOrReplace(playlist)
|
playlistDao.insertOrReplace(playlist)
|
||||||
val streams = lives.map { current ->
|
lives.forEach { current ->
|
||||||
current.toStream(
|
current.toStream(
|
||||||
basicUrl = basicUrl,
|
basicUrl = basicUrl,
|
||||||
username = username,
|
username = username,
|
||||||
@ -177,17 +177,13 @@ class PlaylistRepositoryImpl @Inject constructor(
|
|||||||
playlistUrl = playlist.url,
|
playlistUrl = playlist.url,
|
||||||
category = liveCategories.find { it.categoryId == current.categoryId }?.categoryName.orEmpty(),
|
category = liveCategories.find { it.categoryId == current.categoryId }?.categoryName.orEmpty(),
|
||||||
containerExtension = allowedOutputFormats.first()
|
containerExtension = allowedOutputFormats.first()
|
||||||
).also {
|
).also { stream ->
|
||||||
currentCount += 1
|
currentCount += 1
|
||||||
callback(currentCount, total)
|
callback(currentCount, total)
|
||||||
|
streamDao.insertOrReplace(stream)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
streamDao.compareAndUpdate(
|
logger.log("xtream: lives +[${lives.size}]")
|
||||||
strategy = pref.playlistStrategy,
|
|
||||||
url = playlist.url,
|
|
||||||
update = streams
|
|
||||||
)
|
|
||||||
logger.log("xtream: lives +[${streams.size}]")
|
|
||||||
}
|
}
|
||||||
if (requiredVods) {
|
if (requiredVods) {
|
||||||
val playlist = Playlist(
|
val playlist = Playlist(
|
||||||
@ -200,23 +196,19 @@ class PlaylistRepositoryImpl @Inject constructor(
|
|||||||
source = DataSource.Xtream
|
source = DataSource.Xtream
|
||||||
)
|
)
|
||||||
playlistDao.insertOrReplace(playlist)
|
playlistDao.insertOrReplace(playlist)
|
||||||
val streams = vods.map { current ->
|
vods.forEach { current ->
|
||||||
current.toStream(
|
current.toStream(
|
||||||
basicUrl = basicUrl,
|
basicUrl = basicUrl,
|
||||||
username = username,
|
username = username,
|
||||||
password = password,
|
password = password,
|
||||||
playlistUrl = playlist.url,
|
playlistUrl = playlist.url,
|
||||||
category = vodCategories.find { it.categoryId == current.categoryId }?.categoryName.orEmpty()
|
category = vodCategories.find { it.categoryId == current.categoryId }?.categoryName.orEmpty()
|
||||||
).also {
|
).also { stream ->
|
||||||
currentCount += 1
|
currentCount += 1
|
||||||
callback(currentCount, total)
|
callback(currentCount, total)
|
||||||
|
streamDao.insertOrReplace(stream)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
streamDao.compareAndUpdate(
|
|
||||||
strategy = pref.playlistStrategy,
|
|
||||||
url = playlist.url,
|
|
||||||
update = streams
|
|
||||||
)
|
|
||||||
logger.log("xtream: vods +[${vods.size}]")
|
logger.log("xtream: vods +[${vods.size}]")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -231,12 +223,12 @@ class PlaylistRepositoryImpl @Inject constructor(
|
|||||||
source = DataSource.Xtream
|
source = DataSource.Xtream
|
||||||
)
|
)
|
||||||
playlistDao.insertOrReplace(playlist)
|
playlistDao.insertOrReplace(playlist)
|
||||||
val streams = series.flatMap { current ->
|
series.forEach { current ->
|
||||||
ensureActive()
|
ensureActive()
|
||||||
val seriesInfo = xtreamParser.getSeriesInfo(
|
val seriesInfo = xtreamParser.getSeriesInfo(
|
||||||
input = input.copy(type = DataSource.Xtream.TYPE_SERIES),
|
input = input.copy(type = DataSource.Xtream.TYPE_SERIES),
|
||||||
seriesId = current.seriesId ?: return@flatMap emptyList()
|
seriesId = current.seriesId ?: return@forEach
|
||||||
) ?: return@flatMap emptyList()
|
) ?: return@forEach
|
||||||
seriesInfo.episodes.flatMap { (_, episodes) ->
|
seriesInfo.episodes.flatMap { (_, episodes) ->
|
||||||
episodes.map { episode ->
|
episodes.map { episode ->
|
||||||
Stream(
|
Stream(
|
||||||
@ -245,19 +237,15 @@ class PlaylistRepositoryImpl @Inject constructor(
|
|||||||
title = current.name.orEmpty() + " " + episode.title.orEmpty(),
|
title = current.name.orEmpty() + " " + episode.title.orEmpty(),
|
||||||
cover = current.cover,
|
cover = current.cover,
|
||||||
playlistUrl = playlist.url,
|
playlistUrl = playlist.url,
|
||||||
)
|
).also { stream ->
|
||||||
|
currentCount += 1
|
||||||
|
callback(currentCount, total)
|
||||||
|
streamDao.insertOrReplace(stream)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}.also {
|
|
||||||
currentCount += 1
|
|
||||||
callback(currentCount, total)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
streamDao.compareAndUpdate(
|
logger.log("xtream: series +[${series.size}]")
|
||||||
strategy = pref.playlistStrategy,
|
|
||||||
url = playlist.url,
|
|
||||||
update = streams
|
|
||||||
)
|
|
||||||
logger.log("xtream: series +[${streams.size}]")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,6 +27,7 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.runtime.DisposableEffect
|
import androidx.compose.runtime.DisposableEffect
|
||||||
import androidx.compose.runtime.InternalComposeApi
|
import androidx.compose.runtime.InternalComposeApi
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
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.architecture.pref.LocalPref
|
||||||
import com.m3u.core.util.basic.title
|
import com.m3u.core.util.basic.title
|
||||||
import com.m3u.core.wrapper.Event
|
import com.m3u.core.wrapper.Event
|
||||||
|
import com.m3u.data.database.model.DataSource
|
||||||
import com.m3u.data.database.model.Stream
|
import com.m3u.data.database.model.Stream
|
||||||
import com.m3u.features.playlist.internal.PlaylistScreenImpl
|
import com.m3u.features.playlist.internal.PlaylistScreenImpl
|
||||||
import com.m3u.features.playlist.internal.TvPlaylistScreenImpl
|
import com.m3u.features.playlist.internal.TvPlaylistScreenImpl
|
||||||
@ -87,6 +89,14 @@ internal fun PlaylistRoute(
|
|||||||
val pinnedCategories by viewModel.pinnedCategories.collectAsStateWithLifecycle()
|
val pinnedCategories by viewModel.pinnedCategories.collectAsStateWithLifecycle()
|
||||||
val refreshing by viewModel.subscribingOrRefreshing.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 sorts = viewModel.sorts
|
||||||
val sort by viewModel.sort.collectAsStateWithLifecycle()
|
val sort by viewModel.sort.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
@ -179,6 +189,7 @@ internal fun PlaylistRoute(
|
|||||||
viewModel.onEvent(PlaylistEvent.SavePicture(it))
|
viewModel.onEvent(PlaylistEvent.SavePicture(it))
|
||||||
},
|
},
|
||||||
createShortcut = { viewModel.onEvent(PlaylistEvent.CreateShortcut(context, it)) },
|
createShortcut = { viewModel.onEvent(PlaylistEvent.CreateShortcut(context, it)) },
|
||||||
|
singleLineTitle = singleLineTitle,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.thenIf(!tv && pref.godMode) {
|
.thenIf(!tv && pref.godMode) {
|
||||||
@ -219,6 +230,7 @@ private fun PlaylistScreen(
|
|||||||
savePicture: (streamId: Int) -> Unit,
|
savePicture: (streamId: Int) -> Unit,
|
||||||
createShortcut: (streamId: Int) -> Unit,
|
createShortcut: (streamId: Int) -> Unit,
|
||||||
contentPadding: PaddingValues,
|
contentPadding: PaddingValues,
|
||||||
|
singleLineTitle: Boolean,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
val helper = LocalHelper.current
|
val helper = LocalHelper.current
|
||||||
@ -270,6 +282,7 @@ private fun PlaylistScreen(
|
|||||||
hide = hide,
|
hide = hide,
|
||||||
onSavePicture = savePicture,
|
onSavePicture = savePicture,
|
||||||
createShortcut = createShortcut,
|
createShortcut = createShortcut,
|
||||||
|
singleLineTitle = singleLineTitle,
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
@ -25,6 +25,7 @@ internal fun StreamGallery(
|
|||||||
streams: ImmutableList<Stream>,
|
streams: ImmutableList<Stream>,
|
||||||
zapping: Stream?,
|
zapping: Stream?,
|
||||||
recently: Boolean,
|
recently: Boolean,
|
||||||
|
singleLineTitle: Boolean,
|
||||||
onClick: (Stream) -> Unit,
|
onClick: (Stream) -> Unit,
|
||||||
onMenu: (Stream) -> Unit,
|
onMenu: (Stream) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
@ -41,7 +42,8 @@ internal fun StreamGallery(
|
|||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
onMenu = onMenu,
|
onMenu = onMenu,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
contentPadding = contentPadding
|
contentPadding = contentPadding,
|
||||||
|
singleLineTitle = singleLineTitle
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,6 +72,7 @@ private fun StreamGalleryImpl(
|
|||||||
streams: ImmutableList<Stream>,
|
streams: ImmutableList<Stream>,
|
||||||
zapping: Stream?,
|
zapping: Stream?,
|
||||||
recently: Boolean,
|
recently: Boolean,
|
||||||
|
singleLineTitle: Boolean,
|
||||||
onClick: (Stream) -> Unit,
|
onClick: (Stream) -> Unit,
|
||||||
onMenu: (Stream) -> Unit,
|
onMenu: (Stream) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
@ -97,6 +100,7 @@ private fun StreamGalleryImpl(
|
|||||||
onClick = { onClick(stream) },
|
onClick = { onClick(stream) },
|
||||||
onLongClick = { onMenu(stream) },
|
onLongClick = { onMenu(stream) },
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
singleLineTitle = singleLineTitle
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -55,7 +55,8 @@ internal fun StreamItem(
|
|||||||
zapping: Boolean,
|
zapping: Boolean,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
onLongClick: () -> Unit,
|
onLongClick: () -> Unit,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier,
|
||||||
|
singleLineTitle: Boolean = true
|
||||||
) {
|
) {
|
||||||
when (currentUiMode()) {
|
when (currentUiMode()) {
|
||||||
UiMode.Default -> {
|
UiMode.Default -> {
|
||||||
@ -65,7 +66,8 @@ internal fun StreamItem(
|
|||||||
zapping = zapping,
|
zapping = zapping,
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
onLongClick = onLongClick,
|
onLongClick = onLongClick,
|
||||||
modifier = modifier
|
modifier = modifier,
|
||||||
|
singleLineTitle = singleLineTitle
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,7 +78,8 @@ internal fun StreamItem(
|
|||||||
zapping = zapping,
|
zapping = zapping,
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
onLongClick = onLongClick,
|
onLongClick = onLongClick,
|
||||||
modifier = modifier
|
modifier = modifier,
|
||||||
|
singleLineTitle = singleLineTitle
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,6 +95,7 @@ private fun StreamItemImpl(
|
|||||||
onLongClick: () -> Unit,
|
onLongClick: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
zapping: Boolean = false,
|
zapping: Boolean = false,
|
||||||
|
singleLineTitle: Boolean = true
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val spacing = LocalSpacing.current
|
val spacing = LocalSpacing.current
|
||||||
@ -153,7 +157,7 @@ private fun StreamItemImpl(
|
|||||||
text = stream.title.trim(),
|
text = stream.title.trim(),
|
||||||
style = MaterialTheme.typography.titleSmall,
|
style = MaterialTheme.typography.titleSmall,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
maxLines = 1,
|
maxLines = if (singleLineTitle) 1 else Int.MAX_VALUE,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
)
|
)
|
||||||
if (recently) {
|
if (recently) {
|
||||||
@ -218,6 +222,7 @@ private fun CompactStreamItem(
|
|||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
onLongClick: () -> Unit,
|
onLongClick: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
singleLineTitle: Boolean = true
|
||||||
) {
|
) {
|
||||||
val spacing = LocalSpacing.current
|
val spacing = LocalSpacing.current
|
||||||
val favourite = stream.favourite
|
val favourite = stream.favourite
|
||||||
@ -242,7 +247,7 @@ private fun CompactStreamItem(
|
|||||||
style = MaterialTheme.typography.titleSmall,
|
style = MaterialTheme.typography.titleSmall,
|
||||||
fontSize = MaterialTheme.typography.titleSmall.fontSize,
|
fontSize = MaterialTheme.typography.titleSmall.fontSize,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
maxLines = 1,
|
maxLines = if (singleLineTitle) 1 else Int.MAX_VALUE,
|
||||||
fontWeight = FontWeight.Bold
|
fontWeight = FontWeight.Bold
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
@ -95,6 +95,7 @@ internal fun PlaylistScreenImpl(
|
|||||||
onSavePicture: (streamId: Int) -> Unit,
|
onSavePicture: (streamId: Int) -> Unit,
|
||||||
createShortcut: (streamId: Int) -> Unit,
|
createShortcut: (streamId: Int) -> Unit,
|
||||||
isAtTopState: MutableState<Boolean>,
|
isAtTopState: MutableState<Boolean>,
|
||||||
|
singleLineTitle: Boolean,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
contentPadding: PaddingValues = PaddingValues()
|
contentPadding: PaddingValues = PaddingValues()
|
||||||
) {
|
) {
|
||||||
@ -219,6 +220,7 @@ internal fun PlaylistScreenImpl(
|
|||||||
streams = categories[currentPage].streams,
|
streams = categories[currentPage].streams,
|
||||||
zapping = zapping,
|
zapping = zapping,
|
||||||
recently = sort == Sort.RECENTLY,
|
recently = sort == Sort.RECENTLY,
|
||||||
|
singleLineTitle = singleLineTitle,
|
||||||
onClick = onStream,
|
onClick = onStream,
|
||||||
contentPadding = inner,
|
contentPadding = inner,
|
||||||
onMenu = { dialogStatus = DialogStatus.Selections(it) },
|
onMenu = { dialogStatus = DialogStatus.Selections(it) },
|
||||||
|
Reference in New Issue
Block a user