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

feat: implement enterPictureInPictureOnLeave prop for both platform(Android, iOS) #3385

Open
wants to merge 76 commits into
base: master
Choose a base branch
from

Conversation

YangJonghun
Copy link
Collaborator

@YangJonghun YangJonghun commented Nov 26, 2023

Update the changelog

feat(android): implement pictureInPicture prop and onPictureInPictureStatusChanged event

Describe the changes

  • (Breaking Change) Remove pictureInPicture boolean prop (iOS)

  • Add enterPictureInPictureOnLeave boolean prop (Android, iOS)

  • Add enterPictureInPicture method (Android, iOS)

  • Add exitPictureInPicture method (Android, iOS)

  • Add onPictureInPictureStatusChanged event listener (Android)

  • Resolve issue React Native Video Picture In Picture for Android #2621

memo)
As I know Activity's onUserLeaveHint is acceptable lifecycle method for enter PIP. but it's impossible to detect that method within this library. so, I implement enterPicturInPicture within onHostPause.
If someone doesn't like this approach, please leave comment in this PR, I'll give you intent style code.

@freeboub
Copy link
Collaborator

freeboub commented Nov 26, 2023

Thank you for the PR, can you open a PR in draft, it will allow to comment on it.

public void onHostPause() {
isInBackground = true;
if (pictureInPictureEnabled) {
enterPictureInPictureMode();
return;
}
You cannot do like this, when another application will go over the app, it will automatically switch to Pip ...

@freeboub
Copy link
Collaborator

I don't understand why you use a dedicated fragment : ReactExoplayerFragment can you explain why you had to use it please ? (point to doc or other sample is enough)

@freeboub
Copy link
Collaborator

can you enable pip in sample to be able to test easily please

@YangJonghun
Copy link
Collaborator Author

YangJonghun commented Nov 26, 2023

can you enable pip in sample to be able to test easily please

      <Video
        style={{width: '100%', height: 300, backgroundColor: 'black'}}
        resizeMode={ResizeMode.CONTAIN}
        source={{
          uri: 'https://demo.unified-streaming.com/k8s/features/stable/video/tears-of-steel/tears-of-steel-en.ism/.mpd',
        }}
        selectedTextTrack={{
          type: SelectedTrackType.INDEX,
          value: 0,
        }}
        fullscreen={false}
        controls
        pictureInPicture
        playWhenInactive
        playInBackground={false}
        debug={{enable: true, thread: true}}
        onPictureInPictureStatusChanged={({isActive}) => {
          console.log(isActive, 'isActive');
        }}
      />

@YangJonghun
Copy link
Collaborator Author

YangJonghun commented Nov 26, 2023

@freeboub

I don't understand why you use a dedicated fragment : ReactExoplayerFragment can you explain why you had to use it please ? (point to doc or other sample is enough)

To implement PIP, we need to use the onUserLeaveHint and onPictureInPictureModeChanged methods of Activity. However, this requires forcing the user to modify their native code.
To avoid this, I tricky replaced onUserLeaveHint with onPause in the Fragment and onPictureInPictureModeChanged with Fragment's onPictureInPictureModeChanged in the Fragment.

@freeboub
Copy link
Collaborator

can you enable pip in sample to be able to test easily please

      <Video
        style={{width: '100%', height: 300, backgroundColor: 'black'}}
        resizeMode={ResizeMode.CONTAIN}
        source={{
          uri: 'https://demo.unified-streaming.com/k8s/features/stable/video/tears-of-steel/tears-of-steel-en.ism/.mpd',
        }}
        selectedTextTrack={{
          type: SelectedTrackType.INDEX,
          value: 0,
        }}
        fullscreen={false}
        controls
        pictureInPicture
        playWhenInactive
        playInBackground={false}
        debug={{enable: true, thread: true}}
        onPictureInPictureStatusChanged={({isActive}) => {
          console.log(isActive, 'isActive');
        }}
      />

Ok, but will it work without native controls ?

@YangJonghun
Copy link
Collaborator Author

can you enable pip in sample to be able to test easily please

      <Video
        style={{width: '100%', height: 300, backgroundColor: 'black'}}
        resizeMode={ResizeMode.CONTAIN}
        source={{
          uri: 'https://demo.unified-streaming.com/k8s/features/stable/video/tears-of-steel/tears-of-steel-en.ism/.mpd',
        }}
        selectedTextTrack={{
          type: SelectedTrackType.INDEX,
          value: 0,
        }}
        fullscreen={false}
        controls
        pictureInPicture
        playWhenInactive
        playInBackground={false}
        debug={{enable: true, thread: true}}
        onPictureInPictureStatusChanged={({isActive}) => {
          console.log(isActive, 'isActive');
        }}
      />

Ok, but will it work without native controls ?

Yes, it's same like iOS and there are no restrictions.

@YangJonghun
Copy link
Collaborator Author

YangJonghun commented Nov 26, 2023

Thank you for the PR, can you open a PR in draft, it will allow to comment on it.

public void onHostPause() { isInBackground = true; if (pictureInPictureEnabled) { enterPictureInPictureMode(); return; } You cannot do like this, when another application will go over the app, it will automatically switch to Pip ...

I'm not sure, but as I know onUserLeaveHint is also called when another app is turned on.
Actually I wanted to detect and implement the home key event, but the API was deprecated and I replaced it with onPause.

@YangJonghun
Copy link
Collaborator Author

@nick-mccomb-easygo
Unfortunately, I've been so busy lately that I haven't been able to keep up with this work, but I'm going to finish it.
You're welcome to implement it if you'd like.

@YangJonghun YangJonghun marked this pull request as ready for review January 19, 2024 17:40
@YangJonghun
Copy link
Collaborator Author

YangJonghun commented Jan 19, 2024

@freeboub @KrzysztofMoch

Hi. I'd like to let you know this PR is ready for review 🙌
please review when you possible. :)

FYI,
this PR depends on PR #3489
If doesn't merge, PIP's action button doesn't work properly

I think this code is too mush complicate to understand.
but I couldn't rewrite FullScreenPlayerView code in this PR.
It would be nice if you(or someone) could refactor it later.

Copy link
Member

@KrzysztofMoch KrzysztofMoch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 👍, thank you for the PR!
Tested it on Android 14, and 10 (simulators) and it worked for me. Will test it on device later.
Lets @freeboub review and for me we can merge

@tiplefbxx
Copy link

tiplefbxx commented Sep 9, 2024

I have no any idea how to reproduce it, but it might help you
@YangJonghun

Positivo TL10 (TL10) Android 13 (SDK 33)
Type
java.lang.IndexOutOfBoundsException
Exception java.lang.IndexOutOfBoundsException: Index 0 out of bounds for length 0
  at jdk.internal.util.Preconditions.outOfBounds (Preconditions.java:64)
  at jdk.internal.util.Preconditions.outOfBoundsCheckIndex (Preconditions.java:70)
  at jdk.internal.util.Preconditions.checkIndex (Preconditions.java:266)
  at java.util.Objects.checkIndex (Objects.java:359)
  at java.util.ArrayList.get (ArrayList.java:434)
  at com.brentvatne.exoplayer.ReactExoplayerView.setIsInPictureInPicture (ReactExoplayerView.java:2192)
  at com.brentvatne.exoplayer.ReactExoplayerFragment.onPictureInPictureModeChanged (ReactExoplayerFragment.kt:35)
  at androidx.fragment.app.Fragment.performPictureInPictureModeChanged (Fragment.java:3104)
  at androidx.fragment.app.FragmentManager.dispatchPictureInPictureModeChanged (FragmentManager.java:2863)
  at androidx.fragment.app.FragmentController.dispatchPictureInPictureModeChanged (FragmentController.java:367)
  at androidx.fragment.app.FragmentActivity.onPictureInPictureModeChanged (FragmentActivity.java:227)
  at android.app.Activity.onPictureInPictureModeChanged (Activity.java:2828)
  at androidx.activity.ComponentActivity.onPictureInPictureModeChanged (ComponentActivity.java:1064)
  at android.app.Activity.dispatchPictureInPictureModeChanged (Activity.java:8619)
  at android.app.ActivityThread.handleWindowingModeChangeIfNeeded (ActivityThread.java:6181)
  at android.app.ActivityThread.performActivityConfigurationChanged (ActivityThread.java:6051)
  at android.app.ActivityThread.performConfigurationChangedForActivity (ActivityThread.java:6028)
  at android.app.ActivityThread.handleActivityConfigurationChanged (ActivityThread.java:6314)
  at android.app.servertransaction.ActivityConfigurationChangeItem.execute (ActivityConfigurationChangeItem.java:53)
  at android.app.servertransaction.ActivityTransactionItem.execute (ActivityTransactionItem.java:45)
  at android.app.servertransaction.TransactionExecutor.executeCallbacks (TransactionExecutor.java:137)
  at android.app.servertransaction.TransactionExecutor.execute (TransactionExecutor.java:97)
  at android.app.ActivityThread$H.handleMessage (ActivityThread.java:2393)
  at android.os.Handler.dispatchMessage (Handler.java:106)
  at android.os.Looper.loopOnce (Looper.java:201)
  at android.os.Looper.loop (Looper.java:288)
  at android.app.ActivityThread.main (ActivityThread.java:8100)
  at java.lang.reflect.Method.invoke
  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:703)
  at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:911)

