Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix: Minor pip and background play issues #1168

Merged
merged 7 commits into from
Dec 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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>
Loading