feat(exoplayer): custom subtitle view (#544)

* branch

* Add Custom Subtitles
This commit is contained in:
Sadwhy 2024-12-15 21:19:40 +06:00 committed by GitHub
parent eac4604b3d
commit b04a176870
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 478 additions and 49 deletions

View file

@ -12,6 +12,7 @@ import android.content.Intent
import android.content.pm.ActivityInfo import android.content.pm.ActivityInfo
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.content.res.Configuration import android.content.res.Configuration
import android.content.res.Resources
import android.graphics.Color import android.graphics.Color
import android.graphics.drawable.Animatable import android.graphics.drawable.Animatable
import android.hardware.SensorManager import android.hardware.SensorManager
@ -71,9 +72,12 @@ 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.text.Cue
import androidx.media3.common.text.CueGroup
import androidx.media3.common.TrackGroup 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.Util
import androidx.media3.common.util.UnstableApi import androidx.media3.common.util.UnstableApi
import androidx.media3.datasource.DataSource import androidx.media3.datasource.DataSource
import androidx.media3.datasource.DefaultDataSource import androidx.media3.datasource.DefaultDataSource
@ -137,6 +141,7 @@ import ani.dantotsu.others.getSerialized
import ani.dantotsu.parsers.AnimeSources import ani.dantotsu.parsers.AnimeSources
import ani.dantotsu.parsers.HAnimeSources import ani.dantotsu.parsers.HAnimeSources
import ani.dantotsu.parsers.Subtitle import ani.dantotsu.parsers.Subtitle
import ani.dantotsu.others.Xubtitle
import ani.dantotsu.parsers.SubtitleType 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
@ -226,6 +231,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
private lateinit var animeTitle: TextView private lateinit var animeTitle: TextView
private lateinit var videoInfo: TextView private lateinit var videoInfo: TextView
private lateinit var episodeTitle: Spinner private lateinit var episodeTitle: Spinner
private lateinit var customSubtitleView: Xubtitle
private var orientationListener: OrientationEventListener? = null private var orientationListener: OrientationEventListener? = null
@ -425,6 +431,95 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
} }
} }
private fun applySubtitleStyles(textView: Xubtitle) {
val primaryColor = when (PrefManager.getVal<Int>(PrefName.PrimaryColor)) {
0 -> Color.BLACK
1 -> Color.DKGRAY
2 -> Color.GRAY
3 -> Color.LTGRAY
4 -> Color.WHITE
5 -> Color.RED
6 -> Color.YELLOW
7 -> Color.GREEN
8 -> Color.CYAN
9 -> Color.BLUE
10 -> Color.MAGENTA
11 -> Color.TRANSPARENT
else -> Color.WHITE
}
val subBackground = when (PrefManager.getVal<Int>(PrefName.SubBackground)) {
0 -> Color.TRANSPARENT
1 -> Color.BLACK
2 -> Color.DKGRAY
3 -> Color.GRAY
4 -> Color.LTGRAY
5 -> Color.WHITE
6 -> Color.RED
7 -> Color.YELLOW
8 -> Color.GREEN
9 -> Color.CYAN
10 -> Color.BLUE
11 -> Color.MAGENTA
else -> Color.TRANSPARENT
}
val font = when (PrefManager.getVal<Int>(PrefName.Font)) {
0 -> ResourcesCompat.getFont(this, R.font.poppins_semi_bold)
1 -> ResourcesCompat.getFont(this, R.font.poppins_bold)
2 -> ResourcesCompat.getFont(this, R.font.poppins)
3 -> ResourcesCompat.getFont(this, R.font.poppins_thin)
4 -> ResourcesCompat.getFont(this, R.font.century_gothic_regular)
5 -> ResourcesCompat.getFont(this, R.font.levenim_mt_bold)
6 -> ResourcesCompat.getFont(this, R.font.blocky)
else -> ResourcesCompat.getFont(this, R.font.poppins_semi_bold)
}
val fontSize = PrefManager.getVal<Int>(PrefName.FontSize).toFloat()
textView.setBackgroundColor(subBackground)
textView.setTextColor(primaryColor)
textView.typeface = font
textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, fontSize)
val secondaryColor = when (PrefManager.getVal<Int>(PrefName.SecondaryColor)) {
0 -> Color.BLACK
1 -> Color.DKGRAY
2 -> Color.GRAY
3 -> Color.LTGRAY
4 -> Color.WHITE
5 -> Color.RED
6 -> Color.YELLOW
7 -> Color.GREEN
8 -> Color.CYAN
9 -> Color.BLUE
10 -> Color.MAGENTA
11 -> Color.TRANSPARENT
else -> Color.BLACK
}
val subStroke = PrefManager.getVal<Float>(PrefName.SubStroke)
textView.apply {
when (PrefManager.getVal<Int>(PrefName.Outline)) {
0 -> applyOutline(secondaryColor, subStroke)
1 -> applyShineEffect(secondaryColor)
2 -> applyDropShadow(secondaryColor, subStroke)
3 -> {}
else -> applyOutline(secondaryColor, subStroke)
}
}
textView.alpha =
when (PrefManager.getVal<Boolean>(PrefName.Subtitles)) {
true -> PrefManager.getVal(PrefName.SubAlpha)
false -> 0f
}
val textElevation = PrefManager.getVal<Float>(PrefName.SubBottomMargin) / 30 * resources.displayMetrics.heightPixels
textView.translationY = -textElevation
}
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -470,6 +565,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
skipTimeButton = playerView.findViewById(R.id.exo_skip_timestamp) skipTimeButton = playerView.findViewById(R.id.exo_skip_timestamp)
skipTimeText = skipTimeButton.findViewById(R.id.exo_skip_timestamp_text) skipTimeText = skipTimeButton.findViewById(R.id.exo_skip_timestamp_text)
timeStampText = playerView.findViewById(R.id.exo_time_stamp_text) timeStampText = playerView.findViewById(R.id.exo_time_stamp_text)
customSubtitleView = playerView.findViewById(R.id.customSubtitleView)
animeTitle = playerView.findViewById(R.id.exo_anime_title) animeTitle = playerView.findViewById(R.id.exo_anime_title)
episodeTitle = playerView.findViewById(R.id.exo_ep_sel) episodeTitle = playerView.findViewById(R.id.exo_ep_sel)
@ -523,7 +619,6 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
it.visibility = View.GONE it.visibility = View.GONE
} }
} }
setupSubFormatting(playerView)
if (savedInstanceState != null) { if (savedInstanceState != null) {
currentWindow = savedInstanceState.getInt(resumeWindow) currentWindow = savedInstanceState.getInt(resumeWindow)
@ -1732,6 +1827,54 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
} }
playerView.player = exoPlayer playerView.player = exoPlayer
exoPlayer.addListener(object : Player.Listener {
var activeSubtitles = ArrayDeque<String>(3)
var lastSubtitle: String? = null
var lastPosition: Long = 0
override fun onCues(cueGroup: CueGroup) {
if (PrefManager.getVal<Boolean>(PrefName.TextviewSubtitles)) {
exoSubtitleView.visibility = View.GONE
customSubtitleView.visibility = View.VISIBLE
val newCues = cueGroup.cues.map { it.text.toString() ?: "" }
if (newCues.isEmpty()) {
customSubtitleView.text = ""
activeSubtitles.clear()
lastSubtitle = null
lastPosition = 0
return
}
val currentPosition = exoPlayer.currentPosition
if ((lastSubtitle?.length ?: 0) < 20 || (lastPosition != 0L && currentPosition - lastPosition > 1500)) {
activeSubtitles.clear()
}
for (newCue in newCues) {
if (newCue !in activeSubtitles) {
if (activeSubtitles.size >= 2) {
activeSubtitles.removeLast()
}
activeSubtitles.addFirst(newCue)
lastSubtitle = newCue
lastPosition = currentPosition
}
}
customSubtitleView.text = activeSubtitles.joinToString("\n")
} else {
customSubtitleView.text = ""
customSubtitleView.visibility = View.GONE
exoSubtitleView.visibility = View.VISIBLE
}
}
})
applySubtitleStyles(customSubtitleView)
setupSubFormatting(playerView)
try { try {
val rightNow = Calendar.getInstance() val rightNow = Calendar.getInstance()
mediaSession = MediaSession.Builder(this, exoPlayer) mediaSession = MediaSession.Builder(this, exoPlayer)
@ -2021,7 +2164,10 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
TrackSelectionOverride(trackGroup.mediaTrackGroup, index) TrackSelectionOverride(trackGroup.mediaTrackGroup, index)
) )
.build() .build()
if (type == TRACK_TYPE_TEXT) setupSubFormatting(playerView) if (type == TRACK_TYPE_TEXT) {
setupSubFormatting(playerView)
applySubtitleStyles(customSubtitleView)
}
playerView.subtitleView?.alpha = when (isDisabled) { playerView.subtitleView?.alpha = when (isDisabled) {
false -> PrefManager.getVal(PrefName.SubAlpha) false -> PrefManager.getVal(PrefName.SubAlpha)
true -> 0f true -> 0f
@ -2395,4 +2541,4 @@ class CustomCastButton : MediaRouteButton {
true true
} }
} }
} }

