feat(exoplayer): custom subtitle view (#544)
* branch * Add Custom Subtitles
This commit is contained in:
parent
eac4604b3d
commit
b04a176870
7 changed files with 478 additions and 49 deletions
|
@ -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
|
||||||
|
|
144
app/src/main/java/ani/dantotsu/others/Xubtitle.kt
Normal file
144
app/src/main/java/ani/dantotsu/others/Xubtitle.kt
Normal 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
|
||||||
|
}
|
||||||
|
}
|
|
@ -267,16 +267,16 @@ class PlayerSettingsActivity : AppCompatActivity() {
|
||||||
PrefManager.setVal(PrefName.Cast, isChecked)
|
PrefManager.setVal(PrefName.Cast, isChecked)
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.playerSettingsInternalCast.isChecked = PrefManager.getVal(PrefName.UseInternalCast)
|
|
||||||
binding.playerSettingsInternalCast.setOnCheckedChangeListener { _, isChecked ->
|
|
||||||
PrefManager.setVal(PrefName.UseInternalCast, isChecked)
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.playerSettingsRotate.isChecked = PrefManager.getVal(PrefName.RotationPlayer)
|
binding.playerSettingsRotate.isChecked = PrefManager.getVal(PrefName.RotationPlayer)
|
||||||
binding.playerSettingsRotate.setOnCheckedChangeListener { _, isChecked ->
|
binding.playerSettingsRotate.setOnCheckedChangeListener { _, isChecked ->
|
||||||
PrefManager.setVal(PrefName.RotationPlayer, isChecked)
|
PrefManager.setVal(PrefName.RotationPlayer, isChecked)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
binding.playerSettingsInternalCast.isChecked = PrefManager.getVal(PrefName.UseInternalCast)
|
||||||
|
binding.playerSettingsInternalCast.setOnCheckedChangeListener { _, isChecked ->
|
||||||
|
PrefManager.setVal(PrefName.UseInternalCast, 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)
|
||||||
|
@ -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",
|
||||||
|
|
|
@ -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)),
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue