feat: embedded tracks

modified #338
This commit is contained in:
rebelonion 2024-04-15 01:02:28 -05:00
parent 4b413b78fe
commit 6e399b32e1
5 changed files with 271 additions and 99 deletions

View file

@ -48,6 +48,8 @@ import android.widget.ImageButton
import android.widget.Spinner import android.widget.Spinner
import android.widget.TextView import android.widget.TextView
import androidx.activity.addCallback import androidx.activity.addCallback
import androidx.activity.result.ActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
@ -60,12 +62,16 @@ import androidx.media3.cast.CastPlayer
import androidx.media3.cast.SessionAvailabilityListener import androidx.media3.cast.SessionAvailabilityListener
import androidx.media3.common.C import androidx.media3.common.C
import androidx.media3.common.C.AUDIO_CONTENT_TYPE_MOVIE import androidx.media3.common.C.AUDIO_CONTENT_TYPE_MOVIE
import androidx.media3.common.C.TRACK_TYPE_AUDIO
import androidx.media3.common.C.TRACK_TYPE_TEXT
import androidx.media3.common.C.TRACK_TYPE_VIDEO import androidx.media3.common.C.TRACK_TYPE_VIDEO
import androidx.media3.common.Format
import androidx.media3.common.MediaItem import androidx.media3.common.MediaItem
import androidx.media3.common.MimeTypes import androidx.media3.common.MimeTypes
import androidx.media3.common.PlaybackException import androidx.media3.common.PlaybackException
import androidx.media3.common.PlaybackParameters import androidx.media3.common.PlaybackParameters
import androidx.media3.common.Player import androidx.media3.common.Player
import androidx.media3.common.TrackGroup
import androidx.media3.common.TrackSelectionOverride import androidx.media3.common.TrackSelectionOverride
import androidx.media3.common.Tracks import androidx.media3.common.Tracks
import androidx.media3.common.util.UnstableApi import androidx.media3.common.util.UnstableApi
@ -128,13 +134,13 @@ import ani.dantotsu.parsers.SubtitleType
import ani.dantotsu.parsers.Video import ani.dantotsu.parsers.Video
import ani.dantotsu.parsers.VideoExtractor import ani.dantotsu.parsers.VideoExtractor
import ani.dantotsu.parsers.VideoType import ani.dantotsu.parsers.VideoType
import ani.dantotsu.px
import ani.dantotsu.settings.PlayerSettingsActivity import ani.dantotsu.settings.PlayerSettingsActivity
import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.snackString import ani.dantotsu.snackString
import ani.dantotsu.startMainActivity import ani.dantotsu.startMainActivity
import ani.dantotsu.themes.ThemeManager import ani.dantotsu.themes.ThemeManager
import ani.dantotsu.toPx
import ani.dantotsu.toast import ani.dantotsu.toast
import ani.dantotsu.tryWithSuspend import ani.dantotsu.tryWithSuspend
import ani.dantotsu.util.Logger import ani.dantotsu.util.Logger
@ -153,10 +159,12 @@ import kotlinx.coroutines.runBlocking
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.util.Calendar import java.util.Calendar
import java.util.Locale
import java.util.Timer import java.util.Timer
import java.util.TimerTask import java.util.TimerTask
import java.util.concurrent.Executors import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import kotlin.collections.set
import kotlin.math.max import kotlin.math.max
import kotlin.math.min import kotlin.math.min
import kotlin.math.roundToInt import kotlin.math.roundToInt
@ -189,6 +197,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
private lateinit var exoSettings: ImageButton private lateinit var exoSettings: ImageButton
private lateinit var exoSubtitle: ImageButton private lateinit var exoSubtitle: ImageButton
private lateinit var exoSubtitleView: SubtitleView private lateinit var exoSubtitleView: SubtitleView
private lateinit var exoAudioTrack: ImageButton
private lateinit var exoRotate: ImageButton private lateinit var exoRotate: ImageButton
private lateinit var exoSpeed: ImageButton private lateinit var exoSpeed: ImageButton
private lateinit var exoScreen: ImageButton private lateinit var exoScreen: ImageButton
@ -211,6 +220,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
private var orientationListener: OrientationEventListener? = null private var orientationListener: OrientationEventListener? = null
private var downloadId: String? = null private var downloadId: String? = null
private var hasExtSubtitles = false
companion object { companion object {
var initialized = false var initialized = false
@ -287,7 +297,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
} }
} }
playerView.findViewById<View>(androidx.media3.ui.R.id.exo_buffering).translationY = playerView.findViewById<View>(androidx.media3.ui.R.id.exo_buffering).translationY =
(if (orientation == Configuration.ORIENTATION_LANDSCAPE) 0 else (notchHeight + 8f.px)).dp (if (orientation == Configuration.ORIENTATION_LANDSCAPE) 0 else (notchHeight + 8.toPx)).dp
exoBrightnessCont.updateLayoutParams<ViewGroup.MarginLayoutParams> { exoBrightnessCont.updateLayoutParams<ViewGroup.MarginLayoutParams> {
marginEnd = marginEnd =
if (orientation == Configuration.ORIENTATION_LANDSCAPE) notchHeight else 0 if (orientation == Configuration.ORIENTATION_LANDSCAPE) notchHeight else 0
@ -434,6 +444,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
exoSource = playerView.findViewById(R.id.exo_source) exoSource = playerView.findViewById(R.id.exo_source)
exoSettings = playerView.findViewById(R.id.exo_settings) exoSettings = playerView.findViewById(R.id.exo_settings)
exoSubtitle = playerView.findViewById(R.id.exo_sub) exoSubtitle = playerView.findViewById(R.id.exo_sub)
exoAudioTrack = playerView.findViewById(R.id.exo_audio)
exoSubtitleView = playerView.findViewById(androidx.media3.ui.R.id.exo_subtitles) exoSubtitleView = playerView.findViewById(androidx.media3.ui.R.id.exo_subtitles)
exoRotate = playerView.findViewById(R.id.exo_rotate) exoRotate = playerView.findViewById(R.id.exo_rotate)
exoSpeed = playerView.findViewById(androidx.media3.ui.R.id.exo_playback_speed) exoSpeed = playerView.findViewById(androidx.media3.ui.R.id.exo_playback_speed)
@ -475,14 +486,12 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
} }
rotation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE rotation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE
} }
in 225..315 -> { in 225..315 -> {
if (rotation != ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) { if (rotation != ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) {
exoRotate.visibility = View.VISIBLE exoRotate.visibility = View.VISIBLE
} }
rotation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE rotation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
} }
in 315..360, in 0..45 -> { in 315..360, in 0..45 -> {
if (rotation != ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) { if (rotation != ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) {
exoRotate.visibility = View.VISIBLE exoRotate.visibility = View.VISIBLE
@ -501,7 +510,6 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
it.visibility = View.GONE it.visibility = View.GONE
} }
} }
setupSubFormatting(playerView) setupSubFormatting(playerView)
if (savedInstanceState != null) { if (savedInstanceState != null) {
@ -915,8 +923,8 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
isFastForwarding = true isFastForwarding = true
exoPlayer.setPlaybackSpeed(exoPlayer.playbackParameters.speed * 2) exoPlayer.setPlaybackSpeed(exoPlayer.playbackParameters.speed * 2)
fastForward.visibility = View.VISIBLE fastForward.visibility = View.VISIBLE
val speed = "${exoPlayer.playbackParameters.speed}x" val speedText = "${exoPlayer.playbackParameters.speed}x"
fastForward.text = speed fastForward.text = speedText
} }
fun stopFastForward() { fun stopFastForward() {
@ -1139,6 +1147,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
it it
) )
}, },
smallImage = RPC.Link("Dantotsu", Discord.small_Image),
buttons = buttons buttons = buttons
) )
) )
@ -1208,7 +1217,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
putExtra("subtitle", subtitle) putExtra("subtitle", subtitle)
} }
exoPlayer.pause() exoPlayer.pause()
startActivity(intent) onChangeSettings.launch(intent)
} }
//Speed //Speed
@ -1364,7 +1373,6 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
val ext = episode.extractors?.find { it.server.name == episode.selectedExtractor } ?: return val ext = episode.extractors?.find { it.server.name == episode.selectedExtractor } ?: return
extractor = ext extractor = ext
video = ext.videos.getOrNull(episode.selectedVideo) ?: return video = ext.videos.getOrNull(episode.selectedVideo) ?: return
subtitle = intent.getSerialized("subtitle") subtitle = intent.getSerialized("subtitle")
?: when (val subLang: String? = ?: when (val subLang: String? =
PrefManager.getNullableCustomVal("subLang_${media.id}", null, String::class.java)) { PrefManager.getNullableCustomVal("subLang_${media.id}", null, String::class.java)) {
@ -1381,18 +1389,22 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
} }
//Subtitles //Subtitles
exoSubtitle.isVisible = ext.subtitles.isNotEmpty() hasExtSubtitles = ext.subtitles.isNotEmpty()
exoSubtitle.setOnClickListener { if (hasExtSubtitles) {
subClick() exoSubtitle.isVisible = hasExtSubtitles
exoSubtitle.setOnClickListener {
subClick()
}
} }
var sub: MediaItem.SubtitleConfiguration? = null val sub: MutableList<MediaItem.SubtitleConfiguration> = emptyList<MediaItem.SubtitleConfiguration>().toMutableList()
if (subtitle != null) { ext.subtitles.forEach { subtitle ->
val subtitleUrl = if (!hasExtSubtitles) video!!.file.url else subtitle.file.url
//var localFile: String? = null //var localFile: String? = null
if (subtitle?.type == SubtitleType.UNKNOWN) { if (subtitle.type == SubtitleType.UNKNOWN) {
runBlocking { runBlocking {
val type = SubtitleDownloader.loadSubtitleType(subtitle!!.file.url) val type = SubtitleDownloader.loadSubtitleType(subtitleUrl)
val fileUri = Uri.parse(subtitle!!.file.url) val fileUri = Uri.parse(subtitleUrl)
sub = MediaItem.SubtitleConfiguration sub += MediaItem.SubtitleConfiguration
.Builder(fileUri) .Builder(fileUri)
.setSelectionFlags(C.SELECTION_FLAG_DEFAULT) .setSelectionFlags(C.SELECTION_FLAG_DEFAULT)
.setMimeType( .setMimeType(
@ -1404,16 +1416,17 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
} }
) )
.setId("69") .setId("69")
.setLanguage(subtitle.language)
.build() .build()
} }
println("sub: $sub") println("sub: $sub")
} else { } else {
val subUri = Uri.parse(subtitle!!.file.url) val subUri = Uri.parse(subtitleUrl)
sub = MediaItem.SubtitleConfiguration sub += MediaItem.SubtitleConfiguration
.Builder(subUri) .Builder(subUri)
.setSelectionFlags(C.SELECTION_FLAG_FORCED) .setSelectionFlags(C.SELECTION_FLAG_FORCED)
.setMimeType( .setMimeType(
when (subtitle?.type) { when (subtitle.type) {
SubtitleType.VTT -> MimeTypes.TEXT_VTT SubtitleType.VTT -> MimeTypes.TEXT_VTT
SubtitleType.ASS -> MimeTypes.TEXT_SSA SubtitleType.ASS -> MimeTypes.TEXT_SSA
SubtitleType.SRT -> MimeTypes.APPLICATION_SUBRIP SubtitleType.SRT -> MimeTypes.APPLICATION_SUBRIP
@ -1421,6 +1434,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
} }
) )
.setId("69") .setId("69")
.setLanguage(subtitle.language)
.build() .build()
} }
} }
@ -1462,7 +1476,6 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
} }
val downloadedMediaItem = if (ext.server.offline) { val downloadedMediaItem = if (ext.server.offline) {
val key = ext.server.name
val titleName = ext.server.name.split("/").first() val titleName = ext.server.name.split("/").first()
val episodeName = ext.server.name.split("/").last() val episodeName = ext.server.name.split("/").last()
@ -1491,21 +1504,18 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
val builder = MediaItem.Builder().setUri(video!!.file.url).setMimeType(mimeType) val builder = MediaItem.Builder().setUri(video!!.file.url).setMimeType(mimeType)
Logger.log("url: ${video!!.file.url}") Logger.log("url: ${video!!.file.url}")
Logger.log("mimeType: $mimeType") Logger.log("mimeType: $mimeType")
builder.setSubtitleConfigurations(sub)
if (sub != null) {
val listofnotnullsubs = listOfNotNull(sub)
builder.setSubtitleConfigurations(listofnotnullsubs)
}
builder.build() builder.build()
} else { } else {
val addedSubsDownloadedMediaItem = downloadedMediaItem.buildUpon() if (sub.isNotEmpty()) {
if (sub != null) { val addedSubsDownloadedMediaItem = downloadedMediaItem.buildUpon()
val listofnotnullsubs = listOfNotNull(sub) val addLanguage = sub[0].buildUpon().setLanguage("en").build()
val addLanguage = listofnotnullsubs[0].buildUpon().setLanguage("en").build()
addedSubsDownloadedMediaItem.setSubtitleConfigurations(listOf(addLanguage)) addedSubsDownloadedMediaItem.setSubtitleConfigurations(listOf(addLanguage))
episode.selectedSubtitle = 0 episode.selectedSubtitle = 0
addedSubsDownloadedMediaItem.build()
} else {
downloadedMediaItem
} }
addedSubsDownloadedMediaItem.build()
} }
@ -1516,22 +1526,22 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
//Quality Track //Quality Track
trackSelector = DefaultTrackSelector(this) trackSelector = DefaultTrackSelector(this)
trackSelector.setParameters( val parameters = trackSelector.buildUponParameters()
trackSelector.buildUponParameters() .setAllowVideoMixedMimeTypeAdaptiveness(true)
.setAllowVideoMixedMimeTypeAdaptiveness(true) .setAllowVideoNonSeamlessAdaptiveness(true)
.setAllowVideoNonSeamlessAdaptiveness(true) .setSelectUndeterminedTextLanguage(true)
.setSelectUndeterminedTextLanguage(true) .setAllowAudioMixedMimeTypeAdaptiveness(true)
.setAllowAudioMixedMimeTypeAdaptiveness(true) .setAllowMultipleAdaptiveSelections(true)
.setAllowMultipleAdaptiveSelections(true) .setPreferredTextLanguage(subtitle?.language ?: Locale.getDefault().language)
.setPreferredTextLanguage(subtitle?.language ?: "en") .setPreferredTextRoleFlags(C.ROLE_FLAG_SUBTITLE)
.setPreferredTextRoleFlags(C.ROLE_FLAG_SUBTITLE) .setRendererDisabled(TRACK_TYPE_VIDEO, false)
.setRendererDisabled(TRACK_TYPE_VIDEO, false) .setRendererDisabled(TRACK_TYPE_AUDIO, false)
.setRendererDisabled(C.TRACK_TYPE_AUDIO, false) .setRendererDisabled(TRACK_TYPE_TEXT, false)
.setRendererDisabled(C.TRACK_TYPE_TEXT, false) .setMaxVideoSize(1, 1)
.setMaxVideoSize(1, 1) // .setOverrideForType(TrackSelectionOverride(trackSelector, TRACK_TYPE_VIDEO))
//.setOverrideForType( if (PrefManager.getVal(PrefName.SettingsPreferDub))
// TrackSelectionOverride(trackSelector, 2)) parameters.setPreferredAudioLanguage(Locale.getDefault().language)
) trackSelector.setParameters(parameters)
if (playbackPosition != 0L && !changingServer && !PrefManager.getVal<Boolean>(PrefName.AlwaysContinue)) { if (playbackPosition != 0L && !changingServer && !PrefManager.getVal<Boolean>(PrefName.AlwaysContinue)) {
val time = String.format( val time = String.format(
@ -1596,7 +1606,6 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
} }
playerView.player = exoPlayer playerView.player = exoPlayer
try { try {
val rightNow = Calendar.getInstance() val rightNow = Calendar.getInstance()
mediaSession = MediaSession.Builder(this, exoPlayer) mediaSession = MediaSession.Builder(this, exoPlayer)
@ -1609,32 +1618,11 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
exoPlayer.addListener(this) exoPlayer.addListener(this)
exoPlayer.addAnalyticsListener(EventLogger()) exoPlayer.addAnalyticsListener(EventLogger())
isInitialized = true isInitialized = true
}
/*private fun selectSubtitleTrack() { saving this for later
// Get the current track groups
val trackGroups = exoPlayer.currentTrackGroups
// Prepare a track selector parameters builder if (!hasExtSubtitles && !PrefManager.getVal<Boolean>(PrefName.Subtitles)) {
val parametersBuilder = DefaultTrackSelector.ParametersBuilder(this) onSetTrackGroupOverride(dummyTrack, TRACK_TYPE_TEXT)
// Iterate through the track groups to find the subtitle tracks
for (i in 0 until trackGroups.length) {
val trackGroup = trackGroups[i]
for (j in 0 until trackGroup.length) {
val trackMetadata = trackGroup.getFormat(j)
// Check if the track is a subtitle track
if (MimeTypes.isText(trackMetadata.sampleMimeType)) {
parametersBuilder.setRendererDisabled(i, false) // Enable the renderer for this track group
parametersBuilder.setSelectionOverride(i, trackGroups, DefaultTrackSelector.SelectionOverride(j, 0)) // Override to select this track
break
}
}
} }
}
// Apply the track selector parameters to select the subtitle
trackSelector.setParameters(parametersBuilder)
}*/
private fun releasePlayer() { private fun releasePlayer() {
isPlayerPlaying = exoPlayer.playWhenReady isPlayerPlaying = exoPlayer.playWhenReady
@ -1809,7 +1797,8 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
if (isInitialized) { if (isInitialized) {
val playerCurrentTime = exoPlayer.currentPosition / 1000 val playerCurrentTime = exoPlayer.currentPosition / 1000
currentTimeStamp = model.timeStamps.value?.find { timestamp -> currentTimeStamp = model.timeStamps.value?.find { timestamp ->
timestamp.interval.startTime < playerCurrentTime && playerCurrentTime < (timestamp.interval.endTime - 1) timestamp.interval.startTime < playerCurrentTime
&& playerCurrentTime < (timestamp.interval.endTime - 1)
} }
val new = currentTimeStamp val new = currentTimeStamp
@ -1830,11 +1819,13 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
} }
timer = object : CountDownTimer(5000, 1000) { timer = object : CountDownTimer(5000, 1000) {
override fun onTick(millisUntilFinished: Long) { override fun onTick(millisUntilFinished: Long) {
if (new == null) {
skipTimeButton.visibility = View.GONE skipTimeButton.visibility = View.GONE
exoSkip.isVisible = PrefManager.getVal<Int>(PrefName.SkipTime) > 0 exoSkip.isVisible = PrefManager.getVal<Int>(PrefName.SkipTime) > 0
disappeared = false disappeared = false
functionstarted = false functionstarted = false
cancelTimer() cancelTimer()
}
} }
override fun onFinish() { override fun onFinish() {
@ -1886,46 +1877,98 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
}, 500) }, 500)
} }
fun onSetTrackGroupOverride(trackGroup: Tracks.Group, type: @C.TrackType Int, index: Int = 0) {
val isDisabled = trackGroup.getTrackFormat(0).language == "none"
exoPlayer.trackSelectionParameters = exoPlayer.trackSelectionParameters
.buildUpon()
.setTrackTypeDisabled(TRACK_TYPE_TEXT, isDisabled)
.setOverrideForType(
TrackSelectionOverride(trackGroup.mediaTrackGroup, index)
)
.build()
if (type == TRACK_TYPE_TEXT) setupSubFormatting(playerView)
playerView.subtitleView?.alpha = when (isDisabled) {
false -> PrefManager.getVal(PrefName.SubAlpha)
true -> 0f
}
}
private val dummyTrack = Tracks.Group(
TrackGroup("Dummy Track", Format.Builder().apply { setLanguage("none") }.build()),
true,
intArrayOf(1),
booleanArrayOf(false)
)
override fun onTracksChanged(tracks: Tracks) { override fun onTracksChanged(tracks: Tracks) {
val audioTracks: ArrayList<Tracks.Group> = arrayListOf()
val subTracks: ArrayList<Tracks.Group> = arrayListOf(dummyTrack)
tracks.groups.forEach { tracks.groups.forEach {
println("Track__: $it\nTrack__: ${it.length}\nTrack__: ${it.isSelected}\n" + 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) { when (it.type) {
C.TRACK_TYPE_AUDIO -> { } TRACK_TYPE_AUDIO -> {
C.TRACK_TYPE_TEXT -> { if (it.isSupported(true)) audioTracks.add(it)
}
TRACK_TYPE_TEXT -> {
if (!hasExtSubtitles) {
if (
it.isSupported(true) &&
it.mediaTrackGroup.id != "Dummy Track"
) subTracks.add(it)
return@forEach
}
if (it.mediaTrackGroup.id == "1:") { if (it.mediaTrackGroup.id == "1:") {
playerView.player?.trackSelectionParameters = onSetTrackGroupOverride(it, TRACK_TYPE_TEXT, it.length - 1)
playerView.player?.trackSelectionParameters?.buildUpon()
?.setOverrideForType(
TrackSelectionOverride(it.mediaTrackGroup, it.length - 1)
)
?.build()!!
} else { } else {
playerView.player?.trackSelectionParameters = onSetTrackGroupOverride(dummyTrack, TRACK_TYPE_TEXT)
playerView.player?.trackSelectionParameters?.buildUpon()
?.addOverride(
TrackSelectionOverride(it.mediaTrackGroup, listOf())
)
?.build()!!
} }
} }
C.TRACK_TYPE_VIDEO -> { }
} }
} }
println("Track: ${tracks.groups.size}") exoAudioTrack.isVisible = audioTracks.size > 1
exoAudioTrack.setOnClickListener {
TrackGroupDialogFragment(this, audioTracks, TRACK_TYPE_AUDIO)
.show(supportFragmentManager, "dialog")
}
if (!hasExtSubtitles) {
exoSubtitle.isVisible = subTracks.size > 1
exoSubtitle.setOnClickListener {
TrackGroupDialogFragment(this, subTracks, TRACK_TYPE_TEXT)
.show(supportFragmentManager, "dialog")
}
}
}
private val onChangeSettings = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { _: ActivityResult ->
if (!hasExtSubtitles) {
exoPlayer.currentTracks.groups.forEach { trackGroup ->
when (trackGroup.type) {
TRACK_TYPE_TEXT -> {
if (PrefManager.getVal(PrefName.Subtitles)) {
onSetTrackGroupOverride(trackGroup, TRACK_TYPE_TEXT)
} else {
onSetTrackGroupOverride(dummyTrack, TRACK_TYPE_TEXT)
}
}
else -> { }
}
}
}
if (isInitialized) exoPlayer.play()
} }
override fun onPlayerError(error: PlaybackException) { override fun onPlayerError(error: PlaybackException) {
when (error.errorCode) { when (error.errorCode) {
PlaybackException.ERROR_CODE_IO_BAD_HTTP_STATUS, PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_FAILED PlaybackException.ERROR_CODE_IO_BAD_HTTP_STATUS,
-> { PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_FAILED -> {
toast("Source Exception : ${error.message}") toast("Source Exception : ${error.message}")
isPlayerPlaying = true isPlayerPlaying = true
sourceClick() sourceClick()
} }
else -> {
else
-> {
toast("Player Error ${error.errorCode} (${error.errorCodeName}) : ${error.message}") toast("Player Error ${error.errorCode} (${error.errorCodeName}) : ${error.message}")
Injekt.get<CrashlyticsInterface>().logException(error) Injekt.get<CrashlyticsInterface>().logException(error)
} }
@ -1935,7 +1978,6 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
private var isBuffering = true private var isBuffering = true
override fun onPlaybackStateChanged(playbackState: Int) { override fun onPlaybackStateChanged(playbackState: Int) {
if (playbackState == ExoPlayer.STATE_READY) { if (playbackState == ExoPlayer.STATE_READY) {
exoPlayer.play() exoPlayer.play()
if (episodeLength == 0f) { if (episodeLength == 0f) {
episodeLength = exoPlayer.duration.toFloat() episodeLength = exoPlayer.duration.toFloat()
@ -1990,6 +2032,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
} }
} }
@SuppressLint("UnsafeIntentLaunch")
override fun onNewIntent(intent: Intent?) { override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent) super.onNewIntent(intent)
finishAndRemoveTask() finishAndRemoveTask()
@ -2019,10 +2062,11 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
// Cast // Cast
private fun cast() { private fun cast() {
val videoURL = video?.file?.url ?: return val videoURL = video?.file?.url ?: return
val subtitleUrl = if (!hasExtSubtitles) video!!.file.url else subtitle!!.file.url
val shareVideo = Intent(Intent.ACTION_VIEW) val shareVideo = Intent(Intent.ACTION_VIEW)
shareVideo.setDataAndType(Uri.parse(videoURL), "video/*") shareVideo.setDataAndType(Uri.parse(videoURL), "video/*")
shareVideo.setPackage("com.instantbits.cast.webvideo") shareVideo.setPackage("com.instantbits.cast.webvideo")
if (subtitle != null) shareVideo.putExtra("subtitle", subtitle!!.file.url) if (subtitle != null) shareVideo.putExtra("subtitle", subtitleUrl)
shareVideo.putExtra( shareVideo.putExtra(
"title", "title",
media.userPreferredName + " : Ep " + episodeTitleArr[currentEpisodeIndex] media.userPreferredName + " : Ep " + episodeTitleArr[currentEpisodeIndex]
@ -2218,4 +2262,4 @@ class CustomCastButton : MediaRouteButton {
true true
} }
} }
} }

View file

@ -0,0 +1,113 @@
package ani.dantotsu.media.anime
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.OptIn
import androidx.core.view.isVisible
import androidx.media3.common.C.TRACK_TYPE_AUDIO
import androidx.media3.common.C.TrackType
import androidx.media3.common.Tracks
import androidx.media3.common.util.UnstableApi
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import ani.dantotsu.BottomSheetDialogFragment
import ani.dantotsu.R
import ani.dantotsu.databinding.BottomSheetSubtitlesBinding
import ani.dantotsu.databinding.ItemSubtitleTextBinding
import java.util.Locale
@OptIn(UnstableApi::class)
class TrackGroupDialogFragment(
instance: ExoplayerView, trackGroups: ArrayList<Tracks.Group>, type : @TrackType Int
) : BottomSheetDialogFragment() {
private var _binding: BottomSheetSubtitlesBinding? = null
private val binding get() = _binding!!
private var instance: ExoplayerView
private var trackGroups: ArrayList<Tracks.Group>
private var type: @TrackType Int
init {
this.instance = instance
this.trackGroups = trackGroups
this.type = type
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = BottomSheetSubtitlesBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
if (type == TRACK_TYPE_AUDIO) binding.selectionTitle.text = getString(R.string.audio_tracks)
binding.subtitlesRecycler.layoutManager = LinearLayoutManager(requireContext())
binding.subtitlesRecycler.adapter = TrackGroupAdapter()
}
inner class TrackGroupAdapter : RecyclerView.Adapter<TrackGroupAdapter.StreamViewHolder>() {
inner class StreamViewHolder(val binding: ItemSubtitleTextBinding) :
RecyclerView.ViewHolder(binding.root)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): StreamViewHolder =
StreamViewHolder(
ItemSubtitleTextBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
@OptIn(UnstableApi::class)
override fun onBindViewHolder(holder: StreamViewHolder, position: Int) {
val binding = holder.binding
trackGroups[position].let { trackGroup ->
when (val language = trackGroup.getTrackFormat(0).language?.lowercase()) {
null -> {
binding.subtitleTitle.text = getString(R.string.unknown_track, "Track $position")
}
"none" -> {
binding.subtitleTitle.text = getString(R.string.disabled_track)
}
else -> {
val locale = if (language.contains("-")) {
val parts = language.split("-")
try {
Locale(parts[0], parts[1])
} catch (ignored: Exception) { null }
} else {
try {
Locale(language)
} catch (ignored: Exception) { null }
}
binding.subtitleTitle.text = locale?.let {
"[${it.language}] ${it.displayName}"
} ?: getString(R.string.unknown_track, language)
}
}
if (trackGroup.isSelected) {
val selected = "${binding.subtitleTitle.text}"
binding.subtitleTitle.text = selected
}
binding.root.setOnClickListener {
dismiss()
instance.onSetTrackGroupOverride(trackGroup, type)
}
}
}
override fun getItemCount(): Int = trackGroups.size
}
override fun onDestroy() {
_binding = null
super.onDestroy()
}
}

View file

@ -12,6 +12,7 @@
android:orientation="vertical"> android:orientation="vertical">
<TextView <TextView
android:id="@+id/selectionTitle"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="16dp" android:layout_margin="16dp"

View file

@ -132,11 +132,22 @@
android:layout_width="48dp" android:layout_width="48dp"
android:layout_height="48dp" android:layout_height="48dp"
android:backgroundTint="#00FFFFFF" android:backgroundTint="#00FFFFFF"
android:visibility="gone"
android:scaleX="-1" android:scaleX="-1"
android:src="@drawable/ic_round_subtitles_24" android:src="@drawable/ic_round_subtitles_24"
app:tint="#fff" app:tint="#fff"
tools:ignore="ContentDescription,SpeakableTextPresentCheck" /> tools:ignore="ContentDescription,SpeakableTextPresentCheck" />
<ImageButton
android:id="@+id/exo_audio"
android:layout_width="48dp"
android:layout_height="48dp"
android:backgroundTint="#00FFFFFF"
android:visibility="gone"
android:src="@drawable/ic_round_audiotrack_24"
app:tint="#fff"
tools:ignore="ContentDescription,SpeakableTextPresentCheck" />
<View <View
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="match_parent" android:layout_height="match_parent"

View file

@ -891,7 +891,10 @@ Non quae tempore quo provident laudantium qui illo dolor vel quia dolor et exerc
<string name="social">Social</string> <string name="social">Social</string>
<string name="auto_skip_recap">Auto Skip Recap</string> <string name="auto_skip_recap">Auto Skip Recap</string>
<string name="use_anilist_icon">Use AniList Icon</string> <string name="use_anilist_icon">Use AniList Icon</string>
<string name="audio_tracks">Audio Tracks</string>
<string name="disabled_track">Disabled</string>
<string name="invalid_track">Invalid</string>
<string name="unknown_track">\[%1$s\] Unknown</string>
<string name="accounts_desc">Anilist, MAL and Discord.\nWhat more could you need?</string> <string name="accounts_desc">Anilist, MAL and Discord.\nWhat more could you need?</string>
<string name="theme_desc">Change the vibe of your app</string> <string name="theme_desc">Change the vibe of your app</string>
<string name="extensions_desc">Manage your reliable repositories</string> <string name="extensions_desc">Manage your reliable repositories</string>