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 {
release {
minifyEnabled false
debuggable true
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.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:usesCleartextTraffic="true"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:name=".MainActivity"
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|keyboard|keyboardHidden|navigation"
android:label="@string/app_name">
<intent-filter>
<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) } })
}
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> {
data class Success<out T>(val value: T) : Resource<T>()
data class Failure(

View file

@ -1,29 +1,55 @@
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.Handler
import android.os.Looper
import android.view.LayoutInflater
import android.view.View
import android.view.View.GONE
import android.view.View.VISIBLE
import android.view.ViewGroup
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.lifecycle.ViewModelProvider
import androidx.preference.PreferenceManager
import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.json.JsonMapper
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.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.R
import com.lagradost.cloudstream3.USER_AGENT
import com.lagradost.cloudstream3.mvvm.Resource
import com.lagradost.cloudstream3.mvvm.observe
import com.lagradost.cloudstream3.ui.result.ResultEpisode
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.ExtractorLink
import java.io.FileDescriptor
import java.io.PrintWriter
import kotlinx.android.synthetic.main.fragment_player.*
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_POSITION = "resumePosition"
const val STATE_PLAYER_FULLSCREEN = "playerFullscreen"
@ -75,24 +101,33 @@ class PlayerFragment : Fragment() {
}
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) {
isPlayerPlaying = exoPlayer.playWhenReady
playbackPosition = exoPlayer.currentPosition
currentWindow = exoPlayer.currentWindowIndex
exoPlayer.release()
}
}
override fun onSaveInstanceState(outState: Bundle) {
if (this::exoPlayer.isInitialized) {
outState.putInt(STATE_RESUME_WINDOW, exoPlayer.currentWindowIndex)
outState.putLong(STATE_RESUME_POSITION, exoPlayer.currentPosition)
private class SettingsContentObserver(handler: Handler?, val activity: Activity) : ContentObserver(handler) {
private val audioManager = activity.getSystemService(AUDIO_SERVICE) as? AudioManager
override fun onChange(selfChange: Boolean) {
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 {
fun newInstance(data: PlayerData) =
PlayerFragment().apply {
@ -126,16 +161,41 @@ class PlayerFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
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)!!
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 ->
episodes = _episodes
if (isLoading) {
if (playerData.episodeIndex > 0 && playerData.episodeIndex < episodes.size) {
}
else {
} else {
// 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? {
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)
}
}

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()
}
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) {
allEpisodes = it
}
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).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) {
var syno = d.plot!!
if (syno.length > MAX_SYNO_LENGH) {

View file

@ -8,6 +8,7 @@ import android.view.ViewGroup
import android.widget.ImageView
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.SearchView
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
@ -21,6 +22,8 @@ import com.lagradost.cloudstream3.UIHelper.fixPaddingStatusbar
import com.lagradost.cloudstream3.UIHelper.getGridIsCompact
import com.lagradost.cloudstream3.mvvm.Resource
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.*
class SearchFragment : Fragment() {
@ -126,5 +129,14 @@ class SearchFragment : Fragment() {
search_exit_icon.alpha = 1f
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()
}
}