diff --git a/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchAdapter.kt b/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchAdapter.kt index f3fc4fe7..b206bc94 100644 --- a/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchAdapter.kt +++ b/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchAdapter.kt @@ -489,7 +489,7 @@ class AnimeWatchAdapter( val adapter = ArrayAdapter( fragment.requireContext(), R.layout.item_dropdown, - parser.extension.sources.map { LanguageMapper.mapLanguageCodeToName(it.lang) } + parser.extension.sources.map { LanguageMapper.getLanguageCode(it.lang) } ) val items = adapter.count diff --git a/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchFragment.kt b/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchFragment.kt index 7a3d993e..eb4d0b74 100644 --- a/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchFragment.kt +++ b/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchFragment.kt @@ -371,7 +371,7 @@ class AnimeWatchFragment : Fragment() { var selectedSetting = allSettings[0] if (allSettings.size > 1) { val names = - allSettings.map { LanguageMapper.mapLanguageCodeToName(it.lang) }.toTypedArray() + allSettings.map { LanguageMapper.getLanguageCode(it.lang) }.toTypedArray() val dialog = AlertDialog.Builder(requireContext(), R.style.MyPopup) .setTitle("Select a Source") .setSingleChoiceItems(names, -1) { dialog, which -> diff --git a/app/src/main/java/ani/dantotsu/media/anime/ExoplayerView.kt b/app/src/main/java/ani/dantotsu/media/anime/ExoplayerView.kt index 405dd189..a1efab44 100644 --- a/app/src/main/java/ani/dantotsu/media/anime/ExoplayerView.kt +++ b/app/src/main/java/ani/dantotsu/media/anime/ExoplayerView.kt @@ -82,7 +82,9 @@ import androidx.media3.datasource.cache.CacheDataSource import androidx.media3.datasource.okhttp.OkHttpDataSource import androidx.media3.exoplayer.DefaultLoadControl import androidx.media3.exoplayer.ExoPlayer +import androidx.media3.exoplayer.hls.HlsMediaSource import androidx.media3.exoplayer.source.DefaultMediaSourceFactory +import androidx.media3.exoplayer.source.MergingMediaSource import androidx.media3.exoplayer.trackselection.DefaultTrackSelector import androidx.media3.exoplayer.util.EventLogger import androidx.media3.session.MediaSession @@ -126,6 +128,7 @@ import ani.dantotsu.media.SubtitleDownloader import ani.dantotsu.okHttpClient import ani.dantotsu.others.AniSkip import ani.dantotsu.others.AniSkip.getType +import ani.dantotsu.others.LanguageMapper import ani.dantotsu.others.ResettableTimer import ani.dantotsu.others.getSerialized import ani.dantotsu.parsers.AnimeSources @@ -190,6 +193,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL private lateinit var cacheFactory: CacheDataSource.Factory private lateinit var playbackParameters: PlaybackParameters private lateinit var mediaItem: MediaItem + private lateinit var mediaSource: MergingMediaSource private var mediaSession: MediaSession? = null private lateinit var binding: ActivityExoplayerBinding @@ -223,6 +227,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL private var downloadId: String? = null private var hasExtSubtitles = false + private var audioLanguages = mutableListOf() companion object { var initialized = false @@ -1484,9 +1489,9 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL val titleName = ext.server.name.split("/").first() val episodeName = ext.server.name.split("/").last() downloadId = PrefManager.getAnimeDownloadPreferences() - .getString("$titleName - $episodeName", null) ?: - PrefManager.getAnimeDownloadPreferences() - .getString(ext.server.name, null) + .getString("$titleName - $episodeName", null) + ?: PrefManager.getAnimeDownloadPreferences() + .getString(ext.server.name, null) val exoItem = if (downloadId != null) { Helper.downloadManager(this) .downloadIndex.getDownload(downloadId!!)?.request?.toMediaItem() @@ -1540,6 +1545,32 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL } } + val audioMediaItem = mutableListOf() + audioLanguages.clear() + ext.audioTracks.forEach { + val code = LanguageMapper.getLanguageCode(it.lang) + audioLanguages.add(code) + audioMediaItem.add( + MediaItem.Builder() + .setUri(it.url) + .setMimeType(MimeTypes.AUDIO_UNKNOWN) + .setTag(code) + .build() + ) + } + + val audioSources = audioMediaItem.map { mediaItem -> + if (mediaItem.localConfiguration?.uri.toString().endsWith(".m3u8")) { + HlsMediaSource.Factory(cacheFactory).createMediaSource(mediaItem) + } else { + DefaultMediaSourceFactory(cacheFactory).createMediaSource(mediaItem) + } + }.toTypedArray() + val videoMediaSource = DefaultMediaSourceFactory(cacheFactory) + .createMediaSource(mediaItem) + mediaSource = MergingMediaSource(videoMediaSource, *audioSources) + + //Source exoSource.setOnClickListener { sourceClick() @@ -1615,7 +1646,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL .build().apply { playWhenReady = true this.playbackParameters = this@ExoplayerView.playbackParameters - setMediaItem(mediaItem) + setMediaSource(mediaSource) prepare() PrefManager.getCustomVal( "${media.id}_${media.anime!!.selectedEpisode}_max", @@ -1953,7 +1984,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL } exoAudioTrack.isVisible = audioTracks.size > 1 exoAudioTrack.setOnClickListener { - TrackGroupDialogFragment(this, audioTracks, TRACK_TYPE_AUDIO) + TrackGroupDialogFragment(this, audioTracks, TRACK_TYPE_AUDIO, audioLanguages) .show(supportFragmentManager, "dialog") } if (!hasExtSubtitles) { diff --git a/app/src/main/java/ani/dantotsu/media/anime/TrackGroupDialogFragment.kt b/app/src/main/java/ani/dantotsu/media/anime/TrackGroupDialogFragment.kt index 3ea86097..ecbfd982 100644 --- a/app/src/main/java/ani/dantotsu/media/anime/TrackGroupDialogFragment.kt +++ b/app/src/main/java/ani/dantotsu/media/anime/TrackGroupDialogFragment.kt @@ -19,19 +19,13 @@ import java.util.Locale @OptIn(UnstableApi::class) class TrackGroupDialogFragment( - instance: ExoplayerView, trackGroups: ArrayList, type: @TrackType Int + private var instance: ExoplayerView, + private var trackGroups: ArrayList, + private var type: @TrackType Int, + private var overrideTrackNames: List? = null ) : BottomSheetDialogFragment() { private var _binding: BottomSheetSubtitlesBinding? = null private val binding get() = _binding!! - private var instance: ExoplayerView - private var trackGroups: ArrayList - private var type: @TrackType Int - - init { - this.instance = instance - this.trackGroups = trackGroups - this.type = type - } override fun onCreateView( inflater: LayoutInflater, @@ -67,7 +61,8 @@ class TrackGroupDialogFragment( override fun onBindViewHolder(holder: StreamViewHolder, position: Int) { val binding = holder.binding trackGroups[position].let { trackGroup -> - when (val language = trackGroup.getTrackFormat(0).language?.lowercase()) { + when (val language = overrideTrackNames?.getOrNull(position) + ?: trackGroup.getTrackFormat(0).language?.lowercase()) { null -> { binding.subtitleTitle.text = getString(R.string.unknown_track, "Track $position") @@ -94,7 +89,6 @@ class TrackGroupDialogFragment( } binding.subtitleTitle.text = locale?.let { "[${it.language}] ${it.displayName}" - } ?: getString(R.string.unknown_track, language) } } diff --git a/app/src/main/java/ani/dantotsu/media/manga/MangaReadAdapter.kt b/app/src/main/java/ani/dantotsu/media/manga/MangaReadAdapter.kt index d84c03b8..a44fecc1 100644 --- a/app/src/main/java/ani/dantotsu/media/manga/MangaReadAdapter.kt +++ b/app/src/main/java/ani/dantotsu/media/manga/MangaReadAdapter.kt @@ -548,7 +548,7 @@ class MangaReadAdapter( val adapter = ArrayAdapter( fragment.requireContext(), R.layout.item_dropdown, - parser.extension.sources.map { LanguageMapper.mapLanguageCodeToName(it.lang) } + parser.extension.sources.map { LanguageMapper.getLanguageCode(it.lang) } ) val items = adapter.count binding?.animeSourceLanguageContainer?.isVisible = items > 1 diff --git a/app/src/main/java/ani/dantotsu/media/manga/MangaReadFragment.kt b/app/src/main/java/ani/dantotsu/media/manga/MangaReadFragment.kt index d0694a40..5ea50279 100644 --- a/app/src/main/java/ani/dantotsu/media/manga/MangaReadFragment.kt +++ b/app/src/main/java/ani/dantotsu/media/manga/MangaReadFragment.kt @@ -385,7 +385,7 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener { var selectedSetting = allSettings[0] if (allSettings.size > 1) { val names = - allSettings.map { LanguageMapper.mapLanguageCodeToName(it.lang) }.toTypedArray() + allSettings.map { LanguageMapper.getLanguageCode(it.lang) }.toTypedArray() val dialog = AlertDialog.Builder(requireContext(), R.style.MyPopup) .setTitle("Select a Source") .setSingleChoiceItems(names, -1) { dialog, which -> diff --git a/app/src/main/java/ani/dantotsu/others/LanguageMapper.kt b/app/src/main/java/ani/dantotsu/others/LanguageMapper.kt index 3d9c439b..a1fc9fa1 100644 --- a/app/src/main/java/ani/dantotsu/others/LanguageMapper.kt +++ b/app/src/main/java/ani/dantotsu/others/LanguageMapper.kt @@ -1,124 +1,145 @@ package ani.dantotsu.others +import java.util.Locale + class LanguageMapper { companion object { - fun mapLanguageCodeToName(code: String): String { - return when (code) { - "all" -> "Multi" - "af" -> "Afrikaans" - "am" -> "Amharic" - "ar" -> "Arabic" - "as" -> "Assamese" - "az" -> "Azerbaijani" - "be" -> "Belarusian" - "bg" -> "Bulgarian" - "bn" -> "Bengali" - "bs" -> "Bosnian" - "ca" -> "Catalan" - "ceb" -> "Cebuano" - "cs" -> "Czech" - "da" -> "Danish" - "de" -> "German" - "el" -> "Greek" - "en" -> "English" - "en-Us" -> "English (United States)" - "eo" -> "Esperanto" - "es" -> "Spanish" - "es-419" -> "Spanish (Latin America)" - "et" -> "Estonian" - "eu" -> "Basque" - "fa" -> "Persian" - "fi" -> "Finnish" - "fil" -> "Filipino" - "fo" -> "Faroese" - "fr" -> "French" - "ga" -> "Irish" - "gn" -> "Guarani" - "gu" -> "Gujarati" - "ha" -> "Hausa" - "he" -> "Hebrew" - "hi" -> "Hindi" - "hr" -> "Croatian" - "ht" -> "Haitian Creole" - "hu" -> "Hungarian" - "hy" -> "Armenian" - "id" -> "Indonesian" - "ig" -> "Igbo" - "is" -> "Icelandic" - "it" -> "Italian" - "ja" -> "Japanese" - "jv" -> "Javanese" - "ka" -> "Georgian" - "kk" -> "Kazakh" - "km" -> "Khmer" - "kn" -> "Kannada" - "ko" -> "Korean" - "ku" -> "Kurdish" - "ky" -> "Kyrgyz" - "la" -> "Latin" - "lb" -> "Luxembourgish" - "lo" -> "Lao" - "lt" -> "Lithuanian" - "lv" -> "Latvian" - "mg" -> "Malagasy" - "mi" -> "Maori" - "mk" -> "Macedonian" - "ml" -> "Malayalam" - "mn" -> "Mongolian" - "mo" -> "Moldovan" - "mr" -> "Marathi" - "ms" -> "Malay" - "mt" -> "Maltese" - "my" -> "Burmese" - "ne" -> "Nepali" - "nl" -> "Dutch" - "no" -> "Norwegian" - "ny" -> "Chichewa" - "pl" -> "Polish" - "pt" -> "Portuguese" - "pt-BR" -> "Portuguese (Brazil)" - "pt-PT" -> "Portuguese (Portugal)" - "ps" -> "Pashto" - "ro" -> "Romanian" - "rm" -> "Romansh" - "ru" -> "Russian" - "sd" -> "Sindhi" - "sh" -> "Serbo-Croatian" - "si" -> "Sinhala" - "sk" -> "Slovak" - "sl" -> "Slovenian" - "sm" -> "Samoan" - "sn" -> "Shona" - "so" -> "Somali" - "sq" -> "Albanian" - "sr" -> "Serbian" - "st" -> "Southern Sotho" - "sv" -> "Swedish" - "sw" -> "Swahili" - "ta" -> "Tamil" - "te" -> "Telugu" - "tg" -> "Tajik" - "th" -> "Thai" - "ti" -> "Tigrinya" - "tk" -> "Turkmen" - "tl" -> "Tagalog" - "to" -> "Tongan" - "tr" -> "Turkish" - "uk" -> "Ukrainian" - "ur" -> "Urdu" - "uz" -> "Uzbek" - "vi" -> "Vietnamese" - "yo" -> "Yoruba" - "zh" -> "Chinese" - "zh-Hans" -> "Chinese (Simplified)" - "zh-Hant" -> "Chinese (Traditional)" - "zh-Habt" -> "Chinese (Hakka)" - "zu" -> "Zulu" - else -> code + private val codeMap: Map = mapOf( + "all" to "Multi", + "af" to "Afrikaans", + "am" to "Amharic", + "ar" to "Arabic", + "as" to "Assamese", + "az" to "Azerbaijani", + "be" to "Belarusian", + "bg" to "Bulgarian", + "bn" to "Bengali", + "bs" to "Bosnian", + "ca" to "Catalan", + "ceb" to "Cebuano", + "cs" to "Czech", + "da" to "Danish", + "de" to "German", + "el" to "Greek", + "en" to "English", + "en-Us" to "English (United States)", + "eo" to "Esperanto", + "es" to "Spanish", + "es-419" to "Spanish (Latin America)", + "es-ES" to "Spanish (Spain)", + "et" to "Estonian", + "eu" to "Basque", + "fa" to "Persian", + "fi" to "Finnish", + "fil" to "Filipino", + "fo" to "Faroese", + "fr" to "French", + "ga" to "Irish", + "gn" to "Guarani", + "gu" to "Gujarati", + "ha" to "Hausa", + "he" to "Hebrew", + "hi" to "Hindi", + "hr" to "Croatian", + "ht" to "Haitian Creole", + "hu" to "Hungarian", + "hy" to "Armenian", + "id" to "Indonesian", + "ig" to "Igbo", + "is" to "Icelandic", + "it" to "Italian", + "ja" to "Japanese", + "jv" to "Javanese", + "ka" to "Georgian", + "kk" to "Kazakh", + "km" to "Khmer", + "kn" to "Kannada", + "ko" to "Korean", + "ku" to "Kurdish", + "ky" to "Kyrgyz", + "la" to "Latin", + "lb" to "Luxembourgish", + "lo" to "Lao", + "lt" to "Lithuanian", + "lv" to "Latvian", + "mg" to "Malagasy", + "mi" to "Maori", + "mk" to "Macedonian", + "ml" to "Malayalam", + "mn" to "Mongolian", + "mo" to "Moldovan", + "mr" to "Marathi", + "ms" to "Malay", + "mt" to "Maltese", + "my" to "Burmese", + "ne" to "Nepali", + "nl" to "Dutch", + "no" to "Norwegian", + "ny" to "Chichewa", + "pl" to "Polish", + "pt" to "Portuguese", + "pt-BR" to "Portuguese (Brazil)", + "pt-PT" to "Portuguese (Portugal)", + "ps" to "Pashto", + "ro" to "Romanian", + "rm" to "Romansh", + "ru" to "Russian", + "sd" to "Sindhi", + "sh" to "Serbo-Croatian", + "si" to "Sinhala", + "sk" to "Slovak", + "sl" to "Slovenian", + "sm" to "Samoan", + "sn" to "Shona", + "so" to "Somali", + "sq" to "Albanian", + "sr" to "Serbian", + "st" to "Southern Sotho", + "sv" to "Swedish", + "sw" to "Swahili", + "ta" to "Tamil", + "te" to "Telugu", + "tg" to "Tajik", + "th" to "Thai", + "ti" to "Tigrinya", + "tk" to "Turkmen", + "tl" to "Tagalog", + "to" to "Tongan", + "tr" to "Turkish", + "uk" to "Ukrainian", + "ur" to "Urdu", + "uz" to "Uzbek", + "vi" to "Vietnamese", + "yo" to "Yoruba", + "zh" to "Chinese", + "zh-Hans" to "Chinese (Simplified)", + "zh-Hant" to "Chinese (Traditional)", + "zh-Habt" to "Chinese (Hakka)", + "zu" to "Zulu" + ) + + fun getLanguage(code: String): String { + return if (code.contains("-")) { + try { + val parts = code.split("-") + Locale(parts[0], parts[1]).displayName + } catch (ignored: Exception) { + code + } + } else { + try { + Locale(code).displayName + } catch (ignored: Exception) { + code + } } } + fun getLanguageCode(language: String): String { + return codeMap.filterValues { it.lowercase() == language.lowercase() }.keys.firstOrNull() ?: "all" + } + enum class Language(val code: String) { ALL("all"), ARABIC("ar"), diff --git a/app/src/main/java/ani/dantotsu/parsers/AniyomiAdapter.kt b/app/src/main/java/ani/dantotsu/parsers/AniyomiAdapter.kt index 95edf008..bacc5e22 100644 --- a/app/src/main/java/ani/dantotsu/parsers/AniyomiAdapter.kt +++ b/app/src/main/java/ani/dantotsu/parsers/AniyomiAdapter.kt @@ -511,9 +511,10 @@ class VideoServerPassthrough(private val videoServer: VideoServer) : VideoExtrac override suspend fun extract(): VideoContainer { val vidList = listOfNotNull(videoServer.video?.let { aniVideoToSaiVideo(it) }) val subList = videoServer.video?.subtitleTracks?.map { trackToSubtitle(it) } ?: emptyList() + val audioList = videoServer.video?.audioTracks ?: emptyList() return if (vidList.isNotEmpty()) { - VideoContainer(vidList, subList) + VideoContainer(vidList, subList, audioList) } else { throw Exception("No videos found") } diff --git a/app/src/main/java/ani/dantotsu/parsers/VideoExtractor.kt b/app/src/main/java/ani/dantotsu/parsers/VideoExtractor.kt index ef164b34..e908adee 100644 --- a/app/src/main/java/ani/dantotsu/parsers/VideoExtractor.kt +++ b/app/src/main/java/ani/dantotsu/parsers/VideoExtractor.kt @@ -1,6 +1,7 @@ package ani.dantotsu.parsers import ani.dantotsu.FileUrl +import eu.kanade.tachiyomi.animesource.model.Track import java.io.Serializable /** @@ -12,6 +13,7 @@ abstract class VideoExtractor : Serializable { abstract val server: VideoServer var videos: List