videoplayer stuff

This commit is contained in:
LagradOst 2021-05-28 15:38:06 +02:00
parent c328a315b9
commit 35040e793a
11 changed files with 613 additions and 188 deletions

View file

@ -151,6 +151,10 @@ interface LoadResponse {
val plot: String? val plot: String?
} }
fun LoadResponse.isEpisodeBased(): Boolean {
return this is AnimeLoadResponse || this is TvSeriesLoadResponse
}
data class AnimeLoadResponse( data class AnimeLoadResponse(
val engName: String?, val engName: String?,
val japName: String?, val japName: String?,

View file

@ -2,14 +2,20 @@ package com.lagradost.cloudstream3
import android.Manifest import android.Manifest
import android.app.Activity import android.app.Activity
import android.content.Context
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.content.res.Resources import android.content.res.Resources
import android.media.AudioAttributes
import android.media.AudioFocusRequest
import android.media.AudioManager
import android.os.Build
import android.view.View import android.view.View
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.lagradost.cloudstream3.ui.result.ResultFragment import com.lagradost.cloudstream3.ui.result.ResultFragment
import com.lagradost.cloudstream3.utils.Event
object UIHelper { object UIHelper {
val Int.toPx: Int get() = (this * Resources.getSystem().displayMetrics.density).toInt() val Int.toPx: Int get() = (this * Resources.getSystem().displayMetrics.density).toInt()
@ -70,4 +76,58 @@ object UIHelper {
fun Activity.getGridIsCompact(): Boolean { fun Activity.getGridIsCompact(): Boolean {
return getGridFormat() != "grid" return getGridFormat() != "grid"
} }
fun Activity.requestLocalAudioFocus(focusRequest: AudioFocusRequest?) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && focusRequest != null) {
val audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager
audioManager.requestAudioFocus(focusRequest)
} else {
val audioManager: AudioManager =
getSystemService(Context.AUDIO_SERVICE) as AudioManager
audioManager.requestAudioFocus(
null,
AudioManager.STREAM_MUSIC,
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK
)
}
}
private var _AudioFocusRequest: AudioFocusRequest? = null
private var _OnAudioFocusChangeListener: AudioManager.OnAudioFocusChangeListener? = null
var onAudioFocusEvent = Event<Boolean>()
fun getAudioListener(): AudioManager.OnAudioFocusChangeListener? {
if(_OnAudioFocusChangeListener != null) return _OnAudioFocusChangeListener
_OnAudioFocusChangeListener = AudioManager.OnAudioFocusChangeListener {
onAudioFocusEvent.invoke(
when (it) {
AudioManager.AUDIOFOCUS_GAIN -> true
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE -> true
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT -> true
else -> false
}
)
}
return _OnAudioFocusChangeListener
}
fun getFocusRequest(): AudioFocusRequest? {
if (_AudioFocusRequest != null) return _AudioFocusRequest
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN).run {
setAudioAttributes(AudioAttributes.Builder().run {
setUsage(AudioAttributes.USAGE_MEDIA)
setContentType(AudioAttributes.CONTENT_TYPE_MOVIE)
build()
})
setAcceptsDelayedFocusGain(true)
getAudioListener()?.let {
setOnAudioFocusChangeListener(it)
}
build()
}
} else {
null
}
}
} }

View file

