Skip to content

Commit

Permalink
Merge pull request #424 from bitmovin/PRN-103/PiP-inconsistencies
Browse files Browse the repository at this point in the history
Fix PiP inconsistencies on Android
  • Loading branch information
zigavehovec authored Apr 5, 2024
2 parents b728f41 + d6e1247 commit 6e7b42f
Show file tree
Hide file tree
Showing 6 changed files with 95 additions and 250 deletions.
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# Changelog

## [Unreleased]

### Fixed

- Android: Entering Picture-in-Picture automatically when navigating the app to the background after activating Picture-in-Picture mode once
- Android: Example app Toolbar not visible after going into PiP mode -> Dismissing PiP window (stopping the app) -> Opening the app again

### Changed

- Android: Default Picture-in-Picture implementation doesn't automatically hide/show the Toolbar anymore. This should be handled by the app itself, check out the sample app for an example implementation

## [0.20.0] (2024-03-29)

### Added
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ package com.bitmovin.player.reactnative

import android.annotation.SuppressLint
import android.content.res.Configuration
import android.graphics.Rect
import android.view.View
import android.os.Build
import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.lifecycle.DefaultLifecycleObserver
Expand All @@ -17,8 +16,6 @@ import com.bitmovin.player.api.event.SourceEvent
import com.bitmovin.player.api.ui.PlayerViewConfig
import com.bitmovin.player.api.ui.StyleConfig
import com.bitmovin.player.reactnative.converter.toJson
import com.bitmovin.player.reactnative.ui.RNPictureInPictureDelegate
import com.bitmovin.player.reactnative.ui.RNPictureInPictureHandler
import com.facebook.react.ReactActivity
import com.facebook.react.bridge.*
import com.facebook.react.uimanager.events.RCTEventEmitter
Expand Down Expand Up @@ -101,7 +98,7 @@ private val EVENT_CLASS_TO_REACT_NATIVE_NAME_MAPPING_UI = mapOf<KClass<out Event
@SuppressLint("ViewConstructor")
class RNPlayerView(
private val context: ReactApplicationContext,
) : FrameLayout(context), View.OnLayoutChangeListener, RNPictureInPictureDelegate {
) : FrameLayout(context) {
private val activityLifecycle = (context.currentActivity as? ReactActivity)?.lifecycle
?: error("Trying to create an instance of ${this::class.simpleName} while not attached to a ReactActivity")

Expand Down Expand Up @@ -153,7 +150,6 @@ class RNPlayerView(

private var _playerView: PlayerView? = null
set(value) {
field?.removeOnLayoutChangeListener(this)
field = value
viewEventRelay.eventEmitter = field
playerEventRelay.eventEmitter = field?.player
Expand All @@ -176,11 +172,6 @@ class RNPlayerView(
playerEventRelay.eventEmitter = value
}

/**
* Object that handles PiP mode changes in React Native.
*/
var pictureInPictureHandler: RNPictureInPictureHandler? = null

/**
* Configures the visual presentation and behaviour of the [playerView].
*/
Expand Down Expand Up @@ -214,11 +205,6 @@ class RNPlayerView(
(playerView.parent as ViewGroup?)?.removeView(playerView)
addView(playerView, 0)
}
pictureInPictureHandler?.let {
it.setDelegate(this)
playerView.setPictureInPictureHandler(it)
playerView.addOnLayoutChangeListener(this)
}
}

/**
Expand All @@ -232,61 +218,38 @@ class RNPlayerView(
addView(subtitleView)
}

/**
* Called whenever this view's activity configuration changes.
*/
override fun onConfigurationChanged(newConfig: Configuration?) {
super.onConfigurationChanged(newConfig)
pictureInPictureHandler?.onConfigurationChanged(newConfig)
}

/**
* Called when the player has just entered PiP mode.
*/
override fun onEnterPictureInPicture() {
// Nothing to do
}

/**
* Called when the player has just exited PiP mode.
*/
override fun onExitPictureInPicture() {
// Explicitly call `exitPictureInPicture()` on PlayerView when exiting PiP state, otherwise
// the `PictureInPictureExit` event won't get dispatched.
playerView?.exitPictureInPicture()
private fun isInPictureInPictureMode(): Boolean {
val activity = context.currentActivity ?: return false
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
activity.isInPictureInPictureMode
} else {
false
}
}

/**
* Called when the player's PiP mode changes with a new configuration object.
*/
override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean, newConfig: Configuration?) {
playerView?.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig)
}
private var isCurrentActivityInPictureInPictureMode: Boolean = isInPictureInPictureMode()

/**
* Called whenever the PiP handler needs to compute the PlayerView's global visible rect.
* Called whenever this view's activity configuration changes.
*/
override fun setSourceRectHint(sourceRectHint: Rect) {
playerView?.getGlobalVisibleRect(sourceRectHint)
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
if (isCurrentActivityInPictureInPictureMode != isInPictureInPictureMode()) {
isCurrentActivityInPictureInPictureMode = isInPictureInPictureMode()
onPictureInPictureModeChanged(isCurrentActivityInPictureInPictureMode, newConfig)
}
}

