Player PiP #24
|
@ -28,15 +28,14 @@
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name=".player.PlayerActivity"
|
android:name=".player.PlayerActivity"
|
||||||
android:parentActivityName=".MainActivity"
|
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|layoutDirection"
|
||||||
android:label="@string/app_name"
|
|
||||||
android:theme="@style/PlayerTheme"
|
|
||||||
android:supportsPictureInPicture="true"
|
|
||||||
android:launchMode="singleTask"
|
|
||||||
android:excludeFromRecents="true"
|
android:excludeFromRecents="true"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:launchMode="singleTask"
|
||||||
|
android:parentActivityName=".MainActivity"
|
||||||
|
android:supportsPictureInPicture="true"
|
||||||
android:taskAffinity=".player.PlayerActivity"
|
android:taskAffinity=".player.PlayerActivity"
|
||||||
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|layoutDirection">
|
android:theme="@style/PlayerTheme" />
|
||||||
</activity>
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
|
@ -165,6 +165,9 @@ class MainActivity : AppCompatActivity(), BottomNavigationView.OnNavigationItemS
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* start the player as new activity
|
||||||
|
*/
|
||||||
fun startPlayer(mediaId: Int, episodeId: Int) {
|
fun startPlayer(mediaId: Int, episodeId: Int) {
|
||||||
val intent = Intent(this, PlayerActivity::class.java).apply {
|
val intent = Intent(this, PlayerActivity::class.java).apply {
|
||||||
putExtra(getString(R.string.intent_media_id), mediaId)
|
putExtra(getString(R.string.intent_media_id), mediaId)
|
||||||
|
|
|
@ -3,9 +3,7 @@ package org.mosad.teapod.player
|
||||||
import android.animation.Animator
|
import android.animation.Animator
|
||||||
import android.animation.AnimatorListenerAdapter
|
import android.animation.AnimatorListenerAdapter
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.ActivityManager
|
|
||||||
import android.app.PictureInPictureParams
|
import android.app.PictureInPictureParams
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
|
@ -13,7 +11,9 @@ import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.util.Rational
|
import android.util.Rational
|
||||||
import android.view.*
|
import android.view.GestureDetector
|
||||||
|
import android.view.MotionEvent
|
||||||
|
import android.view.View
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
@ -34,11 +34,13 @@ import org.mosad.teapod.preferences.Preferences
|
||||||
import org.mosad.teapod.ui.components.EpisodesListPlayer
|
import org.mosad.teapod.ui.components.EpisodesListPlayer
|
||||||
import org.mosad.teapod.ui.components.LanguageSettingsPlayer
|
import org.mosad.teapod.ui.components.LanguageSettingsPlayer
|
||||||
import org.mosad.teapod.util.DataTypes
|
import org.mosad.teapod.util.DataTypes
|
||||||
|
import org.mosad.teapod.util.hideBars
|
||||||
|
import org.mosad.teapod.util.isInPiPMode
|
||||||
|
import org.mosad.teapod.util.navToLauncherTask
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import kotlin.concurrent.scheduleAtFixedRate
|
import kotlin.concurrent.scheduleAtFixedRate
|
||||||
|
|
||||||
|
|
||||||
class PlayerActivity : AppCompatActivity() {
|
class PlayerActivity : AppCompatActivity() {
|
||||||
|
|
||||||
private val model: PlayerViewModel by viewModels()
|
private val model: PlayerViewModel by viewModels()
|
||||||
|
@ -47,7 +49,7 @@ class PlayerActivity : AppCompatActivity() {
|
||||||
private lateinit var gestureDetector: GestureDetectorCompat
|
private lateinit var gestureDetector: GestureDetectorCompat
|
||||||
private lateinit var timerUpdates: TimerTask
|
private lateinit var timerUpdates: TimerTask
|
||||||
|
|
||||||
private var wasInPIP = false
|
private var wasInPiP = false
|
||||||
private var playWhenReady = true
|
private var playWhenReady = true
|
||||||
private var currentWindow = 0
|
private var currentWindow = 0
|
||||||
private var playbackPosition: Long = 0
|
private var playbackPosition: Long = 0
|
||||||
|
@ -123,9 +125,8 @@ class PlayerActivity : AppCompatActivity() {
|
||||||
releasePlayer()
|
releasePlayer()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (wasInPIP) {
|
// if the player was in pip, it's on a different task
|
||||||
navToLauncherTask()
|
if (wasInPiP) { navToLauncherTask() }
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSaveInstanceState(outState: Bundle) {
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
|
@ -135,6 +136,22 @@ class PlayerActivity : AppCompatActivity() {
|
||||||
super.onSaveInstanceState(outState)
|
super.onSaveInstanceState(outState)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* used, when the player is in pip and the user selects a new media
|
||||||
|
*/
|
||||||
|
override fun onNewIntent(intent: Intent?) {
|
||||||
|
super.onNewIntent(intent)
|
||||||
|
|
||||||
|
// when the intent changed, lead the new media and play it
|
||||||
|
intent?.let {
|
||||||
|
model.loadMedia(
|
||||||
|
it.getIntExtra(getString(R.string.intent_media_id), 0),
|
||||||
|
it.getIntExtra(getString(R.string.intent_episode_id), 0)
|
||||||
|
)
|
||||||
|
model.playEpisode(model.currentEpisode, replace = true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* previous to android n, don't override
|
* previous to android n, don't override
|
||||||
*/
|
*/
|
||||||
|
@ -156,7 +173,7 @@ class PlayerActivity : AppCompatActivity() {
|
||||||
enterPictureInPictureMode(params)
|
enterPictureInPictureMode(params)
|
||||||
}
|
}
|
||||||
|
|
||||||
wasInPIP = isInPiPMode()
|
wasInPiP = isInPiPMode()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -322,11 +339,19 @@ class PlayerActivity : AppCompatActivity() {
|
||||||
private fun onMediaChanged() {
|
private fun onMediaChanged() {
|
||||||
exo_text_title.text = model.getMediaTitle()
|
exo_text_title.text = model.getMediaTitle()
|
||||||
|
|
||||||
|
// hide the next ep button, if there is none
|
||||||
button_next_ep_c.visibility = if (model.nextEpisode == null) {
|
button_next_ep_c.visibility = if (model.nextEpisode == null) {
|
||||||
View.GONE
|
View.GONE
|
||||||
} else {
|
} else {
|
||||||
View.VISIBLE
|
View.VISIBLE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// hide the episodes button, if the media type changed
|
||||||
|
button_episodes.visibility = if (model.media.type == DataTypes.MediaType.MOVIE) {
|
||||||
|
View.GONE
|
||||||
|
} else {
|
||||||
|
View.VISIBLE
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -374,27 +399,6 @@ class PlayerActivity : AppCompatActivity() {
|
||||||
hideButtonNextEp()
|
hideButtonNextEp()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* hide the status and navigation bar
|
|
||||||
*/
|
|
||||||
private fun hideBars() {
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
|
||||||
window.setDecorFitsSystemWindows(false)
|
|
||||||
window.insetsController?.apply {
|
|
||||||
hide(WindowInsets.Type.statusBars() or WindowInsets.Type.navigationBars())
|
|
||||||
systemBarsBehavior = WindowInsetsController.BEHAVIOR_SHOW_BARS_BY_SWIPE
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
@Suppress("deprecation")
|
|
||||||
window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_IMMERSIVE
|
|
||||||
or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
|
||||||
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
|
||||||
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
|
||||||
or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
|
||||||
or View.SYSTEM_UI_FLAG_FULLSCREEN)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* show the next episode button
|
* show the next episode button
|
||||||
* TODO improve the show animation
|
* TODO improve the show animation
|
||||||
|
@ -440,29 +444,6 @@ class PlayerActivity : AppCompatActivity() {
|
||||||
pauseAndHideControls()
|
pauseAndHideControls()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isInPiPMode(): Boolean {
|
|
||||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
|
||||||
isInPictureInPictureMode
|
|
||||||
} else {
|
|
||||||
false // pip mode not supported
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bring up launcher task to front
|
|
||||||
*/
|
|
||||||
private fun navToLauncherTask() {
|
|
||||||
val activityManager = (this.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager)
|
|
||||||
activityManager.appTasks.forEach { task ->
|
|
||||||
val baseIntent = task.taskInfo.baseIntent
|
|
||||||
val categories = baseIntent.categories
|
|
||||||
if (categories != null && categories.contains(Intent.CATEGORY_LAUNCHER)) {
|
|
||||||
task.moveToFront()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* pause playback and hide controls
|
* pause playback and hide controls
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
package org.mosad.teapod.util
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.app.ActivityManager
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Build
|
||||||
|
import android.view.View
|
||||||
|
import android.view.WindowInsets
|
||||||
|
import android.view.WindowInsetsController
|
||||||
|
|
||||||
|
/**
|
||||||
|
* hide the status and navigation bar
|
||||||
|
*/
|
||||||
|
fun Activity.hideBars() {
|
||||||
|
window.apply {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
|
setDecorFitsSystemWindows(false)
|
||||||
|
insetsController?.apply {
|
||||||
|
hide(WindowInsets.Type.statusBars() or WindowInsets.Type.navigationBars())
|
||||||
|
systemBarsBehavior = WindowInsetsController.BEHAVIOR_SHOW_BARS_BY_SWIPE
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
@Suppress("deprecation")
|
||||||
|
decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_IMMERSIVE
|
||||||
|
or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
||||||
|
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
||||||
|
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||||
|
or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
||||||
|
or View.SYSTEM_UI_FLAG_FULLSCREEN)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Activity.isInPiPMode(): Boolean {
|
||||||
|
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
|
isInPictureInPictureMode
|
||||||
|
} else {
|
||||||
|
false // pip mode not supported
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bring up launcher task to front
|
||||||
|
*/
|
||||||
|
fun Activity.navToLauncherTask() {
|
||||||
|
val activityManager = (getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager)
|
||||||
|
activityManager.appTasks.forEach { task ->
|
||||||
|
val baseIntent = task.taskInfo.baseIntent
|
||||||
|
val categories = baseIntent.categories
|
||||||
|
if (categories != null && categories.contains(Intent.CATEGORY_LAUNCHER)) {
|
||||||
|
task.moveToFront()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue