AquaStream/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt

1571 lines
61 KiB
Kotlin
Raw Normal View History

2022-01-07 19:27:25 +00:00
package com.lagradost.cloudstream3.ui.player
import android.animation.ObjectAnimator
import android.annotation.SuppressLint
2023-08-16 23:19:24 +00:00
import android.app.Activity
2022-01-07 19:27:25 +00:00
import android.content.Context
import android.content.pm.ActivityInfo
import android.content.res.ColorStateList
2023-08-16 23:19:24 +00:00
import android.content.res.Configuration
2022-01-07 19:27:25 +00:00
import android.graphics.Color
import android.media.AudioManager
2022-01-08 13:54:42 +00:00
import android.os.Build
2022-01-07 19:27:25 +00:00
import android.os.Bundle
import android.provider.Settings
2022-02-13 18:06:36 +00:00
import android.text.Editable
import android.text.format.DateUtils
2022-01-07 19:27:25 +00:00
import android.view.KeyEvent
2023-07-18 01:55:00 +00:00
import android.view.LayoutInflater
2022-01-07 19:27:25 +00:00
import android.view.MotionEvent
2023-08-16 23:19:24 +00:00
import android.view.Surface
2022-01-07 19:27:25 +00:00
import android.view.View
2023-07-18 01:55:00 +00:00
import android.view.ViewGroup
2022-01-08 13:54:42 +00:00
import android.view.WindowManager
import android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
2022-01-07 19:27:25 +00:00
import android.view.animation.AlphaAnimation
import android.view.animation.Animation
import android.view.animation.AnimationUtils
2022-02-13 18:06:36 +00:00
import androidx.appcompat.app.AlertDialog
2022-01-07 19:27:25 +00:00
import androidx.core.graphics.blue
import androidx.core.graphics.green
import androidx.core.graphics.red
import androidx.core.view.isGone
import androidx.core.view.isInvisible
2022-01-07 19:27:25 +00:00
import androidx.core.view.isVisible
2022-02-13 18:06:36 +00:00
import androidx.core.widget.doOnTextChanged
2022-01-07 19:27:25 +00:00
import androidx.preference.PreferenceManager
import com.lagradost.cloudstream3.CommonActivity.keyEventListener
import com.lagradost.cloudstream3.CommonActivity.playerEventListener
import com.lagradost.cloudstream3.CommonActivity.screenHeight
import com.lagradost.cloudstream3.CommonActivity.screenWidth
2022-01-07 19:27:25 +00:00
import com.lagradost.cloudstream3.R
2023-07-18 01:55:00 +00:00
import com.lagradost.cloudstream3.databinding.PlayerCustomLayoutBinding
import com.lagradost.cloudstream3.databinding.SubtitleOffsetBinding
2022-01-07 19:27:25 +00:00
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.ui.player.GeneratorPlayer.Companion.subsProvidersIsActive
import com.lagradost.cloudstream3.ui.player.source_priority.QualityDataHelper
2023-07-18 01:55:00 +00:00
import com.lagradost.cloudstream3.ui.result.setText
import com.lagradost.cloudstream3.ui.result.txt
import com.lagradost.cloudstream3.ui.settings.SettingsFragment
import com.lagradost.cloudstream3.utils.AppUtils.isUsingMobileData
2023-10-06 23:39:30 +00:00
import com.lagradost.cloudstream3.utils.DataStoreHelper
2022-01-07 19:27:25 +00:00
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showDialog
import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute
2022-02-13 18:06:36 +00:00
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
2022-01-07 19:27:25 +00:00
import com.lagradost.cloudstream3.utils.UIHelper.getNavigationBarHeight
import com.lagradost.cloudstream3.utils.UIHelper.getStatusBarHeight
import com.lagradost.cloudstream3.utils.UIHelper.hideSystemUI
import com.lagradost.cloudstream3.utils.UIHelper.popCurrentPage
import com.lagradost.cloudstream3.utils.UIHelper.showSystemUI
import com.lagradost.cloudstream3.utils.UIHelper.toPx
import com.lagradost.cloudstream3.utils.UserPreferenceDelegate
2022-01-07 19:27:25 +00:00
import com.lagradost.cloudstream3.utils.Vector2
import kotlin.math.*
const val MINIMUM_SEEK_TIME = 7000L // when swipe seeking
const val MINIMUM_VERTICAL_SWIPE = 2.0f // in percentage
const val MINIMUM_HORIZONTAL_SWIPE = 2.0f // in percentage
const val VERTICAL_MULTIPLIER = 2.0f
const val HORIZONTAL_MULTIPLIER = 2.0f
const val DOUBLE_TAB_MAXIMUM_HOLD_TIME = 200L
const val DOUBLE_TAB_MINIMUM_TIME_BETWEEN = 200L // this also affects the UI show response time
const val DOUBLE_TAB_PAUSE_PERCENTAGE = 0.15 // in both directions
private const val SUBTITLE_DELAY_BUNDLE_KEY = "subtitle_delay"
2022-01-07 19:27:25 +00:00
// All the UI Logic for the player
open class FullScreenPlayer : AbstractPlayerFragment() {
private var isVerticalOrientation: Boolean = false
2022-06-16 01:04:24 +00:00
protected open var lockRotation = true
protected open var isFullScreenPlayer = true
2022-06-20 13:43:53 +00:00
protected open var isTv = false
2023-07-18 01:55:00 +00:00
protected var playerBinding: PlayerCustomLayoutBinding? = null
private var durationMode : Boolean by UserPreferenceDelegate("duration_mode", false)
2022-01-07 19:27:25 +00:00
// state of player UI
protected var isShowing = false
protected var isLocked = false
protected var hasEpisodes = false
private set
//protected val hasEpisodes
// get() = episodes.isNotEmpty()
2022-01-07 19:27:25 +00:00
// options for player
/**
* Default profile 1
* Decides how links should be sorted based on a priority system.
* This will be set in runtime based on settings.
**/
protected var currentQualityProfile = 1
2023-07-18 01:55:00 +00:00
// protected var currentPrefQuality =
// Qualities.P2160.value // preferred maximum quality, used for ppl w bad internet or on cell
2022-01-07 19:27:25 +00:00
protected var fastForwardTime = 10000L
2023-08-02 03:30:50 +00:00
protected var androidTVInterfaceOffSeekTime = 10000L
protected var androidTVInterfaceOnSeekTime = 30000L
2022-01-07 19:27:25 +00:00
protected var swipeHorizontalEnabled = false
protected var swipeVerticalEnabled = false
protected var playBackSpeedEnabled = false
protected var playerResizeEnabled = false
protected var doubleTapEnabled = false
protected var doubleTapPauseEnabled = true
protected var playerRotateEnabled = false
protected var autoPlayerRotateEnabled = false
2022-01-07 19:27:25 +00:00
2022-02-13 18:06:36 +00:00
protected var subtitleDelay
set(value) = try {
player.setSubtitleOffset(-value)
} catch (e: Exception) {
logError(e)
}
get() = try {
-player.getSubtitleOffset()
} catch (e: Exception) {
logError(e)
0L
}
2022-01-07 19:27:25 +00:00
//private var useSystemBrightness = false
protected var useTrueSystemBrightness = true
2022-01-08 13:54:42 +00:00
private val fullscreenNotch = true //TODO SETTING
2022-01-07 19:27:25 +00:00
private var statusBarHeight: Int? = null
private var navigationBarHeight: Int? = null
private val brightnessIcons = listOf(
R.drawable.sun_1,
R.drawable.sun_2,
R.drawable.sun_3,
R.drawable.sun_4,
R.drawable.sun_5,
R.drawable.sun_6,
//R.drawable.sun_7,
// R.drawable.ic_baseline_brightness_1_24,
// R.drawable.ic_baseline_brightness_2_24,
// R.drawable.ic_baseline_brightness_3_24,
// R.drawable.ic_baseline_brightness_4_24,
// R.drawable.ic_baseline_brightness_5_24,
// R.drawable.ic_baseline_brightness_6_24,
// R.drawable.ic_baseline_brightness_7_24,
)
private val volumeIcons = listOf(
R.drawable.ic_baseline_volume_mute_24,
R.drawable.ic_baseline_volume_down_24,
R.drawable.ic_baseline_volume_up_24,
)
2023-07-18 01:55:00 +00:00
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val root = super.onCreateView(inflater, container, savedInstanceState) ?: return null
playerBinding = PlayerCustomLayoutBinding.bind(root.findViewById(R.id.player_holder))
return root
}
override fun onDestroyView() {
playerBinding = null
super.onDestroyView()
}
2022-01-07 19:27:25 +00:00
open fun showMirrorsDialogue() {
throw NotImplementedError()
}
open fun showTracksDialogue() {
throw NotImplementedError()
}
open fun openOnlineSubPicker(
context: Context,
imdbId: Long?,
dismissCallback: (() -> Unit)
) {
throw NotImplementedError()
}
2022-01-07 19:27:25 +00:00
/** Returns false if the touch is on the status bar or navigation bar*/
private fun isValidTouch(rawX: Float, rawY: Float): Boolean {
val statusHeight = statusBarHeight ?: 0
2022-01-16 22:53:01 +00:00
// val navHeight = navigationBarHeight ?: 0
// nav height is removed because screenWidth already takes into account that
return rawY > statusHeight && rawX < screenWidth //- navHeight
2022-01-07 19:27:25 +00:00
}
2022-04-09 22:05:42 +00:00
override fun exitedPipMode() {
animateLayoutChanges()
}
2022-06-16 01:04:24 +00:00
protected fun animateLayoutChanges() {
2022-01-07 19:27:25 +00:00
if (isShowing) {
updateUIVisibility()
} else {
2023-07-18 01:55:00 +00:00
playerBinding?.playerHolder?.postDelayed({ updateUIVisibility() }, 200)
2022-01-07 19:27:25 +00:00
}
val titleMove = if (isShowing) 0f else -50.toPx.toFloat()
2023-07-18 01:55:00 +00:00
playerBinding?.playerVideoTitle?.let {
2022-01-07 19:27:25 +00:00
ObjectAnimator.ofFloat(it, "translationY", titleMove).apply {
duration = 200
start()
}
}
2023-07-18 01:55:00 +00:00
playerBinding?.playerVideoTitleRez?.let {
2022-01-07 19:27:25 +00:00
ObjectAnimator.ofFloat(it, "translationY", titleMove).apply {
duration = 200
start()
}
}
val playerBarMove = if (isShowing) 0f else 50.toPx.toFloat()
2023-07-18 01:55:00 +00:00
playerBinding?.bottomPlayerBar?.let {
2022-01-07 19:27:25 +00:00
ObjectAnimator.ofFloat(it, "translationY", playerBarMove).apply {
duration = 200
start()
}
}
val fadeTo = if (isShowing) 1f else 0f
val fadeAnimation = AlphaAnimation(1f - fadeTo, fadeTo)
fadeAnimation.duration = 100
fadeAnimation.fillAfter = true
val sView = subView
val sStyle = subStyle
if (sView != null && sStyle != null) {
2023-07-18 01:55:00 +00:00
val move = if (isShowing) -((playerBinding?.bottomPlayerBar?.height?.toFloat()
2022-01-07 19:27:25 +00:00
?: 0f) + 40.toPx) else -sStyle.elevation.toPx.toFloat()
ObjectAnimator.ofFloat(sView, "translationY", move).apply {
duration = 200
start()
}
}
2022-06-16 23:58:55 +00:00
val playerSourceMove = if (isShowing) 0f else -50.toPx.toFloat()
2023-07-18 01:55:00 +00:00
playerBinding?.apply {
playerOpenSource.let {
ObjectAnimator.ofFloat(it, "translationY", playerSourceMove).apply {
duration = 200
start()
}
2022-06-16 23:58:55 +00:00
}
2023-07-18 01:55:00 +00:00
if (!isLocked) {
playerFfwdHolder.alpha = 1f
playerRewHolder.alpha = 1f
// player_pause_play_holder?.alpha = 1f
shadowOverlay.isVisible = true
shadowOverlay.startAnimation(fadeAnimation)
playerFfwdHolder.startAnimation(fadeAnimation)
playerRewHolder.startAnimation(fadeAnimation)
playerPausePlay.startAnimation(fadeAnimation)
/*if (isBuffering) {
player_pause_play?.isVisible = false
player_pause_play_holder?.isVisible = false
} else {
player_pause_play?.isVisible = true
player_pause_play_holder?.startAnimation(fadeAnimation)
player_pause_play?.startAnimation(fadeAnimation)
}*/
//player_buffering?.startAnimation(fadeAnimation)
}
2022-06-16 23:58:55 +00:00
2023-07-18 01:55:00 +00:00
bottomPlayerBar.startAnimation(fadeAnimation)
playerOpenSource.startAnimation(fadeAnimation)
playerTopHolder.startAnimation(fadeAnimation)
2022-01-07 19:27:25 +00:00
}
}
2022-02-13 18:06:36 +00:00
override fun subtitlesChanged() {
2023-07-18 01:55:00 +00:00
playerBinding?.playerSubtitleOffsetBtt?.isGone =
player.getCurrentPreferredSubtitle() == null
2022-02-13 18:06:36 +00:00
}
private fun restoreOrientationWithSensor(activity: Activity){
val currentOrientation = activity.resources.configuration.orientation
var orientation = 0
when (currentOrientation) {
Configuration.ORIENTATION_LANDSCAPE ->
orientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
Configuration.ORIENTATION_SQUARE, Configuration.ORIENTATION_UNDEFINED ->
orientation = dynamicOrientation()
Configuration.ORIENTATION_PORTRAIT ->
orientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT
}
activity.requestedOrientation = orientation
}
private fun toggleOrientationWithSensor(activity: Activity){
val currentOrientation = activity.resources.configuration.orientation
var orientation = 0
when (currentOrientation) {
Configuration.ORIENTATION_LANDSCAPE ->
orientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT
Configuration.ORIENTATION_SQUARE, Configuration.ORIENTATION_UNDEFINED ->
orientation = dynamicOrientation()
Configuration.ORIENTATION_PORTRAIT ->
orientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
}
activity.requestedOrientation = orientation
}
2023-08-16 23:19:24 +00:00
open fun lockOrientation(activity: Activity) {
val display =
(activity.getSystemService(Context.WINDOW_SERVICE) as WindowManager).defaultDisplay
val rotation = display.rotation
val currentOrientation = activity.resources.configuration.orientation
var orientation = 0
when (currentOrientation) {
Configuration.ORIENTATION_LANDSCAPE ->
orientation =
if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_90)
ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
else
ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE
Configuration.ORIENTATION_SQUARE, Configuration.ORIENTATION_UNDEFINED ->
orientation = dynamicOrientation()
Configuration.ORIENTATION_PORTRAIT ->
orientation =
if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_270)
ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
else
ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT
2023-08-16 23:19:24 +00:00
}
activity.requestedOrientation = orientation
}
private fun updateOrientation(ignoreDynamicOrientation: Boolean = false) {
2023-08-16 23:19:24 +00:00
activity?.apply {
if(lockRotation) {
if(isLocked) {
lockOrientation(this)
}
else {
if(ignoreDynamicOrientation){
// restore when lock is disabled
restoreOrientationWithSensor(this)
} else {
this.requestedOrientation = dynamicOrientation()
}
2023-08-16 23:19:24 +00:00
}
}
}
}
2022-06-16 17:47:48 +00:00
protected fun enterFullscreen() {
2022-06-16 01:04:24 +00:00
if (isFullScreenPlayer) {
activity?.hideSystemUI()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && fullscreenNotch) {
val params = activity?.window?.attributes
params?.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
activity?.window?.attributes = params
}
2022-01-08 13:54:42 +00:00
}
2023-08-16 23:19:24 +00:00
updateOrientation()
2022-01-07 19:27:25 +00:00
}
2022-06-16 17:47:48 +00:00
protected fun exitFullscreen() {
//if (lockRotation)
activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER
2022-01-08 13:54:42 +00:00
// simply resets brightness and notch settings that might have been overridden
val lp = activity?.window?.attributes
lp?.screenBrightness = WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
lp?.layoutInDisplayCutoutMode =
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
}
activity?.window?.attributes = lp
activity?.showSystemUI()
2022-06-16 17:47:48 +00:00
}
override fun onResume() {
enterFullscreen()
super.onResume()
}
override fun onDestroy() {
exitFullscreen()
player.release()
player.releaseCallbacks()
2022-01-07 19:27:25 +00:00
super.onDestroy()
}
private fun setPlayBackSpeed(speed: Float) {
try {
2023-10-06 23:39:30 +00:00
DataStoreHelper.playBackSpeed = speed
2023-07-18 01:55:00 +00:00
playerBinding?.playerSpeedBtt?.text =
2022-01-07 19:27:25 +00:00
getString(R.string.player_speed_text_format).format(speed)
.replace(".0x", "x")
} catch (e: Exception) {
// the format string was wrong
logError(e)
}
player.setPlaybackSpeed(speed)
}
private fun skipOp() {
player.seekTime(85000) // skip 85s
}
2022-02-13 18:06:36 +00:00
private fun showSubtitleOffsetDialog() {
2023-07-18 01:55:00 +00:00
val ctx = context ?: return
val binding = SubtitleOffsetBinding.inflate(LayoutInflater.from(ctx), null, false)
val builder =
AlertDialog.Builder(ctx, R.style.AlertDialogCustom)
.setView(binding.root)
val dialog = builder.create()
dialog.show()
val beforeOffset = subtitleDelay
/*val applyButton = dialog.findViewById<TextView>(R.id.apply_btt)!!
val cancelButton = dialog.findViewById<TextView>(R.id.cancel_btt)!!
val input = dialog.findViewById<EditText>(R.id.subtitle_offset_input)!!
val sub = dialog.findViewById<ImageView>(R.id.subtitle_offset_subtract)!!
val subMore = dialog.findViewById<ImageView>(R.id.subtitle_offset_subtract_more)!!
val add = dialog.findViewById<ImageView>(R.id.subtitle_offset_add)!!
val addMore = dialog.findViewById<ImageView>(R.id.subtitle_offset_add_more)!!
val subTitle = dialog.findViewById<TextView>(R.id.subtitle_offset_sub_title)!!*/
binding.apply {
subtitleOffsetInput.doOnTextChanged { text, _, _, _ ->
text?.toString()?.toLongOrNull()?.let { time ->
subtitleDelay = time
val str = when {
time > 0L -> {
txt(R.string.subtitle_offset_extra_hint_later_format, time)
2022-02-13 18:06:36 +00:00
}
2023-07-18 01:55:00 +00:00
time < 0L -> {
txt(R.string.subtitle_offset_extra_hint_before_format, -time)
2022-02-13 18:06:36 +00:00
}
2023-07-18 01:55:00 +00:00
2022-02-13 18:06:36 +00:00
else -> {
2023-07-18 01:55:00 +00:00
txt(R.string.subtitle_offset_extra_hint_none_format)
2022-02-13 18:06:36 +00:00
}
}
2023-07-18 01:55:00 +00:00
subtitleOffsetSubTitle.setText(str)
2022-02-13 18:06:36 +00:00
}
}
2023-07-18 01:55:00 +00:00
subtitleOffsetInput.text =
Editable.Factory.getInstance()?.newEditable(beforeOffset.toString())
2022-02-13 18:06:36 +00:00
val buttonChange = 100L
2022-07-28 13:29:09 +00:00
val buttonChangeMore = 1000L
2022-02-13 18:06:36 +00:00
fun changeBy(by: Long) {
2023-07-18 01:55:00 +00:00
val current = (subtitleOffsetInput.text?.toString()?.toLongOrNull() ?: 0) + by
subtitleOffsetInput.text =
Editable.Factory.getInstance()?.newEditable(current.toString())
2022-02-13 18:06:36 +00:00
}
2023-07-18 01:55:00 +00:00
subtitleOffsetAdd.setOnClickListener {
2022-02-13 18:06:36 +00:00
changeBy(buttonChange)
}
2023-07-18 01:55:00 +00:00
subtitleOffsetAddMore.setOnClickListener {
2022-07-28 13:29:09 +00:00
changeBy(buttonChangeMore)
}
2023-07-18 01:55:00 +00:00
subtitleOffsetSubtract.setOnClickListener {
2022-02-13 18:06:36 +00:00
changeBy(-buttonChange)
}
2023-07-18 01:55:00 +00:00
subtitleOffsetSubtractMore.setOnClickListener {
2022-07-28 13:29:09 +00:00
changeBy(-buttonChangeMore)
}
2022-02-13 18:06:36 +00:00
dialog.setOnDismissListener {
2022-06-16 01:04:24 +00:00
if (isFullScreenPlayer)
activity?.hideSystemUI()
2022-02-13 18:06:36 +00:00
}
2023-07-18 01:55:00 +00:00
applyBtt.setOnClickListener {
2022-02-13 18:06:36 +00:00
dialog.dismissSafe(activity)
player.seekTime(1L)
}
2023-07-18 01:55:00 +00:00
cancelBtt.setOnClickListener {
2022-02-13 18:06:36 +00:00
subtitleDelay = beforeOffset
dialog.dismissSafe(activity)
}
}
}
2023-07-18 01:55:00 +00:00
2022-01-07 19:27:25 +00:00
private fun showSpeedDialog() {
val speedsText =
listOf(
"0.5x",
"0.75x",
"0.85x",
"1x",
"1.15x",
"1.25x",
"1.4x",
"1.5x",
"1.75x",
"2x"
)
val speedsNumbers =
listOf(0.5f, 0.75f, 0.85f, 1f, 1.15f, 1.25f, 1.4f, 1.5f, 1.75f, 2f)
val speedIndex = speedsNumbers.indexOf(player.getPlaybackSpeed())
activity?.let { act ->
act.showDialog(
speedsText,
speedIndex,
act.getString(R.string.player_speed),
false,
{
2022-06-16 01:04:24 +00:00
if (isFullScreenPlayer)
activity?.hideSystemUI()
2022-01-07 19:27:25 +00:00
}) { index ->
2022-06-16 01:04:24 +00:00
if (isFullScreenPlayer)
activity?.hideSystemUI()
2022-01-07 19:27:25 +00:00
setPlayBackSpeed(speedsNumbers[index])
}
}
}
fun resetRewindText() {
2023-07-18 01:55:00 +00:00
playerBinding?.exoRewText?.text =
2022-01-07 19:27:25 +00:00
getString(R.string.rew_text_regular_format).format(fastForwardTime / 1000)
}
fun resetFastForwardText() {
2023-07-18 01:55:00 +00:00
playerBinding?.exoFfwdText?.text =
2022-01-07 19:27:25 +00:00
getString(R.string.ffw_text_regular_format).format(fastForwardTime / 1000)
}
private fun rewind() {
try {
2023-07-18 01:55:00 +00:00
playerBinding?.apply {
playerCenterMenu.isGone = false
playerRewHolder.alpha = 1f
2022-01-07 19:27:25 +00:00
2023-07-18 01:55:00 +00:00
val rotateLeft = AnimationUtils.loadAnimation(context, R.anim.rotate_left)
exoRew.startAnimation(rotateLeft)
2022-01-07 19:27:25 +00:00
2023-07-18 01:55:00 +00:00
val goLeft = AnimationUtils.loadAnimation(context, R.anim.go_left)
goLeft.setAnimationListener(object : Animation.AnimationListener {
override fun onAnimationStart(animation: Animation?) {}
2022-01-07 19:27:25 +00:00
2023-07-18 01:55:00 +00:00
override fun onAnimationRepeat(animation: Animation?) {}
2022-01-07 19:27:25 +00:00
2023-07-18 01:55:00 +00:00
override fun onAnimationEnd(animation: Animation?) {
exoRewText.post {
resetRewindText()
playerCenterMenu.isGone = !isShowing
playerRewHolder.alpha = if (isShowing) 1f else 0f
}
2022-01-07 19:27:25 +00:00
}
2023-07-18 01:55:00 +00:00
})
exoRewText.startAnimation(goLeft)
exoRewText.text =
getString(R.string.rew_text_format).format(fastForwardTime / 1000)
}
2022-01-07 19:27:25 +00:00
player.seekTime(-fastForwardTime)
} catch (e: Exception) {
logError(e)
}
}
private fun fastForward() {
try {
2023-07-18 01:55:00 +00:00
playerBinding?.apply {
playerCenterMenu.isGone = false
playerFfwdHolder.alpha = 1f
2022-01-08 17:14:43 +00:00
2023-07-18 01:55:00 +00:00
val rotateRight = AnimationUtils.loadAnimation(context, R.anim.rotate_right)
exoFfwd.startAnimation(rotateRight)
2022-01-07 19:27:25 +00:00
2023-07-18 01:55:00 +00:00
val goRight = AnimationUtils.loadAnimation(context, R.anim.go_right)
goRight.setAnimationListener(object : Animation.AnimationListener {
override fun onAnimationStart(animation: Animation?) {}
2022-01-07 19:27:25 +00:00
2023-07-18 01:55:00 +00:00
override fun onAnimationRepeat(animation: Animation?) {}
2022-01-07 19:27:25 +00:00
2023-07-18 01:55:00 +00:00
override fun onAnimationEnd(animation: Animation?) {
exoFfwdText.post {
resetFastForwardText()
playerCenterMenu.isGone = !isShowing
playerFfwdHolder.alpha = if (isShowing) 1f else 0f
}
2022-01-07 19:27:25 +00:00
}
2023-07-18 01:55:00 +00:00
})
exoFfwdText.startAnimation(goRight)
exoFfwdText.text =
getString(R.string.ffw_text_format).format(fastForwardTime / 1000)
}
2022-01-07 19:27:25 +00:00
player.seekTime(fastForwardTime)
} catch (e: Exception) {
logError(e)
}
}
private fun onClickChange() {
isShowing = !isShowing
if (isShowing) {
2023-07-18 01:55:00 +00:00
playerBinding?.playerIntroPlay?.isGone = true
2022-01-07 19:27:25 +00:00
autoHide()
}
2022-06-16 01:04:24 +00:00
if (isFullScreenPlayer)
activity?.hideSystemUI()
2022-01-07 19:27:25 +00:00
animateLayoutChanges()
2023-07-18 01:55:00 +00:00
playerBinding?.playerPausePlay?.requestFocus()
2022-01-07 19:27:25 +00:00
}
private fun toggleLock() {
if (!isShowing) {
onClickChange()
}
isLocked = !isLocked
updateOrientation(true) // set true to ignore auto rotate to stay in current orientation
2023-08-16 23:19:24 +00:00
2022-01-07 19:27:25 +00:00
if (isLocked && isShowing) {
2023-07-18 01:55:00 +00:00
playerBinding?.playerHolder?.postDelayed({
2022-01-07 19:27:25 +00:00
if (isLocked && isShowing) {
onClickChange()
}
}, 200)
}
val fadeTo = if (isLocked) 0f else 1f
2023-07-18 01:55:00 +00:00
playerBinding?.apply {
val fadeAnimation = AlphaAnimation(playerVideoTitle.alpha, fadeTo).apply {
duration = 100
fillAfter = true
}
2022-01-07 19:27:25 +00:00
2023-07-18 01:55:00 +00:00
updateUIVisibility()
// MENUS
//centerMenu.startAnimation(fadeAnimation)
playerPausePlay.startAnimation(fadeAnimation)
playerFfwdHolder.startAnimation(fadeAnimation)
playerRewHolder.startAnimation(fadeAnimation)
//if (hasEpisodes)
// player_episodes_button?.startAnimation(fadeAnimation)
//player_media_route_button?.startAnimation(fadeAnimation)
//video_bar.startAnimation(fadeAnimation)
//TITLE
playerVideoTitleRez.startAnimation(fadeAnimation)
playerEpisodeFiller.startAnimation(fadeAnimation)
playerVideoTitle.startAnimation(fadeAnimation)
playerTopHolder.startAnimation(fadeAnimation)
// BOTTOM
playerLockHolder.startAnimation(fadeAnimation)
//player_go_back_holder?.startAnimation(fadeAnimation)
shadowOverlay.isVisible = true
shadowOverlay.startAnimation(fadeAnimation)
2022-01-07 19:27:25 +00:00
}
updateLockUI()
}
2022-11-02 20:41:39 +00:00
fun updateUIVisibility() {
2022-01-07 19:27:25 +00:00
val isGone = isLocked || !isShowing
var togglePlayerTitleGone = isGone
context?.let {
val settingsManager = PreferenceManager.getDefaultSharedPreferences(it)
val limitTitle = settingsManager.getInt(getString(R.string.prefer_limit_title_key), 0)
if (limitTitle < 0) {
togglePlayerTitleGone = true
}
}
2023-07-18 01:55:00 +00:00
playerBinding?.apply {
playerLockHolder.isGone = isGone
playerVideoBar.isGone = isGone
playerPausePlay.isGone = isGone
//player_buffering?.isGone = isGone
playerTopHolder.isGone = isGone
//player_episodes_button?.isVisible = !isGone && hasEpisodes
playerVideoTitle.isGone = togglePlayerTitleGone
// player_video_title_rez?.isGone = isGone
2023-07-18 01:55:00 +00:00
playerEpisodeFiller.isGone = isGone
playerCenterMenu.isGone = isGone
playerLock.isGone = !isShowing
//player_media_route_button?.isClickable = !isGone
playerGoBackHolder.isGone = isGone
playerSourcesBtt.isGone = isGone
playerSkipEpisode.isClickable = !isGone
}
2022-01-07 19:27:25 +00:00
}
private fun updateLockUI() {
2023-07-18 01:55:00 +00:00
playerBinding?.apply {
playerLock.setIconResource(if (isLocked) R.drawable.video_locked else R.drawable.video_unlocked)
if (layout == R.layout.fragment_player) {
val color = if (isLocked) context?.colorFromAttribute(R.attr.colorPrimary)
else Color.WHITE
if (color != null) {
playerLock.setTextColor(color)
playerLock.iconTint = ColorStateList.valueOf(color)
playerLock.rippleColor =
ColorStateList.valueOf(Color.argb(50, color.red, color.green, color.blue))
}
}
2022-01-07 19:27:25 +00:00
}
}
private var currentTapIndex = 0
protected fun autoHide() {
currentTapIndex++
val index = currentTapIndex
2023-07-18 01:55:00 +00:00
playerBinding?.playerHolder?.postDelayed({
2022-01-07 19:27:25 +00:00
if (!isCurrentTouchValid && isShowing && index == currentTapIndex && player.getIsPlaying()) {
onClickChange()
}
}, 2000)
}
// this is used because you don't want to hide UI when double tap seeking
private var currentDoubleTapIndex = 0
private fun toggleShowDelayed() {
2022-02-12 14:16:42 +00:00
if (doubleTapEnabled || doubleTapPauseEnabled) {
2022-01-07 19:27:25 +00:00
val index = currentDoubleTapIndex
2023-07-18 01:55:00 +00:00
playerBinding?.playerHolder?.postDelayed({
2022-01-07 19:27:25 +00:00
if (index == currentDoubleTapIndex) {
onClickChange()
}
}, DOUBLE_TAB_MINIMUM_TIME_BETWEEN)
} else {
onClickChange()
}
}
private var isCurrentTouchValid = false
private var currentTouchStart: Vector2? = null
private var currentTouchLast: Vector2? = null
private var currentTouchAction: TouchAction? = null
private var currentLastTouchAction: TouchAction? = null
private var currentTouchStartPlayerTime: Long? =
null // the time in the player when you first click
private var currentTouchStartTime: Long? = null // the system time when you first click
private var currentLastTouchEndTime: Long = 0 // the system time when you released your finger
private var currentClickCount: Int =
0 // amount of times you have double clicked, will reset when other action is taken
// requested volume and brightness is used to make swiping smoother
// to make it not jump between values,
// this value is within the range [0,1]
private var currentRequestedVolume: Float = 0.0f
private var currentRequestedBrightness: Float = 1.0f
enum class TouchAction {
Brightness,
Volume,
Time,
}
companion object {
private fun forceLetters(inp: Long, letters: Int = 2): String {
val added: Int = letters - inp.toString().length
return if (added > 0) {
"0".repeat(added) + inp.toString()
} else {
inp.toString()
}
}
private fun convertTimeToString(sec: Long): String {
val rsec = sec % 60L
val min = ceil((sec - rsec) / 60.0).toInt()
val rmin = min % 60L
val h = ceil((min - rmin) / 60.0).toLong()
//int rh = h;// h % 24;
return (if (h > 0) forceLetters(h) + ":" else "") + (if (rmin >= 0 || h >= 0) forceLetters(
rmin
) + ":" else "") + forceLetters(
rsec
)
}
}
private fun calculateNewTime(
startTime: Long?,
touchStart: Vector2?,
touchEnd: Vector2?
): Long? {
if (touchStart == null || touchEnd == null || startTime == null) return null
val diffX = (touchEnd.x - touchStart.x) * HORIZONTAL_MULTIPLIER / screenWidth.toFloat()
val duration = player.getDuration() ?: return null
return max(
min(
startTime + ((duration * (diffX * diffX)) * (if (diffX < 0) -1 else 1)).toLong(),
duration
), 0
)
}
private fun getBrightness(): Float? {
return if (useTrueSystemBrightness) {
try {
Settings.System.getInt(
context?.contentResolver,
Settings.System.SCREEN_BRIGHTNESS
) / 255f
} catch (e: Exception) {
// because true system brightness requires
// permission, this is a lazy way to check
// as it will throw an error if we do not have it
useTrueSystemBrightness = false
return getBrightness()
}
} else {
try {
activity?.window?.attributes?.screenBrightness
} catch (e: Exception) {
logError(e)
null
}
}
}
private fun setBrightness(brightness: Float) {
if (useTrueSystemBrightness) {
try {
Settings.System.putInt(
context?.contentResolver,
Settings.System.SCREEN_BRIGHTNESS_MODE,
Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL
)
Settings.System.putInt(
context?.contentResolver,
Settings.System.SCREEN_BRIGHTNESS, (brightness * 255).toInt()
)
} catch (e: Exception) {
useTrueSystemBrightness = false
setBrightness(brightness)
}
} else {
try {
val lp = activity?.window?.attributes
lp?.screenBrightness = brightness
activity?.window?.attributes = lp
} catch (e: Exception) {
logError(e)
}
}
}
@SuppressLint("SetTextI18n")
private fun handleMotionEvent(view: View?, event: MotionEvent?): Boolean {
if (event == null || view == null) return false
val currentTouch = Vector2(event.x, event.y)
val startTouch = currentTouchStart
2023-07-18 01:55:00 +00:00
playerBinding?.apply {
playerIntroPlay.isGone = true
when (event.action) {
MotionEvent.ACTION_DOWN -> {
// validates if the touch is inside of the player area
isCurrentTouchValid = isValidTouch(currentTouch.x, currentTouch.y)
/*if (isCurrentTouchValid && player_episode_list?.isVisible == true) {
player_episode_list?.isVisible = false
} else*/ if (isCurrentTouchValid) {
currentTouchStartTime = System.currentTimeMillis()
currentTouchStart = currentTouch
currentTouchLast = currentTouch
currentTouchStartPlayerTime = player.getPosition()
getBrightness()?.let {
currentRequestedBrightness = it
}
(activity?.getSystemService(Context.AUDIO_SERVICE) as? AudioManager)?.let { audioManager ->
val currentVolume =
audioManager.getStreamVolume(AudioManager.STREAM_MUSIC)
val maxVolume =
audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)
currentRequestedVolume = currentVolume.toFloat() / maxVolume.toFloat()
}
2022-01-07 19:27:25 +00:00
}
}
2023-07-18 01:55:00 +00:00
MotionEvent.ACTION_UP -> {
if (isCurrentTouchValid && !isLocked && isFullScreenPlayer) {
// seek time
if (swipeHorizontalEnabled && currentTouchAction == TouchAction.Time) {
val startTime = currentTouchStartPlayerTime
if (startTime != null) {
calculateNewTime(
startTime,
startTouch,
currentTouch
)?.let { seekTo ->
if (abs(seekTo - startTime) > MINIMUM_SEEK_TIME) {
2023-09-09 21:18:21 +00:00
player.seekTo(seekTo, PlayerEventSource.UI)
2023-07-18 01:55:00 +00:00
}
2022-01-07 19:27:25 +00:00
}
}
}
}
2023-07-18 01:55:00 +00:00
// see if click is eligible for seek 10s
val holdTime = currentTouchStartTime?.minus(System.currentTimeMillis())
if (isCurrentTouchValid // is valid
&& currentTouchAction == null // no other action like swiping is taking place
&& currentLastTouchAction == null // last action was none, this prevents mis input random seek
&& holdTime != null
&& holdTime < DOUBLE_TAB_MAXIMUM_HOLD_TIME // it is a click not a long hold
2022-01-07 19:27:25 +00:00
) {
2023-07-18 01:55:00 +00:00
if (!isLocked
&& (System.currentTimeMillis() - currentLastTouchEndTime) < DOUBLE_TAB_MINIMUM_TIME_BETWEEN // the time since the last action is short
) {
currentClickCount++
if (currentClickCount >= 1) { // have double clicked
currentDoubleTapIndex++
if (doubleTapPauseEnabled && isFullScreenPlayer) { // you can pause if your tap is in the middle of the screen
when {
currentTouch.x < screenWidth / 2 - (DOUBLE_TAB_PAUSE_PERCENTAGE * screenWidth) -> {
if (doubleTapEnabled)
rewind()
}
currentTouch.x > screenWidth / 2 + (DOUBLE_TAB_PAUSE_PERCENTAGE * screenWidth) -> {
if (doubleTapEnabled)
fastForward()
}
else -> {
2023-09-09 21:18:21 +00:00
player.handleEvent(CSPlayerEvent.PlayPauseToggle, PlayerEventSource.UI)
2023-07-18 01:55:00 +00:00
}
2022-01-07 19:27:25 +00:00
}
2023-07-18 01:55:00 +00:00
} else if (doubleTapEnabled && isFullScreenPlayer) {
if (currentTouch.x < screenWidth / 2) {
rewind()
} else {
fastForward()
2022-01-07 19:27:25 +00:00
}
}
}
2023-07-18 01:55:00 +00:00
} else {
// is a valid click but not fast enough for seek
currentClickCount = 0
toggleShowDelayed()
//onClickChange()
2022-01-07 19:27:25 +00:00
}
} else {
currentClickCount = 0
}
2023-07-18 01:55:00 +00:00
// call auto hide as it wont hide when you have your finger down
autoHide()
2022-01-07 19:27:25 +00:00
2023-07-18 01:55:00 +00:00
// reset variables
isCurrentTouchValid = false
currentTouchStart = null
currentLastTouchAction = currentTouchAction
currentTouchAction = null
currentTouchStartPlayerTime = null
currentTouchLast = null
currentTouchStartTime = null
// resets UI
playerTimeText.isVisible = false
playerProgressbarLeftHolder.isVisible = false
playerProgressbarRightHolder.isVisible = false
currentLastTouchEndTime = System.currentTimeMillis()
}
2022-01-07 19:27:25 +00:00
2023-07-18 01:55:00 +00:00
MotionEvent.ACTION_MOVE -> {
// if current touch is valid
if (startTouch != null && isCurrentTouchValid && !isLocked && isFullScreenPlayer) {
// action is unassigned and can therefore be assigned
if (currentTouchAction == null) {
val diffFromStart = startTouch - currentTouch
if (swipeVerticalEnabled) {
if (abs(diffFromStart.y * 100 / screenHeight) > MINIMUM_VERTICAL_SWIPE) {
// left = Brightness, right = Volume, but the UI is reversed to show the UI better
currentTouchAction = if (startTouch.x < screenWidth / 2) {
// hide the UI if you hold brightness to show screen better, better UX
if (isShowing) {
isShowing = false
animateLayoutChanges()
}
TouchAction.Brightness
} else {
TouchAction.Volume
}
2022-01-07 19:27:25 +00:00
}
}
2023-07-18 01:55:00 +00:00
if (swipeHorizontalEnabled) {
if (abs(diffFromStart.x * 100 / screenHeight) > MINIMUM_HORIZONTAL_SWIPE) {
currentTouchAction = TouchAction.Time
}
2022-01-07 19:27:25 +00:00
}
}
2023-07-18 01:55:00 +00:00
// display action
val lastTouch = currentTouchLast
if (lastTouch != null) {
val diffFromLast = lastTouch - currentTouch
val verticalAddition =
diffFromLast.y * VERTICAL_MULTIPLIER / screenHeight.toFloat()
// update UI
playerTimeText.isVisible = false
playerProgressbarLeftHolder.isVisible = false
playerProgressbarRightHolder.isVisible = false
when (currentTouchAction) {
TouchAction.Time -> {
// this simply updates UI as the seek logic happens on release
// startTime is rounded to make the UI sync in a nice way
val startTime =
currentTouchStartPlayerTime?.div(1000L)?.times(1000L)
if (startTime != null) {
calculateNewTime(
startTime,
startTouch,
currentTouch
)?.let { newMs ->
val skipMs = newMs - startTime
playerTimeText.apply {
text =
"${convertTimeToString(newMs / 1000)} [${
(if (abs(skipMs) < 1000) "" else (if (skipMs > 0) "+" else "-"))
}${convertTimeToString(abs(skipMs / 1000))}]"
isVisible = true
}
}
2022-01-07 19:27:25 +00:00
}
}
2023-07-18 01:55:00 +00:00
TouchAction.Brightness -> {
playerProgressbarRightHolder.isVisible = true
val lastRequested = currentRequestedBrightness
currentRequestedBrightness =
2022-01-07 19:27:25 +00:00
min(
1.0f,
2023-07-18 01:55:00 +00:00
max(currentRequestedBrightness + verticalAddition, 0.0f)
2022-01-07 19:27:25 +00:00
)
2023-07-18 01:55:00 +00:00
// this is to not spam request it, just in case it fucks over someone
if (lastRequested != currentRequestedBrightness)
setBrightness(currentRequestedBrightness)
2022-01-07 19:27:25 +00:00
// max is set high to make it smooth
2023-07-18 01:55:00 +00:00
playerProgressbarRight.max = 100_000
playerProgressbarRight.progress =
max(2_000, (currentRequestedBrightness * 100_000f).toInt())
2022-01-07 19:27:25 +00:00
2023-07-18 01:55:00 +00:00
playerProgressbarRightIcon.setImageResource(
brightnessIcons[min( // clamp the value just in case
brightnessIcons.size - 1,
2022-01-07 19:27:25 +00:00
max(
0,
2023-07-18 01:55:00 +00:00
round(currentRequestedBrightness * (brightnessIcons.size - 1)).toInt()
2022-01-07 19:27:25 +00:00
)
)]
)
2023-07-18 01:55:00 +00:00
}
TouchAction.Volume -> {
(activity?.getSystemService(Context.AUDIO_SERVICE) as? AudioManager)?.let { audioManager ->
playerProgressbarLeftHolder.isVisible = true
val maxVolume =
audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)
val currentVolume =
audioManager.getStreamVolume(AudioManager.STREAM_MUSIC)
// clamps volume and adds swipe
currentRequestedVolume =
min(
1.0f,
max(currentRequestedVolume + verticalAddition, 0.0f)
)
2022-01-07 19:27:25 +00:00
2023-07-18 01:55:00 +00:00
// max is set high to make it smooth
playerProgressbarLeft.max = 100_000
playerProgressbarLeft.progress =
max(2_000, (currentRequestedVolume * 100_000f).toInt())
playerProgressbarLeftIcon.setImageResource(
volumeIcons[min( // clamp the value just in case
volumeIcons.size - 1,
max(
0,
round(currentRequestedVolume * (volumeIcons.size - 1)).toInt()
)
)]
2022-01-07 19:27:25 +00:00
)
2023-07-18 01:55:00 +00:00
// this is used instead of set volume because old devices does not support it
val desiredVolume =
round(currentRequestedVolume * maxVolume).toInt()
if (desiredVolume != currentVolume) {
val newVolumeAdjusted =
if (desiredVolume < currentVolume) AudioManager.ADJUST_LOWER else AudioManager.ADJUST_RAISE
audioManager.adjustStreamVolume(
AudioManager.STREAM_MUSIC,
newVolumeAdjusted,
0
)
}
2022-01-07 19:27:25 +00:00
}
}
2023-07-18 01:55:00 +00:00
else -> Unit
2022-01-07 19:27:25 +00:00
}
}
}
}
}
}
currentTouchLast = currentTouch
return true
}
private fun handleKeyEvent(event: KeyEvent, hasNavigated: Boolean): Boolean {
if (hasNavigated) {
autoHide()
} else {
event.keyCode.let { keyCode ->
when (event.action) {
KeyEvent.ACTION_DOWN -> {
when (keyCode) {
KeyEvent.KEYCODE_DPAD_CENTER -> {
if (!isShowing) {
if (!isLocked) player.handleEvent(CSPlayerEvent.PlayPauseToggle)
onClickChange()
return true
}
}
2023-07-18 01:55:00 +00:00
KeyEvent.KEYCODE_DPAD_UP -> {
if (!isShowing) {
onClickChange()
return true
}
}
2023-07-18 01:55:00 +00:00
KeyEvent.KEYCODE_DPAD_LEFT -> {
if (!isShowing && !isLocked) {
player.seekTime(-androidTVInterfaceOffSeekTime)
return true
2023-07-18 01:55:00 +00:00
} else if (playerBinding?.playerPausePlay?.isFocused == true) {
player.seekTime(-androidTVInterfaceOnSeekTime)
return true
}
}
2023-07-18 01:55:00 +00:00
KeyEvent.KEYCODE_DPAD_RIGHT -> {
if (!isShowing && !isLocked) {
player.seekTime(androidTVInterfaceOffSeekTime)
return true
2023-07-18 01:55:00 +00:00
} else if (playerBinding?.playerPausePlay?.isFocused == true) {
player.seekTime(androidTVInterfaceOnSeekTime)
return true
}
2022-01-07 19:27:25 +00:00
}
}
}
}
when (keyCode) {
// don't allow dpad move when hidden
KeyEvent.KEYCODE_DPAD_DOWN,
KeyEvent.KEYCODE_DPAD_UP,
KeyEvent.KEYCODE_DPAD_DOWN_LEFT,
KeyEvent.KEYCODE_DPAD_DOWN_RIGHT,
KeyEvent.KEYCODE_DPAD_UP_LEFT,
KeyEvent.KEYCODE_DPAD_UP_RIGHT -> {
if (!isShowing) {
return true
} else {
autoHide()
}
2022-01-07 19:27:25 +00:00
}
// netflix capture back and hide ~monke
KeyEvent.KEYCODE_BACK -> {
2022-06-20 13:43:53 +00:00
if (isShowing && isTv) {
onClickChange()
return true
}
2022-01-07 19:27:25 +00:00
}
}
}
}
return false
}
protected fun uiReset() {
isShowing = false
// if nothing has loaded these buttons should not be visible
2023-07-18 01:55:00 +00:00
playerBinding?.apply {
playerSkipEpisode.isVisible = false
playerTracksBtt.isVisible = false
playerSkipOp.isVisible = false
shadowOverlay.isVisible = false
}
2022-01-07 19:27:25 +00:00
updateLockUI()
updateUIVisibility()
animateLayoutChanges()
resetFastForwardText()
resetRewindText()
}
override fun onSaveInstanceState(outState: Bundle) {
// As this is video specific it is better to not do any setKey/getKey
outState.putLong(SUBTITLE_DELAY_BUNDLE_KEY, subtitleDelay)
super.onSaveInstanceState(outState)
}
2022-01-07 19:27:25 +00:00
@SuppressLint("ClickableViewAccessibility")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// init variables
2023-10-06 23:39:30 +00:00
setPlayBackSpeed(DataStoreHelper.playBackSpeed)
savedInstanceState?.getLong(SUBTITLE_DELAY_BUNDLE_KEY)?.let {
subtitleDelay = it
}
2022-01-07 19:27:25 +00:00
// handle tv controls
playerEventListener = { eventType ->
when (eventType) {
PlayerEventType.Lock -> {
toggleLock()
}
2023-07-18 01:55:00 +00:00
2022-01-07 19:27:25 +00:00
PlayerEventType.NextEpisode -> {
player.handleEvent(CSPlayerEvent.NextEpisode)
}
2023-07-18 01:55:00 +00:00
2022-01-07 19:27:25 +00:00
PlayerEventType.Pause -> {
player.handleEvent(CSPlayerEvent.Pause)
}
2023-07-18 01:55:00 +00:00
2022-01-07 19:27:25 +00:00
PlayerEventType.PlayPauseToggle -> {
player.handleEvent(CSPlayerEvent.PlayPauseToggle)
}
2023-07-18 01:55:00 +00:00
2022-01-07 19:27:25 +00:00
PlayerEventType.Play -> {
player.handleEvent(CSPlayerEvent.Play)
}
2023-07-18 01:55:00 +00:00
PlayerEventType.SkipCurrentChapter -> {
player.handleEvent(CSPlayerEvent.SkipCurrentChapter)
}
2023-07-18 01:55:00 +00:00
2022-01-07 19:27:25 +00:00
PlayerEventType.Resize -> {
nextResize()
}
2023-07-18 01:55:00 +00:00
2022-01-07 19:27:25 +00:00
PlayerEventType.PrevEpisode -> {
player.handleEvent(CSPlayerEvent.PrevEpisode)
}
2023-07-18 01:55:00 +00:00
2022-01-07 19:27:25 +00:00
PlayerEventType.SeekForward -> {
player.handleEvent(CSPlayerEvent.SeekForward)
}
2023-07-18 01:55:00 +00:00
2022-01-07 19:27:25 +00:00
PlayerEventType.ShowSpeed -> {
showSpeedDialog()
}
2023-07-18 01:55:00 +00:00
2022-01-07 19:27:25 +00:00
PlayerEventType.SeekBack -> {
player.handleEvent(CSPlayerEvent.SeekBack)
}
2023-07-18 01:55:00 +00:00
2022-01-07 19:27:25 +00:00
PlayerEventType.ToggleMute -> {
player.handleEvent(CSPlayerEvent.ToggleMute)
}
2023-07-18 01:55:00 +00:00
2022-01-07 19:27:25 +00:00
PlayerEventType.ToggleHide -> {
onClickChange()
}
2023-07-18 01:55:00 +00:00
2022-01-07 19:27:25 +00:00
PlayerEventType.ShowMirrors -> {
showMirrorsDialogue()
}
2023-07-18 01:55:00 +00:00
PlayerEventType.SearchSubtitlesOnline -> {
if (subsProvidersIsActive) {
openOnlineSubPicker(view.context, null) {}
}
}
2023-07-18 01:55:00 +00:00
2022-08-05 23:41:35 +00:00
PlayerEventType.SkipOp -> {
skipOp()
}
2022-01-07 19:27:25 +00:00
}
}
// handle tv controls directly based on player state
keyEventListener = { eventNav ->
// Don't hook player keys if player isn't active
if (player.isActive()) {
val (event, hasNavigated) = eventNav
if (event != null)
handleKeyEvent(event, hasNavigated)
else false
} else false
2022-01-07 19:27:25 +00:00
}
try {
context?.let { ctx ->
2022-02-04 20:49:35 +00:00
val settingsManager = PreferenceManager.getDefaultSharedPreferences(ctx)
2022-05-15 18:38:32 +00:00
fastForwardTime =
settingsManager.getInt(ctx.getString(R.string.double_tap_seek_time_key), 10)
.toLong() * 1000L
androidTVInterfaceOffSeekTime =
settingsManager.getInt(
ctx.getString(R.string.android_tv_interface_off_seek_key),
10
)
.toLong() * 1000L
androidTVInterfaceOnSeekTime =
settingsManager.getInt(
ctx.getString(R.string.android_tv_interface_on_seek_key),
10
)
.toLong() * 1000L
2022-01-07 19:27:25 +00:00
navigationBarHeight = ctx.getNavigationBarHeight()
statusBarHeight = ctx.getStatusBarHeight()
swipeHorizontalEnabled =
settingsManager.getBoolean(ctx.getString(R.string.swipe_enabled_key), true)
swipeVerticalEnabled =
settingsManager.getBoolean(
ctx.getString(R.string.swipe_vertical_enabled_key),
true
)
playBackSpeedEnabled = settingsManager.getBoolean(
ctx.getString(R.string.playback_speed_enabled_key),
false
)
playerRotateEnabled = settingsManager.getBoolean(
ctx.getString(R.string.rotate_video_key),
false
)
autoPlayerRotateEnabled = settingsManager.getBoolean(
ctx.getString(R.string.auto_rotate_video_key),
false
)
2022-01-07 19:27:25 +00:00
playerResizeEnabled =
settingsManager.getBoolean(
ctx.getString(R.string.player_resize_enabled_key),
true
)
doubleTapEnabled =
settingsManager.getBoolean(
ctx.getString(R.string.double_tap_enabled_key),
false
)
doubleTapPauseEnabled =
settingsManager.getBoolean(
ctx.getString(R.string.double_tap_pause_enabled_key),
false
)
val profiles = QualityDataHelper.getProfiles()
val type = if (ctx.isUsingMobileData())
QualityDataHelper.QualityProfileType.Data
else QualityDataHelper.QualityProfileType.WiFi
currentQualityProfile =
profiles.firstOrNull { it.type == type }?.id ?: profiles.firstOrNull()?.id
?: currentQualityProfile
// currentPrefQuality = settingsManager.getInt(
// ctx.getString(if (ctx.isUsingMobileData()) R.string.quality_pref_mobile_data_key else R.string.quality_pref_key),
// currentPrefQuality
// )
2022-01-07 19:27:25 +00:00
// useSystemBrightness =
// settingsManager.getBoolean(ctx.getString(R.string.use_system_brightness_key), false)
}
2023-07-18 01:55:00 +00:00
playerBinding?.apply {
playerSpeedBtt.isVisible = playBackSpeedEnabled
playerResizeBtt.isVisible = playerResizeEnabled
playerRotateBtt.isVisible = playerRotateEnabled
2023-07-18 01:55:00 +00:00
}
2022-01-07 19:27:25 +00:00
} catch (e: Exception) {
logError(e)
}
2023-07-18 01:55:00 +00:00
playerBinding?.apply {
playerPausePlay.setOnClickListener {
autoHide()
player.handleEvent(CSPlayerEvent.PlayPauseToggle)
}
2022-01-07 19:27:25 +00:00
exoDuration.setOnClickListener {
setRemainingTimeCounter(true)
}
timeLeft.setOnClickListener {
setRemainingTimeCounter(false)
}
2023-07-18 01:55:00 +00:00
skipChapterButton.setOnClickListener {
player.handleEvent(CSPlayerEvent.SkipCurrentChapter)
}
2022-01-07 19:27:25 +00:00
playerRotateBtt.setOnClickListener {
autoHide()
toggleRotate()
}
2023-07-18 01:55:00 +00:00
// init clicks
playerResizeBtt.setOnClickListener {
autoHide()
nextResize()
}
2023-07-18 01:55:00 +00:00
playerSpeedBtt.setOnClickListener {
autoHide()
showSpeedDialog()
}
2022-01-07 19:27:25 +00:00
2023-07-18 01:55:00 +00:00
playerSkipOp.setOnClickListener {
autoHide()
skipOp()
}
2022-01-07 19:27:25 +00:00
2023-07-18 01:55:00 +00:00
playerSkipEpisode.setOnClickListener {
autoHide()
player.handleEvent(CSPlayerEvent.NextEpisode)
}
2022-01-07 19:27:25 +00:00
2023-07-18 01:55:00 +00:00
playerLock.setOnClickListener {
autoHide()
toggleLock()
}
2022-01-07 19:27:25 +00:00
2023-07-18 01:55:00 +00:00
playerSubtitleOffsetBtt.setOnClickListener {
showSubtitleOffsetDialog()
}
2022-01-07 19:27:25 +00:00
2023-07-18 01:55:00 +00:00
exoRew.setOnClickListener {
autoHide()
rewind()
}
2022-02-13 18:06:36 +00:00
2023-07-18 01:55:00 +00:00
exoFfwd.setOnClickListener {
autoHide()
fastForward()
}
2022-01-07 19:27:25 +00:00
2023-07-18 01:55:00 +00:00
playerGoBack.setOnClickListener {
activity?.popCurrentPage()
}
2022-01-07 19:27:25 +00:00
2023-07-18 01:55:00 +00:00
playerSourcesBtt.setOnClickListener {
showMirrorsDialogue()
}
2022-01-07 19:27:25 +00:00
2023-07-18 01:55:00 +00:00
playerTracksBtt.setOnClickListener {
showTracksDialogue()
}
2022-01-07 19:27:25 +00:00
2023-07-18 01:55:00 +00:00
// it is !not! a bug that you cant touch the right side, it does not register inputs on navbar or status bar
playerHolder.setOnTouchListener { callView, event ->
return@setOnTouchListener handleMotionEvent(callView, event)
}
2023-07-18 01:55:00 +00:00
exoProgress.setOnTouchListener { _, event ->
// this makes the bar not disappear when sliding
when (event.action) {
MotionEvent.ACTION_DOWN -> {
currentTapIndex++
}
2022-01-07 19:27:25 +00:00
2023-07-18 01:55:00 +00:00
MotionEvent.ACTION_MOVE -> {
currentTapIndex++
}
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL, MotionEvent.ACTION_BUTTON_RELEASE -> {
autoHide()
}
2022-01-14 18:14:24 +00:00
}
2023-07-18 01:55:00 +00:00
return@setOnTouchListener false
2022-01-14 18:14:24 +00:00
}
}
// cs3 is peak media center
setRemainingTimeCounter(durationMode || SettingsFragment.isTrueTvSettings())
playerBinding?.exoPosition?.doOnTextChanged { _, _, _, _ ->
updateRemainingTime()
}
2022-01-07 19:27:25 +00:00
// init UI
try {
uiReset()
} catch (e: Exception) {
logError(e)
}
}
@SuppressLint("SourceLockedOrientationActivity")
private fun toggleRotate() {
activity?.let {
toggleOrientationWithSensor(it)
}
}
override fun playerDimensionsLoaded(width: Int, height: Int) {
isVerticalOrientation = height > width
updateOrientation()
}
private fun updateRemainingTime() {
val duration = player.getDuration()
val position = player.getPosition()
if (duration != null && duration > 1 && position != null) {
val remainingTimeSeconds = (duration - position + 500) / 1000
val formattedTime = "-${DateUtils.formatElapsedTime(remainingTimeSeconds)}"
playerBinding?.timeLeft?.text = formattedTime
}
}
private fun setRemainingTimeCounter(showRemaining: Boolean) {
durationMode = showRemaining
playerBinding?.exoDuration?.isInvisible= showRemaining
playerBinding?.timeLeft?.isVisible = showRemaining
}
private fun dynamicOrientation(): Int {
return if (autoPlayerRotateEnabled) {
if (isVerticalOrientation) {
ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT
} else {
ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
}
} else {
ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE // default orientation
}
}
2024-01-11 14:53:31 +00:00
}