mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 23:51:21 +02:00
Fix smart & default subtitle selection logic (#579)
## Description Fixes the logic for subtitle selection when using `Smart` or `Default` server modes. Also takes into account unknown languages. This is heavily inspired by [the server selection logic](https://github.com/jellyfin/jellyfin/blob/release-10.11.z/Emby.Server.Implementations/Library/MediaStreamSelector.cs). For better or for worse, Wholphin implements this client side. ### Related issues Fixes #570
This commit is contained in:
parent
fd1feddab3
commit
494ff07b72
6 changed files with 591 additions and 25 deletions
2
.github/workflows/pr.yml
vendored
2
.github/workflows/pr.yml
vendored
|
|
@ -43,7 +43,7 @@ jobs:
|
||||||
- name: Build app
|
- name: Build app
|
||||||
id: buildapp
|
id: buildapp
|
||||||
run: |
|
run: |
|
||||||
./gradlew clean assembleDebug --no-daemon
|
./gradlew clean assembleDebug testDebugUnitTest --no-daemon
|
||||||
apks=$(find app/build/outputs/apk -name '*.apk' -print0 | tr '\0' ',' | sed 's/,$//')
|
apks=$(find app/build/outputs/apk -name '*.apk' -print0 | tr '\0' ',' | sed 's/,$//')
|
||||||
echo "apks=$apks" >> "$GITHUB_OUTPUT"
|
echo "apks=$apks" >> "$GITHUB_OUTPUT"
|
||||||
- name: Tar build dirs
|
- name: Tar build dirs
|
||||||
|
|
|
||||||
|
|
@ -249,4 +249,7 @@ dependencies {
|
||||||
if (ffmpegModuleExists || isCI) {
|
if (ffmpegModuleExists || isCI) {
|
||||||
implementation(files("libs/lib-decoder-ffmpeg-release.aar"))
|
implementation(files("libs/lib-decoder-ffmpeg-release.aar"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
testImplementation(libs.mockk.android)
|
||||||
|
testImplementation(libs.mockk.agent)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,7 @@ class ItemPlaybackRepository
|
||||||
)
|
)
|
||||||
val subtitleStream =
|
val subtitleStream =
|
||||||
streamChoiceService.chooseSubtitleStream(
|
streamChoiceService.chooseSubtitleStream(
|
||||||
audioStream = audioStream,
|
audioStreamLang = audioStream?.language,
|
||||||
candidates =
|
candidates =
|
||||||
source.mediaStreams
|
source.mediaStreams
|
||||||
?.filter { it.type == MediaStreamType.SUBTITLE }
|
?.filter { it.type == MediaStreamType.SUBTITLE }
|
||||||
|
|
|
||||||
|
|
@ -153,7 +153,7 @@ class StreamChoiceService
|
||||||
return source.mediaStreams?.letNotEmpty { streams ->
|
return source.mediaStreams?.letNotEmpty { streams ->
|
||||||
val candidates = streams.filter { it.type == MediaStreamType.SUBTITLE }
|
val candidates = streams.filter { it.type == MediaStreamType.SUBTITLE }
|
||||||
chooseSubtitleStream(
|
chooseSubtitleStream(
|
||||||
audioStream,
|
audioStream?.language,
|
||||||
candidates,
|
candidates,
|
||||||
itemPlayback,
|
itemPlayback,
|
||||||
plc,
|
plc,
|
||||||
|
|
@ -163,7 +163,7 @@ class StreamChoiceService
|
||||||
}
|
}
|
||||||
|
|
||||||
fun chooseSubtitleStream(
|
fun chooseSubtitleStream(
|
||||||
audioStream: MediaStream?,
|
audioStreamLang: String?,
|
||||||
candidates: List<MediaStream>,
|
candidates: List<MediaStream>,
|
||||||
itemPlayback: ItemPlayback?,
|
itemPlayback: ItemPlayback?,
|
||||||
playbackLanguageChoice: PlaybackLanguageChoice?,
|
playbackLanguageChoice: PlaybackLanguageChoice?,
|
||||||
|
|
@ -198,48 +198,59 @@ class StreamChoiceService
|
||||||
userConfig?.subtitleMode ?: SubtitlePlaybackMode.DEFAULT
|
userConfig?.subtitleMode ?: SubtitlePlaybackMode.DEFAULT
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
val candidates =
|
||||||
|
candidates.sortedWith(
|
||||||
|
compareBy<MediaStream>(
|
||||||
|
{ it.isDefault },
|
||||||
|
{ !it.isForced && it.language.equals(subtitleLanguage, true) },
|
||||||
|
{ it.isForced && it.language.equals(subtitleLanguage, true) },
|
||||||
|
{ it.isForced && it.language.isUnknown },
|
||||||
|
{ it.isForced },
|
||||||
|
),
|
||||||
|
)
|
||||||
return when (subtitleMode) {
|
return when (subtitleMode) {
|
||||||
SubtitlePlaybackMode.ALWAYS -> {
|
SubtitlePlaybackMode.ALWAYS -> {
|
||||||
if (subtitleLanguage != null) {
|
if (subtitleLanguage.isNotNullOrBlank()) {
|
||||||
candidates.firstOrNull { it.language == subtitleLanguage }
|
candidates.firstOrNull {
|
||||||
|
it.language.equals(subtitleLanguage, true) ||
|
||||||
|
it.language.isUnknown
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
candidates.firstOrNull()
|
candidates.firstOrNull()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SubtitlePlaybackMode.ONLY_FORCED -> {
|
SubtitlePlaybackMode.ONLY_FORCED -> {
|
||||||
if (subtitleLanguage != null) {
|
if (subtitleLanguage.isNotNullOrBlank()) {
|
||||||
candidates.firstOrNull { it.language == subtitleLanguage && it.isForced }
|
candidates.firstOrNull { it.language == subtitleLanguage && it.isForced }
|
||||||
|
?: candidates.firstOrNull { it.language.isUnknown && it.isForced }
|
||||||
} else {
|
} else {
|
||||||
candidates.firstOrNull { it.isForced }
|
candidates.firstOrNull { it.isForced }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SubtitlePlaybackMode.SMART -> {
|
SubtitlePlaybackMode.SMART -> {
|
||||||
val audioLanguage = userConfig?.audioLanguagePreference
|
if (subtitleLanguage.isNotNullOrBlank()) {
|
||||||
val audioStreamLang = audioStream?.language
|
val audioLanguage = userConfig?.audioLanguagePreference
|
||||||
if (audioLanguage.isNotNullOrBlank() && audioStreamLang.isNotNullOrBlank() && audioLanguage != audioStreamLang) {
|
if (audioLanguage.isNotNullOrBlank() && audioLanguage != audioStreamLang) {
|
||||||
candidates.firstOrNull { it.language == subtitleLanguage }
|
candidates.firstOrNull { it.language == subtitleLanguage }
|
||||||
|
?: candidates.firstOrNull { it.language.isUnknown }
|
||||||
|
} else {
|
||||||
|
candidates.firstOrNull { it.isForced && it.language == subtitleLanguage }
|
||||||
|
?: candidates.firstOrNull { it.isForced && it.language.isUnknown }
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
null
|
candidates.firstOrNull { it.isForced }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SubtitlePlaybackMode.DEFAULT -> {
|
SubtitlePlaybackMode.DEFAULT -> {
|
||||||
subtitleLanguage?.let { lang ->
|
if (subtitleLanguage.isNotNullOrBlank()) {
|
||||||
// Find best track that is in the preferred language
|
candidates.firstOrNull { it.language == subtitleLanguage && (it.isDefault || it.isForced) }
|
||||||
(
|
?: candidates.firstOrNull { it.isDefault || it.isForced }
|
||||||
candidates.firstOrNull { it.isDefault && it.isForced && it.language == lang }
|
} else {
|
||||||
?: candidates.firstOrNull { it.isDefault && it.language == lang }
|
candidates.firstOrNull { it.isDefault || it.isForced }
|
||||||
?: candidates.firstOrNull { it.isForced && it.language == lang }
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
?: (
|
|
||||||
// If none in preferred language, just find the best track
|
|
||||||
candidates.firstOrNull { it.isDefault && it.isForced }
|
|
||||||
?: candidates.firstOrNull { it.isDefault }
|
|
||||||
?: candidates.firstOrNull { it.isForced }
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SubtitlePlaybackMode.NONE -> {
|
SubtitlePlaybackMode.NONE -> {
|
||||||
|
|
@ -249,3 +260,12 @@ class StreamChoiceService
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val String?.isUnknown: Boolean
|
||||||
|
get() =
|
||||||
|
this == null ||
|
||||||
|
this.equals("unknown", true) ||
|
||||||
|
this.equals("und", true) ||
|
||||||
|
this.equals("undetermined", true) ||
|
||||||
|
this.equals("mul", true) ||
|
||||||
|
this.equals("zxx", true)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,540 @@
|
||||||
|
package com.github.damontecres.wholphin.test
|
||||||
|
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import com.github.damontecres.wholphin.data.PlaybackLanguageChoiceDao
|
||||||
|
import com.github.damontecres.wholphin.data.ServerRepository
|
||||||
|
import com.github.damontecres.wholphin.data.model.ItemPlayback
|
||||||
|
import com.github.damontecres.wholphin.data.model.PlaybackLanguageChoice
|
||||||
|
import com.github.damontecres.wholphin.data.model.TrackIndex
|
||||||
|
import com.github.damontecres.wholphin.preferences.AppPreferences
|
||||||
|
import com.github.damontecres.wholphin.preferences.DefaultUserConfiguration
|
||||||
|
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||||
|
import com.github.damontecres.wholphin.services.StreamChoiceService
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.mockk
|
||||||
|
import org.jellyfin.sdk.model.UUID
|
||||||
|
import org.jellyfin.sdk.model.api.MediaStream
|
||||||
|
import org.jellyfin.sdk.model.api.MediaStreamType
|
||||||
|
import org.jellyfin.sdk.model.api.SubtitlePlaybackMode
|
||||||
|
import org.jellyfin.sdk.model.api.UserDto
|
||||||
|
import org.junit.Assert
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.junit.runners.Parameterized
|
||||||
|
|
||||||
|
@RunWith(Parameterized::class)
|
||||||
|
class TestStreamChoiceServiceBasic(
|
||||||
|
val input: TestInput,
|
||||||
|
) {
|
||||||
|
@Test
|
||||||
|
fun test() {
|
||||||
|
runTest(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmStatic
|
||||||
|
@Parameterized.Parameters(name = "{index}: {0}")
|
||||||
|
fun data(): Collection<TestInput> =
|
||||||
|
listOf(
|
||||||
|
TestInput(
|
||||||
|
null,
|
||||||
|
SubtitlePlaybackMode.NONE,
|
||||||
|
subtitles =
|
||||||
|
listOf(
|
||||||
|
subtitle(0, "eng", true),
|
||||||
|
subtitle(1, "spa", false),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TestInput(
|
||||||
|
1,
|
||||||
|
SubtitlePlaybackMode.NONE,
|
||||||
|
subtitles =
|
||||||
|
listOf(
|
||||||
|
subtitle(0, "eng", true),
|
||||||
|
subtitle(1, "spa", false),
|
||||||
|
),
|
||||||
|
plc = plc(subtitleLang = "spa"),
|
||||||
|
),
|
||||||
|
TestInput(
|
||||||
|
0,
|
||||||
|
SubtitlePlaybackMode.ALWAYS,
|
||||||
|
subtitles =
|
||||||
|
listOf(
|
||||||
|
subtitle(0, "eng", true),
|
||||||
|
subtitle(1, "spa", false),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TestInput(
|
||||||
|
1,
|
||||||
|
SubtitlePlaybackMode.ALWAYS,
|
||||||
|
subtitles =
|
||||||
|
listOf(
|
||||||
|
subtitle(0, "eng", true),
|
||||||
|
subtitle(1, "spa", false),
|
||||||
|
),
|
||||||
|
userSubtitleLang = "spa",
|
||||||
|
),
|
||||||
|
TestInput(
|
||||||
|
null,
|
||||||
|
SubtitlePlaybackMode.ALWAYS,
|
||||||
|
subtitles =
|
||||||
|
listOf(
|
||||||
|
subtitle(0, "eng", true),
|
||||||
|
subtitle(1, "spa", false),
|
||||||
|
),
|
||||||
|
plc = plc(subtitlesDisabled = true),
|
||||||
|
),
|
||||||
|
TestInput(
|
||||||
|
null,
|
||||||
|
SubtitlePlaybackMode.ALWAYS,
|
||||||
|
subtitles =
|
||||||
|
listOf(
|
||||||
|
subtitle(0, "eng", true),
|
||||||
|
subtitle(1, "spa", false),
|
||||||
|
),
|
||||||
|
itemPlayback = itemPlayback(subtitleIndex = TrackIndex.DISABLED),
|
||||||
|
),
|
||||||
|
TestInput(
|
||||||
|
0,
|
||||||
|
SubtitlePlaybackMode.ALWAYS,
|
||||||
|
subtitles =
|
||||||
|
listOf(
|
||||||
|
subtitle(0, "eng", true),
|
||||||
|
subtitle(1, "spa", false),
|
||||||
|
),
|
||||||
|
itemPlayback = itemPlayback(subtitleIndex = TrackIndex.UNSPECIFIED),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@RunWith(Parameterized::class)
|
||||||
|
class TestStreamChoiceServiceDefault(
|
||||||
|
val input: TestInput,
|
||||||
|
) {
|
||||||
|
@Test
|
||||||
|
fun test() {
|
||||||
|
runTest(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmStatic
|
||||||
|
@Parameterized.Parameters(name = "{index}: {0}")
|
||||||
|
fun data(): Collection<TestInput> =
|
||||||
|
listOf(
|
||||||
|
TestInput(
|
||||||
|
0,
|
||||||
|
SubtitlePlaybackMode.DEFAULT,
|
||||||
|
subtitles =
|
||||||
|
listOf(
|
||||||
|
subtitle(0, "eng", true),
|
||||||
|
subtitle(1, "spa", false),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TestInput(
|
||||||
|
0,
|
||||||
|
SubtitlePlaybackMode.DEFAULT,
|
||||||
|
subtitles =
|
||||||
|
listOf(
|
||||||
|
subtitle(0, "eng", true),
|
||||||
|
subtitle(1, "spa", false),
|
||||||
|
),
|
||||||
|
userSubtitleLang = null,
|
||||||
|
),
|
||||||
|
TestInput(
|
||||||
|
0,
|
||||||
|
SubtitlePlaybackMode.DEFAULT,
|
||||||
|
subtitles =
|
||||||
|
listOf(
|
||||||
|
subtitle(0, "eng", true),
|
||||||
|
subtitle(1, "spa", true),
|
||||||
|
),
|
||||||
|
userSubtitleLang = null,
|
||||||
|
),
|
||||||
|
TestInput(
|
||||||
|
1,
|
||||||
|
SubtitlePlaybackMode.DEFAULT,
|
||||||
|
subtitles =
|
||||||
|
listOf(
|
||||||
|
subtitle(0, "eng", false),
|
||||||
|
subtitle(1, "spa", true),
|
||||||
|
),
|
||||||
|
userSubtitleLang = null,
|
||||||
|
),
|
||||||
|
TestInput(
|
||||||
|
null,
|
||||||
|
SubtitlePlaybackMode.DEFAULT,
|
||||||
|
subtitles =
|
||||||
|
listOf(
|
||||||
|
subtitle(0, "eng", false),
|
||||||
|
subtitle(1, "spa", false),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TestInput(
|
||||||
|
0,
|
||||||
|
SubtitlePlaybackMode.DEFAULT,
|
||||||
|
subtitles =
|
||||||
|
listOf(
|
||||||
|
subtitle(0, "eng", forced = true),
|
||||||
|
subtitle(1, "spa", false),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TestInput(
|
||||||
|
1,
|
||||||
|
SubtitlePlaybackMode.DEFAULT,
|
||||||
|
subtitles =
|
||||||
|
listOf(
|
||||||
|
subtitle(0, "eng", true),
|
||||||
|
subtitle(1, "spa", false),
|
||||||
|
),
|
||||||
|
itemPlayback = itemPlayback(subtitleIndex = 1),
|
||||||
|
),
|
||||||
|
TestInput(
|
||||||
|
1,
|
||||||
|
SubtitlePlaybackMode.DEFAULT,
|
||||||
|
subtitles =
|
||||||
|
listOf(
|
||||||
|
subtitle(0, "eng", forced = true),
|
||||||
|
subtitle(1, "spa", false),
|
||||||
|
),
|
||||||
|
plc = plc(subtitleLang = "spa"),
|
||||||
|
),
|
||||||
|
TestInput(
|
||||||
|
1,
|
||||||
|
SubtitlePlaybackMode.DEFAULT,
|
||||||
|
subtitles =
|
||||||
|
listOf(
|
||||||
|
subtitle(0, "eng", true),
|
||||||
|
subtitle(1, "spa", false),
|
||||||
|
),
|
||||||
|
plc = plc(subtitleLang = "spa"),
|
||||||
|
),
|
||||||
|
TestInput(
|
||||||
|
null,
|
||||||
|
SubtitlePlaybackMode.DEFAULT,
|
||||||
|
subtitles =
|
||||||
|
listOf(
|
||||||
|
subtitle(0, "eng", true),
|
||||||
|
subtitle(1, "spa", false),
|
||||||
|
),
|
||||||
|
plc = plc(subtitlesDisabled = true),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@RunWith(Parameterized::class)
|
||||||
|
class TestStreamChoiceServiceSmart(
|
||||||
|
val input: TestInput,
|
||||||
|
) {
|
||||||
|
@Test
|
||||||
|
fun test() {
|
||||||
|
runTest(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmStatic
|
||||||
|
@Parameterized.Parameters(name = "{index}: {0}")
|
||||||
|
fun data(): Collection<TestInput> =
|
||||||
|
listOf(
|
||||||
|
TestInput(
|
||||||
|
0,
|
||||||
|
SubtitlePlaybackMode.SMART,
|
||||||
|
subtitles =
|
||||||
|
listOf(
|
||||||
|
subtitle(0, "eng", true),
|
||||||
|
subtitle(1, "spa", false),
|
||||||
|
),
|
||||||
|
streamAudioLang = null,
|
||||||
|
),
|
||||||
|
TestInput(
|
||||||
|
null,
|
||||||
|
SubtitlePlaybackMode.SMART,
|
||||||
|
subtitles =
|
||||||
|
listOf(
|
||||||
|
subtitle(0, "eng", true),
|
||||||
|
subtitle(1, "spa", false),
|
||||||
|
),
|
||||||
|
streamAudioLang = "eng",
|
||||||
|
),
|
||||||
|
TestInput(
|
||||||
|
null,
|
||||||
|
SubtitlePlaybackMode.SMART,
|
||||||
|
subtitles =
|
||||||
|
listOf(
|
||||||
|
subtitle(0, "eng", false),
|
||||||
|
subtitle(1, "spa", false),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TestInput(
|
||||||
|
null,
|
||||||
|
SubtitlePlaybackMode.SMART,
|
||||||
|
subtitles =
|
||||||
|
listOf(
|
||||||
|
subtitle(0, "eng", false),
|
||||||
|
subtitle(1, "spa", false),
|
||||||
|
),
|
||||||
|
streamAudioLang = "eng",
|
||||||
|
),
|
||||||
|
TestInput(
|
||||||
|
0,
|
||||||
|
SubtitlePlaybackMode.SMART,
|
||||||
|
subtitles =
|
||||||
|
listOf(
|
||||||
|
subtitle(0, "eng", false),
|
||||||
|
subtitle(1, "spa", false),
|
||||||
|
),
|
||||||
|
streamAudioLang = "spa",
|
||||||
|
),
|
||||||
|
TestInput(
|
||||||
|
0,
|
||||||
|
SubtitlePlaybackMode.SMART,
|
||||||
|
subtitles =
|
||||||
|
listOf(
|
||||||
|
subtitle(0, "eng", forced = true),
|
||||||
|
subtitle(1, "spa", false),
|
||||||
|
),
|
||||||
|
streamAudioLang = "eng",
|
||||||
|
),
|
||||||
|
TestInput(
|
||||||
|
1,
|
||||||
|
SubtitlePlaybackMode.SMART,
|
||||||
|
subtitles =
|
||||||
|
listOf(
|
||||||
|
subtitle(0, "eng", false),
|
||||||
|
subtitle(1, "spa", false),
|
||||||
|
),
|
||||||
|
streamAudioLang = "spa",
|
||||||
|
plc = plc(subtitleLang = "spa"),
|
||||||
|
),
|
||||||
|
TestInput(
|
||||||
|
null,
|
||||||
|
SubtitlePlaybackMode.SMART,
|
||||||
|
subtitles =
|
||||||
|
listOf(
|
||||||
|
subtitle(0, "eng", false),
|
||||||
|
subtitle(1, "spa", false),
|
||||||
|
),
|
||||||
|
streamAudioLang = "spa",
|
||||||
|
plc = plc(subtitlesDisabled = true),
|
||||||
|
),
|
||||||
|
TestInput(
|
||||||
|
1,
|
||||||
|
SubtitlePlaybackMode.SMART,
|
||||||
|
subtitles =
|
||||||
|
listOf(
|
||||||
|
subtitle(0, "eng", true),
|
||||||
|
subtitle(1, "spa", false),
|
||||||
|
),
|
||||||
|
streamAudioLang = "eng",
|
||||||
|
userSubtitleLang = "spa",
|
||||||
|
userAudioLang = "spa",
|
||||||
|
),
|
||||||
|
TestInput(
|
||||||
|
null,
|
||||||
|
SubtitlePlaybackMode.SMART,
|
||||||
|
subtitles =
|
||||||
|
listOf(
|
||||||
|
subtitle(0, "eng", true),
|
||||||
|
subtitle(1, "spa", false),
|
||||||
|
),
|
||||||
|
streamAudioLang = "eng",
|
||||||
|
userSubtitleLang = "spa",
|
||||||
|
userAudioLang = "eng",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@RunWith(Parameterized::class)
|
||||||
|
class TestStreamChoiceServiceOnlyForced(
|
||||||
|
val input: TestInput,
|
||||||
|
) {
|
||||||
|
@Test
|
||||||
|
fun test() {
|
||||||
|
runTest(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmStatic
|
||||||
|
@Parameterized.Parameters(name = "{index}: {0}")
|
||||||
|
fun data(): Collection<TestInput> =
|
||||||
|
listOf(
|
||||||
|
TestInput(
|
||||||
|
null,
|
||||||
|
SubtitlePlaybackMode.ONLY_FORCED,
|
||||||
|
subtitles =
|
||||||
|
listOf(
|
||||||
|
subtitle(0, "eng", forced = false),
|
||||||
|
subtitle(1, "spa", forced = false),
|
||||||
|
),
|
||||||
|
streamAudioLang = "eng",
|
||||||
|
),
|
||||||
|
TestInput(
|
||||||
|
1,
|
||||||
|
SubtitlePlaybackMode.ONLY_FORCED,
|
||||||
|
subtitles =
|
||||||
|
listOf(
|
||||||
|
subtitle(0, "eng", forced = false),
|
||||||
|
subtitle(1, "spa", forced = false),
|
||||||
|
),
|
||||||
|
streamAudioLang = "eng",
|
||||||
|
itemPlayback = itemPlayback(subtitleIndex = 1),
|
||||||
|
),
|
||||||
|
TestInput(
|
||||||
|
0,
|
||||||
|
SubtitlePlaybackMode.ONLY_FORCED,
|
||||||
|
subtitles =
|
||||||
|
listOf(
|
||||||
|
subtitle(0, "eng", forced = false),
|
||||||
|
subtitle(1, "spa", forced = false),
|
||||||
|
),
|
||||||
|
streamAudioLang = "eng",
|
||||||
|
plc = plc(subtitleLang = "eng"),
|
||||||
|
),
|
||||||
|
TestInput(
|
||||||
|
null,
|
||||||
|
SubtitlePlaybackMode.ONLY_FORCED,
|
||||||
|
subtitles =
|
||||||
|
listOf(
|
||||||
|
subtitle(0, "eng", forced = false),
|
||||||
|
subtitle(1, "spa", forced = false),
|
||||||
|
),
|
||||||
|
streamAudioLang = "eng",
|
||||||
|
plc = plc(subtitlesDisabled = true),
|
||||||
|
),
|
||||||
|
TestInput(
|
||||||
|
0,
|
||||||
|
SubtitlePlaybackMode.ONLY_FORCED,
|
||||||
|
subtitles =
|
||||||
|
listOf(
|
||||||
|
subtitle(0, "eng", forced = true),
|
||||||
|
subtitle(1, "spa", forced = false),
|
||||||
|
),
|
||||||
|
streamAudioLang = "eng",
|
||||||
|
),
|
||||||
|
TestInput(
|
||||||
|
null,
|
||||||
|
SubtitlePlaybackMode.ONLY_FORCED,
|
||||||
|
subtitles =
|
||||||
|
listOf(
|
||||||
|
subtitle(0, "eng", forced = false),
|
||||||
|
subtitle(1, "spa", forced = true),
|
||||||
|
),
|
||||||
|
streamAudioLang = "eng",
|
||||||
|
),
|
||||||
|
TestInput(
|
||||||
|
1,
|
||||||
|
SubtitlePlaybackMode.ONLY_FORCED,
|
||||||
|
userSubtitleLang = null,
|
||||||
|
subtitles =
|
||||||
|
listOf(
|
||||||
|
subtitle(0, "eng", forced = false),
|
||||||
|
subtitle(1, "spa", forced = true),
|
||||||
|
),
|
||||||
|
streamAudioLang = "eng",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class TestInput(
|
||||||
|
val expectedIndex: Int?,
|
||||||
|
val userSubtitleMode: SubtitlePlaybackMode?,
|
||||||
|
val userAudioLang: String? = "eng",
|
||||||
|
val userSubtitleLang: String? = "eng",
|
||||||
|
val streamAudioLang: String? = "eng",
|
||||||
|
val subtitles: List<MediaStream>,
|
||||||
|
val itemPlayback: ItemPlayback? = null,
|
||||||
|
val plc: PlaybackLanguageChoice? = null,
|
||||||
|
) {
|
||||||
|
override fun toString(): String = "test(mode=$userSubtitleMode, subtitles=${subtitles.map { it.toShortString() }})"
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun MediaStream.toShortString(): String = "$type(index=$index, lang=$language, default=$isDefault, forced=$isForced)"
|
||||||
|
|
||||||
|
private fun serverRepo(
|
||||||
|
audioLang: String?,
|
||||||
|
subtitleMode: SubtitlePlaybackMode?,
|
||||||
|
subtitleLang: String?,
|
||||||
|
): ServerRepository {
|
||||||
|
val mocked = mockk<ServerRepository>()
|
||||||
|
every { mocked.currentUserDto } returns
|
||||||
|
MutableLiveData(
|
||||||
|
UserDto(
|
||||||
|
id = UUID.randomUUID(),
|
||||||
|
hasPassword = true,
|
||||||
|
hasConfiguredPassword = true,
|
||||||
|
hasConfiguredEasyPassword = true,
|
||||||
|
configuration =
|
||||||
|
DefaultUserConfiguration.copy(
|
||||||
|
audioLanguagePreference = audioLang,
|
||||||
|
subtitleMode = subtitleMode ?: SubtitlePlaybackMode.DEFAULT,
|
||||||
|
subtitleLanguagePreference = subtitleLang,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
return mocked
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun runTest(input: TestInput) {
|
||||||
|
val service =
|
||||||
|
StreamChoiceService(
|
||||||
|
serverRepo(input.userAudioLang, input.userSubtitleMode, input.userSubtitleLang),
|
||||||
|
mockk<PlaybackLanguageChoiceDao>(),
|
||||||
|
)
|
||||||
|
val result =
|
||||||
|
service.chooseSubtitleStream(
|
||||||
|
audioStreamLang = input.streamAudioLang,
|
||||||
|
candidates = input.subtitles,
|
||||||
|
itemPlayback = input.itemPlayback,
|
||||||
|
playbackLanguageChoice = input.plc,
|
||||||
|
prefs = UserPreferences(AppPreferences.getDefaultInstance()),
|
||||||
|
)
|
||||||
|
Assert.assertEquals(input.expectedIndex, result?.index)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun subtitle(
|
||||||
|
index: Int,
|
||||||
|
lang: String?,
|
||||||
|
default: Boolean = false,
|
||||||
|
forced: Boolean = false,
|
||||||
|
): MediaStream =
|
||||||
|
MediaStream(
|
||||||
|
type = MediaStreamType.SUBTITLE,
|
||||||
|
language = lang,
|
||||||
|
isDefault = default,
|
||||||
|
isForced = forced,
|
||||||
|
isHearingImpaired = false,
|
||||||
|
isInterlaced = false,
|
||||||
|
index = index,
|
||||||
|
isExternal = false,
|
||||||
|
isTextSubtitleStream = true,
|
||||||
|
supportsExternalStream = true,
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun itemPlayback(
|
||||||
|
audioIndex: Int = TrackIndex.UNSPECIFIED,
|
||||||
|
subtitleIndex: Int = TrackIndex.UNSPECIFIED,
|
||||||
|
): ItemPlayback =
|
||||||
|
ItemPlayback(
|
||||||
|
rowId = 1,
|
||||||
|
userId = 1,
|
||||||
|
itemId = UUID.randomUUID(),
|
||||||
|
sourceId = UUID.randomUUID(),
|
||||||
|
audioIndex = audioIndex,
|
||||||
|
subtitleIndex = subtitleIndex,
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun plc(
|
||||||
|
audioLang: String? = null,
|
||||||
|
subtitleLang: String? = null,
|
||||||
|
subtitlesDisabled: Boolean? = if (subtitleLang != null) false else null,
|
||||||
|
): PlaybackLanguageChoice =
|
||||||
|
PlaybackLanguageChoice(
|
||||||
|
userId = 1,
|
||||||
|
seriesId = UUID.randomUUID(),
|
||||||
|
audioLanguage = audioLang,
|
||||||
|
subtitleLanguage = subtitleLang,
|
||||||
|
subtitlesDisabled = subtitlesDisabled,
|
||||||
|
)
|
||||||
|
|
@ -11,6 +11,7 @@ ksp = "2.3.0"
|
||||||
coreKtx = "1.17.0"
|
coreKtx = "1.17.0"
|
||||||
appcompat = "1.7.1"
|
appcompat = "1.7.1"
|
||||||
composeBom = "2025.12.01"
|
composeBom = "2025.12.01"
|
||||||
|
mockk = "1.14.7"
|
||||||
multiplatformMarkdownRenderer = "0.38.1"
|
multiplatformMarkdownRenderer = "0.38.1"
|
||||||
programguide = "1.6.0"
|
programguide = "1.6.0"
|
||||||
slf4j2Timber = "1.2"
|
slf4j2Timber = "1.2"
|
||||||
|
|
@ -64,6 +65,8 @@ auto-service-ksp = { module = "dev.zacsweers.autoservice:auto-service-ksp", vers
|
||||||
desugar_jdk_libs = { module = "com.android.tools:desugar_jdk_libs", version.ref = "desugar_jdk_libs" }
|
desugar_jdk_libs = { module = "com.android.tools:desugar_jdk_libs", version.ref = "desugar_jdk_libs" }
|
||||||
hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hilt" }
|
hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hilt" }
|
||||||
hilt-android-compiler = { module = "com.google.dagger:hilt-android-compiler", version.ref = "hilt" }
|
hilt-android-compiler = { module = "com.google.dagger:hilt-android-compiler", version.ref = "hilt" }
|
||||||
|
mockk-agent = { module = "io.mockk:mockk-agent", version.ref = "mockk" }
|
||||||
|
mockk-android = { module = "io.mockk:mockk-android", version.ref = "mockk" }
|
||||||
multiplatform-markdown-renderer = { module = "com.mikepenz:multiplatform-markdown-renderer", version.ref = "multiplatformMarkdownRenderer" }
|
multiplatform-markdown-renderer = { module = "com.mikepenz:multiplatform-markdown-renderer", version.ref = "multiplatformMarkdownRenderer" }
|
||||||
multiplatform-markdown-renderer-m3 = { module = "com.mikepenz:multiplatform-markdown-renderer-m3", version.ref = "multiplatformMarkdownRenderer" }
|
multiplatform-markdown-renderer-m3 = { module = "com.mikepenz:multiplatform-markdown-renderer-m3", version.ref = "multiplatformMarkdownRenderer" }
|
||||||
programguide = { module = "io.github.oleksandrbalan:programguide", version.ref = "programguide" }
|
programguide = { module = "io.github.oleksandrbalan:programguide", version.ref = "programguide" }
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue