From 4fa0229ccbedf0dce9ee08e282832d29a24893bd Mon Sep 17 00:00:00 2001 From: Kai Date: Thu, 29 May 2025 12:38:37 -0500 Subject: [PATCH 1/5] implement the quick PiP feature https://developer.android.com/develop/ui/views/picture-in-picture#setautoenterenabled Changelog: changed --- .../mainactivity/main/VideoDetailFragment.kt | 18 +++---- .../mainactivity/main/VideoDetailView.kt | 51 +++++++++++++++---- 2 files changed, 50 insertions(+), 19 deletions(-) diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailFragment.kt index fd3319f0..e9170cf0 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailFragment.kt @@ -337,13 +337,6 @@ class VideoDetailFragment() : MainFragment() { closeVideoDetails(); }; it.onMaximize.subscribe { maximizeVideoDetail(it) }; - it.onPlayChanged.subscribe { - if(isInPictureInPicture) { - val params = _viewDetail?.getPictureInPictureParams(); - if (params != null) - activity?.setPictureInPictureParams(params); - } - }; it.onEnterPictureInPicture.subscribe { Logger.i(TAG, "onEnterPictureInPicture") isInPictureInPicture = true; @@ -446,9 +439,14 @@ class VideoDetailFragment() : MainFragment() { val viewDetail = _viewDetail; Logger.i(TAG, "onUserLeaveHint preventPictureInPicture=${viewDetail?.preventPictureInPicture} isCasting=${StateCasting.instance.isCasting} isBackgroundPictureInPicture=${Settings.instance.playback.isBackgroundPictureInPicture()} allowBackground=${viewDetail?.allowBackground}"); - if(viewDetail?.preventPictureInPicture == false && !StateCasting.instance.isCasting && Settings.instance.playback.isBackgroundPictureInPicture() && !viewDetail.allowBackground) { - _leavingPiP = false; + if (viewDetail === null) { + return + } + if (viewDetail.shouldEnterPictureInPicture) { + _leavingPiP = false + } + if(Build.VERSION.SDK_INT < Build.VERSION_CODES.S && viewDetail.preventPictureInPicture == false && !StateCasting.instance.isCasting && Settings.instance.playback.isBackgroundPictureInPicture() && !viewDetail.allowBackground) { val params = _viewDetail?.getPictureInPictureParams(); if(params != null) { Logger.i(TAG, "enterPictureInPictureMode") @@ -457,7 +455,7 @@ class VideoDetailFragment() : MainFragment() { } if (isFullscreen) { - viewDetail?.restoreBrightness() + viewDetail.restoreBrightness() } } diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt index fd2cb19b..e141bd49 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt @@ -13,6 +13,7 @@ import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.Drawable import android.graphics.drawable.Icon import android.net.Uri +import android.os.Build import android.support.v4.media.session.PlaybackStateCompat import android.text.Spanned import android.util.AttributeSet @@ -47,6 +48,7 @@ import com.futo.platformplayer.Settings import com.futo.platformplayer.UIDialogs import com.futo.platformplayer.UISlideOverlays import com.futo.platformplayer.activities.MainActivity +import com.futo.platformplayer.activities.SyncShowPairingCodeActivity.Companion.activity import com.futo.platformplayer.api.media.IPluginSourced import com.futo.platformplayer.api.media.LiveChatManager import com.futo.platformplayer.api.media.PlatformID @@ -240,7 +242,13 @@ class VideoDetailView : ConstraintLayout { private val _buttonPins: RoundButtonGroup; //private val _buttonMore: RoundButton; - var preventPictureInPicture: Boolean = false; + var preventPictureInPicture: Boolean = false + set(value) { + if (field != value) { + field = value + onShouldEnterPictureInPictureChanged.emit() + } + } private val _addCommentView: AddCommentView; private var _tabIndex: Int? = null; @@ -309,11 +317,24 @@ class VideoDetailView : ConstraintLayout { val onClose = Event0(); val onFullscreenChanged = Event1(); val onEnterPictureInPicture = Event0(); - val onPlayChanged = Event1(); val onVideoChanged = Event2() - var allowBackground : Boolean = false - private set; + var allowBackground: Boolean = false + private set(value) { + if (field != value) { + field = value + onShouldEnterPictureInPictureChanged.emit() + } + } + + val shouldEnterPictureInPicture: Boolean + get() = !preventPictureInPicture && + !StateCasting.instance.isCasting && + Settings.instance.playback.isBackgroundPictureInPicture() && + !allowBackground && + isPlaying + + val onShouldEnterPictureInPictureChanged = Event0(); val onTouchCancel = Event0(); private var _lastPositionSaveTime: Long = -1; @@ -603,6 +624,11 @@ class VideoDetailView : ConstraintLayout { } }; + onShouldEnterPictureInPictureChanged.subscribe { + val params = getPictureInPictureParams() + fragment.activity?.setPictureInPictureParams(params) + } + if (!isInEditMode) { StateCasting.instance.onActiveDeviceConnectionStateChanged.subscribe(this) { _, connectionState -> if (_onPauseCalled) { @@ -2332,7 +2358,7 @@ class VideoDetailView : ConstraintLayout { } isPlaying = playing; - onPlayChanged.emit(playing); + onShouldEnterPictureInPictureChanged.emit() updateTracker(lastPositionMilliseconds, playing, true); } @@ -2492,6 +2518,8 @@ class VideoDetailView : ConstraintLayout { if (changed) { stopAllGestures(); } + + onShouldEnterPictureInPictureChanged.emit() } fun isLandscapeVideo(): Boolean? { @@ -2759,11 +2787,16 @@ class VideoDetailView : ConstraintLayout { RemoteAction(Icon.createWithResource(context, R.drawable.ic_play_notif), context.getString(R.string.play), context.getString(R.string.resumes_the_video), MediaControlReceiver.getPlayIntent(context, 6)); val toBackgroundAction = RemoteAction(Icon.createWithResource(context, R.drawable.ic_screen_share), context.getString(R.string.background), context.getString(R.string.background_switch_audio), MediaControlReceiver.getToBackgroundIntent(context, 7)); - return PictureInPictureParams.Builder() - .setAspectRatio(Rational(videoSourceWidth, videoSourceHeight)) - .setSourceRectHint(r) + + val params = PictureInPictureParams.Builder() + .setAspectRatio(Rational(videoSourceWidth, videoSourceHeight)).setSourceRectHint(r) .setActions(listOf(toBackgroundAction, playpauseAction)) - .build(); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + params.setAutoEnterEnabled(shouldEnterPictureInPicture) + } + + return params.build() } //Other From 515c5e00e921fddfb3dda7f58aa4c9bd4efd9f34 Mon Sep 17 00:00:00 2001 From: Kai Date: Tue, 10 Jun 2025 15:27:14 -0500 Subject: [PATCH 2/5] Fix live stream PiP mode Prevent splash screen when opening PiP mode Changelog: changed --- .../mainactivity/main/VideoDetailView.kt | 16 ++++++++++++---- .../views/video/FutoVideoPlayer.kt | 7 +++++++ 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt index e141bd49..20045e7f 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt @@ -429,6 +429,9 @@ class VideoDetailView : ConstraintLayout { showChaptersUI(); }; + _layoutPlayerContainer.addOnLayoutChangeListener { v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom -> + onShouldEnterPictureInPictureChanged.emit() + } _buttonSubscribe.onSubscribed.subscribe { _slideUpOverlay = UISlideOverlays.showSubscriptionOptionsOverlay(it, _overlayContainer); @@ -2746,6 +2749,7 @@ class VideoDetailView : ConstraintLayout { _overlayContainer.removeAllViews(); _overlay_quality_selector?.hide(); + _container_content.visibility = GONE _player.fillHeight(false) _layoutPlayerContainer.setPadding(0, 0, 0, 0); @@ -2754,11 +2758,16 @@ class VideoDetailView : ConstraintLayout { Logger.i(TAG, "handleLeavePictureInPicture") if(!_player.isFullScreen) { + _container_content.visibility = VISIBLE _player.fitHeight(); _layoutPlayerContainer.setPadding(0, 0, 0, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 6.0f, Resources.getSystem().displayMetrics).toInt()); } else { _layoutPlayerContainer.setPadding(0, 0, 0, 0); } + + _layoutPlayerContainer.post { + onShouldEnterPictureInPictureChanged.emit() + } } fun getPictureInPictureParams() : PictureInPictureParams { var videoSourceWidth = _player.exoPlayer?.player?.videoSize?.width ?: 0; @@ -2778,9 +2787,7 @@ class VideoDetailView : ConstraintLayout { videoSourceWidth = 9; } - val r = Rect(); - _player.getGlobalVisibleRect(r); - r.right = r.right - _player.paddingEnd; + val r = _player.getVideoRect() val playpauseAction = if(_player.playing) RemoteAction(Icon.createWithResource(context, R.drawable.ic_pause_notif), context.getString(R.string.pause), context.getString(R.string.pauses_the_video), MediaControlReceiver.getPauseIntent(context, 5)); else @@ -2789,7 +2796,8 @@ class VideoDetailView : ConstraintLayout { val toBackgroundAction = RemoteAction(Icon.createWithResource(context, R.drawable.ic_screen_share), context.getString(R.string.background), context.getString(R.string.background_switch_audio), MediaControlReceiver.getToBackgroundIntent(context, 7)); val params = PictureInPictureParams.Builder() - .setAspectRatio(Rational(videoSourceWidth, videoSourceHeight)).setSourceRectHint(r) + .setAspectRatio(Rational(videoSourceWidth, videoSourceHeight)) + .setSourceRectHint(r) .setActions(listOf(toBackgroundAction, playpauseAction)) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { diff --git a/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayer.kt b/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayer.kt index e209f937..baf4ec27 100644 --- a/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayer.kt +++ b/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayer.kt @@ -4,6 +4,7 @@ import android.content.Context import android.content.Intent import android.content.res.Resources import android.graphics.Color +import android.graphics.Rect import android.graphics.drawable.Drawable import android.media.AudioManager import android.net.Uri @@ -464,6 +465,12 @@ class FutoVideoPlayer : FutoVideoPlayerBase { _control_autoplay_fullscreen.setColorFilter(ContextCompat.getColor(context, if (StatePlayer.instance.autoplay) com.futo.futopay.R.color.primary else R.color.white)) } + fun getVideoRect(): Rect { + val r = Rect() + _videoView.getGlobalVisibleRect(r) + return r + } + private fun setSystemBrightness(brightness: Float) { Log.i(TAG, "setSystemBrightness $brightness") if (android.provider.Settings.System.canWrite(context)) { From 98b62138866f7a00e23544de42d775dd12c064e9 Mon Sep 17 00:00:00 2001 From: Kai Date: Tue, 17 Jun 2025 15:26:27 -0500 Subject: [PATCH 3/5] remove layout changed listener Changelog: changed --- .../mainactivity/main/VideoDetailView.kt | 16 +++++++--------- .../views/video/FutoVideoPlayer.kt | 3 ++- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt index 45d43704..eebaac46 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt @@ -432,10 +432,6 @@ class VideoDetailView : ConstraintLayout { showChaptersUI(); }; - _layoutPlayerContainer.addOnLayoutChangeListener { v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom -> - onShouldEnterPictureInPictureChanged.emit() - } - _title.setOnLongClickListener { val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager; val clip = ClipData.newPlainText("Video Title", (it as TextView).text); @@ -1967,6 +1963,10 @@ class VideoDetailView : ConstraintLayout { videoTrackFormats.distinctBy { it.height }.sortedBy { it.height }, audioTrackFormats.distinctBy { it.bitrate }.sortedBy { it.bitrate }); } + + _layoutPlayerContainer.post { + onShouldEnterPictureInPictureChanged.emit() + } } private var _didTriggerDatasourceErrorCount = 0; @@ -2427,7 +2427,6 @@ class VideoDetailView : ConstraintLayout { } isPlaying = playing; - onShouldEnterPictureInPictureChanged.emit() updateTracker(lastPositionMilliseconds, playing, true); } @@ -2559,6 +2558,9 @@ class VideoDetailView : ConstraintLayout { setProgressBarOverlayed(false); } onFullscreenChanged.emit(fullscreen); + _layoutPlayerContainer.post { + onShouldEnterPictureInPictureChanged.emit() + } } private fun setCastEnabled(isCasting: Boolean) { @@ -2832,10 +2834,6 @@ class VideoDetailView : ConstraintLayout { } else { _layoutPlayerContainer.setPadding(0, 0, 0, 0); } - - _layoutPlayerContainer.post { - onShouldEnterPictureInPictureChanged.emit() - } } fun getPictureInPictureParams() : PictureInPictureParams { var videoSourceWidth = _player.exoPlayer?.player?.videoSize?.width ?: 0; diff --git a/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayer.kt b/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayer.kt index 44bc9303..e97a1dfb 100644 --- a/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayer.kt +++ b/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayer.kt @@ -484,7 +484,8 @@ class FutoVideoPlayer : FutoVideoPlayerBase { fun getVideoRect(): Rect { val r = Rect() - _videoView.getGlobalVisibleRect(r) + // this is the only way i could reliably get a reference to a view that matches perfectly with the video playback + _videoView.subtitleView?.getGlobalVisibleRect(r) return r } From c1e6e401ccb15151046f970b56ff69cfe348e80d Mon Sep 17 00:00:00 2001 From: Kai Date: Wed, 18 Jun 2025 09:36:31 -0500 Subject: [PATCH 4/5] formatting Changelog: changed --- .../fragment/mainactivity/main/VideoDetailView.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt index eebaac46..78f5729c 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt @@ -2559,7 +2559,7 @@ class VideoDetailView : ConstraintLayout { } onFullscreenChanged.emit(fullscreen); _layoutPlayerContainer.post { - onShouldEnterPictureInPictureChanged.emit() + onShouldEnterPictureInPictureChanged.emit() } } From a10bc8c7de54cc8c1dcceb7aa229e06a48b9c006 Mon Sep 17 00:00:00 2001 From: Kai Date: Wed, 18 Jun 2025 12:12:39 -0500 Subject: [PATCH 5/5] fix very wide screen videos enter PiP mode Changelog: changed --- .../fragment/mainactivity/main/VideoDetailView.kt | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt index 78f5729c..0e9f3c32 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt @@ -2844,16 +2844,23 @@ class VideoDetailView : ConstraintLayout { videoSourceHeight = 9; } val aspectRatio = videoSourceWidth.toDouble() / videoSourceHeight; + val r = _player.getVideoRect() if(aspectRatio > 2.38) { videoSourceWidth = 16; videoSourceHeight = 9; + + // shrink the left and right equally to get the rect to be 16 by 9 aspect ratio + // we don't want a picture in picture mode that's more squashed than 16 by 9 + val targetWidth = r.height() * 16 / 9 + val shrinkAmount = (r.width() - targetWidth) / 2 + r.left += shrinkAmount + r.right -= shrinkAmount } else if(aspectRatio < 0.43) { videoSourceHeight = 16; videoSourceWidth = 9; } - val r = _player.getVideoRect() val playpauseAction = if(_player.playing) RemoteAction(Icon.createWithResource(context, R.drawable.ic_pause_notif), context.getString(R.string.pause), context.getString(R.string.pauses_the_video), MediaControlReceiver.getPauseIntent(context, 5)); else