@YangJonghun
Copy link
Collaborator Author

@tiplefbxx
Thanks! Error you mentioned occurs in code below.

https://github.com/TheWidlarzGroup/react-native-video/pull/3385/files#diff-a35ae7e6ae6efee3b22522a836bcbcdd9d59a214f087df454e4ccbb8cd5c0e76R2177-R2209

To be precise, it seems to happen in the part where you call rootViewChildrenOriginalVisibility.get(i) when isInPictureInPicture is false.
The role of the code above is to save the visibility of all views except Player when activate PIP and make them invisible, and then restore the visibility of the view normally after PIP close.
It seems that the value of rootView.getChildCount() is different when PIP is on and off, which is causing the error, but debugging it would require reproducible code.🥲

@tiplefbxx
Copy link

tiplefbxx commented Sep 10, 2024

@YangJonghun

Actually I was able to reproduce the crash, not sure that exactly this one, as it not thrown any errors
Steps to reproduce

  1. Open any media in PiP
  2. Being in PiP navigate to the same screen with another props (autoplay next video feature)
  3. PiP started playing new media, but sometimes you can see black '5-10px' line in the top of PiP View
  4. Press expand or close button from PiP View
  5. Instantly crashed without any error

@tiplefbxx
Copy link

@YangJonghun
upd: I checked from logcat, that is error that a pasted above

