diff --git a/app/build.gradle b/app/build.gradle
index a871388b..9ffbc147 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -21,6 +21,7 @@ android {
buildTypes {
release {
minifyEnabled false
+ debuggable true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 311e64d1..0318a76c 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -4,16 +4,19 @@
+
diff --git a/app/src/main/java/com/lagradost/cloudstream3/mvvm/ArchComponentExt.kt b/app/src/main/java/com/lagradost/cloudstream3/mvvm/ArchComponentExt.kt
index 9d884110..453ea8ea 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/mvvm/ArchComponentExt.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/mvvm/ArchComponentExt.kt
@@ -11,6 +11,13 @@ fun LifecycleOwner.observe(liveData: LiveData, action: (t: T) -> Unit) {
liveData.observe(this, Observer { it?.let { t -> action(t) } })
}
+fun LifecycleOwner.observeDirectly(liveData: LiveData, 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 {
data class Success(val value: T) : Resource()
data class Failure(
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerFragment.kt
index 4b60db60..655c0947 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerFragment.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerFragment.kt
@@ -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(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)
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/SSLTrustManager.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/SSLTrustManager.kt
new file mode 100644
index 00000000..0c7226d2
--- /dev/null
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/SSLTrustManager.kt
@@ -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?, p1: String?) {
+ }
+
+ override fun checkServerTrusted(p0: Array?, p1: String?) {
+ }
+
+ override fun getAcceptedIssuers(): Array {
+ return arrayOf()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt
index 26528883..4ec71a82 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt
@@ -106,12 +106,58 @@ class ResultFragment : Fragment() {
activity?.onBackPressed()
}
+
+ val adapter: RecyclerView.Adapter? = 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? = 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) {
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt
index 9753bc6d..62546bf9 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt
@@ -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()
}
}
\ No newline at end of file