View file

@ -0,0 +1,144 @@
package ani.dantotsu.others
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.LinearGradient
import android.graphics.Paint
import android.graphics.Shader
import android.text.Layout
import android.text.StaticLayout
import android.text.TextPaint
import android.util.AttributeSet
import androidx.appcompat.widget.AppCompatTextView
class Xubtitle
@JvmOverloads
constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
) : AppCompatTextView(context, attrs, defStyleAttr) {
private var outlineThickness: Float = 0f
private var effectColor: Int = currentTextColor
private var currentEffect: Effect = Effect.NONE
private val shadowPaint = Paint().apply { isAntiAlias = true }
private val outlinePaint = Paint().apply { isAntiAlias = true }
private var shineShader: Shader? = null
enum class Effect {
NONE,
OUTLINE,
SHINE,
DROP_SHADOW,
}
override fun onDraw(canvas: Canvas) {
val text = text.toString()
val textPaint =
TextPaint(paint).apply {
color = currentTextColor
}
val staticLayout =
StaticLayout.Builder
.obtain(text, 0, text.length, textPaint, width)
.setAlignment(Layout.Alignment.ALIGN_CENTER)
.setLineSpacing(0f, 1f)
.build()
when (currentEffect) {
Effect.OUTLINE -> {
textPaint.style = Paint.Style.STROKE
textPaint.strokeWidth = outlineThickness
textPaint.color = effectColor
staticLayout.draw(canvas)
textPaint.style = Paint.Style.FILL
textPaint.color = currentTextColor
staticLayout.draw(canvas)
}
Effect.DROP_SHADOW -> {
setLayerType(LAYER_TYPE_SOFTWARE, null)
textPaint.setShadowLayer(outlineThickness, 4f, 4f, effectColor)
staticLayout.draw(canvas)
textPaint.clearShadowLayer()
}
Effect.SHINE -> {
val shadowShader =
LinearGradient(
0f,
0f,
width.toFloat(),
height.toFloat(),
intArrayOf(Color.WHITE, effectColor, Color.BLACK),
null,
Shader.TileMode.CLAMP,
)
val shadowPaint =
Paint().apply {
isAntiAlias = true
style = Paint.Style.FILL
textSize = textPaint.textSize
typeface = textPaint.typeface
shader = shadowShader
}
canvas.drawText(
text,
x + 4f, // Shadow offset
y + 4f,
shadowPaint,
)
val shader =
LinearGradient(
0f,
0f,
width.toFloat(),
height.toFloat(),
intArrayOf(effectColor, Color.WHITE, Color.WHITE),
null,
Shader.TileMode.CLAMP,
)
textPaint.shader = shader
staticLayout.draw(canvas)
textPaint.shader = null
}
Effect.NONE -> {
staticLayout.draw(canvas)
}
}
}
fun applyOutline(
color: Int,
outlineThickness: Float,
) {
this.effectColor = color
this.outlineThickness = outlineThickness
currentEffect = Effect.OUTLINE
}
// Too hard for me to figure it out
fun applyShineEffect(color: Int) {
this.effectColor = color
currentEffect = Effect.SHINE
}
fun applyDropShadow(
color: Int,
outlineThickness: Float,
) {
this.effectColor = color
this.outlineThickness = outlineThickness
currentEffect = Effect.DROP_SHADOW
}
}