@YangJonghun
Copy link
Collaborator Author

@tiplefbxx
Thank you for letting me know how to reproduce it. I'll check it out when I have time. 👍
And If you have a little more time, please provide me any small reproduction code🙏 it would be very helpful to me

@tiplefbxx
Copy link

tiplefbxx commented Sep 11, 2024

@YangJonghun Sorry cannot paste simple example in nearest time, but what I found
It seems the most close way to reproduce the issue, is mount and then unmount player while you in PiP

  1. I tested case when I changing source while playing in PiP and it works fine
  2. case when player component mounted and then unmounted while in PiP makes that error

@tiplefbxx
Copy link

tiplefbxx commented Sep 15, 2024

@YangJonghun
Here is simply playground for testing https://github.com/tiplefbxx/React-native-video-PiP-crash
BTW: Don't forget to update doc! It needs to add android:supportsPictureInPicture="true" to main activity, otherwise it will not work

@tiplefbxx
Copy link

It seems one more issue for ios
When you change source being in background with PiP, it will close the pip and do not notify onPictureInPictureStatusChanged, closing manually also do not help videoRef.current?.exitPictureInPicture

@YangJonghun
Copy link
Collaborator Author

@tiplefbxx
Thank you for providing sample code.
Previous code does not assume source or mount changes from entering PIP to leaving PIP, as it is common to utilize the existing view when playing other videos.

Fixed Code has been applied with 362459d to force PIP to exit when the video component is unmounted within PIP

BTW: Don't forget to update doc! It needs to add android:supportsPictureInPicture="true" to main activity, otherwise it will not work

I've already stated this in this PR. Thanks for letting me know. :)

It seems one more issue for ios
When you change source being in background with PiP, it will close the pip and do not notify onPictureInPictureStatusChanged, closing manually also do not help videoRef.current?.exitPictureInPicture

onPictureInPictureStatusChanged is an API provided by iOS native, and it's not clear what the intent of the prop is. To implement the feature, I have to forced call onPictureInPictureStatusChanged for JS, which may be outside the intent of the existing prop.🤔

