Implementing Picture in Picture Mode
Overview
Both Android (starting from Android 8.0, API level 26) and iOS (starting from iOS 9 for iPads and iOS 14 for iPhones) support Picture-in-Picture (PiP) mode. PiP allows users to watch video content in a small, floating window that remains visible while they navigate between apps or browse content on the main screen. This feature enhances multitasking by keeping the video playback active while the user interacts with other apps.
On both platforms, the PiP window can be resized and moved to different corners of the screen, ensuring a seamless experience across different use cases. The system determines the default placement of the PiP window, though users can reposition it.
Android
Declare picture-in-picture support
Register your video activity in your manifest by setting android:supportsPictureInPicture
to true
. Also, specify that your activity handles layout configuration changes as by setting android:configChanges
as mentioned below so that your activity doesn't relaunch when layout changes occur during PiP mode transitions.
<activity
android:name=".MainActivity"
android:configChanges="keyboardHidden|orientation|screenSize|smallestScreenSize|screenLayout"
android:supportsPictureInPicture="true">
Picture in picture implementation
To enter picture-in-picture mode, an activity must call enterPictureInPictureMode(). For example, the following code switches an activity to PiP mode when a the user clicks the home or recent button:
package com.vdocipher.sample_flutter_app
import android.os.Build
import android.view.WindowManager.LayoutParams.FLAG_SECURE
import androidx.annotation.NonNull
import io.flutter.embedding.android.FlutterFragmentActivity
import io.flutter.embedding.engine.FlutterEngine
class MainActivity : FlutterFragmentActivity() {
//Add this to implement picture in picture mode. Adding this will take player in picture in picture mode
// when activity is about to go into the background as the result of user choice.
override fun onUserLeaveHint() {
super.onUserLeaveHint()
//Check if player is attached to the activity currently.
if (this.supportFragmentManager.findFragmentByTag(TAG) != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
enterPictureInPictureMode()
}
}
}
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
window.addFlags(FLAG_SECURE)
super.configureFlutterEngine(flutterEngine)
}
companion object {
private const val TAG = "VdoPlayerUIFragment"
}
}
Handle UI during picture-in-picture
When the video enters PiP mode, custom controls and non-player UI elements (defined in _nonPlayerUiContents()) should be hidden by checking the values provided by the onPictureInPictureModeChanged
callback as the player's state changes. Please refer to the implementation below for a better understanding:
class VdoPlaybackViewState extends State<VdoPlaybackView> {
VdoPlayerController? _controller;
final ValueNotifier<bool> _isFullScreen = ValueNotifier(false);
final ValueNotifier<bool> _isInPictureInPictureMode = ValueNotifier(false);
Widget build(BuildContext context) {
return Scaffold(
body: Column(crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Flexible(
child: Stack(
alignment: Alignment.center,
children: [
SizedBox(
width: _getPlayerWidth(),
height: _getPlayerHeight(),
child: VdoPlayer(
embedInfo: SAMPLE_1,
onFullscreenChange: _onFullscreenChange,
onPlayerCreated: _onPlayerCreated,
onPictureInPictureModeChanged: _onPictureInPictureModeChanged,
onError: _onVdoError,
controls: false, // when using custom controls
),
),
if (_controller == null || widget.controls || _isInPictureInPictureMode.value)
const SizedBox.shrink()
else
SizedBox(
width: _getPlayerWidth(),
height: _getPlayerHeight(),
child: VdoCustomControllerView(
controller: _controller,
onError: _onVdoError,
onFullscreenChange: _onFullscreenChange,
),
)
],
)),
// Hide any non-player UI elements when entering PiP mode for a cleaner experience.
if (_isInPictureInPictureMode.value || _isFullScreen.value)
const SizedBox.shrink()
else
_nonPlayerUiContents()
]));
}
_onFullscreenChange(isFullscreen) {
setState(() {
_isFullScreen.value = isFullscreen;
});
}
_onPictureInPictureModeChanged(isInPictureInPictureMode) {
setState(() {
_isInPictureInPictureMode.value = isInPictureInPictureMode;
});
}
}
iOS
Picture in picture implementation
Enable Picture-in-Picture (PiP) mode in an iOS app using Xcode, as mentioned below:
Enable PiP capability: Open your project in Xcode and go to the "Signing & Capabilities" tab of your target. Click on the "+" button to add a new capability, then search for and add "Background Modes". In the list of background modes, check the option labeled "Audio, AirPlay, and Picture in Picture".