Skip to content

Commit

Permalink
Fix: Minor pip and background play issues (#1168)
Browse files Browse the repository at this point in the history
  • Loading branch information
anilbeesetti authored Dec 16, 2024
1 parent 0f6aa63 commit 78a67a4
Show file tree
Hide file tree
Showing 6 changed files with 110 additions and 49 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,21 @@ package dev.anilbeesetti.nextplayer.feature.player
import android.annotation.SuppressLint
import android.app.Activity
import android.app.AppOpsManager
import android.app.PendingIntent
import android.app.PictureInPictureParams
import android.app.RemoteAction
import android.content.BroadcastReceiver
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.ActivityInfo
import android.content.pm.PackageManager
import android.content.res.Configuration
import android.graphics.Color
import android.graphics.Rect
import android.graphics.Typeface
import android.graphics.drawable.Icon
import android.media.AudioManager
import android.media.audiofx.LoudnessEnhancer
import android.net.Uri
Expand All @@ -35,6 +40,7 @@ import android.widget.Toast
import androidx.activity.addCallback
import androidx.activity.result.contract.ActivityResultContracts.OpenDocument
import androidx.activity.viewModels
import androidx.annotation.DrawableRes
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
Expand Down Expand Up @@ -73,7 +79,6 @@ import dev.anilbeesetti.nextplayer.feature.player.dialogs.nameRes
import dev.anilbeesetti.nextplayer.feature.player.extensions.audioSessionId
import dev.anilbeesetti.nextplayer.feature.player.extensions.isPortrait
import dev.anilbeesetti.nextplayer.feature.player.extensions.next
import dev.anilbeesetti.nextplayer.feature.player.extensions.prettyPrintIntent
import dev.anilbeesetti.nextplayer.feature.player.extensions.seekBack
import dev.anilbeesetti.nextplayer.feature.player.extensions.seekForward
import dev.anilbeesetti.nextplayer.feature.player.extensions.setImageDrawable
Expand Down Expand Up @@ -139,6 +144,7 @@ class PlayerActivity : AppCompatActivity() {
private lateinit var volumeManager: VolumeManager
private lateinit var brightnessManager: BrightnessManager
var loudnessEnhancer: LoudnessEnhancer? = null
private var pipBroadcastReceiver: BroadcastReceiver? = null

/**
* Listeners
Expand Down Expand Up @@ -198,7 +204,6 @@ class PlayerActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
prettyPrintIntent()

AppCompatDelegate.setDefaultNightMode(
when (applicationPreferences.themeConfig) {
Expand Down Expand Up @@ -339,7 +344,7 @@ class PlayerActivity : AppCompatActivity() {
volumeManager.loudnessEnhancer = loudnessEnhancer

if (intent.data != null && intent.data.toString() != currentMediaItem?.mediaId) {
playVideo(uri = viewModel.currentMediaItem?.localConfiguration?.uri ?: intent.data!!)
playVideo(uri = viewModel.currentMediaItemUri ?: intent.data!!)
}
}
subtitleFileLauncherLaunchedForMediaItem = null
Expand Down Expand Up @@ -381,6 +386,7 @@ class PlayerActivity : AppCompatActivity() {
}
}

@SuppressLint("NewApi")
override fun onUserLeaveHint() {
super.onUserLeaveHint()
if (Build.VERSION.SDK_INT in Build.VERSION_CODES.O..<Build.VERSION_CODES.S &&
Expand All @@ -402,11 +408,32 @@ class PlayerActivity : AppCompatActivity() {
if (isInPictureInPictureMode) {
binding.playerView.subtitleView?.setFractionalTextSize(SubtitleView.DEFAULT_TEXT_SIZE_FRACTION)
playerUnlockControls.visibility = View.INVISIBLE
pipBroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
if (intent == null || intent.action != PIP_INTENT_ACTION) return
when (intent.getIntExtra(PIP_INTENT_ACTION_CODE, 0)) {
PIP_ACTION_PLAY -> mediaController?.play()
PIP_ACTION_PAUSE -> mediaController?.pause()
PIP_ACTION_NEXT -> mediaController?.seekToNext()
PIP_ACTION_PREVIOUS -> mediaController?.seekToPrevious()
}
updatePictureInPictureParams()
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
registerReceiver(pipBroadcastReceiver, IntentFilter(PIP_INTENT_ACTION), RECEIVER_NOT_EXPORTED)
} else {
registerReceiver(pipBroadcastReceiver, IntentFilter(PIP_INTENT_ACTION))
}
} else {
binding.playerView.subtitleView?.setFixedTextSize(TypedValue.COMPLEX_UNIT_SP, playerPreferences.subtitleTextSize.toFloat())
if (!isControlsLocked) {
playerUnlockControls.visibility = View.VISIBLE
}
pipBroadcastReceiver?.let {
unregisterReceiver(it)
pipBroadcastReceiver = null
}
}
super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig)
}
Expand All @@ -423,21 +450,52 @@ class PlayerActivity : AppCompatActivity() {
setSourceRectHint(sourceRectHint)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
setSeamlessResizeEnabled(playerPreferences.autoPip && enableAutoEnter)
setAutoEnterEnabled(playerPreferences.autoPip && enableAutoEnter)
}

setActions(
listOf(
createPipAction(
context = this@PlayerActivity,
"skip to previous",
coreUiR.drawable.ic_skip_prev,
PIP_ACTION_PREVIOUS,
),
if (mediaController?.isPlaying == true) {
createPipAction(
context = this@PlayerActivity,
"pause",
coreUiR.drawable.ic_pause,
PIP_ACTION_PAUSE,
)
} else {
createPipAction(
context = this@PlayerActivity,
"play",
coreUiR.drawable.ic_play,
PIP_ACTION_PLAY,
)
},
createPipAction(
context = this@PlayerActivity,
"skip to next",
coreUiR.drawable.ic_skip_next,
PIP_ACTION_NEXT,
),
),
)
}.build().also { setPictureInPictureParams(it) }
}

private fun calculateVideoAspectRatio(): Rational? {
return binding.playerView.player?.videoSize?.let { videoSize ->
if (videoSize.width == 0 || videoSize.height == 0) return@let null

val minAspectRatio = 0.5f // 1:2 aspect ratio
val maxAspectRatio = 2.39f // 21:9 aspect ratio
Rational(
videoSize.width.coerceIn((videoSize.height * minAspectRatio).toInt(), (videoSize.height * maxAspectRatio).toInt()),
videoSize.height.coerceIn((videoSize.width * minAspectRatio).toInt(), (videoSize.width * maxAspectRatio).toInt()),
)
videoSize.width,
videoSize.height,
).takeIf { it.toFloat() in 0.5f..2.39f }
}
}

Expand Down Expand Up @@ -626,7 +684,7 @@ class PlayerActivity : AppCompatActivity() {
private fun playbackStateListener() = object : Player.Listener {
override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
super.onMediaItemTransition(mediaItem, reason)
viewModel.currentMediaItem = mediaItem
viewModel.currentMediaItemUri = mediaItem?.localConfiguration?.uri
isMediaItemReady = false
}

Expand Down Expand Up @@ -661,11 +719,11 @@ class PlayerActivity : AppCompatActivity() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && isPipSupported) {
updatePictureInPictureParams()
}
setOrientation()
}
lifecycleScope.launch {
val videoScale = mediaController?.currentMediaItem?.mediaId?.let { viewModel.getVideoState(it)?.videoScale } ?: 1f
applyVideoScale(videoScale = videoScale)
setOrientation()
}
}

Expand Down Expand Up @@ -723,10 +781,11 @@ class PlayerActivity : AppCompatActivity() {
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
if (intent.data != null) {
mediaController?.clearMediaItems()
setIntent(intent)
prettyPrintIntent()
playVideo(intent.data!!)
currentOrientation = null
viewModel.currentMediaItemUri = intent.data
if (mediaController != null) {
playVideo(intent.data!!)
}
}
}

Expand Down Expand Up @@ -997,5 +1056,34 @@ class PlayerActivity : AppCompatActivity() {

companion object {
const val HIDE_DELAY_MILLIS = 1000L
const val PIP_INTENT_ACTION = "pip_action"
const val PIP_INTENT_ACTION_CODE = "pip_action_code"
const val PIP_ACTION_PLAY = 1
const val PIP_ACTION_PAUSE = 2
const val PIP_ACTION_NEXT = 3
const val PIP_ACTION_PREVIOUS = 4
}
}

@RequiresApi(Build.VERSION_CODES.O)
fun createPipAction(
context: Context,
title: String,
@DrawableRes icon: Int,
actionCode: Int,
): RemoteAction {
return RemoteAction(
Icon.createWithResource(context, icon),
title,
title,
PendingIntent.getBroadcast(
context,
actionCode,
Intent(PlayerActivity.PIP_INTENT_ACTION).apply {
putExtra(PlayerActivity.PIP_INTENT_ACTION_CODE, actionCode)
setPackage(context.packageName)
},
PendingIntent.FLAG_IMMUTABLE,
),
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package dev.anilbeesetti.nextplayer.feature.player
import android.net.Uri
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.media3.common.MediaItem
import dagger.hilt.android.lifecycle.HiltViewModel
import dev.anilbeesetti.nextplayer.core.data.models.VideoState
import dev.anilbeesetti.nextplayer.core.data.repository.MediaRepository
Expand All @@ -25,7 +24,7 @@ class PlayerViewModel @Inject constructor(
private val getSortedPlaylistUseCase: GetSortedPlaylistUseCase,
) : ViewModel() {

var currentMediaItem: MediaItem? = null
var currentMediaItemUri: Uri? = null
var playWhenReady: Boolean = true
var skipSilenceEnabled: Boolean = false

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ import android.view.WindowManager
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat.Type
import androidx.core.view.WindowInsetsControllerCompat
import java.util.Arrays
import timber.log.Timber

/**
* Must call this function after any configuration done to activity to keep system bars behaviour
Expand All @@ -34,29 +32,3 @@ val Activity.currentBrightness: Float
in WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_OFF..WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_FULL -> brightness
else -> Settings.System.getFloat(contentResolver, Settings.System.SCREEN_BRIGHTNESS) / 255
}

@Suppress("DEPRECATION")
fun Activity.prettyPrintIntent() {
try {
Timber.apply {
d("* action: ${intent.action}")
d("* data: ${intent.data}")
d("* type: ${intent.type}")
d("* package: ${intent.`package`}")
d("* component: ${intent.component}")
d("* flags: ${intent.flags}")
intent.extras?.let { bundle ->
d("=== Extras ===")
bundle.keySet().forEachIndexed { i, key ->
buildString {
append("${i + 1}) $key: ")
bundle.get(key).let { append(if (it is Array<*>) Arrays.toString(it) else it) }
}.also { d(it) }
}
}
}
} catch (e: Exception) {
Timber.e(e)
e.printStackTrace()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,4 @@ package dev.anilbeesetti.nextplayer.feature.player.extensions
import androidx.media3.common.VideoSize

val VideoSize.isPortrait: Boolean
get() {
val isRotated = this.unappliedRotationDegrees == 90 || this.unappliedRotationDegrees == 270
return if (isRotated) this.width > this.height else this.height > this.width
}
get() = this.height > this.width
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,10 @@ class PlayerService : MediaSessionService() {
}

CustomCommands.STOP_PLAYER_SESSION -> {
stopSelf()
mediaSession?.run {
player.clearMediaItems()
player.stop()
} ?: stopSelf()
return@future SessionResult(SessionResult.RESULT_SUCCESS)
}
}
Expand Down
2 changes: 2 additions & 0 deletions feature/player/src/main/res/values/drawables.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<drawable name="exo_styled_controls_play" tools:ignore="PrivateResource">@drawable/ic_play</drawable>
<drawable name="exo_styled_controls_pause" tools:ignore="PrivateResource">@drawable/ic_pause</drawable>
<drawable name="exo_styled_controls_next" tools:ignore="PrivateResource">@drawable/ic_skip_next</drawable>
<drawable name="exo_styled_controls_previous" tools:ignore="PrivateResource">@drawable/ic_skip_prev</drawable>
</resources>

0 comments on commit 78a67a4

Please sign in to comment.