@ -1,7 +1,5 @@
package com.lagradost.cloudstream3.ui.player package com.lagradost.cloudstream3.ui.player
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.ObjectAnimator import android.animation.ObjectAnimator
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Activity import android.app.Activity
@ -13,10 +11,7 @@ import android.database.ContentObserver
import android.graphics.Color import android.graphics.Color
import android.media.AudioManager import android.media.AudioManager
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.*
import android.os.Handler
import android.os.Looper
import android.os.SystemClock
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.MotionEvent import android.view.MotionEvent
import android.view.View import android.view.View
@ -26,6 +21,7 @@ import android.view.animation.*
import android.widget.ProgressBar import android.widget.ProgressBar
import android.widget.Toast import android.widget.Toast
import android.widget.Toast.LENGTH_LONG import android.widget.Toast.LENGTH_LONG
import androidx.appcompat.app.AlertDialog
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
@ -44,20 +40,24 @@ import com.google.android.exoplayer2.upstream.DataSource
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory
import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory
import com.google.android.exoplayer2.util.MimeTypes import com.google.android.exoplayer2.util.MimeTypes
import com.lagradost.cloudstream3.LoadResponse import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.UIHelper.getFocusRequest
import com.lagradost.cloudstream3.UIHelper.requestLocalAudioFocus
import com.lagradost.cloudstream3.UIHelper.toPx import com.lagradost.cloudstream3.UIHelper.toPx
import com.lagradost.cloudstream3.USER_AGENT
import com.lagradost.cloudstream3.mvvm.Resource import com.lagradost.cloudstream3.mvvm.Resource
import com.lagradost.cloudstream3.mvvm.observe import com.lagradost.cloudstream3.mvvm.observeDirectly
import com.lagradost.cloudstream3.ui.result.ResultEpisode import com.lagradost.cloudstream3.ui.result.ResultEpisode
import com.lagradost.cloudstream3.ui.result.ResultViewModel import com.lagradost.cloudstream3.ui.result.ResultViewModel
import com.lagradost.cloudstream3.utils.DataStore.getKey import com.lagradost.cloudstream3.utils.DataStore.getKey
import com.lagradost.cloudstream3.utils.DataStore.setKey
import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.getId
import kotlinx.android.synthetic.main.fragment_player.* import kotlinx.android.synthetic.main.fragment_player.*
import kotlinx.android.synthetic.main.player_custom_layout.* import kotlinx.android.synthetic.main.player_custom_layout.*
import kotlinx.coroutines.* import kotlinx.coroutines.*
import java.io.File import java.io.File
import java.util.*
import javax.net.ssl.HttpsURLConnection import javax.net.ssl.HttpsURLConnection
import javax.net.ssl.SSLContext import javax.net.ssl.SSLContext
import javax.net.ssl.SSLSession import javax.net.ssl.SSLSession
@ -77,6 +77,9 @@ const val PLAYBACK_SPEED = "playback_speed"
const val RESIZE_MODE_KEY = "resize_mode" // Last used resize mode const val RESIZE_MODE_KEY = "resize_mode" // Last used resize mode
const val PLAYBACK_SPEED_KEY = "playback_speed" // Last used playback speed const val PLAYBACK_SPEED_KEY = "playback_speed" // Last used playback speed
const val OPENING_PROCENTAGE = 50
const val AUTOLOAD_NEXT_EPISODE_PROCENTAGE = 80
enum class PlayerEventType(val value: Int) { enum class PlayerEventType(val value: Int) {
Stop(-1), Stop(-1),
Pause(0), Pause(0),
@ -100,6 +103,8 @@ data class PlayerData(
)*/ )*/
data class PlayerData( data class PlayerData(
val episodeIndex: Int, val episodeIndex: Int,
val seasonIndex: Int?,
val mirrorId: Int,
) )
class PlayerFragment : Fragment() { class PlayerFragment : Fragment() {
@ -115,6 +120,12 @@ class PlayerFragment : Fragment() {
private var isShowing = true private var isShowing = true
private lateinit var exoPlayer: SimpleExoPlayer private lateinit var exoPlayer: SimpleExoPlayer
//private var currentPercentage = 0
// private var hasNextEpisode = true
val formatBuilder = StringBuilder()
val formatter = Formatter(formatBuilder, Locale.getDefault())
private var width = Resources.getSystem().displayMetrics.heightPixels private var width = Resources.getSystem().displayMetrics.heightPixels
private var height = Resources.getSystem().displayMetrics.widthPixels private var height = Resources.getSystem().displayMetrics.widthPixels
@ -200,13 +211,18 @@ class PlayerFragment : Fragment() {
start() start()
} }
ObjectAnimator.ofFloat(video_title_rez, "translationY", titleMove).apply {
duration = 200
start()
}
val playerBarMove = if (isShowing) 0f else 50.toPx.toFloat() val playerBarMove = if (isShowing) 0f else 50.toPx.toFloat()
ObjectAnimator.ofFloat(bottom_player_bar, "translationY", playerBarMove).apply { ObjectAnimator.ofFloat(bottom_player_bar, "translationY", playerBarMove).apply {
duration = 200 duration = 200
start() start()
} }
changeSkip()
val fadeTo = if (isShowing) 1f else 0f val fadeTo = if (isShowing) 1f else 0f
val fadeAnimation = AlphaAnimation(1f - fadeTo, fadeTo) val fadeAnimation = AlphaAnimation(1f - fadeTo, fadeTo)
@ -217,6 +233,7 @@ class PlayerFragment : Fragment() {
shadow_overlay?.startAnimation(fadeAnimation) shadow_overlay?.startAnimation(fadeAnimation)
} }
video_holder?.startAnimation(fadeAnimation) video_holder?.startAnimation(fadeAnimation)
//video_lock_holder?.startAnimation(fadeAnimation) //video_lock_holder?.startAnimation(fadeAnimation)
} }
@ -252,6 +269,9 @@ class PlayerFragment : Fragment() {
private var hasPassedSkipLimit = false private var hasPassedSkipLimit = false
private val swipeEnabled = true //<settingsManager!!.getBoolean("swipe_enabled", true) private val swipeEnabled = true //<settingsManager!!.getBoolean("swipe_enabled", true)
private val swipeVerticalEnabled = true//settingsManager.getBoolean("swipe_vertical_enabled", true) private val swipeVerticalEnabled = true//settingsManager.getBoolean("swipe_vertical_enabled", true)
private val playBackSpeedEnabled = true//settingsManager!!.getBoolean("playback_speed_enabled", false)
private val playerResizeEnabled = true//settingsManager!!.getBoolean("player_resize_enabled", false)
private var isMovingStartTime = 0L private var isMovingStartTime = 0L
private var currentX = 0F private var currentX = 0F
private var currentY = 0F private var currentY = 0F
@ -290,7 +310,7 @@ class PlayerFragment : Fragment() {
val currentVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC) val currentVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC)
val maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC) val maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)
if (progressBarLeftHolder.alpha <= 0f) { if (progressBarLeftHolder?.alpha ?: 0f <= 0f) {
cachedVolume = currentVolume.toFloat() / maxVolume.toFloat() cachedVolume = currentVolume.toFloat() / maxVolume.toFloat()
} }
@ -324,18 +344,9 @@ class PlayerFragment : Fragment() {
val alpha = minOf(0.95f, val alpha = minOf(0.95f,
brightness_overlay.alpha + diffY.toFloat() * 0.5f) // 0.05f *if (diffY > 0) 1 else -1 brightness_overlay.alpha + diffY.toFloat() * 0.5f) // 0.05f *if (diffY > 0) 1 else -1
brightness_overlay?.alpha = alpha brightness_overlay?.alpha = alpha
//progressBarRight?.progress = ((1f - alpha) * 100).toInt()
progressBarRight?.max = 100 * 100 progressBarRight?.max = 100 * 100
progressBarRight?.progress = ((1f - alpha) * 100 * 100).toInt() progressBarRight?.progress = ((1f - alpha) * 100 * 100).toInt()
/* val animation: ObjectAnimator = ObjectAnimator.ofInt(progressBarRight,
"progress",
progressBarRight.progress,
.toInt())
animation.duration = 100
animation.setAutoCancel(true)
animation.interpolator = DecelerateInterpolator()
animation.start()*/
currentY = motionEvent.rawY currentY = motionEvent.rawY
} }
@ -377,8 +388,11 @@ class PlayerFragment : Fragment() {
TransitionManager.beginDelayedTransition(player_holder, transition) TransitionManager.beginDelayedTransition(player_holder, transition)
if (abs(skipTime) > 7000 && !preventHorizontalSwipe && swipeEnabled) { if (abs(skipTime) > 7000 && !preventHorizontalSwipe && swipeEnabled) {
exoPlayer.seekTo(maxOf(minOf(skipTime + isMovingStartTime, exoPlayer.duration), 0)) seekTo(skipTime + isMovingStartTime)
//exoPlayer.seekTo(maxOf(minOf(skipTime + isMovingStartTime, exoPlayer.duration), 0))
} }
changeSkip()
hasPassedSkipLimit = false hasPassedSkipLimit = false
hasPassedVerticalSwipeThreshold = false hasPassedVerticalSwipeThreshold = false
preventHorizontalSwipe = false preventHorizontalSwipe = false
@ -402,9 +416,43 @@ class PlayerFragment : Fragment() {
} }
} }
private fun seekTime(time: Long) { fun changeSkip(position: Long? = null) {
exoPlayer.seekTo(maxOf(minOf(exoPlayer.currentPosition + time, exoPlayer.duration), 0)) if (exoPlayer.currentPosition >= 0) {
val percentage = ((position ?: exoPlayer.currentPosition) * 100 / exoPlayer.contentDuration).toInt()
val hasNext = hasNextEpisode()
if (percentage >= AUTOLOAD_NEXT_EPISODE_PROCENTAGE && hasNext) {
val ep =
episodes[playerData.episodeIndex + 1]
if ((allEpisodes[ep.id]?.size ?: 0) <= 0) {
viewModel.loadEpisode(ep, false) {
//NOTHING
} }
}
}
val nextEp = percentage >= OPENING_PROCENTAGE
skip_op_text.text = if (nextEp) "Next Episode" else "Skip OP"
val isVis =
if (nextEp) hasNext //&& !isCurrentlySkippingEp
else (localData is AnimeLoadResponse)
skip_op.visibility = if (isVis) View.VISIBLE else View.GONE
}
}
private fun seekTime(time: Long) {
changeSkip()
seekTo(exoPlayer.currentPosition + time)
}
private fun seekTo(time: Long) {
val correctTime = maxOf(minOf(time, exoPlayer.duration), 0)
exoPlayer.seekTo(correctTime)
changeSkip(correctTime)
}
private var hasUsedFirstRender = false
private fun releasePlayer() { private fun releasePlayer() {
val alphaAnimation = AlphaAnimation(0f, 1f) val alphaAnimation = AlphaAnimation(0f, 1f)
@ -460,6 +508,8 @@ class PlayerFragment : Fragment() {
AspectRatioFrameLayout.RESIZE_MODE_ZOOM, AspectRatioFrameLayout.RESIZE_MODE_ZOOM,
) )
private var localData: LoadResponse? = null
private fun updateLock() { private fun updateLock() {
video_locked_img.setImageResource(if (isLocked) R.drawable.video_locked else R.drawable.video_unlocked) video_locked_img.setImageResource(if (isLocked) R.drawable.video_locked else R.drawable.video_unlocked)
val color = if (isLocked) ContextCompat.getColor(requireContext(), R.color.videoColorPrimary) val color = if (isLocked) ContextCompat.getColor(requireContext(), R.color.videoColorPrimary)
@ -533,7 +583,7 @@ class PlayerFragment : Fragment() {
playerData = mapper.readValue(it, PlayerData::class.java) playerData = mapper.readValue(it, PlayerData::class.java)
} }
observe(viewModel.episodes) { _episodes -> observeDirectly(viewModel.episodes) { _episodes ->
episodes = _episodes episodes = _episodes
if (isLoading) { if (isLoading) {
if (playerData.episodeIndex > 0 && playerData.episodeIndex < episodes.size) { if (playerData.episodeIndex > 0 && playerData.episodeIndex < episodes.size) {
@ -544,16 +594,16 @@ class PlayerFragment : Fragment() {
} }
} }
observe(viewModel.allEpisodes) { _allEpisodes -> observeDirectly(viewModel.allEpisodes) { _allEpisodes ->
allEpisodes = _allEpisodes allEpisodes = _allEpisodes
} }
observe(viewModel.resultResponse) { data -> observeDirectly(viewModel.resultResponse) { data ->
when (data) { when (data) {
is Resource.Success -> { is Resource.Success -> {
val d = data.value val d = data.value
if (d is LoadResponse) { if (d is LoadResponse) {
localData = d
} }
} }
is Resource.Failure -> { is Resource.Failure -> {
@ -574,12 +624,9 @@ class PlayerFragment : Fragment() {
val goLeft = AnimationUtils.loadAnimation(context, R.anim.go_left) val goLeft = AnimationUtils.loadAnimation(context, R.anim.go_left)
goLeft.setAnimationListener(object : Animation.AnimationListener { goLeft.setAnimationListener(object : Animation.AnimationListener {
override fun onAnimationStart(animation: Animation?) { override fun onAnimationStart(animation: Animation?) {}
}
override fun onAnimationRepeat(animation: Animation?) { override fun onAnimationRepeat(animation: Animation?) {}
}
override fun onAnimationEnd(animation: Animation?) { override fun onAnimationEnd(animation: Animation?) {
exo_rew_text.post { exo_rew_text.text = "$fastForwardTime" } exo_rew_text.post { exo_rew_text.text = "$fastForwardTime" }
@ -600,12 +647,9 @@ class PlayerFragment : Fragment() {
val goRight = AnimationUtils.loadAnimation(context, R.anim.go_right) val goRight = AnimationUtils.loadAnimation(context, R.anim.go_right)
goRight.setAnimationListener(object : Animation.AnimationListener { goRight.setAnimationListener(object : Animation.AnimationListener {
override fun onAnimationStart(animation: Animation?) { override fun onAnimationStart(animation: Animation?) {}
}
override fun onAnimationRepeat(animation: Animation?) { override fun onAnimationRepeat(animation: Animation?) {}
}
override fun onAnimationEnd(animation: Animation?) { override fun onAnimationEnd(animation: Animation?) {
exo_ffwd_text.post { exo_ffwd_text.text = "$fastForwardTime" } exo_ffwd_text.post { exo_ffwd_text.text = "$fastForwardTime" }
@ -634,6 +678,10 @@ class PlayerFragment : Fragment() {
centerMenu.startAnimation(fadeAnimation) centerMenu.startAnimation(fadeAnimation)
//video_bar.startAnimation(fadeAnimation) //video_bar.startAnimation(fadeAnimation)
//TITLE
video_title_rez.startAnimation(fadeAnimation)
video_title.startAnimation(fadeAnimation)
// BOTTOM // BOTTOM
resize_player.startAnimation(fadeAnimation) resize_player.startAnimation(fadeAnimation)
playback_speed_btt.startAnimation(fadeAnimation) playback_speed_btt.startAnimation(fadeAnimation)
@ -676,14 +724,136 @@ class PlayerFragment : Fragment() {
click_overlay?.setOnTouchListener( click_overlay?.setOnTouchListener(
Listener() Listener()
) )
playback_speed_btt.visibility = if (playBackSpeedEnabled) VISIBLE else GONE
playback_speed_btt.setOnClickListener {
lateinit var dialog: AlertDialog
// Lmao kind bad
val speedsText = arrayOf("0.5x", "0.75x", "1x", "1.25x", "1.5x", "1.75x", "2x")
val speedsNumbers = arrayOf(0.5f, 0.75f, 1f, 1.25f, 1.5f, 1.75f, 2f)
val builder = AlertDialog.Builder(requireContext(), R.style.AlertDialogCustom)
builder.setTitle("Pick playback speed")
builder.setSingleChoiceItems(speedsText, speedsNumbers.indexOf(playbackSpeed)) { _, which ->
//val speed = speedsText[which]
//Toast.makeText(requireContext(), "$speed selected.", Toast.LENGTH_SHORT).show()
playbackSpeed = speedsNumbers[which]
requireContext().setKey(PLAYBACK_SPEED_KEY, playbackSpeed)
val param = PlaybackParameters(playbackSpeed)
exoPlayer.playbackParameters = param
player_speed_text.text = "Speed (${playbackSpeed}x)".replace(".0x", "x")
dialog.dismiss()
}
dialog = builder.create()
dialog.show()
} }
fun getCurrentUrl(): ExtractorLink { sources_btt.setOnClickListener {
return ExtractorLink("", lateinit var dialog: AlertDialog
getUrls()?.let { it1 ->
sortUrls(it1).let { sources ->
val sourcesText = sources.map { it.name }
val builder = AlertDialog.Builder(requireContext(), R.style.AlertDialogCustom)
builder.setTitle("Pick source")
builder.setSingleChoiceItems(sourcesText.toTypedArray(),
sources.indexOf(getCurrentUrl())) { _, which ->
//val speed = speedsText[which]
//Toast.makeText(requireContext(), "$speed selected.", Toast.LENGTH_SHORT).show()
setMirrorId(sources[which].getId())
initPlayer(getCurrentUrl())
dialog.dismiss()
}
dialog = builder.create()
dialog.show()
}
}
}
player_view.resizeMode = resizeModes[resizeMode]
if (playerResizeEnabled) {
resize_player.visibility = VISIBLE
resize_player.setOnClickListener {
resizeMode = (resizeMode + 1) % resizeModes.size
requireContext().setKey(RESIZE_MODE_KEY, resizeMode)
player_view.resizeMode = resizeModes[resizeMode]
//exoPlayer.videoScalingMode = C.VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING
}
} else {
resize_player.visibility = GONE
}
skip_op.setOnClickListener {
if (exoPlayer.currentPosition * 100 / exoPlayer.duration >= OPENING_PROCENTAGE) {
if (hasNextEpisode()) {
// skip_op.visibility = View.GONE
skipToNextEpisode()
}
} else {
skipOP()
}
}
}
private fun getCurrentUrl(): ExtractorLink? {
val urls = getUrls() ?: return null
for (i in urls) {
if (i.getId() == playerData.mirrorId) {
return i
}
}
return null
/*ExtractorLink("",
"TEST", "TEST",
"http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4", "https://v6.4animu.me/Overlord/Overlord-Episode-01-1080p.mp4",
//"http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4",
"", "",
0) 0)*/
}
private fun getUrls(): List<ExtractorLink>? {
return try {
allEpisodes[getEpisode()?.id]
} catch (e: Exception) {
null
}
}
private fun sortUrls(urls: List<ExtractorLink>): List<ExtractorLink> {
return urls.sortedBy { t -> -t.quality }
}
private fun getEpisode(): ResultEpisode? {
return try {
episodes[playerData.episodeIndex]
} catch (e: Exception) {
null
}
}
private fun hasNextEpisode(): Boolean {
return episodes.size > playerData.episodeIndex + 1
}
private var isCurrentlySkippingEp = false
private fun skipToNextEpisode() {
if (isCurrentlySkippingEp) return
isCurrentlySkippingEp = true
val copy = playerData.copy(episodeIndex = playerData.episodeIndex + 1)
playerData = copy
initPlayer()
}
private fun setMirrorId(id: Int) {
val copy = playerData.copy(mirrorId = id)
playerData = copy
initPlayer()
} }
override fun onStart() { override fun onStart() {
@ -730,32 +900,50 @@ class PlayerFragment : Fragment() {
outState.putBoolean(STATE_PLAYER_PLAYING, isPlayerPlaying) outState.putBoolean(STATE_PLAYER_PLAYING, isPlayerPlaying)
outState.putInt(RESIZE_MODE_KEY, resizeMode) outState.putInt(RESIZE_MODE_KEY, resizeMode)
outState.putFloat(PLAYBACK_SPEED, playbackSpeed) outState.putFloat(PLAYBACK_SPEED, playbackSpeed)
outState.putString("data", mapper.writeValueAsString(playerData))
savePos() savePos()
super.onSaveInstanceState(outState) super.onSaveInstanceState(outState)
} }
private var currentWindow = 0 private var currentWindow = 0
private var playbackPosition: Long = 0 private var playbackPosition: Long = 0
/*
private fun updateProgressBar() {
val duration: Long =exoPlayer.getDuration()
val position: Long =exoPlayer.getCurrentPosition()
//http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4 handler.removeCallbacks(updateProgressAction)
private fun initPlayer() { val playbackState = exoPlayer.getPlaybackState()
println("INIT PLAYER") if (playbackState != Player.STATE_IDLE && playbackState != Player.STATE_ENDED) {
view?.setOnTouchListener { _, _ -> return@setOnTouchListener true } // VERY IMPORTANT https://stackoverflow.com/questions/28818926/prevent-clicking-on-a-button-in-an-activity-while-showing-a-fragment var delayMs: Long
thread { if (player.getPlayWhenReady() && playbackState == Player.STATE_READY) {
val currentUrl = getCurrentUrl() delayMs = 1000 - position % 1000
if (currentUrl == null) { if (delayMs < 200) {
activity?.runOnUiThread { delayMs += 1000
Toast.makeText(activity, "Error getting link", LENGTH_LONG).show()
//MainActivity.popCurrentPage()
} }
} else { } else {
delayMs = 1000
}
handler.postDelayed(updateProgressAction, delayMs)
}
}
private val updateProgressAction = Runnable { updateProgressBar() }*/
@SuppressLint("SetTextI18n")
fun initPlayer(currentUrl: ExtractorLink?) {
if (currentUrl == null) return
hasUsedFirstRender = false
try { try {
activity?.runOnUiThread { if (this::exoPlayer.isInitialized) {
savePos()
exoPlayer.release()
}
val isOnline = val isOnline =
currentUrl.url.startsWith("https://") || currentUrl.url.startsWith("http://") currentUrl.url.startsWith("https://") || currentUrl.url.startsWith("http://")
if (settingsManager?.getBoolean("ignore_ssl", true) == true) { if (settingsManager.getBoolean("ignore_ssl", true)) {
// Disables ssl check // Disables ssl check
val sslContext: SSLContext = SSLContext.getInstance("TLS") val sslContext: SSLContext = SSLContext.getInstance("TLS")
sslContext.init(null, arrayOf(SSLTrustManager()), java.security.SecureRandom()) sslContext.init(null, arrayOf(SSLTrustManager()), java.security.SecureRandom())
@ -823,16 +1011,96 @@ class PlayerFragment : Fragment() {
exoPlayer.setHandleAudioBecomingNoisy(true) // WHEN HEADPHONES ARE PLUGGED OUT https://github.com/google/ExoPlayer/issues/7288 exoPlayer.setHandleAudioBecomingNoisy(true) // WHEN HEADPHONES ARE PLUGGED OUT https://github.com/google/ExoPlayer/issues/7288
player_view.player = exoPlayer player_view.player = exoPlayer
// Sets the speed // Sets the speed
exoPlayer.setPlaybackParameters(PlaybackParameters(playbackSpeed!!)) exoPlayer.playbackParameters = PlaybackParameters(playbackSpeed)
player_speed_text?.text = "Speed (${playbackSpeed}x)".replace(".0x", "x") player_speed_text?.text = "Speed (${playbackSpeed}x)".replace(".0x", "x")
if (localData != null) {
val data = localData!!
val localEpisode = getEpisode()
if (localEpisode != null) {
val episode = localEpisode.episode
val season: Int? = localEpisode.season
val isEpisodeBased = data.isEpisodeBased()
video_title?.text = data.name +
if (isEpisodeBased)
if (season == null)
" - Episode $episode"
else
" \"S${season}:E${episode}\""
else ""
video_title_rez?.text = currentUrl.name
}
}
/*
exo_remaining.text = Util.getStringForTime(formatBuilder,
formatter,
exoPlayer.contentDuration - exoPlayer.currentPosition)
*/
//https://stackoverflow.com/questions/47731779/detect-pause-resume-in-exoplayer //https://stackoverflow.com/questions/47731779/detect-pause-resume-in-exoplayer
exoPlayer.addListener(object : Player.Listener { exoPlayer.addListener(object : Player.Listener {
// @SuppressLint("NewApi") override fun onRenderedFirstFrame() {
super.onRenderedFirstFrame()
isCurrentlySkippingEp = false
val height = exoPlayer.videoFormat?.height
val width = exoPlayer.videoFormat?.width
video_title_rez?.text =
if (height == null || width == null) currentUrl.name else "${currentUrl.name} - ${width}x${height}"
if(!hasUsedFirstRender) { // DON'T WANT TO SET MULTIPLE MESSAGES
println("FIRST RENDER")
changeSkip()
exoPlayer
.createMessage { messageType, payload ->
changeSkip()
}
.setLooper(Looper.getMainLooper())
.setPosition( /* positionMs= */exoPlayer.contentDuration * OPENING_PROCENTAGE / 100)
// .setPayload(customPayloadData)
.setDeleteAfterDelivery(false)
.send()
exoPlayer
.createMessage { messageType, payload ->
changeSkip()
}
.setLooper(Looper.getMainLooper())
.setPosition( /* positionMs= */exoPlayer.contentDuration * AUTOLOAD_NEXT_EPISODE_PROCENTAGE / 100)
// .setPayload(customPayloadData)
.setDeleteAfterDelivery(false)
.send()
}
else {
changeSkip()
}
hasUsedFirstRender = true
}
override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) { override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) {
// updatePIPModeActions() // updatePIPModeActions()
if (playWhenReady && playbackState == Player.STATE_READY) { if (playWhenReady) {
// focusRequest?.let { activity?.requestAudioFocus(it) } when (playbackState) {
Player.STATE_READY -> {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
requireActivity().requestLocalAudioFocus(getFocusRequest())
}
}
Player.STATE_ENDED -> {
if (hasNextEpisode()) {
skipToNextEpisode()
}
}
Player.STATE_BUFFERING -> {
changeSkip()
}
else -> {
}
}
} }
} }
@ -871,12 +1139,45 @@ class PlayerFragment : Fragment() {
} }
} }
}) })
}
} catch (e: java.lang.IllegalStateException) { } catch (e: java.lang.IllegalStateException) {
println("Warning: Illegal state exception in PlayerFragment") println("Warning: Illegal state exception in PlayerFragment")
} }
} }
//http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4
@SuppressLint("SetTextI18n")
private fun initPlayer() {
println("INIT PLAYER")
view?.setOnTouchListener { _, _ -> return@setOnTouchListener true } // VERY IMPORTANT https://stackoverflow.com/questions/28818926/prevent-clicking-on-a-button-in-an-activity-while-showing-a-fragment
val tempUrl = getCurrentUrl()
println("TEMP:" + tempUrl?.name)
if (tempUrl == null) {
val localEpisode = getEpisode()
if (localEpisode != null) {
viewModel.loadEpisode(localEpisode, false) {
//if(it is Resource.Success && it.value == true)
val currentUrls = getUrls()
if (currentUrls != null && currentUrls.isNotEmpty()) {
setMirrorId(sortUrls(currentUrls)[0].getId()) // BECAUSE URLS CANT BE REORDERED
} }
initPlayer(getCurrentUrl())
}
}
} else {
initPlayer(tempUrl)
}
/*
val currentUrl = tempUrl
if (currentUrl == null) {
activity?.runOnUiThread {
Toast.makeText(activity, "Error getting link", LENGTH_LONG).show()
//MainActivity.popCurrentPage()
}
} else {
}*/
//isLoadingNextEpisode = false //isLoadingNextEpisode = false
} }

View file

@ -45,6 +45,7 @@ const val MAX_SYNO_LENGH = 600
data class ResultEpisode( data class ResultEpisode(
val name: String?, val name: String?,
val episode: Int, val episode: Int,
val season: Int?,
val data: Any, val data: Any,
val apiName: String, val apiName: String,
val id: Int, val id: Int,
@ -122,7 +123,7 @@ class ResultFragment : Fragment() {
R.anim.exit_anim, R.anim.exit_anim,
R.anim.pop_enter, R.anim.pop_enter,
R.anim.pop_exit) R.anim.pop_exit)
.add(R.id.homeRoot, PlayerFragment.newInstance(PlayerData(index))) .add(R.id.homeRoot, PlayerFragment.newInstance(PlayerData(index, null, 0)))
.commit() .commit()
} else { } else {
when (episodeClick.action) { when (episodeClick.action) {

View file

@ -42,6 +42,7 @@ class ResultViewModel : ViewModel() {
episodes.add(ResultEpisode( episodes.add(ResultEpisode(
null, // TODO ADD NAMES null, // TODO ADD NAMES
index + 1, //TODO MAKE ABLE TO NOT HAVE SOME EPISODE index + 1, //TODO MAKE ABLE TO NOT HAVE SOME EPISODE
null, // TODO FIX SEASON
i, i,
apiName, apiName,
(d.url + index).hashCode(), (d.url + index).hashCode(),
@ -59,6 +60,7 @@ class ResultViewModel : ViewModel() {
episodes.add(ResultEpisode( episodes.add(ResultEpisode(
null, // TODO ADD NAMES null, // TODO ADD NAMES
index + 1, //TODO MAKE ABLE TO NOT HAVE SOME EPISODE index + 1, //TODO MAKE ABLE TO NOT HAVE SOME EPISODE
null, // TODO FIX SEASON
i, i,
apiName, apiName,
(d.url + index).hashCode(), (d.url + index).hashCode(),
@ -70,7 +72,7 @@ class ResultViewModel : ViewModel() {
} }
is MovieLoadResponse -> { is MovieLoadResponse -> {
_episodes.postValue(arrayListOf(ResultEpisode(null, _episodes.postValue(arrayListOf(ResultEpisode(null,
0, 0, null,
d.movieUrl, d.movieUrl,
d.apiName, d.apiName,
(d.url).hashCode(), (d.url).hashCode(),
@ -78,9 +80,7 @@ class ResultViewModel : ViewModel() {
0f))) 0f)))
} }
} }
} }
} }
else -> { else -> {
@ -95,20 +95,25 @@ class ResultViewModel : ViewModel() {
private var _apiName: MutableLiveData<String> = MutableLiveData() private var _apiName: MutableLiveData<String> = MutableLiveData()
fun loadEpisode(episode: ResultEpisode, callback: (Resource<Boolean>) -> Unit) { fun loadEpisode(episode: ResultEpisode, isCasting : Boolean, callback: (Resource<Boolean>) -> Unit) {
loadEpisode(episode.id, episode.data, callback) loadEpisode(episode.id, episode.data, isCasting, callback)
} }
fun loadEpisode(id: Int, data: Any, callback: (Resource<Boolean>) -> Unit) = fun loadEpisode(id: Int, data: Any, isCasting : Boolean, callback: (Resource<Boolean>) -> Unit) =
viewModelScope.launch { viewModelScope.launch {
if (_allEpisodes.value?.contains(id) == true) { if (_allEpisodes.value?.contains(id) == true) {
_allEpisodes.value?.remove(id) _allEpisodes.value?.remove(id)
} }
val links = ArrayList<ExtractorLink>() val links = ArrayList<ExtractorLink>()
val data = safeApiCall { val data = safeApiCall {
getApiFromName(_apiName.value).loadLinks(data, true) { //TODO IMPLEMENT CASTING getApiFromName(_apiName.value).loadLinks(data, isCasting) { //TODO IMPLEMENT CASTING
for (i in links) {
if (i.url == it.url) return@loadLinks
}
links.add(it) links.add(it)
_allEpisodes.value?.set(id, links) _allEpisodes.value?.set(id, links)
// _allEpisodes.value?.get(episode.id)?.add(it) // _allEpisodes.value?.get(episode.id)?.add(it)
} }
} }

View file

@ -130,13 +130,13 @@ class SearchFragment : Fragment() {
search_loading_bar.alpha = 0f search_loading_bar.alpha = 0f
} }
/*
(requireActivity() as AppCompatActivity).supportFragmentManager.beginTransaction() (requireActivity() as AppCompatActivity).supportFragmentManager.beginTransaction()
.setCustomAnimations(R.anim.enter_anim, .setCustomAnimations(R.anim.enter_anim,
R.anim.exit_anim, R.anim.exit_anim,
R.anim.pop_enter, R.anim.pop_enter,
R.anim.pop_exit) R.anim.pop_exit)
.add(R.id.homeRoot, PlayerFragment.newInstance(PlayerData(0))) .add(R.id.homeRoot, PlayerFragment.newInstance(PlayerData(0, null,0)))
.commit() .commit()*/
} }
} }

View file

@ -0,0 +1,18 @@
package com.lagradost.cloudstream3.utils
class Event<T> {
private val observers = mutableSetOf<(T) -> Unit>()
operator fun plusAssign(observer: (T) -> Unit) {
observers.add(observer)
}
operator fun minusAssign(observer: (T) -> Unit) {
observers.remove(observer)
}
operator fun invoke(value: T) {
for (observer in observers)
observer(value)
}
}

View file

@ -15,6 +15,10 @@ data class ExtractorLink(
val isM3u8: Boolean = false, val isM3u8: Boolean = false,
) )
fun ExtractorLink.getId() : Int {
return url.hashCode()
}
enum class Qualities(var value: Int) { enum class Qualities(var value: Int) {
Unknown(0), Unknown(0),
SD(-1), // 360p - 480p SD(-1), // 360p - 480p

View file

@ -109,13 +109,27 @@
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintLeft_toLeftOf="parent"
android:gravity="center" android:gravity="center"
android:textFontWeight="900"
android:layout_marginTop="15dp" android:layout_marginTop="15dp"
android:textStyle="bold"
android:textColor="@color/white" android:textColor="@color/white"
android:id="@+id/video_title" android:id="@+id/video_title"
tools:text="Hello world" tools:text="Hello world"
> >
</TextView> </TextView>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="250dp"
android:layout_marginStart="250dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
android:gravity="center"
android:layout_marginTop="35dp"
android:textColor="@color/white"
android:id="@+id/video_title_rez"
tools:text="1920x1080"
>
</TextView>
<!-- <!--
<LinearLayout <LinearLayout
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
@ -374,6 +388,7 @@
app:played_color="@color/videoColorPrimary" app:played_color="@color/videoColorPrimary"
app:unplayed_color="@color/videoProgress"/> app:unplayed_color="@color/videoProgress"/>
<!-- exo_duration-->
<TextView <TextView
tools:text="23:20" tools:text="23:20"
android:id="@id/exo_duration" android:id="@id/exo_duration"
@ -625,6 +640,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:text="Skip OP" android:text="Skip OP"
android:id="@+id/skip_op_text"
android:gravity="start|center" android:gravity="start|center"
android:paddingStart="10dp" android:paddingStart="10dp"

View file

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<color name="colorPrimary">#3d50fa</color> <color name="colorPrimary">#3d50fa</color>
<color name="colorPrimarySecond">@color/colorPrimary</color>
<color name="colorRipple">#1A3D50FA</color> <color name="colorRipple">#1A3D50FA</color>
<color name="colorSearch">#303135</color> <!--#3444D1 @color/itemBackground--> <color name="colorSearch">#303135</color> <!--#3444D1 @color/itemBackground-->
<color name="colorItemSeen">#1E1E32</color> <color name="colorItemSeen">#1E1E32</color>

View file

@ -82,4 +82,19 @@
<item name="tabMinWidth">75dp</item> <item name="tabMinWidth">75dp</item>
<item name="tabMode">scrollable</item> <item name="tabMode">scrollable</item>
</style> </style>
<style name="AlertDialogCustom" parent="Theme.AppCompat.Dialog.Alert">
<item name="android:textColor">@color/textColor</item>
<item name="android:textColorPrimary">@color/textColor</item>
<!--<item name="android:background">@color/darkBackground</item>-->
<item name="android:textAllCaps">false</item>
<!--<item name="android:colorBackground">@color/darkBackground</item>-->
<item name="textColorAlertDialogListItem">@color/textColor</item>
<item name="colorControlNormal">@color/textColor</item>
<!-- colorPrimarySecond used because colorPrimary fails for no reason -->
<item name="colorControlActivated">@color/colorPrimarySecond</item>
<item name="android:windowMinWidthMajor">@android:dimen/dialog_min_width_major</item>
<item name="android:windowMinWidthMinor">@android:dimen/dialog_min_width_minor</item>
<item name="android:windowBackground">@drawable/dialog__window_background</item>
</style>
</resources> </resources>