View file

@ -267,21 +267,21 @@ class PlayerSettingsActivity : AppCompatActivity() {
PrefManager.setVal(PrefName.Cast, isChecked) PrefManager.setVal(PrefName.Cast, isChecked)
} }
binding.playerSettingsRotate.isChecked = PrefManager.getVal(PrefName.RotationPlayer)
binding.playerSettingsRotate.setOnCheckedChangeListener { _, isChecked ->
PrefManager.setVal(PrefName.RotationPlayer, isChecked)
}
binding.playerSettingsInternalCast.isChecked = PrefManager.getVal(PrefName.UseInternalCast) binding.playerSettingsInternalCast.isChecked = PrefManager.getVal(PrefName.UseInternalCast)
binding.playerSettingsInternalCast.setOnCheckedChangeListener { _, isChecked -> binding.playerSettingsInternalCast.setOnCheckedChangeListener { _, isChecked ->
PrefManager.setVal(PrefName.UseInternalCast, isChecked) PrefManager.setVal(PrefName.UseInternalCast, isChecked)
} }
binding.playerSettingsRotate.isChecked = PrefManager.getVal(PrefName.RotationPlayer)
binding.playerSettingsRotate.setOnCheckedChangeListener { _, isChecked ->
PrefManager.setVal(PrefName.RotationPlayer, isChecked)
}
binding.playerSettingsAdditionalCodec.isChecked = PrefManager.getVal(PrefName.UseAdditionalCodec) binding.playerSettingsAdditionalCodec.isChecked = PrefManager.getVal(PrefName.UseAdditionalCodec)
binding.playerSettingsAdditionalCodec.setOnCheckedChangeListener { _, isChecked -> binding.playerSettingsAdditionalCodec.setOnCheckedChangeListener { _, isChecked ->
PrefManager.setVal(PrefName.UseAdditionalCodec, isChecked) PrefManager.setVal(PrefName.UseAdditionalCodec, isChecked)
} }
val resizeModes = arrayOf("Original", "Zoom", "Stretch") val resizeModes = arrayOf("Original", "Zoom", "Stretch")
binding.playerResizeMode.setOnClickListener { binding.playerResizeMode.setOnClickListener {
customAlertDialog().apply { customAlertDialog().apply {
@ -306,23 +306,50 @@ class PlayerSettingsActivity : AppCompatActivity() {
binding.videoSubColorWindow, binding.videoSubColorWindow,
binding.videoSubFont, binding.videoSubFont,
binding.videoSubAlpha, binding.videoSubAlpha,
binding.videoSubStroke,
binding.subtitleFontSizeText, binding.subtitleFontSizeText,
binding.subtitleFontSize binding.subtitleFontSize,
binding.videoSubLanguage,
binding.subTextSwitch
).forEach { ).forEach {
it.isEnabled = isChecked it.isEnabled = isChecked
it.isClickable = isChecked
it.alpha = when (isChecked) { it.alpha = when (isChecked) {
true -> 1f true -> 1f
false -> 0.5f false -> 0.5f
} }
} }
} }
fun toggleExpSubOptions(isChecked: Boolean) {
arrayOf(
binding.videoSubStrokeButton,
binding.videoSubStroke,
binding.videoSubBottomMarginButton,
binding.videoSubBottomMargin
).forEach {
it.isEnabled = isChecked
it.alpha = when (isChecked) {
true -> 1f
false -> 0.5f
}
}
}
binding.subSwitch.isChecked = PrefManager.getVal(PrefName.Subtitles) binding.subSwitch.isChecked = PrefManager.getVal(PrefName.Subtitles)
binding.subSwitch.setOnCheckedChangeListener { _, isChecked -> binding.subSwitch.setOnCheckedChangeListener { _, isChecked ->
PrefManager.setVal(PrefName.Subtitles, isChecked) PrefManager.setVal(PrefName.Subtitles, isChecked)
toggleSubOptions(isChecked) toggleSubOptions(isChecked)
toggleExpSubOptions(binding.subTextSwitch.isChecked && isChecked)
} }
toggleSubOptions(binding.subSwitch.isChecked) toggleSubOptions(binding.subSwitch.isChecked)
binding.subTextSwitch.isChecked = PrefManager.getVal(PrefName.TextviewSubtitles)
binding.subTextSwitch.setOnCheckedChangeListener { _, isChecked ->
PrefManager.setVal(PrefName.TextviewSubtitles, isChecked)
toggleExpSubOptions(isChecked)
}
toggleExpSubOptions(binding.subTextSwitch.isChecked)
val subLanguages = arrayOf( val subLanguages = arrayOf(
"Albanian", "Albanian",
"Arabic", "Arabic",
@ -366,17 +393,17 @@ class PlayerSettingsActivity : AppCompatActivity() {
"Urdu", "Urdu",
"Vietnamese", "Vietnamese",
) )
val subLanguageDialog = AlertDialog.Builder(this, R.style.MyPopup)
.setTitle(getString(R.string.subtitle_langauge))
binding.videoSubLanguage.setOnClickListener { binding.videoSubLanguage.setOnClickListener {
val dialog = subLanguageDialog.setSingleChoiceItems( customAlertDialog().apply {
subLanguages, setTitle(getString(R.string.subtitle_langauge))
PrefManager.getVal(PrefName.SubLanguage) singleChoiceItems(
) { dialog, count -> subLanguages,
PrefManager.setVal(PrefName.SubLanguage, count) PrefManager.getVal(PrefName.SubLanguage)
dialog.dismiss() ) { count ->
}.show() PrefManager.setVal(PrefName.SubLanguage, count)
dialog.window?.setDimAmount(0.8f) }
show()
}
} }
val colorsPrimary = val colorsPrimary =
arrayOf( arrayOf(
@ -510,6 +537,22 @@ class PlayerSettingsActivity : AppCompatActivity() {
} }
}) })
binding.videoSubStroke.value = PrefManager.getVal(PrefName.SubStroke)
binding.videoSubStroke.addOnChangeListener(OnChangeListener { _, value, fromUser ->
if (fromUser) {
PrefManager.setVal(PrefName.SubStroke, value)
updateSubPreview()
}
})
binding.videoSubBottomMargin.value = PrefManager.getVal(PrefName.SubBottomMargin)
binding.videoSubBottomMargin.addOnChangeListener(OnChangeListener { _, value, fromUser ->
if (fromUser) {
PrefManager.setVal(PrefName.SubBottomMargin, value)
updateSubPreview()
}
})
val fonts = arrayOf( val fonts = arrayOf(
"Poppins Semi Bold", "Poppins Semi Bold",
"Poppins Bold", "Poppins Bold",
@ -625,4 +668,4 @@ class PlayerSettingsActivity : AppCompatActivity() {
) )
} }
} }
} }

View file

@ -94,6 +94,7 @@ enum class PrefName(val data: Pref) { //TODO: Split this into multiple files
CursedSpeeds(Pref(Location.Player, Boolean::class, false)), CursedSpeeds(Pref(Location.Player, Boolean::class, false)),
Resize(Pref(Location.Player, Int::class, 0)), Resize(Pref(Location.Player, Int::class, 0)),
Subtitles(Pref(Location.Player, Boolean::class, true)), Subtitles(Pref(Location.Player, Boolean::class, true)),
TextviewSubtitles(Pref(Location.Player, Boolean::class, false)),
SubLanguage(Pref(Location.Player, Int::class, 9)), SubLanguage(Pref(Location.Player, Int::class, 9)),
PrimaryColor(Pref(Location.Player, Int::class, 4)), PrimaryColor(Pref(Location.Player, Int::class, 4)),
SecondaryColor(Pref(Location.Player, Int::class, 0)), SecondaryColor(Pref(Location.Player, Int::class, 0)),
@ -101,6 +102,8 @@ enum class PrefName(val data: Pref) { //TODO: Split this into multiple files
SubBackground(Pref(Location.Player, Int::class, 0)), SubBackground(Pref(Location.Player, Int::class, 0)),
SubWindow(Pref(Location.Player, Int::class, 0)), SubWindow(Pref(Location.Player, Int::class, 0)),
SubAlpha(Pref(Location.Player, Float::class, 1f)), SubAlpha(Pref(Location.Player, Float::class, 1f)),
SubStroke(Pref(Location.Player, Float::class, 8f)),
SubBottomMargin(Pref(Location.Player, Float::class, 4f)),
Font(Pref(Location.Player, Int::class, 0)), Font(Pref(Location.Player, Int::class, 0)),
FontSize(Pref(Location.Player, Int::class, 20)), FontSize(Pref(Location.Player, Int::class, 20)),
Locale(Pref(Location.Player, Int::class, 2)), Locale(Pref(Location.Player, Int::class, 2)),
@ -128,7 +131,7 @@ enum class PrefName(val data: Pref) { //TODO: Split this into multiple files
Pip(Pref(Location.Player, Boolean::class, true)), Pip(Pref(Location.Player, Boolean::class, true)),
RotationPlayer(Pref(Location.Player, Boolean::class, true)), RotationPlayer(Pref(Location.Player, Boolean::class, true)),
TorrentEnabled(Pref(Location.Player, Boolean::class, false)), TorrentEnabled(Pref(Location.Player, Boolean::class, false)),
UseAdditionalCodec(Pref(Location.Player, Boolean::class, true)), UseAdditionalCodec(Pref(Location.Player, Boolean::class, false)),
//Reader //Reader
ShowSource(Pref(Location.Reader, Boolean::class, true)), ShowSource(Pref(Location.Reader, Boolean::class, true)),
@ -217,4 +220,4 @@ enum class PrefName(val data: Pref) { //TODO: Split this into multiple files
Socks5ProxyPort(Pref(Location.Protected, String::class, "")), Socks5ProxyPort(Pref(Location.Protected, String::class, "")),
Socks5ProxyUsername(Pref(Location.Protected, String::class, "")), Socks5ProxyUsername(Pref(Location.Protected, String::class, "")),
Socks5ProxyPassword(Pref(Location.Protected, String::class, "")), Socks5ProxyPassword(Pref(Location.Protected, String::class, "")),
} }

View file

@ -381,6 +381,88 @@
</com.google.android.material.slider.Slider> </com.google.android.material.slider.Slider>
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/subTextSwitch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:checked="true"
android:drawableStart="@drawable/ic_round_subtitles_24"
android:drawablePadding="16dp"
android:elegantTextHeight="false"
android:fontFamily="@font/poppins_bold"
android:minHeight="64dp"
android:paddingHorizontal="32dp"
android:text="@string/textview_sub"
android:textAlignment="viewStart"
android:textColor="@color/bg_opp"
app:cornerRadius="0dp"
app:drawableTint="?attr/colorPrimary"
app:showText="false"
app:thumbTint="@color/button_switch_track" />
<Button
android:id="@+id/videoSubStrokeButton"
style="@style/Widget.Material3.Button.TextButton"
android:layout_width="match_parent"
android:layout_height="64dp"
android:layout_marginBottom="8dp"
android:clickable="false"
android:fontFamily="@font/poppins_bold"
android:insetTop="0dp"
android:insetBottom="0dp"
android:paddingHorizontal="32dp"
android:text="@string/textview_sub_stroke"
android:textAlignment="viewStart"
android:textAllCaps="false"
android:textColor="@color/bg_opp"
app:cornerRadius="0dp"
app:icon="@drawable/ic_round_color_24"
app:iconPadding="16dp"
app:iconSize="24dp" />
<com.google.android.material.slider.Slider
android:id="@+id/videoSubStroke"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="32dp"
android:valueFrom="1"
android:stepSize="1.0"
android:valueTo="30">
</com.google.android.material.slider.Slider>
<Button
android:id="@+id/videoSubBottomMarginButton"
style="@style/Widget.Material3.Button.TextButton"
android:layout_width="match_parent"
android:layout_height="64dp"
android:layout_marginBottom="8dp"
android:clickable="false"
android:fontFamily="@font/poppins_bold"
android:insetTop="0dp"
android:insetBottom="0dp"
android:paddingHorizontal="32dp"
android:text="@string/textview_sub_bottom_margin"
android:textAlignment="viewStart"
android:textAllCaps="false"
android:textColor="@color/bg_opp"
app:cornerRadius="0dp"
app:icon="@drawable/ic_round_color_24"
app:iconPadding="16dp"
app:iconSize="24dp" />
<com.google.android.material.slider.Slider
android:id="@+id/videoSubBottomMargin"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="32dp"
android:valueFrom="1"
android:stepSize="1.0"
android:valueTo="30">
</com.google.android.material.slider.Slider>
<Button <Button
android:id="@+id/videoSubFont" android:id="@+id/videoSubFont"
style="@style/Widget.Material3.Button.TextButton" style="@style/Widget.Material3.Button.TextButton"
@ -1189,27 +1271,6 @@
</com.google.android.material.materialswitch.MaterialSwitch> </com.google.android.material.materialswitch.MaterialSwitch>
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/playerSettingsInternalCast"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:checked="false"
android:drawableStart="@drawable/cast_warning"
android:drawablePadding="16dp"
android:elegantTextHeight="true"
android:fontFamily="@font/poppins_bold"
android:minHeight="64dp"
android:paddingHorizontal="32dp"
android:text="@string/try_internal_cast_experimental"
android:textAlignment="viewStart"
android:textColor="@color/bg_opp"
app:cornerRadius="0dp"
app:drawableTint="?attr/colorPrimary"
app:showText="false"
app:thumbTint="@color/button_switch_track">
</com.google.android.material.materialswitch.MaterialSwitch>
<com.google.android.material.materialswitch.MaterialSwitch <com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/playerSettingsRotate" android:id="@+id/playerSettingsRotate"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -1232,6 +1293,27 @@
</com.google.android.material.materialswitch.MaterialSwitch> </com.google.android.material.materialswitch.MaterialSwitch>
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/playerSettingsInternalCast"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:checked="false"
android:drawableStart="@drawable/cast_warning"
android:drawablePadding="16dp"
android:elegantTextHeight="true"
android:fontFamily="@font/poppins_bold"
android:minHeight="64dp"
android:paddingHorizontal="32dp"
android:text="@string/try_internal_cast_experimental"
android:textAlignment="viewStart"
android:textColor="@color/bg_opp"
app:cornerRadius="0dp"
app:drawableTint="?attr/colorPrimary"
app:showText="false"
app:thumbTint="@color/button_switch_track">
</com.google.android.material.materialswitch.MaterialSwitch>
<com.google.android.material.materialswitch.MaterialSwitch <com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/playerSettingsAdditionalCodec" android:id="@+id/playerSettingsAdditionalCodec"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -1252,7 +1334,7 @@
app:thumbTint="@color/button_switch_track"> app:thumbTint="@color/button_switch_track">
</com.google.android.material.materialswitch.MaterialSwitch> </com.google.android.material.materialswitch.MaterialSwitch>
<TextView <TextView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -1268,4 +1350,4 @@
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
</androidx.core.widget.NestedScrollView> </androidx.core.widget.NestedScrollView>

View file

@ -55,6 +55,14 @@
</androidx.media3.ui.SubtitleView> </androidx.media3.ui.SubtitleView>
<ani.dantotsu.others.Xubtitle
android:id="@+id/customSubtitleView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal|bottom"
android:adjustViewBounds="true"
android:visibility="gone"/>
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/exo_full_area" android:id="@+id/exo_full_area"
android:layout_width="match_parent" android:layout_width="match_parent"

View file

@ -1078,12 +1078,15 @@ Non quae tempore quo provident laudantium qui illo dolor vel quia dolor et exerc
<string name="host">Host</string> <string name="host">Host</string>
<string name="port">Port</string> <string name="port">Port</string>
<string name="authentication">Authentication</string> <string name="authentication">Authentication</string>
<string name="proxy">Socks5 Proxy</string> <string name="proxy">SOCKS5 Proxy</string>
<string name="proxy_desc">Route All Your Network Traffic Through a Socks5 Proxy</string> <string name="proxy_desc">Route All Your Network Traffic Through a SOCKS5 Proxy</string>
<string name="proxy_setup">Proxy Setup</string> <string name="proxy_setup">Proxy Setup</string>
<string name="proxy_setup_desc">Configure your Socks5 Proxy</string> <string name="proxy_setup_desc">Configure your Socks5 Proxy</string>
<string name="clear_stored_episode">Clear Stored Episode Data</string> <string name="clear_stored_episode">Clear Stored Episode Data</string>
<string name="clear_stored_chapter">Clear Stored Chapter Data</string> <string name="clear_stored_chapter">Clear Stored Chapter Data</string>
<string name="use_additional_codec">Additional Codec Support</string> <string name="use_additional_codec">Additional Codec Support</string>
<string name="textview_sub">Textview Subtitles (Experimental)</string>
<string name="textview_sub_stroke">Subtitle Stroke</string>
<string name="textview_sub_bottom_margin">Bottom Margin</string>
</resources> </resources>