@tiplefbxx
Copy link

Might this ios thing is not related to you pr, but if you started changes for PiP it could be interesting for you
Not crucial issue but
When you go to PiP and return to foreground by clicking app icon from HomeScreen it will return to app, but the PiP will be not resolved, player will be minimised in PiP, so it needs to manually call method to close PiP

It's might be not issue at all as PiP usually works for ios in that way always, but could be surprising behaviour for developers who are used to see closed pip after returning to foreground as it usually works for cross button and app selector

@YangJonghun
Copy link
Collaborator Author

@tiplefbxx
Thanks for suggestion, but for now I don't want this PR to grow any bigger, so I'll figure out it when this PR is merged🥲

@KrzysztofMoch
Copy link
Member

Anyone interested in this feature, please see this discussion

@freeboub
Copy link
Collaborator

I just fix conflict. The only comments I have are in the discussion

@KrzysztofMoch
Copy link
Member

I have created 6.7.0-rc.0 release that contain this PR - as we will get feedback and do some changes I will push more RCs. For now let's see how it will go 🙌

@KrzysztofMoch
Copy link
Member

I have released next rc version 6.8.0-rc.0 - if there will be no reported issues, this PR should be merged 🙌

@tungmin97
Copy link

Hi @YangJonghun, I'm encountering the same issue as @tiplefbxx mentioned where my app would always crash upon return from PIP.

For a little bit more context, I have a separated Video component outside my navigator stack, and in my testing unload the video component below when having a floating player eliminates this behavior.

Your app just crashed. See the error below.
java.lang.IndexOutOfBoundsException: Index: 1, Size: 1
  java.util.ArrayList.get(ArrayList.java:437)
  com.brentvatne.exoplayer.ReactExoplayerView.setIsInPictureInPicture(ReactExoplayerView.java:2305)
  com.brentvatne.exoplayer.ReactExoplayerFragment.onPictureInPictureModeChanged(ReactExoplayerFragment.kt:35)
  androidx.fragment.app.Fragment.performPictureInPictureModeChanged(Fragment.java:3221)
  androidx.fragment.app.FragmentManager.dispatchPictureInPictureModeChanged(FragmentManager.java:3017)
  androidx.fragment.app.FragmentManager.lambda$new$3$androidx-fragment-app-FragmentManager(FragmentManager.java:468)
  androidx.fragment.app.FragmentManager$$ExternalSyntheticLambda3.accept(Unknown Source:4)
  androidx.activity.ComponentActivity.onPictureInPictureModeChanged(ComponentActivity.java:1097)
  android.app.Activity.dispatchPictureInPictureModeChanged(Unknown Source:23)
  android.app.ActivityThread.handleWindowingModeChangeIfNeeded(Unknown Source:40)
  android.app.ActivityThread.performActivityConfigurationChanged(Unknown Source:12)
  android.app.ActivityThread.performConfigurationChangedForActivity(Unknown Source:28)
  android.app.ActivityThread.handleActivityConfigurationChanged(Unknown Source:273)
  android.app.ActivityThread$ActivityClientRecord$1.onConfigurationChanged(Unknown Source:14)
  android.view.ViewRootImpl.performConfigurationChange(Unknown Source:81)
  android.view.ViewRootImpl.handleResized(Unknown Source:112)
  android.view.ViewRootImpl.-$$Nest$mhandleResized(Unknown Source:0)
  android.view.ViewRootImpl$ViewRootHandler.handleMessageImpl(Unknown Source:658)
  android.view.ViewRootImpl$ViewRootHandler.handleMessage(Unknown Source:15)
  android.os.Handler.dispatchMessage(Unknown Source:19)
  android.os.Looper.loopOnce(Unknown Source:182)
  android.os.Looper.loop(Unknown Source:82)
  android.app.ActivityThread.main(Unknown Source:123)
  java.lang.reflect.Method.invoke(Native Method)
  com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(Unknown Source:11)
  com.android.internal.os.ZygoteInit.main(Unknown Source:312)

@YangJonghun
Copy link
Collaborator Author

@tungmin97
Thanks for reporting me. but it's hard to me to reproduce what you're describing🥲
If you can, please provide me any small reproduction code🙏

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants