Fix multiple language related issues
* fix playback for other shows with no language set in cr API * fix selection of preferred season for languages other than german * add support for all content languages to TMDBApiController * preferSecondary is now preferSubbed, this describes the function more clearly * remove jsoup, not used anymore
This commit is contained in:
		@ -59,14 +59,13 @@ dependencies {
 | 
			
		||||
    implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1'
 | 
			
		||||
 | 
			
		||||
    implementation 'com.google.android.material:material:1.4.0'
 | 
			
		||||
    implementation 'com.google.code.gson:gson:2.8.8'
 | 
			
		||||
    implementation 'com.google.code.gson:gson:2.8.8' // TODO remove, still used by metadb
 | 
			
		||||
    implementation 'com.google.android.exoplayer:exoplayer-core:2.15.0'
 | 
			
		||||
    implementation 'com.google.android.exoplayer:exoplayer-hls:2.15.0'
 | 
			
		||||
    implementation 'com.google.android.exoplayer:exoplayer-dash:2.15.0'
 | 
			
		||||
    implementation 'com.google.android.exoplayer:exoplayer-ui:2.15.0'
 | 
			
		||||
    implementation 'com.google.android.exoplayer:extension-mediasession:2.15.0'
 | 
			
		||||
 | 
			
		||||
    implementation 'org.jsoup:jsoup:1.14.2'
 | 
			
		||||
    implementation 'com.github.bumptech.glide:glide:4.12.0'
 | 
			
		||||
    implementation 'jp.wasabeef:glide-transformations:4.3.0'
 | 
			
		||||
    implementation 'com.afollestad.material-dialogs:core:3.3.0'
 | 
			
		||||
 | 
			
		||||
@ -224,22 +224,13 @@ data class Seasons(
 | 
			
		||||
    @SerialName("items") val items: List<Season>
 | 
			
		||||
) {
 | 
			
		||||
    fun getPreferredSeason(local: Locale): Season {
 | 
			
		||||
        // try to get the the first seasons which matches the preferred local
 | 
			
		||||
        items.forEach { season ->
 | 
			
		||||
            if (season.title.startsWith("(${local.language})", true)) {
 | 
			
		||||
                return season
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // if there is no season with the preferred local, try to find a subbed season
 | 
			
		||||
        items.forEach { season ->
 | 
			
		||||
            if (season.isSubbed) {
 | 
			
		||||
                return season
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // if there is no preferred language season and no sub, use the first season
 | 
			
		||||
        return items.first()
 | 
			
		||||
        return items.firstOrNull { season ->
 | 
			
		||||
            // try to get the the first seasons which matches the preferred local
 | 
			
		||||
            season.slugTitle.endsWith("${local.displayLanguage}-dub", true)
 | 
			
		||||
        } ?: items.firstOrNull { season ->
 | 
			
		||||
            // if there is no season with the preferred local, try to find a subbed season
 | 
			
		||||
            season.isSubbed
 | 
			
		||||
        } ?: items.first() // if no preferred language and no sub, use the first season
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -247,6 +238,7 @@ data class Seasons(
 | 
			
		||||
data class Season(
 | 
			
		||||
    @SerialName("id") val id: String,
 | 
			
		||||
    @SerialName("title") val title: String,
 | 
			
		||||
    @SerialName("slug_title") val slugTitle: String,
 | 
			
		||||
    @SerialName("series_id") val seriesId: String,
 | 
			
		||||
    @SerialName("season_number") val seasonNumber: Int,
 | 
			
		||||
    @SerialName("is_subbed") val isSubbed: Boolean,
 | 
			
		||||
@ -254,7 +246,7 @@ data class Season(
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
val NoneSeasons = Seasons(0, emptyList())
 | 
			
		||||
val NoneSeason = Season("", "", "", 0, isSubbed = false, isDubbed = false)
 | 
			
		||||
val NoneSeason = Season("", "", "", "", 0, isSubbed = false, isDubbed = false)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 | 
			
		||||
@ -8,10 +8,10 @@ import java.util.*
 | 
			
		||||
 | 
			
		||||
object Preferences {
 | 
			
		||||
 | 
			
		||||
    var preferSecondary = false
 | 
			
		||||
        internal set
 | 
			
		||||
    var preferredLocale: Locale = Locale.forLanguageTag("en-US") // TODO this should be saved (potential offline usage) but fetched on start
 | 
			
		||||
        internal set
 | 
			
		||||
    var preferSubbed = false
 | 
			
		||||
        internal set
 | 
			
		||||
    var autoplay = true
 | 
			
		||||
        internal set
 | 
			
		||||
    var devSettings = false
 | 
			
		||||
@ -26,15 +26,6 @@ object Preferences {
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun savePreferSecondary(context: Context, preferSecondary: Boolean) {
 | 
			
		||||
        with(getSharedPref(context).edit()) {
 | 
			
		||||
            putBoolean(context.getString(R.string.save_key_prefer_secondary), preferSecondary)
 | 
			
		||||
            apply()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.preferSecondary = preferSecondary
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun savePreferredLocal(context: Context, preferredLocale: Locale) {
 | 
			
		||||
        with(getSharedPref(context).edit()) {
 | 
			
		||||
            putString(context.getString(R.string.save_key_preferred_local), preferredLocale.toLanguageTag())
 | 
			
		||||
@ -44,6 +35,15 @@ object Preferences {
 | 
			
		||||
        this.preferredLocale = preferredLocale
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun savePreferSecondary(context: Context, preferSubbed: Boolean) {
 | 
			
		||||
        with(getSharedPref(context).edit()) {
 | 
			
		||||
            putBoolean(context.getString(R.string.save_key_prefer_secondary), preferSubbed)
 | 
			
		||||
            apply()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.preferSubbed = preferSubbed
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun saveAutoplay(context: Context, autoplay: Boolean) {
 | 
			
		||||
        with(getSharedPref(context).edit()) {
 | 
			
		||||
            putBoolean(context.getString(R.string.save_key_autoplay), autoplay)
 | 
			
		||||
@ -77,14 +77,14 @@ object Preferences {
 | 
			
		||||
    fun load(context: Context) {
 | 
			
		||||
        val sharedPref = getSharedPref(context)
 | 
			
		||||
 | 
			
		||||
        preferSecondary = sharedPref.getBoolean(
 | 
			
		||||
            context.getString(R.string.save_key_prefer_secondary), false
 | 
			
		||||
        )
 | 
			
		||||
        preferredLocale = Locale.forLanguageTag(
 | 
			
		||||
            sharedPref.getString(
 | 
			
		||||
                context.getString(R.string.save_key_preferred_local), "en-US"
 | 
			
		||||
            ) ?: "en-US"
 | 
			
		||||
        )
 | 
			
		||||
        preferSubbed = sharedPref.getBoolean(
 | 
			
		||||
            context.getString(R.string.save_key_prefer_secondary), false
 | 
			
		||||
        )
 | 
			
		||||
        autoplay = sharedPref.getBoolean(
 | 
			
		||||
            context.getString(R.string.save_key_autoplay), true
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
@ -113,9 +113,9 @@ class AboutFragment : Fragment() {
 | 
			
		||||
                "https://github.com/google/material-design-icons", License.APACHE2),
 | 
			
		||||
            ThirdPartyComponent("Material Dialogs", "", "Aidan Follestad",
 | 
			
		||||
                "https://github.com/afollestad/material-dialogs", License.APACHE2),
 | 
			
		||||
            ThirdPartyComponent("Jsoup", "2009 - 2020", "Jonathan Hedley",
 | 
			
		||||
                "https://jsoup.org/", License.MIT),
 | 
			
		||||
            ThirdPartyComponent("kotlinx.coroutines", "2016 - 2019", "JetBrains",
 | 
			
		||||
            ThirdPartyComponent("Ktor", "2014-2021", "JetBrains s.r.o and contributors",
 | 
			
		||||
                "https://ktor.io/", License.APACHE2),
 | 
			
		||||
            ThirdPartyComponent("kotlinx.coroutines", "2016-2021", "JetBrains s.r.o",
 | 
			
		||||
                "https://github.com/Kotlin/kotlinx.coroutines", License.APACHE2),
 | 
			
		||||
            ThirdPartyComponent("Glide", "2014", "Google Inc.",
 | 
			
		||||
                "https://github.com/bumptech/glide", License.BSD2),
 | 
			
		||||
 | 
			
		||||
@ -85,7 +85,7 @@ class AccountFragment : Fragment() {
 | 
			
		||||
                profile.await().preferredContentSubtitleLanguage
 | 
			
		||||
            ).displayLanguage
 | 
			
		||||
        }
 | 
			
		||||
        binding.switchSecondary.isChecked = Preferences.preferSecondary
 | 
			
		||||
        binding.switchSecondary.isChecked = Preferences.preferSubbed
 | 
			
		||||
        binding.switchAutoplay.isChecked = Preferences.autoplay
 | 
			
		||||
        binding.textThemeSelected.text = when (Preferences.theme) {
 | 
			
		||||
            Theme.DARK -> getString(R.string.theme_dark)
 | 
			
		||||
 | 
			
		||||
@ -51,9 +51,6 @@ class MediaFragment(private val mediaIdStr: String) : Fragment() {
 | 
			
		||||
 | 
			
		||||
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
 | 
			
		||||
        super.onViewCreated(view, savedInstanceState)
 | 
			
		||||
 | 
			
		||||
        println("onViewCreated")
 | 
			
		||||
 | 
			
		||||
        binding.frameLoading.visibility = View.VISIBLE
 | 
			
		||||
 | 
			
		||||
        // tab layout and pager
 | 
			
		||||
 | 
			
		||||
@ -47,8 +47,6 @@ import org.mosad.teapod.parser.crunchyroll.NoneEpisodes
 | 
			
		||||
import org.mosad.teapod.parser.crunchyroll.NonePlayback
 | 
			
		||||
import org.mosad.teapod.preferences.Preferences
 | 
			
		||||
import org.mosad.teapod.util.EpisodeMeta
 | 
			
		||||
import org.mosad.teapod.util.Meta
 | 
			
		||||
import org.mosad.teapod.util.TVShowMeta
 | 
			
		||||
import org.mosad.teapod.util.tmdb.TMDBTVSeason
 | 
			
		||||
import java.util.*
 | 
			
		||||
 | 
			
		||||
@ -64,12 +62,12 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application)
 | 
			
		||||
    private val mediaSession = MediaSessionCompat(application, "TEAPOD_PLAYER_SESSION")
 | 
			
		||||
 | 
			
		||||
    val currentEpisodeChangedListener = ArrayList<() -> Unit>()
 | 
			
		||||
    private val preferredLanguage = if (Preferences.preferSecondary) Locale.JAPANESE else Locale.GERMAN
 | 
			
		||||
    private var currentPlayhead: Long = 0
 | 
			
		||||
 | 
			
		||||
    // tmdb/meta data TODO currently not implemented for cr
 | 
			
		||||
    var mediaMeta: Meta? = null
 | 
			
		||||
        internal set
 | 
			
		||||
    // tmdb/meta data
 | 
			
		||||
    // TODO meta data currently not implemented for cr
 | 
			
		||||
//    var mediaMeta: Meta? = null
 | 
			
		||||
//        internal set
 | 
			
		||||
    var tmdbTVSeason: TMDBTVSeason? =null
 | 
			
		||||
        internal set
 | 
			
		||||
    var currentEpisodeMeta: EpisodeMeta? = null
 | 
			
		||||
@ -224,8 +222,9 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application)
 | 
			
		||||
                currentPlayback.streams.adaptive_hls[fallbackLocal.toLanguageTag()]?.url
 | 
			
		||||
            }
 | 
			
		||||
            else -> {
 | 
			
		||||
                // if no language tag is present use the first entry
 | 
			
		||||
                currentLanguage = Locale.ROOT
 | 
			
		||||
                currentPlayback.streams.adaptive_hls[Locale.ROOT.toLanguageTag()]?.url ?: ""
 | 
			
		||||
                currentPlayback.streams.adaptive_hls.entries.first().value.url
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        println("stream url: $url")
 | 
			
		||||
@ -267,25 +266,25 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application)
 | 
			
		||||
        return episodes.items.lastOrNull()?.id == currentEpisode.id
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun getEpisodeMetaByAoDMediaId(aodMediaId: Int): EpisodeMeta? {
 | 
			
		||||
        val meta = mediaMeta
 | 
			
		||||
        return if (meta is TVShowMeta) {
 | 
			
		||||
            meta.episodes.firstOrNull { it.aodMediaId == aodMediaId }
 | 
			
		||||
        } else {
 | 
			
		||||
            null
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // TODO reimplement for cr
 | 
			
		||||
    private suspend fun loadMediaMeta(aodId: Int): Meta? {
 | 
			
		||||
//    fun getEpisodeMetaByAoDMediaId(aodMediaId: Int): EpisodeMeta? {
 | 
			
		||||
//        val meta = mediaMeta
 | 
			
		||||
//        return if (meta is TVShowMeta) {
 | 
			
		||||
//            meta.episodes.firstOrNull { it.aodMediaId == aodMediaId }
 | 
			
		||||
//        } else {
 | 
			
		||||
//            null
 | 
			
		||||
//        }
 | 
			
		||||
//    }
 | 
			
		||||
//
 | 
			
		||||
//    private suspend fun loadMediaMeta(aodId: Int): Meta? {
 | 
			
		||||
//        return if (media.type == DataTypes.MediaType.TVSHOW) {
 | 
			
		||||
//            MetaDBController().getTVShowMetadata(aodId)
 | 
			
		||||
//        } else {
 | 
			
		||||
//            null
 | 
			
		||||
//        }
 | 
			
		||||
 | 
			
		||||
        return null
 | 
			
		||||
    }
 | 
			
		||||
//
 | 
			
		||||
//        return null
 | 
			
		||||
//    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Update the playhead of the current episode, if currentPosition > 1000ms.
 | 
			
		||||
 | 
			
		||||
@ -34,6 +34,7 @@ import kotlinx.coroutines.coroutineScope
 | 
			
		||||
import kotlinx.coroutines.invoke
 | 
			
		||||
import kotlinx.serialization.SerializationException
 | 
			
		||||
import kotlinx.serialization.json.Json
 | 
			
		||||
import org.mosad.teapod.preferences.Preferences
 | 
			
		||||
import org.mosad.teapod.util.concatenate
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@ -54,7 +55,6 @@ class TMDBApiController {
 | 
			
		||||
 | 
			
		||||
    private val apiUrl = "https://api.themoviedb.org/3"
 | 
			
		||||
    private val apiKey = "de959cf9c07a08b5ca7cb51cda9a40c2"
 | 
			
		||||
    private val language = "de"
 | 
			
		||||
 | 
			
		||||
    companion object{
 | 
			
		||||
        const val imageUrl = "https://image.tmdb.org/t/p/w500"
 | 
			
		||||
@ -65,7 +65,10 @@ class TMDBApiController {
 | 
			
		||||
        parameters: List<Pair<String, Any?>> = emptyList()
 | 
			
		||||
    ): T = coroutineScope {
 | 
			
		||||
        val path = "$apiUrl$endpoint"
 | 
			
		||||
        val params = concatenate(listOf("api_key" to apiKey, "language" to language), parameters)
 | 
			
		||||
        val params = concatenate(
 | 
			
		||||
            listOf("api_key" to apiKey, "language" to Preferences.preferredLocale.language),
 | 
			
		||||
            parameters
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        // TODO handle FileNotFoundException
 | 
			
		||||
        return@coroutineScope (Dispatchers.IO) {
 | 
			
		||||
 | 
			
		||||
@ -198,7 +198,7 @@
 | 
			
		||||
                        android:id="@+id/imageView3"
 | 
			
		||||
                        android:layout_width="wrap_content"
 | 
			
		||||
                        android:layout_height="wrap_content"
 | 
			
		||||
                        android:contentDescription="@string/settings_secondary"
 | 
			
		||||
                        android:contentDescription="@string/settings_prefer_subbed"
 | 
			
		||||
                        android:minWidth="48dp"
 | 
			
		||||
                        android:minHeight="48dp"
 | 
			
		||||
                        android:padding="9dp"
 | 
			
		||||
@ -225,7 +225,7 @@
 | 
			
		||||
                                android:layout_width="match_parent"
 | 
			
		||||
                                android:layout_height="wrap_content"
 | 
			
		||||
                                android:layout_weight="1"
 | 
			
		||||
                                android:text="@string/settings_secondary"
 | 
			
		||||
                                android:text="@string/settings_prefer_subbed"
 | 
			
		||||
                                android:textSize="16sp" />
 | 
			
		||||
 | 
			
		||||
                            <TextView
 | 
			
		||||
@ -234,7 +234,7 @@
 | 
			
		||||
                                android:layout_height="wrap_content"
 | 
			
		||||
                                android:layout_weight="1"
 | 
			
		||||
                                android:maxLines="2"
 | 
			
		||||
                                android:text="@string/settings_secondary_desc"
 | 
			
		||||
                                android:text="@string/settings_prefer_subbed_desc"
 | 
			
		||||
                                android:textColor="?textSecondary" />
 | 
			
		||||
                        </LinearLayout>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -42,8 +42,8 @@
 | 
			
		||||
    <string name="settings_content_language">Bevorzuge Inhaltssprache</string>
 | 
			
		||||
    <string name="settings_content_language_desc">Englisch</string>
 | 
			
		||||
    <string name="settings_content_language_none">Keine</string>
 | 
			
		||||
    <string name="settings_secondary">Bevorzuge Japanisch (OmU)</string>
 | 
			
		||||
    <string name="settings_secondary_desc">Japanisch verwenden, sofern vorhanden</string>
 | 
			
		||||
    <string name="settings_prefer_subbed">Bevorzuge OmU</string>
 | 
			
		||||
    <string name="settings_prefer_subbed_desc">Original Sprache verwenden, sofern vorhanden</string>
 | 
			
		||||
    <string name="settings_autoplay">Autoplay</string>
 | 
			
		||||
    <string name="settings_autoplay_desc">Nächste Episode automatisch abspielen</string>
 | 
			
		||||
    <string name="theme">Design</string>
 | 
			
		||||
 | 
			
		||||
@ -40,7 +40,7 @@
 | 
			
		||||
    <string name="component_poster_desc" translatable="false">episode poster</string>
 | 
			
		||||
    <string name="component_watched_desc" translatable="false">already watched</string>
 | 
			
		||||
 | 
			
		||||
    <!-- settings fragment -->
 | 
			
		||||
    <!-- account fragment -->
 | 
			
		||||
    <string name="account">Account</string>
 | 
			
		||||
    <string name="account_login_ex" translatable="false">user@example.com</string>
 | 
			
		||||
    <string name="account_login_desc">Tap to edit</string>
 | 
			
		||||
@ -50,8 +50,8 @@
 | 
			
		||||
    <string name="settings_content_language">Preferred content language</string>
 | 
			
		||||
    <string name="settings_content_language_desc">English</string>
 | 
			
		||||
    <string name="settings_content_language_none">None</string>
 | 
			
		||||
    <string name="settings_secondary">Prefer japanese (sub)</string>
 | 
			
		||||
    <string name="settings_secondary_desc">Use japanese, if present</string>
 | 
			
		||||
    <string name="settings_prefer_subbed">Prefer subbed</string>
 | 
			
		||||
    <string name="settings_prefer_subbed_desc">Use original language, if present</string>
 | 
			
		||||
    <string name="settings_autoplay">Autoplay</string>
 | 
			
		||||
    <string name="settings_autoplay_desc">Play next episode automatically</string>
 | 
			
		||||
    <string name="theme">Theme</string>
 | 
			
		||||
@ -131,6 +131,7 @@
 | 
			
		||||
    <string name="preference_file_key" translatable="false">org.mosad.teapod.preferences</string>
 | 
			
		||||
    <string name="save_key_user_login" translatable="false">org.mosad.teapod.user_login</string>
 | 
			
		||||
    <string name="save_key_user_password" translatable="false">org.mosad.teapod.user_password</string>
 | 
			
		||||
    <!-- for legacy reasons the prefer subbed key is called prefer_secondary-->
 | 
			
		||||
    <string name="save_key_prefer_secondary" translatable="false">org.mosad.teapod.prefer_secondary</string>
 | 
			
		||||
    <string name="save_key_preferred_local" translatable="false">org.mosad.teapod.preferred_local</string>
 | 
			
		||||
    <string name="save_key_autoplay" translatable="false">org.mosad.teapod.autoplay</string>
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user