player stuff

This commit is contained in:
LagradOst 2021-05-23 19:07:43 +02:00
parent 0f5de62e92
commit 3d2f00313b
7 changed files with 364 additions and 67 deletions

View File

@ -21,6 +21,7 @@ android {
buildTypes { buildTypes {
release { release {
minifyEnabled false minifyEnabled false
debuggable true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
} }
} }

View File

@ -4,16 +4,19 @@
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
<application <application
android:allowBackup="true" android:allowBackup="true"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:usesCleartextTraffic="true"
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/AppTheme"> android:theme="@style/AppTheme">
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|keyboard|keyboardHidden|navigation"
android:label="@string/app_name"> android:label="@string/app_name">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN"/> <action android:name="android.intent.action.MAIN"/>

View File

@ -11,6 +11,13 @@ fun <T> LifecycleOwner.observe(liveData: LiveData<T>, action: (t: T) -> Unit) {
liveData.observe(this, Observer { it?.let { t -> action(t) } }) liveData.observe(this, Observer { it?.let { t -> action(t) } })
} }
fun <T> LifecycleOwner.observeDirectly(liveData: LiveData<T>, action: (t: T) -> Unit) {
liveData.observe(this, Observer { it?.let { t -> action(t) } })
val currentValue = liveData.value
if (currentValue != null)
action(currentValue)
}
sealed class Resource<out T> { sealed class Resource<out T> {
data class Success<out T>(val value: T) : Resource<T>() data class Success<out T>(val value: T) : Resource<T>()
data class Failure( data class Failure(

View File

@ -1,29 +1,55 @@
package com.lagradost.cloudstream3.ui.player package com.lagradost.cloudstream3.ui.player
import android.app.Activity
import android.content.Context.AUDIO_SERVICE
import android.content.pm.ActivityInfo
import android.database.ContentObserver
import android.media.AudioManager
import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.View.GONE
import android.view.View.VISIBLE
import android.view.ViewGroup import android.view.ViewGroup
import android.view.animation.AlphaAnimation import android.view.animation.AlphaAnimation
import android.widget.ProgressBar
import android.widget.Toast
import android.widget.Toast.LENGTH_LONG
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.preference.PreferenceManager
import com.fasterxml.jackson.databind.DeserializationFeature import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.json.JsonMapper import com.fasterxml.jackson.databind.json.JsonMapper
import com.fasterxml.jackson.module.kotlin.KotlinModule import com.fasterxml.jackson.module.kotlin.KotlinModule
import com.google.android.exoplayer2.SimpleExoPlayer import com.google.android.exoplayer2.*
import com.google.android.exoplayer2.source.DefaultMediaSourceFactory
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout import com.google.android.exoplayer2.ui.AspectRatioFrameLayout
import com.google.android.exoplayer2.upstream.DataSource
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory
import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory
import com.google.android.exoplayer2.util.MimeTypes
import com.lagradost.cloudstream3.LoadResponse import com.lagradost.cloudstream3.LoadResponse
import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.R
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.observe
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
import com.lagradost.cloudstream3.utils.DataStore.getKey import com.lagradost.cloudstream3.utils.DataStore.getKey
import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.ExtractorLink
import java.io.FileDescriptor import kotlinx.android.synthetic.main.fragment_player.*
import java.io.PrintWriter import kotlinx.android.synthetic.main.player_custom_layout.*
import java.io.File
import javax.net.ssl.HttpsURLConnection
import javax.net.ssl.SSLContext
import javax.net.ssl.SSLSession
import kotlin.concurrent.thread
//http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4
const val STATE_RESUME_WINDOW = "resumeWindow" const val STATE_RESUME_WINDOW = "resumeWindow"
const val STATE_RESUME_POSITION = "resumePosition" const val STATE_RESUME_POSITION = "resumePosition"
const val STATE_PLAYER_FULLSCREEN = "playerFullscreen" const val STATE_PLAYER_FULLSCREEN = "playerFullscreen"
@ -75,24 +101,33 @@ class PlayerFragment : Fragment() {
} }
private fun releasePlayer() { private fun releasePlayer() {
val alphaAnimation = AlphaAnimation(0f, 1f)
alphaAnimation.duration = 100
alphaAnimation.fillAfter = true
loading_overlay.startAnimation(alphaAnimation)
video_go_back_holder.visibility = VISIBLE
if (this::exoPlayer.isInitialized) { if (this::exoPlayer.isInitialized) {
isPlayerPlaying = exoPlayer.playWhenReady
playbackPosition = exoPlayer.currentPosition
currentWindow = exoPlayer.currentWindowIndex
exoPlayer.release() exoPlayer.release()
} }
} }
override fun onSaveInstanceState(outState: Bundle) { private class SettingsContentObserver(handler: Handler?, val activity: Activity) : ContentObserver(handler) {
if (this::exoPlayer.isInitialized) { private val audioManager = activity.getSystemService(AUDIO_SERVICE) as? AudioManager
outState.putInt(STATE_RESUME_WINDOW, exoPlayer.currentWindowIndex) override fun onChange(selfChange: Boolean) {
outState.putLong(STATE_RESUME_POSITION, exoPlayer.currentPosition) val currentVolume = audioManager?.getStreamVolume(AudioManager.STREAM_MUSIC)
val maxVolume = audioManager?.getStreamMaxVolume(AudioManager.STREAM_MUSIC)
val progressBarRight = activity.findViewById<ProgressBar>(R.id.progressBarRight)
if (currentVolume != null && maxVolume != null) {
progressBarRight?.progress = currentVolume * 100 / maxVolume
}
} }
outState.putBoolean(STATE_PLAYER_FULLSCREEN, isFullscreen)
outState.putBoolean(STATE_PLAYER_PLAYING, isPlayerPlaying)
outState.putInt(RESIZE_MODE_KEY, resizeMode)
outState.putFloat(PLAYBACK_SPEED, playbackSpeed)
savePos()
super.onSaveInstanceState(outState)
} }
private lateinit var volumeObserver: SettingsContentObserver
companion object { companion object {
fun newInstance(data: PlayerData) = fun newInstance(data: PlayerData) =
PlayerFragment().apply { PlayerFragment().apply {
@ -126,16 +161,41 @@ class PlayerFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
if (savedInstanceState != null) {
currentWindow = savedInstanceState.getInt(STATE_RESUME_WINDOW)
playbackPosition = savedInstanceState.getLong(STATE_RESUME_POSITION)
isFullscreen = savedInstanceState.getBoolean(STATE_PLAYER_FULLSCREEN)
isPlayerPlaying = savedInstanceState.getBoolean(STATE_PLAYER_PLAYING)
resizeMode = savedInstanceState.getInt(RESIZE_MODE_KEY)
playbackSpeed = savedInstanceState.getFloat(PLAYBACK_SPEED)
}
resizeMode = requireContext().getKey(RESIZE_MODE_KEY, 0)!! resizeMode = requireContext().getKey(RESIZE_MODE_KEY, 0)!!
playbackSpeed = requireContext().getKey(PLAYBACK_SPEED_KEY, 1f)!! playbackSpeed = requireContext().getKey(PLAYBACK_SPEED_KEY, 1f)!!
volumeObserver = SettingsContentObserver(
Handler(
Looper.getMainLooper()
), requireActivity()
)
activity?.contentResolver
?.registerContentObserver(
android.provider.Settings.System.CONTENT_URI, true, volumeObserver
)
viewModel = ViewModelProvider(requireActivity()).get(ResultViewModel::class.java)
arguments?.getString("data")?.let {
playerData = mapper.readValue(it, PlayerData::class.java)
}
observe(viewModel.episodes) { _episodes -> observe(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) {
} } else {
else {
// WHAT THE FUCK DID YOU DO // WHAT THE FUCK DID YOU DO
} }
} }
@ -159,14 +219,211 @@ class PlayerFragment : Fragment() {
} }
} }
println(allEpisodes) println(episodes)
}
fun getCurrentUrl(): ExtractorLink {
return ExtractorLink("",
"TEST",
"http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4",
"",
0)
}
override fun onStart() {
super.onStart()
thread {
// initPlayer()
if (player_view != null) player_view.onResume()
}
}
override fun onResume() {
super.onResume()
activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE
thread {
initPlayer()
if (player_view != null) player_view.onResume()
}
}
override fun onDestroy() {
super.onDestroy()
// releasePlayer()
activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER
}
override fun onPause() {
super.onPause()
if (player_view != null) player_view.onPause()
releasePlayer()
}
override fun onStop() {
super.onStop()
if (player_view != null) player_view.onPause()
releasePlayer()
}
override fun onSaveInstanceState(outState: Bundle) {
if (this::exoPlayer.isInitialized) {
outState.putInt(STATE_RESUME_WINDOW, exoPlayer.currentWindowIndex)
outState.putLong(STATE_RESUME_POSITION, exoPlayer.currentPosition)
}
outState.putBoolean(STATE_PLAYER_FULLSCREEN, isFullscreen)
outState.putBoolean(STATE_PLAYER_PLAYING, isPlayerPlaying)
outState.putInt(RESIZE_MODE_KEY, resizeMode)
outState.putFloat(PLAYBACK_SPEED, playbackSpeed)
savePos()
super.onSaveInstanceState(outState)
}
private var currentWindow = 0
private var playbackPosition: Long = 0
//http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4
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
thread {
val currentUrl = getCurrentUrl()
if (currentUrl == null) {
activity?.runOnUiThread {
Toast.makeText(activity, "Error getting link", LENGTH_LONG).show()
//MainActivity.popCurrentPage()
}
} else {
val settingsManager = PreferenceManager.getDefaultSharedPreferences(activity)
try {
activity?.runOnUiThread {
val isOnline =
currentUrl.url.startsWith("https://") || currentUrl.url.startsWith("http://")
if (settingsManager?.getBoolean("ignore_ssl", true) == true) {
// Disables ssl check
val sslContext: SSLContext = SSLContext.getInstance("TLS")
sslContext.init(null, arrayOf(SSLTrustManager()), java.security.SecureRandom())
sslContext.createSSLEngine()
HttpsURLConnection.setDefaultHostnameVerifier { _: String, _: SSLSession ->
true
}
HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.socketFactory)
}
class CustomFactory : DataSource.Factory {
override fun createDataSource(): DataSource {
return if (isOnline) {
val dataSource = DefaultHttpDataSourceFactory(USER_AGENT).createDataSource()
/*FastAniApi.currentHeaders?.forEach {
dataSource.setRequestProperty(it.key, it.value)
}*/
dataSource.setRequestProperty("Referer", currentUrl.referer)
dataSource
} else {
DefaultDataSourceFactory(requireContext(), USER_AGENT).createDataSource()
}
}
}
val mimeType = if (currentUrl.isM3u8) MimeTypes.APPLICATION_M3U8 else MimeTypes.APPLICATION_MP4
val _mediaItem = MediaItem.Builder()
//Replace needed for android 6.0.0 https://github.com/google/ExoPlayer/issues/5983
.setMimeType(mimeType)
if (isOnline) {
_mediaItem.setUri(currentUrl.url)
} else {
_mediaItem.setUri(Uri.fromFile(File(currentUrl.url)))
}
val mediaItem = _mediaItem.build()
val trackSelector = DefaultTrackSelector(requireContext())
// Disable subtitles
trackSelector.parameters = DefaultTrackSelector.ParametersBuilder(requireContext())
.setRendererDisabled(C.TRACK_TYPE_VIDEO, true)
.setRendererDisabled(C.TRACK_TYPE_TEXT, true)
.setDisabledTextTrackSelectionFlags(C.TRACK_TYPE_TEXT)
.clearSelectionOverrides()
.build()
val _exoPlayer =
SimpleExoPlayer.Builder(this.requireContext())
.setTrackSelector(trackSelector)
_exoPlayer.setMediaSourceFactory(DefaultMediaSourceFactory(CustomFactory()))
exoPlayer = _exoPlayer.build().apply {
playWhenReady = isPlayerPlaying
seekTo(currentWindow, playbackPosition)
setMediaItem(mediaItem, false)
prepare()
}
val alphaAnimation = AlphaAnimation(1f, 0f)
alphaAnimation.duration = 300
alphaAnimation.fillAfter = true
loading_overlay.startAnimation(alphaAnimation)
video_go_back_holder.visibility = GONE
exoPlayer.setHandleAudioBecomingNoisy(true) // WHEN HEADPHONES ARE PLUGGED OUT https://github.com/google/ExoPlayer/issues/7288
player_view.player = exoPlayer
// Sets the speed
exoPlayer.setPlaybackParameters(PlaybackParameters(playbackSpeed!!))
player_speed_text?.text = "Speed (${playbackSpeed}x)".replace(".0x", "x")
//https://stackoverflow.com/questions/47731779/detect-pause-resume-in-exoplayer
exoPlayer.addListener(object : Player.Listener {
// @SuppressLint("NewApi")
override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) {
// updatePIPModeActions()
if (playWhenReady && playbackState == Player.STATE_READY) {
// focusRequest?.let { activity?.requestAudioFocus(it) }
}
}
override fun onPlayerError(error: ExoPlaybackException) {
// Lets pray this doesn't spam Toasts :)
when (error.type) {
ExoPlaybackException.TYPE_SOURCE -> {
if (currentUrl.url != "") {
Toast.makeText(
activity,
"Source error\n" + error.sourceException.message,
LENGTH_LONG
)
.show()
}
}
ExoPlaybackException.TYPE_REMOTE -> {
Toast.makeText(activity, "Remote error", LENGTH_LONG)
.show()
}
ExoPlaybackException.TYPE_RENDERER -> {
Toast.makeText(
activity,
"Renderer error\n" + error.rendererException.message,
LENGTH_LONG
)
.show()
}
ExoPlaybackException.TYPE_UNEXPECTED -> {
Toast.makeText(
activity,
"Unexpected player error\n" + error.unexpectedException.message,
LENGTH_LONG
).show()
}
}
}
})
}
} catch (e: java.lang.IllegalStateException) {
println("Warning: Illegal state exception in PlayerFragment")
}
}
}
//isLoadingNextEpisode = false
} }
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
viewModel = ViewModelProvider(requireActivity()).get(ResultViewModel::class.java)
arguments?.getString("data")?.let {
playerData = mapper.readValue(it, PlayerData::class.java)
}
return inflater.inflate(R.layout.fragment_player, container, false) return inflater.inflate(R.layout.fragment_player, container, false)
} }
} }

