feat: enhance track label formatting for audio, subtitles, and video

This commit is contained in:
2026-04-26 13:15:17 +02:00
parent 01de2ca19c
commit f086956578

View File

@@ -7,6 +7,7 @@ import androidx.media3.common.Tracks
import androidx.media3.common.util.UnstableApi
import hu.bbara.purefin.player.model.TrackOption
import hu.bbara.purefin.player.model.TrackType
import java.util.Locale
import javax.inject.Inject
data class TrackSelectionState(
@@ -35,10 +36,7 @@ class TrackMapper @Inject constructor() {
repeat(group.length) { trackIndex ->
val format = group.getTrackFormat(trackIndex)
val id = "a_${groupIndex}_${trackIndex}"
val label = format.label
?: format.language
?: "${format.channelCount}ch"
?: "Audio ${trackIndex}"
val label = formatAudioLabel(format, trackIndex)
val option = TrackOption(
id = id,
label = label,
@@ -60,10 +58,8 @@ class TrackMapper @Inject constructor() {
repeat(group.length) { trackIndex ->
val format = group.getTrackFormat(trackIndex)
val id = "t_${groupIndex}_${trackIndex}"
val label = format.label
?: format.language
?: "Subtitle ${trackIndex}"
val isForced = (format.selectionFlags and C.SELECTION_FLAG_FORCED) != 0
val label = formatTextLabel(format, trackIndex, isForced)
val option = TrackOption(
id = id,
label = label,
@@ -86,8 +82,7 @@ class TrackMapper @Inject constructor() {
repeat(group.length) { trackIndex ->
val format = group.getTrackFormat(trackIndex)
val id = "v_${groupIndex}_${trackIndex}"
val res = if (format.height != Format.NO_VALUE) "${format.height}p" else null
val label = res ?: format.label ?: "Video ${trackIndex}"
val label = formatVideoLabel(format, trackIndex)
val option = TrackOption(
id = id,
label = label,
@@ -134,4 +129,122 @@ class TrackMapper @Inject constructor() {
selectedVideoTrackId = selectedVideo
)
}
private fun formatAudioLabel(format: Format, trackIndex: Int): String {
val details = listOfNotNull(
displayLanguage(format.language),
audioCodec(format),
channelLabel(format.channelCount)
).joinToString(" ")
return labelWithDetails(
title = format.label,
details = details,
fallback = "Audio ${trackIndex + 1}"
)
}
private fun formatTextLabel(format: Format, trackIndex: Int, isForced: Boolean): String {
val details = listOfNotNull(
displayLanguage(format.language),
subtitleCodec(format),
"Forced".takeIf { isForced }
).joinToString(" ")
return labelWithDetails(
title = format.label,
details = details,
fallback = "Subtitle ${trackIndex + 1}"
)
}
private fun formatVideoLabel(format: Format, trackIndex: Int): String {
val details = if (format.height != Format.NO_VALUE && format.height > 0) {
"${format.height}p"
} else {
null
}
return labelWithDetails(
title = format.label,
details = details,
fallback = "Video ${trackIndex + 1}"
)
}
private fun labelWithDetails(title: String?, details: String?, fallback: String): String {
val cleanTitle = title?.trim()?.takeIf { it.isNotEmpty() }
val cleanDetails = details?.trim()?.takeIf { it.isNotEmpty() }
return when {
cleanTitle != null && cleanDetails != null && cleanTitle != cleanDetails ->
"$cleanTitle - $cleanDetails"
cleanTitle != null -> cleanTitle
cleanDetails != null -> cleanDetails
else -> fallback
}
}
private fun displayLanguage(code: String?): String? {
val cleanCode = code
?.trim()
?.takeIf { it.isNotEmpty() && !it.equals("und", ignoreCase = true) }
?: return null
val locale = Locale.forLanguageTag(cleanCode.replace('_', '-'))
val language = locale.getDisplayLanguage(Locale.getDefault())
return language.takeIf { it.isNotEmpty() && it != cleanCode } ?: cleanCode
}
private fun audioCodec(format: Format): String? {
val value = format.codecText()
return when {
value.contains("truehd") -> "TrueHD"
value.contains("eac3") || value.contains("e-ac-3") || value.contains("ec-3") ->
"Dolby Digital Plus"
value.contains("ac3") || value.contains("ac-3") -> "Dolby Digital"
value.contains("dts") || value.contains("dca") -> "DTS"
value.contains("alac") -> "ALAC"
value.contains("flac") -> "FLAC"
value.contains("opus") -> "Opus"
value.contains("vorbis") || value.contains("ogg") -> "Vorbis"
value.contains("mp3") || value.contains("mpeg") -> "MP3"
value.contains("aac") || value.contains("mp4a") -> "AAC"
value.contains("pcm") || value.contains("raw") || value.contains("lpcm") -> "PCM"
else -> null
}
}
private fun subtitleCodec(format: Format): String? {
val value = format.codecText()
return when {
value.contains("subrip") || value.contains("srt") -> "SRT"
value.contains("ssa") || value.contains("ass") -> "ASS"
value.contains("pgs") || value.contains("pgssub") -> "PGS"
value.contains("dvb") -> "DVB"
value.contains("dvd") || value.contains("vobsub") -> "DVD"
value.contains("vtt") || value.contains("webvtt") -> "VTT"
else -> null
}
}
private fun Format.codecText(): String = listOfNotNull(
sampleMimeType,
containerMimeType,
codecs
).joinToString(" ").lowercase(Locale.US)
private fun channelLabel(channelCount: Int): String? =
when {
channelCount == Format.NO_VALUE || channelCount <= 0 -> null
channelCount == 1 -> "Mono"
channelCount == 2 -> "Stereo"
channelCount == 6 -> "5.1"
channelCount == 8 -> "7.1"
else -> "$channelCount ch"
}
}