diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index e267b893..e98441e9 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,26 +1,29 @@
+ xmlns:tools="http://schemas.android.com/tools" package="com.lagradost.cloudstream3">
+
+
+ android:theme="@style/AppTheme" android:fullBackupContent="@xml/backup_descriptor">
+ android:label="@string/app_name"
+ android:resizeableActivity="true"
+ android:supportsPictureInPicture="true">
@@ -41,5 +44,4 @@
android:resource="@xml/provider_paths"/>
-
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt
index 7dd77387..c1d282e4 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt
@@ -1,24 +1,58 @@
package com.lagradost.cloudstream3
+import android.app.PictureInPictureParams
+import android.content.pm.PackageManager
+import android.os.Build
import android.os.Bundle
import com.google.android.material.bottomnavigation.BottomNavigationView
import androidx.appcompat.app.AppCompatActivity
+import androidx.core.content.ContentProviderCompat.requireContext
import androidx.lifecycle.ViewModelStore
import androidx.lifecycle.ViewModelStoreOwner
import androidx.navigation.findNavController
import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.setupWithNavController
import com.lagradost.cloudstream3.UIHelper.checkWrite
+import com.lagradost.cloudstream3.UIHelper.hasPIPPermission
import com.lagradost.cloudstream3.UIHelper.requestRW
+import com.lagradost.cloudstream3.UIHelper.shouldShowPIPMode
-class MainActivity : AppCompatActivity() {/*, ViewModelStoreOwner {
- private val appViewModelStore: ViewModelStore by lazy {
- ViewModelStore()
+class MainActivity : AppCompatActivity() {
+ /*, ViewModelStoreOwner {
+ private val appViewModelStore: ViewModelStore by lazy {
+ ViewModelStore()
+ }
+
+ override fun getViewModelStore(): ViewModelStore {
+ return appViewModelStore
+ }*/
+ companion object {
+ var isInPlayer: Boolean = false
+ var canShowPipMode: Boolean = false
+ var isInPIPMode: Boolean = false
}
- override fun getViewModelStore(): ViewModelStore {
- return appViewModelStore
- }*/
+ private fun enterPIPMode() {
+ if (!shouldShowPIPMode(isInPlayer) || !canShowPipMode) return
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ try {
+ enterPictureInPictureMode(PictureInPictureParams.Builder().build())
+ } catch (e: Exception) {
+ enterPictureInPictureMode()
+ }
+ } else {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ enterPictureInPictureMode()
+ }
+ }
+ }
+
+ override fun onUserLeaveHint() {
+ super.onUserLeaveHint()
+ if (isInPlayer && canShowPipMode) {
+ enterPIPMode()
+ }
+ }
private fun AppCompatActivity.backPressed(): Boolean {
val currentFragment = supportFragmentManager.fragments.last {
@@ -46,6 +80,13 @@ class MainActivity : AppCompatActivity() {/*, ViewModelStoreOwner {
setContentView(R.layout.activity_main)
val navView: BottomNavigationView = findViewById(R.id.nav_view)
+ //https://stackoverflow.com/questions/52594181/how-to-know-if-user-has-disabled-picture-in-picture-feature-permission
+ //https://developer.android.com/guide/topics/ui/picture-in-picture
+ canShowPipMode =
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && // OS SUPPORT
+ packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE) && // HAS FEATURE, MIGHT BE BLOCKED DUE TO POWER DRAIN
+ hasPIPPermission() // CHECK IF FEATURE IS ENABLED IN SETTINGS
+
val navController = findNavController(R.id.nav_host_fragment)
// Passing each menu ID as a set of Ids because each
// menu should be considered as top level destinations.
diff --git a/app/src/main/java/com/lagradost/cloudstream3/UIHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/UIHelper.kt
index 79e2ff06..96520582 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/UIHelper.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/UIHelper.kt
@@ -2,6 +2,7 @@ package com.lagradost.cloudstream3
import android.Manifest
import android.app.Activity
+import android.app.AppOpsManager
import android.content.Context
import android.content.pm.PackageManager
import android.content.res.Resources
@@ -11,6 +12,7 @@ import android.media.AudioManager
import android.os.Build
import android.view.View
import android.view.WindowManager
+import android.view.inputmethod.InputMethodManager
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
@@ -19,6 +21,7 @@ import androidx.preference.PreferenceManager
import com.google.android.gms.cast.framework.CastContext
import com.google.android.gms.common.ConnectionResult
import com.google.android.gms.common.GoogleApiAvailability
+import com.lagradost.cloudstream3.UIHelper.getGridFormat
import com.lagradost.cloudstream3.ui.result.ResultFragment
import com.lagradost.cloudstream3.utils.Event
@@ -261,4 +264,24 @@ object UIHelper {
) // or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
// window.clearFlags(View.KEEP_SCREEN_ON)
}
+
+ fun Context.shouldShowPIPMode(isInPlayer: Boolean): Boolean {
+ val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
+ return settingsManager?.getBoolean("pip_enabled", true) ?: true && isInPlayer
+ }
+
+ fun Context.hasPIPPermission(): Boolean {
+ val appOps =
+ getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager
+ return appOps.checkOpNoThrow(
+ AppOpsManager.OPSTR_PICTURE_IN_PICTURE,
+ android.os.Process.myUid(),
+ packageName
+ ) == AppOpsManager.MODE_ALLOWED
+ }
+
+ fun Context.hideKeyboard(view: View) {
+ val inputMethodManager = getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager
+ inputMethodManager.hideSoftInputFromWindow(view.windowToken, 0)
+ }
}
\ No newline at end of file
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 2d0f2b47..6b08a1e0 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
@@ -3,22 +3,28 @@ package com.lagradost.cloudstream3.ui.player
import android.animation.ObjectAnimator
import android.annotation.SuppressLint
import android.app.Activity
+import android.app.PendingIntent
+import android.app.PictureInPictureParams
+import android.app.RemoteAction
+import android.content.*
import android.content.Context.AUDIO_SERVICE
-import android.content.SharedPreferences
import android.content.pm.ActivityInfo
import android.content.res.Resources
import android.database.ContentObserver
import android.graphics.Color
+import android.graphics.drawable.Icon
import android.media.AudioManager
import android.net.Uri
import android.os.*
-import android.util.DisplayMetrics
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
import android.view.View.*
import android.view.ViewGroup
-import android.view.animation.*
+import android.view.animation.AccelerateInterpolator
+import android.view.animation.AlphaAnimation
+import android.view.animation.Animation
+import android.view.animation.AnimationUtils
import android.widget.ProgressBar
import android.widget.Toast
import android.widget.Toast.LENGTH_SHORT
@@ -26,7 +32,6 @@ import androidx.appcompat.app.AlertDialog
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
-import androidx.mediarouter.app.MediaRouteButton
import androidx.preference.PreferenceManager
import androidx.transition.Fade
import androidx.transition.Transition
@@ -44,6 +49,7 @@ 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.google.android.exoplayer2.util.Util
import com.google.android.gms.cast.MediaInfo
import com.google.android.gms.cast.MediaMetadata
import com.google.android.gms.cast.MediaQueueItem
@@ -53,10 +59,13 @@ import com.google.android.gms.cast.framework.CastContext
import com.google.android.gms.cast.framework.CastState
import com.google.android.gms.common.images.WebImage
import com.lagradost.cloudstream3.*
+import com.lagradost.cloudstream3.MainActivity.Companion.isInPIPMode
+import com.lagradost.cloudstream3.MainActivity.Companion.isInPlayer
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.UIHelper.getFocusRequest
import com.lagradost.cloudstream3.UIHelper.getNavigationBarHeight
import com.lagradost.cloudstream3.UIHelper.getStatusBarHeight
+import com.lagradost.cloudstream3.UIHelper.hideKeyboard
import com.lagradost.cloudstream3.UIHelper.hideSystemUI
import com.lagradost.cloudstream3.UIHelper.isCastApiAvailable
import com.lagradost.cloudstream3.UIHelper.popCurrentPage
@@ -72,12 +81,10 @@ import com.lagradost.cloudstream3.utils.DataStore.setKey
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_result.*
import kotlinx.android.synthetic.main.player_custom_layout.*
import kotlinx.coroutines.*
import org.json.JSONObject
import java.io.File
-import java.util.*
import javax.net.ssl.HttpsURLConnection
import javax.net.ssl.SSLContext
import javax.net.ssl.SSLSession
@@ -129,12 +136,12 @@ data class PlayerData(
)
class PlayerFragment : Fragment() {
+ private var isCurrentlyPlaying: Boolean = false
private val mapper = JsonMapper.builder().addModule(KotlinModule())
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false).build()
private var isFullscreen = false
private var isPlayerPlaying = true
- private var doubleTapEnabled = false
private lateinit var viewModel: ResultViewModel
private lateinit var playerData: PlayerData
private var isLoading = true
@@ -281,20 +288,21 @@ class PlayerFragment : Fragment() {
)
}
- fun skipOP() {
+ private fun skipOP() {
seekTime(85000L)
}
+ private var swipeEnabled = true // = ArrayList()
var currentPoster: String? = null
+ //region PIP MODE
+ private fun getPen(code: PlayerEventType): PendingIntent {
+ return getPen(code.value)
+ }
+
+ private fun getPen(code: Int): PendingIntent {
+ return PendingIntent.getBroadcast(
+ activity,
+ code,
+ Intent("media_control").putExtra("control_type", code),
+ 0
+ )
+ }
+
+ @SuppressLint("NewApi")
+ private fun getRemoteAction(id: Int, title: String, event: PlayerEventType): RemoteAction {
+ return RemoteAction(
+ Icon.createWithResource(activity, id),
+ title,
+ title,
+ getPen(event)
+ )
+ }
+
+ @SuppressLint("NewApi")
+ private fun updatePIPModeActions() {
+ if (!isInPIPMode || !this::exoPlayer.isInitialized) return
+
+ val actions: ArrayList = ArrayList()
+
+ actions.add(getRemoteAction(R.drawable.go_back_30, "Go Back", PlayerEventType.SeekBack))
+
+ if (exoPlayer.isPlaying) {
+ actions.add(getRemoteAction(R.drawable.netflix_pause, "Pause", PlayerEventType.Pause))
+ } else {
+ actions.add(getRemoteAction(R.drawable.ic_baseline_play_arrow_24, "Play", PlayerEventType.Play))
+ }
+
+ actions.add(getRemoteAction(R.drawable.go_forward_30, "Go Forward", PlayerEventType.SeekForward))
+ activity?.setPictureInPictureParams(PictureInPictureParams.Builder().setActions(actions).build())
+ }
+
+ private var receiver: BroadcastReceiver? = null
+ override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean) {
+ isInPIPMode = isInPictureInPictureMode
+ if (isInPictureInPictureMode) {
+ // Hide the full-screen UI (controls, etc.) while in picture-in-picture mode.
+ player_holder.alpha = 0f
+ receiver = object : BroadcastReceiver() {
+ override fun onReceive(
+ context: Context,
+ intent: Intent,
+ ) {
+ if (ACTION_MEDIA_CONTROL != intent.action) {
+ return
+ }
+ handlePlayerEvent(intent.getIntExtra(EXTRA_CONTROL_TYPE, 0))
+ }
+ }
+ val filter = IntentFilter()
+ filter.addAction(
+ ACTION_MEDIA_CONTROL
+ )
+ activity?.registerReceiver(receiver, filter)
+ updatePIPModeActions()
+ } else {
+ // Restore the full-screen UI.
+ player_holder.alpha = 1f
+ receiver?.let {
+ activity?.unregisterReceiver(it)
+ }
+ activity?.hideSystemUI()
+ this.view?.let { activity?.hideKeyboard(it) }
+ }
+ }
+
+ private fun handlePlayerEvent(event: PlayerEventType) {
+ handlePlayerEvent(event.value)
+ }
+
+ private fun handlePlayerEvent(event: Int) {
+ when (event) {
+ PlayerEventType.Play.value -> exoPlayer.play()
+ PlayerEventType.Pause.value -> exoPlayer.pause()
+ PlayerEventType.SeekBack.value -> seekTime(-30000L)
+ PlayerEventType.SeekForward.value -> seekTime(30000L)
+ }
+ }
+//endregion
+
@SuppressLint("SetTextI18n")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
+ settingsManager = PreferenceManager.getDefaultSharedPreferences(activity)
+ swipeEnabled = settingsManager.getBoolean("swipe_enabled", true)
+ swipeVerticalEnabled = settingsManager.getBoolean("swipe_vertical_enabled", true)
+ playBackSpeedEnabled = settingsManager.getBoolean("playback_speed_enabled", false)
+ playerResizeEnabled = settingsManager.getBoolean("player_resize_enabled", true)
+ doubleTapEnabled = settingsManager.getBoolean("double_tap_enabled", false)
+
+ isInPlayer = true // NEED REFERENCE TO MAIN ACTIVITY FOR PIP
+
navigationBarHeight = requireContext().getNavigationBarHeight()
statusBarHeight = requireContext().getStatusBarHeight()
@@ -723,9 +831,6 @@ class PlayerFragment : Fragment() {
}
}
- println(episodes)
- settingsManager = PreferenceManager.getDefaultSharedPreferences(activity)
-
val fastForwardTime = settingsManager.getInt("fast_forward_button_time", 10)
exo_rew_text.text = fastForwardTime.toString()
exo_ffwd_text.text = fastForwardTime.toString()
@@ -928,6 +1033,8 @@ class PlayerFragment : Fragment() {
}
changeSkip()
+
+ initPlayer()
}
private fun getCurrentUrl(): ExtractorLink? {
@@ -1003,24 +1110,28 @@ class PlayerFragment : Fragment() {
override fun onStart() {
super.onStart()
- thread {
- // initPlayer()
- if (player_view != null) player_view.onResume()
+ if (!isCurrentlyPlaying) {
+ initPlayer()
}
+ if (player_view != null) player_view.onResume()
}
override fun onResume() {
super.onResume()
activity?.hideSystemUI()
activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE
- thread {
- initPlayer()
+ if (Util.SDK_INT <= 23) {
+ if (!isCurrentlyPlaying) {
+ initPlayer()
+ }
if (player_view != null) player_view.onResume()
}
}
+ //TODO FIX NON PIP MODE BUG
override fun onDestroy() {
super.onDestroy()
+ isInPlayer = false
// releasePlayer()
activity?.showSystemUI()
@@ -1029,14 +1140,18 @@ class PlayerFragment : Fragment() {
override fun onPause() {
super.onPause()
- if (player_view != null) player_view.onPause()
- releasePlayer()
+ if (Util.SDK_INT <= 23) {
+ if (player_view != null) player_view.onPause()
+ releasePlayer()
+ }
}
override fun onStop() {
super.onStop()
- if (player_view != null) player_view.onPause()
- releasePlayer()
+ if (Util.SDK_INT > 23) {
+ if (player_view != null) player_view.onPause()
+ releasePlayer()
+ }
}
override fun onSaveInstanceState(outState: Bundle) {
@@ -1229,7 +1344,7 @@ class PlayerFragment : Fragment() {
}
override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) {
- // updatePIPModeActions()
+ updatePIPModeActions()
if (activity == null) return
if (playWhenReady) {
when (playbackState) {
@@ -1296,6 +1411,7 @@ class PlayerFragment : Fragment() {
//http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4
@SuppressLint("SetTextI18n")
private fun initPlayer() {
+ isCurrentlyPlaying = true
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()
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt
new file mode 100644
index 00000000..7024fe34
--- /dev/null
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt
@@ -0,0 +1,11 @@
+package com.lagradost.cloudstream3.ui.settings
+
+import android.os.Bundle
+import androidx.preference.PreferenceFragmentCompat
+import com.lagradost.cloudstream3.R
+
+class SettingsFragment : PreferenceFragmentCompat() {
+ override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
+ setPreferencesFromResource(R.xml.settings, rootKey)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_baseline_discord_24.xml b/app/src/main/res/drawable/ic_baseline_discord_24.xml
new file mode 100644
index 00000000..de884ab6
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_discord_24.xml
@@ -0,0 +1,6 @@
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_baseline_ondemand_video_24.xml b/app/src/main/res/drawable/ic_baseline_ondemand_video_24.xml
new file mode 100644
index 00000000..bbd6d57b
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_ondemand_video_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_baseline_picture_in_picture_alt_24.xml b/app/src/main/res/drawable/ic_baseline_picture_in_picture_alt_24.xml
new file mode 100644
index 00000000..941e803b
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_picture_in_picture_alt_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_baseline_touch_app_24.xml b/app/src/main/res/drawable/ic_baseline_touch_app_24.xml
new file mode 100644
index 00000000..3a060f63
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_touch_app_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_github_logo.xml b/app/src/main/res/drawable/ic_github_logo.xml
new file mode 100644
index 00000000..f7be1e9e
--- /dev/null
+++ b/app/src/main/res/drawable/ic_github_logo.xml
@@ -0,0 +1,5 @@
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/bottom_nav_menu.xml b/app/src/main/res/menu/bottom_nav_menu.xml
index a3aa6fcc..acd53108 100644
--- a/app/src/main/res/menu/bottom_nav_menu.xml
+++ b/app/src/main/res/menu/bottom_nav_menu.xml
@@ -15,5 +15,9 @@
android:id="@+id/navigation_notifications"
android:icon="@drawable/netflix_download"
android:title="@string/title_downloads"/>
+
\ No newline at end of file
diff --git a/app/src/main/res/navigation/mobile_navigation.xml b/app/src/main/res/navigation/mobile_navigation.xml
index c1a96728..343a8392 100644
--- a/app/src/main/res/navigation/mobile_navigation.xml
+++ b/app/src/main/res/navigation/mobile_navigation.xml
@@ -22,4 +22,10 @@
android:name="com.lagradost.cloudstream3.ui.notifications.NotificationsFragment"
android:label="@string/title_downloads"
tools:layout="@layout/fragment_notifications"/>
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 1956de1e..9e1bcf7d 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -3,6 +3,7 @@
Home
Search
Downloads
+ Settings
Search...
Change Providers
search_providers_list
diff --git a/app/src/main/res/xml/backup_descriptor.xml b/app/src/main/res/xml/backup_descriptor.xml
new file mode 100644
index 00000000..761b8012
--- /dev/null
+++ b/app/src/main/res/xml/backup_descriptor.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/xml/settings.xml b/app/src/main/res/xml/settings.xml
new file mode 100644
index 00000000..8456618a
--- /dev/null
+++ b/app/src/main/res/xml/settings.xml
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file