Skip to content

Commit

Permalink
Merge pull request #377 from kot331107/Android-implement-transitionin…
Browse files Browse the repository at this point in the history
…g-to-PiP

Feature/ Android: implement a special event for early transitioning to PiP (on animation start)
  • Loading branch information
tvanlaerhoven authored Oct 23, 2024
2 parents 781bc4d + 8d28b6a commit 4fde26e
Show file tree
Hide file tree
Showing 7 changed files with 83 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ class PresentationManager(

var currentPresentationMode: PresentationMode = PresentationMode.INLINE
private set
var currentPresentationModeChangeContext: PresentationModeChangeContext? = null
private set

var pipConfig: PipConfig = PipConfig()

Expand All @@ -54,12 +56,14 @@ class PresentationManager(
}
onPictureInPictureModeChanged = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
// Dispatch event on every PiP mode change
val transitioningToPip = intent
?.getBooleanExtra("isTransitioningToPip", false) ?: false
val inPip = intent?.getBooleanExtra("isInPictureInPictureMode", false) ?: false
if (inPip) {
onEnterPip()
} else {
onExitPip()
// Dispatch event on every PiP mode change
when {
transitioningToPip -> onEnterPip(true)
inPip -> onEnterPip()
else -> onExitPip()
}
}
}
Expand Down Expand Up @@ -104,9 +108,11 @@ class PresentationManager(
PresentationMode.INLINE -> {
setFullscreen(false)
}

PresentationMode.FULLSCREEN -> {
setFullscreen(true)
}

PresentationMode.PICTURE_IN_PICTURE -> {
setFullscreen(false)
enterPip()
Expand Down Expand Up @@ -144,8 +150,13 @@ class PresentationManager(
}
}

private fun onEnterPip() {
updatePresentationMode(PresentationMode.PICTURE_IN_PICTURE)
private fun onEnterPip(transitioningToPip: Boolean = false) {
updatePresentationMode(
PresentationMode.PICTURE_IN_PICTURE,
if (transitioningToPip)
PresentationModeChangeContext(PresentationModeChangePipContext.TRANSITIONING_TO_PIP)
else null
)
}

private fun onExitPip() {
Expand Down Expand Up @@ -241,11 +252,14 @@ class PresentationManager(
presentationMode: PresentationMode,
context: PresentationModeChangeContext? = null
) {
if (presentationMode == currentPresentationMode) {
if (presentationMode == currentPresentationMode &&
context == currentPresentationModeChangeContext
) {
return
}
val prevPresentationMode = currentPresentationMode
currentPresentationMode = presentationMode
currentPresentationModeChangeContext = context
eventEmitter.emitPresentationModeChange(presentationMode, prevPresentationMode, context)

// Resume playing when going to PiP and player was playing
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ enum class PresentationModeChangePipContext {
CLOSED,

// The PiP window transitioned back into the app.
RESTORED
RESTORED,

// The app transitioning to PiP frame
TRANSITIONING_TO_PIP
}

data class PresentationModeChangeContext(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ class PayloadBuilder {
val contextPayload = Arguments.createMap()
context.pip?.let { pipCtx ->
contextPayload.putString(EVENT_PROP_PIP, when (pipCtx) {
PresentationModeChangePipContext.TRANSITIONING_TO_PIP -> "transitioning-to-pip"
PresentationModeChangePipContext.RESTORED -> "restored"
else -> "closed"
})
Expand Down
21 changes: 21 additions & 0 deletions doc/pip.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,27 @@ override fun onPictureInPictureModeChanged(
}
```

### Enabling early transitioning to PiP event

You might want to enable [transitioning to PiP](<https://developer.android.com/reference/android/app/PictureInPictureUiState#isTransitioningToPip()>) event for Android 15+ (API 35+). To enable it make sure that
the compile SDK version is set to 35 in your build.gradle `compileSdkVersion = 35`. Also the appropriate intent should be sent from the `MainActivity` to let react-native know when the app starts the PiP animation:

```kotlin
override fun onPictureInPictureUiStateChanged(pipState: PictureInPictureUiState) {
super.onPictureInPictureUiStateChanged(pipState)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM &&
pipState.isTransitioningToPip
) {
Intent("onPictureInPictureModeChanged").also {
it.putExtra("isTransitioningToPip", true)
sendBroadcast(it)
}
}
}
```

Then it should set the `context?.pip` to `PresentationModeChangePipContext.TRANSITIONING_TO_PIP` in the event `PlayerEventType.PRESENTATIONMODE_CHANGE`.

### PiP controls

The PiP window will show the default controls to configure, maximize and close the PiP window.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.reactnativetheoplayer

import android.app.PictureInPictureUiState
import android.content.Intent
import android.content.res.Configuration
import android.media.AudioManager
Expand Down Expand Up @@ -39,11 +40,18 @@ open class MainActivity : ReactActivity() {
override fun createReactActivityDelegate(): ReactActivityDelegate =
DefaultReactActivityDelegate(this, mainComponentName, fabricEnabled)

/**
* Called as part of the activity lifecycle when an activity is about to go into the background
* as the result of user choice.
*/
public override fun onUserLeaveHint() {
this.sendBroadcast(Intent("onUserLeaveHint"))
super.onUserLeaveHint()
}

/**
* Called by the system when the activity changes to and from picture-in-picture mode.
*/
override fun onPictureInPictureModeChanged(
isInPictureInPictureMode: Boolean,
newConfig: Configuration
Expand All @@ -55,4 +63,21 @@ open class MainActivity : ReactActivity() {
intent.putExtra("isInPictureInPictureMode", isInPictureInPictureMode)
this.sendBroadcast(intent)
}

/**
* Called by the system when the activity is in PiP and has state changes. Compare to
* onPictureInPictureModeChanged, which is only called when PiP mode changes (meaning, enters
* or exits PiP), this can be called at any time while the activity is in PiP mode.
*/
override fun onPictureInPictureUiStateChanged(pipState: PictureInPictureUiState) {
super.onPictureInPictureUiStateChanged(pipState)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM &&
pipState.isTransitioningToPip
) {
Intent("onPictureInPictureModeChanged").also {
it.putExtra("isTransitioningToPip", true)
sendBroadcast(it)
}
}
}
}
4 changes: 2 additions & 2 deletions example/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ buildscript {
// React Native 0.74+ needs minSdkVersion 23+
// supportsPictureInPicture and networkSecurityConfig need minSdkVersion 24+
minSdkVersion = 24
compileSdkVersion = 34
targetSdkVersion = 34
compileSdkVersion = 35
targetSdkVersion = 35
ndkVersion = "26.1.10909125"
castFrameworkVersion = "21.4.0"
kotlinVersion = "1.9.22"
Expand Down
8 changes: 8 additions & 0 deletions src/api/event/PlayerEvent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,14 @@ export enum PresentationModeChangePipContext {
* The PiP window was restored/maximized.
*/
RESTORED = 'restored',

/**
* The app is transitioning to the PiP frame.
*
* @remarks
* <br/> - This property only applies to Android platforms.
*/
TRANSITIONING_TO_PIP = 'transitioning-to-pip',
}

export interface PresentationModeChangeContext {
Expand Down

0 comments on commit 4fde26e

Please sign in to comment.