View File

@ -0,0 +1,16 @@
package com.lagradost.cloudstream3.ui.player
import java.security.cert.X509Certificate
import javax.net.ssl.X509TrustManager
class SSLTrustManager : X509TrustManager {
override fun checkClientTrusted(p0: Array<out X509Certificate>?, p1: String?) {
}
override fun checkServerTrusted(p0: Array<out X509Certificate>?, p1: String?) {
}
override fun getAcceptedIssuers(): Array<X509Certificate> {
return arrayOf()
}
}

View File

@ -106,12 +106,58 @@ class ResultFragment : Fragment() {
activity?.onBackPressed() activity?.onBackPressed()
} }
val adapter: RecyclerView.Adapter<RecyclerView.ViewHolder>? = activity?.let { it ->
EpisodeAdapter(
it,
ArrayList(),
result_episodes,
) { episodeClick ->
val id = episodeClick.data.id
val index = episodeClick.data.index
val buildInPlayer = true
if (buildInPlayer) {
(requireActivity() as AppCompatActivity).supportFragmentManager.beginTransaction()
.setCustomAnimations(R.anim.enter_anim,
R.anim.exit_anim,
R.anim.pop_enter,
R.anim.pop_exit)
.add(R.id.homeRoot, PlayerFragment.newInstance(PlayerData(index)))
.commit()
} else {
when (episodeClick.action) {
/*
ACTION_PLAY_EPISODE -> {
if (allEpisodes.containsKey(id)) {
playEpisode(allEpisodes[id], index)
} else {
viewModel.loadEpisode(episodeClick.data) { res ->
if (res is Resource.Success) {
playEpisode(allEpisodes[id], index)
}
}
}
}
ACTION_RELOAD_EPISODE -> viewModel.loadEpisode(episodeClick.data) { res ->
if (res is Resource.Success) {
playEpisode(allEpisodes[id], index)
}
}*/
}
}
}
}
result_episodes.adapter = adapter
result_episodes.layoutManager = GridLayoutManager(context, 1)
observe(viewModel.allEpisodes) { observe(viewModel.allEpisodes) {
allEpisodes = it allEpisodes = it
} }
observe(viewModel.episodes) { episodes -> observe(viewModel.episodes) { episodes ->
if(result_episodes == null) return@observe if(result_episodes == null || result_episodes.adapter == null) return@observe
(result_episodes.adapter as EpisodeAdapter).cardList = episodes (result_episodes.adapter as EpisodeAdapter).cardList = episodes
(result_episodes.adapter as EpisodeAdapter).notifyDataSetChanged() (result_episodes.adapter as EpisodeAdapter).notifyDataSetChanged()
} }
@ -203,51 +249,6 @@ activity?.startActivityForResult(vlcIntent, REQUEST_CODE)
} }
} }
val adapter: RecyclerView.Adapter<RecyclerView.ViewHolder>? = activity?.let { it ->
EpisodeAdapter(
it,
ArrayList(),
result_episodes,
) { episodeClick ->
val id = episodeClick.data.id
val index = episodeClick.data.index
val buildInPlayer = true
if (buildInPlayer) {
(requireActivity() as AppCompatActivity).supportFragmentManager.beginTransaction()
.setCustomAnimations(R.anim.enter_anim,
R.anim.exit_anim,
R.anim.pop_enter,
R.anim.pop_exit)
.add(R.id.homeRoot, PlayerFragment.newInstance(PlayerData(index)))
.commit()
} else {
when (episodeClick.action) {
/*
ACTION_PLAY_EPISODE -> {
if (allEpisodes.containsKey(id)) {
playEpisode(allEpisodes[id], index)
} else {
viewModel.loadEpisode(episodeClick.data) { res ->
if (res is Resource.Success) {
playEpisode(allEpisodes[id], index)
}
}
}
}
ACTION_RELOAD_EPISODE -> viewModel.loadEpisode(episodeClick.data) { res ->
if (res is Resource.Success) {
playEpisode(allEpisodes[id], index)
}
}*/
}
}
}
}
result_episodes.adapter = adapter
result_episodes.layoutManager = GridLayoutManager(context, 1)
if (d.plot != null) { if (d.plot != null) {
var syno = d.plot!! var syno = d.plot!!
if (syno.length > MAX_SYNO_LENGH) { if (syno.length > MAX_SYNO_LENGH) {

View File

@ -8,6 +8,7 @@ import android.view.ViewGroup
import android.widget.ImageView import android.widget.ImageView
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.SearchView
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
@ -21,6 +22,8 @@ import com.lagradost.cloudstream3.UIHelper.fixPaddingStatusbar
import com.lagradost.cloudstream3.UIHelper.getGridIsCompact import com.lagradost.cloudstream3.UIHelper.getGridIsCompact
import com.lagradost.cloudstream3.mvvm.Resource import com.lagradost.cloudstream3.mvvm.Resource
import com.lagradost.cloudstream3.mvvm.observe import com.lagradost.cloudstream3.mvvm.observe
import com.lagradost.cloudstream3.ui.player.PlayerData
import com.lagradost.cloudstream3.ui.player.PlayerFragment
import kotlinx.android.synthetic.main.fragment_search.* import kotlinx.android.synthetic.main.fragment_search.*
class SearchFragment : Fragment() { class SearchFragment : Fragment() {
@ -126,5 +129,14 @@ class SearchFragment : Fragment() {
search_exit_icon.alpha = 1f search_exit_icon.alpha = 1f
search_loading_bar.alpha = 0f search_loading_bar.alpha = 0f
} }
(requireActivity() as AppCompatActivity).supportFragmentManager.beginTransaction()
.setCustomAnimations(R.anim.enter_anim,
R.anim.exit_anim,
R.anim.pop_enter,
R.anim.pop_exit)
.add(R.id.homeRoot, PlayerFragment.newInstance(PlayerData(0)))
.commit()
} }
} }