/**
* Called whenever PlayerView's layout changes.
*/
override fun onLayoutChange(
view: View?,
left: Int,
top: Int,
right: Int,
bottom: Int,
oldLeft: Int,
oldTop: Int,
oldRight: Int,
oldBottom: Int,
private fun onPictureInPictureModeChanged(
isInPictureInPictureMode: Boolean,
newConfig: Configuration,
) {
if (left != oldLeft || right != oldRight || top != oldTop || bottom != oldBottom) {
// Update source rect hint whenever the player's layout change
pictureInPictureHandler?.updateSourceRectHint()
val playerView = playerView ?: return
playerView.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig)
if (isInPictureInPictureMode) {
playerView.enterPictureInPicture()
} else {
playerView.exitPictureInPicture()
}
}

Expand Down Expand Up @@ -341,7 +304,7 @@ class RNPlayerView(
*/
data class RNPlayerViewConfigWrapper(
val playerViewConfig: PlayerViewConfig?,
val pictureInPictureConfig: RNPictureInPictureHandler.PictureInPictureConfig?,
val pictureInPictureConfig: PictureInPictureConfig?,
)

data class RNStyleConfigWrapper(
Expand All @@ -352,3 +315,8 @@ data class RNStyleConfigWrapper(
enum class UserInterfaceType {
Bitmovin, Subtitle
}

/**
* Configuration type for picture in picture behaviors.
*/
data class PictureInPictureConfig(val isEnabled: Boolean)
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ class RNPlayerViewManager(private val context: ReactApplicationContext) : Simple
val command = commandId?.toInt()?.toCommand() ?: throw IllegalArgumentException(
"The received command is not supported by the Bitmovin Player View",
)

fun <T> T?.require(): T = this ?: throw InvalidParameterException("Missing parameter")
when (command) {
Commands.ATTACH_PLAYER -> attachPlayer(view, args?.getString(1).require(), args?.getMap(2))
Expand All @@ -172,6 +173,7 @@ class RNPlayerViewManager(private val context: ReactApplicationContext) : Simple
view,
args?.getString(1).require(),
)

Commands.SET_FULLSCREEN -> setFullscreen(view, args?.getBoolean(1).require())
Commands.SET_SCALING_MODE -> setScalingMode(view, args?.getString(1).require())
Commands.SET_PICTURE_IN_PICTURE -> setPictureInPicture(view, args?.getBoolean(1).require())
Expand Down Expand Up @@ -246,9 +248,6 @@ class RNPlayerViewManager(private val context: ReactApplicationContext) : Simple
val playbackConfig = playerConfig?.getMap("playbackConfig")
val isPictureInPictureEnabled = view.config?.pictureInPictureConfig?.isEnabled == true ||
playbackConfig?.getBooleanOrNull("isPictureInPictureEnabled") == true
val pictureInPictureHandler = view.pictureInPictureHandler ?: RNPictureInPictureHandler(context)
view.pictureInPictureHandler = pictureInPictureHandler
view.pictureInPictureHandler?.isPictureInPictureEnabled = isPictureInPictureEnabled

val rnStyleConfigWrapper = playerConfig?.toRNStyleConfigWrapperFromPlayerConfig()
val configuredPlayerViewConfig = view.config?.playerViewConfig ?: PlayerViewConfig()
Expand All @@ -272,6 +271,9 @@ class RNPlayerViewManager(private val context: ReactApplicationContext) : Simple
LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT,
)
if (isPictureInPictureEnabled) {
playerView.setPictureInPictureHandler(RNPictureInPictureHandler(currentActivity, player))
}
view.setPlayerView(playerView)
attachCustomMessageHandlerBridge(view)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import com.bitmovin.player.api.ui.ScalingMode
import com.bitmovin.player.api.ui.StyleConfig
import com.bitmovin.player.api.ui.UiConfig
import com.bitmovin.player.reactnative.BitmovinCastManagerOptions
import com.bitmovin.player.reactnative.PictureInPictureConfig
import com.bitmovin.player.reactnative.RNBufferLevels
import com.bitmovin.player.reactnative.RNPlayerViewConfigWrapper
import com.bitmovin.player.reactnative.RNStyleConfigWrapper
Expand All @@ -70,7 +71,6 @@ import com.bitmovin.player.reactnative.extensions.withInt
import com.bitmovin.player.reactnative.extensions.withMap
import com.bitmovin.player.reactnative.extensions.withString
import com.bitmovin.player.reactnative.extensions.withStringArray
import com.bitmovin.player.reactnative.ui.RNPictureInPictureHandler.PictureInPictureConfig
import com.facebook.react.bridge.*
import java.util.UUID

Expand Down
Loading

0 comments on commit 6e7b42f

Please sign in to comment.