fix(android): fix bugs related Android PIP listeners (#4441)

* fix(android): fixed bug where listeners were not removed properly

* fix(android): fixes a bug that causes PIP to work when player is null

* refactor(android): call pipUnsubscriber without handler

* chore(android): apply lint
This commit is contained in:
YangJH
2025-03-06 20:24:59 +09:00
committed by GitHub
parent 13a5da8955
commit 82f5f3d21c
2 changed files with 23 additions and 19 deletions

View File

@@ -17,6 +17,7 @@ import androidx.annotation.ChecksSdkIntAtLeast
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.core.app.AppOpsManagerCompat import androidx.core.app.AppOpsManagerCompat
import androidx.core.app.PictureInPictureModeChangedInfo import androidx.core.app.PictureInPictureModeChangedInfo
import androidx.core.util.Consumer
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.media3.exoplayer.ExoPlayer import androidx.media3.exoplayer.ExoPlayer
import com.brentvatne.common.toolbox.DebugLog import com.brentvatne.common.toolbox.DebugLog
@@ -40,7 +41,7 @@ object PictureInPictureUtil {
fun addLifecycleEventListener(context: ThemedReactContext, view: ReactExoplayerView): Runnable { fun addLifecycleEventListener(context: ThemedReactContext, view: ReactExoplayerView): Runnable {
val activity = context.findActivity() val activity = context.findActivity()
val onPictureInPictureModeChanged: (info: PictureInPictureModeChangedInfo) -> Unit = { info: PictureInPictureModeChangedInfo -> val onPictureInPictureModeChanged = Consumer<PictureInPictureModeChangedInfo> { info ->
view.setIsInPictureInPicture(info.isInPictureInPictureMode) view.setIsInPictureInPicture(info.isInPictureInPictureMode)
if (!info.isInPictureInPictureMode && activity.lifecycle.currentState == Lifecycle.State.CREATED) { if (!info.isInPictureInPictureMode && activity.lifecycle.currentState == Lifecycle.State.CREATED) {
// when user click close button of PIP // when user click close button of PIP
@@ -48,7 +49,7 @@ object PictureInPictureUtil {
} }
} }
val onUserLeaveHintCallback = { val onUserLeaveHintCallback = Runnable {
if (view.enterPictureInPictureOnLeave) { if (view.enterPictureInPictureOnLeave) {
view.enterPictureInPictureMode() view.enterPictureInPictureMode()
} }
@@ -61,10 +62,10 @@ object PictureInPictureUtil {
} }
// @TODO convert to lambda when ReactExoplayerView migrated // @TODO convert to lambda when ReactExoplayerView migrated
return object : Runnable { return Runnable {
override fun run() { with(activity) {
context.findActivity().removeOnPictureInPictureModeChangedListener(onPictureInPictureModeChanged) removeOnPictureInPictureModeChangedListener(onPictureInPictureModeChanged)
context.findActivity().removeOnUserLeaveHintListener(onUserLeaveHintCallback) removeOnUserLeaveHintListener(onUserLeaveHintCallback)
} }
} }
} }

View File

@@ -224,9 +224,9 @@ public class ReactExoplayerView extends FrameLayout implements
private ArrayList<Integer> rootViewChildrenOriginalVisibility = new ArrayList<Integer>(); private ArrayList<Integer> rootViewChildrenOriginalVisibility = new ArrayList<Integer>();
/* /*
* When user is seeking first called is on onPositionDiscontinuity -> DISCONTINUITY_REASON_SEEK * When user is seeking first called is on onPositionDiscontinuity -> DISCONTINUITY_REASON_SEEK
* Then we set if to false when playback is back in onIsPlayingChanged -> true * Then we set if to false when playback is back in onIsPlayingChanged -> true
*/ */
private boolean isSeeking = false; private boolean isSeeking = false;
private long seekPosition = -1; private long seekPosition = -1;
@@ -332,7 +332,6 @@ public class ReactExoplayerView extends FrameLayout implements
audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
themedReactContext.addLifecycleEventListener(this); themedReactContext.addLifecycleEventListener(this);
pipListenerUnsubscribe = PictureInPictureUtil.addLifecycleEventListener(context, this);
audioBecomingNoisyReceiver = new AudioBecomingNoisyReceiver(themedReactContext); audioBecomingNoisyReceiver = new AudioBecomingNoisyReceiver(themedReactContext);
audioFocusChangeListener = new OnAudioFocusChangedListener(this, themedReactContext); audioFocusChangeListener = new OnAudioFocusChangedListener(this, themedReactContext);
pictureInPictureReceiver = new PictureInPictureReceiver(this, themedReactContext); pictureInPictureReceiver = new PictureInPictureReceiver(this, themedReactContext);
@@ -767,6 +766,8 @@ public class ReactExoplayerView extends FrameLayout implements
if (player == null) { if (player == null) {
// Initialize core configuration and listeners // Initialize core configuration and listeners
initializePlayerCore(self); initializePlayerCore(self);
pipListenerUnsubscribe = PictureInPictureUtil.addLifecycleEventListener(themedReactContext, this);
PictureInPictureUtil.applyAutoEnterEnabled(themedReactContext, pictureInPictureParamsBuilder, this.enterPictureInPictureOnLeave);
} }
if (!source.isLocalAssetFile() && !source.isAsset() && source.getBufferConfig().getCacheSize() > 0) { if (!source.isLocalAssetFile() && !source.isAsset() && source.getBufferConfig().getCacheSize() > 0) {
RNVSimpleCache.INSTANCE.setSimpleCache( RNVSimpleCache.INSTANCE.setSimpleCache(
@@ -935,7 +936,7 @@ public class ReactExoplayerView extends FrameLayout implements
: (e.reason == UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME : (e.reason == UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME
? R.string.error_drm_unsupported_scheme : R.string.error_drm_unknown); ? R.string.error_drm_unsupported_scheme : R.string.error_drm_unknown);
eventEmitter.onVideoError.invoke(getResources().getString(errorStringId), e, "3003"); eventEmitter.onVideoError.invoke(getResources().getString(errorStringId), e, "3003");
} }
} }
} }
return drmSessionManager; return drmSessionManager;
@@ -1270,12 +1271,12 @@ public class ReactExoplayerView extends FrameLayout implements
for (SideLoadedTextTrack track : source.getSideLoadedTextTracks().getTracks()) { for (SideLoadedTextTrack track : source.getSideLoadedTextTracks().getTracks()) {
MediaItem.SubtitleConfiguration subtitleConfiguration = new MediaItem.SubtitleConfiguration.Builder(track.getUri()) MediaItem.SubtitleConfiguration subtitleConfiguration = new MediaItem.SubtitleConfiguration.Builder(track.getUri())
.setMimeType(track.getType()) .setMimeType(track.getType())
.setLanguage(track.getLanguage()) .setLanguage(track.getLanguage())
.setSelectionFlags(C.SELECTION_FLAG_DEFAULT) .setSelectionFlags(C.SELECTION_FLAG_DEFAULT)
.setRoleFlags(C.ROLE_FLAG_SUBTITLE) .setRoleFlags(C.ROLE_FLAG_SUBTITLE)
.setLabel(track.getTitle()) .setLabel(track.getTitle())
.build(); .build();
subtitleConfigurations.add(subtitleConfiguration); subtitleConfigurations.add(subtitleConfiguration);
} }
@@ -1298,7 +1299,7 @@ public class ReactExoplayerView extends FrameLayout implements
player.removeListener(this); player.removeListener(this);
PictureInPictureUtil.applyAutoEnterEnabled(themedReactContext, pictureInPictureParamsBuilder, false); PictureInPictureUtil.applyAutoEnterEnabled(themedReactContext, pictureInPictureParamsBuilder, false);
if (pipListenerUnsubscribe != null) { if (pipListenerUnsubscribe != null) {
new Handler().post(pipListenerUnsubscribe); pipListenerUnsubscribe.run();
} }
trackSelector = null; trackSelector = null;
@@ -2245,7 +2246,9 @@ public class ReactExoplayerView extends FrameLayout implements
public void setEnterPictureInPictureOnLeave(boolean enterPictureInPictureOnLeave) { public void setEnterPictureInPictureOnLeave(boolean enterPictureInPictureOnLeave) {
this.enterPictureInPictureOnLeave = Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && enterPictureInPictureOnLeave; this.enterPictureInPictureOnLeave = Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && enterPictureInPictureOnLeave;
PictureInPictureUtil.applyAutoEnterEnabled(themedReactContext, pictureInPictureParamsBuilder, this.enterPictureInPictureOnLeave); if (player != null) {
PictureInPictureUtil.applyAutoEnterEnabled(themedReactContext, pictureInPictureParamsBuilder, this.enterPictureInPictureOnLeave);
}
} }
protected void setIsInPictureInPicture(boolean isInPictureInPicture) { protected void setIsInPictureInPicture(boolean isInPictureInPicture) {