diff --git a/app/build.gradle b/app/build.gradle index b983443c..1f4997d3 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -48,6 +48,10 @@ android { manifestPlaceholders.icon_placeholder_round = "@mipmap/ic_launcher_alpha_round" debuggable System.getenv("CI") == null isDefault true + debuggable true + jniDebuggable true + minifyEnabled false + shrinkResources false } debug { applicationIdSuffix ".beta" @@ -81,25 +85,26 @@ android { dependencies { // FireBase - googleImplementation platform('com.google.firebase:firebase-bom:33.0.0') - googleImplementation 'com.google.firebase:firebase-analytics-ktx:22.0.0' - googleImplementation 'com.google.firebase:firebase-crashlytics-ktx:19.0.0' + googleImplementation platform('com.google.firebase:firebase-bom:33.13.0') + googleImplementation 'com.google.firebase:firebase-analytics-ktx:22.4.0' + googleImplementation 'com.google.firebase:firebase-crashlytics-ktx:19.4.3' // Core - implementation 'androidx.appcompat:appcompat:1.6.1' + implementation 'androidx.appcompat:appcompat:1.7.0' implementation 'androidx.browser:browser:1.8.0' - implementation 'androidx.core:core-ktx:1.13.1' - implementation 'androidx.fragment:fragment-ktx:1.6.2' + implementation 'androidx.core:core-ktx:1.16.0' + implementation 'androidx.fragment:fragment-ktx:1.8.6' + implementation 'androidx.activity:activity-ktx:1.10.1' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' implementation 'androidx.legacy:legacy-support-v4:1.0.0' implementation 'androidx.multidex:multidex:2.0.1' - implementation "androidx.work:work-runtime-ktx:2.9.0" + implementation "androidx.work:work-runtime-ktx:2.10.1" implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation 'com.google.code.gson:gson:2.10.1' implementation 'com.github.Blatzar:NiceHttp:0.4.4' - implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3' + implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3' implementation 'androidx.preference:preference-ktx:1.2.1' - implementation 'androidx.webkit:webkit:1.11.0' + implementation 'androidx.webkit:webkit:1.13.0' implementation "com.anggrayudi:storage:1.5.5" implementation "androidx.biometric:biometric:1.1.0" @@ -113,7 +118,7 @@ dependencies { implementation 'jp.wasabeef:glide-transformations:4.3.0' // Exoplayer - ext.exo_version = '1.5.1' + ext.exo_version = '1.6.1' implementation "androidx.media3:media3-exoplayer:$exo_version" implementation "androidx.media3:media3-ui:$exo_version" implementation "androidx.media3:media3-exoplayer-hls:$exo_version" @@ -133,7 +138,7 @@ dependencies { implementation 'com.davemorrissey.labs:subsampling-scale-image-view-androidx:3.10.0' implementation 'com.alexvasilkov:gesture-views:2.8.3' implementation 'com.github.VipulOG:ebook-reader:0.1.6' - implementation 'androidx.paging:paging-runtime-ktx:3.2.1' + implementation 'androidx.paging:paging-runtime-ktx:3.3.6' implementation 'com.github.eltos:simpledialogfragments:v3.7' implementation 'com.github.AAChartModel:AAChartCore-Kotlin:7.2.3' @@ -162,13 +167,13 @@ dependencies { implementation 'ca.gosyer:voyager-navigator:1.0.0-rc07' implementation 'com.squareup.logcat:logcat:0.1' implementation 'uy.kohesive.injekt:injekt-core:1.16.+' - implementation 'com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.12' - implementation 'com.squareup.okhttp3:okhttp:5.0.0-alpha.12' + implementation 'com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.14' + implementation 'com.squareup.okhttp3:okhttp:5.0.0-alpha.14' implementation 'com.squareup.okhttp3:okhttp-dnsoverhttps' - implementation 'com.squareup.okio:okio:3.8.0' - implementation 'com.squareup.okhttp3:okhttp-brotli:5.0.0-alpha.12' - implementation 'org.jsoup:jsoup:1.16.1' - implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json-okio:1.6.3' + implementation 'com.squareup.okio:okio:3.9.1' + implementation 'com.squareup.okhttp3:okhttp-brotli:5.0.0-alpha.14' + implementation 'org.jsoup:jsoup:1.18.1' + implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json-okio:1.7.3' implementation 'com.jakewharton.rxrelay:rxrelay:1.2.0' implementation 'com.github.tachiyomiorg:unifile:17bec43' implementation 'com.github.gpanther:java-nat-sort:natural-comparator-1.1' diff --git a/app/src/main/java/ani/dantotsu/media/MediaDetailsActivity.kt b/app/src/main/java/ani/dantotsu/media/MediaDetailsActivity.kt index bcb21c8c..852d4ef6 100644 --- a/app/src/main/java/ani/dantotsu/media/MediaDetailsActivity.kt +++ b/app/src/main/java/ani/dantotsu/media/MediaDetailsActivity.kt @@ -4,6 +4,7 @@ import android.animation.ObjectAnimator import android.annotation.SuppressLint import android.content.Intent import android.content.res.Configuration +import android.graphics.Color import android.os.Bundle import android.text.SpannableStringBuilder import android.view.GestureDetector @@ -12,6 +13,8 @@ import android.view.View import android.view.ViewGroup import android.view.animation.AccelerateDecelerateInterpolator import android.widget.ImageView +import androidx.activity.SystemBarStyle +import androidx.activity.enableEdgeToEdge import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity @@ -19,8 +22,10 @@ import androidx.appcompat.content.res.AppCompatResources import androidx.core.content.ContextCompat import androidx.core.text.bold import androidx.core.text.color +import androidx.core.view.WindowInsetsControllerCompat import androidx.core.view.isVisible import androidx.core.view.marginBottom +import androidx.core.view.setPadding import androidx.core.view.updateLayoutParams import androidx.core.view.updateMargins import androidx.fragment.app.Fragment @@ -79,6 +84,7 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi @SuppressLint("ClickableViewAccessibility") override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) var media: Media = intent.getSerialized("media") ?: mediaSingleton ?: emptyMedia() val id = intent.getIntExtra("mediaId", -1) @@ -109,6 +115,7 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi // Ui init initActivity(this) + binding.mediaViewPager.updateLayoutParams { bottomMargin = navBarHeight } @@ -132,10 +139,12 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi val navBarBottomMargin = if (resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE ) 0 else navBarHeight - navBar.updateLayoutParams { - rightMargin = navBarRightMargin - bottomMargin = navBarBottomMargin - } + navBar.setPadding( + navBar.paddingLeft, + navBar.paddingTop, + navBar.paddingRight + navBarRightMargin, + navBar.paddingBottom + navBarBottomMargin + ) binding.mediaBanner.updateLayoutParams { height += statusBarHeight } binding.mediaBannerNoKen.updateLayoutParams { height += statusBarHeight } binding.mediaClose.updateLayoutParams { topMargin += statusBarHeight } diff --git a/app/src/main/java/ani/dantotsu/media/SubtitleDownloader.kt b/app/src/main/java/ani/dantotsu/media/SubtitleDownloader.kt index 5c66f6a2..7fa32de9 100644 --- a/app/src/main/java/ani/dantotsu/media/SubtitleDownloader.kt +++ b/app/src/main/java/ani/dantotsu/media/SubtitleDownloader.kt @@ -1,6 +1,8 @@ package ani.dantotsu.media import android.content.Context +import androidx.core.net.toFile +import androidx.core.net.toUri import ani.dantotsu.download.DownloadedType import ani.dantotsu.download.DownloadsManager import ani.dantotsu.parsers.SubtitleType @@ -21,28 +23,32 @@ class SubtitleDownloader { suspend fun loadSubtitleType(url: String): SubtitleType = withContext(Dispatchers.IO) { return@withContext try { - // Initialize the NetworkHelper instance. Replace this line based on how you usually initialize it - val networkHelper = Injekt.get() - val request = Request.Builder() - .url(url) - .build() + if (!url.startsWith("file")) { + // Initialize the NetworkHelper instance. Replace this line based on how you usually initialize it + val networkHelper = Injekt.get() + val request = Request.Builder() + .url(url) + .build() - val response = networkHelper.client.newCall(request).execute() + val response = networkHelper.client.newCall(request).execute() - // Check if response is successful - if (response.isSuccessful) { - val responseBody = response.body.string() + // Check if response is successful + if (response.isSuccessful) { + val responseBody = response.body.string() - val subtitleType = when { - responseBody.contains("[Script Info]") -> SubtitleType.ASS - responseBody.contains("WEBVTT") -> SubtitleType.VTT - else -> SubtitleType.SRT + val subtitleType = getType(responseBody) + + subtitleType + } else { + SubtitleType.UNKNOWN } - - subtitleType } else { - SubtitleType.UNKNOWN + val uri = url.toUri() + val file = uri.toFile() + val fileBody = file.readText() + val subtitleType = getType(fileBody) + subtitleType } } catch (e: Exception) { Logger.log(e) @@ -50,6 +56,15 @@ class SubtitleDownloader { } } + private fun getType(content: String): SubtitleType { + return when { + content.contains("[Script Info]") -> SubtitleType.ASS + content.contains("WEBVTT") -> SubtitleType.VTT + content.contains("SRT") -> SubtitleType.SRT + else -> SubtitleType.UNKNOWN + } + } + //actually downloads lol @Deprecated("handled externally") suspend fun downloadSubtitle( 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 465949a4..b2503c28 100644 --- a/app/src/main/java/ani/dantotsu/media/anime/ExoplayerView.kt +++ b/app/src/main/java/ani/dantotsu/media/anime/ExoplayerView.kt @@ -2,7 +2,6 @@ package ani.dantotsu.media.anime import android.animation.ObjectAnimator import android.annotation.SuppressLint -import android.app.AlertDialog import android.app.Dialog import android.app.PictureInPictureParams import android.app.PictureInPictureUiState @@ -19,7 +18,6 @@ import android.media.AudioManager.AUDIOFOCUS_GAIN import android.media.AudioManager.AUDIOFOCUS_LOSS import android.media.AudioManager.AUDIOFOCUS_LOSS_TRANSIENT import android.media.AudioManager.STREAM_MUSIC -import android.net.Uri import android.os.Build import android.os.Bundle import android.os.CountDownTimer @@ -75,9 +73,7 @@ import androidx.media3.common.TrackSelectionOverride import androidx.media3.common.Tracks import androidx.media3.common.text.CueGroup import androidx.media3.common.util.UnstableApi -import androidx.media3.datasource.DataSource import androidx.media3.datasource.DefaultDataSource -import androidx.media3.datasource.HttpDataSource import androidx.media3.datasource.cache.CacheDataSource import androidx.media3.datasource.okhttp.OkHttpDataSource import androidx.media3.exoplayer.DefaultLoadControl @@ -175,10 +171,10 @@ import java.util.Timer import java.util.TimerTask import java.util.concurrent.Executors import java.util.concurrent.TimeUnit -import kotlin.collections.set import kotlin.math.max import kotlin.math.min import kotlin.math.roundToInt +import androidx.core.net.toUri @UnstableApi @SuppressLint("ClickableViewAccessibility") @@ -427,7 +423,8 @@ class ExoplayerView : false -> 0f } - val textElevation = PrefManager.getVal(PrefName.SubBottomMargin) / 50 * resources.displayMetrics.heightPixels + val textElevation = + PrefManager.getVal(PrefName.SubBottomMargin) / 50 * resources.displayMetrics.heightPixels textView.translationY = -textElevation } @@ -606,9 +603,9 @@ class ExoplayerView : if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { pipEnabled = packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE) && - PrefManager.getVal( - PrefName.Pip, - ) + PrefManager.getVal( + PrefName.Pip, + ) if (pipEnabled) { exoPip.visibility = View.VISIBLE exoPip.setOnClickListener { @@ -1044,7 +1041,8 @@ class ExoplayerView : } } - override fun onSingleClick(event: MotionEvent) = if (isSeeking) doubleTap(false, event) else handleController() + override fun onSingleClick(event: MotionEvent) = + if (isSeeking) doubleTap(false, event) else handleController() }, ) val rewindArea = playerView.findViewById(R.id.exo_rewind_area) @@ -1079,7 +1077,8 @@ class ExoplayerView : } } - override fun onSingleClick(event: MotionEvent) = if (isSeeking) doubleTap(true, event) else handleController() + override fun onSingleClick(event: MotionEvent) = + if (isSeeking) doubleTap(true, event) else handleController() }, ) val forwardArea = playerView.findViewById(R.id.exo_forward_area) @@ -1449,7 +1448,8 @@ class ExoplayerView : else -> mutableListOf() } val startTimestamp = Calendar.getInstance() - val durationInSeconds = if (exoPlayer.duration != C.TIME_UNSET) (exoPlayer.duration / 1000).toInt() else 1440 + val durationInSeconds = + if (exoPlayer.duration != C.TIME_UNSET) (exoPlayer.duration / 1000).toInt() else 1440 val endTimestamp = Calendar.getInstance().apply { @@ -1502,12 +1502,12 @@ class ExoplayerView : @Suppress("UNCHECKED_CAST") val list = ( - PrefManager.getNullableCustomVal( - "continueAnimeList", - listOf(), - List::class.java, - ) as List - ).toMutableList() + PrefManager.getNullableCustomVal( + "continueAnimeList", + listOf(), + List::class.java, + ) as List + ).toMutableList() if (list.contains(media.id)) list.remove(media.id) list.add(media.id) PrefManager.setCustomVal("continueAnimeList", list) @@ -1567,7 +1567,11 @@ class ExoplayerView : subtitle = intent.getSerialized("subtitle") ?: when ( val subLang: String? = - PrefManager.getNullableCustomVal("subLang_${media.id}", null, String::class.java) + PrefManager.getNullableCustomVal( + "subLang_${media.id}", + null, + String::class.java + ) ) { null -> { when (episode.selectedSubtitle) { @@ -1575,8 +1579,12 @@ class ExoplayerView : -1 -> ext.subtitles.find { it.language.contains(lang, ignoreCase = true) || - it.language.contains(getLanguageCode(lang), ignoreCase = true) + it.language.contains( + getLanguageCode(lang), + ignoreCase = true + ) } + else -> ext.subtitles.getOrNull(episode.selectedSubtitle!!) } } @@ -1597,29 +1605,27 @@ class ExoplayerView : emptyList().toMutableList() ext.subtitles.forEach { subtitle -> val subtitleUrl = if (!hasExtSubtitles) video!!.file.url else subtitle.file.url - // var localFile: String? = null if (subtitle.type == SubtitleType.UNKNOWN) { runBlocking { val type = SubtitleDownloader.loadSubtitleType(subtitleUrl) - val fileUri = Uri.parse(subtitleUrl) + val fileUri = (subtitleUrl).toUri() sub += MediaItem.SubtitleConfiguration .Builder(fileUri) .setSelectionFlags(C.SELECTION_FLAG_DEFAULT) .setMimeType( when (type) { - SubtitleType.VTT -> MimeTypes.TEXT_SSA + SubtitleType.VTT -> MimeTypes.TEXT_VTT SubtitleType.ASS -> MimeTypes.TEXT_SSA - SubtitleType.SRT -> MimeTypes.TEXT_SSA - else -> MimeTypes.TEXT_SSA + SubtitleType.SRT -> MimeTypes.APPLICATION_SUBRIP + else -> MimeTypes.TEXT_UNKNOWN }, ).setId("69") .setLanguage(subtitle.language) .build() } - println("sub: $sub") } else { - val subUri = Uri.parse(subtitleUrl) + val subUri = subtitleUrl.toUri() sub += MediaItem.SubtitleConfiguration .Builder(subUri) @@ -1649,26 +1655,18 @@ class ExoplayerView : followRedirects(true) followSslRedirects(true) }.build() - val dataSourceFactory = - DataSource.Factory { - val dataSource: HttpDataSource = OkHttpDataSource.Factory(httpClient).createDataSource() - defaultHeaders.forEach { - dataSource.setRequestProperty(it.key, it.value) + val httpDataSourceFactory = + OkHttpDataSource.Factory(httpClient).apply { + setDefaultRequestProperties(defaultHeaders) + video?.file?.headers?.let { + setDefaultRequestProperties(it) } - video?.file?.headers?.forEach { - dataSource.setRequestProperty(it.key, it.value) - } - dataSource } - val dafuckDataSourceFactory = DefaultDataSource.Factory(this) + val defaultDataSourceFactory = DefaultDataSource.Factory(this, httpDataSourceFactory) cacheFactory = CacheDataSource.Factory().apply { setCache(VideoCache.getInstance(this@ExoplayerView)) - if (ext.server.offline) { - setUpstreamDataSourceFactory(dafuckDataSourceFactory) - } else { - setUpstreamDataSourceFactory(dataSourceFactory) - } + setUpstreamDataSourceFactory(defaultDataSourceFactory) setCacheWriteDataSinkFactory(null) } @@ -1717,16 +1715,18 @@ class ExoplayerView : val docFile = directory.listFiles().firstOrNull { it.name?.endsWith(".mp4") == true || - it.name?.endsWith(".mkv") == true || - it.name?.endsWith( - ".${Injekt - .get() - .extension - ?.extension - ?.getFileExtension() - ?.first ?: "ts"}", - ) == - true + it.name?.endsWith(".mkv") == true || + it.name?.endsWith( + ".${ + Injekt + .get() + .extension + ?.extension + ?.getFileExtension() + ?.first ?: "ts" + }", + ) == + true } if (docFile != null) { val uri = docFile.uri @@ -1840,30 +1840,30 @@ class ExoplayerView : "%02d:%02d:%02d", TimeUnit.MILLISECONDS.toHours(playbackPosition), TimeUnit.MILLISECONDS.toMinutes(playbackPosition) - - TimeUnit.HOURS.toMinutes( - TimeUnit.MILLISECONDS.toHours( - playbackPosition, + TimeUnit.HOURS.toMinutes( + TimeUnit.MILLISECONDS.toHours( + playbackPosition, + ), ), - ), TimeUnit.MILLISECONDS.toSeconds(playbackPosition) - - TimeUnit.MINUTES.toSeconds( - TimeUnit.MILLISECONDS.toMinutes( - playbackPosition, + TimeUnit.MINUTES.toSeconds( + TimeUnit.MILLISECONDS.toMinutes( + playbackPosition, + ), ), - ), ) - customAlertDialog().apply { - setTitle(getString(R.string.continue_from, time)) - setCancelable(false) - setPosButton(getString(R.string.yes)) { - buildExoplayer() - } - setNegButton(getString(R.string.no)) { - playbackPosition = 0L - buildExoplayer() - } - show() + customAlertDialog().apply { + setTitle(getString(R.string.continue_from, time)) + setCancelable(false) + setPosButton(getString(R.string.yes)) { + buildExoplayer() } + setNegButton(getString(R.string.no)) { + playbackPosition = 0L + buildExoplayer() + } + show() + } } else { buildExoplayer() } @@ -1940,7 +1940,9 @@ class ExoplayerView : val currentPosition = exoPlayer.currentPosition - if ((lastSubtitle?.length ?: 0) < 20 || (lastPosition != 0L && currentPosition - lastPosition > 1500)) { + if ((lastSubtitle?.length + ?: 0) < 20 || (lastPosition != 0L && currentPosition - lastPosition > 1500) + ) { activeSubtitles.clear() } @@ -2187,7 +2189,7 @@ class ExoplayerView : currentTimeStamp = model.timeStamps.value?.find { timestamp -> timestamp.interval.startTime < playerCurrentTime && - playerCurrentTime < (timestamp.interval.endTime - 1) + playerCurrentTime < (timestamp.interval.endTime - 1) } val new = currentTimeStamp @@ -2213,7 +2215,8 @@ class ExoplayerView : override fun onTick(millisUntilFinished: Long) { if (new == null) { skipTimeButton.visibility = View.GONE - exoSkip.isVisible = PrefManager.getVal(PrefName.SkipTime) > 0 + exoSkip.isVisible = + PrefManager.getVal(PrefName.SkipTime) > 0 disappeared = false functionstarted = false cancelTimer() @@ -2222,7 +2225,8 @@ class ExoplayerView : override fun onFinish() { skipTimeButton.visibility = View.GONE - exoSkip.isVisible = PrefManager.getVal(PrefName.SkipTime) > 0 + exoSkip.isVisible = + PrefManager.getVal(PrefName.SkipTime) > 0 disappeared = true functionstarted = false cancelTimer() @@ -2310,7 +2314,7 @@ class ExoplayerView : tracks.groups.forEach { println( "Track__: $it\nTrack__: ${it.length}\nTrack__: ${it.isSelected}\n" + - "Track__: ${it.type}\nTrack__: ${it.mediaTrackGroup.id}", + "Track__: ${it.type}\nTrack__: ${it.mediaTrackGroup.id}", ) when (it.type) { TRACK_TYPE_AUDIO -> { @@ -2365,7 +2369,7 @@ class ExoplayerView : when (error.errorCode) { PlaybackException.ERROR_CODE_IO_BAD_HTTP_STATUS, PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_FAILED, - -> { + -> { toast("Source Exception : ${error.message}") isPlayerPlaying = true sourceClick() @@ -2403,9 +2407,9 @@ class ExoplayerView : val incognito: Boolean = PrefManager.getVal(PrefName.Incognito) val episodeEnd = exoPlayer.currentPosition / episodeLength > - PrefManager.getVal( - PrefName.WatchPercentage, - ) + PrefManager.getVal( + PrefName.WatchPercentage, + ) val episode0 = currentEpisodeIndex == 0 && PrefManager.getVal(PrefName.ChapterZeroPlayer) if (!incognito && (episodeEnd || episode0) && Anilist.userid != null ) { @@ -2484,7 +2488,7 @@ class ExoplayerView : val videoURL = video?.file?.url ?: return val subtitleUrl = if (!hasExtSubtitles) video!!.file.url else subtitle!!.file.url val shareVideo = Intent(Intent.ACTION_VIEW) - shareVideo.setDataAndType(Uri.parse(videoURL), "video/*") + shareVideo.setDataAndType(videoURL.toUri(), "video/*") shareVideo.setPackage("com.instantbits.cast.webvideo") if (subtitle != null) shareVideo.putExtra("subtitle", subtitleUrl) shareVideo.putExtra( @@ -2506,7 +2510,7 @@ class ExoplayerView : } catch (ex: ActivityNotFoundException) { val intent = Intent(Intent.ACTION_VIEW) val uriString = "market://details?id=com.instantbits.cast.webvideo" - intent.data = Uri.parse(uriString) + intent.data = uriString.toUri() startActivity(intent) } } diff --git a/app/src/main/java/ani/dantotsu/parsers/AniyomiAdapter.kt b/app/src/main/java/ani/dantotsu/parsers/AniyomiAdapter.kt index fa1fa9a2..0f2a438a 100644 --- a/app/src/main/java/ani/dantotsu/parsers/AniyomiAdapter.kt +++ b/app/src/main/java/ani/dantotsu/parsers/AniyomiAdapter.kt @@ -646,7 +646,6 @@ class VideoServerPassthrough(private val videoServer: VideoServer) : VideoExtrac } private fun trackToSubtitle(track: Track): Subtitle { - //use Dispatchers.IO to make a HTTP request to determine the subtitle type var type: SubtitleType? runBlocking { type = findSubtitleType(track.url)